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 }