2016-04-25 19 views
1

In meinem Programm versuche ich, die Koordinaten einer Mausdruck zurück auf die Koordinaten eines Bildes abzubilden. Ich benutze PyQt4 in Python. Das folgende Programm zeigt das Problem. Ich habe ein Widget, das ein paar Bildtransformationen macht. Nach diesen Bildtransformationen wird das Bild in der Mitte des Widgets angezeigt, während das ursprüngliche Seitenverhältnis des Bildes beibehalten wird. Da das Bild skaliert und übersetzt wird, muss eine Koordinate eines MouseEvent dem Koordinatensystem des Bildes zugeordnet werden.PyQt mapping die Koordinaten einer Maus drücken Sie auf das Koordinatensystem eines Bildes

Das folgende Programm hat eine Klasse "ScalingWidget", die in der Lage sein sollte, diese Transformationen durchzuführen und die Koordinate in einem MouseReleaseEvent wieder in das Koordinatensystem des Bildes neu zuordnen sollte. Dies funktioniert perfekt wie erwartet, wenn ich das Widget außerhalb eines Layouts und Hauptfensters zeige, aber es wird böse, wenn ich das Widget in eine größere GUI einbette. Dann zeigen die Koordinaten nach dem Zuordnen zu den Bildkoordinaten plötzlich einen Offset an.

Das unten stehende Minimalprogramm kann mit und ohne den Fehler gestartet werden, indem das Flag -b beim Start des Programms angegeben wird. Die Option -n können in einem „gui“, legen Sie die Instanz von ScalingWidget tief und tiefer und je tiefer sie in den Layouts desto starker der Fehler sichtbar eingebettet sein wird. Die blöde Sache ist, obwohl die Zeichnung anzeigt, dass die Transformationen korrekt sind, zeigen die gemappten Koordinaten (gedruckt in dem Fenstertitel und der Konsole) an, dass eine Neuabbildung an die Bildkoordinaten vermasselt wird, wenn das Flag -b vorhanden ist.

Also meine Frage ist: Was soll ich mit Remapping die Maus falsch zu machen Koordinaten zurück auf die Bildabmessungen, wenn mein ScalingWidget in einem Layout eingebettet ist?

Ich erwarte nicht, die Neuzuordnung Pixel, perfekt zu sein, aber nur so genau wie der End-Benutzer mit der Maus positionieren kann. Es gibt zwei Punkte x = 20, y = 20 und bei x = 380 und y = 380 können diese als Bezugspunkte verwendet werden.

Jede Hilfe ist herzlich willkommen!

#!/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() 

Antwort

0

Die Grundidee von _default_image_transform ist korrekt. Der Fehler liegt am Ende der Funktion.

self.matrix = painter.transform() 

Gemäß der Dokumentation man nur die QPainter.deviceTransform() nennen sollten, wenn Sie mit QT :: GRIFF arbeiten, die eine plattformabhängige Griff:

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 

Die letzte Zeile der Funktion sollte _default_image_transform sein. Da ich nicht mit einem plattformabhängigen Handle gearbeitet habe, hätte ich es nicht nennen sollen. Es funktioniert, wenn ich das Widget zeige, aber nicht, wenn es in ein Layout eingebettet ist. Dann unterscheidet sich die DeviceTransform-Matrix von der normalen QPainter.Transform() -Matrix. Siehe auch http://doc.qt.io/qt-4.8/qpainter.html#deviceTransform