Qt: zmiana rozmiaru etykiety QLabel zawierającej QPixmap z zachowaniem proporcji

81

Używam QLabel do wyświetlania użytkownikowi zawartości większej, dynamicznie zmieniającej się mapy QPixmap. Byłoby miło, gdyby ta etykieta była mniejsza / większa w zależności od dostępnego miejsca. Rozmiar ekranu nie zawsze jest tak duży jak QPixmap.

Jak mogę zmodyfikować QSizePolicyi sizeHint()etykiety QLabel, aby zmienić rozmiar mapy QPixmap, zachowując jednocześnie proporcje oryginalnej QPixmap?

Nie mogę modyfikować sizeHint()QLabel, ustawienie na minimumSize()zero nie pomaga. Ustawienie hasScaledContents()na QLabel pozwala na rozwój, ale psuje proporcje ...

Tworzenie podklas QLabel pomogło, ale to rozwiązanie dodaje zbyt dużo kodu dla prostego problemu ...

Jakieś sprytne wskazówki, jak to osiągnąć bez tworzenia podklas?

marvin2k
źródło
Czy przez dynamiczną zmianę masz na myśli dane w pikselach czy wymiary?
r_ahlskog
Mam na myśli wymiary QLabelw aktualnym układzie. QPixmapPowinny zachować swój rozmiar, treść i wymiar. Byłoby również miło, gdyby zmiana rozmiaru (w rzeczywistości zmniejszanie się) następowała „automagicznie”, aby wypełnić dostępne miejsce - do rozmiaru oryginału QPixmap. Wszystko to zostało zrobione przez podklasy ...
marvin2k

Odpowiedzi:

98

Aby zmienić rozmiar etykiety, możesz wybrać odpowiednią zasadę rozmiaru dla etykiety, taką jak rozwijanie lub minimalne rozwinięcie.

Możesz skalować piksmapę, zachowując jej proporcje przy każdej zmianie:

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));

Są dwa miejsca, w których powinieneś dodać ten kod:

  • Kiedy mapa pikseli jest aktualizowana
  • W resizeEventwidgecie zawierającym etykietę
pnezis
źródło
hm tak, to był w zasadzie rdzeń, kiedy podklasowałem QLabel. Ale pomyślałem, że ten przypadek użycia (wyświetlanie obrazów o dowolnym rozmiarze w widżetach o dowolnym rozmiarze) byłby na tyle powszechny, że miałby coś podobnego do zaimplementowania za pomocą istniejącego kodu ...
marvin2k
AFAIK ta funkcja nie jest dostępna domyślnie. Najbardziej eleganckim sposobem osiągnięcia tego, co chcesz, jest podklasa QLabel. W przeciwnym razie możesz użyć kodu mojej odpowiedzi w slocie / funkcji, która będzie wywoływana za każdym razem, gdy mapa pikseli się zmieni.
pnezis
1
ponieważ chcę QLabelautomagicznie rozszerzać się w oparciu o zmianę rozmiaru użytkowników QMainWindowi dostępnej przestrzeni, nie mogę użyć rozwiązania sygnału / gniazda - nie mogę w ten sposób modelować rozszerzającej się polityki.
marvin2k
21
Aby móc również zmniejszyć skalę, musisz dodać to wezwanie:label->setMinimumSize(1, 1)
Pieter-Jan Busschaert
1
Nie jest to zbyt przydatne, jeśli chcę zachować współczynnik proporcji, nawet gdy użytkownik zmienia rozmiar etykiety.
Tomáš Zato - Przywróć Monikę
33

Dopracowałem tę brakującą podklasę QLabel. Jest niesamowity i działa dobrze.

aspektratiopixmaplabel.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

aspektratiopixmaplabel.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());
}

Mam nadzieję, że to pomoże! (Zaktualizowano resizeEvent, zgodnie z odpowiedzią @ dmzl)

phyatt
źródło
1
Dzięki, działa świetnie. Dodałbym również QLabel::setPixmap(pix.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));do setPixmap()metody.
Hyndrix,
Masz rację. Zrobiłem założenie, że chcesz przechowywać najwyższej jakości wersję mapy pikselowej i że przed zmianą rozmiaru / zakotwiczeniem etykiety wywołujesz setPixmap. Aby zmniejszyć powielanie kodu, prawdopodobnie powinienem umieścić this->resize(width(), height());na końcu setPixmapfunkcji.
phyatt
Dzięki za udostępnienie tego. Czy masz jakieś sugestie, jak ustawić „preferowany” rozmiar QPixmap, tak aby nie przyjmował maksymalnej rozdzielczości przy pierwszym uruchomieniu aplikacji?
Julien M
Użyj układów i reguł rozciągania.
phyatt
3
Świetna odpowiedź! Dla każdego, kto chce pracować na ekranach o wysokiej rozdzielczości, po prostu zmień skaledPixmap (), aby zrobić: auto scaled = pix.scaled(this->size() * devicePixelRatioF(), Qt::KeepAspectRatio, Qt::SmoothTransformation); scaled.setDevicePixelRatio(devicePixelRatioF()); return scaled;Działa to również na ekranach o normalnej skali.
Saul
18

Po prostu używam, contentsMarginaby naprawić współczynnik proporcji.

#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);
    }
}

Jak na razie działa idealnie dla mnie. Nie ma za co.

Timmmm
źródło
4
Właśnie tego użyłem i działa jak urok! Poza tym całkiem sprytne użycie menedżera układu. Powinna być akceptowana odpowiedź, ponieważ wszyscy inni mają wady w narożnych przypadkach.
thokra
2
Chociaż nieintuicyjnie sprytna odpowiedź rozwiązuje zasadniczo inne pytanie: „Ile wewnętrznego wypełnienia powinniśmy dodać między etykietą, której rozmiar jest już dobrze znany, a piksmapą zawartą na tej etykiecie, aby zachować proporcje tej piksmapy? " Każda inna odpowiedź rozwiązuje pierwotne pytanie: „Do jakiego rozmiaru powinniśmy zmienić rozmiar etykiety zawierającej piksmapę, aby zachować proporcje tej piksmapy?” Ta odpowiedź wymaga, aby rozmiar etykiety był w jakiś sposób z góry określony (np. Za pomocą polityki stałego rozmiaru), co jest niepożądane lub nawet niewykonalne w wielu przypadkach użycia.
Cecil Curry,
1
To jest droga do wyświetlaczy HiResolution (inaczej „siatkówka”) - jest o wiele lepsza niż zmniejszanie QPixmap.
jvb,
Może jestem trochę zbyt skoncentrowany na tworzeniu kodu wyrażającego znaczenie wysokiego poziomu ze względu na łatwość utrzymania, ale czy nie miałoby sensu używać QSizezamiast ...Widthi ...Height? Jeśli nic innego, to sprawiłoby, że twoje przedterminowe sprawdzenia byłyby prostym QSize::isEmptywezwaniem. QPixmapi QWidgetobie mają sizemetody pobierania szerokości i wysokości jako pliku QSize.
ssokolow 10.11.19
@ssokolow Tak, to brzmi lepiej - nie krępuj się edytować odpowiedzi.
Timmmm,
5

Próbowałem użyć AspectRatioPixmapLabelklasy Phyatta , ale napotkałem kilka problemów:

  • Czasami moja aplikacja wchodziła w nieskończoną pętlę zdarzeń zmiany rozmiaru. Prześledziłem to z powrotem do wywołania QLabel::setPixmap(...)wewnątrz metody resizeEvent, ponieważ w QLabelrzeczywistości wywołania updateGeometrywewnątrz setPixmap, co może wywołać zdarzenia zmiany rozmiaru ...
  • heightForWidthwydawało się być ignorowane przez widżet zawierający (a QScrollAreaw moim przypadku), dopóki nie zacząłem ustawiać polityki rozmiaru dla etykiety, wyraźnie wywołującpolicy.setHeightForWidth(true)
  • Chcę, aby etykieta nigdy nie rozrosła się bardziej niż oryginalny rozmiar piksmapy
  • QLabelImplementacja minimumSizeHint()robi trochę magii dla etykiet zawierających tekst, ale zawsze resetuje politykę rozmiaru do domyślnej, więc musiałem ją nadpisać

To powiedziawszy, oto moje rozwiązanie. Odkryłem, że mogę po prostu użyć setScaledContents(true)i pozwolić QLabelsobie na zmianę rozmiaru. Oczywiście zależy to od widżetu / układu zawierającego rozszerzenie heightForWidth.

aspektratiopixmaplabel.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

aspektratiopixmaplabel.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();
    }
}
Alexander Schlüter
źródło
Chociaż jest to preferowane w przypadkach skrajnych, w których widżet nadrzędny i / lub układ zawierający tę etykietę respektują heightForWidthwłaściwość, ta odpowiedź zawodzi w ogólnym przypadku, w którym widżet nadrzędny i / lub układ zawierający tę etykietę nie respektują heightForWidthwłaściwości. Które jest niefortunne, ponieważ w przeciwnym razie odpowiedź jest korzystne phyatt „s dawna odpowiedź .
Cecil Curry,
3

Zaadaptowano z Timmmm do 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
źródło
0

Dokumentacja Qt zawiera przykład przeglądarki obrazów, który demonstruje obsługę zmiany rozmiaru obrazów w pliku QLabel. Podstawową ideą jest użycie QScrollAreajako kontenera do QLabelużytku, label.setScaledContents(bool)aw razie potrzeby, scrollarea.setWidgetResizable(bool)do wypełnienia dostępnej przestrzeni i / lub zapewnienia możliwości zmiany rozmiaru QLabel w środku. Dodatkowo, aby zmienić rozmiar QLabel z zachowaniem proporcji:

Opcje widthi heightmożna ustawić na podstawie scrollarea.width()i scrollarea.height(). W ten sposób nie ma potrzeby tworzenia podklasy QLabel.

Omid
źródło