2016-04-25 200 views
1

在我的程序中,我試圖將mousepress的座標映射回圖像的座標尺寸。我在Python中使用PyQt4。下面的程序演示了這個問題。我有一個小部件可以進行一些圖像轉換。在這些圖像轉換之後,圖像顯示在小部件的中心,同時保持圖像的原始高寬比。由於圖像被縮放和平移,因此必須將MouseEvent的座標重新映射到圖像的座標系。PyQt將鼠標按鈕的座標映射到圖像的座標系

下面的程序有一個類「ScalingWidget」,它應該能夠做這些轉換,並且還應該能夠將mouseReleaseEvent中的座標重新映射回圖像的座標系。當我在Layout和mainwindow外部顯示這個小部件時,這個功能完全可以正常工作,但是當我將這個小部件嵌入到一個更大的GUI中時,它會變得很糟糕。然後將它們映射回圖像座標後的座標突然顯示偏移量。

下面的最小程序可以在啓動程序時通過指定標誌-b來啓動,不管是否有bug。選項-n可以將ScalingWidget的實例深入深入到「gui」中,並且嵌入到佈局中的深度越深,可見的bug越強。 愚蠢的是,儘管繪圖表明轉換是正確的,但映射的座標(打印在窗口標題和控制檯中)表示在存在-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() 

回答

0

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

根據該文件應該只調用QPainter.deviceTransform()當你與QT :: HANDLE這是一個依賴於平臺的手柄工作。由於我沒有使用依賴於平臺的句柄,所以我不應該調用它。它在我顯示小部件時運行,但不在嵌入佈局時運行。然後,deviceTransform矩陣與正常的QPainter.transform()矩陣不同。另見http://doc.qt.io/qt-4.8/qpainter.html#deviceTransform