Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copying a graphic to a TMetaFileCanvas outside the screen dimensions

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.

like image 657
Graymatter Avatar asked May 22 '15 00:05

Graymatter


2 Answers

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.

like image 131
Remy Lebeau Avatar answered Nov 15 '22 08:11

Remy Lebeau


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.
like image 39
Morteza Samie Avatar answered Nov 15 '22 09:11

Morteza Samie