简体   繁体   English

Qt:调整包含 QPixmap 的 QLabel 的大小,同时保持其纵横比

[英]Qt: resizing a QLabel containing a QPixmap while keeping its aspect ratio

I use a QLabel to display the content of a bigger, dynamically changing QPixmap to the user.我使用 QLabel 向用户显示更大的、动态变化的 QPixmap 的内容。 It would be nice to make this label smaller/larger depending on the space available.最好根据可用空间使这个 label 变小/变大。 The screen size is not always as big as the QPixmap.屏幕尺寸并不总是与 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?如何修改 QLabel 的QSizePolicysizeHint()以调整 QPixmap 的大小,同时保持原始 QPixmap 的纵横比?

I can't modify sizeHint() of the QLabel, setting the minimumSize() to zero does not help.我无法修改 QLabel 的sizeHint() ,将minimumSize()设置为零也无济于事。 Setting hasScaledContents() on the QLabel allows growing, but breaks the aspect ratio thingy...在 QLabel 上设置hasScaledContents()允许增长,但会破坏纵横比......

Subclassing QLabel did help, but this solution adds too much code for just a simple problem...子类化 QLabel 确实有帮助,但是这个解决方案为一个简单的问题添加了太多代码......

Any smart hints how to accomplish this without subclassing?任何聪明的提示如何在没有子类化的情况下完成这个?

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:您应该在两个地方添加此代码:

  • When the pixmap is updated当像素图更新时
  • In the resizeEvent of the widget that contains the label在包含标签的小部件的resizeEvent

I have polished this missing subclass of QLabel .我已经完善了QLabel这个缺失的子类。 It is awesome and works well.它很棒并且运行良好。

aspectratiopixmaplabel.h纵横比像素图标签.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纵横比图标签.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) (根据@dmzl 的回答更新了resizeEvent

I just use contentsMargin to fix the aspect ratio.我只是使用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);
    }
}

Works perfectly for me so far.到目前为止对我来说非常有效。 You're welcome.别客气。

I tried using phyatt's AspectRatioPixmapLabel class, but experienced a few problems:我尝试使用 phyatt 的AspectRatioPixmapLabel类,但是遇到了一些问题:

  • Sometimes my app entered an infinite loop of resize events.有时我的应用程序会进入无限循环的调整大小事件。 I traced this back to the call of QLabel::setPixmap(...) inside the resizeEvent method, because QLabel actually calls updateGeometry inside setPixmap , which may trigger resize events...我将其追溯到 resizeEvent 方法内部对QLabel::setPixmap(...)的调用,因为QLabel实际上在updateGeometry内部调用setPixmap ,这可能会触发调整大小事件...
  • 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) heightForWidth似乎被包含的小部件(在我的例子中是QScrollArea )忽略,直到我开始为标签设置大小策略,明确调用policy.setHeightForWidth(true)
  • I want the label to never grow more than the original pixmap size我希望标签永远不会超过原始像素图大小
  • 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 it QLabelminimumSizeHint()实现对包含文本的标签做了一些魔术,但总是将大小策略重置为默认策略,所以我不得不覆盖它

That said, here is my solution.也就是说,这是我的解决方案。 I found that I could just use setScaledContents(true) and let QLabel handle the resizing.我发现我可以只使用setScaledContents(true)并让QLabel处理调整大小。 Of course, this depends on the containing widget / layout honoring the heightForWidth .当然,这取决于支持heightForWidth的包含小部件/布局。

aspectratiopixmaplabel.h纵横比像素图标签.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纵横比图标签.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();
    }
}

Adapted from Timmmm to PYQT5改编自 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)

Thanks for sharing this. 谢谢你分享这个。 Would you have any suggestions on how I can set a "preferred" size to the QPixmap so that it does not take the maximum resolution on the first launch of the application? 您对如何为QPixmap设置“首选”大小以便在首次启动应用程序时不采用最大分辨率有任何建议吗?

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.必须覆盖sizeHintresizeEvent ,并设置最小大小和大小策略。 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.当控件与图像的纵横比不同时, setAlignment用于将控件中的图像水平或垂直居中。

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(20, 20)
        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(200, 200)

    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)

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.我将接受的回复https://stackoverflow.com/a/8212120/11413792https://stackoverflow.com/a/43936590/11413792混合使用setContentsMargins ,但只是用我自己的方式对其进行了编码。

/**
 * @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.然后setContentsMargins变得易于使用,只需添加几行,许多人会将这些行合并为一行。

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.有人可能会感兴趣......我需要处理mousePressEvent并知道我在图像中的位置。

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.我的大图像来自相机,我通过除以像素图的宽度然后乘以原始图像的宽度来获得相对坐标 [0, 1)。

If your image is a resource or a file you don't need to subclass anything;如果您的图像是资源或文件,则不需要对任何内容进行子类化; just set image in the label's stylesheet;只需在标签的样式表中设置image 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.您可以选择使用image-position将图像移动到边缘之一。

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.它不适合 OP 的动态更新像素图的情况(我的意思是,您可以随时设置不同的资源,但它们仍然必须是资源),但如果您使用来自资源的像素图,这是一个好方法。

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:或者您可以在 Designer 中设置样式表属性:

在此处输入图像描述

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.需要注意的是,它不会将图像缩放得更大,只会更小,因此如果您希望图像变大,请确保图像大于您的尺寸范围。

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).这种风格从 Qt 早期就已经存在(位置是在 2007 年左右的 4.3 中添加的,但图像在此之前就已经存在)。

This is the port of @phyatt's class to PySide2.这是@phyatt 的 class 到 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.除了移植之外,我在 resizeEvent 中添加了一个额外的对齐方式,以便在可用空间中正确制作新调整大小的图像 position。

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)

The Qt documentations has an Image Viewer example which demonstrates handling resizing images inside a QLabel . Qt 文档有一个图像查看器示例,它演示了如何处理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.基本思想是使用QScrollArea作为QLabel的容器,如果需要,使用label.setScaledContents(bool)scrollarea.setWidgetResizable(bool)来填充可用空间和/或确保 QLabel 内部可调整大小。 Additionally, to resize QLabel while honoring aspect ratio use:此外,要在遵守纵横比的同时调整 QLabel 的大小,请使用:

label.setPixmap(pixmap.scaled(width, height, Qt::KeepAspectRatio, Qt::FastTransformation));

The width and height can be set based on scrollarea.width() and scrollarea.height() .可以根据scrollarea.width()scrollarea.height()设置widthheight In this way there is no need to subclass QLabel.这样就不需要子类化 QLabel。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM