Delphi Windows图元文件的尺寸是否有限制?

Delphi Windows图元文件的尺寸是否有限制?,delphi,winapi,wmf,Delphi,Winapi,Wmf,我正在创建一些.wmf文件,但其中一些文件似乎已损坏,无法在任何图元文件查看器中显示。经过反复试验,我发现问题是由它们的尺寸引起的。如果我按比例缩放同一张图纸以减小尺寸,它将显示出来 现在,我想知道图纸的尺寸是否有限制,或者问题是否出在其他方面。我知道这些文件,所以我猜每个维度的限制是2^16个单位(如果有签名的话,限制是2^15个单位)。但在我的测试中大约是25000。所以我不能依赖这个值,因为限制可以是任何东西(可能是宽度*高度,或者可能是图形的分辨率会影响它)。我找不到有关.wmf文件的可

我正在创建一些.wmf文件,但其中一些文件似乎已损坏,无法在任何图元文件查看器中显示。经过反复试验,我发现问题是由它们的尺寸引起的。如果我按比例缩放同一张图纸以减小尺寸,它将显示出来

现在,我想知道图纸的尺寸是否有限制,或者问题是否出在其他方面。我知道这些文件,所以我猜每个维度的限制是2^16个单位(如果有签名的话,限制是2^15个单位)。但在我的测试中大约是25000。所以我不能依赖这个值,因为限制可以是任何东西(可能是宽度*高度,或者可能是图形的分辨率会影响它)。我找不到有关.wmf文件的可靠资源来描述这一点

下面是显示问题的示例代码:

procedure DrawWMF(const Rect: TRect; const Scale: Double; FileName: string);
var
  Metafile: TMetafile;
  Canvas: TMetafileCanvas;
  W, H: Integer;
begin
  W := Round(Rect.Width * Scale);
  H := Round(Rect.Height * Scale);

  Metafile := TMetafile.Create;
  Metafile.SetSize(W, H);

  Canvas := TMetafileCanvas.Create(Metafile, 0);
  Canvas.LineTo(W, H);
  Canvas.Free;

  Metafile.SaveToFile(FileName);
  Metafile.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
const
  Dim = 40000;
begin
  DrawWMF(Rect(0, 0, Dim, Dim), 1.0, 'Original.wmf');
  DrawWMF(Rect(0, 0, Dim, Dim), 0.5, 'Scaled.wmf');

  try
    Image1.Picture.LoadFromFile('Original.wmf');
  except
    Image1.Picture.Assign(nil);
  end;

  try
    Image2.Picture.LoadFromFile('Scaled.wmf');
  except
    Image2.Picture.Assign(nil);
  end;
end;
PS:我知道将
Metafile.Enhanced
设置为
True
并将其保存为.emf文件可以解决问题,但我为其生成文件的目标应用程序不支持增强的Metafile

编辑: 如以下答案所述,这里有两个不同的问题:

主要问题是关于文件本身,每个维度有2^15个限制。如果图形的宽度或高度超过此值,delphi将写入损坏的文件。您可以在中找到更多详细信息


第二个问题是在
TImage
中加载文件。当您希望在delphi VCL应用程序中显示图像时,还有另一个限制。这一个取决于系统,与图纸将要绘制的DC的dpi相关。详细描述了这一点。将0.7作为
Scale
传递到
DrawWMF
(上面的代码示例)会在我的电脑上再现这种情况。生成的文件正常,可以使用其他图元文件查看器查看(我使用MS Office Picture Manager),但VCL无法显示它,但是,加载文件时不会引发异常。

当文档没有帮助时,查看源代码:)。如果宽度或高度太大,则文件创建失败,并且文件无效。在下面我只看水平维度,但垂直维度被视为相同的

在Vcl.Graphics中:

constructor TMetafileCanvas.CreateWithComment(AMetafile : TMetafile;
  ReferenceDevice: HDC; const CreatedBy, Description: String);

        FMetafile.MMWidth := MulDiv(FMetafile.Width,
          GetDeviceCaps(RefDC, HORZSIZE) * 100, GetDeviceCaps(RefDC, HORZRES));
如果未定义
ReferenceDevice
,则使用屏幕(
GetDC(0)
)。在我的机器上,水平尺寸报告为677,水平分辨率报告为1920。因此
FMetafile.MMWidth:=40000*67700 div 1920(=1410416)
。因为
FMetaFile.MMWidth
是一个整数,所以此时没有问题

接下来,让我们看一下文件写入,它是通过
WriteWMFStream
完成的,因为我们写入
.wmf
文件:

procedure TMetafile.WriteWMFStream(Stream: TStream);
var
  WMF: TMetafileHeader;
  ...
begin
  ...
        Inch := 96          { WMF defaults to 96 units per inch }
  ...
        Right := MulDiv(FWidth, WMF.Inch, HundredthMMPerInch);
  ...
WMF头结构指示了事情的发展方向

  TMetafileHeader = record
    Key: Longint;
    Handle: SmallInt;
    Box: TSmallRect;  // smallint members
    Inch: Word;
    Reserved: Longint;
    CheckSum: Word;
  end;
框:TSmallRect
字段中的坐标不能大于
smallint
大小的值。 右侧的计算公式为:=1410417*96 div 2540(=53307 assmallint=-12229)。图像尺寸溢出,wmf数据无法“播放”到文件中

问题是:我可以在我的机器上使用什么尺寸

FMetaFile.MMWidth和FMetaFile.MMHeight都需要小于或等于

MaxSmallInt * HundredthMMPerInch div UnitsPerInch or
32767 * 2540 div 96 = 866960
在我的测试机上,水平显示尺寸和分辨率分别为677和1920。垂直显示尺寸和分辨率分别为381和1080。因此,图元文件的最大尺寸为:

Horizontal: 866960 * 1920 div 67700 = 24587
Vertical:   866960 * 1080 div 38100 = 24575
通过测试验证


根据评论进行进一步调查后更新

由于水平和垂直尺寸高达32767,图元文件在某些应用程序中是可读的,例如f.ex。GIMP,它显示了图像。这可能是由于这些程序将图形的范围视为
word
,而不是
SmallInt
。GIMP报告的每英寸像素数为90,当更改为96(这是Delphi使用的值)时,GIMP会显示“GIMP消息:插件崩溃:“file wmf.exe”

OP中的过程不会显示尺寸为32767或更小的错误消息。但是,如果任一尺寸高于之前显示的计算最大值,则不会显示图形。读取图元文件时,使用与保存时相同的TMetafileHeader结构类型,并使用
FWidth
FHeight
get负值:

procedure TMetafile.ReadWMFStream(Stream: TStream; Length: Longint);
  ...
    FWidth := MulDiv(WMF.Box.Right - WMF.Box.Left, HundredthMMPerInch, WMF.Inch);
    FHeight := MulDiv(WMF.Box.Bottom - WMF.Box.Top, HundredthMMPerInch, WMF.Inch);

procedure TImage.PictureChanged(Sender: TObject);

  if AutoSize and (Picture.Width > 0) and (Picture.Height > 0) then
    SetBounds(Left, Top, Picture.Width, Picture.Height);
负值通过
desect
函数中的
Paint
过程波动,因此看不到图像

procedure TImage.Paint;
  ...
      with inherited Canvas do
        StretchDraw(DestRect, Picture.Graphic);
DestRect的“右”和“下”为负值

我认为找到实际限制的唯一方法是调用
GetDeviceCaps()
用于水平和垂直大小和分辨率,并执行上述计算。但是,请注意,该文件可能仍然无法在另一台计算机上使用Delphi程序显示。将图形大小保持在20000 x 20000范围内可能是一个安全限制。

您的限制是32767

跟踪VCL代码时,输出文件在
TMetafile.WriteWMFStream
中损坏。VCL在VCL中写入(
TMetafileHeader
)记录,然后调用将“emf”记录转换为“wmf”记录。如果边界矩形的任何维度(在调用
CreateEnhMetaFile
时使用)此函数失败大于32767。如果不检查返回值,VCL不会引发任何异常,并仅用22个字节关闭文件-仅具有“可放置标头”

即使对于小于32767的维度,“可放置标题”也可能有错误的值(请阅读答案的原因和含义以及注释的详细信息),但稍后将对此进行详细说明

我使用下面的代码来查找限制。请注意,
GetWinMetaFileBits
不会在VCL代码中使用增强的元文件进行调用

function IsDimOverLimit(W, H: Integer): Boolean;
var
  Metafile: TMetafile;
  RefDC: HDC;
begin
  Metafile := TMetafile.Create;
  Metafile.SetSize(W, H);
  RefDC := GetDC(0);
  TMetafileCanvas.Create(Metafile, RefDC).Free;
  Result := GetWinMetaFileBits(MetaFile.Handle, 0, nil, MM_ANISOTROPIC, RefDC) > 0;
  ReleaseDC(0, RefDC);
  Metafile.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
begin
  for i := 20000 to 40000 do
    if not IsDimOverLimit(100, i) then begin
      ShowMessage(SysErrorMessage(GetLastError)); // ReleaseDc and freeing meta file does not set any last error
      Break;
    end;
end;
错误是534(“算术结果超过32位”)。显然有一些有符号整数
var
  Header: TEnhMetaHeader;
begin
  DrawWMF(Rect(0, 0, Dim, Dim), 1.0, 'Original.wmf');
  DrawWMF(Rect(0, 0, Dim, Dim), 0.5, 'Scaled.wmf');

  try
    Image1.Picture.LoadFromFile('Original.wmf');
    if (TMetafile(Image1.Picture.Graphic).Width < 0) or
        (TMetafile(Image1.Picture.Graphic).Height < 0) then begin
      GetEnhMetaFileHeader(TMetafile(Image1.Picture.Graphic).Handle,
          SizeOf(Header), @Header);
      TMetafile(Image1.Picture.Graphic).Width := MulDiv(Header.rclFrame.Right,
          Header.szlDevice.cx, Header.szlMillimeters.cx * 100);
      TMetafile(Image1.Picture.Graphic).Height := MulDiv(Header.rclFrame.Bottom,
          Header.szlDevice.cy, Header.szlMillimeters.cy * 100);
  end;

  ...