Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
KANSoftWare

Вращение TMetafile без преобразования в формат bitmap

Delphi , Графика и Игры , MetaFile

Возможно ли повернуть векторное изображение TMetafile без преобразования в формат bitmap?

Мне нужно повернуть изображение TMetafile хотя бы на 90 градусов. Легко можно повернуть его, нарисовав его на полотне bitmap, а затем повернув bitmap, но я бы предпочел сохранить его в формате векторного изображения. Возможно ли это вообще? Если да, то как это сделать?

Подтвержденный ответ: Создайте второй metafile. Используйте SetWorldTransform для создания трансформации вращения. Нарисуйте первый metafile на второй и дайте трансформации сделать остальное.

Комментарий: Спасибо, Дэвид! Как всегда, ты спас мне много часов жизни. :) Я привел пример кода ниже.

Код:

procedure TfMain.btnRotateClick(Sender: TObject);
var
  SourceMetafile: TMetafile;
  DestMetafile: TMetafile;
  DestCanvas: TMetafileCanvas;
  TransformMatrix: XFORM;
  Angle: Double;
begin
  Assert(imgRender.Picture.Graphic is TMetafile);
  SourceMetafile := imgRender.Picture.Graphic as TMetafile;
  DestMetafile := TMetafile.Create();
  DestMetafile.Width := SourceMetafile.Height;
  DestMetafile.Height := SourceMetafile.Width;
  try
    DestCanvas := TMetafileCanvas.Create(DestMetafile, Canvas.Handle);
    try
      SetGraphicsMode(DestCanvas.Handle, GM_ADVANCED);

      ZeroMemory(@TransformMatrix, SizeOf(TransformMatrix));
      TransformMatrix.eM11 := 1;
      TransformMatrix.eM12 := 0;
      TransformMatrix.eM21 := 0;
      TransformMatrix.eM22 := 1;
      TransformMatrix.eDx := -SourceMetafile.Width div 2;
      TransformMatrix.eDy := -SourceMetafile.Height div 2;
      SetWorldTransform(DestCanvas.Handle, TransformMatrix);

      ZeroMemory(@TransformMatrix, SizeOf(TransformMatrix));
      Angle := DegToRad(90);
      TransformMatrix.eM11 := Cos(Angle);
      TransformMatrix.eM12 := Sin(Angle);
      TransformMatrix.eM21 := -Sin(Angle);
      TransformMatrix.eM22 := Cos(Angle);
      TransformMatrix.eDx := 0;
      TransformMatrix.eDy := 0;
      ModifyWorldTransform(DestCanvas.Handle, TransformMatrix, MWT_RIGHTMULTIPLY);

      ZeroMemory(@TransformMatrix, SizeOf(TransformMatrix));
      TransformMatrix.eM11 := 1;
      TransformMatrix.eM12 := 0;
      TransformMatrix.eM21 := 0;
      TransformMatrix.eM22 := 1;
      TransformMatrix.eDx := SourceMetafile.Height div 2;
      TransformMatrix.eDy := SourceMetafile.Width div 2;
      ModifyWorldTransform(DestCanvas.Handle, TransformMatrix, MWT_RIGHTMULTIPLY);

      DestCanvas.Draw(0, 0, SourceMetafile);
    finally
      DestCanvas.Free();
    end;

    imgRender.Picture.Assign(DestMetafile);
  finally
    DestMetafile.Free();
  end;
end;

Альтернативный ответ: У кода btnRotateClick есть ошибки!

  1. С каждым нажатием кнопки время выполнения увеличивается.
  2. При изменении пользователем глобальных настроек экрана в панели управления или с помощью "dpiScaling.exe" изображение уменьшается при каждом нажатии кнопки.

Я написал более простую версию btnRotateClick, которая избегает ошибок округления при делении на 2, но ошибки остаются.

Код:

procedure TfrmPreviewImage.RotateMetafile(ClockWise: Boolean);
var
  DestMetafile: TMetafile;
  DestCanvas: TMetafileCanvas;
  TransformMatrix: XFORM;
begin
  Assert(Image1.Picture.Graphic is TMetafile);

  DestMetafile := TMetafile.Create;
  DestMetafile.Enhanced := True;
  DestMetafile.SetSize(Image1.Picture.Metafile.Height, Image1.Picture.Metafile.Width);
  try
    DestCanvas := TMetafileCanvas.Create(DestMetafile, Canvas.Handle);
    DestCanvas.Lock;
    try
      SetGraphicsMode(DestCanvas.Handle, GM_ADVANCED);
      SetMapMode(DestCanvas.Handle, MM_TEXT);

      if ClockWise then
      begin
        Fillchar(TransformMatrix, SizeOf(TransformMatrix), 0);
        TransformMatrix.eM11 := 0;
        TransformMatrix.eM12 := -1;
        TransformMatrix.eM21 := 1;
        TransformMatrix.eM22 := 0;
        TransformMatrix.eDx := 0;
        TransformMatrix.eDy := Image1.Picture.Metafile.Width;
      end
      else
      begin
        Fillchar(TransformMatrix, SizeOf(TransformMatrix), 0);
        TransformMatrix.eM11 := 0;
        TransformMatrix.eM12 := 1;
        TransformMatrix.eM21 := -1;
        TransformMatrix.eM22 := 0;
        TransformMatrix.eDx := Image1.Picture.Metafile.Height;
        TransformMatrix.eDy := 0;
      end;
      SetWorldTransform(DestCanvas.Handle, TransformMatrix);

      DestCanvas.Draw(0, 0, Image1.Picture.Graphic);
    finally
      DestCanvas.Unlock;
      DestCanvas.Free();
    end;

    Image1.Picture.Metafile.Assign(DestMetafile);
  finally
    DestMetafile.Free;
  end;
end;

Решение для изменения глобальных настроек экрана: Если пользователь меняет глобальные настройки экрана в панели управления, пиксельная ширина всех компонентов на форме остается прежней, но ширина экрана меняется. Мое отображающее устройство имеет горизонтальную ширину 1680 пикселей. Ширина экрана после изменения настроек экрана возвращает 1344 пикселя. Перед мировой трансформацией необходимо скорректировать размер целевого metafile.

w1 := MulDiv(w, Screen.Width, ScreenSize.cx);
h1 := MulDiv(h, Screen.Height, ScreenSize.cy);
DestMetafile.SetSize(h1, w1);

После долгих поисков, чтобы узнать "настоящие" размеры экрана, я нашел:

const
  ENUM_CURRENT_SETTINGS: DWORD = $FFFFFFFF;

EnumDisplaySettings(nil, ENUM_CURRENT_SETTINGS, DevMode);

ScreenSize.cx := DevMode.dmPelsWidth;

После этой корректировки повернутое изображение не меняет размеров.

Решение для увеличения времени выполнения при каждом вращении: Таблица, иллюстрирующая проблему:

Увеличение времени выполнения при каждом вращении.

При накопленном вращении я имею в виду вращение уже повернутого изображения.

Angle nHandles nRecords Size (Bytes) 0 4 173 4192 90 7 214 5372 180 10 273 6998 ... 450 19 692 20064 540 22 1081 36864

Чтобы избежать этого, никогда не вращайте уже повернутое изображение, а вращайте исходное изображение.

Ответ на вопрос зависит от типа программы, которую вы пишете, если вы используете SetWorldTransform.

ALTERNATIVE APPROACH: Change the coordinates of every metafile record. Microsoft published in 2014: [MS-EMF].pdf: Enhanced Metafile Format. Seems a lot of work.

There are other problems.

There is a loss of information

The rotated Metafile has lost the author and description. You can not simply save this information before rotating and restore this information after rotating. The properties CreatedBy and Description are not writable. Use:

DestCanvas := TMetafileCanvas.CreateWithComment

See also

unit Winapi.GDIPAPI for more information on metafile extensions.

online documentation EnumDisplaySettings.

Remarks

Rotating a metafile as bitmap gives a quality loss. I decided to copy and paste the code of Andrew, but found bugs. I wrote the code below, but my testing possibilities are scarse. I have only one EMFPLUS file and one monitor. Tested on Windows8.1

Here is my code: (Delphi XE3)

Unit UEmfRotate;

uses System.Types, Vcl.ExtCtrls;

type TRotationPosition = -3 .. 3;

procedure RotateMetafile(const Path: string; image: TImage; Position: TRotationPosition);

implementation

uses Winapi.Windows, Vcl.Graphics, Vcl.Forms, System.Math;

{ Resolved: Fast increasing rotation time after about 15 rotations. Resolved: Control panel Display Change (dimension of all elements) Resolved: Loose of CreatedBy and Description after rotation }

type TDestinationArray = array [boolean, TRotationPosition] of TRotationPosition; TDegrees = array [TRotationPosition] of cardinal;

const { All destination positions from -3 to 3 (-270.. 270) 0 1 2 3 WWW AW AAA WA AAA AW WWW WA AW AW WA 0 -1 -2 -3 WWW WA AAA AW WWW AW AW WA WA AW } DestinationArray: TDestinationArray = (( 0, -3, -2, -1, 0, 1, 2), // False (-2, -1, 0, 1, 2, 3, 0)); // True Position -3 -2 -1 0 1 2 3 Degrees: TDegrees = (90, 180, 270, 0, 90, 180, 270);

function GetNewPosition(Clockwise: boolean; OldPosition: TRotationPosition): TRotationPosition; begin Result := DestinationArray[Clockwise, OldPosition]; end;

function GetDegrees(Position: Integer): cardinal; begin Result := Degrees[Position]; end;

function GetScreenSize(out Size: System.Types.TSize): boolean; { Used to correct for a change in windows global display settings. } const ENUM_CURRENT_SETTINGS: DWORD = $FFFFFFFF; var DevMode: TDevMode; begin Size.cx := 0; Size.cy := 0; DevMode.dmSize := SizeOf(TDevMode); Result := EnumDisplaySettings(nil, ENUM_CURRENT_SETTINGS, DevMode); if Result then begin Size.cx := DevMode.dmPelsWidth; Size.cy := DevMode.dmPelsHeight; end; end;

procedure RotateMetafile90(image: TImage); var DestMetafile: TMetafile; DestCanvas: TMetafileCanvas; TransformMatrix: XFORM; w, h: Integer; w1, h1: Integer; ScreenSize: System.Types.TSize; begin w := image.Picture.Width; h := image.Picture.Height; // Get screen dimension independent of the control panel display settings. if GetScreenSize(ScreenSize) then begin w1 := MulDiv(w, Screen.Width, ScreenSize.cx); h1 := MulDiv(h, Screen.Height, ScreenSize.cy); end else begin // Can not do anything w1 := w; h1 := h; end; DestMetafile := TMetafile.Create; DestMetafile.Enhanced := True; DestMetafile.SetSize(h1, w1); try DestCanvas := TMetafileCanvas.CreateWithComment(DestMetafile, 0, image.Picture.Metafile.CreatedBy, image.Picture.Metafile.Description); DestCanvas.Lock; Try SetGraphicsMode(DestCanvas.Handle, GM_ADVANCED); SetMapMode(DestCanvas.Handle, MM_TEXT);

Fillchar(TransformMatrix, SizeOf(TransformMatrix), 0); TransformMatrix.eM11 := 0; // Cos(Angle); TransformMatrix.eM12 := 1; // Sin(Angle); TransformMatrix.eM21 := -1; // -Sin(Angle); TransformMatrix.eM22 := 0; // Cos(Angle); TransformMatrix.eDx := h; TransformMatrix.eDy := 0;

SetWorldTransform(DestCanvas.Handle, TransformMatrix);

DestCanvas.Draw(0, 0, image.Picture.Graphic); // Same as Play

Finally DestCanvas.Unlock; DestCanvas.Free(); End;

image.Picture := nil; image.Picture.Metafile.Assign(DestMetafile);

finally DestMetafile.Free; end; end;

procedure RotateMetafile180(image: TImage); var DestMetafile: TMetafile; DestCanvas: TMetafileCanvas; TransformMatrix: XFORM; w, h: Integer; w1, h1: Integer; ScreenSize: System.Types.TSize; begin w := image.Picture.Width; h := image.Picture.Height; // Get screen dimension independent of the control panel display settings. if GetScreenSize(ScreenSize) then begin w1 := MulDiv(w, Screen.Width, ScreenSize.cx); h1 := MulDiv(h, Screen.Height, ScreenSize.cy); end else begin // Can not do anything w1 := w; h1 := h; end; DestMetafile := TMetafile.Create; DestMetafile.Enhanced := True; DestMetafile.SetSize(w1, h1); try DestCanvas := TMetafileCanvas.CreateWithComment(DestMetafile, 0, image.Picture.Metafile.CreatedBy, image.Picture.Metafile.Description); DestCanvas.Lock; Try SetGraphicsMode(DestCanvas.Handle, GM_ADVANCED); SetMapMode(DestCanvas.Handle, MM_TEXT);

Fillchar(TransformMatrix, SizeOf(TransformMatrix), 0); TransformMatrix.eM11 := -1; // Cos(Angle); TransformMatrix.eM12 := 0; // Sin(Angle); TransformMatrix.eM21 := 0; // -Sin(Angle); TransformMatrix.eM22 := -1; // Cos(Angle); TransformMatrix.eDx := w; TransformMatrix.eDy := h;

SetWorldTransform(DestCanvas.Handle, TransformMatrix);

DestCanvas.Draw(0, 0, image.Picture.Graphic); // Same as Play

Finally DestCanvas.Unlock; DestCanvas.Free(); End;

image.Picture := nil; image.Picture.Metafile.Assign(DestMetafile);

finally DestMetafile.Free; end; end;

procedure RotateMetafile270(image: TImage); var DestMetafile: TMetafile; DestCanvas: TMetafileCanvas; TransformMatrix: XFORM; w, h: Integer; w1, h1: Integer; ScreenSize: System.Types.TSize; begin w := image.Picture.Width; h := image.Picture.Height; // Get screen dimension independent of the control panel display settings. if GetScreenSize(ScreenSize) then begin w1 := MulDiv(w, Screen.Width, ScreenSize.cx); h1 := MulDiv(h, Screen.Height, ScreenSize.cy); end else begin // Can not do anything w1 := w; h1 := h; end; DestMetafile := TMetafile.Create; DestMetafile.Enhanced := True; DestMetafile.SetSize(h1, w1); try DestCanvas := TMetafileCanvas.CreateWithComment(DestMetafile, 0, image.Picture.Metafile.CreatedBy, image.Picture.Metafile.Description); DestCanvas.Lock; Try SetGraphicsMode(DestCanvas.Handle, GM_ADVANCED); SetMapMode(DestCanvas.Handle, MM_TEXT);

Fillchar(TransformMatrix, SizeOf(TransformMatrix), 0); TransformMatrix.eM11 := 0; // Cos(Angle); TransformMatrix.eM12 := -1; // Sin(Angle); TransformMatrix.eM21 := 1; // -Sin(Angle); TransformMatrix.eM22 := 0; // Cos(Angle); TransformMatrix.eDx := 0; TransformMatrix.eDy := w;

SetWorldTransform(DestCanvas.Handle, TransformMatrix);

DestCanvas.Draw(0, 0, image.Picture.Graphic); // Same as Play

Finally DestCanvas.Unlock; DestCanvas.Free(); End;

image.Picture := nil; image.Picture.Metafile.Assign(DestMetafile);

finally DestMetafile.Free; end; end;

procedure RotateMetafile(const Path: string; image: TImage; Position: TRotationPosition); { Cumulative rotating causes increasing execution time With cumulative rotating i mean rotate an image already rotated ENHMETAHEADER Size Angle nHandles nRecords (Bytes) 0 4 173 4192 90 7 214 5372 180 10 273 6998 270 13 354 9352 360 16 479 13212 450 19 692 20064 540 22 1081 36864 To avoid this never rotate an already rotated image, but rotate the original image. } begin image.Picture.Metafile.LoadFromFile(Path); Assert(image.Picture.Graphic is TMetafile); case GetDegrees(Position) of 90: RotateMetafile90(image); 180: RotateMetafile180(image); 270: RotateMetafile270(image); end; // image.Picture.SaveToFile('emf.emf'); end; end. ``` В заключение, вращение изображения TMetafile без преобразования в формат bitmap возможно с помощью создания второго metafile и использования SetWorldTransform для создания трансформации вращения. Затем первый metafile рисуется на втором, и трансформация делает остальное. При этом необходимо учитывать потерю информации, такую как автор и описание, и использовать TMetafileCanvas.CreateWithComment для их сохранения. Также важно учитывать изменение глобальных настроек экрана и корректировать размер целевого metafile с помощью MulDiv и EnumDisplaySettings.

Создано по материалам из источника по ссылке.

Вопрос о том, возможно ли повернуть векторное изображение TMetafile без преобразования в формат bitmap.


Комментарии и вопросы

Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.


:: Главная :: MetaFile ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-12-22 20:14:06
2025-06-16 16:35:07/0.0058460235595703/1