学习Qt使用GDI+绘图笔记

借鉴文章:博主「利白」的原创文章
借鉴文章:博主「litanyuan」的原创文章
链接:GDI+ MSDN文档

1.GDI+介绍:

GDI+ 是 Windows 提供的图形设备接口 GDI 的后续版本,相比 GDI ,GDI+ 是面向对象的,使用更方便。

GDI:微软原生的二维绘图引擎
优点: 微软的全力支持,作为操作系统核心层效率方面不用担心,支持多种开发框架(含语言):WinSDK、MFC、Delphi等。
缺点: 不是面向C++对象组织的,使用起来较为繁琐;不支持反锯齿,不支持复杂的绘图效果(这个相对于GDI+而言)。
GDI+:微软后来推出的二维绘图引擎

优点: 微软的全力支持,支持多种开发框架(含语言):WinSDK、MFC、Delphi等,可以实现复杂的绘图效果,如反锯齿、路径画刷等;面向对象的架构,使用起来比较方便。
缺点: 绘图效率较GDI稍低,绘图交互性不如GDI(缺少GDI的支持位运算的绘图模式),开启反锯齿后效率不如Qt。如果不考虑绘图的效果,使用Win32 GDI函数直接绘图的效率大约是同样的GDI+的10倍以上。

关于绘图引擎的更多介绍请看:博主「利白」的原创文章
https://blog.csdn.net/libaineu2004/article/details/105475604


2.GDI+的载入与卸载:

使用 GDI+ 进行绘图,需要引入相关的库文件以及头文件,并且进行 GDI+ 环境的初始化操作。

(1)引入相关头文件(.h)及lib

//#include <stdafx.h>	 //这个头文件可以不用加
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment(lib, "Gdiplus.lib")

(2)GDI+的启动和关闭
在使用 GDI+ 绘图之前需要调用 GdiplusStartup 加载动态链接库 Gdiplus.dll 以对 GDI+进行初始化;在使用绘图结束后需要调用 GdiplusShutdown 卸载动态链接库以对 GDI+ 进行清除工作。

//启动
Gdiplus::GdiplusStartupInput StartupInput;
Gdiplus::GdiplusStartup(&m_gdiplusToken, &StartupInput, NULL);//GDI+初始化
//关闭
Gdiplus::GdiplusShutdown(m_gdiplusToken);//GDI+ 清除

(3)使用 RAII 管理 GDI+ 资源
定义一个类 GDIPlusHelper,把GDI+ 的启动和关闭操作分别写入类的构造和析构函数中,用来对 GDI+ 进行初始化和清理工作;然后在需要使用 GDI+ 绘图的 widget 中定义一个 GDIPlusHelper 成员即可。
GDIPlusHelper 类定义如下:

#pragma once
#include <QObject>
#include <windows.h>
#include <gdiplus.h>#pragma comment(lib, "Gdiplus.lib")
using namespace Gdiplus;
class GDIPlusHelper : public QObject
{
  Q_OBJECT
  
​public:
  GDIPlusHelper(QObject *parent = nullptr)
  {
    Gdiplus::GdiplusStartupInput StartupInput;
    Gdiplus::GdiplusStartup(&m_gdiplusToken, &StartupInput, NULL);//GDI+初始化
  }
  ~GDIPlusHelper()
  {
    Gdiplus::GdiplusShutdown(m_gdiplusToken);//GDI+ 清除
  }
private:
	ULONG_PTRm_gdiplusToken;	//GDI+句柄
  //unsigned long long  m_gdiplusToken;
};

一些注意事项:
1.在使用GDI+中相关函数和结构时,尽量加上Gdiplus命名空间名,以防止与其它模块的代码由于字段的名称同样出现冲突。比方,GDI+库中定义GDI+函数运行结果的每句类型Status,定义例如以下所看到的。假设我们须要推断函数是否正确运行,应该将返回值和Gdiplus::Ok,而不是直接和Ok比较,注意这个加上Gdiplus命名空间名的好习惯。
2.必须在窗体中创建一个此类型的对象或new一个此类型的对象,保证该类的构造函数和析构函数调用,才可使GDI+成功加载。
例如:GDIPlusHelper gidHelper;或者 GIDPlusHelper *gdiHelper = new GDIPlusHelper();
delete gdiHelper;
后续代码中标明!


3.Qt中GDI+的使用

QPainter在高频绘画的使用CPU占用较高,我们可以使用GDI+绘图。由于GDI+是使用GPU绘图,会减少CPU占用,绘图效率非常不错。

因为Qt是通过repaint和update事件触发paintEvent绘图,其他绘图会被覆盖,所以需要以下方法实现GDI+绘图:

(1).pro文件设置

pro或pri文件中增加: LIBS += -lgdi32 -luser32

(2)widget属性设置

在需要绘图的Widget构造函数写

setAttribute(Qt::WA_PaintOnScreen);
(3)重载painEngine()方法

重写该Widget的QPaintEngine * paintEngine()函数返回nullptr;

一些注意事项:
在绘图函数用GDI+绘图,不要在paintEvent实现,因为刷新会慢,在自己主动调用的函数里写

(4)获取HDC

Qt使用GDI绘图关键在于获取HDC,对于Qt5来说,有3种方法:

1、使用gui-private
pro或pri文件中增加: QT += gui-private

#include <qpa/qplatformnativeinterface.h>
 
QPlatformNativeInterface *fooPlatformNativeInterface=  QGuiApplication::platformNativeInterface();
QBackingStore *fooBackingStore = this->topLevelWidget()->backingStore();
HDC fooNRFWGetDC = static_cast<HDC>(fooPlatformNativeInterface->nativeResourceForBackingStore(QByteArrayLiteral("getDC"), fooBackingStore));

这个方法不需要releaseDC。此方法使用了Qt官方不推荐使用的 gui-private,并且在整个窗口绘图,没有限制。

2、强行使用GetDC
pro或pri文件中增加: LIBS += -lgdi32 -luser32

#include <windows.h>
HWND hwnd = (HWND)this->window()->winId();
HDC hdc = GetDC(hwnd);
ReleaseDC(hwnd, dhc);

方法2只适用于顶层窗体。此方法在整个窗口绘图,没有限制。

3、使用QtWin
pro或pri文件中增加 QT += winextras

#include <QtWin>
HDC hdcScreen = GetDC(NULL);
HDC hdc = CreateCompatibleDC(hdcScreen);
HBITMAP hbm = CreateCompatibleBitmap(hdcScreen, rectForMap.width(), rectForMap.height());
SelectObject(hdc, hbm);
QImage img = QtWin::imageFromHBITMAP(hdc, hbm, rectForMap.width(), rectForMap.height());
//释放GDI资源
DeleteObject(hbm);
DeleteDC(hdc);
ReleaseDC(nullptr, hdcScreen);

该方法是Qt5.2后的一种新的使用GDI绘图的方法,在Qt5.2中,新增了命名空间QtWin,推荐使用!


4.静态函数FromFile、FromHBitmap和FromStream的使用

关于这三个静态函数的使用:
CSDN博主「yaowang107」的原创文章


5.在 Qt 中使用 GDI+ 的实例

当做完以上的准备工作之后,就可以开始使用gdi+ 绘图了,在这里使用了第二种GetDC()方式获取HDC(强制获取)。
使用双缓存模式绘图,m_DBuffer 为 Graphics* 类型,作用是将需要的图片现在缓存中绘制。

void QFrameForm::paintEvent(QPaintEvent *event)
{
	/*
	** 双缓冲绘制模式:先在Bitmap(缓存)上绘制需要绘制的图画,
	** 然后将绘制好的Bitmap图像绘制到屏幕上
	*/
	Bitmap bmp(m_nWidth,m_nHeight);		//创建画布(位图)
	m_DBuffer = Graphics::FromImage(&bmp);	//为m_DBuffer指定画布,m_DBuffer为Graphics* 类型
	
	//绘制图像
	DrawBmp();
	
	/*
	** 在实际dc设备上绘制,两种方式:
	*/
	//第一种:直接绘制到屏幕上,绘制速度较慢
	//Graphics graphics(m_hdc);	//指定绘图设备为当前屏幕
	//graphics.DrawImage(&map,m_nWidth,m_nHeight);	//将绘制好的bitmap图像画到屏幕上
	
	//第二种:使用CachedBitmap类,更加快速的绘制
	Graphics graphics(m_hdc);
	CachedBitmap ccbp(&bmp,&graphics);	//绑定位图和绘图设备
	//graphics.Clear(Color::Black);   //刷新背景用(colorse色),防止出现重影
	graphics.DrawCachedBitmap(&ccbp,0,0);
}

void QFrameForm::DrawBmp()
{
	/* 绘制最好在函数中实现
	*/
	Pen pen(m_color,1.0);	
	m_DBuffer->DrawLine(&pen,10,10,50,50);	//画线
	m_DBuffer->DrawEllipse(&pen,csPoint.X,csPoint.Y,100,100);	//画椭圆 csPoint为获取到的鼠标当前位置
}

6.遇到的一些问题:关于Bitmap的复制拷贝,以及多图交互的问题。

在项目中遇到了一个多图交互的问题,

当存在多图交互时,可能会涉及到Bitmap数据的拷贝与保存Bitmap的拷贝与保存使用到了LockBits() 函数和 UnLockBits() 函数有以及BitmapData类,
我在项目中还遇到了Bitmap与QPixmap的相互转换问题。
待续。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值