I’m trying to create a pixmap to be used as a background shadow that could be made by a single one or
multiple combined pixmaps with specific offsets/blur radius.
I know about the QGraphicsDropShadowEffect class, this is not what im looking for, the goal of the function is to create a pixmap by combining multiple images and not to draw each separated image on a QWidget painter.
I write the code below exclusively to help debug the creation of this shadow pixmap.
I’m struggling on how to properly calculate the drawing position of each image according to their offset, radius, size, to generate the combined pixmap
To be more specific, i mean the calc done on the createShadowPixmap function, i already rewritte it multiple times and i cant get it to draw all images correctly.
Testing different values it end up on one or another pixmap being draw at a wrong position, or being moved when it shouldn’t.
// using qt 6.0
class Widget : public QWidget
{
Q_OBJECT
public:
struct Image {
QPoint pos;
int blurRadius;
QSize size;
QColor blurColor;
int borderRadius;
QPixmap pixmap;
};
QPixmap shadowPixmap;
void createShadowPixmap(QList<Image>& imageList)
{
int i = 0;
for (Image& image : imageList)
{
if (image.size.isEmpty() || !image.blurColor.isValid())
continue;
QSize pixmapSize = image.size + QSize(image.blurRadius * 2, image.blurRadius * 2) + QSize(qAbs(image.pos.x()), qAbs(image.pos.y()));
QPixmap pixmap(pixmapSize);
pixmap.fill(Qt::transparent);
// Create individual shadow pixmap
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::NoPen);
painter.setBrush(image.blurColor);
QPoint p(qMax(image.blurRadius, image.blurRadius + image.pos.x()),
qMax(image.blurRadius, image.blurRadius + image.pos.y()));
painter.drawRoundedRect(QRect(p, image.size), image.borderRadius, image.borderRadius);
// Apply blur effect
QImage shadowImage = pixmap.toImage();
QImage blurredImage(shadowImage.size(), QImage::Format_ARGB32_Premultiplied);
blurredImage.fill(0);
QPainter blurPainter(&blurredImage);
qt_blurImage(&blurPainter, shadowImage, image.blurRadius, true, false);
blurPainter.end();
image.pixmap = QPixmap::fromImage(blurredImage);
// -- debug --
QString prefix = QString::number(i++) + "__";
pixmap.save(prefix + "0.png");
shadowImage.save(prefix + "1.png");
blurredImage.save(prefix + "2.png");
}
// Calculate the total size needed for the combined pixmap
int minX = 0, minY = 0, maxX = 0, maxY = 0;
for (const Image& image : imageList)
{
if (image.size.isEmpty() || !image.blurColor.isValid())
continue;
minX = qMin(minX, image.pos.x());
minY = qMin(minY, image.pos.y());
maxX = qMax(maxX, image.pos.x() + image.pixmap.width());
maxY = qMax(maxY, image.pos.y() + image.pixmap.height());
}
QSize totalSize(maxX - minX, maxY - minY);
shadowPixmap = QPixmap(totalSize);
shadowPixmap.fill(Qt::transparent);
QPainter combinedPainter(&shadowPixmap);
// Draw each image's pixmap at its specified position
for (const Image& image : imageList)
{
if (image.size.isEmpty() || !image.blurColor.isValid())
continue;
QPoint drawPos = QPoint(image.pos.x() - minX, image.pos.y() - minY);
combinedPainter.drawPixmap(drawPos, image.pixmap);
}
shadowPixmap.save("shadowPixmap.png");
}
Widget()
{
QGridLayout* gridLayout = new QGridLayout(this);
int i = 0;
for (const QString& str : {"-- image position", "-- blur radius --", "-- size --", "-- blurColor"} ) {
gridLayout->addWidget(new QLabel(str, this), 0, i++, 1, 1);
for (int k = 0; k < 2; k++) {
QHBoxLayout* layout = new QHBoxLayout;
layout->addWidget(new QSpinBox(this));
layout->addWidget(new QSpinBox(this));
gridLayout->addLayout(layout, i, k ? k + 1 : 0, 1, 1);
if (!k)
gridLayout->addWidget(new QSpinBox(this), i, 1, 1, 1);
}
gridLayout->addWidget(new QTextEdit(this), i, 3, 1, 1);
gridLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed), i, 4, 1, 1);
}
QPushButton* btn = new QPushButton("set", this);
QWidget* bg = new QWidget(this);
gridLayout->addWidget(btn, 5, 0, 1, 1);
gridLayout->addWidget(bg, 6, 0, 4, 4);
bg->installEventFilter(this);
gridLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding), 6, 0, 1, 1);
QList<QSpinBox*> sbList = findChildren<QSpinBox*>();
for (int value : {/*image0*/ 2, 2, 12, 300, 100, /*image1*/ -2, -2, 12, 300, 100, /*image2*/ -2, 4, 12, 300, 100}) {
QSpinBox* sb = sbList[i++ - 4];
sb->setRange(-500, 500);
sb->setValue(value);
}
QList<QTextEdit*> textEditList = findChildren<QTextEdit*>();
for (const QString& color : {"#ff0000", "#00ff00", "#0000ff"})
textEditList[i++ - 19]->setText(color);
setStyleSheet("QSpinBox { max-width: 42px; } QTextEdit { max-width: 160px; max-height: 24px; }");
connect(btn, &QPushButton::clicked, this, [=] {
QList<Image> imageList = {
{QPoint(sbList[0]->value(), sbList[1]->value()), sbList[2]->value(), QSize(sbList[3]->value(), sbList[4]->value()), textEditList[0]->toPlainText(), 12},
{QPoint(sbList[5]->value(), sbList[6]->value()), sbList[7]->value(), QSize(sbList[8]->value(), sbList[9]->value()), textEditList[1]->toPlainText(), 12},
{QPoint(sbList[10]->value(), sbList[11]->value()), sbList[12]->value(), QSize(sbList[13]->value(), sbList[14]->value()), textEditList[2]->toPlainText(), 12},
{QPoint(sbList[15]->value(), sbList[16]->value()), sbList[17]->value(), QSize(sbList[18]->value(), sbList[19]->value()), textEditList[3]->toPlainText(), 12}
};
createShadowPixmap(imageList);
bg->update();
});
}
bool eventFilter(QObject* obj, QEvent* event)
{
if (event->type() == QEvent::Paint)
{
QRect bgRect(70, 70, 300, 100);
QPainter painter(qobject_cast<QWidget*>(obj));
painter.setRenderHints(QPainter::Antialiasing|QPainter::SmoothPixmapTransform);
QPoint offset = QPoint(bgRect.width()/2 - shadowPixmap.width()/2 + bgRect.x(),
bgRect.height()/2 - shadowPixmap.height()/2 + bgRect.y());
painter.drawPixmap(offset, shadowPixmap);
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(0, 0, 0, 255));
painter.drawRoundedRect(bgRect, 12, 12);
painter.setPen(QPen(QColor(255, 0, 0, 255), 2, Qt::PenStyle::DashDotDotLine)); // Border to see the widget boundaries
painter.setBrush(Qt::NoBrush);
painter.drawRoundedRect(rect().adjusted(1, 1, -1, -1), 12, 12);
}
return QWidget::eventFilter(obj, event);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.resize(450, 450);
w.show();
return a.exec();
}
By playing with the spin boxes values the shadow pixmap is painted on the widget and its easy to see whats going wrong.