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?
3 Answers
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.

- 601,492
- 42
- 1,072
- 1,490
-
Thanks, David! As usual, you saving many hours of my life. :) I posted a code sample below – Andrew Nov 22 '13 at 05:58
The "btnRotateClick" of Andrew has bugs!
- Fast increasing execution time after 10-15 clicks.
- 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.

- 41
- 4
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;

- 3,696
- 3
- 40
- 71