zoukankan      html  css  js  c++  java
  • GdiPlus[57]: 图像(九) IGPBitmap 特有的属性与方法


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

  • 相关阅读:
    MyBatis(十):MyBatis懒加载 延迟加载详解
    MyBatis(九):MyBatis类型处理器(TypeHandler)详解
    MyBatis(八):MyBatis插件机制详解
    MyBatis(七):MyBatis缓存详解(一级缓存/二级缓存)
    MyBatis(六):MyBatis复杂映射
    内省概念及详解(MethodDescriptor、PropertyDescriptor)
    反射概念及详解
    sublime3 + chrome + xdebug调试php代码
    php的文件系统和程序执行函数整理
    php编程趣事:乘法口诀、百钱买百鸡、过桥等问题
  • 原文地址:https://www.cnblogs.com/del/p/1636711.html
Copyright © 2011-2022 走看看