PyQt 将鼠标按下的坐标映射到图像的坐标系
Posted
技术标签:
【中文标题】PyQt 将鼠标按下的坐标映射到图像的坐标系【英文标题】:PyQt mapping the coordinates of a mouse press to the coordinate system of an image 【发布时间】:2016-04-25 15:34:56 【问题描述】:在我的程序中,我试图将鼠标按下的坐标映射回图像的坐标尺寸。我在 Python 中使用 PyQt4。下面的程序演示了这个问题。我有一个可以进行一些图像转换的小部件。在这些图像转换之后,图像显示在小部件的中心,同时保持图像的原始纵横比。由于图像是缩放和平移的,因此必须将 MouseEvent 的坐标重新映射到 Image 的坐标系。
下面的程序有一个“ScalingWidget”类,它应该能够进行这些转换,并且还应该能够将 mouseReleaseEvent 中的坐标重新映射回图像的坐标系。当我在布局和主窗口之外显示小部件时,这可以完美地工作,但是当我将小部件嵌入到更大的 gui 中时,它会变得很糟糕。然后将它们映射回Image坐标后的坐标突然显示偏移量。
下面的最小程序可以通过在启动程序时指定标志 -b 来启动和不启动错误。选项 -n 可以将 ScalingWidget 的实例放入“gui”中越来越深,并且它在布局中嵌入的越深,错误就越明显。 愚蠢的是,虽然绘图表明转换是正确的,但映射坐标(打印在窗口标题和控制台中)表明,当存在 -b 标志时,将它们重新映射回图像坐标会搞砸。
所以我的问题是:当我的 ScalingWidget 嵌入到布局中时,将鼠标坐标重新映射回图像尺寸有什么问题?
我不希望重新映射是像素完美的,但就像最终用户可以定位鼠标一样准确。有两个点 x=20, y=20 和 x=380 和 y=380 这些点可以作为参考点。
欢迎任何帮助!
#!/usr/bin/env python
from PyQt4 import QtGui
from PyQt4 import QtCore
import sys
import argparse
class ScalingWidget (QtGui.QWidget):
''' Displays a pixmap optimally in the center of the widget, in such way
the pixmap is shown in the middle
'''
white = QtGui.QColor(255,255,255)
black = QtGui.QColor( 0, 0, 0)
arcrect = QtCore.QRect(-10, -10, 20, 20)
def __init__(self):
super(ScalingWidget, self).__init__()
self.pixmap = QtGui.QPixmap(400, 400)
painter = QtGui.QPainter(self.pixmap)
painter.fillRect(self.pixmap.rect(), self.white)
self.point1 = QtCore.QPoint(20, 20)
self.point2 = QtCore.QPoint(380, 380)
painter.setPen(self.black)
painter.drawRect(QtCore.QRect(self.point1, self.point2))
painter.end()
self.matrix = None
def sizeHint(self):
return QtCore.QSize(500,400)
##
# Applies the default transformations
#
def _default_img_transform(self, painter):
#size of widget
winheight = float(self.height())
winwidth = float(self.width())
#size of pixmap
scrwidth = float(self.pixmap.width())
scrheight = float(self.pixmap.height())
assert(painter.transform().isIdentity())
if scrheight <= 0 or scrwidth <= 0:
raise RuntimeError(repr(self) + "Unable to determine Screensize")
widthr = winwidth / scrwidth
heightr = winheight / scrheight
if widthr > heightr:
translate = (winwidth - heightr * scrwidth) /2
painter.translate(translate, 0)
painter.scale(heightr, heightr)
else:
translate = (winheight - widthr * scrheight) / 2
painter.translate(0, translate)
painter.scale(widthr, widthr)
# now store the matrix used to map the mouse coordinates back to the
# coordinates of the pixmap
self.matrix = painter.deviceTransform()
def paintEvent(self, e):
painter = QtGui.QPainter(self)
painter.setClipRegion(e.region())
# fill the background of the entire widget.
painter.fillRect(self.rect(), QtGui.QColor(0,0,0))
# transform to place the image nicely in the center of the widget.
self._default_img_transform(painter)
painter.drawPixmap(self.pixmap.rect(), self.pixmap, self.pixmap.rect())
pen = QtGui.QPen(QtGui.QColor(255,0,0))
# Just draw on the points used to make the black rectangle of the pix map
# drawing is not affected, be remapping those coordinates with the "same"
# matrix is.
pen.setWidth(4)
painter.setPen(pen)
painter.save()
painter.translate(self.point1)
painter.drawPoint(0,0)
painter.restore()
painter.save()
painter.translate(self.point2)
painter.drawPoint(0,0)
painter.restore()
painter.end()
def mouseReleaseEvent(self, event):
x, y = float(event.x()), float(event.y())
inverted, invsucces = self.matrix.inverted()
assert(invsucces)
xmapped, ymapped = inverted.map(x,y)
print x, y
print xmapped, ymapped
self.setWindowTitle("mouse x,y = , , mapped x, y = , "
.format(x, y, xmapped, ymapped)
)
def start_bug():
''' Displays the mouse press mapping bug.
This is a bit contrived, but in the real world
a widget is embedded in deeper in a gui
than a single widget, besides the problem
grows with the depth of embedding.
'''
app = QtGui.QApplication(sys.argv)
win = QtGui.QWidget()
layout = QtGui.QVBoxLayout()
win.setLayout(layout)
widget = None
for i in range(0, args.increase_bug):
if i < args.increase_bug-1:
widget = QtGui.QWidget()
layout.addWidget(widget)
layout= QtGui.QVBoxLayout()
widget.setLayout(layout)
else:
layout.addWidget(ScalingWidget())
win.show()
sys.exit(app.exec_())
def start_no_bug():
''' Does not show the mapping bug, the mouse event.x() and .y() map nicely back to
the coordinate system of the pixmap
'''
app = QtGui.QApplication(sys.argv)
win = ScalingWidget()
win.show()
sys.exit(app.exec_())
# parsing arguments
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('-b', '--display-bug', action='store_true',
help="Toggle this option to get the bugged version"
)
parser.add_argument('-n', '--increase-bug', type=int, default=1,
help="Increase the bug by n times."
)
if __name__ == "__main__":
args = parser.parse_args()
if args.display_bug:
start_bug()
else:
start_no_bug()
【问题讨论】:
【参考方案1】:_default_image_transform 的基本思想是正确的。错误在函数的末尾。
def _default_img_transform(self, painter):
#size of widget
winheight = float(self.height())
winwidth = float(self.width())
#size of pixmap
scrwidth = float(self.pixmap.width())
scrheight = float(self.pixmap.height())
assert(painter.transform().isIdentity())
if scrheight <= 0 or scrwidth <= 0:
raise RuntimeError(repr(self) + "Unable to determine Screensize")
widthr = winwidth / scrwidth
heightr = winheight / scrheight
if widthr > heightr:
translate = (winwidth - heightr * scrwidth) /2
painter.translate(translate, 0)
painter.scale(heightr, heightr)
else:
translate = (winheight - widthr * scrheight) / 2
painter.translate(0, translate)
painter.scale(widthr, widthr)
# now store the matrix used to map the mouse coordinates back to the
# coordinates of the pixmap
self.matrix = painter.deviceTransform() ## <-- error is here
函数_default_image_transform
的最后一行应该是:
self.matrix = painter.transform()
根据文档,只有在使用 QT::HANDLE 时才应调用QPainter.deviceTransform()
,这是一个依赖于平台的句柄。因为我没有使用依赖于平台的句柄,所以我不应该调用它。它在我显示小部件时有效,但在它嵌入布局时无效。那么 deviceTransform 矩阵不同于普通的 QPainter.transform() 矩阵。另见http://doc.qt.io/qt-4.8/qpainter.html#deviceTransform
【讨论】:
以上是关于PyQt 将鼠标按下的坐标映射到图像的坐标系的主要内容,如果未能解决你的问题,请参考以下文章