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

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

cac55 2025-05-11 14:24 21 浏览 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+的编解码器

相关推荐

Mac电脑强制删除任何软件方法-含自启动应用

对于打工者来说,进入企业上班使用的电脑大概率是会被监控起来,比如各种流行的数据防泄漏DLP,奇安信天擎,甚至360安全卫士,这些安全软件你想卸载是非常困难的,甚至卸载后它自己又安装回来了,并且还在你不...

Linux基础知识 | 文件与目录大全讲解

1.linux文件权限与目录配置1.文件属性Linux一般将文件可存取的身份分为三个类别,分别是owner/group/others,且三种身份各read/write/execute等权限文...

文件保护不妥协:2025 年 10 款顶级加密工具推荐

数据安全无小事,2025年这10款加密工具凭借独特功能脱颖而出,从个人到企业场景全覆盖,第一款为Ping32,其余为国外英文软件。1.Ping32企业级加密核心工具,支持200+文件格...

省心省力 一个软件搞定系统维护_省心安装在哪里能找到

◆系统类似于我们居住的房间,需要经常打理才能保持清洁、高效。虽然它本身也自带一些清理和优化的工具,但借助于好用的第三方工具来执行这方面的任务,会更让人省心省力。下面笔者就为大家介绍一款集多项功能于一身...

JAVA程序员常用的几个工具类_java程序员一般用什么软件写程序

好的工具做起事来常常事半功倍,下面介绍几个开发中常用到的工具类,收藏一下,也许后面真的会用到。字符串处理:org.apache.commons.lang.StringUtilsisBlank(Char...

手工解决Windows10的若干难题_windows10系统卡顿怎么解决

【电脑报在线】很多朋友已经开始使用Win10,估计还只是测试版本的原因,使用过程中难免会出现一些问题,这里介绍解决一些解决难题的技巧。技巧1:让ProjectSpartan“重归正途”从10074...

System32文件夹千万不能删除,看完这篇你就知道为什么了

C:\Windows\System32目录是Windows操作系统的关键部分,重要的系统文件存储在该目录中。网上的一些恶作剧者可能会告诉你删除它,但你不应该尝试去操作,如果你尝试的话,我们会告诉你会发...

Windows.old 文件夹:系统备份的解析与安全删除指南

Windows.old是Windows系统升级(如Win10升Win11)或重装时,系统自动在C盘创建的备份文件夹,其核心作用是保留旧系统的文件、程序与配置,为“回退旧系统”提供保...

遇到疑难杂症?Windows 10回收站问题巧解决

回收站是Windows10的一个重要组件。然而,我们在使用过程中,可能会遇到一些问题。例如,不论回收站里有没有文件,都显示同一个图标,让人无法判别回收站的空和满的真实情况;没有了像Windows7...

卸载软件怎么彻底删掉?简单几个步骤彻底卸载,电脑小白看过来

日常工作学习生活中,我们需要在安装一些软件程序,但随着软件的更新迭代速度,很多时候我们需要重新下载安装新的程序,这时就需要将旧的一些软件程序进行卸载。但是卸载软件虽然很简单,但是很多小伙伴们表示卸载不...

用不上就删!如何完全卸载OneDrive?

作为Windows10自带的云盘,OneDrive为资料的自动备份和同步提供了方便。然而,从隐私或其他方面考虑,有些人不愿意使用OneDrive。但Windows10本身不提供直接卸载OneDri...

【Linux知识】Linux下快速删除大量文件/文件夹方法

在Linux下,如果需要快速删除大量文件或文件夹,可以使用如下方法:使用rm命令删除文件:可以使用rm命令删除文件,例如:rm-rf/path/to/directory/*这个命令会递...

清理系统不用第三方工具_清理系统垃圾用什么软件

清理优化系统一定要借助于优化工具吗?其实,手动优化系统也没有那么神秘,掌握了方法和技巧,系统清理也是一件简单和随心的事。一方面要为每一个可能产生累赘的文件找到清理的方法,另一方面要寻找能够提高工作效率...

系统小技巧:软件卸载不了?这里办法多

在正常情况下,我们都是通过软件程序组中的卸载图标,或利用控制面板中的“程序和功能”模块来卸载软件的。但有时,我们也会发现利用卸载图标无法卸载软件或者卸载图标干脆丢失找不到了,甚至控制面板中卸载软件的功...

麒麟系统无法删除文件夹_麒麟系统删除文件权限不够

删除文件夹方法例:sudorm-rf文件夹名称。删除文件方法例:sudorm-r文件名包括扩展名。如果没有权限,给文件夹加一下权限再删。加最高权限chmod775文件名加可执行权限...

取消回复欢迎 发表评论: