1

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...

Angie Quijano
  • 4,167
  • 3
  • 25
  • 30
Srv19
  • 3,458
  • 6
  • 44
  • 75
  • Can you be a little bit more specific? What contains your QListView? Do you want to customize how it looks (that would be in the delegates as you mentioned)? If you want to add "effects", you might have some code that is already running and putting some of it here would certainly help us understand the question. – Uflex May 16 '14 at 14:50

3 Answers3

2

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.

Community
  • 1
  • 1
Srv19
  • 3,458
  • 6
  • 44
  • 75
2

Let's contribute to this topic. As of Qt 5.3, following function will help you a lot with applying QGraphicsEffect to QImage (and not losing the alpha). After you apply the blur, add this QImage into your container as you like.

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

Them, using this function to blur your image is straightforward:

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

Of course, you don't need to save it, this was just an example of usefulness. You can even drop a shadow:

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

And note the extent parameter, it adds extent number of pixels to all sides of the original image, especially useful for shadows and blurs to not be cut-off.

Angie Quijano
  • 4,167
  • 3
  • 25
  • 30
Петър Петров
  • 1,966
  • 1
  • 17
  • 14
0

Well, answer is - I would not suggest to use QGraphicsEffect for delegate class at all. Point is that Qt uses QGraphicsEffect as a pipeline between how certain controls are drawn and a physical graphics device.

It means that base class - QGraphicsEffect declare inside itself several friends, Qt classes which are 'effects enabled' :

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

};

Actually such declaration means that those classes are able to access protected methods of any graphics effect to modify own behaviour inside drawing loops. Saying other way they pass own look into the effect filter before drawing itself.

Since QAbstractItemDelegate is not in this list you simpler will have no way to access effect methods even if you will be able to access graphics effect instance from parent control.

So, I think best way (if you need to mimic an effect for particular pixmap) is to create own class which will do the job rather then using existing effect.

Another option is obviously to apply an effect to whole QListView which in a certain way will process you items, but I think it can be quite tricky implementation

evilruff
  • 3,947
  • 1
  • 15
  • 27
  • I need effects to differ from item to item - for example, in color - depnding on item's data. On mimicking the effect - do you suggest to look into default item delegate implementations and qgraphiceffect implementations in Qt then? – Srv19 May 18 '14 at 17:29
  • Well I would suggest not you stick with graphics effect.. because at the end of the day it's just image processing and create own implementation – evilruff May 19 '14 at 14:19
  • He just need to use QGraphicsScene to render given pixmap with given effect and then, of course, kill the effect, I posted code for that – Петър Петров Jan 19 '15 at 22:23