Fast Pixel Access & Direct Manipulation
When building real-time image processing, games, procedural textures, or computer vision algorithms, standard drawing functions can be too slow. Graphics32 gives you direct pointer access to the raw 32-bit pixel array, allowing high-speed pixel manipulation that runs at native C-like speed.
In this tutorial, you will learn the three ways to access pixels in TBitmap32, ranging from simple coordinate indexing to maximum-performance pointer iteration.
1. Pixel vs PixelS Access
TBitmap32 overall provides three 2D properties for reading and writing pixels by
Pixel[X, Y]: Direct access without bounds checking. Extremely fast, but passing coordinates outsidewill cause memory access violations or corrupt memory! PixelS[X, Y]: Safe access with automatic clipping. Iffalls outside the bitmap bounds, reads return $00000000(transparent black) by default[1] and writes are safely ignored.PixelW[X, Y]: Safe access with automatic wrapping. Iffalls outside the bitmap bounds, the pixel coordinates wrap according to the value of the WrapModeproperty (clamp, repeat, or mirror/reflect).
var
Bitmap: TBitmap32;
Color: TColor32;
begin
Bitmap := TBitmap32.Create;
try
Bitmap.SetSize(100, 100);
// 1. Safe access - checks bounds automatically
// Safe even if coordinates are outside bounds
Bitmap.PixelS[150, -10] := clRed32; // Ignored safely without crashing
// 2. Unchecked access - fast, but requires valid coordinates!
Bitmap.Pixel[50, 50] := clBlue32;
Color := Bitmap.Pixel[50, 50];
// 3. Wrapped access - ensures out-of-bounds coordinates become in-bounds
Bitmap.WrapMode := wmClamp;
Bitmap.PixelW[150, 150] := clGreen32; // Sets the [99, 99] pixel
Color := Bitmap.Pixel[-50, 50]; // Reads the [0, 50] pixel
finally
Bitmap.Free;
end;
end;2. Fast Row Pointer Iteration with ScanLine
While Pixel[X, Y] is convenient, accessing 2D coordinates in nested loops incurs coordinate arithmetic overhead. For image processing loops, the standard Graphics32 pattern uses ScanLine[Y].
ScanLine[Y] returns a PColor32Array pointer directly targeting the first pixel of row
procedure ConvertToGrayscale(Bitmap: TBitmap32);
var
X, Y: Integer;
Row: PColor32Array;
Color: TColor32;
R, G, B, A, Gray: Byte;
begin
for Y := 0 to Bitmap.Height - 1 do
begin
// Obtain direct row pointer for row Y
Row := Bitmap.ScanLine[Y];
for X := 0 to Bitmap.Width - 1 do
begin
Color := Row[X];
// Extract ARGB channels
R := RedComponent(Color);
G := GreenComponent(Color);
B := BlueComponent(Color);
A := AlphaComponent(Color);
// Standard perceived luminance weighting: 0.299*R + 0.587*G + 0.114*B
// Same as: Intensity(Color)
Gray := Round(0.299 * R + 0.587 * G + 0.114 * B);
// Write gray pixel back preserving original alpha channel
// Same as: Gray32(Gray, A)
Row[X] := Color32(Gray, Gray, Gray, A);
end;
end;
Bitmap.Changed; // Notify control/listeners that pixels changed
end;3. Flat Memory Access with Bits Array
In TBitmap32, pixels are stored sequentially in a contiguous memory block row by row (top-to-bottom, left-to-right). You can access this entire memory buffer as a flat 1D array via the Bits pointer property (PColor32Array).
This approach eliminates the row loop entirely, enabling ultra-fast whole-image operations!
procedure InvertColors(Bitmap: TBitmap32);
var
P: PColor32Array;
I, TotalPixels: Integer;
Color: TColor32;
R, G, B, A: Byte;
begin
TotalPixels := Bitmap.Width * Bitmap.Height;
if TotalPixels = 0 then
Exit;
// Get pointer to the start of the entire pixel buffer
P := Bitmap.Bits;
for I := 0 to TotalPixels - 1 do
begin
Color := P[I];
// Extract channels
R := RedComponent(Color);
G := GreenComponent(Color);
B := BlueComponent(Color);
A := AlphaComponent(Color);
// Invert RGB channels while preserving original Alpha
P[I] := Color32(255 - R, 255 - G, 255 - B, A);
// We actually have a function that does exactly this,
// called InvertColor, but you get the picture - so to say.
end;
Bitmap.Changed;
end;INFO
Unlike TBitmap, which store pixel data in either top-down or bottom-up order (and bottom-up by default), TBitmap32 always store pixel data in top-down order.
4. Adjusting Brightness & Contrast Example
Here is a complete practical snippet demonstrating brightness adjustment using flat Bits pointer iteration:
uses
GR32_LowLevel; // required for the Clamp() function
procedure AdjustBrightness(Bitmap: TBitmap32; Amount: Integer);
var
P: PColor32Array;
I, TotalPixels: Integer;
Color: TColor32;
R, G, B, A: Byte;
begin
TotalPixels := Bitmap.Width * Bitmap.Height;
if TotalPixels = 0 then
Exit;
P := Bitmap.Bits;
for I := 0 to TotalPixels - 1 do
begin
Color := P[I];
Color32Components(Color, R, G, B, A);
// Add brightness offset and clamp to valid 8-bit range
R := Clamp(R + Amount);
G := Clamp(G + Amount);
B := Clamp(B + Amount);
P[I] := Color32(R, G, B, A);
end;
Bitmap.Changed;
end;And here is another snippet demonstrating contrast adjustment. We use a slightly different technique here for better performance: A color lookup table:
uses
GR32_LowLevel;
procedure AdjustContrast(Bitmap: TBitmap32; AContrast: Integer);
var
Count: Integer;
LUT: array[Byte] of Byte;
Factor: Single;
P: PColor32Entry;
I, TotalPixels: Integer;
begin
Count := Bitmap.Width * Bitmap.Height;
if Count = 0 then
Exit;
// 1. Calculate the contrast factor
// AContrast values range from -100 to 100 (0 means no change)
Factor := (259 * (AContrast + 255)) / (255 * (259 - AContrast));
// 2. Build a Lookup Table (LUT) for performance
for I := 0 to 255 do
LUT[I] := Clamp(Round(Factor * (I - 128) + 128));
// 3. Apply the LUT directly to the pixel buffer
P := PColor32Entry(ABitmap.Bits);
while (Count > 0) do
begin
// Modify R, G, and B components while preserving Alpha
P.R := LUT[P.R];
P.G := LUT[P.G];
P.B := LUT[P.B];
// Move on to next pixel
Inc(P);
Dec(Count);
end;
Bitmap.Changed;
end;Performance Comparison Summary
| Access Method | Safety | Relative Speed | Recommended Use Case |
|---|---|---|---|
PixelS[X, Y] | Bounds checked | Moderate | Single pixel tweaks, user clicks, UI bounds safety |
PixelW[X, Y] | Bounds checked | Moderate | Same |
Pixel[X, Y] | Unchecked | Fast | Simple 2D algorithms with verified bounds |
ScanLine[Y] | Row pointer | Very Fast | 2D filters, convolution matrices, line-by-line processing |
Bits | Flat buffer | Maximum | 1D filters, whole-image operations, color transforms, blits |
Summary
In this tutorial, you learned:
- The difference between bounds-checked
PixelSandPixelW, and uncheckedPixel. - How to process images line-by-line using
ScanLine[Y]pointers. - How to process the whole bitmap in a single loop using
Bits. - How to perform custom image processing algorithms (grayscale, inversion, brightness and contrast adjustment).
Next, check out Image Resampling & High-Quality Scaling to see how Graphics32 resamples and resizes bitmaps!
The color value return by out of bounds pixel reads can be configured with the
OuterColorproperty. The default isclNone32=$00000000. ↩︎