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. Java CAS 原理详解

    1. 背景 在JDK 5之前Java语言是靠 synchronized 关键字保证同步的,这会导致有锁.锁机制存在以下问题: 在多线程竞争下,加锁.释放锁会导致比较多的上下文切换和调度延时,引起性能问 ...

  2. ProE许可、PTC许可、Creo许可、许可分析、分析许可

    Pro/Engineer操作软件(又简称ProE)是美国参数技术公司(PTC)旗下的CAD/CAM/CAE一体化的三维软件,Creo是美国PTC公司于2010年10月推出CAD设计软件包,creo是P ...

  3. Android学习笔记3

    (5)练习做一个实现两个数相乘的APP ①.java代码: //MainActivity.java package com.example.hello; import android.content. ...

  4. 【get√】golang中实现从腾讯云CVM查询网卡流量的两种方法

    公众号文章链接 主要参考了以下位置的资料: 云服务器监控接口 腾讯云go-sdk example 方法一:使用腾讯云go-sdk go.mod文件中增加这样一行: github.com/tencent ...

  5. DEEP LEARNING WITH PYTORCH: A 60 MINUTE BLITZ | TORCH.AUTOGRAD

    torch.autograd 是PyTorch的自动微分引擎,用以推动神经网络训练.在本节,你将会对autograd如何帮助神经网络训练的概念有所理解. 背景 神经网络(NNs)是在输入数据上执行的嵌 ...

  6. Ajax_Json用法

    Ajax_Json用法 关于json的服务端代码 //首先在方法里面设置一个响应json数据对象   const data = {       name:'chenxigua'   }​ //因为 s ...

  7. postgresql安装(windows)

    官网: https://www.postgresql.org/ 下载页面:https://www.enterprisedb.com/downloads/postgres-postgresql-down ...

  8. 使用Termux并与ubuntu建立ssh连接

    什么是Termux? Termux是一个Android终端仿真器和Linux环境应用程序,直接工作,无需根目录或设置.一个最小的基本系统被自动安装-额外的软件包可以使用APT软件包管理器来使用.不需要 ...

  9. keepalived的抢占与非抢占模式

    目录 一:keepalived的抢占与非抢占模式 1.抢占模式 2.非抢占模式 二:接下来分4种情况说明 三:以上3种,只要级别高就会获取master,与state状态是无关的 一:keepalive ...

  10. K8S SVC 转发原理

    在前面的文章中,我们已经多次使用到了 Service 这个 Kubernetes 里重要的服务对象.而 Kubernetes 之所以需要 Service,一方面是因为 Pod 的 IP 不是固定的,另 ...