Windows 图元文件的尺寸是否有限制?
Is there a limitation on dimensions of Windows Metafiles?
我正在创建一些 .wmf 文件,但其中一些文件似乎已损坏,无法在任何图元文件查看器中显示。经过反复试验,我发现问题是由它们的尺寸引起的。如果我按比例缩放同一张图以减小尺寸,它就会显示出来。
现在,我想知道是绘图尺寸有限制还是其他问题。我知道这些文件 have a 16-bit data structure,所以我猜每个维度的限制是 2^16 个单位(如果已签名,则为 2^15)。但在我的测试中,它大约是 25,000。所以我不能依赖这个值,因为限制可以是任何东西(可能是宽度*高度,或者绘图的分辨率可能会影响它)。我找不到有关描述此内容的 .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 文件可以解决问题,但是我为其生成文件的目标应用程序不支持增强型图元文件。
编辑:
正如下面的答案中提到的,这里有两个不同的问题:
主要问题是文件本身,每个维度都有 2^15 的限制。如果绘图的宽度或高度超过此值,delphi 将写入损坏的文件。您可以在 .
中找到更多详细信息
第二个问题是关于在 TImage
中加载文件。当您想在 delphi VCL 应用程序中显示图像时,还有另一个限制。这个是系统相关的,并且与将要绘制绘图的 DC 的 dpi 有关。 对此进行了详细描述。将 0.7 作为 Scale
传递给 DrawWMF
(上面的代码示例)在我的 PC 上重现了这种情况。生成的文件没问题,可以用其他图元文件查看器查看(我用的是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 header 结构指示事情向南发展的方向
TMetafileHeader = record
Key: Longint;
Handle: SmallInt;
Box: TSmallRect; // smallint members
Inch: Word;
Reserved: Longint;
CheckSum: Word;
end;
Box: TSmallRect
字段不能包含比 smallint
大小的值更大的坐标。
权利计算为Right := 1410417 * 96 div 2540 ( = 53307 as smallint= -12229)
。图片尺寸溢出,wmf数据无法'played'到文件
问题来了:我可以在我的机器上使用什么尺寸?
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 消息:Plug-in 崩溃:"file-wmf.exe"。
OP 中的过程不会显示维度为 32767 或更小的错误消息。但是,如果任一尺寸高于先前显示的计算最大值,则不会显示绘图。读取图元文件时,使用与保存时相同的 TMetafileHeader 结构类型,FWidth
和 FHeight
得到负值:
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);
负值波及到 DestRect
函数中的 Paint
过程,因此看不到图像。
procedure TImage.Paint;
...
with inherited Canvas do
StretchDraw(DestRect, Picture.Graphic);
DestRect 的 Right 和 Bottom 值为负
我认为找到实际限制的唯一方法是为水平和垂直尺寸和分辨率调用 GetDeviceCaps()
,并执行上述计算。但是请注意,该文件可能仍然无法在另一台机器上使用 Delphi 程序显示。将绘图尺寸保持在 20000 x 20000 以内可能是一个安全限制。
您的上限是 32767。
跟踪 VCL 代码,输出文件在 TMetafile.WriteWMFStream
中损坏。 VCL 写入 WmfPlaceableFileHeader
(TMetafileHeader
in VCL) record and then calls GetWinMetaFileBits
将 'emf' 记录转换为 'wmf' 记录。如果边界矩形的任何尺寸(调用 CreateEnhMetaFile
时使用)大于 32767,则此函数失败。不检查 return 值,VCL 不会引发任何异常并仅关闭文件 22字节 - 只有 "placeable header"。
即使维度小于 32767,"placeable header" 也可能有错误的值(请阅读 中有关原因和含义的详细信息以及对答案的评论),但稍后会详细介绍。 ..
我使用下面的代码找到了限制。请注意,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 ("Arithmetic result exceeded 32 bits")。显然有一些有符号整数溢出。某些 'mf3216.dll'(“32 位到 16 位图元文件转换 DLL”)在 GetWinMetaFileBits
对其导出的 ConvertEmfToWmf
调用期间设置错误功能,但这不会导致任何关于溢出的文档。我能找到的关于 wmf 限制的唯一官方文档是 this(其主要观点是 "use wmf only in 16 bit executables" :))。
如前所述,伪造的 "placeable header" 结构可能具有 "bogus" 值,这可能会阻止 VCL 正确播放图元文件。具体来说,VCL 所知道的图元文件的维度可能会溢出。加载图像后,您可以执行简单的健全性检查以使其正确显示:
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;
...
我正在创建一些 .wmf 文件,但其中一些文件似乎已损坏,无法在任何图元文件查看器中显示。经过反复试验,我发现问题是由它们的尺寸引起的。如果我按比例缩放同一张图以减小尺寸,它就会显示出来。
现在,我想知道是绘图尺寸有限制还是其他问题。我知道这些文件 have a 16-bit data structure,所以我猜每个维度的限制是 2^16 个单位(如果已签名,则为 2^15)。但在我的测试中,它大约是 25,000。所以我不能依赖这个值,因为限制可以是任何东西(可能是宽度*高度,或者绘图的分辨率可能会影响它)。我找不到有关描述此内容的 .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 文件可以解决问题,但是我为其生成文件的目标应用程序不支持增强型图元文件。
编辑: 正如下面的答案中提到的,这里有两个不同的问题:
主要问题是文件本身,每个维度都有 2^15 的限制。如果绘图的宽度或高度超过此值,delphi 将写入损坏的文件。您可以在
第二个问题是关于在 TImage
中加载文件。当您想在 delphi VCL 应用程序中显示图像时,还有另一个限制。这个是系统相关的,并且与将要绘制绘图的 DC 的 dpi 有关。 Scale
传递给 DrawWMF
(上面的代码示例)在我的 PC 上重现了这种情况。生成的文件没问题,可以用其他图元文件查看器查看(我用的是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 header 结构指示事情向南发展的方向
TMetafileHeader = record
Key: Longint;
Handle: SmallInt;
Box: TSmallRect; // smallint members
Inch: Word;
Reserved: Longint;
CheckSum: Word;
end;
Box: TSmallRect
字段不能包含比 smallint
大小的值更大的坐标。
权利计算为Right := 1410417 * 96 div 2540 ( = 53307 as smallint= -12229)
。图片尺寸溢出,wmf数据无法'played'到文件
问题来了:我可以在我的机器上使用什么尺寸?
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 消息:Plug-in 崩溃:"file-wmf.exe"。
OP 中的过程不会显示维度为 32767 或更小的错误消息。但是,如果任一尺寸高于先前显示的计算最大值,则不会显示绘图。读取图元文件时,使用与保存时相同的 TMetafileHeader 结构类型,FWidth
和 FHeight
得到负值:
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);
负值波及到 DestRect
函数中的 Paint
过程,因此看不到图像。
procedure TImage.Paint;
...
with inherited Canvas do
StretchDraw(DestRect, Picture.Graphic);
DestRect 的 Right 和 Bottom 值为负
我认为找到实际限制的唯一方法是为水平和垂直尺寸和分辨率调用 GetDeviceCaps()
,并执行上述计算。但是请注意,该文件可能仍然无法在另一台机器上使用 Delphi 程序显示。将绘图尺寸保持在 20000 x 20000 以内可能是一个安全限制。
您的上限是 32767。
跟踪 VCL 代码,输出文件在 TMetafile.WriteWMFStream
中损坏。 VCL 写入 WmfPlaceableFileHeader
(TMetafileHeader
in VCL) record and then calls GetWinMetaFileBits
将 'emf' 记录转换为 'wmf' 记录。如果边界矩形的任何尺寸(调用 CreateEnhMetaFile
时使用)大于 32767,则此函数失败。不检查 return 值,VCL 不会引发任何异常并仅关闭文件 22字节 - 只有 "placeable header"。
即使维度小于 32767,"placeable header" 也可能有错误的值(请阅读
我使用下面的代码找到了限制。请注意,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 ("Arithmetic result exceeded 32 bits")。显然有一些有符号整数溢出。某些 'mf3216.dll'(“32 位到 16 位图元文件转换 DLL”)在 GetWinMetaFileBits
对其导出的 ConvertEmfToWmf
调用期间设置错误功能,但这不会导致任何关于溢出的文档。我能找到的关于 wmf 限制的唯一官方文档是 this(其主要观点是 "use wmf only in 16 bit executables" :))。
如前所述,伪造的 "placeable header" 结构可能具有 "bogus" 值,这可能会阻止 VCL 正确播放图元文件。具体来说,VCL 所知道的图元文件的维度可能会溢出。加载图像后,您可以执行简单的健全性检查以使其正确显示:
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;
...