File indexing completed on 2024-05-12 15:56:49

0001 /* This file is part of the KDE project
0002  * SPDX-FileCopyrightText: 2008-2009 Jan Hambrecht <jaham@gmx.net>
0003  * SPDX-FileCopyrightText: 2010 Thomas Zander <zander@kde.org>
0004  * SPDX-FileCopyrightText: 2010 Ariya Hidayat <ariya.hidayat@gmail.com>
0005  * SPDX-FileCopyrightText: 2010-2011 Yue Liu <yue.liu@mail.com>
0006  *
0007  * SPDX-License-Identifier: LGPL-2.0-or-later
0008  */
0009 
0010 #include "KoShapeShadow.h"
0011 #include "KoShapeGroup.h"
0012 #include "KoSelection.h"
0013 #include "KoShapeSavingContext.h"
0014 #include "KoShapeStrokeModel.h"
0015 #include "KoShape.h"
0016 #include "KoInsets.h"
0017 #include "KoPathShape.h"
0018 #include <FlakeDebug.h>
0019 #include <QPainter>
0020 #include <QPainterPath>
0021 #include <QAtomicInt>
0022 #include <QImage>
0023 #include <QRectF>
0024 
0025 class Q_DECL_HIDDEN KoShapeShadow::Private
0026 {
0027 public:
0028     Private()
0029             : offset(2, 2), color(Qt::black), blur(8), visible(true), refCount(0) {
0030     }
0031     QPointF offset;
0032     QColor color;
0033     qreal blur;
0034     bool visible;
0035     QAtomicInt refCount;
0036 
0037     /**
0038      * Paints the shadow of the shape group to the buffer image.
0039      * @param group the shape group to paint around
0040      * @param painter the painter to paint on the image
0041      * @param converter to convert between internal and view coordinates.
0042      */
0043     void paintGroupShadow(KoShapeGroup *group, QPainter &painter);
0044     /**
0045      * Paints the shadow of the shape to the buffer image.
0046      * @param shape the shape to paint around
0047      * @param painter the painter to paint on the image
0048      */
0049     void paintShadow(KoShape *shape, QPainter &painter);
0050     void blurShadow(QImage &image, int radius, const QColor& shadowColor);
0051 };
0052 
0053 void KoShapeShadow::Private::paintGroupShadow(KoShapeGroup *group, QPainter &painter)
0054 {
0055     QList<KoShape*> shapes = group->shapes();
0056     Q_FOREACH (KoShape *child, shapes) {
0057         // we paint recursively here, so we do not have to check recursively for visibility
0058         if (!child->isVisible(false))
0059             continue;
0060         painter.save();
0061         //apply group child's transformation
0062         painter.setTransform(child->absoluteTransformation(), true);
0063         paintShadow(child, painter);
0064         painter.restore();
0065     }
0066 }
0067 
0068 void KoShapeShadow::Private::paintShadow(KoShape *shape, QPainter &painter)
0069 {
0070     QPainterPath path(shape->shadowOutline());
0071     if (!path.isEmpty()) {
0072         painter.save();
0073         painter.setBrush(QBrush(color));
0074 
0075         // Make sure the shadow has the same fill rule as the shape.
0076         KoPathShape * pathShape = dynamic_cast<KoPathShape*>(shape);
0077         if (pathShape)
0078             path.setFillRule(pathShape->fillRule());
0079 
0080         painter.drawPath(path);
0081         painter.restore();
0082     }
0083 
0084     if (shape->stroke()) {
0085         shape->stroke()->paint(shape, painter);
0086     }
0087 }
0088 
0089 /* You can also find a BSD version to this method from
0090  * https://github.com/ariya/X2/tree/master/graphics/shadowblur
0091  */
0092 void KoShapeShadow::Private::blurShadow(QImage &image, int radius, const QColor& shadowColor)
0093 {
0094     static const int BlurSumShift = 15;
0095 
0096     // Check http://www.w3.org/TR/SVG/filters.html#
0097     // As noted in the SVG filter specification, ru
0098     // approximates a real gaussian blur nicely.
0099     // See comments in https://bugs.webkit.org/show_bug.cgi?id=40793, it seems sensible
0100     // to follow Skia's limit of 128 pixels for the blur radius.
0101     if (radius > 128)
0102         radius = 128;
0103 
0104     int channels[4] = { 3, 0, 1, 3 };
0105     int dmax = radius >> 1;
0106     int dmin = dmax - 1 + (radius & 1);
0107     if (dmin < 0)
0108         dmin = 0;
0109 
0110     // Two stages: horizontal and vertical
0111     for (int k = 0; k < 2; ++k) {
0112 
0113         unsigned char* pixels = image.bits();
0114         int stride = (k == 0) ? 4 : image.bytesPerLine();
0115         int delta = (k == 0) ? image.bytesPerLine() : 4;
0116         int jfinal = (k == 0) ? image.height() : image.width();
0117         int dim = (k == 0) ? image.width() : image.height();
0118 
0119         for (int j = 0; j < jfinal; ++j, pixels += delta) {
0120 
0121             // For each step, we blur the alpha in a channel and store the result
0122             // in another channel for the subsequent step.
0123             // We use sliding window algorithm to accumulate the alpha values.
0124             // This is much more efficient than computing the sum of each pixels
0125             // covered by the box kernel size for each x.
0126 
0127             for (int step = 0; step < 3; ++step) {
0128                 int side1 = (step == 0) ? dmin : dmax;
0129                 int side2 = (step == 1) ? dmin : dmax;
0130                 int pixelCount = side1 + 1 + side2;
0131                 int invCount = ((1 << BlurSumShift) + pixelCount - 1) / pixelCount;
0132                 int ofs = 1 + side2;
0133                 int alpha1 = pixels[channels[step]];
0134                 int alpha2 = pixels[(dim - 1) * stride + channels[step]];
0135                 unsigned char* ptr = pixels + channels[step + 1];
0136                 unsigned char* prev = pixels + stride + channels[step];
0137                 unsigned char* next = pixels + ofs * stride + channels[step];
0138 
0139                 int i;
0140                 int sum = side1 * alpha1 + alpha1;
0141                 int limit = (dim < side2 + 1) ? dim : side2 + 1;
0142                 for (i = 1; i < limit; ++i, prev += stride)
0143                     sum += *prev;
0144                 if (limit <= side2)
0145                     sum += (side2 - limit + 1) * alpha2;
0146 
0147                 limit = (side1 < dim) ? side1 : dim;
0148                 for (i = 0; i < limit; ptr += stride, next += stride, ++i, ++ofs) {
0149                     *ptr = (sum * invCount) >> BlurSumShift;
0150                     sum += ((ofs < dim) ? *next : alpha2) - alpha1;
0151                 }
0152                 prev = pixels + channels[step];
0153                 for (; ofs < dim; ptr += stride, prev += stride, next += stride, ++i, ++ofs) {
0154                     *ptr = (sum * invCount) >> BlurSumShift;
0155                     sum += (*next) - (*prev);
0156                 }
0157                 for (; i < dim; ptr += stride, prev += stride, ++i) {
0158                     *ptr = (sum * invCount) >> BlurSumShift;
0159                     sum += alpha2 - (*prev);
0160                 }
0161             }
0162         }
0163     }
0164 
0165     // "Colorize" with the right shadow color.
0166     QPainter p(&image);
0167     p.setCompositionMode(QPainter::CompositionMode_SourceIn);
0168     p.fillRect(image.rect(), shadowColor);
0169     p.end();
0170 }
0171 
0172 
0173 // ----------------------------------------------------------------
0174 //                         KoShapeShadow
0175 
0176 
0177 KoShapeShadow::KoShapeShadow()
0178         : d(new Private())
0179 {
0180 }
0181 
0182 KoShapeShadow::~KoShapeShadow()
0183 {
0184     delete d;
0185 }
0186 
0187 KoShapeShadow::KoShapeShadow(const KoShapeShadow &rhs)
0188     : d(new Private(*rhs.d))
0189 {
0190     d->refCount = 0;
0191 }
0192 
0193 KoShapeShadow& KoShapeShadow::operator=(const KoShapeShadow &rhs)
0194 {
0195     *d = *rhs.d;
0196     d->refCount = 0;
0197     return *this;
0198 }
0199 
0200 void KoShapeShadow::paint(KoShape *shape, QPainter &painter)
0201 {
0202     if (! d->visible)
0203         return;
0204 
0205     // So the approach we are taking here is to draw into a buffer image the size of boundingRect
0206     // We offset by the shadow offset at the time we draw into the buffer
0207     // Then we filter the image and draw it at the position of the bounding rect on canvas
0208 
0209     QTransform documentToView = painter.transform();
0210 
0211     //the boundingRect of the shape or the KoSelection boundingRect of the group
0212     QRectF shadowRect = shape->boundingRect();
0213     QRectF zoomedClipRegion = documentToView.mapRect(shadowRect);
0214 
0215     // Init the buffer image
0216     QImage sourceGraphic(zoomedClipRegion.size().toSize(), QImage::Format_ARGB32_Premultiplied);
0217     sourceGraphic.fill(qRgba(0,0,0,0));
0218     // Init the buffer painter
0219     QPainter imagePainter(&sourceGraphic);
0220     imagePainter.setPen(Qt::NoPen);
0221     imagePainter.setBrush(Qt::NoBrush);
0222     imagePainter.setRenderHint(QPainter::Antialiasing, painter.testRenderHint(QPainter::Antialiasing));
0223     // Since our imagebuffer and the canvas don't align we need to offset our drawings
0224     imagePainter.translate(-1.0f*documentToView.map(shadowRect.topLeft()));
0225 
0226     // Handle the shadow offset
0227     imagePainter.translate(documentToView.map(offset()));
0228 
0229     KoShapeGroup *group = dynamic_cast<KoShapeGroup*>(shape);
0230     if (group) {
0231         d->paintGroupShadow(group, imagePainter);
0232     } else {
0233         //apply shape's transformation
0234         imagePainter.setTransform(shape->absoluteTransformation(), true);
0235 
0236         d->paintShadow(shape, imagePainter);
0237     }
0238     imagePainter.end();
0239 
0240     // Blur the shadow (well the entire buffer)
0241     d->blurShadow(sourceGraphic, qRound(documentToView.m11() * d->blur), d->color);
0242 
0243     // Paint the result
0244     painter.save();
0245     // The painter is initialized for us with canvas transform 'plus' shape transform
0246     // we are only interested in the canvas transform so 'subtract' the shape transform part
0247     painter.setTransform(shape->absoluteTransformation().inverted() * painter.transform());
0248     painter.drawImage(zoomedClipRegion.topLeft(), sourceGraphic);
0249     painter.restore();
0250 }
0251 
0252 void KoShapeShadow::setOffset(const QPointF & offset)
0253 {
0254     d->offset = offset;
0255 }
0256 
0257 QPointF KoShapeShadow::offset() const
0258 {
0259     return d->offset;
0260 }
0261 
0262 void KoShapeShadow::setColor(const QColor &color)
0263 {
0264     d->color = color;
0265 }
0266 
0267 QColor KoShapeShadow::color() const
0268 {
0269     return d->color;
0270 }
0271 
0272 void KoShapeShadow::setBlur(qreal blur)
0273 {
0274     // force positive blur radius
0275     d->blur = qAbs(blur);
0276 }
0277 
0278 qreal KoShapeShadow::blur() const
0279 {
0280     return d->blur;
0281 }
0282 
0283 void KoShapeShadow::setVisible(bool visible)
0284 {
0285     d->visible = visible;
0286 }
0287 
0288 bool KoShapeShadow::isVisible() const
0289 {
0290     return d->visible;
0291 }
0292 
0293 void KoShapeShadow::insets(KoInsets &insets) const
0294 {
0295     if (!d->visible) {
0296         insets.top = 0;
0297         insets.bottom = 0;
0298         insets.left = 0;
0299         insets.right = 0;
0300         return;
0301     }
0302 
0303     qreal expand = d->blur;
0304 
0305     insets.left = (d->offset.x() < 0.0) ? qAbs(d->offset.x()) : 0.0;
0306     insets.top = (d->offset.y() < 0.0) ? qAbs(d->offset.y()) : 0.0;
0307     insets.right = (d->offset.x() > 0.0) ? d->offset.x() : 0.0;
0308     insets.bottom = (d->offset.y() > 0.0) ? d->offset.y() : 0.0;
0309 
0310     insets.left += expand;
0311     insets.top += expand;
0312     insets.right += expand;
0313     insets.bottom += expand;
0314 }
0315 
0316 bool KoShapeShadow::ref()
0317 {
0318     return d->refCount.ref();
0319 }
0320 
0321 bool KoShapeShadow::deref()
0322 {
0323     return d->refCount.deref();
0324 }
0325 
0326 int KoShapeShadow::useCount() const
0327 {
0328     return d->refCount;
0329 }