這包括:
//屬性
IGPBitmap.Pixels; { 獲取或設置指定像素的顏色 }
//方法
IGPBitmap.SetResolution(); { 設置分辨率 }
IGPBitmap.GetHBitmap(); { 創建 GDI 格式的 Bitmap 並返回句柄 }
IGPBitmap.GetHIcon; { 創建 Icon 文件並返回句柄 }
IGPBitmap.LockBits(); { 鎖定對象中內存中的像素數據 }
IGPBitmap.UnlockBits(); { 解除 LockBits 的鎖定 }
IGPBitmap.Pixels 屬性測試:
uses GdiPlus;
procedure TForm1.FormPaint(Sender: TObject);
var
Bitmap: IGPBitmap;
Graphics: IGPGraphics;
i,j: Integer;
Color: TGPColor;
begin
Bitmap := TGPBitmap.Create('C:\GdiPlusImg\Grapes.jpg' );
Graphics := TGPGraphics.Create(Handle);
Graphics.DrawImage(Bitmap, 10 , 10, Bitmap.Width, Bitmap.Height);
for i := 0 to Bitmap.Width - 1 do for j := 0 to Bitmap.Height - 1 do
begin
Color := Bitmap.Pixels[i,j];
Bitmap.Pixels[i,j] := TGPColor.Create(Color.R, 0, 0);
end;
Graphics.TranslateTransform(Bitmap.Width + 10, 0);
Graphics.DrawImage(Bitmap, 10, 10, Bitmap.Width, Bitmap.Height);
end;
IGPBitmap.SetResolution 方法測試:
uses GdiPlus;
procedure TForm1.FormPaint(Sender: TObject);
var
Graphics: IGPGraphics;
Bitmap: IGPBitmap;
begin
Bitmap := TGPBitmap.Create('C:\GdiPlusImg\Apple.gif');
Graphics := TGPGraphics.Create(Handle);
Graphics.DrawImage(Bitmap, 10, 10);
Graphics.TranslateTransform(Bitmap.Width + 10, 0);
Bitmap.SetResolution(Bitmap.HorizontalResolution/2, Bitmap.VerticalResolution/2);
Graphics.DrawImage(Bitmap, 10, 10);
end;
IGPBitmap.GetHBitmap 方法測試:
uses GdiPlus;
procedure TForm1.FormPaint(Sender: TObject);
var
Bitmap: IGPBitmap;
GDIBitmap: TBitmap;
begin
Bitmap := TGPBitmap.Create('C:\GdiPlusImg\Bird.bmp');
GDIBitmap := TBitmap.Create;
//GetHBitmap 的參數是背景色, 用於填充透明部分; 無透明部分時它會被忽略
GDIBitmap.Handle := Bitmap.GetHBitmap($FF00000);
GDIBitmap.TransparentColor := clWhite;
GDIBitmap.Transparent := True;
Canvas.Draw(10, 10, GDIBitmap);
GDIBitmap.Free;
end;
IGPBitmap.GetHIcon 方法測試:
uses GdiPlus;
var Bitmap: IGPBitmap;
procedure TForm1.FormPaint(Sender: TObject);
var
Image: IGPImage;
Rect: TGPRect;
Graphics: IGPGraphics;
Attr: IGPImageAttributes;
begin
Image := TGPImage.Create('C:\GdiPlusImg\Apple.gif');
Bitmap := TGPBitmap.Create(16, 16);
Rect.Initialize(0, 0, Bitmap.Width, Bitmap.Height);
Graphics := TGPGraphics.Create(Bitmap);
Attr := TGPImageAttributes.Create;
Attr.SetColorKey($FFFFFFFF, $FFFFFFFF);
Graphics.DrawImage(Image, Rect, 0, 0, Image.Width, Image.Height, UnitPixel, Attr);
Application.Icon.Handle := Bitmap.GetHIcon;
end;
接下來都是和 IGPBitmap.LockBits、IGPBitmap.UnlockBits 相關的了.
在 WinAPI 中常常見到類似的函數, 因為內存中 Windows 的自動管理下, 數據中內存中的地址是動態的, 所以操作內存前一般要先鎖定, 鎖定後再給你指針; 這裡的 LockBits 也是這樣, 當然操作完成後要解鎖.
這裡的 LockBits 返回的不僅僅有內存地址, 而是包含內存地址的一個結構:
TGPBitmapData = record
Width: Cardinal; { 像素寬度; 或者說是一個掃描行中的像素數 }
Height: Cardinal; { 像素高度; 或者說是掃描行數 }
Stride: Integer; { 每一行的掃描寬度; 它應該是 4 的倍數 }
PixelFormat: TGPPixelFormat; { 像素格式信息 }
Scan0: Pointer; { 第一個像素數據的地址 }
Reserved: Cardinal; { 保留 }
end;
結構中只有 Stride 比較費解, 它是每行所占用的字節數, 譬如:
一行有 11 個像素(Width = 11), 對一個 32 位(每個像素 4 字節)的圖像, Stride = 11 * 4 = 44.
但還有個字節對齊的問題, 譬如:
一行有 11 個像素(Width = 11), 對一個 24 位(每個像素 3 字節)的圖像, Stride = 11 * 3 + 3 = 36.
為什麼不是 Stride = 33? 因為它是按 4 字節對齊的.
根據上面道理, 我們可以手動計算 Stride 的值:
1、Stride = 每像素占用的字節數(也就是像素位數/8) * Width;
2、如果 Stride 不是 4 的倍數, 那麼 Stride = Stride + (4 - Stride mod 4);
下面的例子測試和驗證了上面的思路:
uses GdiPlus;
procedure TForm1.FormPaint(Sender: TObject);
var
Bitmap: IGPBitmap;
Rect: TGPRect;
Graphics: IGPGraphics;
Data: TGPBitmapData;
n: Integer;
begin
ChDir('C:\GdiPlusImg\');
Bitmap := TGPBitmap.Create('Grapes.jpg');
Rect.Initialize(0, 0, Bitmap.Width, Bitmap.Height);
Data := Bitmap.LockBits(Rect, [ImageLockModeRead], Bitmap.PixelFormat);
n := GetPixelFormatSize(Data.PixelFormat) div 8 * Data.Width;
n := n + (4 - n mod 4);
ShowMessageFmt('%d, %d, %d', [Data.Width, Data.Stride, n]); { 187, 564, 564 }
Bitmap.UnlockBits(Data);
//
Bitmap := TGPBitmap.Create('Bird.bmp');
Rect.Initialize(0, 0, Bitmap.Width, Bitmap.Height);
Data := Bitmap.LockBits(Rect, [ImageLockModeRead], Bitmap.PixelFormat);
n := GetPixelFormatSize(Data.PixelFormat) div 8 * Data.Width;
if n mod 4 <> 0 then n := n + (4 - n mod 4);
ShowMessageFmt('%d, %d, %d', [Data.Width, Data.Stride, n]); { 110, 112, 112 }
Bitmap.UnlockBits(Data);
//
Bitmap := TGPBitmap.Create('Apple.gif');
Rect.Initialize(0, 0, Bitmap.Width, Bitmap.Height);
Data := Bitmap.LockBits(Rect, [ImageLockModeRead], Bitmap.PixelFormat);
n := GetPixelFormatSize(Data.PixelFormat) div 8 * Data.Width;
if n mod 4 <> 0 then n := n + (4 - n mod 4);
ShowMessageFmt('%d, %d, %d', [Data.Width, Data.Stride, n]); { 120, 120, 120 }
Bitmap.UnlockBits(Data);
//
Bitmap := TGPBitmap.Create('ImageFileSmall.jpg');
Rect.Initialize(0, 0, Bitmap.Width, Bitmap.Height);
Data := Bitmap.LockBits(Rect, [ImageLockModeRead], Bitmap.PixelFormat);
n := GetPixelFormatSize(Data.PixelFormat) div 8 * Data.Width;
if n mod 4 <> 0 then n := n + (4 - n mod 4);
ShowMessageFmt('%d, %d, %d', [Data.Width, Data.Stride, n]); { 320, 960, 960 }
Bitmap.UnlockBits(Data);
end;
面對不同像素格式的圖像, 這個字節對齊的問題的確很麻煩;
但對 32 位的圖像就可以不考慮這個問題了, 因為它每行的數據剛好是 4 的倍數; 下面的練習就都討了這個巧.
另外還要知道: 32 位圖像每個像素的 4 個字節在內存中的排列次序是: BBGGRRAA.
通過修改內存數據使圖像半透明的例子:
uses GdiPlus;
type
PPixelFour = ^TPixelFour;
TPixelFour = record
b1,b2,b3,b4: Byte;
end;
procedure TForm1.FormPaint(Sender: TObject);
var
Bitmap, Bitmap32: IGPBitmap;
Graphics: IGPGraphics;
Rect: TGPRect;
Data: TGPBitmapData;
P: PPixelFour;
Brush: IGPHatchBrush;
i: Integer;
begin
Bitmap := TGPBitmap.Create('C:\GdiPlusImg\Grapes.jpg');
Rect.Initialize(0, 0, Bitmap.Width, Bitmap.Height);
// 獲取一個 32 位像素格式的圖像;
// 好像 IGPBitmap.Clone 方法就可以方便做這個轉換, 但不靈;
// 還有一個 IGPBitmap.ConvertFormat 方法是 GDI+1.1 的;
// 只能手動了
Bitmap32 := TGPBitmap.Create(Bitmap.Width, Bitmap.Height, PixelFormat32bppARGB);
Graphics := TGPGraphics.Create(Bitmap32);
Graphics.DrawImage(Bitmap, Rect);
Data := Bitmap32.LockBits(Rect, [ImageLockModeWrite], Bitmap32.PixelFormat);
P := Data.Scan0;
{$PointerMath On}
for i := 0 to Data.Width * Data.Height - 1 do P[i].b4 := $80;
{$PointerMath Off}
Bitmap32.UnlockBits(Data);
Graphics := TGPGraphics.Create(Handle);
Brush := TGPHatchBrush.Create(HatchStyleCross, $FFD0D0D0, $FFFFFFFF);
Graphics.FillRectangle(Brush, TGPRect.Create(ClIEntRect));
Graphics.DrawImage(Bitmap32, 10, 10);
end;
通過修改內存數據只保留圖像綠色通道的例子:
uses GdiPlus;
type
PPixelFour = ^TPixelFour;
TPixelFour = record
b1,b2,b3,b4: Byte;
end;
procedure TForm1.FormPaint(Sender: TObject);
var
Bitmap, Bitmap32: IGPBitmap;
Graphics: IGPGraphics;
Rect: TGPRect;
Data: TGPBitmapData;
P: PPixelFour;
i: Integer;
begin
Bitmap := TGPBitmap.Create('C:\GdiPlusImg\Grapes.jpg');
Rect.Initialize(0, 0, Bitmap.Width, Bitmap.Height);
// 獲取一個 32 位像素格式的圖像;
Bitmap32 := TGPBitmap.Create(Bitmap.Width, Bitmap.Height, PixelFormat32bppARGB);
Graphics := TGPGraphics.Create(Bitmap32);
Graphics.DrawImage(Bitmap, Rect);
Data := Bitmap32.LockBits(Rect, [ImageLockModeWrite], Bitmap32.PixelFormat);
P := Data.Scan0;
{$PointerMath On}
for i := 0 to Data.Width * Data.Height - 1 do
begin
P[i].b1 := 0;
P[i].b3 := 0;
end;
{$PointerMath Off}
Bitmap32.UnlockBits(Data);
Graphics := TGPGraphics.Create(Handle);
Graphics.DrawImage(Bitmap32, 10, 10);
end;
通過修改內存數據使圖像轉灰度的例子:
uses GdiPlus;
type
PPixelFour = ^TPixelFour;
TPixelFour = record
b1,b2,b3,b4: Byte;
end;
procedure TForm1.FormPaint(Sender: TObject);
var
Bitmap, Bitmap32: IGPBitmap;
Graphics: IGPGraphics;
Rect: TGPRect;
Data: TGPBitmapData;
P: PPixelFour;
i,g: Integer;
begin
Bitmap := TGPBitmap.Create('C:\GdiPlusImg\Grapes.jpg');
Rect.Initialize(0, 0, Bitmap.Width, Bitmap.Height);
// 獲取一個 32 位像素格式的圖像;
Bitmap32 := TGPBitmap.Create(Bitmap.Width, Bitmap.Height, PixelFormat32bppARGB);
Graphics := TGPGraphics.Create(Bitmap32);
Graphics.DrawImage(Bitmap, Rect);
Data := Bitmap32.LockBits(Rect, [ImageLockModeWrite], Bitmap32.PixelFormat);
P := Data.Scan0;
{$PointerMath On}
for i := 0 to Data.Width * Data.Height - 1 do
begin
g := (P[i].b1 + P[i].b2 + P[i].b3) div 3;
P[i].b1 := g;
P[i].b2 := g;
P[i].b3 := g;
end;
{$PointerMath Off}
Bitmap32.UnlockBits(Data);
Graphics := TGPGraphics.Create(Handle);
Graphics.DrawImage(Bitmap32, 10, 10);
end;
另外:
1、其實僅就顏色變換, 以上手段都不如使用 ColorMatrix 來得方便;
2、以上內存操作, 換用 IGPBitmap.Pixels 也都能更方便地做到, 但它太慢了.
3、在處理字節偏移時或許 IGPImageBytes 接口是個解決方案, 不過沒有測試.