Подтвердить что ты не робот

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

4b9b3361

Ответ 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, вероятно, является безопасным пределом.