Qt: cambiar el tamaño de un QLabel que contiene un QPixmap manteniendo su relación de aspecto

81

Utilizo QLabel para mostrar al usuario el contenido de un QPixmap más grande que cambia dinámicamente. Sería bueno hacer esta etiqueta más pequeña / más grande según el espacio disponible. El tamaño de la pantalla no siempre es tan grande como el QPixmap.

¿Cómo puedo modificar QSizePolicyy sizeHint()de QLabel para cambiar el tamaño del QPixmap manteniendo la relación de aspecto del QPixmap original?

No puedo modificar sizeHint()el QLabel, establecer el minimumSize()en cero no ayuda. La configuración hasScaledContents()en QLabel permite crecer, pero rompe la relación de aspecto ...

Subclasificar QLabel ayudó, pero esta solución agrega demasiado código para un simple problema ...

¿Algún consejo inteligente sobre cómo lograr esto sin subclases?

marvin2k
fuente
Por cambiar dinámicamente, ¿te refieres a los datos de píxeles o las dimensiones?
r_ahlskog
Me refiero a las dimensiones del QLabeldiseño actual. El QPixmapdebe mantener su tamaño, contenido y dimensión. Además, sería bueno que el cambio de tamaño (encogiéndose en realidad) ocurra "automágicamente" para llenar el espacio disponible, hasta el tamaño del original QPixmap. Todo esto se hizo mediante subclases ...
marvin2k

Respuestas:

98

Para cambiar el tamaño de la etiqueta, puede seleccionar una política de tamaño adecuada para la etiqueta, como expansión o expansión mínima.

Puede escalar el mapa de píxeles manteniendo su relación de aspecto cada vez que cambia:

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

Hay dos lugares donde debe agregar este código:

  • Cuando se actualiza el mapa de píxeles
  • En el resizeEventdel widget que contiene la etiqueta
pnezis
fuente
hm sí, esto era básicamente el núcleo cuando subclasé QLabel. Pero pensé que este caso de uso (que muestra imágenes con tamaño arbitrario en widgets de tamaño arbitrario) sería lo suficientemente común como para tener algo así implementable a través del código existente ...
marvin2k
AFAIK, esta funcionalidad no se proporciona de forma predeterminada. La forma más elegante de lograr lo que desea es subclase QLabel. De lo contrario, puede usar el código de mi respuesta en una ranura / función que se llamará cada vez que cambie el mapa de píxeles.
pnezis
1
Dado que quiero QLabelque se expanda automáticamente en función del cambio de tamaño de los usuarios QMainWindowy el espacio disponible, no puedo usar la solución de señal / ranura; no puedo modelar una política de expansión de esta manera.
marvin2k
21
Para poder reducir la escala también, debe agregar esta llamada:label->setMinimumSize(1, 1)
Pieter-Jan Busschaert
1
Esto no es muy útil si quiero conservar la relación de aspecto incluso cuando el usuario cambia el tamaño de la etiqueta.
Tomáš Zato - Reincorporación a Monica
33

He pulido esta subclase faltante de QLabel. Es impresionante y funciona bien.

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

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

¡Espero que ayude! (Actualizado resizeEvent, según la respuesta de @ dmzl)

phyatt
fuente
1
Gracias, funciona muy bien. También agregaría QLabel::setPixmap(pix.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));al setPixmap()método.
Hyndrix
Tienes razón. Supuse que desea almacenar la versión de la más alta calidad del mapa de píxeles y que llama a setPixmap antes de cambiar el tamaño / anclar la etiqueta. Para reducir la duplicación de código, probablemente debería ponerlo this->resize(width(), height());al final de la setPixmapfunción.
phyatt
Gracias por compartir esto. ¿Tiene alguna sugerencia sobre cómo puedo establecer un tamaño "preferido" para QPixmap para que no tome la resolución máxima en el primer lanzamiento de la aplicación?
Julien M
Utilice diseños y reglas de estiramiento.
phyatt
3
¡Gran respuesta! Para cualquiera que necesite trabajar en pantallas High DPI, simplemente cambie scaledPixmap () para hacer: auto scaled = pix.scaled(this->size() * devicePixelRatioF(), Qt::KeepAspectRatio, Qt::SmoothTransformation); scaled.setDevicePixelRatio(devicePixelRatioF()); return scaled;Esto también funciona en pantallas normalmente escaladas.
Saúl
18

Solo uso contentsMarginpara arreglar la relación de aspecto.

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

Funciona perfectamente para mí hasta ahora. De nada.

Timmmm
fuente
4
¡Solo usé esto y funciona como un encanto! Además, uso bastante inteligente del administrador de diseño. Debería ser la respuesta aceptada ya que todas las demás tienen fallas en casos de esquina.
thokra
2
Si bien no es intuitivamente inteligente, esta respuesta resuelve una pregunta fundamentalmente diferente : "¿Cuánto relleno interno deberíamos agregar entre una etiqueta cuyo tamaño ya es bien conocido y el mapa de píxeles contenido en esa etiqueta para preservar la relación de aspecto de ese mapa de píxeles? " Cualquier otra respuesta resuelve la pregunta original: "¿A qué tamaño deberíamos cambiar el tamaño de una etiqueta que contiene un mapa de píxeles para preservar la relación de aspecto de ese mapa de píxeles?" Esta respuesta requiere que el tamaño de la etiqueta esté predeterminado de alguna manera (por ejemplo, con una política de tamaño fijo), lo cual es indeseable o incluso inviable en muchos casos de uso.
Cecil Curry
1
Ese es el camino a seguir para las pantallas HiResolution (también conocidas como "retina"): es mucho mejor que reducir la escala del QPixmap.
jvb
Tal vez estoy un poco demasiado concentrado en hacer que el código exprese un significado de alto nivel por el bien de la mantenibilidad, pero ¿no tendría más sentido usarlo en QSizelugar de ...Widthy ...Height? Si nada más, eso haría que sus cheques de devolución anticipada fueran una simple QSize::isEmptyllamada. QPixmapy QWidgetambos tienen sizemétodos para recuperar el ancho y el alto como un QSize.
ssokolow
@ssokolow Sí, suena mejor. Siéntase libre de editar la respuesta.
Timmmm
5

Intenté usar la AspectRatioPixmapLabelclase de phyatt , pero experimenté algunos problemas:

  • A veces, mi aplicación entraba en un ciclo infinito de eventos de cambio de tamaño. QLabel::setPixmap(...)Rastreé esto hasta la llamada de dentro del método resizeEvent, porque en QLabelrealidad las llamadas updateGeometrydentro setPixmap, lo que puede desencadenar eventos de cambio de tamaño ...
  • heightForWidthparecía ser ignorado por el widget contenedor (a QScrollAreaen mi caso) hasta que comencé a configurar una política de tamaño para la etiqueta, llamando explícitamentepolicy.setHeightForWidth(true)
  • Quiero que la etiqueta nunca crezca más que el tamaño del mapa de píxeles original
  • QLabelLa implementación de minimumSizeHint()hace algo de magia para las etiquetas que contienen texto, pero siempre restablece la política de tamaño a la predeterminada, así que tuve que sobrescribirla

Dicho esto, aquí está mi solución. Descubrí que podía usar setScaledContents(true)y dejar QLabelmanejar el cambio de tamaño. Por supuesto, esto depende del widget / diseño que lo contenga respetando el heightForWidth.

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

Aspectratiopixmaplabel.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
fuente
Si bien es preferible para los casos extremos en los que el widget principal y / o el diseño que contiene esta etiqueta respetan la heightForWidthpropiedad, esta respuesta falla para el caso general en el que el widget principal y / o el diseño que contiene esta etiqueta no respetan la heightForWidthpropiedad. Lo cual es lamentable, ya que esta respuesta es preferible a la respuesta de larga data de phyatt .
Cecil Curry
3

Adaptado de Timmmm a 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
fuente
0

La documentación de Qt tiene un ejemplo de Visor de imágenes que demuestra cómo manejar imágenes de cambio de tamaño dentro de un archivoQLabel . La idea básica es utilizar QScrollAreacomo un contenedor para el QLabely si es necesario, utilice label.setScaledContents(bool)y scrollarea.setWidgetResizable(bool)para llenar el espacio disponible y / o garantizar QLabel interior es de tamaño variable. Además, para cambiar el tamaño de QLabel respetando la relación de aspecto, utilice:

El widthy heightse puede establecer basándose en scrollarea.width()y scrollarea.height(). De esta manera, no es necesario crear una subclase de QLabel.

Omid
fuente