在放大图像时控制平移(以锚定点)

时间:2014-01-06 03:39:31

标签: python image zoom pyqt4 pan

我正在编写一个简单的图像查看器,并且正在实现平移和缩放功能(分别使用鼠标拖动和鼠标滚轮滚动)。我已经成功实现了平底锅(简易模式)和一个天真的进入左上角'变焦。
我现在想要优化缩放,以便缩放时用户鼠标的坐标变为焦点&#39 ;:也就是说,当缩放时,平移更新以便用户鼠标下的像素(图像)保持不变(这样他们就可以放大该区域)

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

属性scale表示当前的缩放级别(比例为2表示图像被观看的时间是它的真实大小的两倍,0.5表示一半,比例> 0),{{1 }}是当前查看的图像区域左上角的坐标(通过平移)。

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

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() pressed完全用于平移,并参考当时初始鼠标按下的位置图像视图位置(分别地))

anchor

以下是缩放代码,但未尝试调整平移。它导致所有东西从屏幕的左上角缩小或增长

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()

我想要上述效果,但滚动时锚定点位于用户的鼠标上。这是我的尝试,它的工作非常轻微:缩放仍然不是我想要的,但滚动到鼠标的一般区域,没有锚定。实际上,将鼠标保持在相同位置并放大似乎遵循弯曲路径,向右平移然后向左平移。

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()

这背后的理论是,在缩放之前,像素在&#39;之下。鼠标左上角是 dx dy 位置)。在缩放后,我们通过将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到我的屏幕坐标的映射是某种方式关闭,或者更可能:我的数学错误,因为我&# 39;混淆像素和屏幕坐标 我尝试了一些直观的变化,没有任何东西接近预期的锚定。

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

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

感谢任何帮助!

1 个答案:

答案 0 :(得分:3)

好的,我设法让它正常工作

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 ),并使用此功能,我们的锚定像素与屏幕边缘之间的距离(根据定义) )
  • 我们使用 screenpoint 位置来查找鼠标在图像平面方面的坐标(即:悬停像素的2D索引),如 oldpoint
  • 应用我们的缩放,我们计算像素的新2D索引(新点)
  • 我们想要在屏幕上显示此像素,但不在左上角:我们希望左上角的 dx dy 位置

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