我正在编写一个简单的图像查看器并正在实现平移和缩放功能(分别使用鼠标拖动和鼠标滚轮滚动)。我已经成功地实现了平移(简单模式)和一个天真的“到左上角”缩放。
我现在想优化缩放,以便缩放时用户鼠标的坐标成为“焦点”:也就是说,缩放时,平移会更新,以便用户鼠标下方的像素(图像)保持不变相同(以便他们真正放大该区域)

通过覆盖原本普通的 QWidget 上的paintEvent 来查看图像。
尽可能使用直观的方法尝试,我似乎无法实现正确的缩放行为。

属性 scale 表示当前的缩放级别(比例为 2 表示图像是真实尺寸的两倍,0.5 表示一半,比例 > 0),position 是当前图像区域左上角的坐标查看(通过平移)。

以下是实际图像显示的执行方式

def paintEvent(self, event):
    painter = QtGui.QPainter()
    painter.begin(self)

    painter.drawImage(0, 0,
        self.image.scaled(
            self.image.width() * self.scale,
            self.image.height() * self.scale,
            QtCore.Qt.KeepAspectRatio),
        self.position[0], self.position[1])

    painter.end()

这是平移代码(相对简单):
( pressedanchor 完全用于平移,分别指当时的初始鼠标按下位置和 ImageView 位置(分别))
def mousePressEvent(self, event):
    self.pressed = event.pos()
    self.anchor = self.position

def mouseReleaseEvent(self, event):
    self.pressed = None

def mouseMoveEvent(self, event):
    if (self.pressed):
        dx, dy = event.x() - self.pressed.x(), event.y() - self.pressed.y()
        self.position = (self.anchor[0] - dx, self.anchor[1] - dy)
    self.repaint()

这是没有尝试调整平移 的缩放代码 。它导致所有内容从/到屏幕左上角缩小或增长
def wheelEvent(self, event):
    oldscale = self.scale
    self.scale += event.delta() / 1200.0
    if (self.scale < 0.1):
        self.scale = oldscale
    self.repaint()

这是带有平移的缩放代码 以保留( anchor 定)可见区域 的左上角。放大时,屏幕左上角的像素不会改变。
def wheelEvent(self, event):
    oldscale = self.scale
    self.scale += event.delta() / 1200
    if (self.scale < 0.1):
        self.scale = oldscale

    self.position = (self.position[0] * (self.scale / oldscale),
                     self.position[1] * (self.scale / oldscale))
    self.repaint()

我想要上述效果,但滚动时 anchor 位于用户的鼠标上。这是我的尝试,效果很小:缩放仍然不是我想要的,而是滚动到鼠标的一般区域,没有 anchor 定。事实上,将鼠标保持在同一位置并放大似乎遵循一条弯曲的路径,向右平移然后向左平移。
def wheelEvent(self, event):
    oldscale = self.scale
    self.scale += event.delta() / 1200.0
    if (self.scale < 0.1):
        self.scale = oldscale

    oldpoint = self.mapFromGlobal(QtGui.QCursor.pos())
    dx, dy = oldpoint.x() - self.position[0], oldpoint.y() - self.position[1]
    newpoint = (oldpoint.x() * (self.scale/oldscale),
                oldpoint.y() * (self.scale/oldscale))
    self.position = (newpoint[0] - dx, newpoint[1] - dy)

这背后的理论是,在缩放之前,鼠标“下方”的像素距离左上角(位置)的长度为 dx 和 dy。缩放后,我们计算此像素的新位置,并通过将 self.position 调整为像素的 dx 和 dy 西侧和北侧将其强制在屏幕上的相同坐标下。

我不完全确定我哪里出错了:我怀疑 old point 到我的屏幕坐标的映射在某种程度上是关闭的,或者更有可能:我的数学是错误的,因为我混淆了像素和屏幕坐标。
我已经尝试了一些直观的变化,但没有任何东西接近预期的 anchor 定。

我想这对于文件查看器来说是一项非常常见的任务(因为大多数似乎都像这样缩放),但我发现研究算法非常困难。

这是修改缩放的完整代码(需要 PyQt4):
http://pastebin.com/vvpdZy9g

任何帮助表示赞赏!

最佳答案

好的,我设法让它工作

def wheelEvent(self, event):
    oldscale = self.scale
    self.scale += event.delta() / 1200.0
    if (self.scale < 0.1):
        self.scale = oldscale

    screenpoint = self.mapFromGlobal(QtGui.QCursor.pos())
    dx, dy = screenpoint.x(), screenpoint.y()
    oldpoint = (screenpoint.x() + self.position[0], screenpoint.y() + self.position[1])
    newpoint = (oldpoint[0] * (self.scale/oldscale),
                oldpoint[1] * (self.scale/oldscale))
    self.position = (newpoint[0] - dx, newpoint[1] - dy)

这里的逻辑:
  • 我们得到了鼠标在屏幕上的位置(screenpoint),使用它,我们得到了 anchor 定像素和屏幕边缘之间的距离(根据定义)
  • 我们使用 screenpoint 和 position 来找到鼠标在图像平面上的坐标(即:悬停像素的二维索引),作为 oldpoint
  • 应用我们的缩放,我们计算像素(新点)的新 2D 索引
  • 我们希望这个像素在我们的屏幕上,而不是在左上角:我们希望它从左上角(位置)开始 dx 和 dy

  • 问题确实是图像和显示坐标之间的一个微不足道的混淆。

    关于python - 放大图像时控制平移(以 anchor 定点),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/20942586/

    10-15 22:52