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

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