It is not a new page, but “Manipulating Pixels With Delphi’s ScanLine Property” tech note from efg’s Computer Lab is probably the ultimate reference to the topic, full of Danny Thorpe’s comments explaining the inner workings of TBitmap class implementation.
Accessing ScanLine property can be expensive in terms of performance, so it is the best to calculate the offset in bytes for every row in a bitmap just once and use it for performing pointer arithmetics as demonstrated by ScanlineTiming test application that opens and compiles beautifully in Delphi 2010 on Windows 7.
What interests me more is to compare performance of changing bitmaps using “Pixels” and “ScanLine” properties. Delphi 2010 introduced into the VCL the new TStopwatch type in “Diagnostics” unit that should be the most convenient way of measuring time.
Here is my rewritten ToGray procedure that is now taking one additional parameter “ba: TBitmapAccess” that controls how bitmaps are accessed: via ScanLine or via Pixels properties.
type TColor2Grayscale = ( c2gAverage, c2gLightness, c2gLuminosity ); TBitmapAccess = ( baScanLine, baPixels ); // ... function RGBToGray(R, G, B: byte; cg: TColor2Grayscale = c2gLuminosity): byte; begin case cg of c2gAverage: Result := (R + G + B) div</strong> 3; c2gLightness: Result := (max(R, G, B) + min(R, G, B)) div</strong> 2; c2gLuminosity: Result := round(0.2989*R + 0.5870*G + 0.1141*B); // coeffs from Matlab else raise Exception.Create('Unknown Color2Grayscale value'</span>); end</strong>; end</strong>; // ... procedure ToGray(aBitmap: Graphics.TBitmap; cg: TColor2Grayscale = c2gLuminosity; ba: TBitmapAccess = baScanLine); var w, h: integer; CurrRow, OffSet: integer; x: byte; pRed, pGreen, pBlue: PByte; begin if ba = baPixels then begin if aBitmap <> nil then for h := 0 to aBitmap.Height - 1 do for w := 0 to aBitmap.Width - 1 do aBitmap.Canvas.Pixels[w,h] := ColorToGray(aBitmap.Canvas.Pixels[w,h], cg); end else // ba = baScanLine begin if aBitmap.PixelFormat <> pf24bit then raise Exception.Create( 'Not implemented. PixelFormat has to be "pf24bit"'</span>); CurrRow := Integer(aBitmap.ScanLine); OffSet := Integer(aBitmap.ScanLine) - CurrRow; for h := 0 to aBitmap.Height - 1 do begin for w := 0 to aBitmap.Width - 1 do begin pBlue := pByte(CurrRow + w*3); pGreen := pByte(CurrRow + w*3 + 1); pRed := pByte(CurrRow + w*3 + 2); x := RGBToGray(pRed^, pGreen^, pBlue^, cg); pBlue^ := x; pGreen^ := x; pRed^ := x; end</strong>; inc(CurrRow, OffSet); end</strong>; end end</strong>;
On average my test application shows that using “ScanLine” property for bitmap access is approximately 30 times faster then using “Pixels”. That’s really more then I was expecting…
In the code above I have only implemented support for “pf24bit” pixel format.
In order to optimize code, I’m precalculating offset between scanlines to avoid too many calls to “ScanLine” as it may result in degraded performance. The other benefit of this approach is taking automatically into account byte padding in memory.
The source code for this test application can be downloaded from the Embarcadero Developer Network.