随笔 - 2146  文章 - 97 评论 - 11761 trackbacks - 253


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

posted on 2009-12-31 11:39 万一 阅读(...) 评论(...) 编辑 收藏