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 }