【问题标题】:Controlling the pan (to anchor a point) when zooming into an image放大图像时控制平移(以锚定点)
【发布时间】:2014-01-23 10:06:36
【问题描述】:

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

通过在一个普通的 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 完全用于平移,分别指鼠标初始按下的位置当时的图像视图位置

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

我想要上面的效果,但是滚动时锚点要在用户的鼠标上。这是我的尝试,效果非常轻微:缩放仍然不是我想要的,而是滚动到鼠标的一般区域,没有锚定。事实上,保持鼠标在同一个位置并放大,似乎是沿着一条弯曲的路径,向右平移,然后向左平移。

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)

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

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

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

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

感谢任何帮助!

【问题讨论】:

    标签: python image zooming pyqt4 pan


    【解决方案1】:

    好的,我设法让它工作了

    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),并使用它,我们得到了我们的锚定像素和屏幕边缘之间的距离(根据定义)
    • 我们使用 screenpointposition 来查找鼠标相对于图像平面的坐标(即:悬停像素的 2D 索引),如 旧点
    • 应用我们的缩放,我们计算像素的新二维索引(新点)
    • 我们希望这个像素出现在屏幕上,而不是左上角:我们希望它从左上角 dxdy 开始(position)

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

    【讨论】:

      猜你喜欢
      • 2021-03-05
      • 2021-03-02
      • 1970-01-01
      • 1970-01-01
      • 2015-03-21
      • 1970-01-01
      • 2021-12-07
      • 1970-01-01
      • 2010-10-19
      相关资源
      最近更新 更多