Как я могу применить графический эффект к изображению в QListView?

Я хотел бы применить некоторый графический эффект к растровому изображению элемента списка в QListView.

Что я должен сделать, чтобы достичь этого?

Насколько я понимаю, мне нужно сделать свой собственный делегат для этого. Но как мне использовать в нем QGraphicsEffect?

Обновлять.

Если используется QListWidget, я могу сделать что-то со следующим эффектом. Создайте виджеты для каждого элемента списка и примените к ним нужный QGraphicsEffect. Этот виджет будет выглядеть так (например):

class PortraitViewWidget : public QFrame
{
    Q_OBJECT

public:
    explicit PortraitViewWidget(QWidget* parent = nullptr)
{
    auto imageView = new QWidget();
    auto imageViewLayout = new QVBoxLayout();
    auto imageLabel = new QLabel();
    auto textLabel = new QLabel();

    // test defaults
    imageLabel->setPixmap(QPixmap("/Lenna.png"));
    imageLabel->setScaledContents(true);

    static qreal quality = 0.f;
    quality += 0.1752f;

    if(quality > 1.f)
        quality = 1.f;

    textLabel->setText(QString("%1%").arg(quality * 100.f, 0, 'f', 1));
    textLabel->setAlignment(Qt::AlignCenter);
    textLabel->setStyleSheet(
        "QLabel {"
        "   background-color: white;"
        "   color: black;"
        "   font-size: 16px;"
        "   padding: 2px; }");

    imageViewLayout->addWidget(imageLabel);
    imageViewLayout->addWidget(textLabel);

    imageViewLayout->setMargin(0);
    imageViewLayout->setSpacing(0);
    imageViewLayout->setContentsMargins(0, 0, 0, 0);

    imageView->setLayout(imageViewLayout);

    auto effect = new QGraphicsDropShadowEffect();
    effect->setBlurRadius(55);
    effect->setOffset(0.f);
    effect->setColor(Qt::green);

    imageView->setGraphicsEffect(effect);

    imageView->setSizePolicy(
        QSizePolicy::Expanding,
        QSizePolicy::Expanding);

    imageView->setMinimumSize(240, 320);
    imageView->setMaximumSize(480, 640);

    auto layout = new QVBoxLayout();
    layout->addWidget(imageView);
    layout->setMargin(25);

    setLayout(layout);
}

};

Но в этом случае мне также придется реализовать обновление данных в виджетах, чтобы отразить содержание почти вручную, и это очень утомительно. В настоящее время с QListView изменение данных в модели является простым и понятным - и я даже могу изменить используемую модель на лету. .

Есть ли способ добиться того же внешнего вида предмета? Возможно, есть шаблон реализации делегатов, который может быть применим...


person Srv19    schedule 16.05.2014    source источник
comment
Можете ли вы быть немного более конкретным? Что содержит ваш QListView? Вы хотите настроить, как это выглядит (это было бы в делегатах, как вы упомянули)? Если вы хотите добавить эффекты, у вас может быть какой-то код, который уже запущен, и размещение его здесь, безусловно, поможет нам понять вопрос.   -  person Uflex    schedule 16.05.2014


Ответы (3)


Давайте внесем свой вклад в эту тему. Начиная с Qt 5.3, следующая функция очень поможет вам применить QGraphicsEffect к QImage (и не потерять альфу). После того, как вы примените размытие, добавьте этот QImage в свой контейнер, как вам нравится.

QImage applyEffectToImage(QImage src, QGraphicsEffect *effect, int extent = 0)
{
    if(src.isNull()) return QImage();   //No need to do anything else!
    if(!effect) return src;             //No need to do anything else!
    QGraphicsScene scene;
    QGraphicsPixmapItem item;
    item.setPixmap(QPixmap::fromImage(src));
    item.setGraphicsEffect(effect);
    scene.addItem(&item);
    QImage res(src.size()+QSize(extent*2, extent*2), QImage::Format_ARGB32);
    res.fill(Qt::transparent);
    QPainter ptr(&res);
    scene.render(&ptr, QRectF(), QRectF( -extent, -extent, src.width()+extent*2, src.height()+extent*2 ) );
    return res;
}

Им очень просто использовать эту функцию для размытия изображения:

QGraphicsBlurEffect *blur = new QGraphicsBlurEffect;
blur->setBlurRadius(8);
QImage source("://img1.png");
QImage result = applyEffectToImage(source, blur);
result.save("final.png");

Конечно, вам не нужно сохранять его, это был просто пример полезности. Можно даже отбросить тень:

QGraphicsDropShadowEffect *e = new QGraphicsDropShadowEffect;
e->setColor(QColor(40,40,40,245));
e->setOffset(0,10);
e->setBlurRadius(50);
QImage p("://img3.png");
QImage res = applyEffectToImage(p, e, 40);

И обратите внимание на параметр экстента, он добавляет extent пикселей ко всем сторонам исходного изображения, что особенно полезно для теней и размытия, чтобы они не обрезались.

person Петър Петров    schedule 19.01.2015

Вдохновленный следующим вопросом: Как размыть изображение QPixmap, я пришел к следующему решение: используйте реализацию фильтра dropshadow в делегате, вместо того, чтобы пытаться использовать там QGraphicsEffect.

Итак, я пришел к следующему:

QT_BEGIN_NAMESPACE
  extern Q_WIDGETS_EXPORT void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0 );
QT_END_NAMESPACE

#define RADIUS 20

void 
GalleryDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    if(option.decorationSize.isValid() && 
        (option.decorationPosition == QStyleOptionViewItem::Top))
    {

        painter->save();

        QPixmap decoration(index.data(Qt::DecorationRole).value<QPixmap>());

        //1. paint background
        painter->fillRect(option.rect, option.backgroundBrush);
        //2. make image with shadow
        QRect src(QPoint(0, 0), option.decorationSize);
        src.translate(RADIUS, RADIUS);
        QRect dst(src.adjusted(-RADIUS, -RADIUS, RADIUS, RADIUS + option.fontMetrics.height()));

        QImage tmp(dst.size(), QImage::Format_ARGB32_Premultiplied);
        tmp.fill(0);
        QPainter tmpPainter(&tmp);
        tmpPainter.setCompositionMode(QPainter::CompositionMode_Source);
        tmpPainter.fillRect(src.adjusted(-3, -3, 3, 3 + option.fontMetrics.height() * 1.2), Qt::white);

        QRect textRectangle(RADIUS, src.bottom(), 
            tmp.width() - 2 * RADIUS, tmp.height() - src.bottom() - RADIUS);

        tmpPainter.end();

        // blur the alpha channel
        QImage blurred(tmp.size(), QImage::Format_ARGB32_Premultiplied);
        blurred.fill(0);
        QPainter blurPainter(&blurred);
        qt_blurImage(&blurPainter, tmp, RADIUS*1.5f, false, true);
        blurPainter.end();

        tmp = blurred;

        // blacken the image...
        tmpPainter.begin(&tmp);
        tmpPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
        tmpPainter.fillRect(tmp.rect(),Qt::green);
        tmpPainter.end();

        // draw the blurred drop shadow...
        painter->drawImage(option.rect.topLeft(), tmp);

        // Draw the actual pixmap...
        painter->drawPixmap(src.translated(option.rect.topLeft()),
            decoration.scaled(src.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));

        //4. draw text under it
        painter->fillRect(textRectangle.adjusted(0, 2, 0, -2).translated(option.rect.topLeft()), Qt::white);
        painter->drawText(textRectangle.translated(option.rect.topLeft()), Qt::AlignCenter, 
            index.data(Qt::DisplayRole).toString());

        if(option.state & QStyle::State_Selected)
        {
            QPen highlight(Qt::magenta, 5);
            QRect border(option.rect);
            border.adjust(3, 3, -3, -3);
            painter->setPen(index.data(Qt::red);
            painter->drawRoundedRect(border, 5.f, 5.f);
        }

        painter->restore();
    }
    else
        QStyledItemDelegate::paint(painter, option, index);
}

Большая часть кода, выполняющего размытие, взята из QPixmapDropShadowFilter. реализация.

person Srv19    schedule 22.05.2014

Ну, ответ - я бы вообще не предлагал использовать QGraphicsEffect для класса делегата. Дело в том, что Qt использует QGraphicsEffect в качестве конвейера между тем, как отрисовываются определенные элементы управления, и физическим графическим устройством.

Это означает, что базовый класс - QGraphicsEffect объявляет внутри себя несколько друзей, классов Qt, для которых "включены эффекты":

class QGraphicsEffect {
....
private:
...
    friend class QGraphicsItem;
    friend class QGraphicsItemPrivate;
    friend class QGraphicsScenePrivate;
    friend class QWidget;
    friend class QWidgetPrivate;
...

};

На самом деле такое объявление означает, что эти классы могут получить доступ к защищенным методам любого графического эффекта для изменения собственного поведения внутри циклов рисования. Другими словами, они пропускают свой взгляд в фильтр эффектов, прежде чем рисовать себя.

Поскольку QAbstractItemDelegate отсутствует в этом списке, у вас не будет доступа к методам эффекта, даже если вы сможете получить доступ к экземпляру графического эффекта из родительского элемента управления.

Итак, я думаю, что лучший способ (если вам нужно имитировать эффект для определенного растрового изображения) - это создать собственный класс, который будет выполнять эту работу, а не использовать существующий эффект.

Другим вариантом, очевидно, является применение эффекта ко всему QListView, который определенным образом будет обрабатывать ваши элементы, но я думаю, что это может быть довольно сложной реализацией.

person evilruff    schedule 16.05.2014
comment
Мне нужно, чтобы эффекты отличались от предмета к предмету — например, по цвету — в зависимости от данных предмета. При имитации эффекта - вы предлагаете тогда изучить реализации делегата элемента по умолчанию и реализации qgraphiceffect в Qt? - person Srv19; 18.05.2014
comment
Что ж, я бы посоветовал вам не зацикливаться на графическом эффекте ... потому что, в конце концов, это просто обработка изображения и создание собственной реализации. - person evilruff; 19.05.2014
comment
Ему просто нужно использовать QGraphicsScene для рендеринга данного растрового изображения с заданным эффектом, а затем, конечно, убить эффект, я разместил код для этого - person Петър Петров; 20.01.2015