Search code examples
c++qtqlistview

How can I apply a graphic effect to the image in QListView?


I would like to apply some graphic effect to the pixmap of the list item in QListView.

What should i do to achieve that?

As far as i understand, i need to make my own delegate for that. But how do i use QGraphicsEffect in it?

Update.

If QListWidget is used, i can do something to the following effect. Create widgets for every list item and apply desired QGraphicsEffect for them. This widget would go like this (for example):

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

};

But in this case i will have to also implement updating data on widgets to reflect contnts almost by hand, and this is thoroughly bothersome.Currently, with QListView changing data in model is simple and straightforward - and i can even change used model on the fly.

Is there a way to achieve the same outlook of the item? Maybe there is a pattern of implementing delegates that may be applicable...


Solution

  • Inspired by following question: How to blur QPixmap image, I came to the following solution: use implementation of dropshadow filter in the delegate, instead of trying to use QGraphicsEffect there.

    So, what I arrived at was this:

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

    Most of code that performs blur is taken from QPixmapDropShadowFilter implementation.