むかしむかし この質問 を読んで、自分ですべてのビットを操作せずにビットマップをある程度回転させる方法を知りました。最近、 他の誰か にも明らかに問題がありました。
90°間隔での回転を処理する多くの質問があり、最も注目すべきは これ ですが、実際の角度で回転したいと思います。回転により画像サイズを調整する可能性があり、画像表面に追加されるパーツのカスタム(透明)背景色を設定することが望ましいです。次に、ルーチンのシグネチャは次のようになります。
procedure RotateBitmap(Bmp: TBitmap; Angle: Single; AdjustSize: Boolean;
BackColor: TColor);
これらの回答 このルーチンを構築するための次の候補に言及します:SetWorldTransform、PlgBlt、GDI +、しかし(効率的な)実装を望んでいます。
tl; dr;GDI +を使用
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 関数は、ソースデバイスコンテキストの指定された四角形から宛先デバイスコンテキストの指定された平行四辺形へのビットブロック転送を実行します。 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;
Graphics32 は、高速ビットマップ処理用に特別に設計されたライブラリです。完全な可能性を理解するにはある程度の経験が必要ですが、 ドキュメント と提供されている例を使用すると、作業を開始できます。
TBitmap32
画像の回転は、多くの利用可能な変換クラスの1つによって変換することによって行われます。ここでは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;
Windows XPで導入されたMicrosoftの GDI + APIは、デフォルトよりも効率的ですGDIAPI。Delphi2009以降では、ライブラリが利用可能です ここから 。古い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;
上記のルーチンは、追加のTransparent
パラメーターを必要とするGraphics32ソリューションを除いて、feadビットマップの透過設定を保持します。
さまざまなメソッドのパフォーマンスを調整し、結果の画像品質を比較するために、テストアプリケーション(以下の完全なコードを参照)を作成しました。
最初の最も重要な結論は、GDI +が他のアンチエイリアスを使用しない場所でアンチエイリアスを使用することで、最高の画質が得られるということです。 (私はCompositingQuality
、InterpolationMode
、SmoothingMode
、およびPixelOffsetMode
を設定してアンチエイリアスを防止しようとしましたが、アンチエイリアスが優先されない場合は、 GDI +を使用しないでください。)
さらに、GDI +ソリューションは、断然最速の方法でもあります。
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ビデオライブラリ(非商用利用の場合は無料: link )も確認してください。 VCLおよびFireMonkey。これは、すべての低レベルの詳細を処理します。これにより、NGLNの優れた答えが探るような詳細なコーディングを回避できます。
過去2年間使用しており、商用アプリでvery満足しています。
静的な画像とビデオストリームで動作する回転コンポーネントがあります。彼らのライブラリは完全にマルチタスクであり、オプションとして、インテル独自のパフォーマンスライブラリ( http://software.intel.com/en-us/articles/intel -ipp )
中程度のハードウェアでは、複数のビデオまたはbmpストリームを実行し、それらをリアルタイムで回転、クリップ、スケーリング、およびピクセルレベルで処理できます。