Is there a limitation on dimensions of Windows Metafiles?
我正在创建一些.wmf文件,但是其中一些似乎已损坏,无法在任何图元文件查看器中显示。经过反复试验,我发现问题是由尺寸引起的。如果我将同一图形按比例缩小以缩小尺寸,则会显示出来。
现在,我想知道图纸尺寸是否有限制,或者问题是否出在其他方面。我知道这些文件具有16位数据结构,因此我想每个维度的限制为2 ^ 16个单位(如果签名,则为2 ^ 15个单位)。但是在我的测试中,它大约是25,000。因此我不能依赖此值,因为限制可以是任何值(可能是宽*高,或者图形的分辨率可能会影响它)。我找不到描述此内容的有关.wmf文件的可靠资源。
以下是显示问题的示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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:我知道将
编辑:
如下面的答案中所述,这里有两个不同的问题:
主要问题在于文件本身,它在每个维度上都有2 ^ 15的限制。如果图形的宽度或高度超过此值,则delphi将写入损坏的文件。您可以在Sertac的答案中找到更多详细信息。
第二个问题是关于在
您的限制是32767。
跟踪VCL代码,输出文件在
即使尺寸小于32767,"可放置的标头"也可能具有错误的值(请阅读Tom的答案以及答案注释中有关原因和含义的详细信息),但稍后会对此进行详细介绍...
我使用下面的代码来查找限制。请注意,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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位")。显然有一些带符号的整数溢出。某些" mf3216.dll"(" 32位至16位元文件转换DLL")在
如前所述,虚假的"可放置标头"结构可能具有"虚假"值,这可能会阻止VCL正确播放图元文件。具体来说,VCL知道,图元文件的尺寸可能会溢出。加载图像以使其正确显示后,您可以执行简单的健全性检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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; ... |
当文档无济于事时,请查看源代码:)。如果宽度或高度太大,则文件创建失败,并且文件无效。在下面的内容中,我仅查看水平尺寸,但垂直尺寸的处理方法相同。
在Vcl.Graphics中:
1 2 3 4 5 | constructor TMetafileCanvas.CreateWithComment(AMetafile : TMetafile; ReferenceDevice: HDC; const CreatedBy, Description: String); FMetafile.MMWidth := MulDiv(FMetafile.Width, GetDeviceCaps(RefDC, HORZSIZE) * 100, GetDeviceCaps(RefDC, HORZRES)); |
如果未定义
接下来,让我们看一下用
1 2 3 4 5 6 7 8 9 10 | 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标头结构指示事物向南移动
1 2 3 4 5 6 7 8 | TMetafileHeader = record Key: Longint; Handle: SmallInt; Box: TSmallRect; // smallint members Inch: Word; Reserved: Longint; CheckSum: Word; end; |
右计算为
问题在于:我可以在机器上使用什么尺寸?
FMetaFile.MMWidth和FMetaFile.MMHeight都必须小于或等于
1 2 | MaxSmallInt * HundredthMMPerInch div UnitsPerInch or 32767 * 2540 div 96 = 866960 |
在我的测试机上,水平显示的大小和分辨率分别为677和1920。垂直显示的大小和分辨率分别为381和1080。因此,图元文件的最大尺寸变为:
1 2 | Horizontal: 866960 * 1920 div 67700 = 24587 Vertical: 866960 * 1080 div 38100 = 24575 |
已通过测试验证。
受到评论启发进行进一步调查后更新:
水平和垂直尺寸最大为32767,该图元文件可在某些应用程序f.ex中读取。 GIMP,它显示图像。可能是由于这些程序将图形的范围考虑为
OP中的过程不会显示尺寸为32767或更小的错误消息。但是,如果任一尺寸都大于先前显示的计算最大值,则不会显示该图。读取图元文件时,将使用与保存时相同的TMetafileHeader结构类型,并且
1 2 3 4 5 6 7 8 9 | 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); |
负值会波动到
1 2 3 4 | procedure TImage.Paint; ... with inherited Canvas do StretchDraw(DestRect, Picture.Graphic); |
DestRect的Right和Bottom
具有负值
我坚持认为,找到实际极限的唯一方法是针对水平和垂直尺寸和分辨率调用