File indexing completed on 2024-05-19 04:24:15

0001 /*
0002  *  SPDX-FileCopyrightText: 1999 Matthias Elter <me@kde.org>
0003  *  SPDX-FileCopyrightText: 2003 Patrick Julien <freak@codepimps.org>
0004  *  SPDX-FileCopyrightText: 2004-2008 Boudewijn Rempt <boud@valdyas.org>
0005  *  SPDX-FileCopyrightText: 2004 Adrian Page <adrian@pagenet.plus.com>
0006  *  SPDX-FileCopyrightText: 2005 Bart Coppens <kde@bartcoppens.be>
0007  *  SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net>
0008  *
0009  *  SPDX-License-Identifier: GPL-2.0-or-later
0010  */
0011 
0012 #include "kis_brush.h"
0013 
0014 #include <QDomElement>
0015 #include <QFile>
0016 #include <QPainterPath>
0017 #include <QPoint>
0018 #include <QFileInfo>
0019 #include <QBuffer>
0020 
0021 #include <kis_debug.h>
0022 #include <klocalizedstring.h>
0023 
0024 #include <KoColor.h>
0025 #include <KoColorSpaceMaths.h>
0026 #include <KoColorSpaceRegistry.h>
0027 
0028 #include "kis_datamanager.h"
0029 #include "kis_paint_device.h"
0030 #include "kis_global.h"
0031 #include "kis_boundary.h"
0032 #include "kis_image.h"
0033 #include "kis_iterator_ng.h"
0034 #include "kis_brush_registry.h"
0035 #include <brushengine/kis_paint_information.h>
0036 #include <kis_fixed_paint_device.h>
0037 #include <kis_qimage_pyramid.h>
0038 #include <brushengine/kis_paintop_lod_limitations.h>
0039 #include <resources/KoAbstractGradient.h>
0040 #include <resources/KoCachedGradient.h>
0041 #include <KoResource.h>
0042 #include <KoResourceServerProvider.h>
0043 #include <KisLazySharedCacheStorage.h>
0044 #include <KisOptimizedBrushOutline.h>
0045 #include <KisStaticInitializer.h>
0046 
0047 
0048 KIS_DECLARE_STATIC_INITIALIZER {
0049     qRegisterMetaType<KisBrushSP>("KisBrushSP");
0050     QMetaType::registerEqualsComparator<KisBrushSP>();
0051 }
0052 
0053 const QString KisBrush::brushTypeMetaDataKey = "image-based-brush";
0054 
0055 KisBrush::ColoringInformation::~ColoringInformation()
0056 {
0057 }
0058 
0059 KisBrush::PlainColoringInformation::PlainColoringInformation(const quint8* color) : m_color(color)
0060 {
0061 }
0062 
0063 KisBrush::PlainColoringInformation::~PlainColoringInformation()
0064 {
0065 }
0066 
0067 const quint8* KisBrush::PlainColoringInformation::color() const
0068 {
0069     return m_color;
0070 }
0071 
0072 void KisBrush::PlainColoringInformation::nextColumn()
0073 {
0074 }
0075 
0076 void KisBrush::PlainColoringInformation::nextRow()
0077 {
0078 }
0079 
0080 KisBrush::PaintDeviceColoringInformation::PaintDeviceColoringInformation(const KisPaintDeviceSP source, int width)
0081     : m_source(source)
0082     , m_iterator(m_source->createHLineConstIteratorNG(0, 0, width))
0083 {
0084 }
0085 
0086 KisBrush::PaintDeviceColoringInformation::~PaintDeviceColoringInformation()
0087 {
0088 }
0089 
0090 const quint8* KisBrush::PaintDeviceColoringInformation::color() const
0091 {
0092     return m_iterator->oldRawData();
0093 }
0094 
0095 void KisBrush::PaintDeviceColoringInformation::nextColumn()
0096 {
0097     m_iterator->nextPixel();
0098 }
0099 void KisBrush::PaintDeviceColoringInformation::nextRow()
0100 {
0101     m_iterator->nextRow();
0102 }
0103 
0104 namespace detail {
0105 KisOptimizedBrushOutline* outlineFactory(const KisBrush *brush) {
0106     KisFixedPaintDeviceSP dev = brush->outlineSourceImage();
0107 
0108     KisBoundary boundary(dev);
0109     boundary.generateBoundary();
0110     return new KisOptimizedBrushOutline(boundary.path());
0111 }
0112 }
0113 
0114 struct KisBrush::Private {
0115     Private()
0116         : brushType(INVALID)
0117         , brushApplication(ALPHAMASK)
0118         , width(0)
0119         , height(0)
0120         , spacing (1.0)
0121         , hasColor(false)
0122         , angle(0)
0123         , scale(1.0)
0124         , gradient(0)
0125         , autoSpacingActive(false)
0126         , autoSpacingCoeff(1.0)
0127         , threadingAllowed(true)
0128         , brushPyramid([] (const KisBrush* brush)
0129                        {
0130                            return new KisQImagePyramid(brush->brushTipImage());
0131                        })
0132         , brushOutline(&detail::outlineFactory)
0133 
0134     {
0135     }
0136 
0137     Private(const Private &rhs)
0138         : brushType(rhs.brushType),
0139           brushApplication(rhs.brushApplication),
0140           width(rhs.width),
0141           height(rhs.height),
0142           spacing(rhs.spacing),
0143           hotSpot(rhs.hotSpot),
0144           hasColor(rhs.hasColor),
0145           angle(rhs.angle),
0146           scale(rhs.scale),
0147           autoSpacingActive(rhs.autoSpacingActive),
0148           autoSpacingCoeff(rhs.autoSpacingCoeff),
0149           threadingAllowed(rhs.threadingAllowed),
0150           brushTipImage(rhs.brushTipImage),
0151           /**
0152            * Be careful! The pyramid is shared between two brush objects,
0153            * therefore you cannot change it, only recreate! That is the
0154            * reason why it is defined as const!
0155            *
0156            * Take it also into account that the object is defined as
0157            * KisLazySharedCacheStorage**Linked**, that is, when a cloned
0158            * object updates the cache, the cache of the source object is
0159            * also updated. The two caches are detached only when any of
0160            * the objects calls cache.reset().
0161            */
0162           brushPyramid(rhs.brushPyramid),
0163           brushOutline(rhs.brushOutline)
0164     {
0165         gradient = rhs.gradient;
0166         if (rhs.cachedGradient) {
0167             cachedGradient = rhs.cachedGradient->clone().staticCast<KoCachedGradient>();
0168         }
0169     }
0170 
0171     ~Private() {
0172     }
0173 
0174     enumBrushType brushType;
0175     enumBrushApplication brushApplication;
0176 
0177     qint32 width;
0178     qint32 height;
0179     double spacing;
0180     QPointF hotSpot;
0181     bool hasColor;
0182     qreal angle;
0183     qreal scale;
0184 
0185     KoAbstractGradientSP gradient;
0186     QSharedPointer<KoCachedGradient> cachedGradient;
0187 
0188     bool autoSpacingActive;
0189     qreal autoSpacingCoeff;
0190     bool threadingAllowed;
0191 
0192     QImage brushTipImage;
0193     mutable KisLazySharedCacheStorageLinked<KisQImagePyramid, const KisBrush*> brushPyramid;
0194     mutable KisLazySharedCacheStorageLinked<KisOptimizedBrushOutline, const KisBrush*> brushOutline;
0195 };
0196 
0197 KisBrush::KisBrush()
0198     : KoResource(QString())
0199     , d(new Private)
0200 {
0201 }
0202 
0203 KisBrush::KisBrush(const QString& filename)
0204     : KoResource(filename)
0205     , d(new Private)
0206 {
0207 }
0208 
0209 KisBrush::KisBrush(const KisBrush& rhs)
0210     : KoResource(rhs)
0211     , d(new Private(*rhs.d))
0212 {
0213 }
0214 
0215 KisBrush::~KisBrush()
0216 {
0217     delete d;
0218 }
0219 
0220 QImage KisBrush::brushTipImage() const
0221 {
0222     KIS_SAFE_ASSERT_RECOVER_NOOP(!d->brushTipImage.isNull());
0223     return d->brushTipImage;
0224 }
0225 
0226 qint32 KisBrush::width() const
0227 {
0228     return d->width;
0229 }
0230 
0231 void KisBrush::setWidth(qint32 width)
0232 {
0233     d->width = width;
0234 }
0235 
0236 qint32 KisBrush::height() const
0237 {
0238     return d->height;
0239 }
0240 
0241 void KisBrush::setHeight(qint32 height)
0242 {
0243     d->height = height;
0244 }
0245 
0246 void KisBrush::setHotSpot(QPointF pt)
0247 {
0248     double x = pt.x();
0249     double y = pt.y();
0250 
0251     if (x < 0)
0252         x = 0;
0253     else if (x >= width())
0254         x = width() - 1;
0255 
0256     if (y < 0)
0257         y = 0;
0258     else if (y >= height())
0259         y = height() - 1;
0260 
0261     d->hotSpot = QPointF(x, y);
0262 }
0263 
0264 QPointF KisBrush::hotSpot(KisDabShape const& shape, const KisPaintInformation& info) const
0265 {
0266     Q_UNUSED(info);
0267 
0268     QSizeF metric = characteristicSize(shape);
0269 
0270     qreal w = metric.width();
0271     qreal h = metric.height();
0272 
0273     // The smallest brush we can produce is a single pixel.
0274     if (w < 1) {
0275         w = 1;
0276     }
0277 
0278     if (h < 1) {
0279         h = 1;
0280     }
0281 
0282     // XXX: This should take d->hotSpot into account, though it
0283     // isn't specified by gimp brushes so it would default to the center
0284     // anyway.
0285     QPointF p(w / 2, h / 2);
0286     return p;
0287 }
0288 
0289 void KisBrush::setBrushApplication(enumBrushApplication brushApplication)
0290 {
0291     if (d->brushApplication != brushApplication) {
0292         d->brushApplication = brushApplication;
0293         clearBrushPyramid();
0294     }
0295 }
0296 
0297 enumBrushApplication KisBrush::brushApplication() const
0298 {
0299     return d->brushApplication;
0300 }
0301 
0302 bool KisBrush::preserveLightness() const
0303 {
0304     return d->brushApplication == LIGHTNESSMAP;
0305 }
0306 
0307 bool KisBrush::applyingGradient() const
0308 {
0309     return d->brushApplication == GRADIENTMAP;
0310 }
0311 
0312 void KisBrush::setGradient(KoAbstractGradientSP gradient) {
0313     if (gradient && gradient->valid()) {
0314         d->gradient = gradient;
0315 
0316         if (!d->cachedGradient) {
0317             d->cachedGradient = toQShared(new KoCachedGradient(d->gradient, 256, d->gradient->colorSpace()));
0318         } else {
0319             d->cachedGradient->setGradient(d->gradient, 256, d->gradient->colorSpace());
0320         }
0321     }
0322 }
0323 
0324 bool KisBrush::isPiercedApprox() const
0325 {
0326     QImage image = brushTipImage();
0327 
0328     qreal w = image.width();
0329     qreal h = image.height();
0330 
0331     qreal xPortion = qMin(0.1, 5.0 / w);
0332     qreal yPortion = qMin(0.1, 5.0 / h);
0333 
0334     int x0 = std::floor((0.5 - xPortion) * w);
0335     int x1 = std::ceil((0.5 + xPortion) * w);
0336 
0337     int y0 = std::floor((0.5 - yPortion) * h);
0338     int y1 = std::ceil((0.5 + yPortion) * h);
0339 
0340     const int maxNumSamples = (x1 - x0 + 1) * (y1 - y0 + 1);
0341     const int failedPixelsThreshold = 0.1 * maxNumSamples;
0342     const int thresholdValue = 0.95 * 255;
0343     int failedPixels = 0;
0344 
0345     for (int y = y0; y <= y1; y++) {
0346         for (int x = x0; x <= x1; x++) {
0347             QRgb pixel = image.pixel(x,y);
0348 
0349             if (qRed(pixel) > thresholdValue) {
0350                 failedPixels++;
0351             }
0352         }
0353     }
0354 
0355     return failedPixels > failedPixelsThreshold;
0356 }
0357 
0358 namespace {
0359 void fetchPremultipliedRed(const QRgb* src, quint8 *dst, int maskWidth)
0360 {
0361     for (int x = 0; x < maskWidth; x++) {
0362         *dst = KoColorSpaceMaths<quint8>::multiply(255 - *src, qAlpha(*src));
0363         src++;
0364         dst++;
0365     }
0366 }
0367 }
0368 
0369 KisFixedPaintDeviceSP KisBrush::outlineSourceImage() const
0370 {
0371     /**
0372      * We need to generate the mask manually, skipping the
0373      * construction of the image pyramid
0374      */
0375 
0376     const KoColorSpace* cs = KoColorSpaceRegistry::instance()->alpha8();
0377     KisFixedPaintDeviceSP dev = new KisFixedPaintDevice(cs);
0378     const QImage image = brushTipImage().convertToFormat(QImage::Format_ARGB32);
0379 
0380     dev->setRect(image.rect());
0381     dev->lazyGrowBufferWithoutInitialization();
0382 
0383     const int maskWidth = image.width();
0384     const int maskHeight = image.height();
0385 
0386     quint8 *dstPtr = dev->data();
0387 
0388     for (int y = 0; y < maskHeight; y++) {
0389         const QRgb* maskPointer = reinterpret_cast<const QRgb*>(image.constScanLine(y));
0390         fetchPremultipliedRed(maskPointer, dstPtr, maskWidth);
0391         dstPtr += maskWidth;
0392     }
0393 
0394     return dev;
0395 }
0396 
0397 bool KisBrush::canPaintFor(const KisPaintInformation& /*info*/)
0398 {
0399     return true;
0400 }
0401 
0402 void KisBrush::setBrushTipImage(const QImage& image)
0403 {
0404     d->brushTipImage = image;
0405 
0406     if (!image.isNull()) {
0407         if (image.width() > 128 || image.height() > 128) {
0408             KoResource::setImage(image.scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation));
0409         }
0410         else {
0411             KoResource::setImage(image);
0412         }
0413         setWidth(image.width());
0414         setHeight(image.height());
0415     }
0416     clearBrushPyramid();
0417     resetOutlineCache();
0418 }
0419 
0420 void KisBrush::setBrushType(enumBrushType type)
0421 {
0422     d->brushType = type;
0423     addMetaData(brushTypeMetaDataKey,
0424                 QVariant::fromValue(type == IMAGE || type == PIPE_IMAGE));
0425 }
0426 
0427 enumBrushType KisBrush::brushType() const
0428 {
0429     return d->brushType;
0430 }
0431 
0432 void KisBrush::predefinedBrushToXML(const QString &type, QDomElement& e) const
0433 {
0434     e.setAttribute("type", type);
0435     e.setAttribute("filename", filename());
0436     e.setAttribute("md5sum", md5Sum());
0437     e.setAttribute("spacing", QString::number(spacing()));
0438     e.setAttribute("useAutoSpacing", QString::number(autoSpacingActive()));
0439     e.setAttribute("autoSpacingCoeff", QString::number(autoSpacingCoeff()));
0440     e.setAttribute("angle", QString::number(angle()));
0441     e.setAttribute("scale", QString::number(scale()));
0442     e.setAttribute("brushApplication", QString::number((int)brushApplication()));
0443 }
0444 
0445 void KisBrush::toXML(QDomDocument& /*document*/ , QDomElement& element) const
0446 {
0447     element.setAttribute("BrushVersion", "2");
0448 }
0449 
0450 KisBrushSP KisBrush::fromXML(const QDomElement& element, KisResourcesInterfaceSP resourcesInterface)
0451 {
0452     KoResourceLoadResult result = fromXMLLoadResult(element, resourcesInterface);
0453 
0454     KisBrushSP brush = result.resource<KisBrush>();
0455     if (!brush) {
0456         QDomElement el;
0457         brush = KisBrushRegistry::instance()->get("auto_brush")->createBrush(el, resourcesInterface).resource<KisBrush>();
0458     }
0459     return brush;
0460 }
0461 
0462 KoResourceLoadResult KisBrush::fromXMLLoadResult(const QDomElement& element, KisResourcesInterfaceSP resourcesInterface)
0463 {
0464     KoResourceLoadResult result = KisBrushRegistry::instance()->createBrush(element, resourcesInterface);
0465 
0466     KisBrushSP brush = result.resource<KisBrush>();
0467     if (brush && element.attribute("BrushVersion", "1") == "1") {
0468         brush->setScale(brush->scale() * 2.0);
0469     }
0470 
0471     return result;
0472 }
0473 
0474 QSizeF KisBrush::characteristicSize(KisDabShape const& shape) const
0475 {
0476     KisDabShape normalizedShape(
0477                 shape.scale() * d->scale,
0478                 shape.ratio(),
0479                 normalizeAngle(shape.rotation() + d->angle));
0480     return KisQImagePyramid::characteristicSize(
0481                 QSize(width(), height()), normalizedShape);
0482 }
0483 
0484 qint32 KisBrush::maskWidth(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const
0485 {
0486     Q_UNUSED(info);
0487 
0488     qreal angle = normalizeAngle(shape.rotation() + d->angle);
0489     qreal scale = shape.scale() * d->scale;
0490 
0491     return KisQImagePyramid::imageSize(QSize(width(), height()),
0492                                        KisDabShape(scale, shape.ratio(), angle),
0493                                        subPixelX, subPixelY).width();
0494 }
0495 
0496 qint32 KisBrush::maskHeight(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const
0497 {
0498     Q_UNUSED(info);
0499 
0500     qreal angle = normalizeAngle(shape.rotation() + d->angle);
0501     qreal scale = shape.scale() * d->scale;
0502 
0503     return KisQImagePyramid::imageSize(QSize(width(), height()),
0504                                        KisDabShape(scale, shape.ratio(), angle),
0505                                        subPixelX, subPixelY).height();
0506 }
0507 
0508 double KisBrush::maskAngle(double angle) const
0509 {
0510     return normalizeAngle(angle + d->angle);
0511 }
0512 
0513 quint32 KisBrush::brushIndex() const
0514 {
0515     return 0;
0516 }
0517 
0518 void KisBrush::setSpacing(double s)
0519 {
0520     if (s < 0.02) s = 0.02;
0521     d->spacing = s;
0522 }
0523 
0524 double KisBrush::spacing() const
0525 {
0526     return d->spacing;
0527 }
0528 
0529 void KisBrush::setAutoSpacing(bool active, qreal coeff)
0530 {
0531     d->autoSpacingCoeff = coeff;
0532     d->autoSpacingActive = active;
0533 }
0534 
0535 bool KisBrush::autoSpacingActive() const
0536 {
0537     return d->autoSpacingActive;
0538 }
0539 
0540 qreal KisBrush::autoSpacingCoeff() const
0541 {
0542     return d->autoSpacingCoeff;
0543 }
0544 
0545 void KisBrush::notifyStrokeStarted()
0546 {
0547 }
0548 
0549 void KisBrush::notifyBrushIsGoingToBeClonedForStroke()
0550 {
0551     /// Default implementation for all image-based brushes:
0552     /// just recreate the shared pyramid
0553     d->brushPyramid.initialize(this);
0554 }
0555 
0556 void KisBrush::prepareForSeqNo(const KisPaintInformation &info, int seqNo)
0557 {
0558     Q_UNUSED(info);
0559     Q_UNUSED(seqNo);
0560 }
0561 
0562 void KisBrush::clearBrushPyramid()
0563 {
0564     d->brushPyramid.reset();
0565 }
0566 
0567 void KisBrush::mask(KisFixedPaintDeviceSP dst, const KoColor& color, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor, qreal lightnessStrength) const
0568 {
0569     PlainColoringInformation pci(color.data());
0570     generateMaskAndApplyMaskOrCreateDab(dst, &pci, shape, info, subPixelX, subPixelY, softnessFactor, lightnessStrength);
0571 }
0572 
0573 void KisBrush::mask(KisFixedPaintDeviceSP dst, const KisPaintDeviceSP src, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor, qreal lightnessStrength) const
0574 {
0575     PaintDeviceColoringInformation pdci(src, maskWidth(shape, subPixelX, subPixelY, info));
0576     generateMaskAndApplyMaskOrCreateDab(dst, &pdci, shape, info, subPixelX, subPixelY, softnessFactor, lightnessStrength);
0577 }
0578 
0579 void KisBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst,
0580                                                    ColoringInformation* coloringInformation,
0581                                                    KisDabShape const& shape,
0582                                                    const KisPaintInformation& info_,
0583                                                    double subPixelX, double subPixelY, qreal softnessFactor) const
0584 {
0585     generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info_, subPixelX, subPixelY, softnessFactor, DEFAULT_LIGHTNESS_STRENGTH);
0586 }
0587 
0588 void KisBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst,
0589         ColoringInformation* coloringInformation,
0590         KisDabShape const& shape,
0591         const KisPaintInformation& info_,
0592         double subPixelX, double subPixelY, qreal softnessFactor, qreal lightnessStrength) const
0593 {
0594     KIS_SAFE_ASSERT_RECOVER_RETURN(valid());
0595     Q_UNUSED(info_);
0596     Q_UNUSED(softnessFactor);
0597 
0598     QImage outputImage = d->brushPyramid.value(this)->createImage(KisDabShape(
0599                                                                          shape.scale() * d->scale, shape.ratio(),
0600                                                                          -normalizeAngle(shape.rotation() + d->angle)),
0601                                                                      subPixelX, subPixelY);
0602 
0603     qint32 maskWidth = outputImage.width();
0604     qint32 maskHeight = outputImage.height();
0605 
0606     dst->setRect(QRect(0, 0, maskWidth, maskHeight));
0607     dst->lazyGrowBufferWithoutInitialization();
0608 
0609     KIS_SAFE_ASSERT_RECOVER_RETURN(coloringInformation);
0610 
0611     quint8* color = 0;
0612     if (dynamic_cast<PlainColoringInformation*>(coloringInformation)) {
0613         color = const_cast<quint8*>(coloringInformation->color());
0614     }
0615 
0616     const KoColorSpace *cs = dst->colorSpace();
0617     const quint32 pixelSize = cs->pixelSize();
0618     const quint32 maskPixelSize = sizeof(QRgb);
0619     quint8 *rowPointer = dst->data();
0620 
0621     const bool preserveLightness = this->preserveLightness();
0622     bool applyGradient = this->applyingGradient();
0623     QScopedPointer<KoColor> fallbackColor;
0624 
0625     if (applyGradient) {
0626         if (d->cachedGradient) {
0627             KIS_SAFE_ASSERT_RECOVER_RETURN(d->cachedGradient);
0628             d->cachedGradient->setColorSpace(cs); //convert gradient to colorspace so we don't have to convert each pixel
0629         } else {
0630             fallbackColor.reset(new KoColor(Qt::red, cs));
0631             color = fallbackColor->data();
0632             applyGradient = false;
0633         }
0634     }
0635 
0636     KoColor gradientcolor(Qt::blue, cs);
0637     for (int y = 0; y < maskHeight; y++) {
0638         const quint8* maskPointer = outputImage.constScanLine(y);
0639         if (color) {
0640             if (preserveLightness) {
0641                 cs->fillGrayBrushWithColorAndLightnessWithStrength(rowPointer, reinterpret_cast<const QRgb*>(maskPointer), color, lightnessStrength, maskWidth);
0642             }
0643             else if (applyGradient) {
0644                 quint8* pixel = rowPointer;
0645                 for (int x = 0; x < maskWidth; x++) {
0646                     const QRgb* maskQRgb = reinterpret_cast<const QRgb*>(maskPointer);
0647                     qreal maskOpacity = qreal(qAlpha(*maskQRgb)) / 255.0;
0648                     if (maskOpacity > 0) {
0649                         qreal gradientvalue = qreal(qGray(*maskQRgb)) / 255.0;
0650                         gradientcolor.setColor(d->cachedGradient->cachedAt(gradientvalue), cs);
0651                     }
0652                     qreal gradientOpacity = gradientcolor.opacityF();
0653                     qreal opacity = gradientOpacity * maskOpacity;
0654                     gradientcolor.setOpacity(opacity);
0655                     memcpy(pixel, gradientcolor.data(), pixelSize);
0656 
0657                     maskPointer += maskPixelSize; 
0658                     pixel += pixelSize;
0659                 }
0660             }
0661             else {
0662                 cs->fillGrayBrushWithColor(rowPointer, reinterpret_cast<const QRgb*>(maskPointer), color, maskWidth);
0663             }
0664         }
0665         else {
0666             {
0667                 quint8 *dst = rowPointer;
0668                 for (int x = 0; x < maskWidth; x++) {
0669                     memcpy(dst, coloringInformation->color(), pixelSize);
0670                     coloringInformation->nextColumn();
0671                     dst += pixelSize;
0672                 }
0673             }
0674 
0675             QScopedArrayPointer<quint8> alphaArray(new quint8[maskWidth]);
0676             fetchPremultipliedRed(reinterpret_cast<const QRgb*>(maskPointer), alphaArray.data(), maskWidth);
0677             cs->applyAlphaU8Mask(rowPointer, alphaArray.data(), maskWidth);
0678         }
0679 
0680         rowPointer += maskWidth * pixelSize;
0681 
0682         if (!color) {
0683             coloringInformation->nextRow();
0684         }
0685     }
0686 
0687 
0688 }
0689 
0690 KisFixedPaintDeviceSP KisBrush::paintDevice(const KoColorSpace * colorSpace,
0691                                             KisDabShape const& shape,
0692                                             const KisPaintInformation& info,
0693                                             double subPixelX, double subPixelY) const
0694 {
0695     Q_ASSERT(valid());
0696     Q_UNUSED(info);
0697     double angle = normalizeAngle(shape.rotation() + d->angle);
0698     double scale = shape.scale() * d->scale;
0699 
0700     QImage outputImage = d->brushPyramid.value(this)->createImage(
0701                 KisDabShape(scale, shape.ratio(), -angle), subPixelX, subPixelY);
0702 
0703     KisFixedPaintDeviceSP dab = new KisFixedPaintDevice(colorSpace);
0704     Q_CHECK_PTR(dab);
0705     dab->convertFromQImage(outputImage, "");
0706 
0707     return dab;
0708 }
0709 
0710 void KisBrush::resetOutlineCache()
0711 {
0712     d->brushOutline.reset();
0713 }
0714 
0715 void KisBrush::generateOutlineCache()
0716 {
0717     d->brushOutline.initialize(this);
0718 }
0719 
0720 bool KisBrush::outlineCacheIsValid() const
0721 {
0722     return !d->brushOutline.isNull();
0723 }
0724 
0725 void KisBrush::setScale(qreal _scale)
0726 {
0727     d->scale = _scale;
0728 }
0729 
0730 qreal KisBrush::scale() const
0731 {
0732     return d->scale;
0733 }
0734 
0735 void KisBrush::setAngle(qreal _rotation)
0736 {
0737     d->angle = _rotation;
0738 }
0739 
0740 qreal KisBrush::angle() const
0741 {
0742     return d->angle;
0743 }
0744 
0745 KisOptimizedBrushOutline KisBrush::outline(bool forcePreciseOutline) const
0746 {
0747     Q_UNUSED(forcePreciseOutline);
0748 
0749     return *d->brushOutline.value(this);
0750 }
0751 
0752 void KisBrush::lodLimitations(KisPaintopLodLimitations *l) const
0753 {
0754     if (spacing() > 0.5) {
0755         l->limitations << KoID("huge-spacing", i18nc("PaintOp instant preview limitation", "Spacing > 0.5, consider disabling Instant Preview"));
0756     }
0757 }
0758 
0759 bool KisBrush::supportsCaching() const
0760 {
0761     return true;
0762 }
0763 
0764 void KisBrush::coldInitBrush()
0765 {
0766     d->brushPyramid.initialize(this);
0767     generateOutlineCache();
0768 }