Когда-то, читая этот вопрос, я задавался вопросом, как повернуть растровое изображение в любом случае, не путаясь со всеми битами. В последнее время кто-то другой также сталкивался с определенными трудностями.
Есть уже много вопросов, касающихся вращения с интервалом 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
выполняется путем преобразования его одним из многих доступных классов преобразования. Здесь нужен класс 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 API GDI + более эффективен, чем API GDI по умолчанию. Для 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 + использует сглаживание, а другие нет, что приводит к лучшему качеству изображения. (Я безуспешно пытался предотвратить сглаживание, установив 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.
this library
, она также может стать одним из самых популярных фаворитов для этого теста ... А также результаты сглаживания looks impressive
(я должен попробовать это :-)
Если кто-то ищет поворот изображения, они также могут посмотреть видео-библиотеку Mitov (бесплатно для некоммерческого использования: ссылка). VCL и FireMonkey. Он заботится обо всех деталях низкого уровня, что позволяет нам избегать подробного кодирования, которое исследует превосходный ответ NGLN.
Мы использовали его в течение последних двух лет и были очень довольны этим в нашем коммерческом приложении.
Он имеет компонент rotate, который работает со статическими изображениями и видеопотоками. Их библиотека полностью многозадачна, опционально использует все основные и примитивные примитивы низкого уровня на чипсетах Intel с собственной библиотекой производительности Intel (http://software.intel.com/en-us/articles/intel-ipp)
На умеренном оборудовании мы можем запускать несколько потоков видео или BMP, которые мы вращаем, клип, масштабируем и обрабатываем на уровне пикселей в режиме реального времени.