问题
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...
回答1:
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.
回答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.
回答3:
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
来源:https://stackoverflow.com/questions/23698114/how-can-i-apply-a-graphic-effect-to-the-image-in-qlistview