GdiPlus[57]: 图像(九) IGPBitmap 特有的属性与方法


这包括:

//属性

IGPBitmap.Pixels;          { 获取或设置指定像素的颜色 }



//方法

IGPBitmap.SetResolution(); { 设置分辨率 }

IGPBitmap.GetHBitmap();    { 创建 GDI 格式的 Bitmap 并返回句柄 }

IGPBitmap.GetHIcon;        { 创建 Icon 文件并返回句柄 }

IGPBitmap.LockBits();      { 锁定对象中内存中的像素数据 }

IGPBitmap.UnlockBits();    { 解除 LockBits 的锁定 }


 
   

IGPBitmap.Pixels 属性测试:

GdiPlus[57]: 图像(九) IGPBitmap 特有的属性与方法


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 方法测试:

GdiPlus[57]: 图像(九) IGPBitmap 特有的属性与方法


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 方法测试:

GdiPlus[57]: 图像(九) IGPBitmap 特有的属性与方法


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.

通过修改内存数据使图像半透明的例子:

GdiPlus[57]: 图像(九) IGPBitmap 特有的属性与方法


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;


 
   

通过修改内存数据只保留图像绿色通道的例子:

GdiPlus[57]: 图像(九) IGPBitmap 特有的属性与方法


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;


 
   

通过修改内存数据使图像转灰度的例子:

GdiPlus[57]: 图像(九) IGPBitmap 特有的属性与方法


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 接口是个解决方案, 不过没有测试.

你可能感兴趣的:(bitmap)