繁体   English   中英

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

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

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

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

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

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

任何聪明的提示如何在没有子类化的情况下完成这个?

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

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

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这个缺失的子类。 它很棒并且运行良好。

纵横比像素图标签.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

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

希望有帮助! (根据@dmzl 的回答更新了resizeEvent

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

到目前为止对我来说非常有效。 别客气。

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

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

也就是说,这是我的解决方案。 我发现我可以只使用setScaledContents(true)并让QLabel处理调整大小。 当然,这取决于支持heightForWidth的包含小部件/布局。

纵横比像素图标签.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

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

改编自 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)

谢谢你分享这个。 您对如何为QPixmap设置“首选”大小以便在首次启动应用程序时不采用最大分辨率有任何建议吗?

我终于让它按预期工作了。 必须覆盖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(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)

这里真的没有什么新东西。

我将接受的回复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)。

如果您的图像是资源或文件,则不需要对任何内容进行子类化; 只需在标签的样式表中设置image 它将被缩放以适应标签,同时保持其纵横比,并将跟踪对标签所做的任何大小更改。 您可以选择使用image-position将图像移动到边缘之一。

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

样式表示例:

image: url(:/resource/path);
image-position: right center; /* optional: default is centered. */

在代码中(例如):

QString stylesheet = "image:url(%1);image-position:right center;";
existingLabel->setStyleSheet(stylesheet.arg(":/resource/path"));

或者您可以在 Designer 中设置样式表属性:

在此处输入图像描述

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

标签的大小可以像往常一样控制:要么使用样式表中的大小元素,要么使用标准布局和大小策略策略。

有关详细信息,请参阅文档

这种风格从 Qt 早期就已经存在(位置是在 2007 年左右的 4.3 中添加的,但图像在此之前就已经存在)。

这是@phyatt 的 class 到 PySide2 的端口。

除了移植之外,我在 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)

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

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

可以根据scrollarea.width()scrollarea.height()设置widthheight 这样就不需要子类化 QLabel。

暂无
暂无

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

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