这包括:
//属性 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 方法测试:

接下来都是和 IGPBitmap.LockBits、IGPBitmap.UnlockBits 相关的了.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;
在 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 接口是个解决方案, 不过没有测试.
分类:
Delphi 与 GDI+
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
2008-12-31 C# 语法练习(6): 枚举
2008-12-31 显示系统托盘列表(并执行隐藏与显示) - 回复 "anybet" 的问题
2007-12-31 Addr 函数 - 获取对象指针
2007-12-31 AbstractErrorProc - 抽象错误处理的指针