1 /*
2  *  Copyright (c) 1999 Matthias Elter  <me@kde.org>
3  *  Copyright (c) 2003 Patrick Julien  <freak@codepimps.org>
4  *  Copyright (c) 2004-2008 Boudewijn Rempt <boud@valdyas.org>
5  *  Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
6  *  Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be>
7  *  Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
8  *
9  *  This program is free software; you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License as published by
11  *  the Free Software Foundation; either version 2 of the License, or
12  *  (at your option) any later version.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program; if not, write to the Free Software
21  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22  */
23 
24 #include "kis_brush.h"
25 
26 #include <QDomElement>
27 #include <QFile>
28 #include <QPainterPath>
29 #include <QPoint>
30 #include <QFileInfo>
31 #include <QBuffer>
32 
33 #include <kis_debug.h>
34 #include <klocalizedstring.h>
35 
36 #include <KoColor.h>
37 #include <KoColorSpaceMaths.h>
38 #include <KoColorSpaceRegistry.h>
39 
40 #include "kis_datamanager.h"
41 #include "kis_paint_device.h"
42 #include "kis_global.h"
43 #include "kis_boundary.h"
44 #include "kis_image.h"
45 #include "kis_iterator_ng.h"
46 #include "kis_brush_registry.h"
47 #include <brushengine/kis_paint_information.h>
48 #include <kis_fixed_paint_device.h>
49 #include <kis_qimage_pyramid.h>
50 #include <KisSharedQImagePyramid.h>
51 #include <brushengine/kis_paintop_lod_limitations.h>
52 #include <resources/KoAbstractGradient.h>
53 #include <resources/KoCachedGradient.h>
54 #include <resources/KoResource.h>
55 #include <KoResourceServerProvider.h>
56 
57 
~ColoringInformation()58 KisBrush::ColoringInformation::~ColoringInformation()
59 {
60 }
61 
PlainColoringInformation(const quint8 * color)62 KisBrush::PlainColoringInformation::PlainColoringInformation(const quint8* color) : m_color(color)
63 {
64 }
65 
~PlainColoringInformation()66 KisBrush::PlainColoringInformation::~PlainColoringInformation()
67 {
68 }
69 
color() const70 const quint8* KisBrush::PlainColoringInformation::color() const
71 {
72     return m_color;
73 }
74 
nextColumn()75 void KisBrush::PlainColoringInformation::nextColumn()
76 {
77 }
78 
nextRow()79 void KisBrush::PlainColoringInformation::nextRow()
80 {
81 }
82 
PaintDeviceColoringInformation(const KisPaintDeviceSP source,int width)83 KisBrush::PaintDeviceColoringInformation::PaintDeviceColoringInformation(const KisPaintDeviceSP source, int width)
84     : m_source(source)
85     , m_iterator(m_source->createHLineConstIteratorNG(0, 0, width))
86 {
87 }
88 
~PaintDeviceColoringInformation()89 KisBrush::PaintDeviceColoringInformation::~PaintDeviceColoringInformation()
90 {
91 }
92 
color() const93 const quint8* KisBrush::PaintDeviceColoringInformation::color() const
94 {
95     return m_iterator->oldRawData();
96 }
97 
nextColumn()98 void KisBrush::PaintDeviceColoringInformation::nextColumn()
99 {
100     m_iterator->nextPixel();
101 }
nextRow()102 void KisBrush::PaintDeviceColoringInformation::nextRow()
103 {
104     m_iterator->nextRow();
105 }
106 
107 
108 struct KisBrush::Private {
PrivateKisBrush::Private109     Private()
110         : boundary(0)
111         , angle(0)
112         , scale(1.0)
113         , brushType(INVALID)
114         , brushApplication(ALPHAMASK)
115         , gradient(0)
116         , autoSpacingActive(false)
117         , autoSpacingCoeff(1.0)
118         , threadingAllowed(true)
119     {
120     }
121 
~PrivateKisBrush::Private122     ~Private() {
123         delete boundary;
124     }
125 
126     mutable KisBoundary* boundary;
127     qreal angle;
128     qreal scale;
129     enumBrushType brushType;
130     enumBrushApplication brushApplication;
131 
132     qint32 width;
133     qint32 height;
134     double spacing;
135     QPointF hotSpot;
136 
137     mutable QSharedPointer<KisSharedQImagePyramid> brushPyramid;
138 
139     QImage brushTipImage;
140 
141     const KoAbstractGradient* gradient;
142     QScopedPointer<KoCachedGradient> cachedGradient;
143 
144     bool autoSpacingActive;
145     qreal autoSpacingCoeff;
146 
147     bool threadingAllowed;
148 };
149 
KisBrush()150 KisBrush::KisBrush()
151     : KoResource(QString())
152     , d(new Private)
153 {
154 }
155 
KisBrush(const QString & filename)156 KisBrush::KisBrush(const QString& filename)
157     : KoResource(filename)
158     , d(new Private)
159 {
160 }
161 
KisBrush(const KisBrush & rhs)162 KisBrush::KisBrush(const KisBrush& rhs)
163     : KoResource(QString())
164     , KisShared()
165     , d(new Private)
166 {
167     setBrushTipImage(rhs.brushTipImage());
168     d->brushType = rhs.d->brushType;
169     d->brushApplication = rhs.d->brushApplication;
170     d->width = rhs.d->width;
171     d->height = rhs.d->height;
172     d->spacing = rhs.d->spacing;
173     d->hotSpot = rhs.d->hotSpot;
174     d->angle = rhs.d->angle;
175     d->scale = rhs.d->scale;
176     d->autoSpacingActive = rhs.d->autoSpacingActive;
177     d->autoSpacingCoeff = rhs.d->autoSpacingCoeff;
178     d->threadingAllowed = rhs.d->threadingAllowed;
179     d->gradient = rhs.d->gradient;
180     if (rhs.d->cachedGradient) {
181         d->cachedGradient.reset(static_cast<KoCachedGradient*>(rhs.d->cachedGradient->clone()));
182     }
183     setFilename(rhs.filename());
184 
185     /**
186      * Be careful! The pyramid is shared between two brush objects,
187      * therefore you cannot change it, only recreate! That is the
188      * reason why it is defined as const!
189      */
190     d->brushPyramid = rhs.d->brushPyramid;
191 
192     // don't copy the boundary, it will be regenerated -- see bug 291910
193 }
194 
~KisBrush()195 KisBrush::~KisBrush()
196 {
197     delete d;
198 }
199 
brushTipImage() const200 QImage KisBrush::brushTipImage() const
201 {
202     if (d->brushTipImage.isNull()) {
203         const_cast<KisBrush*>(this)->load();
204     }
205     return d->brushTipImage;
206 }
207 
width() const208 qint32 KisBrush::width() const
209 {
210     return d->width;
211 }
212 
setWidth(qint32 width)213 void KisBrush::setWidth(qint32 width)
214 {
215     d->width = width;
216 }
217 
height() const218 qint32 KisBrush::height() const
219 {
220     return d->height;
221 }
222 
setHeight(qint32 height)223 void KisBrush::setHeight(qint32 height)
224 {
225     d->height = height;
226 }
227 
setHotSpot(QPointF pt)228 void KisBrush::setHotSpot(QPointF pt)
229 {
230     double x = pt.x();
231     double y = pt.y();
232 
233     if (x < 0)
234         x = 0;
235     else if (x >= width())
236         x = width() - 1;
237 
238     if (y < 0)
239         y = 0;
240     else if (y >= height())
241         y = height() - 1;
242 
243     d->hotSpot = QPointF(x, y);
244 }
245 
hotSpot(KisDabShape const & shape,const KisPaintInformation & info) const246 QPointF KisBrush::hotSpot(KisDabShape const& shape, const KisPaintInformation& info) const
247 {
248     Q_UNUSED(info);
249 
250     QSizeF metric = characteristicSize(shape);
251 
252     qreal w = metric.width();
253     qreal h = metric.height();
254 
255     // The smallest brush we can produce is a single pixel.
256     if (w < 1) {
257         w = 1;
258     }
259 
260     if (h < 1) {
261         h = 1;
262     }
263 
264     // XXX: This should take d->hotSpot into account, though it
265     // isn't specified by gimp brushes so it would default to the center
266     // anyway.
267     QPointF p(w / 2, h / 2);
268     return p;
269 }
270 
setBrushApplication(enumBrushApplication brushApplication)271 void KisBrush::setBrushApplication(enumBrushApplication brushApplication)
272 {
273     d->brushApplication = brushApplication;
274     clearBrushPyramid();
275 }
276 
brushApplication() const277 enumBrushApplication KisBrush::brushApplication() const
278 {
279     return d->brushApplication;
280 }
281 
preserveLightness() const282 bool KisBrush::preserveLightness() const
283 {
284     return d->brushApplication == LIGHTNESSMAP;
285 }
286 
applyingGradient() const287 bool KisBrush::applyingGradient() const
288 {
289     return d->brushApplication == GRADIENTMAP;
290 }
291 
setGradient(const KoAbstractGradient * gradient)292 void KisBrush::setGradient(const KoAbstractGradient* gradient) {
293     if (gradient && gradient->valid()) {
294         d->gradient = gradient;
295 
296         if (!d->cachedGradient) {
297             d->cachedGradient.reset(new KoCachedGradient(d->gradient, 256, d->gradient->colorSpace()));
298         } else {
299             d->cachedGradient->setGradient(d->gradient, 256, d->gradient->colorSpace());
300         }
301     }
302 }
303 
isPiercedApprox() const304 bool KisBrush::isPiercedApprox() const
305 {
306     QImage image = brushTipImage();
307 
308     qreal w = image.width();
309     qreal h = image.height();
310 
311     qreal xPortion = qMin(0.1, 5.0 / w);
312     qreal yPortion = qMin(0.1, 5.0 / h);
313 
314     int x0 = std::floor((0.5 - xPortion) * w);
315     int x1 = std::ceil((0.5 + xPortion) * w);
316 
317     int y0 = std::floor((0.5 - yPortion) * h);
318     int y1 = std::ceil((0.5 + yPortion) * h);
319 
320     const int maxNumSamples = (x1 - x0 + 1) * (y1 - y0 + 1);
321     const int failedPixelsThreshold = 0.1 * maxNumSamples;
322     const int thresholdValue = 0.95 * 255;
323     int failedPixels = 0;
324 
325     for (int y = y0; y <= y1; y++) {
326         for (int x = x0; x <= x1; x++) {
327             QRgb pixel = image.pixel(x,y);
328 
329             if (qRed(pixel) > thresholdValue) {
330                 failedPixels++;
331             }
332         }
333     }
334 
335     return failedPixels > failedPixelsThreshold;
336 }
337 
canPaintFor(const KisPaintInformation &)338 bool KisBrush::canPaintFor(const KisPaintInformation& /*info*/)
339 {
340     return true;
341 }
342 
setBrushTipImage(const QImage & image)343 void KisBrush::setBrushTipImage(const QImage& image)
344 {
345     d->brushTipImage = image;
346 
347     if (!image.isNull()) {
348         if (image.width() > 128 || image.height() > 128) {
349             KoResource::setImage(image.scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation));
350         }
351         else {
352             KoResource::setImage(image);
353         }
354         setWidth(image.width());
355         setHeight(image.height());
356     }
357     clearBrushPyramid();
358 
359 }
360 
setBrushType(enumBrushType type)361 void KisBrush::setBrushType(enumBrushType type)
362 {
363     d->brushType = type;
364 }
365 
brushType() const366 enumBrushType KisBrush::brushType() const
367 {
368     return d->brushType;
369 }
370 
predefinedBrushToXML(const QString & type,QDomElement & e) const371 void KisBrush::predefinedBrushToXML(const QString &type, QDomElement& e) const
372 {
373     e.setAttribute("type", type);
374     e.setAttribute("filename", shortFilename());
375     e.setAttribute("spacing", QString::number(spacing()));
376     e.setAttribute("useAutoSpacing", QString::number(autoSpacingActive()));
377     e.setAttribute("autoSpacingCoeff", QString::number(autoSpacingCoeff()));
378     e.setAttribute("angle", QString::number(angle()));
379     e.setAttribute("scale", QString::number(scale()));
380     e.setAttribute("brushApplication", QString::number((int)brushApplication()));
381 }
382 
toXML(QDomDocument &,QDomElement & element) const383 void KisBrush::toXML(QDomDocument& /*document*/ , QDomElement& element) const
384 {
385     element.setAttribute("BrushVersion", "2");
386 }
387 
fromXML(const QDomElement & element)388 KisBrushSP KisBrush::fromXML(const QDomElement& element)
389 {
390     KisBrushSP brush = KisBrushRegistry::instance()->createBrush(element);
391     if (brush && element.attribute("BrushVersion", "1") == "1") {
392         brush->setScale(brush->scale() * 2.0);
393     }
394     return brush;
395 }
396 
characteristicSize(KisDabShape const & shape) const397 QSizeF KisBrush::characteristicSize(KisDabShape const& shape) const
398 {
399     KisDabShape normalizedShape(
400          shape.scale() * d->scale,
401          shape.ratio(),
402          normalizeAngle(shape.rotation() + d->angle));
403     return KisQImagePyramid::characteristicSize(
404         QSize(width(), height()), normalizedShape);
405 }
406 
maskWidth(KisDabShape const & shape,qreal subPixelX,qreal subPixelY,const KisPaintInformation & info) const407 qint32 KisBrush::maskWidth(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const
408 {
409     Q_UNUSED(info);
410 
411     qreal angle = normalizeAngle(shape.rotation() + d->angle);
412     qreal scale = shape.scale() * d->scale;
413 
414     return KisQImagePyramid::imageSize(QSize(width(), height()),
415                                        KisDabShape(scale, shape.ratio(), angle),
416                                        subPixelX, subPixelY).width();
417 }
418 
maskHeight(KisDabShape const & shape,qreal subPixelX,qreal subPixelY,const KisPaintInformation & info) const419 qint32 KisBrush::maskHeight(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const
420 {
421     Q_UNUSED(info);
422 
423     qreal angle = normalizeAngle(shape.rotation() + d->angle);
424     qreal scale = shape.scale() * d->scale;
425 
426     return KisQImagePyramid::imageSize(QSize(width(), height()),
427                                        KisDabShape(scale, shape.ratio(), angle),
428                                        subPixelX, subPixelY).height();
429 }
430 
maskAngle(double angle) const431 double KisBrush::maskAngle(double angle) const
432 {
433     return normalizeAngle(angle + d->angle);
434 }
435 
brushIndex() const436 quint32 KisBrush::brushIndex() const
437 {
438     return 0;
439 }
440 
setSpacing(double s)441 void KisBrush::setSpacing(double s)
442 {
443     if (s < 0.02) s = 0.02;
444     d->spacing = s;
445 }
446 
spacing() const447 double KisBrush::spacing() const
448 {
449     return d->spacing;
450 }
451 
setAutoSpacing(bool active,qreal coeff)452 void KisBrush::setAutoSpacing(bool active, qreal coeff)
453 {
454     d->autoSpacingCoeff = coeff;
455     d->autoSpacingActive = active;
456 }
457 
autoSpacingActive() const458 bool KisBrush::autoSpacingActive() const
459 {
460     return d->autoSpacingActive;
461 }
462 
autoSpacingCoeff() const463 qreal KisBrush::autoSpacingCoeff() const
464 {
465     return d->autoSpacingCoeff;
466 }
467 
notifyStrokeStarted()468 void KisBrush::notifyStrokeStarted()
469 {
470 }
471 
prepareForSeqNo(const KisPaintInformation & info,int seqNo)472 void KisBrush::prepareForSeqNo(const KisPaintInformation &info, int seqNo)
473 {
474     Q_UNUSED(info);
475     Q_UNUSED(seqNo);
476 }
477 
setThreadingAllowed(bool value)478 void KisBrush::setThreadingAllowed(bool value)
479 {
480     d->threadingAllowed = value;
481 }
482 
threadingAllowed() const483 bool KisBrush::threadingAllowed() const
484 {
485     return d->threadingAllowed;
486 }
487 
clearBrushPyramid()488 void KisBrush::clearBrushPyramid()
489 {
490     d->brushPyramid.reset(new KisSharedQImagePyramid());
491 }
492 
mask(KisFixedPaintDeviceSP dst,const KoColor & color,KisDabShape const & shape,const KisPaintInformation & info,double subPixelX,double subPixelY,qreal softnessFactor,qreal lightnessStrength) const493 void KisBrush::mask(KisFixedPaintDeviceSP dst, const KoColor& color, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor, qreal lightnessStrength) const
494 {
495     PlainColoringInformation pci(color.data());
496     generateMaskAndApplyMaskOrCreateDab(dst, &pci, shape, info, subPixelX, subPixelY, softnessFactor, lightnessStrength);
497 }
498 
mask(KisFixedPaintDeviceSP dst,const KisPaintDeviceSP src,KisDabShape const & shape,const KisPaintInformation & info,double subPixelX,double subPixelY,qreal softnessFactor,qreal lightnessStrength) const499 void KisBrush::mask(KisFixedPaintDeviceSP dst, const KisPaintDeviceSP src, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor, qreal lightnessStrength) const
500 {
501     PaintDeviceColoringInformation pdci(src, maskWidth(shape, subPixelX, subPixelY, info));
502     generateMaskAndApplyMaskOrCreateDab(dst, &pdci, shape, info, subPixelX, subPixelY, softnessFactor, lightnessStrength);
503 }
504 
505 namespace {
fetchPremultipliedRed(const QRgb * src,quint8 * dst,int maskWidth)506 void fetchPremultipliedRed(const QRgb* src, quint8 *dst, int maskWidth)
507 {
508     for (int x = 0; x < maskWidth; x++) {
509         *dst = KoColorSpaceMaths<quint8>::multiply(255 - *src, qAlpha(*src));
510         src++;
511         dst++;
512     }
513 }
514 }
515 
generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst,ColoringInformation * coloringInformation,KisDabShape const & shape,const KisPaintInformation & info_,double subPixelX,double subPixelY,qreal softnessFactor) const516 void KisBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst,
517     ColoringInformation* coloringInformation,
518     KisDabShape const& shape,
519     const KisPaintInformation& info_,
520     double subPixelX, double subPixelY, qreal softnessFactor) const
521 {
522     generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info_, subPixelX, subPixelY, softnessFactor, DEFAULT_LIGHTNESS_STRENGTH);
523 }
524 
generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst,ColoringInformation * coloringInformation,KisDabShape const & shape,const KisPaintInformation & info_,double subPixelX,double subPixelY,qreal softnessFactor,qreal lightnessStrength) const525 void KisBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst,
526         ColoringInformation* coloringInformation,
527         KisDabShape const& shape,
528         const KisPaintInformation& info_,
529         double subPixelX, double subPixelY, qreal softnessFactor, qreal lightnessStrength) const
530 {
531     KIS_SAFE_ASSERT_RECOVER_RETURN(valid());
532     Q_UNUSED(info_);
533     Q_UNUSED(softnessFactor);
534 
535     QImage outputImage = d->brushPyramid->pyramid(this)->createImage(KisDabShape(
536             shape.scale() * d->scale, shape.ratio(),
537             -normalizeAngle(shape.rotation() + d->angle)),
538         subPixelX, subPixelY);
539 
540     qint32 maskWidth = outputImage.width();
541     qint32 maskHeight = outputImage.height();
542 
543     dst->setRect(QRect(0, 0, maskWidth, maskHeight));
544     dst->lazyGrowBufferWithoutInitialization();
545 
546     KIS_SAFE_ASSERT_RECOVER_RETURN(coloringInformation);
547 
548     quint8* color = 0;
549     if (dynamic_cast<PlainColoringInformation*>(coloringInformation)) {
550         color = const_cast<quint8*>(coloringInformation->color());
551     }
552 
553     const KoColorSpace *cs = dst->colorSpace();
554     const quint32 pixelSize = cs->pixelSize();
555     const quint32 maskPixelSize = sizeof(QRgb);
556     quint8 *rowPointer = dst->data();
557 
558     const bool preserveLightness = this->preserveLightness();
559     bool applyGradient = this->applyingGradient();
560     QScopedPointer<KoColor> fallbackColor;
561 
562     if (applyGradient) {
563         if (d->cachedGradient) {
564             KIS_SAFE_ASSERT_RECOVER_RETURN(d->cachedGradient);
565             d->cachedGradient->setColorSpace(cs); //convert gradient to colorspace so we don't have to convert each pixel
566         } else {
567             fallbackColor.reset(new KoColor(Qt::red, cs));
568             color = fallbackColor->data();
569             applyGradient = false;
570         }
571     }
572 
573     KoColor gradientcolor(Qt::blue, cs);
574     for (int y = 0; y < maskHeight; y++) {
575         const quint8* maskPointer = outputImage.constScanLine(y);
576         if (color) {
577             if (preserveLightness) {
578                 cs->fillGrayBrushWithColorAndLightnessWithStrength(rowPointer, reinterpret_cast<const QRgb*>(maskPointer), color, lightnessStrength, maskWidth);
579             }
580             else if (applyGradient) {
581                 quint8* pixel = rowPointer;
582                 for (int x = 0; x < maskWidth; x++) {
583                     const QRgb* maskQRgb = reinterpret_cast<const QRgb*>(maskPointer);
584                     qreal maskOpacity = qreal(qAlpha(*maskQRgb)) / 255.0;
585                     if (maskOpacity > 0) {
586                         qreal gradientvalue = qreal(qGray(*maskQRgb)) / 255.0;
587                         gradientcolor.setColor(d->cachedGradient->cachedAt(gradientvalue), cs);
588                     }
589                     qreal gradientOpacity = gradientcolor.opacityF();
590                     qreal opacity = gradientOpacity * maskOpacity;
591                     gradientcolor.setOpacity(opacity);
592                     memcpy(pixel, gradientcolor.data(), pixelSize);
593 
594                     maskPointer += maskPixelSize;
595                     pixel += pixelSize;
596                 }
597             }
598             else {
599                 cs->fillGrayBrushWithColor(rowPointer, reinterpret_cast<const QRgb*>(maskPointer), color, maskWidth);
600             }
601         }
602         else {
603             {
604                 quint8 *dst = rowPointer;
605                 for (int x = 0; x < maskWidth; x++) {
606                     memcpy(dst, coloringInformation->color(), pixelSize);
607                     coloringInformation->nextColumn();
608                     dst += pixelSize;
609                 }
610             }
611 
612             QScopedArrayPointer<quint8> alphaArray(new quint8[maskWidth]);
613             fetchPremultipliedRed(reinterpret_cast<const QRgb*>(maskPointer), alphaArray.data(), maskWidth);
614             cs->applyAlphaU8Mask(rowPointer, alphaArray.data(), maskWidth);
615         }
616 
617         rowPointer += maskWidth * pixelSize;
618 
619         if (!color) {
620             coloringInformation->nextRow();
621         }
622     }
623 
624 
625 }
626 
paintDevice(const KoColorSpace * colorSpace,KisDabShape const & shape,const KisPaintInformation & info,double subPixelX,double subPixelY) const627 KisFixedPaintDeviceSP KisBrush::paintDevice(const KoColorSpace * colorSpace,
628         KisDabShape const& shape,
629         const KisPaintInformation& info,
630         double subPixelX, double subPixelY) const
631 {
632     Q_ASSERT(valid());
633     Q_UNUSED(info);
634     double angle = normalizeAngle(shape.rotation() + d->angle);
635     double scale = shape.scale() * d->scale;
636 
637     QImage outputImage = d->brushPyramid->pyramid(this)->createImage(
638         KisDabShape(scale, shape.ratio(), -angle), subPixelX, subPixelY);
639 
640     KisFixedPaintDeviceSP dab = new KisFixedPaintDevice(colorSpace);
641     Q_CHECK_PTR(dab);
642     dab->convertFromQImage(outputImage, "");
643 
644     return dab;
645 }
646 
resetBoundary()647 void KisBrush::resetBoundary()
648 {
649     delete d->boundary;
650     d->boundary = 0;
651 }
652 
generateBoundary() const653 void KisBrush::generateBoundary() const
654 {
655     KisFixedPaintDeviceSP dev;
656     KisDabShape inverseTransform(1.0 / scale(), 1.0, -angle());
657 
658     if (brushApplication() == IMAGESTAMP) {
659         dev = paintDevice(KoColorSpaceRegistry::instance()->rgb8(),
660             inverseTransform, KisPaintInformation());
661     }
662     else {
663         const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
664         dev = new KisFixedPaintDevice(cs);
665         mask(dev, KoColor(Qt::black, cs), inverseTransform, KisPaintInformation());
666     }
667 
668     d->boundary = new KisBoundary(dev);
669     d->boundary->generateBoundary();
670 }
671 
boundary() const672 const KisBoundary* KisBrush::boundary() const
673 {
674     if (!d->boundary)
675         generateBoundary();
676     return d->boundary;
677 }
678 
setScale(qreal _scale)679 void KisBrush::setScale(qreal _scale)
680 {
681     d->scale = _scale;
682 }
683 
scale() const684 qreal KisBrush::scale() const
685 {
686     return d->scale;
687 }
688 
setAngle(qreal _rotation)689 void KisBrush::setAngle(qreal _rotation)
690 {
691     d->angle = _rotation;
692 }
693 
angle() const694 qreal KisBrush::angle() const
695 {
696     return d->angle;
697 }
698 
outline() const699 QPainterPath KisBrush::outline() const
700 {
701     return boundary()->path();
702 }
703 
lodLimitations(KisPaintopLodLimitations * l) const704 void KisBrush::lodLimitations(KisPaintopLodLimitations *l) const
705 {
706     if (spacing() > 0.5) {
707         l->limitations << KoID("huge-spacing", i18nc("PaintOp instant preview limitation", "Spacing > 0.5, consider disabling Instant Preview"));
708     }
709 }
710 
supportsCaching() const711 bool KisBrush::supportsCaching() const
712 {
713     return true;
714 }
715