【发布时间】:2018-07-21 12:05:34
【问题描述】:
我有一个自定义的 QQuickPaintedItem,它可以绘制用户用鼠标在其上绘制的任何内容。到目前为止,实现非常简单,只需绘制整个图像,即使在放大时也是如此。我注意到放大和平移图像时 FPS 真的很慢,所以我决定逐步提高绘画性能。
我当前的步骤只是绘制可见图像的子集。为此,我使用this overload of QPainter::drawImage()。这是允许缩放和平移的最小示例(重要部分是recalculateStuff()):
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QDebug>
#include <QQuickItem>
#include <QImage>
#include <QQuickPaintedItem>
#include <QPainter>
#include <QtMath>
class ImageCanvas : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(QPoint offset READ offset WRITE setOffset NOTIFY offsetChanged)
Q_PROPERTY(int zoom READ zoom WRITE setZoom NOTIFY zoomChanged)
Q_PROPERTY(QRect sourceRect READ sourceRect NOTIFY sourceRectChanged)
Q_PROPERTY(QRect targetRect READ targetRect NOTIFY targetRectChanged)
public:
ImageCanvas() :
mZoom(1)
{
// Construct a test image from coloured squares.
mImage = QImage(500, 500, QImage::Format_ARGB32);
QPainter painter(&mImage);
for (int y = 0; y < mImage.width(); y += 50) {
for (int x = 0; x < mImage.width(); x += 50) {
const QColor colour((x / 500.0) * 255, (y / 500.0) * 255, 0);
painter.fillRect(x, y, 50, 50, colour);
}
}
recalculateStuff();
}
QPoint offset() const {
return mOffset;
}
void setOffset(const QPoint &offset) {
mOffset = offset;
recalculateStuff();
emit offsetChanged();
}
int zoom() const {
return mZoom;
}
void setZoom(int zoom) {
mZoom = qMax(1, zoom);
recalculateStuff();
emit zoomChanged();
}
QRect targetRect() const {
return mTargetRect;
}
QRect sourceRect() const {
return mSourceRect;
}
void recalculateStuff() {
const QRect oldTargetRect = mTargetRect;
const QRect oldSourceRect = mSourceRect;
mTargetRect = QRect(0, 0, mImage.width() * mZoom, mImage.height() * mZoom);
mSourceRect = QRect(0, 0, mImage.width(), mImage.height());
const int contentLeft = mOffset.x();
if (contentLeft < 0) {
// The left edge of the content is outside of the viewport, so don't draw that portion.
mTargetRect.setX(qAbs(contentLeft));
mSourceRect.setX(qAbs(contentLeft));
}
const int contentTop = mOffset.y();
if (contentTop < 0) {
// The top edge of the content is outside of the viewport, so don't draw that portion.
mTargetRect.setY(qAbs(contentTop));
mSourceRect.setY(qAbs(contentTop));
}
const int contentRight = mOffset.x() + mImage.width();
const int viewportRight = qFloor(width());
if (contentRight > viewportRight) {
// The right edge of the content is outside of the viewport, so don't draw that portion.
mTargetRect.setWidth(mTargetRect.width() - (contentRight - viewportRight));
mSourceRect.setWidth(mSourceRect.width() - (contentRight - viewportRight));
}
const int contentBottom = mOffset.y() + mImage.height();
const int viewportBottom = qFloor(height());
if (contentBottom > viewportBottom) {
// The bottom edge of the content is outside of the viewport, so don't draw that portion.
mTargetRect.setHeight(mTargetRect.height() - (contentBottom - viewportBottom));
mSourceRect.setHeight(mSourceRect.height() - (contentBottom - viewportBottom));
}
if (mTargetRect != oldTargetRect)
emit targetRectChanged();
if (mSourceRect != oldSourceRect)
emit sourceRectChanged();
update();
}
void paint(QPainter *painter) override {
painter->translate(mOffset);
painter->drawImage(mTargetRect, mImage, mSourceRect);
}
protected:
void geometryChanged(const QRectF &, const QRectF &) override {
recalculateStuff();
}
signals:
void offsetChanged();
void zoomChanged();
void sourceRectChanged();
void targetRectChanged();
private:
QPoint mOffset;
int mZoom;
QRect mSourceRect;
QRect mTargetRect;
QImage mImage;
};
int main(int argc, char *argv[])
{
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<ImageCanvas>("App", 1, 0, "ImageCanvas");
QQmlApplicationEngine engine;
engine.load(QUrl("qrc:/main.qml"));
return app.exec();
}
#include "main.moc"
main.qml:
import QtQuick 2.10
import QtQuick.Controls 2.3
import App 1.0
ApplicationWindow {
id: window
width: 600
height: 600
visible: true
title: "targetRect=" + canvas.targetRect + " sourceRect=" + canvas.sourceRect
ImageCanvas {
id: canvas
anchors.fill: parent
offset: Qt.point(xOffsetSlider.value, yOffsetSlider.value)
zoom: zoomSpinBox.value
}
SpinBox {
id: zoomSpinBox
from: 1
to: 8
}
Slider {
id: xOffsetSlider
anchors.bottom: parent.bottom
width: parent.width - height
from: -window.width * canvas.zoom
to: window.width * canvas.zoom
ToolTip {
id: xOffsetToolTip
parent: xOffsetSlider.handle
visible: true
text: xOffsetSlider.value.toFixed(1)
Binding {
target: xOffsetToolTip
property: "visible"
value: !yOffsetToolTip.visible
}
}
}
Slider {
id: yOffsetSlider
anchors.right: parent.right
height: parent.height - width
orientation: Qt.Vertical
from: -window.height * canvas.zoom
scale: -1
to: window.height * canvas.zoom
ToolTip {
id: yOffsetToolTip
parent: yOffsetSlider.handle
text: yOffsetSlider.value.toFixed(1)
Binding {
target: yOffsetToolTip
property: "visible"
value: !xOffsetToolTip.visible
}
}
}
}
这在缩放级别为 1 时效果很好,但一旦你放大,目标和源矩形就错了。我一直在尝试修复它,但我无法完全理解它。例如,一个幼稚的想法是使用非缩放坐标进行所有计算,然后缩放目标矩形:
diff --git a/main.cpp b/main.cpp
index 8409baf..06841b7 100644
--- a/main.cpp
+++ b/main.cpp
@@ -64,24 +64,24 @@ public:
const QRect oldTargetRect = mTargetRect;
const QRect oldSourceRect = mSourceRect;
- mTargetRect = QRect(0, 0, mImage.width() * mZoom, mImage.height() * mZoom);
+ mTargetRect = QRect(0, 0, mImage.width(), mImage.height());
mSourceRect = QRect(0, 0, mImage.width(), mImage.height());
- const int contentLeft = mOffset.x();
+ const int contentLeft = mOffset.x() / mZoom;
if (contentLeft < 0) {
// The left edge of the content is outside of the viewport, so don't draw that portion.
mTargetRect.setX(qAbs(contentLeft));
mSourceRect.setX(qAbs(contentLeft));
}
- const int contentTop = mOffset.y();
+ const int contentTop = mOffset.y() / mZoom;
if (contentTop < 0) {
// The top edge of the content is outside of the viewport, so don't draw that portion.
mTargetRect.setY(qAbs(contentTop));
mSourceRect.setY(qAbs(contentTop));
}
- const int contentRight = mOffset.x() + mImage.width();
+ const int contentRight = (mOffset.x() / mZoom) + mImage.width();
const int viewportRight = qFloor(width());
if (contentRight > viewportRight) {
// The right edge of the content is outside of the viewport, so don't draw that portion.
@@ -89,7 +89,7 @@ public:
mSourceRect.setWidth(mSourceRect.width() - (contentRight - viewportRight));
}
- const int contentBottom = mOffset.y() + mImage.height();
+ const int contentBottom = (mOffset.y() / mZoom) + mImage.height();
const int viewportBottom = qFloor(height());
if (contentBottom > viewportBottom) {
// The bottom edge of the content is outside of the viewport, so don't draw that portion.
@@ -97,6 +97,11 @@ public:
mSourceRect.setHeight(mSourceRect.height() - (contentBottom - viewportBottom));
}
+ mTargetRect.setX(mTargetRect.x() * mZoom);
+ mTargetRect.setY(mTargetRect.y() * mZoom);
+ mTargetRect.setWidth(mTargetRect.width() * mZoom);
+ mTargetRect.setHeight(mTargetRect.height() * mZoom);
+
if (mTargetRect != oldTargetRect)
emit targetRectChanged();
这是行不通的,因为图像越来越像你一样被拉伸。在缩放设置为 2 的情况下向下平移,而不是保持相同的比例。
那么,计算目标和源矩形的正确方法是什么,以确保我只在放大图像时绘制图像的可见部分?
【问题讨论】:
-
发布完整和最小示例的道具。它确实让弄清楚它变得容易和愉快。