Есть ли ограничения на размеры метафайлов Windows?
Я создаю некоторые файлы.wmf, но некоторые из них кажутся поврежденными и не могут быть отображены в любой программе просмотра метафайлов. После некоторых проб и ошибок я обнаружил, что проблема связана с их размерами. Если я уменьшу размер одного и того же чертежа, чтобы уменьшить размеры, он будет показан.
Теперь я хочу знать, есть ли ограничение на размер рисунка или проблема в чем-то другом. Я знаю, что эти файлы имеют 16-битную структуру данных, поэтому я предполагаю, что ограничение будет 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 решит проблему, но целевое приложение, для которого я создаю файлы, не поддерживает расширенные метафайлы.
Изменить: как упоминалось в ответах ниже, здесь есть две разные проблемы:
Основная проблема связана с самим файлом, он имеет ограничение 2^15 для каждого измерения. Если ширина или высота чертежа превышает это значение, delphi запишет поврежденный файл. Вы можете найти более подробную информацию в ответе Сертака.
Вторая проблема заключается в загрузке файла в TImage
, Есть еще одно ограничение, когда вы хотите показать изображение в приложении Delphi VCL. Этот зависит от системы и связан с dpi DC, на котором будет нарисован чертеж. Ответ Тома описывает это в деталях. Проходя 0,7 как Scale
в DrawWMF
(пример кода выше) воспроизводит эту ситуацию на моем ПК. Сгенерированный файл в порядке и его можно просматривать с помощью других средств просмотра метафайлов (я использую MS Office Picture Manager), но VCL не может его показать, однако при загрузке файла исключение не возникает.
2 ответа
Ваш лимит составляет 32767.
Отслеживая код VCL, выходной файл поврежден в TMetafile.WriteWMFStream
, VCL пишет WmfPlaceableFileHeader
(TMetafileHeader
в VCL) запись а потом звонки GetWinMetaFileBits
преобразовать записи '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 бита"). Очевидно, есть некоторое целочисленное переполнение со знаком. Некоторые "mf3216.dll" ("DLL преобразования 32-битных в 16-битные метафайлы") устанавливают ошибку во время вызова GetWinMetaFileBits
к его вывозу ConvertEmfToWmf
функция, но это не приводит к какой-либо документации относительно переполнения. Единственная официальная документация, касающаяся ограничений wmf, которую я могу найти, такова (ее основной смысл - "использовать wmf только в 16-битных исполняемых файлах":)).
Как упоминалось ранее, структура фиктивного "размещаемого заголовка" может иметь "фиктивные" значения, и это может помешать 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;
...
Когда документы не помогают, посмотрите на источник:). Создание файла завершается неудачно, если ширина или высота слишком велики и файл становится недействительным. Далее я рассматриваю только горизонтальное измерение, но вертикальное измерение рассматривается одинаково.
В 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;
Box: TSmallRect
поле не может содержать больше координат, чем smallint
значения. Право рассчитывается как Right := 1410417 * 96 div 2540 ( = 53307 as smallint= -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 метафайл читается в некоторых приложениях, например GIMP, это показывает изображение. Возможно, это связано с тем, что программы рассматривают размеры чертежа как word
вместо SmallInt
, GIMP сообщает, что число пикселей на дюйм составляет 90, а при изменении на 96 (что является значением, используемым Delphi, GIMP разбивался с помощью сообщения "GIMP Message: Plug-in crash: "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);
Отрицательные значения распространяются на Paint
процедура в DestRect
функция и изображение поэтому не видно.
procedure TImage.Paint;
...
with inherited Canvas do
StretchDraw(DestRect, Picture.Graphic);
DestRect имеет отрицательные значения для правого и нижнего
Я утверждаю, что единственный способ узнать фактический лимит - это позвонить GetDeviceCaps()
как для горизонтального и вертикального размера и разрешения, так и выполнить вычисления выше. Однако обратите внимание, что файл все еще может не отображаться с программой Delphi на другом компьютере. Сохранение размера рисунка в пределах 20000 x 20000, вероятно, является безопасным пределом.