前兩個結果因機器不同而不同,第三個結果很特別,ScanLine[0]與ScanLine[1]之間相差3000=1000像素寬×3字節這很容易理解,但為什麼是負數呢?因為BMP圖像數據是“按行存放,每行按雙字對齊,行按倒序方式存放”的,也就是說屏幕顯示的第一行存放在最後,屏幕顯示的最後一行存放在前面,所以用ACDSee等看圖軟件查看尺寸較大的位圖時先從下部開始顯示就是這個道理。
從上面的結果可以看出TBitmap的圖像數據在內存中是按行倒序連續存放的,通過TBitmap.ScanLine[TBitmap.Height-1]可以取得首地址即圖像緩沖區地址。接著我們來實踐一下,通過直接對圖像緩沖區的讀寫將圖像淡出到黑色:
procedure TForm1.Button1Click(Sender: TObject);
const
FADEOUT_STEP = 24; //淡出衰減值
FIX_WIDTH = 320;
FIX_HEIGHT = 200;
var
BitMap: TBitmap;
hWinDC: HDC;
flagAgein: Boolean;
lpBuffer: PByte; //圖像緩沖區指針
begin
BitMap := TBitmap.Create;
if not Assigned(BitMap) then Exit;
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) then BitBlt(Bitmap.Canvas.Handle, 0, 0, FIX_WIDTH, FIX_HEIGHT, hWinDC, 0, 0, SRCCOPY)
else BitBlt(Bitmap.Canvas.Handle, 0, 0, FIX_WIDTH, FIX_HEIGHT, Canvas.Handle, 0, 0, SRCCOPY);
repeat
flagAgein := false;
lpBuffer := BitMap.ScanLine[FIX_HEIGHT-1]; //取得圖像緩沖區首地址
//Integer(BitMap.ScanLine[0]) + FIX_WIDTH*3 為圖像緩沖區結束地址
while Integer(lpBuffer) < Integer(BitMap.ScanLine[0]) + FIX_WIDTH*3 do begin
if lpBuffer^>FADEOUT_STEP then
begin
Dec(lpBuffer^, FADEOUT_STEP);
flagAgein := true;
end
else lpBuffer^ :=0;
Inc(lpBuffer);
Application.ProcessMessages;
end;
Canvas.Draw(0, 0, BitMap);
until (not flagAgein);
MessageBox(Handle, 'Done', 'Fadeout', MB_OK);
finally
if Assigned(BitMap) then FreeAndNil(BitMap);
Button1.Visible := true;
end;
end;
最後補充說明一下:
1、Bitmap圖像緩沖區是雙節對齊的,如果把例1中的圖像寬度改為999,一個像素行還是占3000個字節。
2、目前Bitmap.PixelFormat有pfDevice、pf1bit、pf4bit、pf8bit、pf15bit、pf16bit、pf24bit、pf32bit、pfCustom共9種,不同格式每個像素所占字節數不同,其中pf4bit和pf8bit格式的圖像緩沖區保存的為顏色索引號,真正的顏色值在調色板中,pf15bit、pf16bit格式中RGB所占的位數(Bit)不一定是等長的。有興趣的可查閱相關資料。