简体   繁体   English

在 Qlabel 中获取 QPixmap 的实际大小

[英]get real size of QPixmap in Qlabel

Is there some simple way in PyQt5 to get real dimensions of the pixmap displayed in QLabel? PyQt5 中是否有一些简单的方法可以获取 QLabel 中显示的像素图的真实尺寸? I am trying to select part of the image with rubber band.我正在尝试用橡皮筋 select 图像的一部分。 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. QLabel.pixmap().rect() 返回整个 QLabel 的维度,而不仅仅是像素图。 The problem arises when the pixmap is scaled and there are stripes on the sides of the picture.当像素图被缩放并且图片的两侧有条纹时,就会出现问题。

The Example image示例图像

Example image 2示例图 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.您可以将图像的宽度和高度(在从字节创建像素图之前)存储到全局变量中,然后使用 getter 从 class 外部访问它。

The "easy" answer to your question is that you can get the actual geometry of the QPixmap by moving its QRect .您的问题的“简单”答案是您可以通过移动 QPixmap 的QRect来获得其实际几何形状。 Since you're using center alignment, that's very simple:由于您使用的是中心 alignment,因此非常简单:

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.不幸的是,你不能只在你的实现中使用那个矩形,主要是因为你并没有真正使用 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.虽然 QSizeGrips 使调整大小变得更容易,但它们的行为不能轻易“限制”:您可能最终会尝试重新实现resizeresizeEvent (冒着递归的风险),可能会进行一些棘手且复杂的鼠标事件检查。 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.此外,您永远无法将“虚拟”橡皮筋的大小调整为小于 QSizeGrips 大小的总和,也无法调整为“负”选择。
Also, in your code you never resize the actual QRubberBand geometry (but that can be done within the ResizableRubberBand.resizeEvent() ).此外,在您的代码中,您永远不会调整实际的 QRubberBand 几何形状(但这可以在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.我认为更好的解决方案是使用简单的 QRubberBand 并直接从使用它的小部件实现其交互。 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 代码,因为您应该避免在resizeEvent()中进行任何调整大小(即使在您的情况下它没有做任何事情,因为resize()的大小参数是相同的)并对_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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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