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