ULIK 16 Posted July 28, 2020 (edited) 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: 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: 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. 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: Edited July 28, 2020 by ULIK additional information Share this post Link to post
doxiaobao 0 Posted August 24, 2023 (edited) Hello, did you solve this problem?I also encountered similar problems Edited August 24, 2023 by doxiaobao Share this post Link to post
Alexander Halser 26 Posted August 25, 2023 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
Alexander Sviridenkov 360 Posted August 26, 2023 (edited) 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 August 26, 2023 by Alexander Sviridenkov Share this post Link to post
Alexander Halser 26 Posted August 26, 2023 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
Alexander Sviridenkov 360 Posted August 26, 2023 Yes, please see this topic https://microsoft.public.win32.programmer.gdi.narkive.com/xYv8gdam/preventing-gdi-from-scaling-images-how-to-ignore-dpi-settings-rep In my tests even this set of parameters was not enough, I got stable result only when passing not null ImageAttributes (last parameter). Share this post Link to post
ULIK 16 Posted August 28, 2023 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; 2 Share this post Link to post