2016-04-25 1 views
1

Dans mon programme, j'essaie de mapper les coordonnées d'une souris aux coordonnées d'une image. J'utilise PyQt4 en Python. Le programme ci-dessous illustre le problème. J'ai un widget qui fait quelques transformations d'image. Après ces transformations d'image, l'image est affichée au centre du widget, tout en conservant le format d'origine de l'image. Puisque l'image est mise à l'échelle et traduite, une coordonnée d'un MouseEvent doit être remappée sur le système de coordonnées de l'image. Le programme ci-dessous a une classe "ScalingWidget", qui devrait être capable de faire ces transformations et devrait également être en mesure de remapper la coordonnée dans un mouseReleaseEvent de retour au système de coordonnées de l'image. Cela fonctionne parfaitement comme prévu quand je montre le widget en dehors d'une mise en page et d'une fenêtre principale, mais cela devient mal quand j'intègre le widget dans un plus grand GUI. Ensuite, les coordonnées après les avoir mises en correspondance avec les coordonnées de l'image affichent soudainement un décalage.PyQt mappant les coordonnées d'une presse de la souris au système de coordonnées d'une image

Le programme minimal ci-dessous peut être démarré avec ou sans le bogue en spécifiant le flag -b au démarrage du programme. L'option -n peut placer l'instance de ScalingWidget plus profondément et plus profondément dans un "gui", et plus elle sera intégrée dans les mises en page, plus le bogue sera visible. La chose stupide est, bien que le dessin indique que les transformations sont correctes, les coordonnées mappées (imprimées dans le titre de la fenêtre et la console) indiquent que les reconfigurer aux coordonnées de l'image est foutu lorsque le drapeau -b est présent. Donc, ma question est: Qu'est-ce que je fais de mal à remapper les coordonnées de la souris aux dimensions de l'image lorsque mon ScalingWidget est intégré dans une mise en page?

Je ne m'attends pas à ce que le remappage soit parfait au pixel près, mais tout aussi précis que l'utilisateur final peut positionner la souris. Il y a deux points x = 20, y = 20 et à x = 380 et y = 380 ceux-ci peuvent être utilisés comme point de référence.

Toute aide est la bienvenue!

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

Répondre

0

L'idée de base de _default_image_transform est correcte. L'erreur est à la fin de la fonction.

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 

La dernière ligne de la fonction _default_image_transform doit être:

self.matrix = painter.transform() 

Selon la documentation on doit seulement appeler le QPainter.deviceTransform() lorsque vous travaillez avec QT :: Handle qui est une poignée dépendant de la plate-forme. Comme je ne travaillais pas avec une poignée dépendant de la plateforme, je n'aurais pas dû l'appeler. Cela fonctionne quand je montre le widget, mais pas quand il est intégré dans une mise en page. La matrice deviceTransform est alors différente de la matrice QPainter.transform() normale. Voir aussi http://doc.qt.io/qt-4.8/qpainter.html#deviceTransform