Qt: зміна розміру QLabel, що містить QPixmap, зберігаючи співвідношення сторін


81

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

Як я можу змінити QSizePolicyі sizeHint()QLabel для зміни розміру QPixmap, зберігаючи співвідношення сторін оригінального QPixmap?

Я не можу змінити sizeHint()QLabel, встановлення minimumSize()нуля не допомагає. Встановлення hasScaledContents()на QLabel дозволяє зростати, але порушує пропорції ...

Підкласифікація QLabel справді допомогла, але це рішення додає занадто багато коду для простої проблеми ...

Будь-які розумні підказки, як це зробити, не підкласуючи?


Під динамічними змінами ви маєте на увазі дані пікселів або розміри?
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));

Цей код слід додати у два місця:

  • Коли pixmap буде оновлено
  • У resizeEventвіджеті, що містить мітку

так, це, в основному, було ядром, коли я підкласував QLabel. Але я думав, що цей випадок використання (показ зображень із довільним розміром у віджетах довільного розміру) буде досить поширеним, щоб щось подібне реалізувати за допомогою існуючого коду ...
marvin2k

AFAIK ця функція не надається за замовчуванням. Найелегантніший спосіб досягти бажаного - це підклас QLabel. В іншому випадку ви можете використовувати код моєї відповіді у слоті / функції, яка буде викликатися кожного разу, коли змінюється pixmap.
пнезіс

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

Ти правий. Я зробив припущення, що ви хочете зберегти найякіснішу версію pixmap і що ви викликаєте setPixmap перед зміною розміру / закріплення мітки. Щоб зменшити дублювання коду, я, мабуть, повинен поставити this->resize(width(), height());в кінці setPixmapфункції.
phyatt

Дякуємо, що поділились цим. Чи могли б ви отримати пропозиції щодо того, як я можу встановити "бажаний" розмір для QPixmap, щоб він не приймав максимальну роздільну здатність при першому запуску програми?
Julien M

Використовуйте макети та правила розтягування.
фіат

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 Так, це звучить краще - сміливо редагуйте відповідь.
Тімммм

5

Я спробував використати AspectRatioPixmapLabelклас phyatt's , але зазнав декількох проблем:

  • Іноді мій додаток потрапляв у нескінченний цикл подій зміни розміру. Я простежив це назад до виклику QLabel::setPixmap(...)всередині методу resizeEvent, оскільки QLabelнасправді виклики updateGeometryвсередині setPixmap, що може викликати події зміни розміру ...
  • heightForWidthздавалося, ігнорується вміщуючим віджетом ( 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)

0

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

widthІ heightможе бути встановлений на основі scrollarea.width()і scrollarea.height(). Таким чином, немає необхідності підкласувати QLabel.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.