在交换图像时保持QGraphicsView的视图/滚动位置

1 投票
1 回答
2583 浏览
提问于 2025-04-17 10:41

我在用 QGraphicsView 加载 TIFF 图片时,遇到了缩放的问题,使用的是 QGraphicsPixmapItem

问题主要是如何在保持图片质量的同时,确保缩放速度不会让应用卡顿。最开始,我只是用一个缩放过的 QPixmap 替换原图像。当用户按住水平滑块时,我使用 Qt.FastTransformation,然后在滑块释放时,再用 Qt.SmoothTransformation 替换成另一个缩放过的图像。这样做的效果是缩放后的图片质量很好,但当图片的大小超过原始尺寸后,缩放就变得不流畅;缩小图片时倒是没问题。

QGraphicsView 上使用 QTransform.fromScale() 可以实现更平滑的缩放,但图像质量却较低,即使我对 QGraphicsView 应用了 .setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing)

我最新的做法是结合这两种方法,在 QGraphicsView 上使用 QTransform 来实现平滑缩放,但当用户释放滑块时,再用缩放过的图像替换 QGraphicsView 中的图像。这样做效果很好,但视图中的位置就会丢失——用户放大到某个区域后,由于缩放过的图像变大,释放滑块时视图会跳到另一个位置,替换掉之前的图像。

我想,由于两幅图像的宽高比是相同的,我可以在图像交换之前获取滚动条的百分比,然后在交换后应用相同的百分比,这样应该就能正常工作。大部分情况下这样做是有效的,但有时在交换图像后,视图还是会“跳动”。

我觉得我可能在这里做错了什么。有没有人知道更好的方法,或者能不能在下面的代码中找出可能导致跳动的地方?

这是保存/恢复滚动条位置的代码。这些是我自定义的 QGraphicsView 的方法:

def store_scrollbar_position(self):
    x_max = self.horizontalScrollBar().maximum()
    if x_max:
        x = self.horizontalScrollBar().sliderPosition()
        self.scroll_x_percentage = x * (100 / float(x_max))

    y_max = self.verticalScrollBar().maximum()
    if y_max:
        y = self.verticalScrollBar().sliderPosition()
        self.scroll_y_percentage = y * (100 / float(y_max))


def restore_scrollbar_position(self):
    x_max = self.horizontalScrollBar().maximum()
    if self.scroll_x_percentage and x_max:
        x = x_max * (float(self.scroll_x_percentage) / 100)
        self.horizontalScrollBar().setSliderPosition(x)

    y_max = self.verticalScrollBar().maximum()
    if self.scroll_y_percentage and y_max:
        y = y_max * (float(self.scroll_y_percentage) / 100)
        self.verticalScrollBar().setSliderPosition(y)

这是我进行缩放的方式。self.imageFile 是一个 QPixmap,而 self.image 是我的 QGraphicsPixmapItem。同样,这也是自定义的 QGraphicsView 的一部分。这个方法与滑块的移动绑定,highQuality 参数设置为 False。在滑块释放时再次调用这个方法,highQuality 设置为 True 来交换图像。

def setImageScale(self, scale=None, highQuality=True):
    if self.imageFile.isNull():
        return
    if scale is None:
        scale = self.scale
    self.scale = scale

    self.image.setPixmap(self.imageFile)
    self.scene.setSceneRect(self.image.boundingRect())
    self.image.setPos(0, 0)
    if not highQuality:
        self.setTransform(QTransform.fromScale(self.scaleFactor, self.scaleFactor))
        self.store_scrollbar_position()
    else:
        self.image.setPixmap(self.imageFile.scaled(self.scaleFactor * self.imageFile.size(),
                                                   Qt.KeepAspectRatio, Qt.SmoothTransformation))
        self.setTransform(self.transform)
        self.scene.setSceneRect(self.image.boundingRect())
        self.image.setPos(0, 0)
        self.restore_scrollbar_position()
    return

任何帮助都将不胜感激。我现在开始感到相当沮丧了。

1 个回答

1

我找到了一种比我最开始发的代码更好的解决方案。虽然还不是完美的,但已经有很大改善了。为了防止其他人也在解决类似的问题,我分享一下。

在设置低质量图像时,我调用了这个方法,它是我添加到QGraphicsView中的:

def get_scroll_state(self):
    """
    Returns a tuple of scene extents percentages. 
    """
    centerPoint = self.mapToScene(self.viewport().width()/2,
                                  self.viewport().height()/2)
    sceneRect = self.sceneRect()
    centerWidth = centerPoint.x() - sceneRect.left()
    centerHeight = centerPoint.y() - sceneRect.top()
    sceneWidth =  sceneRect.width()
    sceneHeight = sceneRect.height()

    sceneWidthPercent = centerWidth / sceneWidth if sceneWidth != 0 else 0
    sceneHeightPercent = centerHeight / sceneHeight if sceneHeight != 0 else 0
    return sceneWidthPercent, sceneHeightPercent

这个方法的结果会存储在self.scroll_state里。当我设置高质量图像时,我会调用另一个函数来恢复之前使用的百分比。

def set_scroll_state(self, scroll_state):
    sceneWidthPercent, sceneHeightPercent = scroll_state
    x = (sceneWidthPercent * self.sceneRect().width() +
         self.sceneRect().left())
    y = (sceneHeightPercent * self.sceneRect().height() +
         self.sceneRect().top())
    self.centerOn(x, y)

这个函数会把中心位置设置到和我在切换图像之前一样的地方(按百分比计算)。

撰写回答