自定义 QQuickPaintedItem 中的渲染质量差

Posted

技术标签:

【中文标题】自定义 QQuickPaintedItem 中的渲染质量差【英文标题】:Poor rendering quality in custom QQuickPaintedItem 【发布时间】:2019-12-06 01:07:33 【问题描述】:

我在 QML 中创建了一个小型绘图应用程序,我创建了 QQuickPaintedItem 的一个小型子类。然后在 QML 中,我使用 MouseArea 将输入提供给我的班级。从那里我只需将鼠标位置存储在一个向量中,然后使用QPainter 将收到的点绘制到QImage 上(我使用一个简单的算法使用向量中的最后三个点绘制二次贝塞尔曲线)。然后我调用QQuickPainted::update() 并在我的QQuickPaintedItem::paint() 实现中绘制图像。现在程序运行正常,但问题是绘画的渲染效果很差(我已经在使用QPainter::AntiAliasing)。下面有一张图片。如您所见,曲线不是很锐利,而且我可以看到斜线上的“像素”(当我用 OneNote 尝试同样的事情时,一切都变得平滑而美观)。

如果你想测试一下,这里是a full example from my github repository(代码也在下面)。对此我能做些什么吗? 。

#ifndef DRAWINGCANVAS_H
#define DRAWINGCANVAS_H

#include <QObject>
#include <QQuickPaintedItem>
#include <QPixmap>
#include <QPainter>

struct Outline
    QPolygonF points;

    void addPoint(QPointF p)
        points.append(p);
    
    void clear()
        points.clear();
    
;

// a  custom QQuickPainted used as a canvas in QML
class DrawingCanvas : public QQuickPaintedItem

    Q_OBJECT
    Q_PROPERTY(bool drawing READ drawing WRITE setDrawing NOTIFY drawingChanged)
    Q_PROPERTY(int penWidth READ penWidth WRITE setPenWidth NOTIFY penWidthChanged)
    Q_PROPERTY(QString penColor READ penColor WRITE setPenColor NOTIFY penColorChanged)

public:
    explicit DrawingCanvas(QQuickItem *parent = nullptr);
    bool drawing() const;
    Q_INVOKABLE void initiateBuffer();

    Q_INVOKABLE void penPressed(QPointF pos);
    Q_INVOKABLE void penMoved(QPointF pos);
    Q_INVOKABLE void penReleased();
    int penWidth() const;

    void paint(QPainter *painter) override;

    QString penColor() const;


public slots:
    void setDrawing(bool drawing);

    void setPenWidth(int penWidth);

    void setPenColor(QString penColor);

signals:
    void drawingChanged(bool drawing);
    void penWidthChanged(int penWidth);
    void penColorChanged(QString penColor);

private:
    void drawOnBuffer(QPointF pos);

    bool m_drawing;
    QPixmap m_buffer;
    int m_penWidth;
    QString m_penColor;

    QPointF m_lastPoint;
    Outline m_currentOutline;
    QRect m_updateRect;
    QVector<Outline> m_outlines;


    bool m_outlineEraser;
;

#endif // DRAWINGCANVAS_H
#include "drawingcanvas.h"

#include <QPainter>

DrawingCanvas::DrawingCanvas(QQuickItem *parent) : QQuickPaintedItem(parent)

    m_penWidth = 4;


bool DrawingCanvas::drawing() const

    return m_drawing;


void DrawingCanvas::penPressed(QPointF pos)

    setDrawing(true);
    m_currentOutline.addPoint(pos);
    m_lastPoint = pos;



void DrawingCanvas::penMoved(QPointF pos)

    if(drawing())
        m_currentOutline.addPoint(pos);
        // draw the points on the buffer
        drawOnBuffer(pos);
    
    m_lastPoint = pos;


void DrawingCanvas::penReleased()

    setDrawing(false);
    m_outlines.append(m_currentOutline);
    m_currentOutline.clear();
    m_lastPoint = QPointF();


// draws the actual item
void DrawingCanvas::paint(QPainter *painter)

    painter->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
    QPen pen;
    pen.setWidth(penWidth());
    pen.setColor(penColor());

    painter->setPen(pen);
    painter->drawPixmap(m_updateRect, m_buffer, m_updateRect);

    m_updateRect = QRect();


// draws on the image
void DrawingCanvas::drawOnBuffer(QPointF pos)

    QPainter bufferPainter;
    if(bufferPainter.begin(&m_buffer))
        QPen pen;
        pen.setWidth(penWidth());
        pen.setColor(penColor());

        bufferPainter.setPen(pen);
        bufferPainter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);

        int pointsLength = m_currentOutline.points.length();
        QPainterPath path;

        // this will help smoothing the curves
        if(pointsLength > 2)
            auto previousPoint = m_currentOutline.points.at(pointsLength - 3);

            auto mid1 = (m_lastPoint + previousPoint)/2;
            auto mid2 = (pos + m_lastPoint)/2;

            path.moveTo(mid1);
            path.quadTo(m_lastPoint, mid2);
            bufferPainter.drawPath(path);
        
        // update the canvas
        int rad = (penWidth() / 2) + 2;

        auto dirtyRect = path.boundingRect().toRect().normalized()
                .adjusted(-rad, -rad, +rad, +rad);

        // change the canvas dirty region
        if(m_updateRect.isNull())
            m_updateRect = dirtyRect;
        
        else
            m_updateRect = m_updateRect.united(dirtyRect);
        
        update(dirtyRect);

        m_lastPoint = pos;
    


QString DrawingCanvas::penColor() const

    return m_penColor;


int DrawingCanvas::penWidth() const

    return m_penWidth;


void DrawingCanvas::setDrawing(bool drawing)

    if (m_drawing == drawing)
        return;

    m_drawing = drawing;
    emit drawingChanged(m_drawing);


void DrawingCanvas::setPenWidth(int penWidth)

    if (m_penWidth == penWidth)
        return;

    m_penWidth = penWidth;
    emit penWidthChanged(m_penWidth);


void DrawingCanvas::setPenColor(QString penColor)

    if (m_penColor == penColor)
        return;

    m_penColor = penColor;
    emit penColorChanged(m_penColor);


// initiates the QImage buffer
void DrawingCanvas::initiateBuffer()

    qDebug() << this << "Initiating buffer" << width() << height();
    m_buffer = QPixmap(width(), height());


在 QML 中:

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.12
import QtQuick.Dialogs 1.3
import Drawing 1.0

ApplicationWindow 
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    Flickable 
        id: scrollView
        anchors.fill: parent
        contentHeight: drawingCanvas.height
        DrawingCanvas 
            id: drawingCanvas
            width: parent.width
            height: 2000
            penColor: "red"
            onWidthChanged: drawingCanvas.initiateBuffer()
        
    

    MouseArea 
        anchors.fill: parent
        anchors.rightMargin: 20
        onPressed: drawingCanvas.penPressed(
                       Qt.point(mouseX, mouseY + scrollView.contentY))
        onPositionChanged: drawingCanvas.penMoved(
                               Qt.point(mouseX, mouseY + scrollView.contentY))
        onReleased: drawingCanvas.penReleased()
    

【问题讨论】:

提供minimal reproducible example @eyllanesc 我将我的 github 存储库与我的完整项目相关联 不,这不是我要求的。 SO中MRE的目的是让未来的读者分析他们是否有类似的问题,然后尝试解决方案是否有帮助,但链接不能是MRE,因为没有人保证1年,10年或仍然存在的时候,网址不会中断。因此,如果您需要帮助提供 MRE,否则您的问题可能会被关闭。 @eyllanesc 好的,我将代码复制到这里 同样的论点适用于为什么在 SO 中我们不接受只是链接的问题,因此我们消除了它们。问答必须是自给自足的。 【参考方案1】:

您的渲染问题似乎不是由于抗锯齿 qt 选项,而更多是由于您的笔画平滑。我建议您修改您的自定义贝塞尔平滑技术或为此 [0] 使用专用库。

其次,如果你想要“OneNote 绘图的感觉”,你应该在你的绘图方法中创建一个专用的 QPen,并使用 QPen 和 QBrush 选项[1]“玩”。我看到的两个屏幕截图之间的主要区别是笔刷缩放动态(在笔触的开头和结尾处)。

0:例如https://github.com/oysteinmyrmo/bezier

1:https://doc.qt.io/qt-5/qpen.html

【讨论】:

以上是关于自定义 QQuickPaintedItem 中的渲染质量差的主要内容,如果未能解决你的问题,请参考以下文章

如何在 QQuickPaintedItem 中以有效的方式绘制顺序图像

form在模版中的渲 染方式

在 QQuickPaintedItem 上用鼠标绘制

如何在 QQuickPaintedItem 中选择一个区域

不能在 QML 中使用 C++ QQuickPaintedItem Singleton

要在屏幕上绘制一些小图块,我应该使用 QQuickItem 还是 QQuickPaintedItem?