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
windows gdi+ Bitmap 总结的更多相关文章
- GDI+ Bitmap与WPF BitmapImage的相互转换
原文:GDI+ Bitmap与WPF BitmapImage的相互转换 using System.Windows.Interop; //... // Convert BitmapImage to Bi ...
- 用 windows GDI 实现软光栅化渲染器--gdi3d(开源)
尝试用windows GDI实现了一个简单的软光栅化渲染器,把OpenGL渲染管线实现了一遍,还是挺有收获的,搞清了以前一些似是而非的疑惑. ----更新2015-10-16代码已上传.gihub地址 ...
- C# windows GDI+仿画图 绘图程序设计
C# windows GDI+仿画图 绘图程序设计 1.介绍 这里分享一个简单的画图程序 原作者:author: ping3108@163.com 2.程序主窗体设计 3.程序设计 本程序工程使用VS ...
- 图像处理---《在图片上打印文字 windows+GDI+TrueType字体》
图像处理---<在图片上打印文字 windows+GDI+TrueType字体> 刚开始使用的是putText()函数做,缺陷是只能显示非中文: 接着,看大多数推荐Freetype库来做 ...
- Delphi利用Windows GDI实现文字倾斜
Delphi利用Windows GDI实现文字倾斜 摘要 Delphi利用Windows GDI实现文字倾斜 procedure TForm1.FormPaint(Sender: TObject);v ...
- WPF GDI+ bitmap.save 一般性错误
做水印图片的时候,发现WPF的System.Windows.Shapes类有绘制直线,椭圆等形状.却没有绘字符串的类. 无奈之下又用回GDI+ 发生的GDI+一般性错误初步估计的线程的原因. 在loa ...
- Windows GDI绘图基础知识
一.Windows可以画直线.椭圆线(椭圆圆周上的曲线)和贝塞尔曲线.////////////7 个画线函式是:(1)画直线LineTo BOOL LineTo(HDC hdc,int nXEn ...
- Windows GDI 窗口与 Direct3D 屏幕截图
前言 Windows 上,屏幕截图一般是调用 win32 api 完成的,如果 C# 想实现截图功能,就需要封装相关 api.在 Windows 上,主要图形接口有 GDI 和 DirectX.GDI ...
- 【Visual C++】Windows GDI贴图闪烁解决方法
一般的windows 复杂的界面需要使用多层窗口而且要用贴图来美化,所以不可避免在窗口移动或者改变大小的时候出现闪烁. 先来谈谈闪烁产生的原因 原因一:如果熟悉显卡原理的话,调用GDI函数向屏幕输出的 ...
随机推荐
- spring boot 使用 mybatis 开启事务回滚 的总结
1.前言 以前没有使用mybatis,可以关闭自动提交,然后做sql操作,对操作进行catch捕获异常, 如果没有异常则commit 提交 ,有异常则 rollback 回滚,新增的数据则删除 ,修改 ...
- 解决MySQL服务器禁止远程连接的问题
1. 改表法. 可能是你的帐号不允许从远程登陆,只能在localhost.这个时候只要在localhost的那台电脑,登入mysql后,更改 "mysql" 数据库里的 " ...
- 微信小程序封装mixins方法
在app.js中这样引入 import '@src/utils/mixins' mixins函数如下 /** * 封装类似vue的混入功能 */ let native = Page Page = (o ...
- LG1290 欧几里德的游戏
https://www.luogu.com.cn/problem/P1290 博弈论游戏,用到mod. 辗转相除法的过程,会构成n种状态. 到达最后一个状态就赢了. 对于一次过程如果div>1那 ...
- C++ 基本类型的大小
C++的基本类型: char bool (unsigned) short (int) (unsigned) int (unsigned) long (int) (unsigned) long long ...
- 一个BPMN流程示例带你认识项目中流程的生命周期
摘要:本文详细说明了在工作流Activiti框架中的BPMN流程定义整个运行的生命周期. 本文分享自华为云社区<本文详细说明了在工作流Activiti框架中的BPMN流程定义整个运行的生命周期& ...
- gin的源码解读4-gin的路由算法
gin的路由算法 gin的是路由算法其实就是一个Trie树(也就是前缀树). 有关数据结构的可以自己去网上找相关资料查看. 注册路由预处理 我们在使用gin时通过下面的代码注册路由 普通注册 rout ...
- java内部类概述和修饰符
1 package face_09; 2 /* 3 * 内部类访问特点: 4 * 1,内部类可以直接访问外部类的成员. 5 * 2,外部类要访问内部类,必须建立内部类的对象. 6 * 7 * 一把用于 ...
- Ubuntu 14.04更换内核
1:查看当前安装的内核 dpkg -l|grep linux-image 2:查看可以更新的内核版本: sudo apt-cache search linux-image 3:安装新内核 sudo a ...
- Power Apps 创建响应式布局
前言 我们都知道Power Apps作为低代码平台,最大的优势就是各个设备之间的兼容性,尤其是自带的响应式布局,非常好用. 这不,我们就为大家分享一下,如何使用Power Apps画布应用,创建响应式 ...