简体   繁体   中英

get real size of QPixmap in Qlabel

Is there some simple way in PyQt5 to get real dimensions of the pixmap displayed in QLabel? I am trying to select part of the image with rubber band. But I can't find a way to limit the rubberband only to pixmap. The QLabel.pixmap().rect() returns dimensions of the whole QLabel not only the pixmap. The problem arises when the pixmap is scaled and there are stripes on the sides of the picture.

The Example image

Example image 2

I posted are quite self explanatory. I don't want the rubberband to be able to move out of the picture to the white stripes.

class ResizableRubberBand(QWidget):

    def __init__(self, parent=None):
        super(ResizableRubberBand, self).__init__(parent)

        self.aspect_ratio = None

        self.setWindowFlags(Qt.SubWindow)
        self.layout = QHBoxLayout(self)
        self.layout.setContentsMargins(0, 0, 0, 0)

        self.grip1 = QSizeGrip(self)
        self.grip2 = QSizeGrip(self)
        self.layout.addWidget(self.grip1, 0, Qt.AlignLeft | Qt.AlignTop)
        self.layout.addWidget(self.grip2, 0, Qt.AlignRight | Qt.AlignBottom)

        self.rubberband = QRubberBand(QRubberBand.Rectangle, self)
        self.rubberband.setStyle(QStyleFactory.create("Fusion"))
        self.rubberband.move(0, 0)
        self.rubberband.show()
        self.show()


class ResizablePixmap(QLabel):

    def __init__(self, bytes_image):

        QLabel.__init__(self)
        self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
        self.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
        self.setStyleSheet("background-color:#ffffff;")

        self.update_pixmap(bytes_image)

    def resizeEvent(self, event):

        if event:
            x = event.size().width()
            y = event.size().height()
        else:
            x = self.width()
            y = self.height()

        self.current_pixmap = self._bytes2pixmap(self.bytes_image_edit)
        self.setPixmap(self.current_pixmap.scaled(x, y, Qt.KeepAspectRatio))
        self.resize(x, y)

    def update_pixmap(self, bytes_image):

        self.bytes_image_edit = bytes_image

        self.current_pixmap = self._bytes2pixmap(bytes_image)
        self.setPixmap(self.current_pixmap)

        self.resizeEvent(None)

    @staticmethod
    def _bytes2pixmap(raw_image):

        image = QImage()
        image.loadFromData(raw_image)
        return QPixmap(image)

    @staticmethod
    def _pixmap2bytes(pixmap):

        byte_array = QByteArray()
        buffer = QBuffer(byte_array)
        buffer.open(QIODevice.WriteOnly)
        pixmap.save(buffer, 'PNG')
        return byte_array.data()

    @property
    def image_dims(self):
        return self.width(), self.height()

    def force_resize(self, qsize):
        self.resizeEvent(QResizeEvent(qsize, qsize))


class SelectablePixmap(ResizablePixmap):

    def __init__(self, bytes_image):

        super().__init__(bytes_image)

        self.currentQRubberBand = None
        self.move_rubber_band = False
        self.rubber_band_offset = None

    def cancel_selection(self):
        self.currentQRubberBand.hide()
        self.currentQRubberBand.deleteLater()
        self.currentQRubberBand = None
        self.selectionActive.emit(False)

    def mousePressEvent(self, eventQMouseEvent):

        if not self.currentQRubberBand:
            self.currentQRubberBand = ResizableRubberBand(self)
            self.selectionActive.emit(True)

        if self.currentQRubberBand.geometry().contains(eventQMouseEvent.pos()):
            self.move_rubber_band = True
            self.rubber_band_offset = (eventQMouseEvent.pos() -
                                       self.currentQRubberBand.pos())
        else:
            self.originQPoint = eventQMouseEvent.pos()
            if self.pixmap().rect().contains(self.originQPoint):
                self.currentQRubberBand.setGeometry(QRect(self.originQPoint,
                                                          QSize()))
                self.currentQRubberBand.show()

    def mouseMoveEvent(self, eventQMouseEvent):

        if self.move_rubber_band:
            pos = eventQMouseEvent.pos() - self.rubber_band_offset
            if self.pixmap().rect().contains(pos):
                self.currentQRubberBand.move(pos)
        else:
            rect = QRect(self.originQPoint, eventQMouseEvent.pos())
            self.currentQRubberBand.setGeometry(rect.normalized())

    def mouseReleaseEvent(self, eventQMouseEvent):

        if self.move_rubber_band:
            self.move_rubber_band = False

You could store width and height of the image (before you create the pixmap from bytes) into global variable and then use getter to access it from outside of class.

The "easy" answer to your question is that you can get the actual geometry of the QPixmap by moving its QRect . Since you're using center alignment, that's very simple:

pixmap_rect = self.pixmap.rect()
pixmap_rect.moveCenter(self.rect().center())

Unfortunately you can't just use that rectangle with your implementation, mostly because you are not really using a QRubberBand.
The concept of a child rubberband, using size grips for resizing, is clever, but has a lot of limitations.
While QSizeGrips make resizing easier, their behavior can't be easily "restricted": you'll probably end up trying to reimplement resize and resizeEvent (risking recursions), maybe with some tricky and convoluted mouse event checking. Also, you'll never be able to resize that "virtual" rubberband to a size smaller to the sum of the QSizeGrips' sizes, nor to a "negative" selection.
Also, in your code you never resize the actual QRubberBand geometry (but that can be done within the ResizableRubberBand.resizeEvent() ).

Finally, even if you haven't implemented the selection resizing after an image resizing, you would have a lot of issues if you did (mostly because of the aforementioned minimum size restrainings).


I think that a better solution is to use a simple QRubberBand and implement its interaction directly from the widget that uses it. This lets you have finer control over it, also allowing complete resize features (not only top left and bottom right corners).
I slightly modified your base class code, as you should avoid any resizing within a resizeEvent() (even if it didn't do anything in your case, since the size argument of resize() was the same) and did unnecessary calls to _bytes2pixmap .

class ResizablePixmap(QLabel):
    def __init__(self, bytes_image):
        QLabel.__init__(self)
        self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
        self.setAlignment(Qt.AlignCenter)
        self.setStyleSheet("background-color: #ffffff;")

        self.update_pixmap(bytes_image)

    def update_pixmap(self, bytes_image):
        self.bytes_image_edit = bytes_image
        self.current_pixmap = self._bytes2pixmap(bytes_image)

    def scale(self, fromResize=False):
        # use a single central method for scaling; there's no need to call it upon
        # creation and also resize() won't work anyway in a layout
        self.setPixmap(self.current_pixmap.scaled(self.width(), self.height(), 
            Qt.KeepAspectRatio, Qt.SmoothTransformation))

    def resizeEvent(self, event):
        super(ResizablePixmap, self).resizeEvent(event)
        self.scale(True)

    @staticmethod
    def _bytes2pixmap(raw_image):
        image = QImage()
        image.loadFromData(raw_image)
        return QPixmap(image)


class SelectablePixmap(ResizablePixmap):
    selectionActive = pyqtSignal(bool)

    def __init__(self, bytes_image):
        super().__init__(bytes_image)

        # activate mouse tracking to change cursor on rubberband hover
        self.setMouseTracking(True)
        self.currentQRubberBand = None
        self.rubber_band_offset = None
        self.moveDirection = 0

    def create_selection(self, pos):
        if self.currentQRubberBand:
            self.cancel_selection()
        self.currentQRubberBand = QRubberBand(QRubberBand.Rectangle, self)
        self.currentQRubberBand.setStyle(QStyleFactory.create("Fusion"))
        self.currentQRubberBand.setGeometry(pos.x(), pos.y(), 1, 1)
        self.currentQRubberBand.show()
        self.originQPoint = pos
        self.currentQRubberBand.installEventFilter(self)

    def cancel_selection(self):
        self.currentQRubberBand.hide()
        self.currentQRubberBand.deleteLater()
        self.currentQRubberBand = None
        self.originQPoint = None
        self.selectionActive.emit(False)

    def scale(self, fromResize=False):
        if fromResize and self.currentQRubberBand:
            # keep data for rubber resizing, before scaling
            oldPixmapRect = self.pixmap().rect()
            oldOrigin = self.currentQRubberBand.pos() - self.pixmapRect.topLeft()
        super(SelectablePixmap, self).scale()

        # assuming that you always align the image in the center, get the current
        # pixmap rect and move the rectangle center to the current geometry
        self.pixmapRect = self.pixmap().rect()
        self.pixmapRect.moveCenter(self.rect().center())
        if fromResize and self.currentQRubberBand:
            # find the new size ratio based on the previous
            xRatio = self.pixmapRect.width() / oldPixmapRect.width()
            yRatio = self.pixmapRect.height() / oldPixmapRect.height()
            # create a new geometry using 0-rounding for improved accuracy
            self.currentQRubberBand.setGeometry(
                round(oldOrigin.x() * xRatio, 0) + self.pixmapRect.x(), 
                round(oldOrigin.y() * yRatio + self.pixmapRect.y(), 0), 
                round(self.currentQRubberBand.width() * xRatio, 0), 
                round(self.currentQRubberBand.height() * yRatio, 0))

    def updateMargins(self):
        # whenever the rubber rectangle geometry changes, create virtual
        # rectangles for corners and sides to ease up mouse event checking
        rect = self.currentQRubberBand.geometry()
        self.rubberTopLeft = QRect(rect.topLeft(), QSize(8, 8))
        self.rubberTopRight = QRect(rect.topRight(), QSize(-8, 8)).normalized()
        self.rubberBottomRight = QRect(rect.bottomRight(), QSize(-8, -8)).normalized()
        self.rubberBottomLeft = QRect(rect.bottomLeft(), QSize(8, -8)).normalized()
        self.rubberLeft = QRect(self.rubberTopLeft.bottomLeft(), self.rubberBottomLeft.topRight())
        self.rubberTop = QRect(self.rubberTopLeft.topRight(), self.rubberTopRight.bottomLeft())
        self.rubberRight = QRect(self.rubberTopRight.bottomLeft(), self.rubberBottomRight.topRight())
        self.rubberBottom = QRect(self.rubberBottomLeft.topRight(), self.rubberBottomRight.bottomLeft())
        self.rubberInnerRect = QRect(self.rubberTop.bottomLeft(), self.rubberBottom.topRight())

    def eventFilter(self, source, event):
        if event.type() in (QEvent.Resize, QEvent.Move):
            self.updateMargins()
        return super(SelectablePixmap, self).eventFilter(source, event)

    def mousePressEvent(self, event):
        pos = event.pos()
        if not self.currentQRubberBand or not pos in self.currentQRubberBand.geometry():
            if pos not in self.pixmapRect:
                self.originQPoint = None
                return
            self.create_selection(pos)
        elif pos in self.rubberTopLeft:
            self.originQPoint = self.currentQRubberBand.geometry().bottomRight()
        elif pos in self.rubberTopRight:
            self.originQPoint = self.currentQRubberBand.geometry().bottomLeft()
        elif pos in self.rubberBottomRight:
            self.originQPoint = self.currentQRubberBand.geometry().topLeft()
        elif pos in self.rubberBottomLeft:
            self.originQPoint = self.currentQRubberBand.geometry().topRight()
        elif pos in self.rubberTop:
            self.originQPoint = self.currentQRubberBand.geometry().bottomLeft()
            self.moveDirection = Qt.Vertical
        elif pos in self.rubberBottom:
            self.originQPoint = self.currentQRubberBand.geometry().topLeft()
            self.moveDirection = Qt.Vertical
        elif pos in self.rubberLeft:
            self.originQPoint = self.currentQRubberBand.geometry().topRight()
            self.moveDirection = Qt.Horizontal
        elif pos in self.rubberRight:
            self.originQPoint = self.currentQRubberBand.geometry().topLeft()
            self.moveDirection = Qt.Horizontal
        else:
            self.rubber_band_offset = pos - self.currentQRubberBand.pos()

    def mouseMoveEvent(self, event):
        pos = event.pos()
        if event.buttons() == Qt.NoButton and self.currentQRubberBand:
            if pos in self.rubberTopLeft or pos in self.rubberBottomRight:
                self.setCursor(Qt.SizeFDiagCursor)
            elif pos in self.rubberTopRight or pos in self.rubberBottomLeft:
                self.setCursor(Qt.SizeBDiagCursor)
            elif pos in self.rubberLeft or pos in self.rubberRight:
                self.setCursor(Qt.SizeHorCursor)
            elif pos in self.rubberTop or pos in self.rubberBottom:
                self.setCursor(Qt.SizeVerCursor)
            elif pos in self.rubberInnerRect:
                self.setCursor(Qt.SizeAllCursor)
            else:
                self.unsetCursor()
        elif event.buttons():
            if self.rubber_band_offset:
                target = pos - self.rubber_band_offset
                rect = QRect(target, self.currentQRubberBand.size())
                # limit positioning of the selection to the image rectangle
                if rect.x() < self.pixmapRect.x():
                    rect.moveLeft(self.pixmapRect.x())
                elif rect.right() > self.pixmapRect.right():
                    rect.moveRight(self.pixmapRect.right())
                if rect.y() < self.pixmapRect.y():
                    rect.moveTop(self.pixmapRect.y())
                elif rect.bottom() > self.pixmapRect.bottom():
                    rect.moveBottom(self.pixmapRect.bottom())
                self.currentQRubberBand.setGeometry(rect)
            elif self.originQPoint:
                if self.moveDirection == Qt.Vertical:
                    # keep the X fixed to the current right, so that only the
                    # vertical position is changed
                    pos.setX(self.currentQRubberBand.geometry().right())
                else:
                    # limit the X to the pixmapRect extent
                    if pos.x() < self.pixmapRect.x():
                        pos.setX(self.pixmapRect.x())
                    elif pos.x() > self.pixmapRect.right():
                        pos.setX(self.pixmapRect.right())
                if self.moveDirection == Qt.Horizontal:
                    # same as before, but for the Y position
                    pos.setY(self.currentQRubberBand.geometry().bottom())
                else:
                    # limit the Y to the pixmapRect extent
                    if pos.y() < self.pixmapRect.y():
                        pos.setY(self.pixmapRect.y())
                    elif pos.y() > self.pixmapRect.bottom():
                        pos.setY(self.pixmapRect.bottom())
                rect = QRect(self.originQPoint, pos)
                self.currentQRubberBand.setGeometry(rect.normalized())

    def mouseReleaseEvent(self, event):
        self.rubber_band_offset = None
        self.originQPoint = None
        self.moveDirection = 0

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM