Difference between revisions of "Fast direct pixel access"

From Lazarus wiki
Line 130: Line 130:
 
   X, Y: Integer;
 
   X, Y: Integer;
 
   PixelPtr: PInteger;
 
   PixelPtr: PInteger;
 +
  PixelRowPtr: PInteger;
 
   P: TPixelFormat;
 
   P: TPixelFormat;
 
   RawImage: TRawImage;
 
   RawImage: TRawImage;
Line 139: Line 140:
 
     PixelPtr := PInteger(RawImage.Data);
 
     PixelPtr := PInteger(RawImage.Data);
 
     BytePerPixel := RawImage.Description.BitsPerPixel div 8;
 
     BytePerPixel := RawImage.Description.BitsPerPixel div 8;
     for X := 0 to Size.X - 1 do
+
     for X := 0 to Size.X - 1 do begin
 +
      PixelPtr := PixelRowPtr;
 
       for Y := 0 to Size.Y - 1 do begin
 
       for Y := 0 to Size.Y - 1 do begin
 
         PixelPtr^ := Pixels[X, Y] * $010101;
 
         PixelPtr^ := Pixels[X, Y] * $010101;
 
         Inc(PByte(PixelPtr), BytePerPixel);
 
         Inc(PByte(PixelPtr), BytePerPixel);
 
       end;
 
       end;
 +
      Inc(PByte(PixelRowPtr), RawImage.Description.BytesPerLine);
 +
    end;
 
   finally
 
   finally
 
     Bitmap.EndUpdate(False);
 
     Bitmap.EndUpdate(False);

Revision as of 23:42, 16 March 2011

Introduction

Standard graphical LCL components provides Canvas object for common drawing. But most of available graphic routines have some overhead given by universality, platform independence and safety. To achieve best drawing speed it is necessary to use specialized bitmap structures and routines. There are some graphical libraries available for faster graphic processing with wide drawing functions. But for some specific usage it is necessary to create own small library with data structures fitted for own application.

In this test let assume that we have simple bitmap structure designed as two dimensional byte array where each pixel have 256 possible colors. This could be gray image or some palette mapped image. All image manipulation will be done with custom functions with direct pixel access. Thanks to defined data structure functions could be optimized for faster block memory operations if necessary.

<delphi>type

 TPixel = Byte;
 TFastBitmap = class
 private
   function GetSize: TPoint;
   procedure SetSize(const AValue: TPoint); 
 public
   Pixels: array of array of TPixel;
   property Size: TPoint read GetSize write SetSize;  
 end;</delphi>

To be able to display image on Form custom image have to be copied to some WinControl area. Image have to be copied repeatedly if motion image is generated. This will shift timing demands even lower.

You can draw image as fast as possible in simple loop: <delphi>repeat

 FastBitmapToBitmap(FastBitmap, Image1.Picture.Bitmap);
 Application.ProcessMessages;

until Terminated;</delphi>

Or draw image for example using Timer with defined drawing interval. Even if nothing is changed on bitmap there is no need to copy bitmap to screen so RedrawPending simple flag could be used. Thanks to delayed draw execution with calling Redraw method drawing of frames could be skipped.

<delphi>TForm1 = class(TForm) published

 procedure Timer1Execute(Sender: TObject);
 ...  

public

 RedrawPending: Boolean;
 Drawing: Boolean;
 FastBitmap: TFastBitmap;
 procedure Redraw;
 ...

end;

procedure TForm1.Redraw; begin

 RedrawPending := True;

end;

procedure TForm1.Timer1Execute(Sender: TObject); begin

 if (not Drawing) and RedrawPending then 
 try
   Drawing := True;
   CustomProcessing(FastBitmap);
   FastBitmapToBitmap(FastBitmap, Image1.Picture.Bitmap);        
 finally
   RedrawPending := False;
   Drawing := False;
 end;

end;</delphi>

Methods

TBitmap.Canvas.Pixels

This is most straighforward but slowest method.

<delphi>function FastBitmapToBitmap(FastBitmap: TFastBitmap; Bitmap: TBitmap); var

 X, Y: Integer;

begin

 for X := 0 to FastBitmap.Size.X - 1 do
   for Y := 0 to FastBitmap.Size.Y - 1 do
     Bitmap.Canvas.Pixels[X, Y] := FastBitmap.Pixels[X, Y] * $010101;  

end;</delphi>

TBitmap.Canvas.Pixels with BeginUpdate and EndUpdate

Previous method could be speeded up by update locking and thus redusing per pixel update and event signaling.

<delphi>function FastBitmapToBitmap(FastBitmap: TFastBitmap; Bitmap: TBitmap); var

 X, Y: Integer;

begin

 try
   Bitmap.BeginUpdate(True);
   for X := 0 to FastBitmap.Size.X - 1 do
     for Y := 0 to FastBitmap.Size.Y - 1 do
       Bitmap.Canvas.Pixels[X, Y] := FastBitmap.Pixels[X, Y] * $010101;  
 finally
   Bitmap.EndUpdate(False);
 end;

end;</delphi>

TLazIntfImage

TBitmap is general bitmap component compatible with Delphi and it use TColor type for pixels. But LCL provide another component better suited for image handling. This component provide faster access to pixels and in addition it supports alpha channel.

<delphi>uses

 ..., LCLType, LCLProc, LCLIntf;

function FastBitmapToBitmap(FastBitmap: TFastBitmap; Bitmap: TBitmap); var

 X, Y: Integer;
 TempIntfImage: TLazIntfImage;

begin

 try
   TempIntfImage := Bitmap.CreateIntfImage; // Temp image could be precreated and holded owning class
   for X := 0 to FastBitmap.Size.X - 1 do
     for Y := 0 to FastBitmap.Size.Y - 1 do begin
       TempIntfImage.Colors[X, Y] := TColorToFPColor(FastBitmap.Pixels[X, Y] * $010101);
     end;
   Bitmap.LoadFromIntfImage(TempIntfImage);
 finally
   TempIntfImage.Free;
 end;                           

end;</delphi>

TBitmap.ScanLines

Method used frequently on Delphi is not supported by LCL. ScanLines property give access to memory starting point for each rows raw data. Direct manipulation with pixels was much faster than using Pixels property with respect to internal data structure. LCL provide classes TLazIntfImage and TRawImage for direct pixel access.

TBitmap.RawImage

This method is much faster than previous ones but more complicated as special care have to be given to bitmap data structure. Example assume bitmap PixelFormat is pf24bit. Accessed raw may differs across platforms.

<delphi>uses

 ..., GraphType;

function FastBitmapToBitmap(FastBitmap: TFastBitmap; Bitmap: TBitmap); var

 X, Y: Integer;
 PixelPtr: PInteger;
 PixelRowPtr: PInteger;
 P: TPixelFormat;
 RawImage: TRawImage;
 BytePerPixel: Integer;

begin

 try
   Bitmap.BeginUpdate(False);
   RawImage := Bitmap.RawImage;
   PixelPtr := PInteger(RawImage.Data);
   BytePerPixel := RawImage.Description.BitsPerPixel div 8;
   for X := 0 to Size.X - 1 do begin
     PixelPtr := PixelRowPtr;
     for Y := 0 to Size.Y - 1 do begin
       PixelPtr^ := Pixels[X, Y] * $010101;
       Inc(PByte(PixelPtr), BytePerPixel);
     end;
     Inc(PByte(PixelRowPtr), RawImage.Description.BytesPerLine);
   end;
 finally
   Bitmap.EndUpdate(False);
 end;  

end;</delphi>

Speed comparison

This table shows only raw benchmark results which are dependent on used computer.

Method Frame duration [ms]
TBitmap.Canvas.Pixels 650
TBitmap.Canvas.Pixels with BeginUpdate and EndUpdate 150
TLazIntfImage  9
TBitmap.RawImage 0.75

See also