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

windows gdi+ Bitmap 总结的更多相关文章

  1. GDI+ Bitmap与WPF BitmapImage的相互转换

    原文:GDI+ Bitmap与WPF BitmapImage的相互转换 using System.Windows.Interop; //... // Convert BitmapImage to Bi ...

  2. 用 windows GDI 实现软光栅化渲染器--gdi3d(开源)

    尝试用windows GDI实现了一个简单的软光栅化渲染器,把OpenGL渲染管线实现了一遍,还是挺有收获的,搞清了以前一些似是而非的疑惑. ----更新2015-10-16代码已上传.gihub地址 ...

  3. C# windows GDI+仿画图 绘图程序设计

    C# windows GDI+仿画图 绘图程序设计 1.介绍 这里分享一个简单的画图程序 原作者:author: ping3108@163.com 2.程序主窗体设计 3.程序设计 本程序工程使用VS ...

  4. 图像处理---《在图片上打印文字 windows+GDI+TrueType字体》

    图像处理---<在图片上打印文字  windows+GDI+TrueType字体> 刚开始使用的是putText()函数做,缺陷是只能显示非中文: 接着,看大多数推荐Freetype库来做 ...

  5. Delphi利用Windows GDI实现文字倾斜

    Delphi利用Windows GDI实现文字倾斜 摘要 Delphi利用Windows GDI实现文字倾斜 procedure TForm1.FormPaint(Sender: TObject);v ...

  6. WPF GDI+ bitmap.save 一般性错误

    做水印图片的时候,发现WPF的System.Windows.Shapes类有绘制直线,椭圆等形状.却没有绘字符串的类. 无奈之下又用回GDI+ 发生的GDI+一般性错误初步估计的线程的原因. 在loa ...

  7. Windows GDI绘图基础知识

    一.Windows可以画直线.椭圆线(椭圆圆周上的曲线)和贝塞尔曲线.////////////7 个画线函式是:(1)画直线LineTo    BOOL LineTo(HDC hdc,int nXEn ...

  8. Windows GDI 窗口与 Direct3D 屏幕截图

    前言 Windows 上,屏幕截图一般是调用 win32 api 完成的,如果 C# 想实现截图功能,就需要封装相关 api.在 Windows 上,主要图形接口有 GDI 和 DirectX.GDI ...

  9. 【Visual C++】Windows GDI贴图闪烁解决方法

    一般的windows 复杂的界面需要使用多层窗口而且要用贴图来美化,所以不可避免在窗口移动或者改变大小的时候出现闪烁. 先来谈谈闪烁产生的原因 原因一:如果熟悉显卡原理的话,调用GDI函数向屏幕输出的 ...

随机推荐

  1. ':app@debug/compileClasspath': Could not find any version that matches com.android.support:appcompat-v7:30.+.

    ERROR: Unable to resolve dependency for ':app@debug/compileClasspath': Could not find any version th ...

  2. spring security 继承 WebSecurityConfigurerAdapter 的重写方法configure() 参数 HttpSecurity 常用方法及说明

    HttpSecurity 常用方法及说明 方法 说明 openidLogin() 用于基于 OpenId 的验证 headers() 将安全标头添加到响应 cors() 配置跨域资源共享( CORS ...

  3. Centos7 查看文件命令总结

    ls命令 ls -d --文件或者目录是否存在 ls -l 或者ll --显示详细信息 ls -lt --文件按时间顺序排序(升序) ls -ltr --按时间倒叙排序 ls -i --显示索引节点 ...

  4. Termux搭建hexo博客并部署到GitHub

    Termux搭建hexo博客并部署到GitHub 安装 termux-change-repo apt update apt install git && nodejs &&am ...

  5. leetcode 24. 两两交换链表中的节点 及 25. K 个一组翻转链表

    24. 两两交换链表中的节点 问题描述 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表. 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换. 示例: 给定 1->2-> ...

  6. csapp lab2 拆炸弹

    1. 实验内容 包含一个二进制应用bomb,需要根据该应用猜测程序的运行过程.程序主体包含了六个函数phase_1到phase_6,每个函数会根据用户的输入做出反应,当输入符合要求时,会炸弹拆解成功, ...

  7. 【小测试】使用腾讯云上的群集版redis

    具体的文档请见:https://cloud.tencent.com/document/product/239/3205 群集版本相当于很多个redis进程构成一个群集,最大支持128个分片(猜测分片就 ...

  8. 【记录一个问题】cuda核函数可能存在栈溢出,导致main()函数退出后程序卡死30秒CUDA

    调试一个CUDA核函数过程中发现一个奇怪的问题:调用某个核函数,程序耗时33秒,并且主要时间是main()函数结束后的33秒:而注释掉此核函数,程序执行不到1秒. 由此可见,可能是某种栈溢出,导致了程 ...

  9. 如何获取Repeater行号(索引)、记录总数?

    Repeater控件想必搞ASP.NET开发的人,基本上都到了用的炉火纯青的地步了.今个又吃了懒的亏,翻了好几个项目的代码都没找到如何获取Repeater记录总数的代码来,又Google了半天难得从老 ...

  10. java原码、反码、补码、位运算

    1.对于有符号的数(java中的数都是有符号的) 二进制的最高位是符号位:0表示正数,1表示负数 正数的原码,反码,补码都一样 负数的反码=它的原码符号位不变,其它位取反 负数的补码=它的反码+1 0 ...