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.
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
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With