Копирование графики в TMetaFileCanvas вне размеров экрана

У нас есть проблема с выводом TMetaFileCanvas при рисовании изображения по координатам за пределами разрешения экрана. Векторные операции, кажется, не имеют проблем, но операции с изображениями просто "игнорируются". Если мы рисуем одно и то же изображение с координатой в пределах экрана, проблем не возникает.

Например. Этот SSCCE будет производить 4 выходных файла. Вариант растрового изображения не имеет проблем и будет отображаться, как и ожидалось, с красным квадратом в верхнем левом углу для inscreen.bmp и красный квадрат в правом нижнем углу для outsidescreen.bmp, inscreen.emf метафайл работает как положено с красным квадратом, нарисованным в верхнем левом углу. outsidescreen.emf не работает, и только линия рисуется.

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.

Кто-нибудь может увидеть, в чем проблема, или вы знаете, как обойти это?

Обновить

Я должен был включить это, когда я первоначально задал вопрос. Мы провели тестирование с использованием HDC для большого растрового изображения, и это показало ту же проблему. Я обновил пример кода, чтобы продемонстрировать это.

Обновление 2

К сожалению, решение остается труднодостижимым даже после щедрости. Любая операция BitBlt за пределами размера экрана не рисуется.

Вот извлечение операций метафайла, когда изображение находится в границах экранных координат:

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}

Вот извлечение операций метафайла, когда изображение выходит за границы координат экрана:

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}

Вы можете очень ясно увидеть, что операция BilBlt (R0025 в первом) отсутствует.

2 ответа

Вы создаете TMetaFileCanvas с ReferenceDevice параметр установлен в 0, поэтому он будет устанавливать ReferenceDevice к HDC от GetDC(0)т.е. экран. ReferenceDevice используется для получения разрешения и возможностей, которые используются во время рисования ЭДС. Например, когда TMetaFileразмеры пустые, TMetaFileCanvas использует размеры ReferenceDevice, TMetaFileCanvas затем создает HDC для себя, который имеет ограничивающий прямоугольник, основанный на размерах либо TMetaFile или же ReferenceDeviceкакой бы ни был действителен.

Итак, чтобы обойти вашу проблему, предоставьте ReferenceDevice это достаточно большой, чтобы справиться с вашим рисунком. Вы можете предварительно определить размер TMetaFile размеры до желаемого максимального размера до создания TMetaFileCanvas, но вам, вероятно, придется создать TBitmap желаемого максимального размера и использовать его Canvas.Handle как ReferenceDevice вместо использования экрана.

Внутренне TCanvas.StretchDraw() просто звонки TGraphic.Draw(), TMetaFile.Draw() "воспроизводит" метафайл на целевом холсте HDC, Когда это HDC это тот, который создан TMetaFileCanvasвы не можете рисовать вне размеров, назначенных этому 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.
Другие вопросы по тегам