Есть ли ограничения на размеры метафайлов 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, вероятно, является безопасным пределом.

Другие вопросы по тегам