首页 文章

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

提问于
浏览
3

我已经成功实现了平移(简易模式)和一个天真的'进入左上角'变焦 .
当缩放成为'focal point'时,我是'd now like to refine the zoom such that the coordinate of the user'的鼠标:也就是说,当缩放时,平移会更新,以便用户's mouse stays the same (so that they'下的像素(图像)真正放大到该区域)

通过覆盖原始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()

这是缩放代码 without attempting to adjust the pan . 它导致所有东西从屏幕的左上角缩小或增长

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

这是缩放代码 with panning to preserve (anchor) the top left corner of the visible region . 放大时,屏幕左上角的像素不会改变 .

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)

这背后的理论是,在缩放之前,像素'under'鼠标是从左上角(位置)的长度dx和dy . 在缩放之后,我们计算该像素的新位置并通过将 self.position 调整为像素的西和北的dx和dy来强制它在屏幕上的相同坐标下 .

我错了:我怀疑 old point 到我的屏幕坐标的映射是某种方式关闭,或者更可能:我的数学错误,因为我混淆了像素和屏幕坐标 .
我尝试了一些直观的变化,没有任何东西接近预期的锚定 .

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

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

任何帮助表示赞赏!

1 回答

  • 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和position来根据图像的平面(即:悬停像素的2D索引)找到鼠标的坐标,如oldpoint

    • 应用我们的缩放,我们计算像素的新2D索引(新点)

    • 我们想在屏幕上显示这个像素,但不是在左上角:我们想要从左上角(位置)看dx和dy

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

相关问题