【问题标题】:Qt:调整包含 QPixmap 的 QLabel 的大小,同时保持其纵横比
【发布时间】:2012-01-02 23:05:16
【问题描述】:

我使用 QLabel 向用户显示更大、动态变化的 QPixmap 的内容。根据可用空间使这个标签更小/更大会很好。屏幕尺寸并不总是像 QPixmap 一样大。

如何修改 QLabel 的 QSizePolicysizeHint() 以调整 QPixmap 的大小,同时保持原始 QPixmap 的纵横比?

我无法修改 QLabel 的 sizeHint(),将 minimumSize() 设置为零无济于事。在 QLabel 上设置hasScaledContents() 允许增长,但会破坏纵横比...

子类化 QLabel 确实有帮助,但是这个解决方案为一个简单的问题添加了太多代码......

任何巧妙的提示如何在没有子类的情况下完成这个?

【问题讨论】:

  • 动态变化是指像素数据还是维度?
  • 我指的是当前布局中QLabel 的尺寸。 QPixmap 应保持其大小、内容和维度。此外,如果“自动”发生大小调整(实际上是缩小)以填充可用空间,这将是一件好事——最大到原始QPixmap 的大小。所有这些都是通过子类化完成的......

标签: c++ qt qt4 qlabel


【解决方案1】:

为了更改标签尺寸,您可以为标签选择适当的尺寸策略,例如展开或最小展开。

您可以通过在每次更改时保持其纵横比来缩放像素图:

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

您应该在两个地方添加此代码:

  • 像素图更新时
  • 在包含标签的小部件的resizeEvent

【讨论】:

  • 嗯,是的,当我将QLabel 子类化时,这基本上是核心。但我认为这个用例(在任意大小的小部件中显示任意大小的图像)足够普遍,可以通过现有代码实现类似的东西......
  • AFAIK 默认情况下不提供此功能。实现你想要的最优雅的方法是继承QLabel。否则,您可以在每次像素图更改时调用的槽/函数中使用我的答案代码。
  • 因为我希望QLabel 根据用户调整QMainWindow 的大小和可用空间自动扩展,所以我不能使用信号/插槽解决方案——我不能建模以这种方式扩展政策。
  • 为了能够缩小规模,你需要添加这个调用:label->setMinimumSize(1, 1)
  • 如果我想在用户更改标签大小时保留纵横比,这不是很有用。
【解决方案2】:

我已经完善了QLabel 的这个缺失子类。它很棒而且效果很好。

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

希望对您有所帮助! (更新resizeEvent,根据@dmzl 的回答)

【讨论】:

  • 谢谢,效果很好。我还将QLabel::setPixmap(pix.scaled(this-&gt;size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); 添加到setPixmap() 方法中。
  • 你是对的。我假设您想要存储最高质量版本的像素图,并且在调整标签大小/锚定标签之前调用 setPixmap。为了减少代码重复,我可能应该将this-&gt;resize(width(), height()); 放在setPixmap 函数的末尾。
  • 感谢分享。您对我如何为 QPixmap 设置“首选”大小以使其在首次启动应用程序时不采用最大分辨率有什么建议吗?
  • 使用布局和拉伸规则。
  • 很好的答案!对于需要在高 DPI 屏幕上工作的任何人,只需将 scaledPixmap() 更改为:auto scaled = pix.scaled(this-&gt;size() * devicePixelRatioF(), Qt::KeepAspectRatio, Qt::SmoothTransformation); scaled.setDevicePixelRatio(devicePixelRatioF()); return scaled; 这也适用于正常缩放的屏幕。
【解决方案3】:

我只是使用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);
    }
}

到目前为止,对我来说非常适合。不客气。

【讨论】:

  • 刚刚用过它,它就像一个魅力!此外,非常巧妙地使用了布局管理器。应该是公认的答案,因为所有其他人在极端情况下都有缺陷。
  • 虽然不直观,但这个答案从根本上解决了一个不同的问题:“我们应该在大小已经众所周知的标签和像素图之间添加多少内部填充包含在该标签中以保持该像素图的纵横比?”每个其他答案都解决了最初的问题:“我们应该将包含像素图的标签调整到什么大小以保持该像素图的纵横比?”这个答案需要以某种方式预先确定标签的大小(例如,使用固定大小策略),这在许多用例中是不可取的,甚至是不可行的。
  • 这就是 HiResolution(又名“视网膜”)显示器的方式 - 这比缩小 QPixmap 要好得多。
  • 为了可维护性,我可能有点过于专注于让代码表达高级含义,但是使用QSize 而不是...Width...Height 不是更有意义吗?如果不出意外,那将使您的提前返回检查成为一个简单的QSize::isEmpty 调用。 QPixmapQWidget 都有 size 方法来检索 QSize 的宽度和高度。
  • @ssokolow 是的,这听起来更好 - 随时编辑答案。
【解决方案4】:

我尝试使用 phyatt 的 AspectRatioPixmapLabel 类,但遇到了一些问题:

  • 有时我的应用程序会进入无限循环的调整大小事件。我将此追溯到 resizeEvent 方法中对QLabel::setPixmap(...) 的调用,因为QLabel 实际上在setPixmap 中调用了updateGeometry,这可能会触发resize 事件...
  • heightForWidth 似乎被包含小部件(在我的情况下为 QScrollArea)忽略,直到我开始为标签设置大小策略,显式调用 policy.setHeightForWidth(true)
  • 我希望标签的大小永远不会超过原始像素图大小
  • QLabelminimumSizeHint() 的实现对包含文本的标签有一些魔力,但总是将大小策略重置为默认值,所以我不得不覆盖它

也就是说,这是我的解决方案。我发现我可以只使用setScaledContents(true) 并让QLabel 处理调整大小。 当然,这取决于遵循 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();
    }
}

【讨论】:

  • 虽然对于包含此标签的父小部件和/或布局尊重 heightForWidth 属性的边缘情况更可取,但对于包含此标签的父小部件和/或布局的一般情况,此答案失败标签尊重heightForWidth 属性。这是不幸的,因为这个答案比phyattlong-standing answer更可取。
【解决方案5】:

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

【讨论】:

    【解决方案6】:

    我终于让它按预期工作了。必须覆盖sizeHintresizeEvent,并设置最小大小和大小策略。 当控件与图像的纵横比不同时,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(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)
    

    【讨论】:

      【解决方案7】:

      这里真的没有什么新鲜事。

      我混合了接受的回复 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;
      }
      

      一个函数计算将一个矩形居中另一个矩形所需的边距。它是一个非常通用的函数,虽然我不知道是什么,但它可以用于很多事情。

      然后setContentsMargins 变得易于使用,只需添加几行 很多人会合二为一。

      QPixmap scaled = p.scaled(this->size(), Qt::KeepAspectRatio);
      QMargins margins = calcMargins(this->size(), scaled.size());
      this->setContentsMargins(margins);
      setPixmap(scaled);
      

      可能有人会感兴趣...我需要处理 mousePressEvent 并知道我在图像中的位置。

      void MyClass::mousePressEvent(QMouseEvent *ev)
      {
          QMargins margins = contentsMargins();
      
          QPoint labelCoordinateClickPos = ev->pos();
          QPoint pixmapCoordinateClickedPos = labelCoordinateClickPos - QPoint(margins.left(),margins.top());
          ... more stuff here
      }
      

      我的大图像来自相机,我通过除以像素图的宽度然后乘以原始图像的宽度得到相对坐标 [0, 1)。

      【讨论】:

        【解决方案8】:

        Qt 文档有一个Image Viewer example,它演示了如何在QLabel 中处理图像大小调整。基本思想是使用QScrollArea 作为QLabel 的容器,如果需要,使用label.setScaledContents(bool)scrollarea.setWidgetResizable(bool) 来填充可用空间和/或确保内部的QLabel 可调整大小。 此外,在尊重纵横比的同时调整 QLabel 的大小:

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

        widthheight可以基于scrollarea.width()scrollarea.height()来设置。 这样就不需要子类化QLabel了。

        【讨论】:

        • 该示例在自动调整大小时不会保持纵横比。它允许在保持纵横比的同时手动调整大小,并且可以在不保持纵横比的情况下自动调整大小,但不能同时进行。
        猜你喜欢
        • 2017-09-13
        • 2015-09-26
        • 1970-01-01
        • 2011-10-05
        • 1970-01-01
        • 1970-01-01
        • 2011-09-10
        • 2014-08-07
        • 1970-01-01
        相关资源
        最近更新 更多