Affine Transformations (Rotate, Scale, Skew)
Affine transformations allow you to rotate, scale, translate (move), and skew (shear) bitmaps while preserving parallel lines. Graphics32 provides the TAffineTransformation class in GR32_Transforms.pas to perform smooth matrix transformations on bitmap surfaces and vector paths.
In this tutorial, you will learn how to set up transformation matrices, rotate an image around its center point, and render transformed bitmaps.
1. Understanding TAffineTransformation
An affine transformation uses a 3x3 matrix to map points from source space
Translate(Dx, Dy): Shifts coordinates by. Scale(Sx, Sy): Scales coordinates by horizontal factorand vertical factor . Rotate(Cx, Cy, AngleDegrees): Rotates coordinates around center pointby specified degrees. Skew(Sx, Sy): Shears coordinates horizontally and vertically.Clear: Resets matrix to identity.
2. Rotating a Bitmap Around Its Center
To rotate an image around its center without shifting it off-screen, follow this order of operations:
- Translate origin to center:
Translate(-Width / 2, -Height / 2) - Apply rotation:
Rotate(0, 0, Angle) - Translate back to target destination center:
Translate(DstX, DstY)
uses
GR32, GR32_Transforms, GR32_Resamplers;
var
Src, Dst: TBitmap32;
Transformer: TAffineTransformation;
begin
Src := TBitmap32.Create;
Dst := TBitmap32.Create;
Transformer := TAffineTransformation.Create;
try
Src.LoadFromFile('logo.png');
Dst.SetSize(600, 600);
Dst.Clear(clWhite32); // Clear target canvas
// Enable smooth resampling on source
TKernelResampler.Create(Src).Kernel := TLanczosKernel.Create;
// Build transformation chain
Transformer.Clear;
// 1. Move origin to bitmap center
Transformer.Translate(-Src.Width * 0.5, -Src.Height * 0.5);
// 2. Rotate by 45 degrees clockwise
Transformer.Rotate(45);
// 3. Move origin back to top-left corner
Transformer.Translate(Src.Width * 0.5, Src.Height * 0.5);
// 4. Move rotated result to center of destination canvas (300, 300)
Transformer.Translate(300, 300);
// Apply transformation blit from Src into Dst
Transform(Dst, Src, Transformer);
Dst.SaveToFile('rotated_logo.png');
finally
Transformer.Free;
Dst.Free;
Src.Free;
end;
end;This sequence of translations:
- Moving the origin
- Rotating about the origin
- Moving the origin back
...is so common that it is actually built into TAffineTransformation. We just need to specify the center of rotation when we call the Rotate method and it will take care of doing it for us:
uses
GR32, GR32_Transforms, GR32_Resamplers;
var
Src, Dst: TBitmap32;
Transformer: TAffineTransformation;
begin
Src := TBitmap32.Create;
Dst := TBitmap32.Create;
Transformer := TAffineTransformation.Create;
try
Src.LoadFromFile('logo.png');
Dst.SetSize(600, 600);
Dst.Clear(clWhite32); // Clear target canvas
// Enable smooth resampling on source
TKernelResampler.Create(Src).Kernel := TLanczosKernel.Create;
// Build transformation chain
Transformer.Clear;
// 1. Rotate by 45 degrees clockwise about the center
Transformer.Rotate(Src.Width * 0.5, Src.Height * 0.5, 45);
// 2. Move rotated result to center of destination canvas (300, 300)
Transformer.Translate(300, 300);
// Apply transformation blit from Src into Dst
Transform(Dst, Src, Transformer);
Dst.SaveToFile('rotated_logo.png');
finally
Transformer.Free;
Dst.Free;
Src.Free;
end;
end;In the above, the size of the result bitmap is hardcoded and the position of the rotated image within it isn't quite perfect. We'll take care of that in just a moment. Hang on.
TIP
Have you noticed that instead of dividing by 2 (e.g. Width / 2), we are multiplying by 0.5 (e.g. Width * 0.5)?
The reason is that division is expensive (i.e. slow) while multiplication is relatively cheap (i.e. fast). This technique, called reciprocal multiplication, is a very common optimization.
3. Combining Scaling, Skewing, and Rotation
Affine operations are cumulative; Order matters! Performing rotation before translation yields a completely different result than translation before rotation.
var
Transformer: TAffineTransformation;
begin
Transformer := TAffineTransformation.Create;
try
Transformer.Clear;
// Step 1: Scale to 150% horizontally, 80% vertically
Transformer.Scale(1.5, 0.8);
// Step 2: Skew horizontally by 15 degrees
Transformer.Skew(15, 0);
// Step 3: Rotate by 30 degrees
Transformer.Rotate(30);
// Step 4: Translate to canvas position (200, 150)
Transformer.Translate(200, 150);
// Perform transform
Transform(DstBitmap, SrcBitmap, Transformer);
finally
Transformer.Free;
end;
end;4. Automatic Transformed Bounds Calculation
When rotating or skewing an image, its bounding box expands. TAffineTransformation can automatically calculate the required destination canvas dimensions using GetTransformedBounds.
var
Src, Dst: TBitmap32;
Transformer: TAffineTransformation;
DstRect: TFloatRect;
begin
Src := TBitmap32.Create;
Dst := TBitmap32.Create;
Transformer := TAffineTransformation.Create;
try
Src.LoadFromFile('photo.jpg');
// Setup rotation around source center
Transformer.Clear;
Transformer.Rotate(Src.Width * 0.5, Src.Height * 0.5, 33); // 33 degree tilt
// Calculate exact bounding rectangle required for transformed image
DstRect := Transformer.GetTransformedBounds(Src.BoundsRect);
// Translate matrix so the transformation result is centered in the destination bitmap
// The translation below is just two translations rolled into one:
// 1. Translate(-Src.Width * 0.5, -Src.Height * 0.5)
// 2. Translate(DstRect.Width * 0.5, DstRect.Height * 0.5)
Transformer.Translate((DstRect.Width - Src.Width) * 0.5, (DstRect.Height - Src.Height) * 0.5);
// Resize destination bitmap to fit transformed dimensions perfectly
Dst.SetSize(Ceil(DstRect.Width), Ceil(DstRect.Height));
// Transform without cropping edge pixels
Transform(Dst, Src, Transformer);
Dst.SaveToFile('fitted_rotation.png');
finally
Transformer.Free;
Dst.Free;
Src.Free;
end;
end;Summary
In this tutorial, you learned:
- How
TAffineTransformationmanages 2D matrix transformations. - The importance of matrix operation order (Translate -> Rotate/Scale -> Translate).
- How to rotate bitmaps around custom center points.
- How to use
GetTransformedBoundsto resize target canvases so transformed images are never cropped.
Next, proceed to Vector Polygons & Path Drawing to explore scalable vector graphics in Graphics32!