简体   繁体   中英

How can I have a QLabel exactly the size of the QPixmap it is showing?

I need some advice concerning Layouts in QT/PyQt.

What I want to achieve is to have an image displayed, centered, scaled correctly, taking the most space it can while keeping its aspect ratio. That can easily be done using this:

class Demo(QWidget):
    def __init__(self) -> None:
        super().__init__()
        self.setWindowState(Qt.WindowMaximized)
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        self.button = QPushButton("Next image")
        self.label = QLabel()
        self.label.setStyleSheet("QLabel { background-color : red; }")
        self.label.setAlignment(Qt.AlignCenter)

        self.layout.addWidget(self.label)
        self.layout.addWidget(self.button)

        self.button.clicked.connect(self.showImage)
        self.show()


    def showImage(self):
        self.pixmap = QPixmap("image.jpg")
        scaled = self.pixmap.scaled(self.label.size(), Qt.KeepAspectRatio)
        self.label.setPixmap(scaled)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Demo()
    sys.exit(app.exec_())

The problem with this is, that the image will be displayed centered on the Label, with much "excess Label" around it see screenshot, the red stuff

在此输入图像描述

But I need the Label to be exactly the size of the image. I want to do some rubberbanding on the image to select parts of them (select boxes for later use in retinanet training) and to calculate the correct size of the boxes I need to mapToGlobal and mapFromGlobal. I can't do that from the Pixmap itself (since it is no widget) and when I do it from the Label, I get wrong values since it is larger than the actual image.

Now, I could use a GridLayout or a HBoxLayout with SpacerItems. The problem is the logic with that. The size of the Label (and the SpacerItems get determind when the UI loads. When I later dynamically add an image, it gets scaled to the size of the Label, even if it could be larger, if only the SpacerItems would "give way" do the Label. Result is, that instead of having "too much Label" left and right, I then have "too much label" top and bottom. I hope you get what I mean.

What I currently do is:

  1. Resize the window to screen resolution
  2. Add the (scaled) image
  3. resize (0,0)

The application then gets resized so that the Labe is exactly the size of the image (what I want) but it is an evil, hacky, flickering, crazy way to do it ;-)

EDIT: Thanks a lot for the answer, @eyllanesc :-) I tried that and it does what I want for the first picture. What I forgot to mention is, that when the first image is properly "rubberbanded", after clicking the "Next" button, another image is displayed. If this image is smaller than the actual Label (or landscape instead of portrait), the label shrinks. Lo and behold, after some images with different resolutions and orientation, the Label keeps shrinking and shrinking...

https://pastebin.com/ZMMw20Z7

https://giphy.com/gifs/xlou5RpQoX805fUymR

One possible solution is to change the horizontal policy of size to QSizePolicy::Maximum and center the widget in the layout:

from PyQt5 import QtCore, QtGui, QtWidgets

class Demo(QtWidgets.QWidget):
    def __init__(self) -> None:
        super().__init__()
        self.setWindowState(QtCore.Qt.WindowMaximized)
        layout = QtWidgets.QVBoxLayout(self)
        self.button = QtWidgets.QPushButton("Next image")
        self.label = QtWidgets.QLabel()
        self.label.setStyleSheet("QLabel { background-color : red; }")
        layout.addWidget(self.label)
        layout.addWidget(self.button)
        self.button.clicked.connect(self.showImage)
        self.show()

    def showImage(self):
        self.pixmap = QtGui.QPixmap("image.jpg")
        scaled = self.pixmap.scaled(self.label.size(), QtCore.Qt.KeepAspectRatio)
        self.label.setPixmap(scaled)
        sp = self.label.sizePolicy()
        sp.setHorizontalPolicy(QtWidgets.QSizePolicy.Maximum)
        self.label.setSizePolicy(sp)
        self.layout().setAlignment(self.label, QtCore.Qt.AlignCenter)

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    ex = Demo()
    sys.exit(app.exec_())

在此输入图像描述

Update:

The problem with the previous solution is that the size of the previous QLabel is used as a reference to establish the new size, instead it should be based on the maximum size it could have and for this I have implemented a custom class that tracks the size maximum:

from PyQt5 import QtCore, QtGui, QtWidgets
from itertools import cycle
from glob import glob

class Label(QtWidgets.QLabel):
    def resizeEvent(self, event):
        if not hasattr(self, 'maximum_size'):
            self.maximum_size = self.size()
        else:
            self.maximum_size = QtCore.QSize(
                max(self.maximum_size.width(), self.width()),
                max(self.maximum_size.height(), self.height()),
            )
        super(Label, self).resizeEvent(event)

    def setPixmap(self, pixmap):
        scaled = pixmap.scaled(self.maximum_size, QtCore.Qt.KeepAspectRatio)
        super(Label, self).setPixmap(scaled)

class Demo(QtWidgets.QWidget):
    def __init__(self) -> None:
        super().__init__()
        self.setWindowState(QtCore.Qt.WindowMaximized)
        layout = QtWidgets.QVBoxLayout(self)
        self.button = QtWidgets.QPushButton("Next image")
        self.label = Label()
        self.label.setStyleSheet("QLabel { background-color : red; }")
        layout.addWidget(self.label)
        layout.addWidget(self.button)
        self.button.clicked.connect(self.showImage)
        self.show() 
        self.images = cycle(glob("images/*"))

    def showImage(self):
        try:
            filename = next(self.images)
            self.label.setPixmap(QtGui.QPixmap(filename))
            sp = self.label.sizePolicy()
            sp.setHorizontalPolicy(QtWidgets.QSizePolicy.Maximum)
            self.label.setSizePolicy(sp)
            self.layout().setAlignment(self.label, QtCore.Qt.AlignCenter)
        except StopIteration:
            pass

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    ex = Demo()
    sys.exit(app.exec_())

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