在 Cimg 上创建的图像在使用 GDI+ 保存时在 pdf 上显示不同,而在 pdf 上使用 jagPDF 创建

image created on Cimg display different on a pdf when saved using GDI+ with pdf created with jagPDF

我需要做的很简单,我需要使用 CIMG 绘制一个矢量,然后将图形保存为 jpg 并使用 JAPDF 将 jpg 添加到 PDF 文档。为了将 CIMG 保存为 JPG,该程序使用名为 Image Magick.

的外部程序

我想避免使用该程序,而是使用 GDI+,方法是先将 CIMG 保存为 BMP(它本身就是这样做的),然后从 bmp 保存 jpg。

MCVE 程序如下所示

#include "CImg.h"
#include <jagpdf/api.h>
#include <vector>

using namespace jag;
using namespace cimg_library;

int main(int argc, char** const argv)
{

    const float x0 = 0;
    const float x1 = 9;
    const int resolution = 5000;

    // Create plot data.
    CImg<double> values(1, resolution, 1, 1, 0);

    const unsigned int r = resolution - 1;

    for (int i1 = 0; i1 < resolution; ++i1)
    {
        double xtime = x0 + i1*(x1 - x0) / r;
        values(0, i1) = 2 * sin(xtime);
    }

    CImg<unsigned char> graph;
    graph.assign(750, 240, 1, 3, 255);

    static const unsigned char black[] = { 0, 0, 0 }, white[] = { 255, 255, 255 };
    static const unsigned char red[] = { 255, 200, 200 }, bred[] = { 255, 0, 0 };

    graph.draw_grid(6, 6, 0, 0, false, true, red, 10.0f, 0xFFFFFFFF, 0xFFFFFFFF);
    graph.draw_grid(30, 30, 0, 0, false, true, bred, 10.0f, 0xFFFFFFFF, 0xFFFFFFFF);

    graph.draw_graph(values, black, 1, 1, 1, 2, -2, 0xFFFFFFFF);;

    //////////////Method 1: Using Image Magick////////////////
    graph.save_jpeg("plot2.jpg");

    pdf::Document doc(pdf::create_file("report.pdf"));
    doc.page_start(848.68, 597.6);
    pdf::Image imag2 = doc.image_load_file("plot2.jpg");
    doc.page().canvas().image(imag2, 50, 50);
    doc.page_end();
    doc.finalize();
    //////////////Method 2: Using GDI+////////////////
    graph.save("plot.bmp");
    SaveFile();
    pdf::Document doc2(pdf::create_file("report2.pdf"));
    doc2.page_start(848.68, 597.6);
    pdf::Image imag = doc2.image_load_file("plot.jpg");
    doc2.page().canvas().image(imag, 50, 50);
    doc2.page_end();
    doc2.finalize();

    return 0;
}

SaveFile() 是使用 GDI+ 将 plot.bmp 转换为 plot.jpg

的以下函数
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
#include "GdiplusHelperFunctions.h"


#pragma comment (lib,"Gdiplus.lib")

VOID SaveFile()
{
    // Initialize GDI+.
    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    CLSID   encoderClsid;
    Status  stat;
    Image*   image = new Gdiplus::Image(L"plot.bmp");

    // Get the CLSID of the PNG encoder.
    GetEncoderClsid(L"image/jpeg", &encoderClsid);

    stat = image->Save(L"plot.jpg", &encoderClsid, NULL);

    if (stat == Ok)
        printf("plot.jpg was saved successfully\n");
    else
        printf("Failure: stat = %d\n", stat);

    delete image;
    GdiplusShutdown(gdiplusToken);
    }

两种方法都保存在属性中似乎具有相同大小的 jpg,但第一种方法将图像正确地放入 pdf 中,而第二种方法将一个巨大的图像放入 pdf 中,即使它们应该具有相同的大小。我该如何解决这个问题?

附上报告 1 和报告 2 的截图

解决方案

根据您的建议,我能够修改 SaveFile 函数以便能够控制 de DPI,我 post 新代码以备不时之需。

VOID SaveFile()
{
    // Initialize GDI+.
    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    CLSID   encoderClsid;
    Status  stat;
    EncoderParameters encoderParameters;
    ULONG    quality;

    Gdiplus::Bitmap*   bitmap = new Gdiplus::Bitmap(L"plot.bmp");
    Gdiplus::REAL dpi = 96;
    bitmap->SetResolution(dpi,dpi);


    // Get the CLSID of the PNG encoder.
    GetEncoderClsid(L"image/jpeg", &encoderClsid);

    encoderParameters.Count = 1;
    encoderParameters.Parameter[0].Guid = EncoderQuality;
    encoderParameters.Parameter[0].Type = EncoderParameterValueTypeLong;
    encoderParameters.Parameter[0].NumberOfValues = 1;

   quality = 100;
   encoderParameters.Parameter[0].Value = &quality;

   stat = bitmap->Save(L"plot.jpg", &encoderClsid, &encoderParameters);


   if (stat == Ok)
        printf("plot.jpg was saved successfully\n");
   else
        printf("Failure: stat = %d\n", stat);

   delete bitmap;
   GdiplusShutdown(gdiplusToken);
   return;
}

我猜想 ImageMagick 包含一些过滤图像以适应 canvas 的额外功能。聪明人。 在导出为 JPEG 之前,我会尝试调整图像大小。你可以试试 this guide。它基本上说你可以调整 bmp 的大小(在这个例子中它检查 w/h 比率但是......)。目标应该是指定 canvas 所需的大小。

Gdiplus::Bitmap* GDIPlusImageProcessor::ResizeClone(Bitmap *bmp, INT width, INT height)
{
    UINT o_height = bmp->GetHeight();
    UINT o_width = bmp->GetWidth();
    INT n_width = width;
    INT n_height = height;
    double ratio = ((double)o_width) / ((double)o_height);
    if (o_width > o_height) {
        // Resize down by width
        n_height = static_cast<UINT>(((double)n_width) / ratio);
    } else {
        n_width = static_cast<UINT>(n_height * ratio);
    }
    Gdiplus::Bitmap* newBitmap = new Gdiplus::Bitmap(n_width, n_height, bmp->GetPixelFormat());
    Gdiplus::Graphics graphics(newBitmap);
    graphics.DrawImage(bmp, 0, 0, n_width, n_height);
    return newBitmap;
}

然后,使用编码器保存它。此外,您还想检查是否需要使用编码器参数设置生成的 JPEG 的质量,如官方 documentation.

中所示
// Get the CLSID of the JPEG encoder.
   GetEncoderClsid(L"image/jpeg", &encoderClsid);

   // Before we call Image::Save, we must initialize an
   // EncoderParameters object. The EncoderParameters object
   // has an array of EncoderParameter objects. In this
   // case, there is only one EncoderParameter object in the array.
   // The one EncoderParameter object has an array of values.
   // In this case, there is only one value (of type ULONG)
   // in the array. We will let this value vary from 0 to 100.

   encoderParameters.Count = 1;
   encoderParameters.Parameter[0].Guid = EncoderQuality;
   encoderParameters.Parameter[0].Type = EncoderParameterValueTypeLong;
   encoderParameters.Parameter[0].NumberOfValues = 1;

   // Save the image as a JPEG with quality level 0.
   quality = 0;
   encoderParameters.Parameter[0].Value = &quality;
   stat = image->Save(L"Shapes001.jpg", &encoderClsid, &encoderParameters);

   if(stat == Ok)
      wprintf(L"%s saved successfully.\n", L"Shapes001.jpg");
   else
      wprintf(L"%d  Attempt to save %s failed.\n", stat, L"Shapes001.jpg");

   // Save the image as a JPEG with quality level 50.
   quality = 50;
   encoderParameters.Parameter[0].Value = &quality;
   stat = image->Save(L"Shapes050.jpg", &encoderClsid, &encoderParameters);

   if(stat == Ok)
      wprintf(L"%s saved successfully.\n", L"Shapes050.jpg");
   else
      wprintf(L"%d  Attempt to save %s failed.\n", stat, L"Shapes050.jpg");

编辑:JAGPDF 也 says 图像 DPI 在绘画时被考虑在内。所以我们可能走在正确的道路上。

Let's say we would like to tile a region of the page with our image. To do so we need to know the image dimensions. Because width() and width() return size in pixels we need to recalculate these to user space units. Image DPI is taken into account when the image is painted onto a canvas. An image usually specifies its DPI. If it is not so a value of images.default_dpi is used

img_width = img.width() / img.dpi_x() * 72
img_height = img.height() / img.dpi_y() * 72
for x in range(7):
    for y in range(15):
        canvas.image(img, 90 + x * img_width, 100 + y * img_height)

您可以尝试使用此 SO answer 更改 DPI。

如果我对你的问题理解正确,你的目标是消除对 ImageMagick.

的依赖

您可以通过告诉 CImg 使用其对 JPEG 的内置支持来更简单地做到这一点。您只需

  • 定义cimg_use_jpeg
  • link 与 libjpeg

所以你的编译命令变成:

g++ -Dcimg_use_jpeg ... -ljpeg