windows gdi+ Bitmap 总结

windows gdi+ 是对 windows gdi 的一个c++封装,同时增加了一些扩展功能,如反走样,样条曲线,变换矩阵,图像编解码等。

gdi+ 相对于 gdi 也存在一些不足之处,如 执行效率较低,不支持位运算(gdi可通过位运算实现局部透明)等。

在实际应用中,由于主要做位图相关操作,我比较倾向选择使用 gdi 进行绘图,这样可以得到较高的绘制速度,以及更多的灵活性。

对于 gdi+,我主要关注点在于 Bitmap 类,下面只讨论 Bitmap 类相关内容。

 

1 使用 gdi+ 的安全考虑

首先,Microsoft 给出了使用 gdi+ 的 Security Considerations,我将其记录下来,以提醒自己。

    1)检查构造函数是否成功

     通过 Image::GetLastStatus() 函数检查是否成功构造位图,示例代码如下:

     

Image myImage(L"Climber.jpg");
Status st = myImage.GetLastStatus();
if(Ok == st)
// The constructor was successful. Use myImage.
else if(InvalidParameter == st)
// The constructor failed because of an invalid parameter.
else
// Compare st to other elements of the Status
// enumeration or do general error processing.

    2)某些函数调用前需要分配足够内存,代码如下:

 

GraphicsPath path;
path.AddEllipse(10, 10, 200, 100);
INT count = path.GetPointCount(); // get the size
Point* pointArray = new Point[count]; // allocate the buffer
if(pointArray) // Check for successful allocation.
{
path.GetPathPoints(pointArray, count); // get the data
... // use pointArray
delete[] pointArray; // release the buffer
pointArray = NULL;
}

    3)错误检查,检查函数返回值以确保函数执行成功。

    4)线程同步

         当一个gdi+对象在多个线程中使用时,gdi+没有提供自动同步机制,需要程序员确保线程同步;

         官网同时解释到,某些gdi+函数可能返回ObjectBusy,但不要仅指望该机制实现线程同步,程序员需要使用如互斥量等方式以确保线程同步。

 

2 Bitmap类所支持文件格式

   windows gdi+ 提供了 Image 类处理光栅图像与矢量图像,该类提供一些共有的处理函数,如图像打开,图像保存,图像尺寸等。

   对于更多不同的处理函数,Image 分别派生出 Bitmap 与 Metafile l类,Bitmap 负责光栅图像相关处理,Metafile 负责矢量图像相关处理。

   Bitmap 包含了基本图像编解码功能,支持特定图像格式,如果需要支持更多其他图像格式,windows 提供了 WIC(windows image component)可进行扩展。

   一般情况下,Bitmap 所提供的几种图像格式已经可以满足需求,主要图像文件格式包括:

  1)BMP

      这是一个标准的非压缩图像文件格式,用于存储设备无关位图,支持位深包括 1,2,4,8,15(16),24,32,64。

  2)GIF(Graphics Interchange Format)

      该图像格式常用于网络传输中,采用无损压缩,支持透明,支持多帧(动画),其最大位深为 8 位。

  3)PNG(Portable Network Graphics)

     PNG与GIF类似,采用无损压缩,但支持更大位深,彩色图像位深可以为 8, 24,48,黑白图像位深可以为 1,2,4,8,16,同时支持渐进显示,以及存储gamma曲线等功能。

  4)JPEG(Joint Photographic Experts Group)

     JPEG采用有损压缩,通过调整压缩比例可以控制图像文件大小。

  5)Exif(Exchangeable Image File)

    Exif 专为数码相机使用,使用 JPEG 压缩,同时增加了一些相机拍照时相关信息。

  6)TIFF(Tag Image File Format)

    TIFF是一个灵活可扩展的图像文件格式,支持任意位深,可采用多种压缩算法。

 

3 使用Bitmap类

    1)创建Bitmap对象

    可以通过构造函数创建一个Bitmap对象,也可以使用对应的静态创建一个Bitmap对象,当使用静态函数创建对象时,在对象使用完成后需要手动删除。

    创建Bitmap对象的数据源可以为:文件,文件流,内存DIB,内存DDB等,这里只关心文件与内存DIB。

    Bitmap(IN const WCHAR *filename, IN BOOL useEmbeddedColorManagement = FALSE);

    static Bitmap* FromFile(IN const WCHAR *filename, IN BOOL useEmbeddedColorManagement = FALSE);

    filename 为文件名,注意需要使用 unicode 编码,参数 useEmbeddedColorManagement 为色彩校正相关内容,一般使用默认值即可。

    Bitmap(IN const BITMAPINFO* gdiBitmapInfo, IN VOID* gdiBitmapData);

    static Bitmap* FromBITMAPINFO(IN const BITMAPINFO* gdiBitmapInfo, IN VOID* gdiBitmapData);

    gdiBitmapInfo 为DIB位图结构体信息,gdiBitmapData 为DIB位图数据信息。

   2)访问Bitmap对象数据

    可以通过 LockBits 与 UnlockBits 函数访问 Bitmap对象数据,首先调用 LockBits 获得 BitmapData 结构体,然后通过结构体访问图像数据,完成后调用 UnlockBits。

    BitmapData  结构体定义如下:

    class BitmapData
   {
       public:
       UINT Width;
       UINT Height;
       INT Stride;
       PixelFormat PixelFormat;
       VOID* Scan0;
       UINT_PTR Reserved;
   };

   Width,Height 表示图像宽度与高度,PixelFormat 为图像格式定义,包括:

   #define PixelFormat1bppIndexed (1 | ( 1 << 8) | PixelFormatIndexed | PixelFormatGDI)
   #define PixelFormat4bppIndexed (2 | ( 4 << 8) | PixelFormatIndexed | PixelFormatGDI)
   #define PixelFormat8bppIndexed (3 | ( 8 << 8) | PixelFormatIndexed | PixelFormatGDI)
   #define PixelFormat16bppGrayScale (4 | (16 << 8) | PixelFormatExtended)
   #define PixelFormat16bppRGB555 (5 | (16 << 8) | PixelFormatGDI)
   #define PixelFormat16bppRGB565 (6 | (16 << 8) | PixelFormatGDI)
   #define PixelFormat16bppARGB1555 (7 | (16 << 8) | PixelFormatAlpha | PixelFormatGDI)
   #define PixelFormat24bppRGB (8 | (24 << 8) | PixelFormatGDI)
   #define PixelFormat32bppRGB (9 | (32 << 8) | PixelFormatGDI)
   #define PixelFormat32bppARGB (10 | (32 << 8) | PixelFormatAlpha | PixelFormatGDI | PixelFormatCanonical)
   #define PixelFormat32bppPARGB (11 | (32 << 8) | PixelFormatAlpha | PixelFormatPAlpha | PixelFormatGDI)
   #define PixelFormat48bppRGB (12 | (48 << 8) | PixelFormatExtended)
   #define PixelFormat64bppARGB (13 | (64 << 8) | PixelFormatAlpha | PixelFormatCanonical | PixelFormatExtended)
   #define PixelFormat64bppPARGB (14 | (64 << 8) | PixelFormatAlpha | PixelFormatPAlpha | PixelFormatExtended)
   #define PixelFormat32bppCMYK (15 | (32 << 8))
   #define PixelFormatMax 16

   根据格式可以确定每个像素所占用的位数,如:

   PixelFormat1bppIndexed,PixelFormat4bppIndexed,PixelFormat8bppIndexed 每个像素分别占用1位,4位,8位内存,需要使用查找表以映射到真实颜色;

   PixelFormat16bppGrayScale 是每个像素占用16位(2字节)的黑白图像;

   PixelFormat16bppRGB555 是每个像素占用16位的彩色图像,其中,每个RGB分量占用5位,剩下1位内存未使用;

   PixelFormat16bppRGB565 是每个像素占用16位的彩色图像,其中,RB分量占用5位,G分量占用6位;

   PixelFormat24bppRGB 是每个像素占用24位的真彩色图像,其中,每个RGB分量占用8位,这是目前很常用的图像格式;

   PixelFormat32bppARGB 是每个像素占用32位的真彩色图像,增加了 alpha 通道描述透明色;

   PixelFormat32bppPARGB 是每个像素占用32位的真彩色图像,P表示RBG分量被预乘以alpha透明分量,这用在半透明图像融合中;

   .......

   Stride 表示图像每行字节数,Scan0 位图像数据指针,这两个参数需要特别注意:

   当 Stride > 0 时,Scan0 指向图像内存区域的起始位置;当 Stride < 0 时,Scan0 指向图像最后一行所在的内存地址;

   之所以如此,我以为是坐标原点差异所引起(仅是猜测)。

   gdi+位图使用左上角点作为坐标原点,设备无关位图(DIB)使用左下角作为坐标原点;

   当从DIB构造gdi+位图时,Scan0 指向DIB图像最后一行,Stride 小于 0;

   当从文件构造gdi+位图时,gdi+并不知道该文件所存储的图像是有什么数据形成,所以默认以左上角为坐标原点,Scan0 指向数据起始点,Stride 大于0;

   以下示例代码从文件中构造gdi+位图,拷贝gdi+数据,再使用拷贝数据从内存DIB中构造位图:

  

void GdiPlusBitmapTest()
{

    /*
        假定3*3图像如下:
        i_00 i_01 i_02
        i_10 i_11 i_12
        i_20 i_21 i_22
        当从文件中构造Bitmap时,bit_data.Scan0指向i_00
        当从内存中构造Bitmap时,bit_data.Scan0指向i_20
    */

    // 从文件中构造Bitmap, bit_data.Stride 为正数,bit_data.Scan0指向图像数据第一行
    Bitmap* bm_f = Bitmap::FromFile(L"1.bmp");

    Gdiplus::BitmapData bit_data;
    Gdiplus::Rect rc(0, 0, bm_f->GetWidth(), bm_f->GetHeight());
    bm_f->LockBits(&rc, ImageLockModeRead, bm_f->GetPixelFormat(), &bit_data);
    BYTE* data = (BYTE*)malloc(bit_data.Height * bit_data.Stride);
    memcpy(data, bit_data.Scan0, bit_data.Height * bit_data.Stride); // 拷贝图像,用于内存构造GDI+位图
    bm_f->UnlockBits(&bit_data);

    BYTE buffer[1024];
    memset(buffer, 0, 1024);
    LPBITMAPINFOHEADER  lpBmpInfoHead = (LPBITMAPINFOHEADER)buffer;
    lpBmpInfoHead->biSize = sizeof(BITMAPINFOHEADER);
    lpBmpInfoHead->biBitCount = bit_data.Stride / bit_data.Width * 8;
    lpBmpInfoHead->biWidth = bit_data.Width;
    lpBmpInfoHead->biHeight = bit_data.Height;
    lpBmpInfoHead->biPlanes = 1;
    lpBmpInfoHead->biCompression = BI_RGB;


    // 从内存构造Bitmap,bit_data.Stride 为负数,bit_data.Scan0指向图像数据最后一行
    Bitmap* bm_mem = Bitmap::FromBITMAPINFO((LPBITMAPINFO)lpBmpInfoHead, data);
    
    Gdiplus::Rect rc_u(0, 0, bm_mem->GetWidth(), bm_mem->GetHeight());
    bm_mem->LockBits(&rc_u, ImageLockModeRead, bm_mem->GetPixelFormat(), &bit_data);
    // !!!拷贝数据越界!!!
    //memcpy(data, bit_data.Scan0, bit_data.Height * (-bit_data.Stride)); 
    // 需要将 Scan0 移动到图像第一行位置再拷贝
    memcpy(data, (BYTE*)bit_data.Scan0 + bit_data.Stride * (bit_data.Height - 1), bit_data.Height * (-bit_data.Stride));
    bm_mem->UnlockBits(&bit_data);

    if (!data)
        free(data);

        if(bm_f)
                delete bm_f;

}

     

    参考资料 GDI+ - Win32 apps | Microsoft Docs

posted @ 2022-02-17 17:09  罗飞居  阅读(2768)  评论(0编辑  收藏  举报