Reputation: 1653
I use a QLabel to display the content of a bigger, dynamically changing QPixmap to the user. It would be nice to make this label smaller/larger depending on the space available. The screen size is not always as big as the QPixmap.
How can I modify the QSizePolicy
and sizeHint()
of the QLabel to resize the QPixmap while keeping the aspect ratio of the original QPixmap?
I can't modify sizeHint()
of the QLabel, setting the minimumSize()
to zero does not help. Setting hasScaledContents()
on the QLabel allows growing, but breaks the aspect ratio thingy...
Subclassing QLabel did help, but this solution adds too much code for just a simple problem...
Any smart hints how to accomplish this without subclassing?
Upvotes: 105
Views: 151781
Reputation: 61
This is the port of @phyatt's class to PySide2.
Apart from porting i added an additional aligment in the resizeEvent in order to make the newly resized image position properly in the available space.
from typing import Union
from PySide2.QtCore import QSize, Qt
from PySide2.QtGui import QPixmap, QResizeEvent
from PySide2.QtWidgets import QLabel, QWidget
class QResizingPixmapLabel(QLabel):
def __init__(self, parent: Union[QWidget, None] = ...):
super().__init__(parent)
self.setMinimumSize(1,1)
self.setScaledContents(False)
self._pixmap: Union[QPixmap, None] = None
def heightForWidth(self, width:int) -> int:
if self._pixmap is None:
return self.height()
else:
return self._pixmap.height() * width / self._pixmap.width()
def scaledPixmap(self) -> QPixmap:
scaled = self._pixmap.scaled(
self.size() * self.devicePixelRatioF(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
scaled.setDevicePixelRatio(self.devicePixelRatioF());
return scaled;
def setPixmap(self, pixmap: QPixmap) -> None:
self._pixmap = pixmap
super().setPixmap(pixmap)
def sizeHint(self) -> QSize:
width = self.width()
return QSize(width, self.heightForWidth(width))
def resizeEvent(self, event: QResizeEvent) -> None:
if self._pixmap is not None:
super().setPixmap(self.scaledPixmap())
self.setAlignment(Qt.AlignCenter)
Upvotes: 0
Reputation: 40315
If your image is a resource or a file you don't need to subclass anything; just set image
in the label's stylesheet; and it will be scaled to fit the label while keeping its aspect ratio, and will track any size changes made to the label. You can optionally use image-position
to move the image to one of the edges.
It doesn't fit the OP's case of a dynamically updated pixmap (I mean, you can set different resources whenever you want but they still have to be resources), but it's a good method if you're using pixmaps from resources.
Stylesheet example:
image: url(:/resource/path);
image-position: right center; /* optional: default is centered. */
In code (for example):
QString stylesheet = "image:url(%1);image-position:right center;";
existingLabel->setStyleSheet(stylesheet.arg(":/resource/path"));
Or you can just set the stylesheet property right in Designer:
Icon source: Designspace Team via Flaticon
The caveat is that it won't scale the image larger, only smaller, so make sure your image is bigger than your range of sizes if you want it to grow (note that it can support SVG, which can improve quality).
The label's size can be controlled as per usual: either use size elements in the stylesheet or use the standard layout and size policy strategies.
See the documentation for details.
This style has been present since early Qt (position was added in 4.3 circa 2007 but image was around before then).
Upvotes: 8
Reputation: 41
Nothing new here really.
I mixed the accepted reply
https://stackoverflow.com/a/8212120/11413792
and
https://stackoverflow.com/a/43936590/11413792
which uses setContentsMargins
,
but just coded it a bit my own way.
/**
* @brief calcMargins Calculate the margins when a rectangle of one size is centred inside another
* @param outside - the size of the surrounding rectanle
* @param inside - the size of the surrounded rectangle
* @return the size of the four margins, as a QMargins
*/
QMargins calcMargins(QSize const outside, QSize const inside)
{
int left = (outside.width()-inside.width())/2;
int top = (outside.height()-inside.height())/2;
int right = outside.width()-(inside.width()+left);
int bottom = outside.height()-(inside.height()+top);
QMargins margins(left, top, right, bottom);
return margins;
}
A function calculates the margins required to centre one rectangle inside another. Its a pretty generic function that could be used for lots of things though I have no idea what.
Then setContentsMargins
becomes easy to use with a couple of extra lines
which many people would combine into one.
QPixmap scaled = p.scaled(this->size(), Qt::KeepAspectRatio);
QMargins margins = calcMargins(this->size(), scaled.size());
this->setContentsMargins(margins);
setPixmap(scaled);
It may interest somebody ... I needed to handle mousePressEvent
and to know where I am within the image.
void MyClass::mousePressEvent(QMouseEvent *ev)
{
QMargins margins = contentsMargins();
QPoint labelCoordinateClickPos = ev->pos();
QPoint pixmapCoordinateClickedPos = labelCoordinateClickPos - QPoint(margins.left(),margins.top());
... more stuff here
}
My large image was from a camera and I obtained the relative coordinates [0, 1) by dividing by the width of the pixmap and then multiplied up by the width of the original image.
Upvotes: 0
Reputation: 896
I finally got this to work as expected. It is essential to override sizeHint
as well as resizeEvent
, and to set the minimum size and the size policy.
setAlignment
is used to centre the image in the control either horizontally or vertically when the control is a different aspect ratio to the image.
class ImageDisplayWidget(QLabel):
def __init__(self, max_enlargement=2.0):
super().__init__()
self.max_enlargement = max_enlargement
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.setAlignment(Qt.AlignCenter)
self.setMinimumSize(1, 1)
self.__image = None
def setImage(self, image):
self.__image = image
self.resize(self.sizeHint())
self.update()
def sizeHint(self):
if self.__image:
return self.__image.size() * self.max_enlargement
else:
return QSize(1, 1)
def resizeEvent(self, event):
if self.__image:
pixmap = QPixmap.fromImage(self.__image)
scaled = pixmap.scaled(event.size(), Qt.KeepAspectRatio)
self.setPixmap(scaled)
super().resizeEvent(event)
Upvotes: 1
Reputation: 283
The Qt documentations has an Image Viewer example which demonstrates handling resizing images inside a QLabel
. The basic idea is to use QScrollArea
as a container for the QLabel
and if needed use label.setScaledContents(bool)
and scrollarea.setWidgetResizable(bool)
to fill available space and/or ensure QLabel inside is resizable.
Additionally, to resize QLabel while honoring aspect ratio use:
label.setPixmap(pixmap.scaled(width, height, Qt::KeepAspectRatio, Qt::FastTransformation));
The width
and height
can be set based on scrollarea.width()
and scrollarea.height()
.
In this way there is no need to subclass QLabel.
Upvotes: 0
Reputation: 519
Adapted from Timmmm to 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)
Upvotes: 10
Reputation: 96547
I just use contentsMargin
to fix the aspect ratio.
#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);
}
}
Works perfectly for me so far. You're welcome.
Upvotes: 24
Reputation: 1187
I tried using phyatt's AspectRatioPixmapLabel
class, but experienced a few problems:
QLabel::setPixmap(...)
inside the resizeEvent method, because QLabel
actually calls updateGeometry
inside setPixmap
, which may trigger resize events...heightForWidth
seemed to be ignored by the containing widget (a QScrollArea
in my case) until I started setting a size policy for the label, explicitly calling policy.setHeightForWidth(true)
QLabel
's implementation of minimumSizeHint()
does some magic for labels containing text, but always resets the size policy to the default one, so I had to overwrite itThat said, here is my solution. I found that I could just use setScaledContents(true)
and let QLabel
handle the resizing.
Of course, this depends on the containing widget / layout honoring the 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();
}
}
Upvotes: 6
Reputation: 19102
I have polished this missing subclass of QLabel
. It is awesome and works well.
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());
}
Hope that helps!
(Updated resizeEvent
, per @dmzl's answer)
Upvotes: 48
Reputation: 12321
In order to change the label size you can select an appropriate size policy for the label like expanding or minimum expanding.
You can scale the pixmap by keeping its aspect ratio every time it changes:
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));
There are two places where you should add this code:
resizeEvent
of the widget that contains the labelUpvotes: 117