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函数向屏幕输出的 ...
随机推荐
- java 字符串 大小写转换 、去掉首末端空格 、根据索引切割字符 、判断是否含有某连续字符串
1. 字符串转大写: toUpperCase() 字符串转小写: toLowerCase() @Test public void tt(){ String d = "sdgGHJGjghGH ...
- .net core 和 WPF 开发升讯威在线客服系统:调用百度翻译接口实现实时自动翻译
业余时间用 .net core 写了一个在线客服系统.并在博客园写了一个系列的文章,写介绍这个开发过程. 我把这款业余时间写的小系统丢在网上,陆续有人找我要私有化版本,我都给了,毕竟软件业的初衷就是免 ...
- 《剑指offer》面试题48. 最长不含重复字符的子字符串
问题描述 请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度. 示例 1: 输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串 ...
- Microsoft Store 桌面应用发布流程(一)之打包应用
这篇博客主要是介绍桌面应用打包的流程,应用发布流程请看 Microsoft Store 桌面应用发布流程(二)之提交应用 1. 创建打包项目 打开现有的桌面应用项目.选择解决方案项目,右键选择 添加新 ...
- Cesium源码剖析---Post Processing之物体描边(Silhouette)
Cesium在1.46版本中新增了对整个场景的后期处理(Post Processing)功能,包括模型描边.黑白图.明亮度调整.夜视效果.环境光遮蔽等.对于这么炫酷的功能,我们绝不犹豫,先去翻一翻它的 ...
- springMvc 启动过程
转载自https://www.jianshu.com/p/dc64d02e49ac 这里给出一个简洁的文字描述版SpringMVC启动过程: tomcat web容器启动时会去读取web.xml这样的 ...
- JUC之文章整理以及汇总
JUC文章汇总 JUC部分将学习<JUC并发编程的艺术>和<尚硅谷-大厂必备技术之JUC并发编程>进行博客的整理,各文章中也会不断的完善和丰富. JUC概述 JUC的视频学习和 ...
- 使用VS Code的MySQL扩展管理数据库
我将在本文告诉你如何用VS Code的扩展程序管理MySQL数据库,包括连接到MySQL.新建数据库和表.修改字段定义.简单的查询方法以及导入导出. 在许多情况下,我们需要随时查看数据库的记录来确保程 ...
- Servlet-base标签的作用(相对路径和绝对路径)
Web中的相对路径和绝对路径 在javaWeb中,路径分为相对路径和绝对路径: 相对路径: . 表示当前目录 .. 表示上一级目录 资源名 表示当前目录/资源名 绝对路径: http://ip ...
- String类-intern方法
1 package cn.itcast.p1.string.demo; 2 3 class StringObjectDemo { 4 public static void main(String[] ...