3

我需要将 TMetafile 图像旋转至少 90 度。通过在位图画布上绘制我的图元文件然后旋转位图来旋转它很容易,但我更愿意将其保留为矢量图像格式。这可能吗?如果是,那我该怎么做?

4

3 回答 3

4

创建第二个元文件。使用 SetWorldTransform 创建旋转变换。将第一个图元文件绘制到第二个图元文件上,然后让转换完成其余的工作。

于 2013-11-22T04:47:10.077 回答
3

安德鲁的“btnRotateClick”有错误!

  1. 点击 10-15 次后执行时间快速增加。
  2. 如果用户更改控制面板\显示中的全局窗口设置或使用“dpiScaling.exe”,则每次单击时图像都会缩小。

我编写了一个更简单的 btnRotateClick 版本,可以避免 div 2 的舍入错误,但仍然存在错误。

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;

更改全局显示设置的解决方案:

如果用户更改控制面板中的全局显示设置,则表单上所有组件的像素宽度保持不变。但是屏幕宽度会发生变化。我的显示器有 horzontaly 1680 像素。更改显示设置后的 Screen.Width 返回 1344 像素。在世界转换之前,您需要更正目标图元文件的大小。

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;   

在此更正之后,旋转的图像不会改变尺寸。

增加每次轮换执行时间的解决方案。

我在这里给出一个表格来说明这个问题。

累积轮换会导致执行时间增加。

使用累积旋转我的意思是旋转已经旋转的图像。

   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

为避免这种情况,切勿旋转已旋转的图像,而是旋转原始图像。

如果您使用 SetWorldTransform,问题的答案取决于您编写的程序类型。

另一种方法是更改​​每个元文件记录的坐标。Microsoft 在 2014 年发布了:[MS-EMF].pdf:增强的元文件格式。看起来工作量很大。

还有其他问题。

信息丢失

旋转的元文件丢失了作者和描述。您不能简单地在旋转之前保存此信息并在旋转后恢复此信息。属性 CreatedBy 和 Description 不可写。利用:

DestCanvas := TMetafileCanvas.CreateWithComment

也可以看看

单元 Winapi.GDIPAPI 以获取有关元文件扩展名的更多信息。

在线文档 EnumDisplaySettings。

评论

将元文件旋转为位图会造成质量损失。我决定复制并粘贴安德鲁的代码,但发现了错误。我写了下面的代码,但我的测试可能性很小。我只有一个 EMFPLUS 文件和一台显示器。在Windows8.1上测试

这是我的代码:(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.
于 2014-03-15T10:58:36.280 回答
1

工作代码示例,由 David 的建议制作。每次单击按钮都会将存储在 TImage 中的元文件旋转 90 度。

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;
于 2013-11-22T05:56:28.713 回答