Question

I need to rotate the TMetafile image, at least by 90 degrees step. It is easy to rotate it by drawing my metafile on a bitmap canvas and then rotating the bitmap, but I would prefer to keep it in vector image format. Is this possible at all? If yes, then how can I do that?

Was it helpful?

Solution

Create a second metafile. Use SetWorldTransform to create the rotation transform. Draw the first metafile onto the second and let the transform do the rest.

OTHER TIPS

The "btnRotateClick" of Andrew has bugs!

  1. Fast increasing execution time after 10-15 clicks.
  2. If the user changes the global window settings in the control panel\Display or with "dpiScaling.exe" the image shrinks at every click.

I have written an more simple version of btnRotateClick, that avoids rounding errors with div 2, but the bugs remain.

procedure TfrmPreviewImage.RotateMetafile(ClockWise: Boolean);
// Not risolved: Fast increasing rotation time after about 15 rotations.
// Not resolved: Control panel Screen Change dimension of all elements
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);
        // Angle := DegToRad(90);
        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 := image1.Picture.Metafile.Width;
      end
      else
      begin
        Fillchar(TransformMatrix, SizeOf(TransformMatrix), 0);
        // Angle := DegToRad(90);
        TransformMatrix.eM11 := 0;  // Cos(Angle);
        TransformMatrix.eM12 := 1;  // Sin(Angle);
        TransformMatrix.eM21 := -1; // -Sin(Angle);
        TransformMatrix.eM22 := 0;  // Cos(Angle);
        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;

Solution for change of global display settings:

If an user changes the global display setting in the control panel, the pixel width of all components on the form remain the same. But the screen width changes. My display has horzontaly 1680 px. The Screen.Width after a change of display setting return 1344 px. Before the world trasformation you need to correct the destination metafile size.

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

After a long search to know the 'real' dimensions of the screen i found:

const
   ENUM_CURRENT_SETTINGS: DWORD = $FFFFFFFF;

EnumDisplaySettings(nil, ENUM_CURRENT_SETTINGS, DevMode);

ScreenSize.cx := DevMode.dmPelsWidth;   

After this correcion the rotated image does not change dimensions.

Solution for increasing execution time at every rotation.

I give here a table to illustrate the problem.

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
    ...
    450         19          692     20064
    540         22         1081     36864

To avoid this never rotate an already rotated image, but rotate the original image.

The answer on the question depends on the type of program you write if you use SetWorldTransform.

An alternative approach is changing the coordinates of every metafile record. Microsoft has 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 possibilties are scarse. I have only one EMFPLUS file and one monitor. Tested on Windows8.1

Here is my code: (Delphi XE3)

unit UEmfRotate;
{

  Use:
  var
    FRotationPos : TRotationPosition;

    FRotationPos := GetNewPosition(Clockwise, FRotationPos);
    UEmfRotate.RotateMetafile(Filename, image1, FRotationPos);

}

interface

uses
  System.Types, Vcl.ExtCtrls;

type
  TRotationPosition = -3 .. 3;

procedure RotateMetafile(const Path: string; image: TImage;
  Position: TRotationPosition);
function GetNewPosition(Clockwise: boolean;
  OldPosition: TRotationPosition): 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
}

{

  All destination positions from -3 to 3 (-270.. 270)
  0     1      2     3
  WWW   AW    AAA    WA
  AAA   AW    WWW    WA
        AW           WA

  0     -1    -2     -3
  WWW   WA    AAA     AW
  AAA   WA    WWW     AW
        WA            AW

}

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

const                     // OldPosition  -3  -2  -1   0  -1  -2  -3    Clockwise
  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.

Working code sample, made by David's recommendations. Each button click will rotate the metafile, stored inside of the TImage, by 90 degrees.

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;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top