百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

MFC转QT:Qt高级特性 - QPainter绘图

cac55 2025-05-11 14:24 8 浏览 0 评论



QPainter概述

QPainter是Qt提供的2D图形绘制API,它为开发者提供了一个统一的跨平台绘图接口,相比MFC的CDC绘图系统更加强大和易用。对于从MFC迁移的开发者,QPainter将是一个显著的升级。

与MFC绘图系统对比

特性

Qt (QPainter)

MFC (CDC)

基本概念

QPainter对象

CDC设备上下文

坐标系统

默认逻辑坐标,支持变换

设备坐标,通过映射模式转换

绘图表面

QWidget, QPixmap, QImage等

HWND, HDC, HBITMAP等

抗锯齿支持

原生支持

有限或无支持(GDI+除外)

透明度支持

完全支持alpha通道

有限或无支持(GDI+除外)

平台依赖性

跨平台统一API

仅限Windows平台

渲染引擎

可选后端 (软件, OpenGL, Direct3D等)

GDI/GDI+

图形保存

多种格式支持

需额外代码处理

打印集成

无缝集成QPrinter

需区分打印DC和显示DC

基础绘图操作

在窗口部件上绘图

在Qt中,通过重写QWidget的paintEvent()方法使用QPainter进行绘图:

 class MyWidget : public QWidget
 {
 protected:
     void paintEvent(QPaintEvent *event) override
     {
         QPainter painter(this);
         
         // 启用抗锯齿
         painter.setRenderHint(QPainter::Antialiasing);
         
         // 设置画笔(轮廓)
         painter.setPen(QPen(Qt::blue, 2, Qt::SolidLine, Qt::RoundCap));
         
         // 设置画刷(填充)
         painter.setBrush(QBrush(Qt::yellow, Qt::SolidPattern));
         
         // 绘制矩形
         painter.drawRect(20, 20, 100, 60);
         
         // 绘制椭圆
         painter.setPen(QPen(Qt::red, 3));
         painter.setBrush(Qt::green);
         painter.drawEllipse(150, 20, 100, 60);
         
         // 绘制文本
         painter.setPen(Qt::black);
         painter.setFont(QFont("Arial", 12, QFont::Bold));
         painter.drawText(rect(), Qt::AlignCenter, "Hello QPainter!");
     }
 };

绘图表面比较

Qt支持多种绘图表面,每种都有不同的用途:

  1. QWidget - 绘制到屏幕上的窗口部件
  2. QPixmap - 针对屏幕优化的图像
  3. QImage - 用于访问单个像素和图像处理
  4. QPicture - 记录和重放QPainter命令
  5. QPrinter - 打印输出
  6. QOpenGLPaintDevice - OpenGL加速绘图
 // 在QPixmap上绘制
 QPixmap pixmap(400, 200);
 pixmap.fill(Qt::white);
 
 QPainter painter(&pixmap);
 painter.setRenderHint(QPainter::Antialiasing);
 painter.setPen(QPen(Qt::blue, 2));
 painter.drawRect(20, 20, 100, 60);
 painter.end();
 
 // 保存图像
 pixmap.save("output.png");
 
 // 在QImage上绘制
 QImage image(400, 200, QImage::Format_ARGB32);
 image.fill(Qt::transparent);
 
 QPainter imagePainter(&image);
 imagePainter.setRenderHint(QPainter::Antialiasing);
 imagePainter.setPen(QPen(Qt::blue, 2));
 imagePainter.drawRect(20, 20, 100, 60);
 imagePainter.end();
 
 // 访问单个像素
 image.setPixel(10, 10, qRgb(255, 0, 0));
 
 // 在QPicture上记录
 QPicture picture;
 QPainter picturePainter(&picture);
 picturePainter.setRenderHint(QPainter::Antialiasing);
 picturePainter.setPen(QPen(Qt::blue, 2));
 picturePainter.drawRect(20, 20, 100, 60);
 picturePainter.end();
 
 // 保存到文件
 picture.save("drawing.pic");
 
 // 在其他地方重放
 QPicture loadedPicture;
 loadedPicture.load("drawing.pic");
 QPainter replayPainter(this);
 loadedPicture.play(&replayPainter);

绘图元素与工具

QPen - 控制线条样式

QPen用于定义如何绘制线条和形状轮廓:

 // 创建各种样式的画笔
 QPen pen1(Qt::red, 2, Qt::SolidLine, Qt::RoundCap, Qt::MiterJoin);
 QPen pen2(QColor(30, 100, 160), 3, Qt::DashLine, Qt::SquareCap, Qt::BevelJoin);
 QPen pen3(QColor(0, 150, 0, 100), 4); // 半透明绿色
 
 // 设置更多属性
 pen3.setDashPattern({5, 2, 1, 2}); // 自定义虚线模式
 pen3.setCosmetic(true); // 与设备无关的宽度
 
 // 使用画笔
 painter.setPen(pen1);
 painter.drawLine(10, 10, 100, 10);
 painter.setPen(pen2);
 painter.drawLine(10, 30, 100, 30);
 painter.setPen(pen3);
 painter.drawLine(10, 50, 100, 50);

QBrush - 控制填充样式

QBrush用于定义如何填充形状内部:

 // 创建各种样式的画刷
 QBrush brush1(Qt::red, Qt::SolidPattern);
 QBrush brush2(Qt::blue, Qt::DiagCrossPattern);
 
 // 纹理画刷
 QPixmap texture(":/textures/wood.png");
 QBrush brush3(texture);
 
 // 渐变画刷
 QLinearGradient gradient(0, 0, 100, 100);
 gradient.setColorAt(0, Qt::white);
 gradient.setColorAt(0.5, Qt::green);
 gradient.setColorAt(1, Qt::black);
 QBrush brush4(gradient);
 
 // 使用画刷
 painter.setBrush(brush1);
 painter.drawRect(10, 10, 80, 60);
 painter.setBrush(brush2);
 painter.drawRect(100, 10, 80, 60);
 painter.setBrush(brush3);
 painter.drawRect(10, 80, 80, 60);
 painter.setBrush(brush4);
 painter.drawRect(100, 80, 80, 60);

渐变效果

Qt支持多种渐变类型,这是MFC中难以实现的功能:

 // 线性渐变
 QLinearGradient linearGradient(0, 0, 300, 0);
 linearGradient.setColorAt(0, Qt::white);
 linearGradient.setColorAt(1, Qt::blue);
 linearGradient.setSpread(QGradient::ReflectSpread); // 反射扩展模式
 painter.fillRect(10, 10, 300, 50, linearGradient);
 
 // 径向渐变
 QRadialGradient radialGradient(150, 100, 100);
 radialGradient.setColorAt(0, Qt::yellow);
 radialGradient.setColorAt(0.5, Qt::red);
 radialGradient.setColorAt(1, Qt::black);
 painter.fillRect(10, 70, 300, 100, radialGradient);
 
 // 锥形渐变
 QConicalGradient conicalGradient(150, 220, 0);
 conicalGradient.setColorAt(0, Qt::red);
 conicalGradient.setColorAt(0.25, Qt::yellow);
 conicalGradient.setColorAt(0.5, Qt::green);
 conicalGradient.setColorAt(0.75, Qt::blue);
 conicalGradient.setColorAt(1, Qt::red);
 painter.fillRect(10, 180, 300, 100, conicalGradient);

路径绘制 (QPainterPath)

QPainterPath用于创建复杂的形状:

 QPainterPath path;
 path.moveTo(100, 30);
 path.cubicTo(150, 30, 200, 100, 100, 100);
 path.cubicTo(0, 100, 50, 30, 100, 30);
 
 painter.setPen(QPen(Qt::blue, 2));
 painter.setBrush(Qt::green);
 painter.drawPath(path);
 
 // 创建文本路径
 QPainterPath textPath;
 textPath.addText(QPointF(10, 50), QFont("Times", 40), "Qt 路径");
 
 painter.setPen(QPen(Qt::black, 1));
 painter.setBrush(QBrush(Qt::red));
 painter.drawPath(textPath);
 
 // 组合路径
 QPainterPath combinedPath;
 combinedPath.addRect(10, 10, 100, 100);
 combinedPath.addEllipse(50, 50, 80, 80);
 combinedPath.setFillRule(Qt::OddEvenFill); // 设置填充规则
 
 painter.setPen(QPen(Qt::black, 1));
 painter.setBrush(QBrush(Qt::cyan));
 painter.drawPath(combinedPath);

坐标变换与裁剪

QPainter提供了强大的坐标变换功能,远超MFC的基本映射模式:

 // 保存当前状态
 painter.save();
 
 // 平移坐标系
 painter.translate(100, 50);
 
 // 旋转坐标系(角度)
 painter.rotate(45);
 
 // 缩放坐标系
 painter.scale(0.5, 0.5);
 
 // 在变换的坐标系中绘制
 painter.drawRect(0, 0, 100, 100);
 
 // 恢复之前的状态
 painter.restore();
 
 // 设置自定义变换矩阵
 QTransform transform;
 transform.translate(200, 200);
 transform.rotate(30);
 transform.scale(1.5, 0.75);
 transform.shear(0.1, 0.2);
 painter.setTransform(transform);
 
 painter.drawEllipse(0, 0, 100, 100);
 
 // 复合变换
 painter.resetTransform();
 painter.translate(300, 100);
 painter.rotate(90);
 painter.drawText(0, 0, "旋转的文本");

裁剪区域

设置裁剪区域来限制绘图范围:

 // 矩形裁剪
 painter.setClipRect(QRect(50, 50, 200, 200));
 
 // 路径裁剪
 QPainterPath clipPath;
 clipPath.addEllipse(100, 100, 150, 100);
 painter.setClipPath(clipPath);
 
 // 组合裁剪区域
 painter.setClipRect(QRect(50, 50, 300, 200), Qt::IntersectClip);
 
 // 禁用裁剪
 painter.setClipping(false);

图像处理与效果

QPainter可以绘制和处理图像:

// 加载图像
QImage image(":/images/photo.jpg");

// 绘制图像
painter.drawImage(10, 10, image);

// 缩放绘制
painter.drawImage(QRect(10, 120, 100, 75), image);

// 裁剪绘制
painter.drawImage(QPoint(120, 10), image, 
                  QRect(image.width()/4, image.height()/4, 
                        image.width()/2, image.height()/2));

// 应用滤镜效果
QImage processedImage = image.convertToFormat(QImage::Format_Grayscale8);
painter.drawImage(120, 120, processedImage);

合成模式

设置合成模式改变绘图元素如何与下层内容混合:

// 绘制背景
painter.fillRect(0, 0, 300, 200, Qt::white);
painter.fillRect(50, 50, 200, 100, Qt::blue);

// 默认合成模式:Source Over
painter.fillRect(100, 70, 150, 80, QColor(255, 0, 0, 128));

// 不同的合成模式
painter.setCompositionMode(QPainter::CompositionMode_Multiply);
painter.fillRect(200, 20, 80, 80, QColor(0, 255, 0, 128));

painter.setCompositionMode(QPainter::CompositionMode_Screen);
painter.fillRect(20, 100, 80, 80, QColor(0, 0, 255, 128));

painter.setCompositionMode(QPainter::CompositionMode_Difference);
painter.fillRect(150, 120, 80, 80, QColor(255, 255, 0, 255));

与MFC绘图的对比示例

为了帮助MFC开发者更好地理解转换过程,这里展示常见MFC绘图代码及其Qt等效实现:

基本形状绘制

// MFC代码
void CMyView::OnDraw(CDC* pDC)
{
    // 设置画笔和画刷
    CPen pen(PS_SOLID, 2, RGB(0, 0, 255));
    CBrush brush(RGB(255, 255, 0));
    CPen* pOldPen = pDC->SelectObject(&pen);
    CBrush* pOldBrush = pDC->SelectObject(&brush);
    
    // 绘制矩形
    pDC->Rectangle(20, 20, 120, 80);
    
    // 绘制椭圆
    pDC->Ellipse(150, 20, 250, 80);
    
    // 绘制线条
    pDC->MoveTo(20, 100);
    pDC->LineTo(250, 150);
    
    // 绘制文本
    pDC->SetTextColor(RGB(0, 0, 0));
    pDC->SetBkMode(TRANSPARENT);
    pDC->TextOut(100, 200, _T("Hello MFC!"), 10);
    
    // 恢复GDI对象
    pDC->SelectObject(pOldPen);
    pDC->SelectObject(pOldBrush);
}

// Qt等效代码
void MyWidget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    
    // 设置画笔和画刷
    painter.setPen(QPen(Qt::blue, 2));
    painter.setBrush(Qt::yellow);
    
    // 绘制矩形
    painter.drawRect(20, 20, 100, 60);
    
    // 绘制椭圆
    painter.drawEllipse(150, 20, 100, 60);
    
    // 绘制线条
    painter.drawLine(20, 100, 250, 150);
    
    // 绘制文本
    painter.setPen(Qt::black);
    painter.setBackgroundMode(Qt::TransparentMode);
    painter.drawText(100, 200, "Hello Qt!");
}

坐标变换

// MFC代码
void CMyView::OnDraw(CDC* pDC)
{
    // 保存当前状态
    int nSavedDC = pDC->SaveDC();
    
    // 设置映射模式
    pDC->SetMapMode(MM_ANISOTROPIC);
    
    // 设置窗口和视区范围
    pDC->SetWindowExt(100, 100);
    pDC->SetViewportExt(200, -200); // Y轴反向
    pDC->SetViewportOrg(100, 300);  // 原点偏移
    
    // 在变换后坐标系绘制
    pDC->Rectangle(0, 0, 50, 50);
    
    // 恢复状态
    pDC->RestoreDC(nSavedDC);
}

// Qt等效代码
void MyWidget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    
    // 保存当前状态
    painter.save();
    
    // 设置变换
    painter.translate(100, 300);
    painter.scale(2.0, -2.0); // Y轴反向
    
    // 在变换后坐标系绘制
    painter.drawRect(0, 0, 50, 50);
    
    // 恢复状态
    painter.restore();
}

双缓冲绘图

// MFC双缓冲绘图
void CMyView::OnDraw(CDC* pDC)
{
    CRect rect;
    GetClientRect(&rect);
    
    // 创建内存DC和位图
    CDC memDC;
    memDC.CreateCompatibleDC(pDC);
    CBitmap memBitmap;
    memBitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());
    CBitmap* pOldBitmap = memDC.SelectObject(&memBitmap);
    
    // 在内存DC上绘制
    memDC.FillSolidRect(rect, RGB(255, 255, 255)); // 清背景
    
    // 实际绘图代码...
    memDC.Rectangle(20, 20, 120, 80);
    
    // 复制到屏幕
    pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY);
    
    // 清理
    memDC.SelectObject(pOldBitmap);
}

// Qt中无需手动双缓冲
void MyWidget::paintEvent(QPaintEvent *)
{
    // Qt自动处理双缓冲,无需额外代码
    QPainter painter(this);
    
    // 直接绘制
    painter.fillRect(rect(), Qt::white); // 清背景
    painter.drawRect(20, 20, 100, 60);
}

高级绘图技术

抗锯齿设置

void MyWidget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    
    // 不使用抗锯齿
    painter.setRenderHint(QPainter::Antialiasing, false);
    painter.drawEllipse(10, 10, 100, 100);
    
    // 使用抗锯齿
    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.drawEllipse(120, 10, 100, 100);
    
    // 文本抗锯齿
    painter.setRenderHint(QPainter::TextAntialiasing, true);
    painter.setFont(QFont("Arial", 24));
    painter.drawText(10, 150, "抗锯齿文本");
    
    // 多种渲染提示
    painter.setRenderHints(
        QPainter::Antialiasing | 
        QPainter::SmoothPixmapTransform | 
        QPainter::TextAntialiasing,
        true);
    painter.drawEllipse(230, 10, 100, 100);
}

性能优化技巧

void MyWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    
    // 1. 仅重绘更新区域
    QRegion region = event->region();
    foreach (const QRect &rect, region.rects()) {
        // 仅绘制需要更新的矩形区域
        painter.setClipRect(rect);
        drawContents(&painter, rect);
    }
    
    // 2. 避免过度绘制
    if (!isVisible())
        return;
        
    // 3. 使用不透明提示优化
    painter.setCompositionMode(QPainter::CompositionMode_Source);
    painter.fillRect(rect(), Qt::white); // 背景总是不透明
    painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
    
    // 4. 关闭不需要的渲染提示
    painter.setRenderHint(QPainter::Antialiasing, 
                         needAntialiasing()); // 仅在需要时开启
    
    // 5. 预先计算缩放、旋转等操作
    QTransform transform;
    transform.rotate(45);
    painter.setWorldTransform(transform);
    
    // 6. 使用缓存技术
    if (m_cachedDrawing.isNull()) {
        m_cachedDrawing = QPixmap(200, 200);
        QPainter cachePainter(&m_cachedDrawing);
        drawComplexShape(&cachePainter);
    }
    
    // 绘制缓存的图像
    painter.drawPixmap(100, 100, m_cachedDrawing);
}

打印支持

QPainter还可以无缝支持打印:

void MyWidget::print()
{
    QPrinter printer(QPrinter::HighResolution);
    printer.setPageSize(QPageSize(QPageSize::A4));
    
    QPrintDialog printDialog(&printer, this);
    if (printDialog.exec() == QDialog::Accepted) {
        QPainter painter(&printer);
        
        // 缩放到打印页面
        QRect pageRect = printer.pageRect(QPrinter::DevicePixel).toRect();
        QRect contentRect = rect();
        
        qreal xscale = pageRect.width() / qreal(contentRect.width());
        qreal yscale = pageRect.height() / qreal(contentRect.height());
        qreal scale = qMin(xscale, yscale);
        
        painter.translate(pageRect.width() / 2, pageRect.height() / 2);
        painter.scale(scale, scale);
        painter.translate(-contentRect.width() / 2, -contentRect.height() / 2);
        
        // 绘制内容
        render(&painter);
    }
}

从MFC迁移的最佳实践

1. 绘图代码迁移策略

  • 使用paintEvent()替代OnDraw()和OnPaint()
  • 使用QPainter替代CDC
  • 使用Qt的坐标系统(默认左上角为原点)
  • 使用QPen和QBrush替代Windows GDI对象
  • 使用Qt的render hints替代GDI+质量设置

2. MFC开发者常见错误

  • 忘记在paintEvent以外的地方调用update()触发重绘
  • 尝试保存QPainter对象以供以后使用(它只在创建它的函数范围内有效)
  • 在同一时间在同一设备上使用多个QPainter
  • 不处理高DPI显示缩放

3. 性能优化建议

  • 减少paint事件中的逻辑处理,专注于绘图
  • 缓存复杂或重复使用的图形
  • 优先使用预渲染图像而非复杂矢量绘图
  • 仅在需要时启用抗锯齿
  • 对于复杂图形考虑使用OpenGL渲染

4. 从GDI/GDI+转换

  • 使用QPainter的RenderHints替代GDI+的SmoothingMode
  • 使用QPainterPath替代GraphicsPath
  • 使用Qt的渐变类替代GDI+渐变
  • 使用Qt的QPixmap/QImage替代Bitmap/Image
  • 利用Qt内置的图像格式支持替代GDI+的编解码器

相关推荐

让组策略保护Windows XP的安全

默认安装完WindowsXP之后,我们的WindowsXP并不很安全。因此,我们有必要对系统进行一些修修补补,一般情况下我们都要动用到注册表。诚然,修改注册表是一种非常有效的方法,但是它需要一定的...

你造吗?十种方式保护你免受"零日攻击"

|责编:王迪WindowsXP的寿终正寝,数据安全问题又再一次成为人们关注的焦点。近日,微软透漏,一个基于InternetExplorer的“零日攻击”给用户带来了严重破坏。“零日攻击”一种利用...

特立独行——打造游戏专用独立系统

大部分人的电脑是为了学习和工作用的,所以,如果你是一个游戏迷,那么推荐你安装一个独立系统专用于游戏,做到工作娱乐两不相扰。方案1:游戏专用移动WindowsXP目的:解决游戏兼容性问题喜欢玩游戏的都...

驰为VX8 3G Win8入门教程篇

距离Win8.1的正式发布也将近1年了,凭借着Win8.1在移动便携以及娱乐办公上的优势,现在的Win8平板越来越受到消费者的追捧,而驰为VX83G就是其中一款,搭载了卓越的英特尔Z3735G四核芯,...

易淘收银软件说明

易淘收银系统,简称易淘收银,专为小型及连锁零售、餐饮行业打造。基于SaaS模式,智能便捷,无需维护,轻量级设计却功能强大,简约而不失专业,助力门店高效管理收银。1、前台系统:收银客户端;2、后台系...

CAD打不开怎么办?原因可能是电脑中毒了,6步就能完美解决问题

一、问题描述我的CAD安装后无法打开,安装过程中没有出现任何问题,但是安装后打开就出现一个对话框“DBXCAS0”点击后又出现“FATALERROR:UnhandledAccessViola...

腾讯QQ6.1正式版发布更新

2014-07-2405:12:00作者:张林【中关村在线软件资讯】7月24日消息:腾讯QQ官网小幅更新了QQ6.1正式版,最新版本号升级至11905,继续主打扁平化、炫酷登录窗口、支持同步最近一...

Win10等网页版OneDrive无法登陆怎么办?

IT之家(www.ithome.com):Win10等网页版OneDrive无法登陆怎么办?Win10之家报道,微软OneDrive云网盘是跨平台的数据同步和存储服务,支持WindowsPC(如Wi...

经典回顾:折戟沉沙的Windows Longhorn有着惊艳的登录屏幕

尽管微软原先计划让WindowsLonghorn继承WindowsXP操作系统的衣钵,但这个充满雄心壮志的操作系统项目最终还是未能迎来曙光,而是被微软用WindowsVista取而代...

电脑怎么优化

电脑配置和宽带流量也是硬件,但这些要求其实并不需要很高,关键还是怎么去安全使用电脑并进行有效的优化。电脑的应用和优化处理一、电脑的应用和优化处理二、目前,大家使用的个人电脑,配置方面均没多大问题,比如...

怎么安装usb驱动

USB驱动主要是针对WIN98时代的说法,如今WINXP已集成大部分USB驱动,通常都能识别。只有极少数情况下,例如手机、打印机或扫描仪等办公设备的USB驱动可能无法自动识别。1、USB驱动偶尔无法...

普通话考试多名考生信息被泄露,接投诉后涉事网站被限制访问

“陕西普通话成绩查询网(sxpth.cn)”泄露个人信息网站截图网传图片显示,407名普通话考试考生的姓名、身份证号码等个人信息疑似被泄露。9月26日下午,涉事网站sxpth.cn的域名注册商——成...

电脑伪技巧——个人电脑无需设置登录密码

默认情况下,我们每次登录系统都要输入登录账户对应的密码才能进入桌面。有些朋友觉得这样很麻烦,由于电脑只是自己使用,还不如不要设置密码,这样每次可以自动登录。大家知道,账户密码是系统验证用户合法性的唯一...

Windows 10/11 自带远程桌面:实用技巧与操作指南

Windows10/11自带远程桌面:实用技巧与操作指南在当今快节奏的数字时代,远程访问和控制计算机的需求日益增长。微软在Windows10和Windows11中内置了远程桌面功能,为用户提供...

不升级系统的5大原因造吗?

2015-01-2405:54:00作者:陈占伟Windows10系统的发布,让人们重新将目光聚焦到生命力长久的Windows系统之上。如今操作系统越来越多,似乎Windows升级的获得的关注度...

取消回复欢迎 发表评论: