Есть ли ограничение на размеры метафайлов Windows?
Я создаю некоторые .wmf файлы, но некоторые из них кажутся поврежденными и не могут быть показаны в любом метафайле. После некоторых проб и ошибок я обнаружил, что проблема связана с их размерами. Если я уменьшу масштаб одного и того же чертежа, чтобы уменьшить размеры, это будет показано.
Теперь, я хочу знать, есть ли ограничение размера чертежа, или если проблема - это что-то другое. Я знаю, что эти файлы имеют 16-битную структуру данных, поэтому я предполагаю, что ограничение будет составлять 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 решит проблему, но целевое приложение, для которого я создаю файлы, не поддержка улучшенных метафайлов.
Edit:
Как упоминалось в ответах ниже, здесь есть две разные проблемы:
Основная проблема заключается в самом файле, он имеет ограничение 2 ^ 15 для каждого измерения. Если ширина или высота чертежа превышают это значение, delphi напишет поврежденный файл. Вы можете найти более подробную информацию в Sertac answer.
Вторая проблема заключается в загрузке файла в TImage
. Еще одно ограничение, когда вы хотите показать изображение в приложении Delphi VCL. Эта система зависит от системы и связана с разрешением dpi, что чертеж будет окрашен. Tom answer подробно описывает это. Передача 0,7 от Scale
до DrawWMF
(пример кода выше) воспроизводит эту ситуацию на моем ПК. Сгенерированный файл в порядке и может быть просмотрен с другими программами просмотра метафайлов (я использую диспетчер изображений MS Office), но VCL не может его показать, однако при загрузке файла не возникает никаких исключений.
Ответы
Ответ 1
Ваш лимит - 32767.
Отслеживание кода VCL, выходной файл поврежден в TMetafile.WriteWMFStream
. VCL записывает запись WmfPlaceableFileHeader
(TMetafileHeader
в VCL), а затем вызывает GetWinMetaFileBits
, чтобы записи 'emf' были преобразованы в записи 'wmf'. Эта функция не работает, если какой-либо размер ограничивающего прямоугольника (используемый при вызове CreateEnhMetaFile
) больше 32767. Не проверяя возвращаемое значение, VCL не вызывает никакого исключения и закрывает файл только с 22 байтами - место размещения ".
Даже для размеров менее 32767, "место размещения заголовка" может иметь возможные неправильные значения (читайте подробности о причине и последствиях от Tom answer и комментарии к ответу), но подробнее об этом позже...
Я использовал приведенный ниже код, чтобы найти предел. Обратите внимание, что 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" ( "32-битная до 16-разрядной библиотеки метаданных для преобразования метаданных" ) устанавливает ошибку во время вызова GetWinMetaFileBits
на экспортированную функцию ConvertEmfToWmf
, но это не приводит к какой-либо документации относительно переполнения, Единственная официальная документация, касающаяся ограничений wmf, которую я могу найти, это this (ее основная точка - "использовать 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;
...
Ответ 2
Когда документы не помогают, посмотрите на источник:). Создание файла не выполняется, если ширина или высота слишком велика, и файл становится недействительным. В дальнейшем я смотрю только горизонтальное измерение, но вертикальный размер обрабатывается одинаково.
В 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 метафайл читается с некоторыми приложениями, f.ex. GIMP, он показывает изображение. Возможно, это связано с теми программами, которые рассматривают экстенты чертежа как word
вместо smallint
. GIMP сообщил, что пиксели на дюйм равны 90, а при изменении на 96 (это значение, используемое Delphi, GIMP, обработанное с помощью "GIMP Message: Plug-in crashed:" 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, вероятно, является безопасным пределом.