Copying a graphic to a TMetaFileCanvas outside the screen dimensions
Asked Answered
B

2

7

We have a problem with the TMetaFileCanvas output when drawing an image to a coordinate outside of the screen resolution. Vector operations seem to have no issues but image operations are just "ignored". If we draw the same image to a coordinate within the screen bounds then there are no issues.

For example. This SSCCE will produce 4 output files. The bitmap variant has no problems and will output as expected with the red square in the top left hand corner for inscreen.bmp and the red square in the bottom right hand corner for outsidescreen.bmp. The inscreen.emf meta file works as expected with the red square drawn in the top left corner. outsidescreen.emf doesn't work and only the line is drawn.

program Project6;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Types,
  Windows,
  Vcl.Graphics;

const
  SIZECONST = 3000; // should be larger than your screen resolution
  OFFSET = 1500;

  function GetMyMetafile(const aHDC: HDC): TMetafile;
  var
    metcnv: TMetafileCanvas;
  begin
    Result := TMetafile.Create;
    Result.SetSize(500, 500);

    metcnv := TMetafileCanvas.Create(Result, aHDC);
    metcnv.Brush.Color := clRed;
    metcnv.FillRect(Rect(0, 0, 500, 500));
    metcnv.Free;
  end;

  procedure OutputToMetaFile(const aFilename: string; const aStartOffset,
      aEndOffset, aMaxSize: Integer; aGraphic: TGraphic; aHDC: HDC);
  var
    metafile: TMetafile;
    metcnv: TMetafileCanvas;
  begin
    metafile := TMetafile.Create;
    try
      metafile.SetSize(aMaxSize, aMaxSize);

      metcnv := TMetafileCanvas.Create(metafile, aHDC);
      try
        // draw it somewhere offscreen
        metcnv.StretchDraw(Rect(aStartOffset, aStartOffset, aEndOffset, aEndOffset), aGraphic);
        metcnv.MoveTo(aStartOffset, aStartOffset);
        metcnv.LineTo(aEndOffset, aEndOffset);
      finally
        metcnv.Free;
      end;

      metafile.SaveToFile(aFilename);
    finally
      metafile.Free;
    end;
  end;

  procedure OutputToBitmap(const aFilename: string; const aStartOffset,
      aEndOffset, aMaxSize: Integer; aGraphic: TGraphic);
  var
    bmp: TBitmap;
  begin
    bmp := TBitmap.Create;
    try
      bmp.SetSize(aMaxSize, aMaxSize);

      bmp.Canvas.StretchDraw(Rect(aStartOffset, aStartOffset, aEndOffset, aEndOffset), aGraphic);
      bmp.Canvas.MoveTo(aStartOffset, aStartOffset);
      bmp.Canvas.LineTo(aEndOffset, aEndOffset);

      bmp.SaveToFile(aFilename);
    finally
      bmp.Free;
    end;
  end;

var
  mygraph: TMetafile;
  bigBitmap: TBitmap;
begin
  bigBitmap := TBitmap.Create;
  try
    bigBitmap.PixelFormat := pf24bit;
    Assert(bigBitmap.HandleType = bmDIB, 'Handle Type should be a DIB');
    bigBitmap.Width := SIZECONST;
    bigBitmap.Height := SIZECONST;
    mygraph := GetMyMetafile(bigBitmap.Canvas.Handle);
    OutputToMetaFile('inscreen.emf', 0, 1000, SIZECONST, mygraph, bigBitmap.Canvas.Handle);
    OutputToMetaFile('outsidescreen.emf', OFFSET, SIZECONST-1, SIZECONST, mygraph, bigBitmap.Canvas.Handle);

    // do the same using bitmap
    OutputToBitmap('inscreen.bmp', 0, 1000, SIZECONST, mygraph);
    OutputToBitmap('outsidescreen.bmp', OFFSET, SIZECONST-1, SIZECONST, mygraph);
  finally
    bigBitmap.Free;
    mygraph.Free;
  end;
end.

Can anyone see what the problem is or do you know of a work around for this?

Update

I should have included this when I originally asked the question. We did test using the HDC for a large bitmap and that exhibited the same problem. I have updated the example code to demonstrate this.

Update 2

Unfortunately the solution is still elusive even after the bounty. Any BitBlt operation outside the screen size is not drawn.

Here is a extraction of the Metafile operations when the image is in the bounds of the screen coordinates:

R0001: [001] EMR_HEADER (s=108) {{ Bounds(500,500,18138,18129), Frame(0,0,105000,105000), ver(0x10000), size(688), recs(33), handles(2), pals(0), dev_pix(1080,1920), dev_mil(381,677), pixf_size(0), pixf_ofs(0), openGL(0) }}
R0002: [033] EMR_SAVEDC (s=8)
R0003: [115] EMR_SETLAYOUT  (s=12)  {iMode(0=<default>)}
R0004: [028] EMR_SETMETARGN (s=8)
R0005: [037] EMR_SELECTOBJECT   (s=12)  {Stock object: 0=OBJ_BRUSH.(BS_SOLID)}
R0006: [037] EMR_SELECTOBJECT   (s=12)  {Stock object: 7=OBJ_PEN.(PS_SOLID | COSMETIC)}
R0007: [037] EMR_SELECTOBJECT   (s=12)  {Stock object: 14=OBJ_FONT}
R0008: [025] EMR_SETBKCOLOR (s=12)  {0x00FFFFFF}
R0009: [024] EMR_SETTEXTCOLOR   (s=12)  {0x00000000}
R0010: [018] EMR_SETBKMODE  (s=12)  {iMode(2=OPAQUE)}
R0011: [019] EMR_SETPOLYFILLMODE    (s=12)  {iMode(1=ALTERNATE)}
R0012: [020] EMR_SETROP2    (s=12)  {iMode(13=R2_COPYPEN)}
R0013: [021] EMR_SETSTRETCHBLTMODE  (s=12)  {iMode(1=BLACKONWHITE)}
R0014: [022] EMR_SETTEXTALIGN   (s=12)  {iMode(0= TA_LEFT TA_TOP)}
R0015: [013] EMR_SETBRUSHORGEX  (s=16)  {ptlOrigin(0,0)}
R0016: [058] EMR_SETMITERLIMIT  (s=12)  {Limit:0.000}
R0017: [027] EMR_MOVETOEX   (s=16)  { ptl(0,0)}
R0018: [035] EMR_SETWORLDTRANSFORM  (s=32)  {xform(eDx:500.000000, eDy:500.000000, eM11:5.039683, eM12:0.000000, eM21:0.000000, eM22:5.037203)}
R0019: [036] EMR_MODIFYWORLDTRANSFORM   (s=36)  {iMode(4=MWT_??), xform(eDx:500.000000, eDy:500.000000, eM11:5.039683, eM12:0.000000, eM21:0.000000, eM22:5.037203)}
R0020: [115] EMR_SETLAYOUT  (s=12)  {iMode(0=<default>)}
R0021: [070] EMR_GDICOMMENT (s=40)  {GDI.Begin Group}
R0022: [039] EMR_CREATEBRUSHINDIRECT    (s=24)  {ihBrush(1), style(0=BS_SOLID, color:0x000000FF)}
R0023: [037] EMR_SELECTOBJECT   (s=12)  {Table object: 1=OBJ_BRUSH.(BS_SOLID)}
R0024: [037] EMR_SELECTOBJECT   (s=12)  {Table object: 1=OBJ_BRUSH.(BS_SOLID)}
R0025: [076] EMR_BITBLT (s=100) {rclBounds(500,500,18138,18129), Dest[x:0, y:0, cx:3500, cy:3500)], dwRop(0x00F00021), Src[x:0, y:0, xform(eDx:0.000000, eDy:0.000000, eM11:1.000000, eM12:0.000000, eM21:0.000000, eM22:1.000000), BkColor:0x00000000, iUsage:0, offBmi:0, Bmi:0, offBits:0, Bits:0]}
R0026: [037] EMR_SELECTOBJECT   (s=12)  {Table object: 1=OBJ_BRUSH.(BS_SOLID)}
R0027: [037] EMR_SELECTOBJECT   (s=12)  {Stock object: 0=OBJ_BRUSH.(BS_SOLID)}
R0028: [040] EMR_DELETEOBJECT   (s=12)  {ihObject(1)}
R0029: [070] EMR_GDICOMMENT (s=20)  {GDI.End Group}
R0030: [034] EMR_RESTOREDC  (s=12)  {iRelative(-1)}
R0031: [027] EMR_MOVETOEX   (s=16)  { ptl(500,500)}
R0032: [054] EMR_LINETO (s=16)  { ptl(1000,1000)}
R0033: [014] EMR_EOF    (s=20)  {nPalEntries:0, offPalEntries:16, nSizeLast:20}

Here is a extraction of the Metafile operations when the image is outside the bounds of the screen coordinates:

R0001: [001] EMR_HEADER (s=108) {{ Bounds(1500,1500,2999,2999), Frame(0,0,105000,105000), ver(0x10000), size(588), recs(32), handles(2), pals(0), dev_pix(1080,1920), dev_mil(381,677), pixf_size(0), pixf_ofs(0), openGL(0) }}
R0002: [033] EMR_SAVEDC (s=8)
R0003: [115] EMR_SETLAYOUT  (s=12)  {iMode(0=<default>)}
R0004: [028] EMR_SETMETARGN (s=8)
R0005: [037] EMR_SELECTOBJECT   (s=12)  {Stock object: 0=OBJ_BRUSH.(BS_SOLID)}
R0006: [037] EMR_SELECTOBJECT   (s=12)  {Stock object: 7=OBJ_PEN.(PS_SOLID | COSMETIC)}
R0007: [037] EMR_SELECTOBJECT   (s=12)  {Stock object: 14=OBJ_FONT}
R0008: [025] EMR_SETBKCOLOR (s=12)  {0x00FFFFFF}
R0009: [024] EMR_SETTEXTCOLOR   (s=12)  {0x00000000}
R0010: [018] EMR_SETBKMODE  (s=12)  {iMode(2=OPAQUE)}
R0011: [019] EMR_SETPOLYFILLMODE    (s=12)  {iMode(1=ALTERNATE)}
R0012: [020] EMR_SETROP2    (s=12)  {iMode(13=R2_COPYPEN)}
R0013: [021] EMR_SETSTRETCHBLTMODE  (s=12)  {iMode(1=BLACKONWHITE)}
R0014: [022] EMR_SETTEXTALIGN   (s=12)  {iMode(0= TA_LEFT TA_TOP)}
R0015: [013] EMR_SETBRUSHORGEX  (s=16)  {ptlOrigin(0,0)}
R0016: [058] EMR_SETMITERLIMIT  (s=12)  {Limit:0.000}
R0017: [027] EMR_MOVETOEX   (s=16)  { ptl(0,0)}
R0018: [035] EMR_SETWORLDTRANSFORM  (s=32)  {xform(eDx:1500.000000, eDy:1500.000000, eM11:15.108969, eM12:0.000000, eM21:0.000000, eM22:15.101533)}
R0019: [036] EMR_MODIFYWORLDTRANSFORM   (s=36)  {iMode(4=MWT_??), xform(eDx:1500.000000, eDy:1500.000000, eM11:15.108969, eM12:0.000000, eM21:0.000000, eM22:15.101533)}
R0020: [115] EMR_SETLAYOUT  (s=12)  {iMode(0=<default>)}
R0021: [070] EMR_GDICOMMENT (s=40)  {GDI.Begin Group}
R0022: [039] EMR_CREATEBRUSHINDIRECT    (s=24)  {ihBrush(1), style(0=BS_SOLID, color:0x000000FF)}
R0023: [037] EMR_SELECTOBJECT   (s=12)  {Table object: 1=OBJ_BRUSH.(BS_SOLID)}
R0024: [037] EMR_SELECTOBJECT   (s=12)  {Table object: 1=OBJ_BRUSH.(BS_SOLID)}
R0025: [037] EMR_SELECTOBJECT   (s=12)  {Table object: 1=OBJ_BRUSH.(BS_SOLID)}
R0026: [037] EMR_SELECTOBJECT   (s=12)  {Stock object: 0=OBJ_BRUSH.(BS_SOLID)}
R0027: [040] EMR_DELETEOBJECT   (s=12)  {ihObject(1)}
R0028: [070] EMR_GDICOMMENT (s=20)  {GDI.End Group}
R0029: [034] EMR_RESTOREDC  (s=12)  {iRelative(-1)}
R0030: [027] EMR_MOVETOEX   (s=16)  { ptl(1500,1500)}
R0031: [054] EMR_LINETO (s=16)  { ptl(2999,2999)}
R0032: [014] EMR_EOF    (s=20)  {nPalEntries:0, offPalEntries:16, nSizeLast:20}

You can see very clearly that the BilBlt operation (R0025 in the first one) is missing.

Biophysics answered 22/5, 2015 at 0:35 Comment(0)
T
6

You are creating TMetaFileCanvas with the ReferenceDevice parameter set to 0, so it will set ReferenceDevice to the HDC from GetDC(0), ie the screen. ReferenceDevice is used for getting resolution and capabilities that are used during the EMF drawing. For instance, when the TMetaFile's dimensions are empty, TMetaFileCanvas uses the dimensions of ReferenceDevice. TMetaFileCanvas then creates a HDC for itself that has a bounding rectangle based on the dimensions of either the TMetaFile or ReferenceDevice, whichever one was valid.

So, to work around your issue, provide a ReferenceDevice that is large enough to handle your drawing. You can pre-size the TMetaFile dimensions to the desired max size before creating the TMetaFileCanvas, but you will likely have to create a TBitmap of the desired max size and use its Canvas.Handle as ReferenceDevice instead of using the screen.

Internally, TCanvas.StretchDraw() just calls TGraphic.Draw(). TMetaFile.Draw() "plays" the metafile onto the target canvas's HDC. When that HDC is the one created by TMetaFileCanvas, you cannot draw outside the dimensions assigned to that TMetaFileCanvas.

Toxinantitoxin answered 22/5, 2015 at 1:21 Comment(2)
We tried that. I should have included it in the question. I will update the question with a code example using a large bitmap which exhibits the same problem.Biophysics
Bounty awarded to this answer. I think the answer lies in either the reference device that needs to be created or we have to wait for a Windows fix to the problem.Biophysics
V
2
program Project1;
{$APPTYPE CONSOLE}

uses
 SysUtils, Types, Windows, Graphics;

const
 SIZECONST = 3000; // should be larger than your screen resolution
 OFFSET = 1500;
var
 //holds millimeter per pixel ratios
 MMPerPixelHorz,
 MMPerPixelVer: Integer;

 procedure CreateMyMetafile(var HmyGraphic: HENHMETAFILE; aHDC: HDC);
 var
  R: Trect;
  TheBrush: HBRUSH;
  OldBrush: HBRUSH;
  MetafileDC: HDC;
begin
  R:= Rect(0, 0, 100*MMPerPixelHorz, 100*MMPerPixelVer);
  MetafileDC:= CreateEnhMetaFile(aHDC, 'myGraphic.emf', @R, nil);

  TheBrush:=CreateSolidBrush(RGB(255, 0, 0));
  OldBrush:=SelectObject(MetafileDC, TheBrush);

  Rectangle(MetafileDC, r.Left, r.Top, r.Right, r.Bottom);

  SelectObject(MetafileDC, OldBrush);
  DeleteObject(TheBrush);
  HmyGraphic:=CloseEnhMetaFile(MetafileDC);
end;

procedure OutputToMetaFile(const aFilename: string; const aStartOffset,
     aEndOffset, aMaxSize: Integer; aHDC: HDC);
var
  r: Trect;

 ReferenceRect: TRect;
 MetafileDC: HDC;
 HMetaFile, HMetaMyGraphic: HENHMETAFILE; {EMF file handle}
begin
 //create our reference rectangle for the metafile
 ReferenceRect:= Rect(0, 0, aMaxSize * MMPerPixelHorz, aMaxSize * MMPerPixelVer);

 //Create First EnhMetaFile
 CreateMyMetafile(HMetaMyGraphic, aHDC);

 MetafileDC:=CreateEnhMetaFile(aHDC, pchar(aFilename),@ReferenceRect, nil);
 //SetMapMode(MetafileDC, MM_ANISOTROPIC);

 try
   r:= Rect(aStartOffset, aStartOffset, aEndOffset, aEndOffset);
   PlayEnhMetaFile (MetaFileDC, HMetaMyGraphic, r);

   MoveToEx(MetafileDC, aStartOffset, aStartOffset, nil);
   LineTo(MetafileDC, aEndOffset, aEndOffset);
   HMetaFile:=CloseEnhMetaFile(MetafileDC);

 finally
   DeleteEnhMetaFile (HMetaFile);
   DeleteEnhMetaFile (HMetaMyGraphic);
 end;
end;

var
 WidthInMM,
 HeightInMM,
 WidthInPixels,
 HeightInPixels: Integer;

 bigBitmap: TBitmap;
 mHDC: HDC;
begin
  bigBitmap := TBitmap.Create;
  try
    bigBitmap.PixelFormat := pf24bit;
    Assert(bigBitmap.HandleType = bmDIB, 'Handle Type should be a DIB');
    bigBitmap.Width := SIZECONST;
    bigBitmap.Height := SIZECONST;
    mHDC:= bigBitmap.Canvas.Handle;

   //retrieve the size of the screen in millimeters
    WidthInMM:=GetDeviceCaps(mHDC, HORZSIZE);
    HeightInMM:=GetDeviceCaps(mHDC, VERTSIZE);

   //retrieve the size of the screen in pixels
   WidthInPixels:=GetDeviceCaps(mHDC, HORZRES);
   HeightInPixels:=GetDeviceCaps(mHDC, VERTRES);

   MMPerPixelHorz:=(WidthInMM * 100) div WidthInPixels;
   MMPerPixelVer:=(HeightInMM * 100) div HeightInPixels;

   OutputToMetaFile('inscreen.emf', 500, 1000, SIZECONST, mHDC);
   OutputToMetaFile('outsidescreen.emf', OFFSET, SIZECONST-1, SIZECONST, mHDC);
 finally
   bigBitmap.Free;
 end;
end.
Ventriloquize answered 27/5, 2015 at 21:26 Comment(6)
Thanks, this answer looks to be correct. We are just double checking a few things and I will mark the answer as soon as I have confirmed.Biophysics
Ok, this doesn't work properly. This is still using a vector operation so the problem remains. The original question used a FillRect in GetMyMetaFile which generates a BitBlt operation in the meta file. This code uses Rectangle in CreateMyMetafile which is a vector operation.Biophysics
AFAIK bounty rules, half the bounty will be automatically awarded since the answer has 2 (or more) votes. Obviously the answer has been up-voted by people who have not noticed that it did not address the problem. Down-voting or revoking should be the natural course of action...Grundyism
@SertacAkyuz I will award the bounty to Remy's answer. It's either a bug in Windows or the answer is to create a special ReferenceDevice. I won't accept the answer though. Hopefully in the future someone will be able to answer the question.Biophysics
@Gray - It's a bug. If you stretch your raster from up above, it nicely extends to where it shouldn't (according to Remy). In your code in the question, make the offset 1080 and you have the image, make it 1090 and you don't. Remy's answer have no substance.Grundyism
@SertacAkyuz Two incorrect answers but I had to award the bounty somewhere. The reason why I awarded to Remy is that if you use a printer Reference Device, it works, depending on the size of the printer canvas so it is dependent on the size of the reference device. The problem is that Windows is somehow picking up the screen from the DIB which is wrong. If I could get another reference device that isn't based on the screen then we would be in business. Remy's answer indicates part of the problem but his suggestion to create a Bitmap is wrong.Biophysics

© 2022 - 2024 — McMap. All rights reserved.