使用Dephi进行图像处理可以有多种方法,最常用的应该算是TBitmap,它提供方便的图像存取能力,结合Canvas可进行画线、画圆、图像拷贝等操作。
不过在进行大量的图像处理操作时,为了获得更高的速度,我们希望能够直接对图像缓冲区进行读写。查阅Dephi的帮助手册没有发现直接取得整个图像缓冲区的功能,
但提供的ScanLine属性可以取得指定行图像数据的指针,比较接近我们的要求,先看看ScanLine的描述:

property ScanLine[Row: Integer]: Pointer; Provides indexed access to each line of pixels.
ScanLine is used only with DIBs (Device Independent Bitmaps) for image editing tools that do low-level pixel work.

  tagBITMAPINFOHEADER = recordbiSize: DWORD;biWidth: Longint;biHeight: Longint;biPlanes: Word;biBitCount: Word;biCompression: DWORD;biSizeImage: DWORD;biXPelsPerMeter: Longint;biYPelsPerMeter: Longint;biClrUsed: DWORD;biClrImportant: DWORD;end;

DWORD biSize;//指定结构需要的比特数。这个值不包含在结构的结尾被添上的颜色表或者颜色盒的大小。
LONG   biWidth;//指定位图的像素宽度
LONG   biHeight;//指定位图的高度。单位是像素
biPlanes //Specifies the number of planes for the target device. This value must be set to 1.
biBitCount //指定每像素的比特数(bpp)。0 - JPEG or PNG, 1 - Monochrome
biCompression //对于压缩的视频和YUV格式,该数字是一个FOURCC代码。对于没有压缩的RGB,这个值是BI_RGB
DWORD biSizeImage;//位图的大小,单位是比特。对于没有压缩的RGB位图,该数值可以被设置为0
LONG   biXPelsPerMeter;//规定了该位图的目标设备的水平模式,单位是像素每米
LONG   biYPelsPerMeter;//规定了该位图的目标设备的垂直模式,单位是像素每米
DWORD biClrUsed;//规定了颜色表中的颜色点数,它们被位图使用
DWORD biClrImportant;//规定了在位图中比较重要的颜色点数,如果值为0,那么所有的颜色都是重要的。

biWidth

The width of the bitmap, in pixels.
If biCompression is BI_JPEG or BI_PNG, the biWidth member specifies the width of the decompressed JPEG or PNG image file, respectively.

biHeight

The height of the bitmap, in pixels.
If biHeight is positive, the bitmap is a bottom-up DIB and its origin is the lower-left corner.
If biHeight is negative, the bitmap is a top-down DIB and its origin is the upper-left corner.
If biHeight is negative, indicating a top-down DIB, biCompression must be either BI_RGB or BI_BITFIELDS. Top-down DIBs cannot be compressed.

If biCompression is BI_JPEG or BI_PNG, the biHeight member specifies the height of the decompressed JPEG or PNG image file, respectively.

BI_RGB
An uncompressed format.

BI_BITFIELDS
Specifies that the bitmap is not compressed and that the color table consists of three DWORD color masks
that specify the red, green, and blue components, respectively, of each pixel.
This is valid when used with 16- and 32-bpp bitmaps.

function BytesPerScanline(PixelsPerScanline, BitsPerPixel, Alignment: Longint): Longint;
beginDec(Alignment);Result := ((PixelsPerScanline * BitsPerPixel) + Alignment) and not Alignment;Result := Result div 8;
end;

function TBitmap.GetScanLine(Row: Integer): Pointer;
beginChanging(Self);with FImage.FDIB, dsbm, dsbmih dobeginif (Row < 0) or (Row >= bmHeight) thenInvalidOperation(@SScanLine);DIBNeeded;GDIFlush;if biHeight > 0 then  // bottom-up DIBRow := biHeight - Row - 1    else    Row := Row; // top-down DIB Result := PByte(bmBits) + Row * BytesPerScanline(biWidth, biBitCount, 32);end;
end;

procedure TForm1.Button1Click( Sender : TObject );
varBitMap : TBitmap;S : String;
beginBitMap := TBitmap.Create;tryBitMap.PixelFormat := pf24bit; // 24位色,每像素点3个字节 -- 首先设置 PixelFormat 否则出错 !BitMap.Width := 1000;BitMap.Height := 2;       // BitMap.Height := -2; -- Top-Down DIB [ ScanLine[0] -- First Row ]   FmtStr( S, 'ScanLine[0]:%8x'#13'ScanLine[1]:%8x'#13'ScanLine[1]-ScanLine[0]:%d', 
    [ Integer( BitMap.ScanLine[ 0 ] ), Integer( BitMap.ScanLine[ 1 ] ), Integer( BitMap.ScanLine[ 1 ] ) - Integer( BitMap.ScanLine[ 0 ] ) ] );
    MessageBox( Handle, PChar( S ), 'ScanLine', MB_OK );finallyif Assigned( BitMap ) thenFreeAndNil( BitMap );end;
end

ScanLine[0]: E90BB8
ScanLine[1]: E90000
ScanLine[1]-ScanLine[0]:-3000

让我们再看看ScanLine[0]、ScanLine[1]的关系, ScanLine[0]与ScanLine[1]之间相差3000=1000像素宽×3字节这很容易理解,但为什么是负数呢?
因为BMP图像数据是“按行存放,每行按双字对齐,行按倒序方式存放”的,也就是说屏幕显示的第一行存放在最后,
屏幕显示的最后一行存放在前面,所以用ACDSee等看图软件查看尺寸较大的位图时先从下部开始显示就是这个道理。
从上面的结果可以看出TBitmap的图像数据在内存中是按行倒序连续存放的,
通过TBitmap.ScanLine[TBitmap.Height-1]可以取得首地址即图像缓冲区地址

Memory : Bottom-Up DIB : origin is the lower-left cornerScanLine[H-1] \    /  *****************ScanLine[H-2]  \  /   *****************\/ScanLine[1]     /\    *****************ScanLine[0]   /   \   *****************Memory :  Top-Down DIB : origin is the upper-left corner.ScanLine[0]   -----   *****************ScanLine[1]   -----   *****************ScanLine[H-2] -----   *****************ScanLine[H-1] -----   *****************

Top-Down vs. Bottom-Up DIBs

http://msdn.microsoft.com/en-us/library/windows/desktop/dd407212(v=vs.85).aspx

If you are new to graphics programming, you might expect that a bitmap would be arranged in memory
so that the top row of the image appeared at the start of the buffer, followed by the next row, and so forth.
However, this is not necessarily the case. In Windows, device-independent bitmaps (DIBs) can be placed in memory
in two different orientations, bottom-up and top-down.

In a bottom-up DIB, the image buffer starts with the bottom row of pixels, followed by the next row up, and so forth.
The top row of the image is the last row in the buffer.
Therefore, the first byte in memory is the bottom-left pixel of the image.
In GDI, all DIBs are bottom-up. The following diagram shows the physical layout of a bottom-up DIB.

In a top-down DIB, the order of the rows is reversed.
The top row of the image is the first row in memory, followed by the next row down.
The bottom row of the image is the last row in the buffer.
With a top-down DIB, the first byte in memory is the top-left pixel of the image.
DirectDraw uses top-down DIBs. The following diagram shows the physical layout of a top-down DIB:

For RGB DIBs, the image orientation is indicated by the biHeight member of the BITMAPINFOHEADER structure.
If biHeight is positive, the image is bottom-up.
If biHeight is negative, the image is top-down.

DIBs in YUV formats are always top-down, and the sign of the biHeight member is ignored.
Decoders should offer YUV formats with positive biHeight, but they should also accept YUV formats
with negative biHeight and ignore the sign.

Also, any DIB type that uses a FOURCC in the biCompression member, should express its biHeight as a positive number no matter
what its orientation is, since theFOURCC itself identifies a compression scheme whose image orientation should be
understood by any compatible filter.

function CreateDIB_TopDown( nWidth, nHeight : Integer ) : HBITMAP;
varhbm : HBITMAP;hdcMem : HDC;bmi : BITMAPINFO;  pvBits : LPBYTE;  // ScanLine[0]
beginhdcMem := CreateCompatibleDC( 0 );if hdcMem > 0 thenbeginZeroMemory( @( bmi.bmiHeader ), sizeof( BITMAPINFOHEADER ) );bmi.bmiHeader.biSize := sizeof( BITMAPINFOHEADER );bmi.bmiHeader.biWidth := nWidth;bmi.bmiHeader.biHeight := -nHeight; // Use a top-down DIBbmi.bmiHeader.biPlanes := 1;bmi.bmiHeader.biBitCount := 32;
    hbm := CreateDIBSection( hdcMem, bmi, DIB_RGB_COLORS, Pointer( pvBits), 0, 0 );if ( hbm > 0 ) thenbegin// Fill in the pixels of the bitmap
end;DeleteDC( hdcMem );end;Result := hbm;
end;

Bitmap data area 
This is a byte stream that describes the pixels of the image (this may or may not be in compressed form) in 1-, 4-, 8-, 16-, or 24-bit format.
The data is in line-by-line order, but it may be upside-down so that the first line of data is the last line of the image.
You can detect this by looking at the sign of biHeight— a positive sign means the bitmap is upside-down,
and a negative sign means the bitmap is normal.

最后补充说明一下:

  1. Bitmap图像缓冲区是双节对齐的,如果把例1中的图像宽度改为999,一个像素行还是占3000个字节。
  2. 目前Bitmap.PixelFormat有pfDevice、pf1bit、pf4bit、pf8bit、pf15bit、pf16bit、pf24bit、pf32bit、pfCustom共9种,
    不同格式每个像素所占字节数不同,其中pf4bit和pf8bit格式的图像缓冲区保存的为颜色索引号,真正的颜色值在调色板中,
    pf15bit、pf16bit格式中RGB所占的位数(Bit)不一定是等长的。

通过直接对图像缓冲区的读写将图像淡出到黑色

procedure TForm1.Button1Click( Sender : TObject );
constFADEOUT_STEP = 24; // 淡出衰减值FIX_WIDTH = 320;FIX_HEIGHT = 200;
varBitMap : TBitmap;hWinDC : HDC;flagAgein : Boolean;lpBuffer : PByte; // 图像缓冲区指针
beginBitMap := TBitmap.Create;if not Assigned( BitMap ) thenExit;try// 设置位图格式、宽度、高度BitMap.PixelFormat := pf24bit;BitMap.Width := FIX_WIDTH;BitMap.Height := FIX_HEIGHT;// 设置Form的宽充、高度,便于显示结果Button1.Visible := false;ClientWidth := FIX_WIDTH;ClientHeight := FIX_HEIGHT;// 拷贝图像到Bitmap中hWinDC := GetDC( 0 );if ( hWinDC <> NULL ) thenBitBlt( BitMap.Canvas.Handle, 0, 0, FIX_WIDTH, FIX_HEIGHT, hWinDC, 0, 0, SRCCOPY )elseBitBlt( BitMap.Canvas.Handle, 0, 0, FIX_WIDTH, FIX_HEIGHT, Canvas.Handle, 0, 0, SRCCOPY );repeatflagAgein := false;lpBuffer := BitMap.ScanLine[ FIX_HEIGHT - 1 ];       // 取得图像缓冲区首地址 Integer(BitMap.ScanLine[0]) + FIX_WIDTH*3 为图像缓冲区结束地址while Integer( lpBuffer ) < Integer( BitMap.ScanLine[ 0 ] ) +FIX_WIDTH * 3 dobeginif lpBuffer^ > FADEOUT_STEP thenbeginDec( lpBuffer^, FADEOUT_STEP );flagAgein := true;endelselpBuffer^ := 0;Inc( lpBuffer );Application.ProcessMessages;end;Canvas.Draw( 0, 0, BitMap );until ( not flagAgein );MessageBox( Handle, 'Done', 'Fadeout', MB_OK );finallyif Assigned( BitMap ) thenFreeAndNil( BitMap );Button1.Visible := true;end;
end;

http://www.esanu.name/delphi/Multimedia/Graphics/Bitmap.Scanline%20for%20PixelFormat=pf1bit,%20pf8bit,%20pf24bit.html

Bitmap.Scanline for PixelFormat=pf1bit, pf8bit, pf24bit

Since someone from Italy asked me for an example of using pf1bit Bitmaps, I thought I would post part of my response and add other details for pf8bit and pf24bit here in case others were wondering.

Background

The new Delphi 3 scanline property allows quick access to individual pixels, but you must know what Bitmap.PixelFormat you're working with before you can access the pixels.

Possible PixelFormats include:

pfDevice pf1bit pf4bit pf8bit pf15bit pf16bit pf24bit pf32bit

For pf24bit bitmaps, I define (I wish Borland would)

CONSTPixelCountMax = 32768;TYPEpRGBArray = ^TRGBArray;TRGBArray = ARRAY [ 0 .. PixelCountMax - 1 ] OF TRGBTriple;

Note: TRGBTriple is defined in the Windows.PAS unit.

To step through a 24-bit bitmap and while creating a new one and access the 3-bytes-per-pixel data, use a construct like the following

VARi : INTEGER;j : INTEGER;RowOriginal : pRGBArray;RowProcessed : pRGBArray;
BEGINIF OriginalBitmap.PixelFormat <> pf24bit THENRAISE EImageProcessingError.Create( 'GetImageSpace: ' + 'Bitmap must be 24-bit color.' );{ Step through each row of image. }FOR j := OriginalBitmap.Height - 1 DOWNTO 0 DOBEGINRowOriginal := pRGBArray( OriginalBitmap.Scanline[ j ] );RowProcessed := pRGBArray( ProcessedBitmap.Scanline[ j ] );FOR i := OriginalBitmap.Width - 1 DOWNTO 0 DOBEGIN// Access individual color RGB color planes with references like:// RowProcessed[i].rgbtRed := RowOriginal[i].rgbtRed;// RowProcessed[i].rgbtGreen := RowOriginal[i].rgbtGreen;// RowProcessed[i].rgbtBlue := RowOriginal[i].rgbtBlue;ENDEND
END;

pf8bit Bitmaps

Access to these byte-per-pixel bitmaps is easy using the TByteArray (defined in SysUtils.PAS)

PByteArray = ^TByteArray;
TByteArray = array[0..32767] of Byte;

I suppose, but I've never tried it, you could access pf16bit Bitmaps using the following defined in SysUtils.PAS:

PWordArray = ^TWordArray;
TWordArray = array[0..16383] of Word; ) 

To process an 8-bit (pf8bit) bitmap, use a construct like the following that constructs a histogram of such a bitmap:

VAR
Histogram : THistogram;
i : INTEGER;
j : INTEGER;
Row : pByteArray;FOR i := Low( THistogram ) TO High( THistogram ) DO          Histogram[ i ] := 0;
IF Bitmap.PixelFormat = pf8bit THEN
BEGINFOR j := Bitmap.Height - 1 DOWNTO 0 DOBEGINRow := pByteArray( Bitmap.Scanline[ j ] );FOR i := Bitmap.Width - 1 DOWNTO 0 DOBEGININC( Histogram[ Row[ i ] ] )ENDEND
END

pf1bit Bitmaps

Accessing pf8bit bitmaps is easy since they are one byte per pixel.
But you can save a lot of memory if you only need a single bit per pixel (such as with various masks), if you use pf1bit Bitmaps.
As with pf8bit bitmaps, use a TByteArray to access pf1bit Scanlines.
But you will need to perform bit operations on the bytes to access the various pixels.
Also, the width of the Scanline is Bitmap.Width DIV 8 bytes.

The following code shows how to create the following kinds of 1-bit bitmaps:
black, white, stripes, "g", "arrow" and random -- an "invert" option is also available.
(Send me an E-mail if you'd like the complete working source code including the form.)

Create a form with an Image1: TImage on it -- I used 1 256x256 Image1 with Stretch := TRUE to see the individual pixels more easily.
The buttons Black, White and Stripes have tags of 0, 255, and 85 ($55 = 01010101 binary) that call ButtonStripesClick when selected.
Buttons "g" and "arrow" call separate event handlers to draw these bitmaps taken form HP Laserjet examples.
"Random" just randomly sets bits on in the 1-bit bitmaps.
"Invert" changes all the 0s to 1's and vice versa.

// Example of how to use Bitmap.Scanline for PixelFormat=pf1Bit.
// Requested by Mino Ballone from Italy.
//
// Copyright (C) 1997, Earl F. Glynn, Overland Park, KS. All rights reserved.
// May be freely used for non-commerical purposes.unit ScreenSingleBit;interfaceusesWindows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,StdCtrls, ExtCtrls;typeTForm1 = class( TForm )Image1 : TImage;ButtonBlack : TButton;ButtonWhite : TButton;ButtonStripes : TButton;ButtonG : TButton;ButtonArrow : TButton;ButtonRandom : TButton;ButtonInvert : TButton;procedure ButtonStripesClick( Sender : TObject );procedure ButtonGClick( Sender : TObject );procedure FormCreate( Sender : TObject );procedure FormDestroy( Sender : TObject );procedure ButtonRandomClick( Sender : TObject );procedure ButtonInvertClick( Sender : TObject );procedure ButtonArrowClick( Sender : TObject );privateBitmap : TBitmap;{ Private declarations }public{ Public declarations }end;varForm1 : TForm1;implementation{$R *.DFM}CONSTBitsPerPixel = 8;procedure TForm1.ButtonStripesClick( Sender : TObject );
VARi : INTEGER;j : INTEGER;Row : pByteArray;Value : BYTE;
beginValue := ( Sender AS TButton ).Tag;// Value = $00 = 00000000 binary for black// Value = $FF = 11111111 binary for white// Value = $55 = 01010101 binary for black & white stripesFOR j := 0 TO Bitmap.Height - 1 DOBEGINRow := pByteArray( Bitmap.Scanline[ j ] );FOR i := 0 TO ( Bitmap.Width DIV BitsPerPixel ) - 1 DOBEGINRow[ i ] := ValueENDEND;Image1.Picture.Graphic := Bitmap
end;procedure TForm1.ButtonGClick( Sender : TObject );
CONST{ The "g" bitmap was adapted from the LaserJet IIP Printer Tech RefManual }G : ARRAY [ 0 .. 31, 0 .. 3 ] OF BYTE ={ 0 } ( ( $00, $FC, $0F, $C0 ),  { 00000000 11111100 00001111 11000000 }{ 1 } ( $07, $FF, $1F, $E0 ),  { 00000111 11111111 00011111 11100000 }{ 2 } ( $0F, $FF, $9F, $C0 ),  { 00001111 11111111 10011111 11000000 }{ 3 } ( $3F, $D7, $DE, $00 ),  { 00111111 11010111 11011110 00000000 }{ 4 } ( $3E, $01, $FE, $00 ),  { 00111110 00000001 11111110 00000000 }{ 5 } ( $7C, $00, $7E, $00 ),  { 01111100 00000000 01111110 00000000 }{ 6 } ( $78, $00, $7E, $00 ),  { 01111000 00000000 01111110 00000000 }{ 7 } ( $F0, $00, $3E, $00 ),  { 11110000 00000000 00111110 00000000 }{ 8 } ( $F0, $00, $3E, $00 ),  { 11110000 00000000 00111110 00000000 }{ 9 } ( $F0, $00, $1E, $00 ),  { 11110000 00000000 00011110 00000000 }{ 10 } ( $F0, $00, $1E, $00 ), { 11110000 00000000 00011110 00000000 }{ 11 } ( $F0, $00, $1E, $00 ), { 11110000 00000000 00011110 00000000 }{ 12 } ( $F0, $00, $1E, $00 ), { 11110000 00000000 00011110 00000000 }{ 13 } ( $F0, $00, $3E, $00 ), { 11110000 00000000 00111110 00000000 }{ 14 } ( $78, $00, $3E, $00 ), { 01111000 00000000 00111110 00000000 }{ 15 } ( $78, $00, $3E, $00 ), { 01111000 00000000 00111110 00000000 }{ 16 } ( $78, $00, $7E, $00 ), { 01111000 00000000 01111110 00000000 }{ 17 } ( $3C, $00, $FE, $00 ), { 00111100 00000000 11111110 00000000 }{ 18 } ( $1F, $D7, $DE, $00 ), { 00011111 11010111 11011110 00000000 }{ 19 } ( $0F, $FF, $5E, $00 ), { 00001111 11111111 10011110 00000000 }{ 20 } ( $07, $FF, $1E, $00 ), { 00000111 11111111 00011110 00000000 }{ 21 } ( $00, $A8, $1E, $00 ), { 00000000 10101000 00011110 00000000 }{ 22 } ( $00, $00, $1E, $00 ), { 00000000 00000000 00011110 00000000 }{ 23 } ( $00, $00, $1E, $00 ), { 00000000 00000000 00011110 00000000 }{ 24 } ( $00, $00, $1E, $00 ), { 00000000 00000000 00011110 00000000 }{ 25 } ( $00, $00, $3E, $00 ), { 00000000 00000000 00111110 00000000 }{ 26 } ( $00, $00, $3C, $00 ), { 00000000 00000000 00111100 00000000 }{ 27 } ( $00, $00, $7C, $00 ), { 00000000 00000000 01111100 00000000 }{ 28 } ( $00, $01, $F8, $00 ), { 00000000 00000001 11111000 00000000 }{ 29 } ( $01, $FF, $F0, $00 ), { 00000001 11111111 11110000 00000000 }{ 30 } ( $03, $FF, $E0, $00 ), { 00000011 11111111 11100000 00000000 }{ 31 } ( $01, $FF, $80, $00 )  { 00000001 11111111 10000000 00000000 } );VARi : INTEGER;j : INTEGER;Row : pByteArray;
beginFOR j := 0 TO Bitmap.Height - 1 DOBEGINRow := pByteArray( Bitmap.Scanline[ j ] );FOR i := 0 TO ( Bitmap.Width DIV BitsPerPixel ) - 1 DOBEGINRow[ i ] := G[ j, i ]ENDEND;Image1.Picture.Graphic := Bitmap
end;procedure TForm1.ButtonArrowClick( Sender : TObject );
CONST{ The "arrow" bitmap was adapted from the LaserJet IIP Printer Tech Ref Manual }Arrow : ARRAY [ 0 .. 31, 0 .. 3 ] OF BYTE ={ 0 } ( ( $00, $00, $80, $00 ), { 00000000 00000000 10000000 00000000 }{ 1 } ( $00, $00, $C0, $00 ), { 00000000 00000000 11000000 00000000 }{ 2 } ( $00, $00, $E0, $00 ), { 00000000 00000000 11100000 00000000 }{ 3 } ( $00, $00, $F0, $00 ), { 00000000 00000000 11110000 00000000 }{ 4 } ( $00, $00, $F8, $00 ), { 00000000 00000000 11111000 00000000 }{ 5 } ( $00, $00, $FC, $00 ), { 00000000 00000000 11111100 00000000 }{ 6 } ( $00, $00, $FE, $00 ), { 00000000 00000000 11111110 00000000 }{ 7 } ( $00, $00, $FF, $00 ), { 00000000 00000000 11111111 00000000 }{ 8 } ( $00, $00, $FF, $80 ), { 00000000 00000000 11111111 10000000 }{ 9 } ( $FF, $FF, $FF, $C0 ), { 11111111 11111111 11111111 11000000 }{ 10} ( $FF, $FF, $FF, $E0 ), { 11111111 11111111 11111111 11100000 }{ 11} ( $FF, $FF, $FF, $F0 ), { 11111111 11111111 11111111 11110000 }{ 12} ( $FF, $FF, $FF, $F8 ), { 11111111 11111111 11111111 11111000 }{ 13} ( $FF, $FF, $FF, $FC ), { 11111111 11111111 11111111 11111100 }{ 14} ( $FF, $FF, $FF, $FE ), { 11111111 11111111 11111111 11111110 }{ 15} ( $FF, $FF, $FF, $FF ), { 11111111 11111111 11111111 11111111 }{ 16} ( $FF, $FF, $FF, $FF ), { 11111111 11111111 11111111 11111111 }{ 17} ( $FF, $FF, $FF, $FE ), { 11111111 11111111 11111111 11111110 }{ 18} ( $FF, $FF, $FF, $FC ), { 11111111 11111111 11111111 11111100 }{ 19} ( $FF, $FF, $FF, $F8 ), { 11111111 11111111 11111111 11111000 }{ 20} ( $FF, $FF, $FF, $F0 ), { 11111111 11111111 11111111 11110000 }{ 21} ( $FF, $FF, $FF, $E0 ), { 11111111 11111111 11111111 11100000 }{ 22} ( $FF, $FF, $FF, $C0 ), { 11111111 11111111 11111111 11000000 }{ 23} ( $00, $00, $FF, $80 ), { 00000000 00000000 11111111 10000000 }{ 24} ( $00, $00, $FF, $00 ), { 00000000 00000000 11111111 00000000 }{ 25} ( $00, $00, $FE, $00 ), { 00000000 00000000 11111110 00000000 }{ 26} ( $00, $00, $FC, $00 ), { 00000000 00000000 11111100 00000000 }{ 27} ( $00, $00, $F8, $00 ), { 00000000 00000000 11111000 00000000 }{ 28} ( $00, $00, $F0, $00 ), { 00000000 00000000 11110000 00000000 }{ 29} ( $00, $00, $E0, $00 ), { 00000000 00000000 11100000 00000000 }{ 30} ( $00, $00, $C0, $00 ), { 00000000 00000000 11000000 00000000 }{ 31} ( $00, $00, $80, $00 )  { 00000000 00000000 10000000 00000000 }
);
VARi : INTEGER;j : INTEGER;Row : pByteArray;
beginFOR j := 0 TO Bitmap.Height - 1 DOBEGINRow := pByteArray( Bitmap.Scanline[ j ] );FOR i := 0 TO ( Bitmap.Width DIV BitsPerPixel ) - 1 DOBEGINRow[ i ] := Arrow[ j, i ]ENDEND;Image1.Picture.Graphic := Bitmap
end;procedure TForm1.FormCreate( Sender : TObject );
beginBitmap := TBitmap.Create;WITH Bitmap DOBEGINWidth := 32;Height := 32;PixelFormat := pf1bitEND;Image1.Picture.Graphic := Bitmap
end;procedure TForm1.FormDestroy( Sender : TObject );
beginBitmap.Free
end;procedure TForm1.ButtonRandomClick( Sender : TObject );
VARi : INTEGER;j : INTEGER;Row : pByteArray;
beginFOR j := 0 TO Bitmap.Height - 1 DOBEGINRow := pByteArray( Bitmap.Scanline[ j ] );FOR i := 0 TO ( Bitmap.Width DIV BitsPerPixel ) - 1 DOBEGIN Row[ i ] := Random( 256 )ENDEND;Image1.Picture.Graphic := Bitmap
end;procedure TForm1.ButtonInvertClick( Sender : TObject );
VARi : INTEGER;j : INTEGER;Row : pByteArray;
beginFOR j := 0 TO Bitmap.Height - 1 DOBEGINRow := pByteArray( Bitmap.Scanline[ j ] );FOR i := 0 TO ( Bitmap.Width DIV BitsPerPixel ) - 1 DOBEGINRow[ i ] := NOT Row[ i ]ENDEND;Image1.Picture.Graphic := Bitmap
end;end.

http://stackoverflow.com/questions/13583451/how-to-use-scanline-property-for-24-bit-bitmaps

How to use ScanLine property for 24-bit bitmaps?

1. Introduction

In this post I'll try to explain the ScanLine property usage only for 24-bit bitmap pixel format and if you actually need to use it.
As first take a look what makes this property so important.

2. ScanLine or not...?

You can ask yourself why to use such tricky technique like using ScanLine property seemingly is
when you can simply use Pixels to access your bitmap's pixels.
The answer is a big performance difference noticable when you perform pixel modifications even on a relatively small pixel area.

The Pixels property internally uses Windows API functions - GetPixel andSetPixel, for getting and setting device context color values.
The performance lack at Pixels technique is that you usually need to get pixel color values before you modify them,
what internally means the call of both mentioned Windows API functions.
The ScanLine property is winning this race because provides a direct access to the memory where the bitmap pixel data are stored.
And direct memory access is just faster than two Windows API function calls.

But, it doesn't mean that Pixels property is totally bad and that you should avoid to use it in all cases.
When you are going to modify just few pixels (not a big areas) occasionally e.g., then Pixels might be sufficient for you.
But don't use it when you are going to manipulate with a pixel area.

3. Deep inside the pixels

3.1 Raw data

Pixel data of a bitmap (let's call them raw data for now) you can imagine as a single dimensional array of bytes,
containing the sequence of intensity values of color components for every single pixel.
Every pixel in bitmap consists from a fixed count of bytes depending on used pixel format.

For instance, the 24-bit pixel format has 1 byte for each of its color components - for red, green and blue channel.
The following picture illustrates how to imagine raw data byte array for such 24-bit bitmap.
Each colored rectangle here represents one byte:

3.2 Case study

Imagine you have a 24-bit bitmap 3x2 pixels (width 3px; height 2px) and keep it in your mind
because I'll try to explain some internals and show a principle of ScanLine property usage on it.
It is so small just because of space needed for a deep view inside
(for those having a bright sight is a green example of such image in png format here
↘  ↙ :-)

3.3 Pixel composition

As first let's take a look how the pixel data of our bitmap image are internally stored; look at the raw data.
The following image shows the raw data byte array, where you can see each byte of our tiny bitmap with its index in that array.
You can also notice, how the groups of 3 bytes forms the individual pixels, and on which coordinates are these pixels situated on our bitmap:

[ picture ]

Another view of the same provides the following image. Each box represents one pixel of our imaginary bitmap there.
In each pixel you can see its coordinates and the group of 3 bytes with their indexes from the raw databyte array:

[ picture ]

4. Living with colors

4.1. Initial values

As we already know, pixels in our imaginary 24-bit bitmap are composed from 3 bytes - 1 byte for each color channel.
When you've created this bitmap in your imagination, all of those bytes in all pixels
have been against your will initialized to the max byte value - to 255. It means that all channels have now the maximal color intensities:

When we take a look, which color is mixed from these initial channel values for each pixel, we'll see that our bitmap is entirely white.
So, when you create a 24-bit bitmap in Delphi, it is initially white.
Well, white will be bitmap in every pixel format by default, but they may differ in initial raw data byte values.

5. Secret life of ScanLine

From the above reading I hope you understood, how the bitmap data are stored in a raw data byte array and how the individual pixels are formed from these data.
Now move on to the ScanLine property itself and how can be useful in a direct raw data processing.

5.1. ScanLine purpose

A main dish of this post, the ScanLine property, is a read only indexed property that returns pointer
to the first byte of the array of raw databytes that belongs to a specified row in a bitmap.
In other words we request the access to the array of raw data bytes for a given row and what we receive is a pointer to the first byte of that array.
The index parameter of this property specifies the 0 based index of a row for which we want to get these data.

The following image illustrates our imaginary bitmap and the pointers we get by the ScanLine property using different row indexes:

5.2. ScanLine advantage

So, from what we know, we can summarize that ScanLine gives us a pointer to a certain row data byte array.
And with that row array of raw data we can work - we can read or overwrite its bytes,
but only in a range of the array bounds of a particular row:

Well, we have an array of color intensities for each pixel of a certain row. Considering iteration of such array;
it wouldn't be much comfortable to loop through this array by one byte and adjust only one of 3 color portions of a pixel.
Better will be loop through the pixels and adjust all 3 color bytes at once with each iteration - just like with Pixels as we used to do.

5.3. Jumping through the pixels

To simplify a row array loop we need a structure matching our pixel data.
Fortunately, for 24-bit bitmaps there's the RGBTRIPLE structure; in Delphi translated like TRGBTriple.
This structure, in short looks like this (each member there represents intensity of one color channel):

typeTRGBTriple = packed recordrgbtBlue: Byte;rgbtGreen: Byte;rgbtRed: Byte;end;

Since I've tried to be tolerant to those having Delphi version below 2009 and because it makes the code somehow more understandable
I won't use pointer arithmetic for iteration, but a fixed length array with a pointer to it in the following examples
(pointer arithmetic would be less readable in Delphi 2009 below).

So, we have the TRGBTriple structure for a pixel and now we define a type for the row array.
This will simplify the iteration of bitmap row pixels. This one I just borrowed from the ShadowWnd.pas unit
(home of one interesting class, anyway). Here it is:

typePRGBTripleArray = ^TRGBTripleArray;TRGBTripleArray = array[0..4095] of TRGBTriple;

As you can see, it has a limit of 4096 pixels for a row, what should be enough for usually wide images.
If this won't be sufficient for you, just increase the high bound.

6. ScanLine in practice

6.1. Make the second row black

Let's start with the first example. In that we objectify our imaginary bitmap, set it proper width, height and pixel format (or if you want, a bit depth). Then we use ScanLine with row parameter 1 to get pointer to the second row's raw data byte array. The pointer we get we'll assign to theRowPixels variable which points to the array of TRGBTriple, so since that time we can take it as an array of row pixels. Then we iterate this array in the whole width of the bitmap and set all color values of each pixel to 0, which results to a bitmap with the first row white (white is by default, as mentioned above) and what makes the second row black. This bitmap is then saved to file, but don't be surprised when you see it, it's really very small:

typePRGBTripleArray = ^TRGBTripleArray;TRGBTripleArray = array[0..4095] of TRGBTriple;procedure TForm1.Button1Click(Sender: TObject);
varI: Integer;Bitmap: TBitmap;Pixels: PRGBTripleArray;
beginBitmap := TBitmap.Create;tryBitmap.Width := 3;Bitmap.Height := 2;Bitmap.PixelFormat := pf24bit;// get pointer to the second row's raw dataPixels := Bitmap.ScanLine[1];// iterate our row pixel data array in a whole widthfor I := 0 to Bitmap.Width - 1 dobeginPixels[I].rgbtBlue := 0;Pixels[I].rgbtGreen := 0;Pixels[I].rgbtRed := 0;end;Bitmap.SaveToFile('c:\Image.bmp');finallyBitmap.Free;end;
end;

6.2. Grayscale bitmap using luminance

As a sort of a meaningful example I'm posting here a procedure for grayscaling bitmaps using luminance. It uses the iteration of all bitmap rows from top to bottom. For each row is then obtained pointer to a raw dataand as before taken as the array of pixels. For each pixel of that array is then computed luminance value by this formula:

Luminance = 0.299 R + 0.587 G + 0.114 B

This luminance value is then assigned to each color component of the iterated pixel:

typePRGBTripleArray = ^TRGBTripleArray;TRGBTripleArray = array[0..4095] of TRGBTriple;procedure GrayscaleBitmap(ABitmap: TBitmap);
varX: Integer;Y: Integer;Gray: Byte;Pixels: PRGBTripleArray;
begin// iterate bitmap from top to bottom to get access to each row's raw datafor Y := 0 to ABitmap.Height - 1 dobegin// get pointer to the currently iterated row's raw dataPixels := ABitmap.ScanLine[Y];// iterate the row's pixels from left to right in the whole bitmap widthfor X := 0 to ABitmap.Width - 1 dobegin// calculate luminance for the current pixel by the mentioned formulaGray := Round((0.299 * Pixels[X].rgbtRed) +(0.587 * Pixels[X].rgbtGreen) + (0.114 * Pixels[X].rgbtBlue));// and assign the luminance to each color component of the current pixelPixels[X].rgbtRed := Gray;Pixels[X].rgbtGreen := Gray;Pixels[X].rgbtBlue := Gray;end;end;
end;

And the possible usage of the above procedure. Notice, that you can use this procedure only for 24-bit bitmaps:

procedure TForm1.Button1Click(Sender: TObject);
varBitmap: TBitmap;
beginBitmap := TBitmap.Create;tryBitmap.LoadFromFile('c:\ColorImage.bmp');if Bitmap.PixelFormat <> pf24bit thenraise Exception.Create('Incorrect bit depth, bitmap must be 24-bit!');GrayscaleBitmap(Bitmap);Bitmap.SaveToFile('c:\GrayscaleImage.bmp');finallyBitmap.Free;end;
end;

http://edn.embarcadero.com/article/29173

How to Use Scanlines

This article shows a fast way to manipulate bitmaps, without using canvas : the scanlines.

How To Use Scanlines - By Leonel Togniolli

Scanlines are a very fast way to to access an image, while accessing directly the pixels is very slow.
Its very easy to use it, and most of the code written using the pixels of a canvas can be easily changed to use scanlines,
and routines planned to use them can have performance several times better.

I do not intend to replace Earl's excellent resource on scanlines , but to provide a simpler introduction to them,
and let his article be a more advanced technical reading.

Scanlines are the horizontal rows of an image.
One thing extremelly important is that they are PixelFormat dependant, meaning that their structure vary with the color depth of the image.
One of the most common mistakes when learning is forgetting to set the PixelFormat properly before trying to access them.
When you get a scanline, you get a pointer to the first byte of that row, and have access byte by byte.
That means that the size of a scanline is * , padded to next dword.
When working in pf32bit mode (4 bytes) - which we will assume from now on -, a scanline looks like this :

Blue Green Red Alpha Blue Green Red Alpha Blue Green Red Alpha ...
|---first pixel----| |---second pixel---| |----third pixel---| 

Tip: Alpha is usually used to store information about transparency. In pratice, you can store whatever you need in the alpha channel.

But thats not a good way to work. We are going to define a type containing the four color components, and through good old pointer arithmetic,
work our way every four bytes in the scanline. You could also use TRGBQuad here, the important is that you understand how it works.

We have now defined a record containing the color components, a very big array of it, and a pointer to this array.
Now we can access any byte in this row, using this definition as a mask to the array of bytes ScanLine[y] points to.This is how I did it:

Drop a TImage in a Form. Add a button with the following code in its OnClick handler :

procedure TForm1.Button1Click(Sender: TObject);
var x,y : Integer;
beginwith Image1.Picture.Bitmap dobeginPixelFormat := pf32bit;Width := Image1.Width;Height := Image1.Height;for x := 0 to Width - 1 dofor y := 0 to Height - 1 doCanvas.Pixels[x,y] := x xor y;end;Image1.Invalidate;
end;

That should draw a nice red pattern. Now we are going to do it using scanlines. Since we have access to an entire row at a time, it makes sense to iterate through them first. Add a second button and add this code to it :

procedure TForm1.Button2Click(Sender: TObject);
var x,y  : Integer;Line : PRGB32Array;
beginwith Image1.Picture.Bitmap dobeginPixelFormat := pf32bit;Width := Image1.Width;Height := Image1.Height;for y := 0 to Height - 1 dobeginLine := Scanline[y];for x := 0 to Width - 1 dobeginLine[x].B := 0;Line[x].G := 0;Line[x].R := x xor y;Line[x].A := 0;end;end;end;Image1.Invalidate;
end;

Notice that you have now access to every component of the color of a given pixel, where every byte represents a color intensity from 0 to 255, instead of suppling a TColor like in the first routine. According to Delphis help, if you specify TColor as a specific 4-byte hexadecimal number instead of using the constants defined in the Graphics unit, the low three bytes represent RGB color intensities for blue, green, and red, respectively. The value $00FF0000 represents full-intensity, pure blue, $0000FF00 is pure green, and $000000FF is pure red. $00000000 is black and $00FFFFFF is white.. That means that you can get any color component of a given TColor if you AND it with the constants above, ie (clGray and $000000FF) gives you the red component of clGray.

I timed both routines, calling each one of them ten times in a 1024x1024 bitmap. Button1Click averaged 7298ms per call, while Button2Click averaged 24ms per call. Thats about 300 times faster using scanlines.

Remember that is very important that the PixelFormat is set to pf32bit, or you would have to use a diferent record to access them. The default value of this property is pfDevice, with is the current color depth of windows. So, if you want your code to work properly on every machine, remember to set it explicitely.

Scanlines are stored in memory sequencially, usually in reverse order, like this:

BGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGR(...)BGR(Padding) // Last Line
(...)
BGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGR(...)BGR(Padding) // Second Line
BGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGR(...)BGR(Padding) // First Line

They are also equally long. We can use that in our favor. If we have a pointer to the first line, and the difference, in bytes, between each line, we can access any pixel by pure math. I declared variables to hold them in the private section of the form :

privateLineLength : Longint;FirstLine  : PRGB32Array;

Loaded then on the OnCreate method of the form :

procedure TForm1.FormCreate(Sender: TObject);
beginwith Image1.Picture.Bitmap dobeginPixelFormat := pf32bit;Width := Image1.Width;Height := Image1.Height;FirstLine := Scanline[0];LineLength := (Longint(Scanline[1]) - Longint(FirstLine)) div SizeOf(TRGB32);end;
end;

And used them on the OnMouseMove event of the image :

procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
beginif (ssLeft in Shift) and (x in [0..Image1.Width-1]) and (y in [0..Image1.Height-1]) thenbeginwith FirstLine[Y*LineLength+X] dobeginB := 0;G := 0;R := 255;A := 0;end;Image1.Invalidate;end;
end;

Now click on the image puts and move your mouse around . It puts red dots on it (blazing fast! ). Try to access the pixel like the in last procedure, using with, because its slightly faster, since it only evaluates the expression and access the array once. You can check this on the CPU window, if you are just a bit proeficient in ASM.

You can download this sample application from CodeCentral.

You should make sure that you always have valid pointers to scanlines, so you should refresh your cache whenever the bitmap has changed Width, Heigth, PixelFormat, etc. A good place to do it is in the TBitmap's Changed virtual method, if you have a TBitmap descendant. Another thing you should pay special attention to is not to mix this cached scanline access and direct GDI access. If you do, make sure to call GDIFlush to make sure the changes are applied, or call ScanLine[x] again, since it makes sure everything is in order.

Thats all for now. In the next article I'll be writing about using this method to write special effects in Delphi. See you there!

http://www.efg2.com/Lab/ImageProcessing/Scanline.htm

Manipulating Pixels With Delphi's ScanLine Property

http://wiki.freepascal.org/Fast_direct_pixel_access

Pixel format

We can use simple integer pixels which would be faster on 32-bit platform:

TFastBitmapPixel = Integer;

Or more abstract pixels with separated components:

TFastBitmapPixelComponents = packed recordBlue: Byte;Green: Byte;Red: Byte;Alpha: Byte;
end;

It is possible even go further to bit level and define 16-bit RGB pixel used for some LCD displays:

TFastBitmapPixelComponents16Bit = packed recordBlue: 0..31; // 5 bitsGreen: 0..63; // 6 bitsRed: 0..31; // 5 bits
end;

Pixel can be pointer which would be useful for cases where pixel value itself is rather large or can be compressed somehow.

TFastBitmapPixelComponentsValue = packed recordBlue: Word;Green: Word;Red: Word;Alpha: Word;
end;TFastBitmapPixelComponents = ^TFastBitmapPixelComponentsValue;

Another situation is use of polymorphism of classes.

TFastBitmapPixel = classprocedure Clear; virtual;
end;TFastBitmapPixelComponents = class(TFastBitmapPixel)Blue: Word;Green: Word;Red: Word;Alpha: Word;procedure Clear; override;
end;TFastBitmapPixelByte = class(TFastBitmapPixel)Value: Byte;procedure Clear; override;
end;

Bitmap structure

Bitmap class should provide direct pixel access given by X, Y coordinate. But some graphic operation could be further optimized by not doing coordinate calculations for every pixel and rather do pixel pointer shifting by simple memory pointer addition. Some mass operation as filling rectangular region could be optimized using Move and FillChar functions.

Two dimensional dynamic array

This is native way to express two dimensional array in pascal. Internal structure is implemented as pointer to array of pointers to data because dynamic array is in fact pointer to array data. Then calculation of pixel position is matter of fetching pointer for rows and add horizontal position to it.

interfacetypeTFastBitmap = classprivatefunction GetSize: TPoint;procedure SetSize(const AValue: TPoint);publicPixels: array of array of TFastBitmapPixel;property Size: TPoint read GetSize write SetSize;end;implementation{ TFastBitmap }function TFastBitmap.GetSize: TPoint;
beginResult.X := Length(Pixels);if Result.X > 0 then Result.Y := Length(Pixels[0])else Result.Y := 0;
end;procedure TFastBitmap.SetSize(const AValue: TPoint);
beginSetLength(Pixels, AValue.X, AValue.Y);
end;

Raw dynamic memory

It is good to have whole bitmap in one compact memory area. Such memory block behave as video memory of video card. Position of pixels have to be calculated by using equation Y * Width + X with use of instructions for addition and multiplication. Access to pixels is pretty fast thanks to GetPixel and SetPixel methods inlining. But more instruction have to be used than in case of two dimensional dynamic array.

interfacetypePFastBitmapPixel = ^TFastBitmapPixel;TFastBitmap = classprivateFPixelsData: PByte;FSize: TPoint;function GetPixel(X, Y: Integer): TFastBitmapPixel; inline;procedure SetPixel(X, Y: Integer; const AValue: TFastBitmapPixel); inline;procedure SetSize(const AValue: TPoint);publicconstructor Create;destructor Destroy; override;property Size: TPoint read FSize write SetSize;property Pixels[X, Y: Integer]: TFastBitmapPixel read GetPixel write SetPixel;end;implementation{ TFastBitmap }function TFastBitmap.GetPixel(X, Y: Integer): TFastBitmapPixel;
beginResult := PFastBitmapPixel(FPixelsData + (Y * FSize.X + X) * SizeOf(TFastBitmapPixel))^;
end;procedure TFastBitmap.SetPixel(X, Y: Integer; const AValue: TFastBitmapPixel);
beginPFastBitmapPixel(FPixelsData + (Y * FSize.X + X) * SizeOf(TFastBitmapPixel))^ := AValue;
end;procedure TFastBitmap.SetSize(const AValue: TPoint);
beginif (FSize.X = AValue.X) and (FSize.Y = AValue.X) then Exit;FSize := AValue;FPixelsData := ReAllocMem(FPixelsData, FSize.X * FSize.Y * SizeOf(TFastBitmapPixel));
end;constructor TFastBitmap.Create;
beginSize := Point(0, 0);
end;destructor TFastBitmap.Destroy;
beginFreeMem(FPixelsData);inherited Destroy;
end;

Strict Pointer pixel access

We are able eliminate some of coordinate multiplications with low level pixel access using pointers only. Then only addition(incrementation) is necessary to change current pixel position.

interfacetypeTFastBitmap = classprivateFPixelsData: PByte;FSize: TPoint;procedure SetSize(const AValue: TPoint);publicconstructor Create;destructor Destroy; override;procedure RandomImage;property Size: TPoint read FSize write SetSize;function GetPixelAddress(X, Y: Integer): PFastBitmapPixel; inline;function GetPixelSize: Integer; inline;end;  implementation{ TFastBitmap }procedure TFastBitmap.SetSize(const AValue: TPoint);
beginif (FSize.X = AValue.X) and (FSize.Y = AValue.X) then Exit;FSize := AValue;FPixelsData := ReAllocMem(FPixelsData, FSize.X * FSize.Y * SizeOf(TFastBitmapPixel));
end;constructor TFastBitmap.Create;
beginSize := Point(0, 0);
end;destructor TFastBitmap.Destroy;
beginFreeData(FPixelData);inherited Destroy;
end;function TFastBitmap.GetPixelAddress(X, Y: Integer): PFastBitmapPixel;
beginResult := PFastBitmapPixel(FPixelsData) + Y * FSize.X + X;
end;function TFastBitmap.GetPixelSize: Integer;
beginResult := SizeOf(TFastBitmapPixel);
end;

In this case drawing pixels is less readable:

procedure RandomImage(FastBitmap: TFastBitmap);
varX, Y: Integer;PRow: PFastBitmapPixel;PPixel: PFastBitmapPixel;
beginwith FastBitmap do beginPRow := GetPixelAddress(0, Size.Y div 2);for Y := 0 to Size.Y - 1 do beginPPixel := PRow;for X := 0 to Size.X - 1 do beginPPixel^ := Random(256) or (Random(256) shl 16) or (Random(256) shl 8);Inc(PPixel);end;Inc(PRow, Size.X);end;end;
end;

Pixel operation optimization

Basic line algorithm

This is naive form which is readable but with price of slower processing.

procedure TFastBitmap.HorizontalLine(X, Y, Length: Integer; Color: TFastBitmapPixel);
varI: Integer;
beginfor I := 0 to Length - 1 doPixels[X + I, Y] := Color;
end;

Pointers

With use of pointers we can eliminate much of pixel address addition and multiplication by Pixels property access. Only fast increment operation is performed.

procedure TFastBitmap.HorizontalLine(X, Y, Length: Integer; Color: TFastBitmapPixel);
varI: Integer;P: PFastBitmapPixel;
beginP := PFastBitmapPixel(FPixelData + (Y * Size.X + X) * SizeOf(TFastBitmapPixel));for I := 0 to Length - 1 do beginP^ := Color;Inc(P);end;
end;

Mass fill using FillDWord

Access using pointers and incrementation is fastest possible using conventional single operations. But most of todays CPU offer instructions for mass operations like MOVS, STOS for x86 architecture. Pixel size should be 1, 2 or 4 bytes to be able to use this optimization.

procedure TFastBitmap.HorizontalLine(X, Y, Length: Integer; Color: TFastBitmapPixel);
varI: Integer;P: PFastBitmapPixel;
beginP := PFastBitmapPixel(FPixelData + (Y * Size.X + X) * SizeOf(TFastBitmapPixel));FillDWord(P^, Length, Color);
end;

Inlining

If code is notably smaller like SetPixel and GetPixel methods it is better to inline instructions rather than do push and pop operations on stact with execution of call and ret instruction. This optimization will be even significant if such operation is executed many times as pixel operations do.

procedure TFastBitmap.HorizontalLine(X, Y, Length: Integer; Color: TFastBitmapPixel); inline;
varI: Integer;P: PFastBitmapPixel;
beginP := PFastBitmapPixel(FPixelData + (Y * Size.X + X) * SizeOf(TFastBitmapPixel));FillDWord(P^, Length, Color);
end;

DMA

If memory block have to be copied to another memory place or device memory DMA(Direct Memory Access) can be used. CPU doesn't have to be involved in copy operations and can do further processing. This kind of optimization can be used in OpenGL for copying data to video card memory.

Drawing bitmap on screen

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 custom data structure functions could be optimized for faster block memory operations if necessary.

To be able to display image on Form custom bitmap have to be copied to some TWinControl canvas area. Image have to be copied repeatedly if motion image is generated. Every bitmap copy in memory take some time. Then our aim is to do as low as possible copy operations and rather copy our bitmap to screen directly if possible.

You can draw image as fast as possible in simple loop:

repeatFastBitmapToBitmap(FastBitmap, Image1.Picture.Bitmap);Application.ProcessMessages;
until Terminated;

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.

TForm1 = class(TForm)
publishedprocedure Timer1Execute(Sender: TObject);...
publicRedrawPending: Boolean;Drawing: Boolean;FastBitmap: TFastBitmap;procedure Redraw;...
end;procedure TForm1.Redraw;
beginRedrawPending := True;
end;procedure TForm1.Timer1Execute(Sender: TObject);
beginif (not Drawing) and RedrawPending then tryDrawing := True;CustomProcessing(FastBitmap);FastBitmapToBitmap(FastBitmap, Image1.Picture.Bitmap);        finallyRedrawPending := False;Drawing := False;end;
end;

Draw methods

TBitmap.Canvas.Pixels

This is most straighforward but slowest method. Suppose we have grayscale values from 0 to 255 in our TFastBitmap, then to copy it on the TBitmap, we could write this :

function FastBitmapToBitmap(FastBitmap: TFastBitmap; Bitmap: TBitmap);
varX, Y: Integer;
beginfor X := 0 to FastBitmap.Size.X - 1 dofor Y := 0 to FastBitmap.Size.Y - 1 doBitmap.Canvas.Pixels[X, Y] := FastBitmap.Pixels[X, Y] * $010101;
end;

TBitmap.Canvas.Pixels with Update locking

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

function FastBitmapToBitmap(FastBitmap: TFastBitmap; Bitmap: TBitmap);
varX, Y: Integer;
begintryBitmap.BeginUpdate(True);for X := 0 to FastBitmap.Size.X - 1 dofor Y := 0 to FastBitmap.Size.Y - 1 doBitmap.Canvas.Pixels[X, Y] := FastBitmap.Pixels[X, Y] * $010101;  finallyBitmap.EndUpdate(False);end;
end;

TBGRABitmap.ScanLine

There is graphic library BGRABitmap which allow access to scan lines. Overall speed of this method is pretty good. Drawing is done directly to Canvas of some TWinControl components like TForm of TPaintBox. The pixel format is 32-bit color with alpha channel, i.e. 8-bit for each channel.

Using TBitmap.ScanLine was a method used frequently on Delphi. But TBitmap.ScanLine is not supported by LCL. ScanLine property give access to memory starting point for each row raw data. Then direct manipulation with pixels is much faster than using Pixels property as no additional events is fired.

We can copy our grayscale FastBitmap data to a TBGRABitmap to render it on the screen.

uses..., BGRABitmap, BGRABitmapTypes;procedure FastBitmapToCanvas(FastBitmap: TFastBitmap; Canvas: TCanvas);
varX, Y: Integer;P: PBGRAPixel;bgra: TBGRABitmap;
beginbgra := TBGRABitmap.Create(FastBitmap.Size.X,FastBitmap.Size.Y);with FastBitmap dofor Y := 0 to Size.Y - 1 do beginP := PInteger(bgra.ScanLine[Y]);for X := 0 to Size.X - 1 do beginPInteger(P)^  := (Pixels[X, Y] * $010101) or $ff000000;// It is a shortcut for :// P^.Red := Pixels[X, Y];// P^.Green := Pixels[X, Y];// P^.Blue := Pixels[X, Y];// P^.Alpha := 255;Inc(P);end;end;bgra.InvalidateBitmap; // Changed by direct accessbgra.Draw(Canvas, 0, 0, False);bgra.Free;
end;

We can also use TBGRABitmap only. This library works if possible with device independent bitmaps of the operating system, so it is generally a direct pixel access or quasi-direct pixel access.

TBitmap.RawImage

This method is so far fastest in comparing to previous ones but more complicated as special care have to be given to bitmap data structure. Example assume that bitmap PixelFormat is pf24bit. Accessed raw data may differs across platforms.

uses..., GraphType;function FastBitmapToBitmap(FastBitmap: TFastBitmap; Bitmap: TBitmap);
varX, Y: Integer;PixelPtr: PInteger;PixelRowPtr: PInteger;P: TPixelFormat;RawImage: TRawImage;BytePerPixel: Integer;
begintryBitmap.BeginUpdate(False);RawImage := Bitmap.RawImage;PixelRowPtr := PInteger(RawImage.Data);BytePerPixel := RawImage.Description.BitsPerPixel div 8;for Y := 0 to Size.Y - 1 do beginPixelPtr := PixelRowPtr;for X := 0 to Size.X - 1 do beginPixelPtr^ := Pixels[X, Y] * $010101;Inc(PByte(PixelPtr), BytePerPixel);end;Inc(PByte(PixelRowPtr), RawImage.Description.BytesPerLine);end;finallyBitmap.EndUpdate(False);end;
end;

RawImage.Description values examples on various platforms:

Platform Format Depth BitsPerPixel BitOrder ByteOrder LineOrder LineEnd RedPrec RedShift GreenPrec GreenShift BluePrec BlueShift AlphaPrec AlphaShift
Windows RGBA 15 16 ReverseBits LSBFirst TopToBottom DWordBoundary 5 10 5 5 5 0 0 0
Windows RGBA 24 24 ReverseBits LSBFirst TopToBottom DWordBoundary 8 16 8 8 8 0 0 0
Linux GTK2 RGBA 24 32 BitsInOrder LSBFirst TopToBottom DWordBoundary 8 16 8 8 8 0 0 0

Delphi TBitmap Scanline相关推荐

  1. Delphi下实现全屏快速找图找色

    前言 最近有好几个朋友都在问我找图找色的问题,奇怪?于是乎写了一个专门用于找图找色的单元文件"BitmapData.pas".在这个单元文件中我实现了从文件中导入位图.屏幕截图.鼠 ...

  2. 网摘-按键精灵屏幕找色原理分析

    一.数据提取 位图其实可以看成是一个由象素组成的矩阵,找图找色可以看成是象素值的比对.很多新手在设计这类的程序时喜欢使用TBitmap.Canvas.Pixels属性,这个属性其实是对API函数Get ...

  3. Delphi 10.3.3 演示FMX TBitmap.Canvas绘图属性和方法

    这个例子展示了如何使用TBitmap.Canvas属性.这个示例在图像上绘制了一个矩形. 要构建和测试这个例子,请创建一个多设备应用程序--Delphi,然后将下一个对象添加到表单中. 一个TImag ...

  4. Delphi 从PaintBox拷贝一部分内容到TBitmap

    将指定的TPaintBox内容(假如为paintbox1)拷贝到一个TBitmap(如Bitmap),可以这么做 Bitmap.Width := PaintBox1.Width; Bitmap.Hei ...

  5. 如何用DELPHI区分彩色图和黑白图(多色与单色)??

    如何用DELPHI区分彩色图和黑白图(多色与单色)?? Delphi / Windows SDK/API http://www.delphi2007.net/DelphiMultimedia/html ...

  6. delphi之模糊找图

    AutoHotkey的源码,模糊找图和精确找图思路一样,也是用笨方法.原来的C代码比较难看懂,这里的delphi代码,很容易弄明白. 以下是模糊的找图.如果需要,可以再做优化处理.注意我这里去掉了透明 ...

  7. 用scanline取BMP上某点的颜色,代码如下,为什么可以编译,运行时却出错呢?...

    用scanline取BMP上某点的颜色,代码如下,为什么可以编译,运行时却出错呢? Delphi / Windows SDK/API http://www.delphi2007.net/DelphiM ...

  8. Delphi 汇编学习(八)--- 图像水平镜像垂直镜像的极致优化

    一:水平镜像/翻转        将一幅图像水平镜像/翻转,代码很简单,就一行代码: procedure HorizMirror(bmp: TBitmap); beginbmp.Canvas.Copy ...

  9. Delphi 二维码产生和扫描

    Zint用于产生二维码. Zxing用读取二维码. VFrames.pas和VSample.pas用于摄像头. 另附带摄像头相关的类库,也可用开源的dspack也可用于摄像头的需求. 以上为开源的信息 ...

最新文章

  1. 笔记 | PyTorch安装及入门教程
  2. Linux下测试的c++的使用
  3. Exchange企业实战技巧(16)发布SMTP、POP、IMAP连接信息设置
  4. OpenKruise v0.9.0 版本发布:新增 Pod 重启、删除防护等重磅功能
  5. Wowza® Media Systems 使用配置手册。
  6. 国外程序员整理的 C++ 资源大全 (zt)
  7. RabbitMQ整合SpringBoot(web)
  8. php显示无法找到该网页,window_Win8系统IE浏览器提示无法找到该网页的解决方法,  我们在浏览网页的时候, - phpStudy...
  9. Asp.net中实现同一用户名同时登陆,注销先前用户(转)
  10. was 程序jvm_【保家护行航】WAS知识学习分享
  11. 日期控件的用法 winform
  12. mysql 创建函数_MySQL文件及目录权限设置分析-爱可生
  13. sql 如何设置行级锁_如何使用SQL Server 2016行级安全性过滤和阻止数据访问
  14. 抛物线交点式公式_二次函数顶点式、交点式、两根式概念解读
  15. Java对象转Map,Map转对象
  16. 鼎捷T100权限管控设定
  17. Spring核心内容
  18. Docker Harbor——拥有独特UI界面的私有仓库
  19. [MTCTF]从出题人视角看ez_cms
  20. Excel 2010 VBA 入门 138 添加选项卡和内置控件

热门文章

  1. 下面属于python内置对象的有哪些_Python内置对象实现的方法及注意事项
  2. python叮当猫代码_详细介绍一个利用html+css实现叮当猫的实例代码
  3. html软件dr,了解HTML锚点 - osc_mbqdr3w5的个人空间 - OSCHINA - 中文开源技术交流社区...
  4. gridlayout布局单元格宽度设置_安卓界面布局之线性布局
  5. 让你“爱”上 GitHub,解决访问时图裂、加载慢的问题
  6. 面试 AI 算法岗,项目实战与比赛经验到底能为你加成多少?
  7. 《R语言预测实战》PDF,数据及代码
  8. 基于多搜索引擎和深度学习技术的自动问答
  9. 从零开始掌握Python机器学习(附不可错过的资源)
  10. jenkins pipeline_Jenkins流水线(pipeline)实战之:从部署到体验