Jump to content
ULIK

GDI+ DrawImage output differs by used printer on HighDPI system

Recommended Posts

Hi,

 

when I print an image using GDI+ DrawImage method on a high DPI system (192 DPI), the result differs from what printer is used. Next is my test code:

procedure TForm1.Button1Click(Sender: TObject);
var
  DC: HDC;
  gpGraphics: TGPGraphics;
  gpImage: TGPBitmap;
  gpPen: TGPPen;
  nLogPixUI: Integer;
  nLogPx: Integer;
  png: TpngImage;
  rDest: TGPRectF;
  rectImage: TRect;
  rectImage72: TRect;
begin
  memo1.Clear;

  // Just to get some information on the image
  png := TPngImage.Create;
  try
    png.LoadFromFile('image.png');

    memo1.Lines.Add(Format('PNG.Width: %d', [png.Width]));
    memo1.Lines.Add(Format('PNG.Height: %d', [png.Height]));
    memo1.Lines.Add(Format('PNG.Res: %d, %d', [png.PixelInformation.PPUnitX, png.PixelInformation.PPUnitY]));
  finally
    png.Free;
  end;



  if PrintDialog1.Execute(Handle) then
  begin
    Printer.BeginDoc;
    try

      gpGraphics := TGPGraphics.Create(Printer.Canvas.Handle);
      try
        // let's use pixel
        gpGraphics.SetPageUnit(UnitPixel);

        // get device DPI
        DC := gpGraphics.GetHDC;
        try
          nLogPx := GetDeviceCaps(DC, LOGPIXELSX);
          memo1.Lines.Add(Format('Log. Pixel Device: %d', [nLogPx]))
        finally
          gpGraphics.ReleaseHDC(DC);
        end;

        // and create an image rectangle based on some MS Ink coordinates
        nLogPixUI := Screen.MonitorFromWindow(Self.Handle).PixelsPerInch;
        memo1.Lines.Add(Format('Log. Pixel Screen: %d', [nLogPixUI]));

        // create the image rectangle, based on a 90x90 pixel rectangle  (just for test)
        rectImage.Create(
          MulDiv(10, nLogPx, nLogPixUI),
          MulDiv(10, nLogPx, nLogPixUI),
          MulDiv(100, nLogPx, nLogPixUI),
          MulDiv(100, nLogPx, nLogPixUI)
        );

        // center coordinates system on image midpoint
        gpGraphics.TranslateTransform(rectImage.CenterPoint.X, rectImage.CenterPoint.y);

        gpPen := TGPPen.Create(ColorRefToARGB(ColorToRGB(clRed)), 1);
        try
          // create a red rectangle around the image
          gpGraphics.DrawRectangle(gpPen, - rectIMage.Width/2, - rectImage.Height/2, rectImage.Width, rectImage.Height);

          // load the image
          gpImage := TGPBitmap.Create('image.png', False);
          try
            memo1.Lines.Add(Format('Img.Width: %d', [gpImage.GetWidth]));
            memo1.Lines.Add(Format('Img.Height: %d', [gpImage.GetHeight]));
            memo1.Lines.Add(Format('Img.Res: %f, %f', [gpImage.GetHorizontalResolution, gpImage.GetVerticalResolution]));

            // create the destination rectangle for printing based on rectImage
            rDest.X := -rectIMage.Width/2;
            rDest.Y := -rectIMage.Height/2;
            rDEst.Width := rectIMage.Width;
            rDest.Height := rectImage.Height;

            gpGraphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
            gpGraphics.SetSmoothingMode(SmoothingModeHighQuality);
            gpGraphics.DrawImage(gpImage, rDest, 0, 0, gpImage.GetWidth, gpImage.GetHeight, UnitPixel);

          finally
            gpImage.Free;
          end;
        finally
          gpPen.Free;
        end;
      finally
        gpGraphics.Free;
      end;


    finally
      Printer.EndDoc;
    end;
  end;
end;

When I run this code against the attached image (see below) on a 96 DPI desktop system, everything is fine when printing on Microsoft PrintToPDF as well as any other installed printer:

 

expected_output.png.70d318751be855f7facf634a6d36a615.png

 

 

Next I run the same code on a SurfacePro 7 with 192 DPI.  Now the result differs from used printer:  HP Universal PS as well as SnagIt produce the expected output:

 

expected_output_192.png.18de96c67d5a110fe5fbae72756f8b82.png

 

But Microsoft Print to PDF, OneNote for Windows 10 and a HP Laserjet 400 MFP M425dw (607BA1) printer creates a wrong output: rectangle size is fine but content not.

 

wrong_output_192.png.ed653311e9baa748f3d37cdc831c78cc.png

 

So the question is: why produces the output on different printers different outputs? It looks like the wrong output includes some scaling between 96 and 192 DPI but DrawImage should be independent of it, when setting source, destination explicitly. Especially why this is printer dependent?

 

Any ideas, what causes this problem?

 

 

Thanks,

Ulrich

 

forgot to mention: Delphi XE 10.2 Tokyo

 

used image for printing:

image.png

Edited by ULIK
additional information

Share this post


Link to post

Hello, did you solve this problem?I also encountered similar problems

Edited by doxiaobao

Share this post


Link to post

Without trying your example ... 

When you print an image, the screen DPI doesn't matter at all. You are dealing with printer DPI instead. Nothing else.

 

TGPBitmap loads the PNG image and that image has a defined size in pixels. With TGPBitmap.DrawImage the picture is rendered onto a Windows device, which can be a screen device or a printer device. For GDI+ this is more or less the same (some technical printer details omitted). Fact is, GDI+ does the stretching and resampling to make the image fit into the rectangle you specify. And that target rectangle must be in device pixels. If the device is a printer, get LOGPIXELSX from the printer canvas and relate it to 96 dpi (= Windows standard display resolution at 100%).

 

// get printer device DPI
        DC := GetDC(Printer.Canvas.Handle);
        try
          nLogPx := GetDeviceCaps(DC, LOGPIXELSX);
          memo1.Lines.Add(Format('Printer Device: %d', [nLogPx]))
        finally
          ReleaseHDC(DC);
        end;

        rectImage.Create(
          MulDiv(10, nLogPx, 96),
          MulDiv(10, nLogPx, 96),
          MulDiv(100, nLogPx, 96),
          MulDiv(100, nLogPx, 96)
        );

(The code example is untested, typed from memory. Point is, get the resolution of the printer canvas, not the screen.)

Share this post


Link to post
3 hours ago, Alexander Halser said:

When you print an image, the screen DPI doesn't matter at all. You are dealing with printer DPI instead. Nothing else. 

This may sound strange, but screen DPI does matter. GPImage has internal DPI (same as screen DPI on image creation) and this DPI do affect image drawing on any device.

You cannot change this DPI and there is only one of many overloaded DrawImage methods that ignores this DPI and let your render image with exact source and destination rectangles. It is not described in documentation and is not intuitive, I have no idea why MS done this.

Edited by Alexander Sviridenkov

Share this post


Link to post
5 hours ago, Alexander Sviridenkov said:

GPImage has internal DPI (same as screen DPI on image creation) and this DPI do affect image drawing

It really does? I wasn't aware of that. Maybe we have just been lucky and were always using that one DrawImage method, that works. We draw pictures with the GDI+ function below. Bounds is the destination rectangle, already calculated in device pixels.

procedure DoGDIPlusDraw(DC: HDC; bmp: TBitmap; bounds: TRect);
var
  gpgraphics: TGPGraphics;
  gpimage:    TGPBitmap;
  Width, Height: UINT;
begin
  gpgraphics := TGPGraphics.Create(DC);
  gpimage    := TGPBitmap.Create(bmp.Handle, bmp.palette);
  try
    Width  := gpimage.GetWidth;
    Height := gpimage.GetHeight;

    gpgraphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);   // 7
    gpgraphics.DrawImage(gpimage,
      bounds,      // destination rectangle
      0, 0,        // upper-left corner of source rectangle
      Width,       // width of source rectangle
      Height,      // height of source rectangle
      UnitPixel);

  finally
    gpimage.Free;
    gpgraphics.Free;
  end;
end;

 

Share this post


Link to post

I finally found a solution for this problem: the PNG images I printed had no pHYs chunks set (as they were initially created during runtime). As soon as the missing chucks were added, printing from high DPI systems works fine. The point is: it doesn't matter what exact values you are using, it's just that those chunks had to be set to a somewhat reasonable value.

 

function TPaImageStrokeHelper.FixPNGPixelInformation(APNGImage: TPngImage): Boolean;
begin
  Result := False;

  if not Assigned(APNGImage) then exit;

  if not APNGImage.HasPixelInformation then
  begin
    APNGImage.PixelInformation.PPUnitX := 3780;  // relates to 96 DPI:   3780 Points / Meter
    APNGImage.PixelInformation.PPUnitY := 3780;
    APNGImage.PixelInformation.UnitType := utMeter;
    Result := True;
  end;
end;

 

  • Like 2

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×