按真实角度旋转位图 [英] Rotate bitmap by real angle

查看:48
本文介绍了按真实角度旋转位图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

曾几何时,在阅读

unit RotateTestForm;界面用途Windows、SysUtils、类、图形、控件、窗体、对话框、ExtCtrls、JPEG、数学、GR32、GR32_Transforms、GDIPOBJ、GDIPAPI {、GdiPlus};类型TTestForm = 类(TForm)私人的FImage:TImage;FOpenDialog: TOpenDialog;程序 FormPaint(Sender: TObject);上市构造函数创建(AOwner:TComponent);覆盖;结尾;无功测试表:TTestForm;执行{$R *.dfm}程序 RotateBitmapSWT(Bmp: TBitmap; Rads: Single; AdjustSize: Boolean;BkColor: TColor = clNone);无功C:单身;S:单身;XForm:TXForm;Tmp:TBitmap;开始C := Cos(Rads);S := 罪 (Rads);XForm.eM11 := C;XForm.eM12 := S;XForm.eM21 := -S;XForm.eM22 := C;Tmp := TBitmap.Create;尝试Tmp.TransparentColor := Bmp.TransparentColor;Tmp.TransparentMode := Bmp.TransparentMode;Tmp.Transparent := Bmp.Transparent;Tmp.Canvas.Brush.Color := BkColor;如果 AdjustSize 那么开始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;结尾别的开始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;结尾;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);最后Tmp.Free;结尾;结尾;程序 RotateBitmapPLG(Bmp: TBitmap; Rads: Single; AdjustSize: Boolean;BkColor: TColor = clNone);无功C:单身;S:单身;Tmp:TBitmap;OffsetX:单个;OffsetY:单个;点数:TPoint 的数组[0..2];开始C := Cos(Rads);S := 罪 (Rads);Tmp := TBitmap.Create;尝试Tmp.TransparentColor := Bmp.TransparentColor;Tmp.TransparentMode := Bmp.TransparentMode;Tmp.Transparent := Bmp.Transparent;Tmp.Canvas.Brush.Color := BkColor;如果 AdjustSize 那么开始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;结尾别的开始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;结尾;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);最后Tmp.Free;结尾;结尾;程序 RotateBitmapGR32(Bmp: TBitmap32; Degs: Integer; AdjustSize: Boolean;BkColor: TColor = clNone;透明:Boolean = False);超载;无功Tmp:TBitmap32;变换:TAffineTransformation;开始Tmp := TBitmap32.Create;转换 := TAffineTransformation.Create;尝试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);如果 AdjustSize 那么使用 Transformation.GetTransformedBounds 做Tmp.SetSize(Round(Right - Left), Round(Bottom - Top))别的Tmp.SetSize(Bmp.Width, Bmp.Height);Transformation.Translate(0.5 * Tmp.Width, 0.5 * Tmp.Height);Transformation.EndUpdate;Tmp.Clear(Color32(BkColor));如果不透明那么Bmp.DrawMode := dmTransparent;变换(Tmp,Bmp,变换);Bmp.Assign(Tmp);Bmp.OuterColor := Color32(BkColor);如果透明那么Bmp.DrawMode := dmTransparent;最后转换.免费;Tmp.Free;结尾;结尾;程序 RotateBitmapGR32(Bmp: TBitmap; Degs: Integer; AdjustSize: Boolean;BkColor: TColor = clNone);超载;无功Tmp:TBitmap32;透明:布尔型;开始Tmp := TBitmap32.Create;尝试透明 := Bmp.Transparent;Tmp.Assign(Bmp);RotateBitmapGR32(Tmp, Degs, AdjustSize, BkColor, Transparent);Bmp.Assign(Tmp);如果透明那么Bmp.Transparent := True;最后Tmp.Free;结尾;结尾;程序 RotateBitmapGDIP(Bmp: TBitmap; Degs: Integer; AdjustSize: Boolean;BkColor: TColor = clNone);无功Tmp:TGPBitmap;矩阵:TGP矩阵;C:单身;S:单身;新尺寸:TS尺寸;图形:TGPGraphics;P:TGPPointF;开始Tmp := TGPBitmap.Create(Bmp.Handle, Bmp.Palette);矩阵:= TGPMatrix.Create;尝试Matrix.RotateAt(Degs, MakePoint(0.5 * Bmp.Width, 0.5 * Bmp.Height));如果 AdjustSize 那么开始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;结尾;图形:= TGPGraphics.Create(Bmp.Canvas.Handle);尝试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);最后Graphs.Free;结尾;最后Matrix.Free;Tmp.Free;结尾;结尾;{ TTestForm }构造函数 TTestForm.Create(AOwner: TComponent);开始继承创建(AOwner);Font.Name := 'Tahoma';顶部:= 0;客户端宽度:= 560;客户端高度:= 915;展示;FImage := TImage.Create(Self);FOpenDialog := TOpenDialog.Create(Self);FOpenDialog.Title := '选择一个小尺寸的图像(最小 100 x 100)';FOpenDialog.Options := FOpenDialog.Options + [ofFileMustExist];FOpenDialog.Filter := 'JPEG|*.JPG|BMP|*.BMP';如果 FOpenDialog.Execute 那么开始FImage.Picture.LoadFromFile(FOpenDialog.FileName);OnPaint := FormPaint;无效;结尾别的申请.终止;结尾;程序 TTestForm.FormPaint(Sender: TObject);无功img:TBitmap;Bmp:TBitmap;Bmp32:TBitmap32;BkColor:TColor;调整大小:布尔值;度数:整数;Rads:单;RotCount:整数;I:整数;勾选:红衣主教;开始Img := TBitmap.Create;Bmp := TBitmap.Create;Bmp32 := TBitmap32.Create;尝试BkColor := clBtnFace;Img.Canvas.Brush.Color := BkColor;图像宽度:= 100;图像高度:= 100;Img.Canvas.Draw(0, 0, FImage.Picture.Graphic);调整大小:= 假;度数:= 45;Rads := DegToRad(Degs);旋转计数:= 1000;Canvas.TextOut(10, 10, '原文:');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 旋转:', [RotCount]));Canvas.TextOut(120, 10, 'SetWorldTransform:');Bmp.Assign(Img);RotateBitmapSWT(Bmp, Rads, AdjustSize, BkColor);Canvas.Draw(120, 30, Bmp);如果不是 AdjustSize 那么开始滴答:= GetTickCount;对于 I := 0 到 RotCount - 2 做RotateBitmapSWT(Bmp, Rads, AdjustSize, BkColor);Canvas.TextOut(120, 250, Format('%d msec', [GetTickCount - Tick]));Canvas.Draw(120, 140, Bmp);结尾;Canvas.TextOut(230, 10, 'PlgBlt:');Bmp.Assign(Img);RotateBitmapPLG(Bmp, Rads, AdjustSize, BkColor);Canvas.Draw(230, 30, Bmp);如果不是 AdjustSize 那么开始滴答:= GetTickCount;对于 I := 0 到 RotCount - 2 做RotateBitmapPLG(Bmp, Rads, AdjustSize, BkColor);Canvas.TextOut(230, 250, Format('%d msec', [GetTickCount - Tick]));Canvas.Draw(230, 140, Bmp);结尾;Canvas.TextOut(340, 10, 'Graphics32:');Bmp.Assign(Img);RotateBitmapGR32(Bmp, Degs, AdjustSize, BkColor);Canvas.Draw(340, 30, Bmp);如果不是 AdjustSize 那么开始滴答:= GetTickCount;对于 I := 0 到 RotCount - 2 做RotateBitmapGR32(Bmp, Degs, AdjustSize, BkColor);Canvas.TextOut(340, 250, Format('%d msec', [GetTickCount - Tick]));Canvas.Draw(340, 140, Bmp);//没有中间转换为 TBitmap:Bmp32.Assign(Img);滴答:= GetTickCount;对于 I := 0 到 RotCount - 1 做RotateBitmapGR32(Bmp32, Degs, AdjustSize, BkColor, False);Canvas.TextOut(340, 270, Format('%d msec (optimized)',[GetTickCount - 滴答]));结尾;Canvas.TextOut(450, 10, 'GDI+:');Bmp.Assign(Img);RotateBitmapGDIP(Bmp, Degs, AdjustSize, BkColor);Canvas.Draw(450, 30, Bmp);如果不是 AdjustSize 那么开始滴答:= GetTickCount;对于 I := 0 到 RotCount - 2 做RotateBitmapGDIP(Bmp, Degs, AdjustSize, BkColor);Canvas.TextOut(450, 250, Format('%d msec', [GetTickCount - Tick]));Canvas.Draw(450, 140, Bmp);结尾;最后Bmp32.免费;Bmp.Free;图像免费;OnPaint := nil;结尾;结尾;结尾.

Once upon a time, reading this question, I wondered how to rotate a bitmap by any degree without fiddling around with all the bits myself. Recently, someone else had obvious difficulties with it too.

There are already many questions dealing with rotation at 90° intervals, most notabaly this one, but I want to rotate by a real angle. Preferably with the possibility to adjust the image size due to the rotation, and with setting a custom (transparent) background color for the parts that will be added to image surface. I then suppose the signature of the routine would look something like:

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

These answers mention the following candidates for constructing this routine: SetWorldTransform, PlgBlt, GDI+, but I would like to see an (efficient) implementation.

解决方案

tl;dr; Use GDI+

SetWorldTransform

With WinAPI's SetWorldTransform you can transform the space of device context: rotate, shear, offset, and scale. This is done by setting the members of a transform matrix of type XFORM. Fill its members according the documentation.

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

The PlgBlt function performs a bit-block transfer from the specified rectangle in the source device context to the specified parallelogram in the destination device context. Map the corner points of the source image via the lpPoint parameter.

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;

Graphics32

Graphics32 is a library especially designed for fast bitmap handling. It requires some experience to grasp its full potential, but the documentation as well as the provided examples should get you started.

A rotation of a TBitmap32 image is done by transforming it by one of the many available transformation classes. The TAffineTransformation class is needed here. First, shift the image half its size to the upper left, then rotate, and shift the result back to the lower right, possibly using the new image dimensions.

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+

Introduced in Windows XP, Microsoft's GDI+ API is more efficient then the default GDI API. For Delphi 2009 and up, the library is available from here. For older Delphi versions, the library is available from here.

In GDI+ the rotation is also done by a transformation matrix. Drawing works quite differently though. Create a TGPGraphics object and attach it to a device context with its constructor. Subsequently, drawing operations on the object are translated by the API and will be output to the destination context.

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;

Handling transparency

The routines above preserve the transparent settings of the fead bitmap, with the exception of the Graphics32 solution which requires an additional Transparent parameter.

Performance and image quality

I wrote a test application (see full code below) to tune the performance of the various methods and to compare the resulting image quality.

The first and most important conclusion is that GDI+ uses anti-aliasing where the others do not, resulting in the best image quality. (I unsuccessfully tried to prevent anti-aliasing by setting CompositingQuality, InterpolationMode, SmoothingMode, and PixelOffsetMode, so when anti-aliasing is not preferred, do not use GDI+.)

Furthermore, the GDI+ solution is also the fastest method, by far.

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.

这篇关于按真实角度旋转位图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆