Я хотел бы применить некоторый графический эффект к растровому изображению элемента списка в 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 изменение данных в модели просто и понятно — и я даже могу изменить используемую модель на лету ,
Есть ли способ добиться того же внешнего вида предмета? Может быть, есть шаблон реализации делегатов, который может быть применим …
Вдохновлен следующим вопросом: Как размыть изображение QPixmap, Я пришел к следующему решению: использовать реализацию фильтра drophadow в делегате, вместо того, чтобы пытаться использовать 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 реализация.
Давайте внесем вклад в эту тему. Начиная с 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
количество пикселей со всех сторон исходного изображения, особенно полезно, чтобы тени и размытия не обрезались.
Что ж, ответ таков: я бы вообще не предлагал использовать 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, который определенным образом обработает ваши элементы, но я думаю, что это может быть довольно сложной реализацией.