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函数向屏幕输出的 ...
随机推荐
- 第10组 Alpha冲刺 (3/6)
1.1基本情况 ·队名:今晚不睡觉 ·组长博客:https://www.cnblogs.com/cpandbb/p/13971668.html ·作业博客:https://edu.cnblogs.co ...
- Autofac实现有条件的DI
Autofac.Annotation框架是我用.netcore写的一个DI框架,基于Autofac参考 Spring注解方式所有容器的注册和装配,切面,拦截器等都是依赖标签来完成. 开源地址:http ...
- Three.js 实现虎年春节3D创意页面
背景 虎年 春节将至,本文使用 React + Three.js 技术栈,实现趣味 3D 创意页面.本文包含的知识点主要包括:ShadowMaterial. MeshPhongMaterial 两种基 ...
- 安装DataX的管理控制台(转)
原文地址 https://github.com/WeiYe-Jing/datax-web/blob/master/doc/datax-web/datax-web-deploy.md 环境准备 1)基础 ...
- Web安全攻防(一)XSS注入和CSRF
跨站脚本攻击(XSS) XSS(Cross Site Scripting),为不和层叠样式表CSS混淆,故将跨站脚本攻击缩写为XSS. 攻击原理: 恶意攻击者往Web页面里插入恶意Script代码,当 ...
- Ajax的IE缓存问题
Ajax之IE缓存问题 <!-- IE浏览器会对ajax的结果进行一个缓存,这样就会导致一个缓存问题 浏览器会读取缓存 而不会去使用一个新的数据 这样对一个时效性比较强的场景 ajax的缓存会影 ...
- vue3源码node的问题
下载vue3源码后,下载依赖时,node的版本需要在10.0.0以上,并且不同的vue3里面的插件的配置对版本依赖还不同,14.0.0以上的版本基本都不支持win7了, win7系统可以安装12.0. ...
- vector自实现(一)
vector.h: #ifndef __Vector__H__ #define __Vector__H__ typedef int Rank; #define DEFAULT_CAPACITY 3 t ...
- git命令,github
1.git原理 2.git和svn的区别 SVN是集中式版本控制系统,版本库是集中放在中央服务器的,而干活的时候,用的都是自己的电脑,所以首先要从中央服务器哪里得到最新的版本,然后干活,干完后,需要把 ...
- 使用 Kubeadm+Containerd 部署一个 Kubernetes 集群
本文独立博客阅读地址:https://ryan4yin.space/posts/kubernetes-deployemnt-using-kubeadm/ 本文由个人笔记 ryan4yin/knowle ...