这包括:
//属性 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 接口是个解决方案, 不过没有测试.