Krita Source Code Documentation
Loading...
Searching...
No Matches
KoShapeShadow.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 * SPDX-FileCopyrightText: 2008-2009 Jan Hambrecht <jaham@gmx.net>
3 * SPDX-FileCopyrightText: 2010 Thomas Zander <zander@kde.org>
4 * SPDX-FileCopyrightText: 2010 Ariya Hidayat <ariya.hidayat@gmail.com>
5 * SPDX-FileCopyrightText: 2010-2011 Yue Liu <yue.liu@mail.com>
6 *
7 * SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9
10#include "KoShapeShadow.h"
11#include "KoShapeGroup.h"
12#include "KoSelection.h"
14#include "KoShapeStrokeModel.h"
15#include "KoShape.h"
16#include "KoInsets.h"
17#include "KoPathShape.h"
18#include <FlakeDebug.h>
19#include <QPainter>
20#include <QPainterPath>
21#include <QAtomicInt>
22#include <QImage>
23#include <QRectF>
24
25class Q_DECL_HIDDEN KoShapeShadow::Private
26{
27public:
29 : offset(2, 2), color(Qt::black), blur(8), visible(true), refCount(0) {
30 }
31 QPointF offset;
32 QColor color;
33 qreal blur;
34 bool visible;
35 QAtomicInt refCount;
36
43 void paintGroupShadow(KoShapeGroup *group, QPainter &painter);
49 void paintShadow(KoShape *shape, QPainter &painter);
50 void blurShadow(QImage &image, int radius, const QColor& shadowColor);
51};
52
53void KoShapeShadow::Private::paintGroupShadow(KoShapeGroup *group, QPainter &painter)
54{
55 QList<KoShape*> shapes = group->shapes();
56 Q_FOREACH (KoShape *child, shapes) {
57 // we paint recursively here, so we do not have to check recursively for visibility
58 if (!child->isVisible(false))
59 continue;
60 painter.save();
61 //apply group child's transformation
62 painter.setTransform(child->absoluteTransformation(), true);
63 paintShadow(child, painter);
64 painter.restore();
65 }
66}
67
68void KoShapeShadow::Private::paintShadow(KoShape *shape, QPainter &painter)
69{
70 QPainterPath path(shape->shadowOutline());
71 if (!path.isEmpty()) {
72 painter.save();
73 painter.setBrush(QBrush(color));
74
75 // Make sure the shadow has the same fill rule as the shape.
76 KoPathShape * pathShape = dynamic_cast<KoPathShape*>(shape);
77 if (pathShape)
78 path.setFillRule(pathShape->fillRule());
79
80 painter.drawPath(path);
81 painter.restore();
82 }
83
84 if (shape->stroke()) {
85 shape->stroke()->paint(shape, painter);
86 }
87}
88
89/* You can also find a BSD version to this method from
90 * https://github.com/ariya/X2/tree/master/graphics/shadowblur
91 */
92void KoShapeShadow::Private::blurShadow(QImage &image, int radius, const QColor& shadowColor)
93{
94 static const int BlurSumShift = 15;
95
96 // Check http://www.w3.org/TR/SVG/filters.html#
97 // As noted in the SVG filter specification, ru
98 // approximates a real gaussian blur nicely.
99 // See comments in https://bugs.webkit.org/show_bug.cgi?id=40793, it seems sensible
100 // to follow Skia's limit of 128 pixels for the blur radius.
101 if (radius > 128)
102 radius = 128;
103
104 int channels[4] = { 3, 0, 1, 3 };
105 int dmax = radius >> 1;
106 int dmin = dmax - 1 + (radius & 1);
107 if (dmin < 0)
108 dmin = 0;
109
110 // Two stages: horizontal and vertical
111 for (int k = 0; k < 2; ++k) {
112
113 unsigned char* pixels = image.bits();
114 int stride = (k == 0) ? 4 : image.bytesPerLine();
115 int delta = (k == 0) ? image.bytesPerLine() : 4;
116 int jfinal = (k == 0) ? image.height() : image.width();
117 int dim = (k == 0) ? image.width() : image.height();
118
119 for (int j = 0; j < jfinal; ++j, pixels += delta) {
120
121 // For each step, we blur the alpha in a channel and store the result
122 // in another channel for the subsequent step.
123 // We use sliding window algorithm to accumulate the alpha values.
124 // This is much more efficient than computing the sum of each pixels
125 // covered by the box kernel size for each x.
126
127 for (int step = 0; step < 3; ++step) {
128 int side1 = (step == 0) ? dmin : dmax;
129 int side2 = (step == 1) ? dmin : dmax;
130 int pixelCount = side1 + 1 + side2;
131 int invCount = ((1 << BlurSumShift) + pixelCount - 1) / pixelCount;
132 int ofs = 1 + side2;
133 int alpha1 = pixels[channels[step]];
134 int alpha2 = pixels[(dim - 1) * stride + channels[step]];
135 unsigned char* ptr = pixels + channels[step + 1];
136 unsigned char* prev = pixels + stride + channels[step];
137 unsigned char* next = pixels + ofs * stride + channels[step];
138
139 int i;
140 int sum = side1 * alpha1 + alpha1;
141 int limit = (dim < side2 + 1) ? dim : side2 + 1;
142 for (i = 1; i < limit; ++i, prev += stride)
143 sum += *prev;
144 if (limit <= side2)
145 sum += (side2 - limit + 1) * alpha2;
146
147 limit = (side1 < dim) ? side1 : dim;
148 for (i = 0; i < limit; ptr += stride, next += stride, ++i, ++ofs) {
149 *ptr = (sum * invCount) >> BlurSumShift;
150 sum += ((ofs < dim) ? *next : alpha2) - alpha1;
151 }
152 prev = pixels + channels[step];
153 for (; ofs < dim; ptr += stride, prev += stride, next += stride, ++i, ++ofs) {
154 *ptr = (sum * invCount) >> BlurSumShift;
155 sum += (*next) - (*prev);
156 }
157 for (; i < dim; ptr += stride, prev += stride, ++i) {
158 *ptr = (sum * invCount) >> BlurSumShift;
159 sum += alpha2 - (*prev);
160 }
161 }
162 }
163 }
164
165 // "Colorize" with the right shadow color.
166 QPainter p(&image);
167 p.setCompositionMode(QPainter::CompositionMode_SourceIn);
168 p.fillRect(image.rect(), shadowColor);
169 p.end();
170}
171
172
173// ----------------------------------------------------------------
174// KoShapeShadow
175
176
178 : d(new Private())
179{
180}
181
183{
184 delete d;
185}
186
188 : d(new Private(*rhs.d))
189{
190 d->refCount = 0;
191}
192
194{
195 *d = *rhs.d;
196 d->refCount = 0;
197 return *this;
198}
199
200void KoShapeShadow::paint(KoShape *shape, QPainter &painter)
201{
202 if (! d->visible)
203 return;
204
205 // So the approach we are taking here is to draw into a buffer image the size of boundingRect
206 // We offset by the shadow offset at the time we draw into the buffer
207 // Then we filter the image and draw it at the position of the bounding rect on canvas
208
209 QTransform documentToView = painter.transform();
210
211 //the boundingRect of the shape or the KoSelection boundingRect of the group
212 QRectF shadowRect = shape->boundingRect();
213 QRectF zoomedClipRegion = documentToView.mapRect(shadowRect);
214
215 // Init the buffer image
216 QImage sourceGraphic(zoomedClipRegion.size().toSize(), QImage::Format_ARGB32_Premultiplied);
217 sourceGraphic.fill(qRgba(0,0,0,0));
218 // Init the buffer painter
219 QPainter imagePainter(&sourceGraphic);
220 imagePainter.setPen(Qt::NoPen);
221 imagePainter.setBrush(Qt::NoBrush);
222 imagePainter.setRenderHint(QPainter::Antialiasing, painter.testRenderHint(QPainter::Antialiasing));
223 // Since our image buffer and the canvas don't align we need to offset our drawings
224 imagePainter.translate(-1.0f*documentToView.map(shadowRect.topLeft()));
225
226 // Handle the shadow offset
227 imagePainter.translate(documentToView.map(offset()));
228
229 KoShapeGroup *group = dynamic_cast<KoShapeGroup*>(shape);
230 if (group) {
231 d->paintGroupShadow(group, imagePainter);
232 } else {
233 //apply shape's transformation
234 imagePainter.setTransform(shape->absoluteTransformation(), true);
235
236 d->paintShadow(shape, imagePainter);
237 }
238 imagePainter.end();
239
240 // Blur the shadow (well the entire buffer)
241 d->blurShadow(sourceGraphic, qRound(documentToView.m11() * d->blur), d->color);
242
243 // Paint the result
244 painter.save();
245 // The painter is initialized for us with canvas transform 'plus' shape transform
246 // we are only interested in the canvas transform so 'subtract' the shape transform part
247 painter.setTransform(shape->absoluteTransformation().inverted() * painter.transform());
248 painter.drawImage(zoomedClipRegion.topLeft(), sourceGraphic);
249 painter.restore();
250}
251
252void KoShapeShadow::setOffset(const QPointF & offset)
253{
254 d->offset = offset;
255}
256
257QPointF KoShapeShadow::offset() const
258{
259 return d->offset;
260}
261
262void KoShapeShadow::setColor(const QColor &color)
263{
264 d->color = color;
265}
266
267QColor KoShapeShadow::color() const
268{
269 return d->color;
270}
271
273{
274 // force positive blur radius
275 d->blur = qAbs(blur);
276}
277
278qreal KoShapeShadow::blur() const
279{
280 return d->blur;
281}
282
284{
285 d->visible = visible;
286}
287
289{
290 return d->visible;
291}
292
294{
295 if (!d->visible) {
296 insets.top = 0;
297 insets.bottom = 0;
298 insets.left = 0;
299 insets.right = 0;
300 return;
301 }
302
303 qreal expand = d->blur;
304
305 insets.left = (d->offset.x() < 0.0) ? qAbs(d->offset.x()) : 0.0;
306 insets.top = (d->offset.y() < 0.0) ? qAbs(d->offset.y()) : 0.0;
307 insets.right = (d->offset.x() > 0.0) ? d->offset.x() : 0.0;
308 insets.bottom = (d->offset.y() > 0.0) ? d->offset.y() : 0.0;
309
310 insets.left += expand;
311 insets.top += expand;
312 insets.right += expand;
313 insets.bottom += expand;
314}
315
317{
318 return d->refCount.ref();
319}
320
322{
323 return d->refCount.deref();
324}
325
327{
328 return d->refCount;
329}
const Params2D p
void expand(ExpansionStrategy &expansionStrategy, KoUpdater *progressUpdater)
The position of a path point within a path shape.
Definition KoPathShape.h:63
Qt::FillRule fillRule() const
Returns the fill rule for the path object.
void setFillRule(Qt::FillRule fillRule)
Sets the fill rule to be used for painting the background.
QList< KoShape * > shapes() const
void blurShadow(QImage &image, int radius, const QColor &shadowColor)
bool isVisible() const
Returns if shadow is visible.
QAtomicInt refCount
KoShapeShadow & operator=(const KoShapeShadow &rhs)
void setOffset(const QPointF &offset)
Private *const d
void setBlur(qreal blur)
void setColor(const QColor &color)
void paintShadow(KoShape *shape, QPainter &painter)
int useCount() const
Return the usage count.
void paint(KoShape *shape, QPainter &painter)
void insets(KoInsets &insets) const
Fills the insets object with the space the shadow takes around a shape.
void paintGroupShadow(KoShapeGroup *group, QPainter &painter)
void setVisible(bool visible)
Sets the shadow visibility.
virtual KoShapeStrokeModelSP stroke() const
Definition KoShape.cpp:1067
virtual QRectF boundingRect() const
Get the bounding box of the shape.
Definition KoShape.cpp:335
QTransform absoluteTransformation() const
Definition KoShape.cpp:382
virtual QPainterPath shadowOutline() const
Definition KoShape.cpp:644
bool isVisible(bool recursive=true) const
Definition KoShape.cpp:979
QAction * next(const QObject *recvr, const char *slot, QObject *parent)