在Qt中实现无限可缩放的画布

Implementing an infinite zoomable canvas in Qt

本文关键字:缩放 无限 Qt 实现      更新时间:2023-10-16

我正在从事一个需要无限表面(样条曲线和文本(的项目,该项目也需要可扩展。"虚拟图面"的要求是:

  • 能够在表面上滚动和移动
  • 能够动态调整其高度(可能到无穷大,但实际上高达 200000 "像素"(
  • 固定宽度
  • 能够绘制矢量路径、文本和图像
  • 可缩放(使用捏合缩放。此外,当比例更改时,所有矢量都应以新比例渲染。
  • 能够部分更新表面区域

看看Qt,我列出了3个主要选项来实现这一点,但我不完全确定其中哪个最适合我的需求(或者即使我错过了另一种选择:

  1. QGraphicsView,它可以轻松地为我提供一个可滚动的视口,并具有几个内置的渲染策略来实现我的目标(尤其是这个 https://doc.qt.io/qt-5/qgraphicsview.html#ViewportUpdateMode-enum(。这也将自动为我提供轻拂和调整内容大小的工具。唯一的问题是这个类是Qt小部件的一部分,我真的很想使用QML(。
  2. 使用
  3. QQuickPaintedItem作为我的图面的视口,并在使用滚动和缩放时动态绘制它。这将需要我从QML更新视口(可能使用可轻拂和PinchArea的组合(。但是,我不确定使用此选项滚动是否足够流畅,因为我需要大量重绘该项目才能实现 60fps。通过将已绘制的区域存储在某些 QImage 对象中,然后在需要时绘制它们(本质上是基于磁贴的自定义渲染解决方案,需要大量时间才能正确实现(,可以缓解这种情况。
  4. 使用QQuickItem而不是QQuickPaintedItem来实现更好的性能,但是如果没有 QPainter,绘制路径、形状和文本将非常困难,并且需要使用QSG类进行大量工作。

Qt还有其他方法可以实现这一点吗?

我在 64.000 像素的水平可滚动区域中绘制时遇到了同样的问题。为了解决这个问题,我使用 ListView 将区域划分为段,每个段 8.000 像素。计算的段计数用作模型,委托是用于C++绘画的 QQuickPainted项目。但这也适用于 QML-Canvas。

ListView {
id: graphicView
anchors.fill: parent
orientation: Qt.Horizontal
spacing: 0
// always repaint items on scroll
reuseItems: false
// the width of the scrollable area
readonly property real displayWidth: 64000
// define a max width for segmentation to avoid the contentWidth limitation of qml
readonly property int segmentWith: 8000
readonly property int segmentCount: Math.max(1, Math.round(displayWidth / segmentWith))
onSegmentCountChanged: {
console.log("OsziGraphic: displayWidth = " + displayWidth + ", segmentCount = " + segmentCount);
}
model: graphicView.segmentCount
delegate:
TestGraphicModel {
height: ListView.view.height
width: graphicView.displayWidth / graphicView.segmentCount
segmentIndex: index
}
}

在后端,我继承了 QQuickPaintedItem 的属性,为当前段索引和要绘制的渲染区域添加属性。我平移渲染的坐标进行绘制,就好像使用了整个区域一样。

void QQuickSegmentedPaintedItem::paint(QPainter *painter)
{
if (m_segmentIndex == -1)
return;
// calculate the current render rect inside the drawing area
m_renderRect = QRect(m_segmentIndex * boundingRect().width(), 0, boundingRect().width(), boundingRect().height());
// move the drawing area relative to the current render area
painter->translate(-m_renderRect.left(), 0);
}

这很好地满足了我的需求。我希望你明白这个想法。

干杯!