ventajas vectorial usos una que modo mapa imagen formatos formato entre diferencia desventajas colores delphi image-processing rotation

delphi - vectorial - modo de mapa de bits



Girar mapa de bits por ángulo real (2)

Érase una vez, al leer esta pregunta , me pregunté cómo rotar un mapa de bits en cualquier grado sin tener que jugar con todos los bits. Recientemente, otra persona también tuvo dificultades obvias.

Ya hay muchas preguntas relacionadas con la rotación a intervalos de 90 °, sobre todo esta , pero quiero rotar en un ángulo real. Preferiblemente con la posibilidad de ajustar el tamaño de la imagen debido a la rotación, y con la configuración de un color de fondo personalizado (transparente) para las partes que se agregarán a la superficie de la imagen. Entonces supongo que la firma de la rutina sería algo así como:

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

Estas respuestas mencionan los siguientes candidatos para construir esta rutina: SetWorldTransform, PlgBlt, GDI +, pero me gustaría ver una implementación (eficiente).


tl; dr; Use GDI +

SetWorldTransform

Con SetWorldTransform de SetWorldTransform puede transformar el espacio del contexto del dispositivo: rotar, cizallar, desplazar y escalar. Esto se hace configurando los miembros de una matriz de transformación de tipo XFORM. Complete sus miembros de acuerdo con la documentación .

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

La función PlgBlt realiza una transferencia de bloques de bits desde el rectángulo especificado en el contexto del dispositivo fuente al paralelogramo especificado en el contexto del dispositivo de destino. Asigne los puntos de esquina de la imagen de origen a través del parámetro 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

Graphics32 es una biblioteca especialmente diseñada para el manejo rápido de mapas de bits. Requiere algo de experiencia para aprovechar todo su potencial, pero la documentación , así como los ejemplos proporcionados, deberían ayudarlo a comenzar.

La rotación de una imagen TBitmap32 se realiza transformándola en una de las muchas clases de transformación disponibles. La clase TAffineTransformation es necesaria aquí. Primero, desplace la imagen a la mitad de su tamaño hacia la esquina superior izquierda, luego gírela y desplace el resultado nuevamente hacia la esquina inferior derecha, posiblemente utilizando las nuevas dimensiones de la imagen.

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 +

Introducido en Windows XP, la API GDI+ Microsoft es más eficiente que la API GDI predeterminada. Para Delphi 2009 en adelante, la biblioteca está disponible desde aquí . Para versiones anteriores de Delphi, la biblioteca está disponible desde aquí .

En GDI +, la rotación también se realiza mediante una matriz de transformación. El dibujo funciona de manera bastante diferente. Cree un objeto TGPGraphics y adjúntelo a un contexto de dispositivo con su constructor. Posteriormente, las operaciones de dibujo en el objeto son traducidas por la API y se enviarán al contexto de destino.

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;

Manejo de transparencia

Las rutinas anteriores conservan las configuraciones transparentes del mapa de bits fead, con la excepción de la solución Graphics32 que requiere un parámetro Transparent adicional.

Rendimiento y calidad de imagen

Escribí una aplicación de prueba (vea el código completo a continuación) para ajustar el rendimiento de los diversos métodos y para comparar la calidad de imagen resultante.

La primera y más importante conclusión es que GDI + usa anti-aliasing cuando los otros no, lo que resulta en la mejor calidad de imagen. (Sin éxito, traté de evitar el anti-aliasing configurando CompositingQuality , InterpolationMode , SmoothingMode , y PixelOffsetMode , por lo que cuando no se prefiere el anti-aliasing, no use GDI +.)

Además, la solución GDI + es también el método más rápido, de lejos.

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.


Si alguien está mirando la rotación de imágenes, también puede echar un vistazo a la biblioteca de videos de Mitov (gratis para uso no comercial: link ). VCL y FireMonkey. Cuida todos los detalles de bajo nivel, lo que nos permite evitar el tipo de codificación detallada que explora la excelente respuesta de NGLN.

Lo hemos usado durante los últimos dos años y hemos estado muy contentos con él en nuestra aplicación comercial.

Tiene un componente de rotación que funciona con imágenes estáticas y secuencias de video. Su biblioteca es totalmente multi-tarea, opcionalmente utilizando todos los núcleos y primitivas de bajo nivel disponibles, en los conjuntos de chips Intel con la biblioteca de rendimiento propia de Intel ( http://software.intel.com/en-us/articles/intel-ipp )

En hardware moderado, podemos ejecutar múltiples flujos de video o bmp que rotamos, recortamos, escalamos y procesamos a nivel de píxel, en tiempo real.