Как я могу иметь QLabel, точно соответствующий размеру отображаемой QPixmap?

#python #pyqt #qlabel #qlayout

#python #pyqt #qlabel #qlayout

Вопрос:

Мне нужен совет относительно макетов в QT / PyQt.

Чего я хочу добиться, так это чтобы изображение отображалось правильно, центрировалось, масштабировалось, занимая как можно больше места при сохранении соотношения сторон. Это можно легко сделать, используя это:

 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_())
  

Проблема в том, что изображение будет отображаться по центру метки с большим количеством «лишних меток» вокруг него, см. скриншот, красный материал

введите описание изображения здесь

Но мне нужно, чтобы метка была точно размером с изображение. Я хочу выполнить некоторую резиновую разметку на изображении, чтобы выделить их части (выделить поля для последующего использования в обучении retinanet) и рассчитать правильный размер полей, которые мне нужны для mapToGlobal и mapFromGlobal. Я не могу сделать это из самой пиксельной карты (поскольку это не виджет), и когда я делаю это из метки, я получаю неправильные значения, поскольку она больше фактического изображения.

Теперь я мог бы использовать GridLayout или HBoxLayout с разделительными элементами. Проблема в логике этого. Размер метки (и элементов SpacerItems) определяется при загрузке пользовательского интерфейса. Когда я позже динамически добавляю изображение, оно масштабируется до размера метки, даже если оно могло бы быть больше, если бы только элементы пространства «уступали» место для метки. В результате вместо того, чтобы иметь «слишком много меток» слева и справа, у меня получается «слишком много меток» сверху и снизу. Я надеюсь, вы понимаете, что я имею в виду.

Что я в настоящее время делаю, так это:

  1. Измените размер окна в соответствии с разрешением экрана
  2. Добавьте (масштабированное) изображение
  3. изменить размер (0,0)

Затем приложение изменяется таким образом, чтобы Labe в точности соответствовал размеру изображения (то, что я хочу), но это злой, хакерский, мерцающий, сумасшедший способ сделать это 😉

РЕДАКТИРОВАТЬ: Большое спасибо за ответ, @eyllanesc 🙂 Я попробовал это, и это делает то, что я хочу для первого изображения. Что я забыл упомянуть, так это то, что когда первое изображение правильно «обрезано», после нажатия кнопки «Далее» отображается другое изображение. Если это изображение меньше, чем фактическая метка (или альбомная, а не книжная), метка сжимается. О чудо, после нескольких изображений с разными разрешениями и ориентацией метка продолжает уменьшаться и уменьшаться…

https://pastebin.com/ZMMw20Z7

https://giphy.com/gifs/xlou5RpQoX805fUymR

Ответ №1:

Одно из возможных решений — изменить политику горизонтального размера на QSizePolicy::Maximum и центрировать виджет в макете:

 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_())
  

введите описание изображения здесь

Обновить:

Проблема с предыдущим решением заключается в том, что размер предыдущей QLabel используется в качестве ссылки для определения нового размера, вместо этого он должен основываться на максимальном размере, который он мог бы иметь, и для этого я реализовал пользовательский класс, который отслеживает максимальный размер:

 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_())
  

Комментарии:

1. Большое спасибо за этот ответ 🙂 Я попробовал, и он делает то, что я хочу, для первого изображения . Что я забыл упомянуть, так это то, что когда первое изображение правильно «обрезано», после нажатия кнопки «Далее» отображается другое изображение. Если это изображение меньше, чем фактическая метка (или альбомная, а не книжная), метка сжимается. О чудо, после нескольких изображений с разными разрешениями и ориентацией метка продолжает уменьшаться и уменьшаться…

2.Это было сделано точно с вашим кодом сверху, одни и те же два изображения снова и снова. pastebin.com/ZMMw20Z7 giphy.com/gifs/xlou5RpQoX805fUymR

3. Я хотел бы, чтобы приложение работало в полноэкранном режиме, а изображения масштабировались до максимально возможного размера с сохранением соотношения сторон. Я считаю, что таким образом, резиновая привязка проще всего… Его не нужно увеличивать, (исправлено) масштабирование до разрешения экрана тоже было бы неплохо

4. Работает как шарм! Тысяча благодарностей 🙂