#include "shadowpixmap.h" #include #include #include #include #include #include #include namespace { // Width of the uniform, stretchable interior between the rounded corners. // Kept small (the centre is a flat solid colour, so 4 logical px is plenty). constexpr int kCenter = 4; int borderFor(int blurRadius, int cornerRadius) { // Falloff reach beyond the object edge, plus the corner radius which lives // inside the fixed (non-stretched) border so corners never distort. return (blurRadius + 1) + cornerRadius; } int totalFor(int blurRadius, int cornerRadius) { return 2 * borderFor(blurRadius, cornerRadius) + kCenter; } QString cacheFilePath(int blurRadius, int cornerRadius, const QColor &color, qreal dpr) { const QString dir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/shadows"; QDir().mkpath(dir); // dpr folded in at x100 so e.g. 1.5 -> 150; colour as rrggbbaa. const QString name = QString("shadow_b%1_c%2_%3_d%4.png") .arg(blurRadius) .arg(cornerRadius) .arg(static_cast(color.rgba()), 8, 16, QLatin1Char('0')) .arg(qRound(dpr * 100)); return dir + '/' + name; } // Render the rounded rect, then blur it off-screen via QGraphicsBlurEffect. // Done once per cache key; the cost here is irrelevant to runtime frame rate. QImage bake(int blurRadius, int cornerRadius, const QColor &color, qreal dpr) { const int total = totalFor(blurRadius, cornerRadius); const int border = borderFor(blurRadius, cornerRadius); const qreal s = dpr; const int physTotal = qMax(1, qRound(total * s)); QImage shape(physTotal, physTotal, QImage::Format_ARGB32_Premultiplied); shape.fill(Qt::transparent); { QPainter p(&shape); p.setRenderHint(QPainter::Antialiasing, true); p.setPen(Qt::NoPen); p.setBrush(color); // Object footprint: inset by the falloff reach (border - corner) so the // blur has room to fade on every side. const qreal reach = (border - cornerRadius) * s; const qreal objSide = (total - 2 * (border - cornerRadius)) * s; p.drawRoundedRect(QRectF(reach, reach, objSide, objSide), cornerRadius * s, cornerRadius * s); } QGraphicsScene scene; auto *item = scene.addPixmap(QPixmap::fromImage(shape)); auto *blur = new QGraphicsBlurEffect; blur->setBlurRadius(blurRadius * s); blur->setBlurHints(QGraphicsBlurEffect::QualityHint); item->setGraphicsEffect(blur); QImage out(physTotal, physTotal, QImage::Format_ARGB32_Premultiplied); out.fill(Qt::transparent); { QPainter op(&out); scene.render(&op, QRectF(0, 0, physTotal, physTotal), QRectF(0, 0, physTotal, physTotal)); } return out; } } // namespace namespace ShadowPixmap { NinePatchShadow make(int blurRadius, int cornerRadius, const QColor &color, qreal dpr) { NinePatchShadow result; result.border = borderFor(blurRadius, cornerRadius); result.total = totalFor(blurRadius, cornerRadius); result.dpr = dpr; const QString path = cacheFilePath(blurRadius, cornerRadius, color, dpr); QPixmap cached; if (cached.load(path, "PNG") && !cached.isNull()) { result.pixmap = cached; return result; } const QImage baked = bake(blurRadius, cornerRadius, color, dpr); result.pixmap = QPixmap::fromImage(baked); baked.save(path, "PNG"); // best-effort cache; failure just re-bakes next time return result; } void drawNinePatch(QPainter *painter, const QRect &target, const NinePatchShadow &patch) { if (!painter || patch.isNull()) { return; } const int b = patch.border; const int t = patch.total; const qreal s = patch.dpr; const QRect r = target; // Degenerate target (smaller than the two fixed borders): nothing sensible // to slice, skip rather than draw garbage. if (r.width() < 2 * b || r.height() < 2 * b) { return; } // 3x3 grid: corners fixed, edges/centre stretched. Target columns/rows are // logical; source columns/rows are physical (pixmap carries no dpr). const int xt[4] = {r.left(), r.left() + b, r.right() + 1 - b, r.right() + 1}; const int yt[4] = {r.top(), r.top() + b, r.bottom() + 1 - b, r.bottom() + 1}; const qreal xs[4] = {0.0, b * s, (t - b) * s, t * s}; const qreal ys[4] = {0.0, b * s, (t - b) * s, t * s}; for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { const QRectF targetCell(xt[i], yt[j], xt[i + 1] - xt[i], yt[j + 1] - yt[j]); const QRectF sourceCell(xs[i], ys[j], xs[i + 1] - xs[i], ys[j + 1] - ys[j]); painter->drawPixmap(targetCell, patch.pixmap, sourceCell); } } } } // namespace ShadowPixmap