Qt: изменение размера QLabel, содержащего QPixmap, с сохранением его соотношения сторон

81

Я использую QLabel для отображения пользователю содержимого более крупного, динамически изменяющегося QPixmap. Было бы неплохо сделать эту этикетку меньше / больше в зависимости от доступного места. Размер экрана не всегда такой большой, как у QPixmap.

Как я могу изменить QSizePolicyи sizeHint()QLabel, чтобы изменить размер QPixmap, сохраняя соотношение сторон исходного QPixmap?

Я не могу изменить sizeHint()QLabel, установка minimumSize()нуля не помогает. Настройка hasScaledContents()в QLabel позволяет расти, но нарушает соотношение сторон ...

Создание подкласса QLabel действительно помогло, но это решение добавляет слишком много кода для простой проблемы ...

Есть какие-нибудь умные подсказки, как этого добиться без подкласса?

marvin2k
источник
Под динамическим изменением вы подразумеваете данные в пикселях или размеры?
r_ahlskog
Я имею ввиду габариты QLabelв текущем макете. Он QPixmapдолжен сохранять свой размер, содержание и размер. Кроме того, было бы хорошо, если бы изменение размера (сжатие в реальности) происходило «автоматически», чтобы заполнить доступное пространство - до размера оригинала QPixmap. Все это было сделано с помощью подклассов ...
marvin2k

Ответы:

98

Чтобы изменить размер метки, вы можете выбрать подходящую политику размера для метки, например, расширение или минимальное расширение.

Вы можете масштабировать растровое изображение, сохраняя его соотношение сторон при каждом изменении:

QPixmap p; // load pixmap
// get label dimensions
int w = label->width();
int h = label->height();

// set a scaled pixmap to a w x h window keeping its aspect ratio 
label->setPixmap(p.scaled(w,h,Qt::KeepAspectRatio));

Есть два места, куда вы должны добавить этот код:

  • Когда растровое изображение обновлено
  • В resizeEventвиджете, содержащем метку
Пнезис
источник
хм, да, когда я разделил подклассы, это было в основном ядро QLabel. Но я думал, что этот вариант использования (отображение изображений произвольного размера в виджетах произвольного размера) будет достаточно распространенным, чтобы нечто подобное можно было реализовать с помощью существующего кода ...
marvin2k
AFAIK эта функция не предоставляется по умолчанию. Самый элегантный способ добиться желаемого - создать подкласс QLabel. В противном случае вы можете использовать код моего ответа в слоте / функции, которая будет вызываться каждый раз при изменении растрового изображения.
pnezis
1
так как я хочу, QLabelчтобы он автоматически расширялся в зависимости от изменения пользователем размера QMainWindowи доступного пространства, я не могу использовать решение сигнал / слот - я не могу смоделировать политику расширения таким образом.
marvin2k
21
Чтобы иметь возможность также уменьшить масштаб, вам нужно добавить этот звонок:label->setMinimumSize(1, 1)
Питер-Ян Бушарт,
1
Это не очень полезно, если я хочу сохранить соотношение сторон, даже когда пользователь изменяет размер метки.
Томаш Зато - Восстановите Монику
33

Я отполировал этот недостающий подкласс QLabel. Это потрясающе и хорошо работает.

аспектratiopixmaplabel.h

#ifndef ASPECTRATIOPIXMAPLABEL_H
#define ASPECTRATIOPIXMAPLABEL_H

#include <QLabel>
#include <QPixmap>
#include <QResizeEvent>

class AspectRatioPixmapLabel : public QLabel
{
    Q_OBJECT
public:
    explicit AspectRatioPixmapLabel(QWidget *parent = 0);
    virtual int heightForWidth( int width ) const;
    virtual QSize sizeHint() const;
    QPixmap scaledPixmap() const;
public slots:
    void setPixmap ( const QPixmap & );
    void resizeEvent(QResizeEvent *);
private:
    QPixmap pix;
};

#endif // ASPECTRATIOPIXMAPLABEL_H

аспектratiopixmaplabel.cpp

#include "aspectratiopixmaplabel.h"
//#include <QDebug>

AspectRatioPixmapLabel::AspectRatioPixmapLabel(QWidget *parent) :
    QLabel(parent)
{
    this->setMinimumSize(1,1);
    setScaledContents(false);
}

void AspectRatioPixmapLabel::setPixmap ( const QPixmap & p)
{
    pix = p;
    QLabel::setPixmap(scaledPixmap());
}

int AspectRatioPixmapLabel::heightForWidth( int width ) const
{
    return pix.isNull() ? this->height() : ((qreal)pix.height()*width)/pix.width();
}

QSize AspectRatioPixmapLabel::sizeHint() const
{
    int w = this->width();
    return QSize( w, heightForWidth(w) );
}

QPixmap AspectRatioPixmapLabel::scaledPixmap() const
{
    return pix.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
}

void AspectRatioPixmapLabel::resizeEvent(QResizeEvent * e)
{
    if(!pix.isNull())
        QLabel::setPixmap(scaledPixmap());
}

Надеюсь, это поможет! (Обновлено resizeEvent, согласно ответу @dmzl)

фьятт
источник
1
Спасибо, отлично работает. Еще бы QLabel::setPixmap(pix.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));по setPixmap()методу добавил .
Hyndrix
Ты прав. Я сделал предположение, что вы хотите сохранить версию растрового изображения самого высокого качества и что вы вызываете setPixmap перед изменением размера / привязкой метки. Чтобы уменьшить дублирование кода, мне, вероятно, следует поставить функцию в this->resize(width(), height());конце setPixmap.
phyatt
Спасибо, что поделились этим. Есть ли у вас какие-либо предложения о том, как я могу установить «предпочтительный» размер для QPixmap, чтобы он не принимал максимальное разрешение при первом запуске приложения?
Julien M
Используйте макеты и правила растяжки.
phyatt
3
Отличный ответ! Для тех, кому нужно работать на экранах с высоким разрешением, просто измените scaledPixmap () на выполнение: auto scaled = pix.scaled(this->size() * devicePixelRatioF(), Qt::KeepAspectRatio, Qt::SmoothTransformation); scaled.setDevicePixelRatio(devicePixelRatioF()); return scaled;Это также работает на экранах с нормальным масштабированием.
Саул
18

Я просто использую, contentsMarginчтобы поправить соотношение сторон.

#pragma once

#include <QLabel>

class AspectRatioLabel : public QLabel
{
public:
    explicit AspectRatioLabel(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
    ~AspectRatioLabel();

public slots:
    void setPixmap(const QPixmap& pm);

protected:
    void resizeEvent(QResizeEvent* event) override;

private:
    void updateMargins();

    int pixmapWidth = 0;
    int pixmapHeight = 0;
};
#include "AspectRatioLabel.h"

AspectRatioLabel::AspectRatioLabel(QWidget* parent, Qt::WindowFlags f) : QLabel(parent, f)
{
}

AspectRatioLabel::~AspectRatioLabel()
{
}

void AspectRatioLabel::setPixmap(const QPixmap& pm)
{
    pixmapWidth = pm.width();
    pixmapHeight = pm.height();

    updateMargins();
    QLabel::setPixmap(pm);
}

void AspectRatioLabel::resizeEvent(QResizeEvent* event)
{
    updateMargins();
    QLabel::resizeEvent(event);
}

void AspectRatioLabel::updateMargins()
{
    if (pixmapWidth <= 0 || pixmapHeight <= 0)
        return;

    int w = this->width();
    int h = this->height();

    if (w <= 0 || h <= 0)
        return;

    if (w * pixmapHeight > h * pixmapWidth)
    {
        int m = (w - (pixmapWidth * h / pixmapHeight)) / 2;
        setContentsMargins(m, 0, m, 0);
    }
    else
    {
        int m = (h - (pixmapHeight * w / pixmapWidth)) / 2;
        setContentsMargins(0, m, 0, m);
    }
}

У меня пока работает отлично. Пожалуйста.

Тимммм
источник
4
Просто использовал это, и это работает как шарм! Кроме того, довольно умное использование менеджера по расположению. Должен быть принятым ответом, поскольку у всех остальных есть недостатки в угловых случаях.
thokra
2
Хотя этот ответ не является интуитивно понятным, он решает принципиально другой вопрос: «Сколько внутренних отступов мы должны добавить между меткой, размер которой уже хорошо известен, и растровым изображением, содержащимся в этой метке, чтобы сохранить соотношение сторон этого растрового изображения? " Каждый второй ответ решает исходный вопрос: «До какого размера мы должны изменить размер метки, содержащей растровое изображение, чтобы сохранить соотношение сторон этого растрового изображения?» Этот ответ требует, чтобы размер метки был каким-то образом предопределен (например, с помощью политики фиксированного размера), что нежелательно или даже невозможно во многих случаях использования.
Сесил Карри
1
Это лучший вариант для дисплеев HiResolution (также известного как «сетчатка») - это намного лучше, чем уменьшение масштаба QPixmap.
jvb
Может быть, я слишком сосредоточен на том, чтобы код выражал высокоуровневое значение ради удобства обслуживания, но разве не имеет смысла использовать QSizeвместо ...Widthи ...Height? По крайней мере, это сделало бы ваши чеки на досрочное возвращение простой QSize::isEmptyзадачей. QPixmapи у QWidgetобоих есть sizeметоды для получения ширины и высоты как QSize.
ssokolow
@ssokolow Да, звучит лучше - не стесняйтесь редактировать ответ.
Timmmm
5

Я пробовал использовать AspectRatioPixmapLabelкласс phyatt , но столкнулся с несколькими проблемами:

  • Иногда мое приложение входило в бесконечный цикл событий изменения размера. Я проследил это до вызова QLabel::setPixmap(...)внутри метода resizeEvent, потому что на QLabelсамом деле вызывает updateGeometryвнутри setPixmap, что может запускать события изменения размера ...
  • heightForWidthказалось, игнорируется содержащим виджетом (a QScrollAreaв моем случае), пока я не начал устанавливать политику размера для метки, явно вызываяpolicy.setHeightForWidth(true)
  • Я хочу, чтобы размер метки никогда не превышал исходный размер растрового изображения
  • QLabelреализация minimumSizeHint()делает некоторую магию для меток, содержащих текст, но всегда сбрасывает политику размера на значение по умолчанию, поэтому мне пришлось перезаписать ее

Тем не менее, вот мое решение. Я обнаружил, что могу просто использовать setScaledContents(true)и позволить QLabelизменять размер. Конечно, это зависит от содержащегося в нем виджета / макета, поддерживающего heightForWidth.

аспектratiopixmaplabel.h

#ifndef ASPECTRATIOPIXMAPLABEL_H
#define ASPECTRATIOPIXMAPLABEL_H

#include <QLabel>
#include <QPixmap>

class AspectRatioPixmapLabel : public QLabel
{
    Q_OBJECT
public:
    explicit AspectRatioPixmapLabel(const QPixmap &pixmap, QWidget *parent = 0);
    virtual int heightForWidth(int width) const;
    virtual bool hasHeightForWidth() { return true; }
    virtual QSize sizeHint() const { return pixmap()->size(); }
    virtual QSize minimumSizeHint() const { return QSize(0, 0); }
};

#endif // ASPECTRATIOPIXMAPLABEL_H

аспектratiopixmaplabel.cpp

#include "aspectratiopixmaplabel.h"

AspectRatioPixmapLabel::AspectRatioPixmapLabel(const QPixmap &pixmap, QWidget *parent) :
    QLabel(parent)
{
    QLabel::setPixmap(pixmap);
    setScaledContents(true);
    QSizePolicy policy(QSizePolicy::Maximum, QSizePolicy::Maximum);
    policy.setHeightForWidth(true);
    this->setSizePolicy(policy);
}

int AspectRatioPixmapLabel::heightForWidth(int width) const
{
    if (width > pixmap()->width()) {
        return pixmap()->height();
    } else {
        return ((qreal)pixmap()->height()*width)/pixmap()->width();
    }
}
Александр Шлютер
источник
Хотя heightForWidthэтот ответ предпочтительнее для крайних случаев, когда родительский виджет и / или макет, содержащий эту метку, соблюдает свойство, в общем случае, когда родительский виджет и / или макет, содержащий эту метку, не соблюдают свойство, этот ответ не выполняется heightForWidth. Что прискорбно, так как этот ответ иначе предпочтительнее phyatt «S давний ответ .
Сесил Карри,
3

Адаптировано из Timmmm к PYQT5

from PyQt5.QtGui import QPixmap
from PyQt5.QtGui import QResizeEvent
from PyQt5.QtWidgets import QLabel


class Label(QLabel):

    def __init__(self):
        super(Label, self).__init__()
        self.pixmap_width: int = 1
        self.pixmapHeight: int = 1

    def setPixmap(self, pm: QPixmap) -> None:
        self.pixmap_width = pm.width()
        self.pixmapHeight = pm.height()

        self.updateMargins()
        super(Label, self).setPixmap(pm)

    def resizeEvent(self, a0: QResizeEvent) -> None:
        self.updateMargins()
        super(Label, self).resizeEvent(a0)

    def updateMargins(self):
        if self.pixmap() is None:
            return
        pixmapWidth = self.pixmap().width()
        pixmapHeight = self.pixmap().height()
        if pixmapWidth <= 0 or pixmapHeight <= 0:
            return
        w, h = self.width(), self.height()
        if w <= 0 or h <= 0:
            return

        if w * pixmapHeight > h * pixmapWidth:
            m = int((w - (pixmapWidth * h / pixmapHeight)) / 2)
            self.setContentsMargins(m, 0, m, 0)
        else:
            m = int((h - (pixmapHeight * w / pixmapWidth)) / 2)
            self.setContentsMargins(0, m, 0, m)
kblst
источник
0

В документации Qt есть пример средства просмотра изображений, который демонстрирует обработку изменения размера изображений внутри файла QLabel. Основная идея состоит в том, чтобы использовать QScrollAreaв качестве контейнера для QLabelи, если необходимо, использовать label.setScaledContents(bool)и scrollarea.setWidgetResizable(bool)заполнить доступное пространство и / или обеспечить возможность изменения размера QLabel внутри. Кроме того, чтобы изменить размер QLabel с учетом соотношения сторон, используйте:

widthИ heightможет быть установлен на основе scrollarea.width()и scrollarea.height(). Таким образом, нет необходимости создавать подклассы QLabel.

Омид
источник