程序员的知识教程库

网站首页 > 教程分享 正文

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

henian88 2025-05-09 18:53:29 教程分享 3 ℃ 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+的编解码器

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表