通过真实角度旋转位图
image-processing
5
0

曾几何时,阅读这个问题 ,我想知道如何在不摆弄所有位的情况下旋转位图。最近, 其他人也遇到了明显的困难。

已经有很多问题以90°的间隔旋转,这在大多数情况下都不 ,但我想以真实角度旋转。优选地,由于旋转而具有调整图像尺寸的可能性,并为将添加到图像表面的零件设置自定义(透明)背景色。然后,我认为例程的签名将类似于:

procedure RotateBitmap(Bmp: TBitmap; Angle: Single; AdjustSize: Boolean; 
  BackColor: TColor);

这些答案提到以下用于构造此例程的候选对象:SetWorldTransform,PlgBlt,GDI +,但我希望看到一个(有效的)实现。

参考资料:
Stack Overflow
收藏
评论
共 2 个回答
高赞 时间 活跃

tl; dr;使用GDI +

SetWorldTransform

使用WinAPI的SetWorldTransform,您可以变换设备上下文的空间:旋转,剪切,偏移和缩放。这是通过设置XFORM类型的转换矩阵的成员来完成的。根据文档填写其成员。

procedure RotateBitmap(Bmp: TBitmap; Rads: Single; AdjustSize: Boolean;
  BkColor: TColor = clNone);
var
  C: Single;
  S: Single;
  XForm: tagXFORM;
  Tmp: TBitmap;
begin
  C := Cos(Rads);
  S := Sin(Rads);
  XForm.eM11 := C;
  XForm.eM12 := S;
  XForm.eM21 := -S;
  XForm.eM22 := C;
  Tmp := TBitmap.Create;
  try
    Tmp.TransparentColor := Bmp.TransparentColor;
    Tmp.TransparentMode := Bmp.TransparentMode;
    Tmp.Transparent := Bmp.Transparent;
    Tmp.Canvas.Brush.Color := BkColor;
    if AdjustSize then
    begin
      Tmp.Width := Round(Bmp.Width * Abs(C) + Bmp.Height * Abs(S));
      Tmp.Height := Round(Bmp.Width * Abs(S) + Bmp.Height * Abs(C));
      XForm.eDx := (Tmp.Width - Bmp.Width * C + Bmp.Height * S) / 2;
      XForm.eDy := (Tmp.Height - Bmp.Width * S - Bmp.Height * C) / 2;
    end
    else
    begin
      Tmp.Width := Bmp.Width;
      Tmp.Height := Bmp.Height;
      XForm.eDx := (Bmp.Width - Bmp.Width * C + Bmp.Height * S) / 2;
      XForm.eDy := (Bmp.Height - Bmp.Width * S - Bmp.Height * C) / 2;
    end;
    SetGraphicsMode(Tmp.Canvas.Handle, GM_ADVANCED);
    SetWorldTransform(Tmp.Canvas.Handle, XForm);
    BitBlt(Tmp.Canvas.Handle, 0, 0, Tmp.Width, Tmp.Height, Bmp.Canvas.Handle,
      0, 0, SRCCOPY);
    Bmp.Assign(Tmp);
  finally
    Tmp.Free;
  end;
end;

PlgBlt

PlgBlt函数执行从源设备上下文中的指定矩形到目标设备上下文中的指定平行四边形的位块传输。通过lpPoint参数映射源图像的角点。

procedure RotateBitmap(Bmp: TBitmap; Rads: Single; AdjustSize: Boolean;
  BkColor: TColor = clNone);
var
  C: Single;
  S: Single;
  Tmp: TBitmap;
  OffsetX: Single;
  OffsetY: Single;
  Points: array[0..2] of TPoint;
begin
  C := Cos(Rads);
  S := Sin(Rads);
  Tmp := TBitmap.Create;
  try
    Tmp.TransparentColor := Bmp.TransparentColor;
    Tmp.TransparentMode := Bmp.TransparentMode;
    Tmp.Transparent := Bmp.Transparent;
    Tmp.Canvas.Brush.Color := BkColor;
    if AdjustSize then
    begin
      Tmp.Width := Round(Bmp.Width * Abs(C) + Bmp.Height * Abs(S));
      Tmp.Height := Round(Bmp.Width * Abs(S) + Bmp.Height * Abs(C));
      OffsetX := (Tmp.Width - Bmp.Width * C + Bmp.Height * S) / 2;
      OffsetY := (Tmp.Height - Bmp.Width * S - Bmp.Height * C) / 2;
    end
    else
    begin
      Tmp.Width := Bmp.Width;
      Tmp.Height := Bmp.Height;
      OffsetX := (Bmp.Width - Bmp.Width * C + Bmp.Height * S) / 2;
      OffsetY := (Bmp.Height - Bmp.Width * S - Bmp.Height * C) / 2;
    end;
    Points[0].X := Round(OffsetX);
    Points[0].Y := Round(OffsetY);
    Points[1].X := Round(OffsetX + Bmp.Width * C);
    Points[1].Y := Round(OffsetY + Bmp.Width * S);
    Points[2].X := Round(OffsetX - Bmp.Height * S);
    Points[2].Y := Round(OffsetY + Bmp.Height * C);
    PlgBlt(Tmp.Canvas.Handle, Points, Bmp.Canvas.Handle, 0, 0, Bmp.Width,
      Bmp.Height, 0, 0, 0);
    Bmp.Assign(Tmp);
  finally
    Tmp.Free;
  end;
end;

图形32

Graphics32是专门为快速位图处理而设计的库。它需要一定的经验才能充分发挥其潜能,但是文档以及所提供的示例应该可以帮助您入门。

TBitmap32图像的旋转是通过使用许多可用转换类之一进行转换来完成的。这里需要TAffineTransformation类。首先,将图像尺寸减半到左上方,然后旋转,然后将结果移回到右下方,可能使用新的图像尺寸。

uses
  GR32, GR32_Transforms;

procedure RotateBitmap(Bmp: TBitmap32; Degs: Integer; AdjustSize: Boolean;
  BkColor: TColor = clNone; Transparent: Boolean = False); overload;
var
  Tmp: TBitmap32;
  Transformation: TAffineTransformation;
begin
  Tmp := TBitmap32.Create;
  Transformation := TAffineTransformation.Create;
  try
    Transformation.BeginUpdate;
    Transformation.SrcRect := FloatRect(0, 0, Bmp.Width, Bmp.Height);
    Transformation.Translate(-0.5 * Bmp.Width, -0.5 * Bmp.Height);
    Transformation.Rotate(0, 0, -Degs);
    if AdjustSize then
      with Transformation.GetTransformedBounds do
        Tmp.SetSize(Round(Right - Left), Round(Bottom - Top))
    else
      Tmp.SetSize(Bmp.Width, Bmp.Height);
    Transformation.Translate(0.5 * Tmp.Width, 0.5 * Tmp.Height);
    Transformation.EndUpdate;
    Tmp.Clear(Color32(BkColor));
    if not Transparent then
      Bmp.DrawMode := dmTransparent;
    Transform(Tmp, Bmp, Transformation);
    Bmp.Assign(Tmp);
    Bmp.OuterColor := Color32(BkColor);
    if Transparent then
      Bmp.DrawMode := dmTransparent;
  finally
    Transformation.Free;
    Tmp.Free;
  end;
end;

procedure RotateBitmap(Bmp: TBitmap; Degs: Integer; AdjustSize: Boolean;
  BkColor: TColor = clNone); overload;
var
  Tmp: TBitmap32;
  Transparent: Boolean;
begin
  Tmp := TBitmap32.Create;
  try
    Transparent := Bmp.Transparent;
    Tmp.Assign(Bmp);
    RotateBitmapGR32(Tmp, Degs, AdjustSize, BkColor, Transparent);
    Bmp.Assign(Tmp);
    if Transparent then
      Bmp.Transparent := True;
  finally
    Tmp.Free;
  end;
end;

GDI +

在Windows XP中引入的Microsoft的GDI + API比默认的GDI API更有效。对于Delphi 2009及更高版本,可从此处获得该库。对于较早的Delphi版本,可从此处获得该库。

在GDI +中,旋转也由变换矩阵完成。绘图的工作原理却大不相同。创建一个TGPGraphics对象,并使用其构造函数将其附加到设备上下文。随后,该对象对绘图的操作将由API转换,并将输出到目标上下文。

uses
  GDIPOBJ, GDIPAPI; // < D2009
  GdiPlus;          // >= D2009

procedure RotateBitmap(Bmp: TBitmap; Degs: Integer; AdjustSize: Boolean;
  BkColor: TColor = clNone);
var
  Tmp: TGPBitmap;
  Matrix: TGPMatrix;
  C: Single;
  S: Single;
  NewSize: TSize;
  Graphs: TGPGraphics;
  P: TGPPointF;
begin
  Tmp := TGPBitmap.Create(Bmp.Handle, Bmp.Palette);
  Matrix := TGPMatrix.Create;
  try
    Matrix.RotateAt(Degs, MakePoint(0.5 * Bmp.Width, 0.5 * Bmp.Height));
    if AdjustSize then
    begin
      C := Cos(DegToRad(Degs));
      S := Sin(DegToRad(Degs));
      NewSize.cx := Round(Bmp.Width * Abs(C) + Bmp.Height * Abs(S));
      NewSize.cy := Round(Bmp.Width * Abs(S) + Bmp.Height * Abs(C));
      Bmp.Width := NewSize.cx;
      Bmp.Height := NewSize.cy;
    end;
    Graphs := TGPGraphics.Create(Bmp.Canvas.Handle);
    try
      Graphs.Clear(ColorRefToARGB(ColorToRGB(BkColor)));
      Graphs.SetTransform(Matrix);
      Graphs.DrawImage(Tmp, (Cardinal(Bmp.Width) - Tmp.GetWidth) div 2,
        (Cardinal(Bmp.Height) - Tmp.GetHeight) div 2);
    finally
      Graphs.Free;
    end;
  finally
    Matrix.Free;
    Tmp.Free;
  end;
end;

处理透明度

上面的例程保留了fead位图的透明设置,但Graphics32解决方案除外,该解决方案需要附加的Transparent参数。

性能和图像质量

我编写了一个测试应用程序(请参见下面的完整代码),以调整各种方法的性能并比较最终的图像质量。

第一个也是最重要的结论是,GDI +在其他不使用的情况下使用了抗锯齿功能,从而获得了最佳的图像质量。 (我未尝试通过设置CompositingQualityInterpolationModeSmoothingModePixelOffsetMode来阻止抗锯齿,因此当不首选抗锯齿时,请勿使用GDI +。)

此外,到目前为止,GDI +解决方案也是最快的方法。

Test results

unit RotateTestForm;

interface

uses
  Windows, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls,
  JPEG, Math, GR32, GR32_Transforms, GDIPOBJ, GDIPAPI {, GdiPlus};

type
  TTestForm = class(TForm)
  private
    FImage: TImage;
    FOpenDialog: TOpenDialog;
    procedure FormPaint(Sender: TObject);
  public
    constructor Create(AOwner: TComponent); override;
  end;

var
  TestForm: TTestForm;

implementation

{$R *.dfm}

procedure RotateBitmapSWT(Bmp: TBitmap; Rads: Single; AdjustSize: Boolean;
  BkColor: TColor = clNone);
var
  C: Single;
  S: Single;
  XForm: TXForm;
  Tmp: TBitmap;
begin
  C := Cos(Rads);
  S := Sin(Rads);
  XForm.eM11 := C;
  XForm.eM12 := S;
  XForm.eM21 := -S;
  XForm.eM22 := C;
  Tmp := TBitmap.Create;
  try
    Tmp.TransparentColor := Bmp.TransparentColor;
    Tmp.TransparentMode := Bmp.TransparentMode;
    Tmp.Transparent := Bmp.Transparent;
    Tmp.Canvas.Brush.Color := BkColor;
    if AdjustSize then
    begin
      Tmp.Width := Round(Bmp.Width * Abs(C) + Bmp.Height * Abs(S));
      Tmp.Height := Round(Bmp.Width * Abs(S) + Bmp.Height * Abs(C));
      XForm.eDx := (Tmp.Width - Bmp.Width * C + Bmp.Height * S) / 2;
      XForm.eDy := (Tmp.Height - Bmp.Width * S - Bmp.Height * C) / 2;
    end
    else
    begin
      Tmp.Width := Bmp.Width;
      Tmp.Height := Bmp.Height;
      XForm.eDx := (Bmp.Width - Bmp.Width * C + Bmp.Height * S) / 2;
      XForm.eDy := (Bmp.Height - Bmp.Width * S - Bmp.Height * C) / 2;
    end;
    SetGraphicsMode(Tmp.Canvas.Handle, GM_ADVANCED);
    SetWorldTransform(Tmp.Canvas.Handle, XForm);
    BitBlt(Tmp.Canvas.Handle, 0, 0, Tmp.Width, Tmp.Height, Bmp.Canvas.Handle,
      0, 0, SRCCOPY);
    Bmp.Assign(Tmp);
  finally
    Tmp.Free;
  end;
end;

procedure RotateBitmapPLG(Bmp: TBitmap; Rads: Single; AdjustSize: Boolean;
  BkColor: TColor = clNone);
var
  C: Single;
  S: Single;
  Tmp: TBitmap;
  OffsetX: Single;
  OffsetY: Single;
  Points: array[0..2] of TPoint;
begin
  C := Cos(Rads);
  S := Sin(Rads);
  Tmp := TBitmap.Create;
  try
    Tmp.TransparentColor := Bmp.TransparentColor;
    Tmp.TransparentMode := Bmp.TransparentMode;
    Tmp.Transparent := Bmp.Transparent;
    Tmp.Canvas.Brush.Color := BkColor;
    if AdjustSize then
    begin
      Tmp.Width := Round(Bmp.Width * Abs(C) + Bmp.Height * Abs(S));
      Tmp.Height := Round(Bmp.Width * Abs(S) + Bmp.Height * Abs(C));
      OffsetX := (Tmp.Width - Bmp.Width * C + Bmp.Height * S) / 2;
      OffsetY := (Tmp.Height - Bmp.Width * S - Bmp.Height * C) / 2;
    end
    else
    begin
      Tmp.Width := Bmp.Width;
      Tmp.Height := Bmp.Height;
      OffsetX := (Bmp.Width - Bmp.Width * C + Bmp.Height * S) / 2;
      OffsetY := (Bmp.Height - Bmp.Width * S - Bmp.Height * C) / 2;
    end;
    Points[0].X := Round(OffsetX);
    Points[0].Y := Round(OffsetY);
    Points[1].X := Round(OffsetX + Bmp.Width * C);
    Points[1].Y := Round(OffsetY + Bmp.Width * S);
    Points[2].X := Round(OffsetX - Bmp.Height * S);
    Points[2].Y := Round(OffsetY + Bmp.Height * C);
    PlgBlt(Tmp.Canvas.Handle, Points, Bmp.Canvas.Handle, 0, 0, Bmp.Width,
      Bmp.Height, 0, 0, 0);
    Bmp.Assign(Tmp);
  finally
    Tmp.Free;
  end;
end;

procedure RotateBitmapGR32(Bmp: TBitmap32; Degs: Integer; AdjustSize: Boolean;
  BkColor: TColor = clNone; Transparent: Boolean = False); overload;
var
  Tmp: TBitmap32;
  Transformation: TAffineTransformation;
begin
  Tmp := TBitmap32.Create;
  Transformation := TAffineTransformation.Create;
  try
    Transformation.BeginUpdate;
    Transformation.SrcRect := FloatRect(0, 0, Bmp.Width, Bmp.Height);
    Transformation.Translate(-0.5 * Bmp.Width, -0.5 * Bmp.Height);
    Transformation.Rotate(0, 0, -Degs);
    if AdjustSize then
      with Transformation.GetTransformedBounds do
        Tmp.SetSize(Round(Right - Left), Round(Bottom - Top))
    else
      Tmp.SetSize(Bmp.Width, Bmp.Height);
    Transformation.Translate(0.5 * Tmp.Width, 0.5 * Tmp.Height);
    Transformation.EndUpdate;
    Tmp.Clear(Color32(BkColor));
    if not Transparent then
      Bmp.DrawMode := dmTransparent;
    Transform(Tmp, Bmp, Transformation);
    Bmp.Assign(Tmp);
    Bmp.OuterColor := Color32(BkColor);
    if Transparent then
      Bmp.DrawMode := dmTransparent;
  finally
    Transformation.Free;
    Tmp.Free;
  end;
end;

procedure RotateBitmapGR32(Bmp: TBitmap; Degs: Integer; AdjustSize: Boolean;
  BkColor: TColor = clNone); overload;
var
  Tmp: TBitmap32;
  Transparent: Boolean;
begin
  Tmp := TBitmap32.Create;
  try
    Transparent := Bmp.Transparent;
    Tmp.Assign(Bmp);
    RotateBitmapGR32(Tmp, Degs, AdjustSize, BkColor, Transparent);
    Bmp.Assign(Tmp);
    if Transparent then
      Bmp.Transparent := True;
  finally
    Tmp.Free;
  end;
end;

procedure RotateBitmapGDIP(Bmp: TBitmap; Degs: Integer; AdjustSize: Boolean;
  BkColor: TColor = clNone);
var
  Tmp: TGPBitmap;
  Matrix: TGPMatrix;
  C: Single;
  S: Single;
  NewSize: TSize;
  Graphs: TGPGraphics;
  P: TGPPointF;
begin
  Tmp := TGPBitmap.Create(Bmp.Handle, Bmp.Palette);
  Matrix := TGPMatrix.Create;
  try
    Matrix.RotateAt(Degs, MakePoint(0.5 * Bmp.Width, 0.5 * Bmp.Height));
    if AdjustSize then
    begin
      C := Cos(DegToRad(Degs));
      S := Sin(DegToRad(Degs));
      NewSize.cx := Round(Bmp.Width * Abs(C) + Bmp.Height * Abs(S));
      NewSize.cy := Round(Bmp.Width * Abs(S) + Bmp.Height * Abs(C));
      Bmp.Width := NewSize.cx;
      Bmp.Height := NewSize.cy;
    end;
    Graphs := TGPGraphics.Create(Bmp.Canvas.Handle);
    try
      Graphs.Clear(ColorRefToARGB(ColorToRGB(BkColor)));
      Graphs.SetTransform(Matrix);
      Graphs.DrawImage(Tmp, (Cardinal(Bmp.Width) - Tmp.GetWidth) div 2,
        (Cardinal(Bmp.Height) - Tmp.GetHeight) div 2);
    finally
      Graphs.Free;
    end;
  finally
    Matrix.Free;
    Tmp.Free;
  end;
end;

{ TTestForm }

constructor TTestForm.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Font.Name := 'Tahoma';
  Top := 0;
  ClientWidth := 560;
  ClientHeight := 915;
  Show;
  FImage := TImage.Create(Self);
  FOpenDialog := TOpenDialog.Create(Self);
  FOpenDialog.Title := 'Select an small sized image (min. 100 x 100)';
  FOpenDialog.Options := FOpenDialog.Options + [ofFileMustExist];
  FOpenDialog.Filter := 'JPEG|*.JPG|BMP|*.BMP';
  if FOpenDialog.Execute then
  begin
    FImage.Picture.LoadFromFile(FOpenDialog.FileName);
    OnPaint := FormPaint;
    Invalidate;
  end
  else
    Application.Terminate;
end;

procedure TTestForm.FormPaint(Sender: TObject);
var
  Img: TBitmap;
  Bmp: TBitmap;
  Bmp32: TBitmap32;
  BkColor: TColor;
  AdjustSize: Boolean;
  Degs: Integer;
  Rads: Single;
  RotCount: Integer;
  I: Integer;
  Tick: Cardinal;
begin
  Img := TBitmap.Create;
  Bmp := TBitmap.Create;
  Bmp32 := TBitmap32.Create;
  try
    BkColor := clBtnFace;
    Img.Canvas.Brush.Color := BkColor;
    Img.Width := 100;
    Img.Height := 100;
    Img.Canvas.Draw(0, 0, FImage.Picture.Graphic);
    AdjustSize := False;
    Degs := 45;
    Rads := DegToRad(Degs);
    RotCount := 1000;

    Canvas.TextOut(10, 10, 'Original:');
    Canvas.Draw(10, 30, Img);
    Canvas.TextOut(10, 140, Format('Size = %d x %d', [Img.Width, Img.Height]));
    Canvas.TextOut(10, 160, Format('Angle = %d°', [Degs]));
    Canvas.TextOut(10, 250, Format('%d rotations:', [RotCount]));

    Canvas.TextOut(120, 10, 'SetWorldTransform:');
    Bmp.Assign(Img);
    RotateBitmapSWT(Bmp, Rads, AdjustSize, BkColor);
    Canvas.Draw(120, 30, Bmp);
    if not AdjustSize then
    begin
      Tick := GetTickCount;
      for I := 0 to RotCount - 2 do
        RotateBitmapSWT(Bmp, Rads, AdjustSize, BkColor);
      Canvas.TextOut(120, 250, Format('%d msec', [GetTickCount - Tick]));
      Canvas.Draw(120, 140, Bmp);
    end;

    Canvas.TextOut(230, 10, 'PlgBlt:');
    Bmp.Assign(Img);
    RotateBitmapPLG(Bmp, Rads, AdjustSize, BkColor);
    Canvas.Draw(230, 30, Bmp);
    if not AdjustSize then
    begin
      Tick := GetTickCount;
      for I := 0 to RotCount - 2 do
        RotateBitmapPLG(Bmp, Rads, AdjustSize, BkColor);
      Canvas.TextOut(230, 250, Format('%d msec', [GetTickCount - Tick]));
      Canvas.Draw(230, 140, Bmp);
    end;

    Canvas.TextOut(340, 10, 'Graphics32:');
    Bmp.Assign(Img);
    RotateBitmapGR32(Bmp, Degs, AdjustSize, BkColor);
    Canvas.Draw(340, 30, Bmp);
    if not AdjustSize then
    begin
      Tick := GetTickCount;
      for I := 0 to RotCount - 2 do
        RotateBitmapGR32(Bmp, Degs, AdjustSize, BkColor);
      Canvas.TextOut(340, 250, Format('%d msec', [GetTickCount - Tick]));
      Canvas.Draw(340, 140, Bmp);

      // Without in between conversion to TBitmap:
      Bmp32.Assign(Img);
      Tick := GetTickCount;
      for I := 0 to RotCount - 1 do
        RotateBitmapGR32(Bmp32, Degs, AdjustSize, BkColor, False);
      Canvas.TextOut(340, 270, Format('%d msec (optimized)',
        [GetTickCount - Tick]));
    end;

    Canvas.TextOut(450, 10, 'GDI+ :');
    Bmp.Assign(Img);
    RotateBitmapGDIP(Bmp, Degs, AdjustSize, BkColor);
    Canvas.Draw(450, 30, Bmp);
    if not AdjustSize then
    begin
      Tick := GetTickCount;
      for I := 0 to RotCount - 2 do
        RotateBitmapGDIP(Bmp, Degs, AdjustSize, BkColor);
      Canvas.TextOut(450, 250, Format('%d msec', [GetTickCount - Tick]));
      Canvas.Draw(450, 140, Bmp);
    end;
  finally
    Bmp32.Free;
    Bmp.Free;
    Img.Free;
    OnPaint := nil;
  end;
end;

end.
收藏
评论

如果有人正在研究图像旋转,他们可能还会看一下Mitov视频库(非商业用途免费: 链接 )。 VCL和FireMonkey。它会处理所有底层细节,这使我们避免了NGLN出色答案所探索的那种详细编码。

在过去的两年中,我们一直在使用它,并且在我们的商业应用程序中对此感到非常满意。

它具有旋转组件,可处理静态图像和视频流。他们的库是完全多任务的,可以选择在具有英特尔自有性能库( http://software.intel.com/zh-cn/articles/intel-ipp )的英特尔芯片组上使用所有可用的内核和低级基元。

在中等硬件上,我们可以运行多个视频或bmp流,并在像素级别实时旋转,裁剪,缩放和处理。

收藏
评论
新手导航
  • 社区规范
  • 提出问题
  • 进行投票
  • 个人资料
  • 优化问题
  • 回答问题

关于我们

常见问题

内容许可

联系我们

@2020 AskGo
京ICP备20001863号