Diego Fernando Pava Diego Fernando Pava - 2 months ago 36
C++ Question

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

What I need to do is very simple, I need to plot a vector using CIMG and then save the graph ina jpg and add the jpg to a PDF document using JAGPDF. In order to save CIMG as JPG, the program uses an external program called Image Magick.

I wanted to avoid using that program and use GDI+ instead by first saving the CIMG as a BMP (it does that natively) and then saving the jpg from the bmp.

MCVE program looks like this

#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;
}


With SaveFile() being the following function using GDI+ to convert from plot.bmp to 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);
}


Both methods save jpgs that in properties seems to have the same size but the first put the image correctly in the pdf while the second puts a huge image in the pdf even though they are supossed to be the same size. How can I fix this?

Attached is scrrenshots of report1 and report2

enter image description here

enter image description here

SOLUTION

With your suggestions, I was able to modify the SaveFile function in order to be able to control de DPI, I post the new code in case someone needs it.

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;
}

Answer

I would guess ImageMagick include some perks that filter the image to fit the canvas. The smartass. I'd try resizing the image before exporting to JPEG. You might give a go to this guide. It basically says you can resize the bmp (in the example it checks w/h ratio but well...). THe goal should be to specify the size you need for the canvas is exactly that.

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;
}

And then, save it using the encoder. ALso, you'd like to check whether you might need to set the quality of the resulting JPEG using encoderparameters as shown in the official 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");

EDIT: JAGPDF also says image DPI is taken into account when painting. SO we probably are on the right path.

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)

You might try changing DPI using this SO answer.