[基礎篇]
首先看一段實現24位色圖像灰度化轉換的代碼
procedure Grayscale(const Bitmap:TBitmap);
var
X: Integer;
Y: Integer;
R,G,B,Gray: Byte;
Color: TColor;
begin
for Y := 0 to (Bitmap.Height - 1) do
begin
for X := 0 to (Bitmap.Width - 1) do
begin
Color := Bitmap.Canvas.Pixels[X,Y];
R := Color and $FF;
G := (Color and $FF00) shr 8;
B := (Color and $FF0000) shr 16;
Gray := Trunc(0.3 * R + 0.59 * G + 0.11 * B);
Bitmap.Canvas.Pixels[X,Y] := Gray shl 16 or Gray shl 8 or Gray;
end
end
end;
{這段代碼效率是非常低的,但可以方便我們理解同時一些問題}
Delphi的幫助中對TColor已經有了詳細的描述,這可以方便我們理解上面的代碼!
首先看:
R := Color and $FF;
G := (Color and $FF00) shr 8;
B := (Color and $FF0000) shr 16;
這是段常見的從TColor中提取三原色的代碼,但它是什麼意思呢?
首先應該知道and是與(.)運算,0.1=0,0.0=0,1.1=1,以取綠色為例:$FF00實際上就是$00FF00,它與一個TColor類型數按位進行與運算後,表示紅色和綠色的位都變為了$00,而表示綠色的部分不變(0,1和1進行與運算值都不變),再右移8位,自然就獲得了綠色值的8位表示!
再獲得三原色的值後,就是計算灰度值,0.3 * Red + 0.59 * Green + 0.11 * Blue 這是求加權平均值的公式。(因為人眼對顏色的敏感度不同,所以權值不同,就像在pf16bit中用了6位表示綠色,其它兩種顏色只用了5位,這問題以後另寫文章說明)
然後就是像素顏色信息的寫回,剛才是右移,現在自然就是左移,而或(+)運算就是(0+1=1,0+0=0,1+1=1),舉個簡單例子就是:($FF shl 16 = $FF0000) or ($FF shl 8 = $FF00) or $FF = $FFFFFF ,其實這裡的或運算當然也可以用 + 代替。
雖然上面的代碼實現了24位色圖像的灰度化,但當圖像比較大時,速度非常慢,為什麼?查看相關VCL代碼可知調用Bitmap.Canvas.Pixels獲取,寫入像素的顏色信息實際上是利用了API GetPixel、SetPixel,這種方法是非常低效的!(唯一的好處是在進行一些和顏色無關的操作,如圖像的旋轉,翻轉時不需要因為PixelFormat的不同而修改代碼)所以應該換一種更高效的訪問像素點數據的方法,如用API GetDIBits、SetDIBits,但這種方法比較復雜,好在Delphi3以後版本的TBitmap中提供了Scanline。利用Scanline可以快速對像素進行訪問!
還是以24位色(PixelFormats=pf24bit)為例,可改寫為:
procedure Grayscale(const Bitmap:TBitmap);
const
PixelCountMax = 32768;
type
pRGBTripleArray = ^TRGBTripleArray;
TRGBTripleArray = ARRAY[0..PixelCountMax-1] OF TRGBTriple;
var
Row: pRGBTripleArray;
X: Integer;
Y: Integer;
Gray: Byte;
begin
for Y := 0 to (Bitmap.Height - 1) do
begin
Row := Bitmap.ScanLine[Y];
for X := 0 to (Bitmap.Width - 1) do
begin
Gray := Trunc(0.3 * Row^[X].rgbtRed + 0.59 * Row^[X].rgbtGreen + 0.11 * Row^[X].rgbtBlue);
Row^[X].rgbtRed:=Gray;
Row^[X].rgbtGreen:=Gray;
Row^[X].rgbtBlue:=Gray;
end;
end;
end;
上面的例子用了一個TRGBTriple數組
PRGBTriple = ^TRGBTriple;
tagRGBTRIPLE = packed record
rgbtBlue: Byte;
rgbtGreen: Byte;
rgbtRed: Byte;
end;
TRGBTriple = tagRGBTRIPLE;
這種方法會限制位圖的大小,但一般不用理會,直接用TBitmap可處理不了那麼大的位圖
當然也可用指針的移動實現,實測結果這樣更快~~~
procedure Grayscale(const Bitmap:TBitmap);
var
X: Integer;
Y: Integer;
PRGB: pRGBTriple;
Gray: Byte;
begin
for Y := 0 to (Bitmap.Height - 1) do
begin
PRGB := Bitmap.ScanLine[Y];
for X := 0 to (Bitmap.Width - 1) do
begin
Gray := Trunc(0.3 * PRGB^.rgbtRed + 0.59 * PRGB^.rgbtGreen + 0.11 * PRGB^.rgbtBlue);
PRGB^.rgbtRed:=Gray;
PRGB^.rgbtGreen:=Gray;
PRGB^.rgbtBlue:=Gray;
Inc(PRGB);
end;
end;
end;
下篇中將進行進一步的探討!