File indexing completed on 2024-05-12 15:56:09
0001 /* 0002 * SPDX-FileCopyrightText: 2004, 2007-2009 Cyrille Berger <cberger@cberger.net> 0003 * SPDX-FileCopyrightText: 2010 Lukáš Tvrdý <lukast.dev@gmail.com> 0004 * SPDX-FileCopyrightText: 2012 Sven Langkamp <sven.langkamp@gmail.com> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "kis_auto_brush.h" 0010 0011 #include <kis_debug.h> 0012 #include <math.h> 0013 0014 #include <QPainterPath> 0015 #include <QRect> 0016 #include <QDomElement> 0017 #include <QtConcurrentMap> 0018 #include <QByteArray> 0019 #include <QBuffer> 0020 #include <QFile> 0021 #include <QFileInfo> 0022 0023 #include <KoColor.h> 0024 #include <KoColorSpace.h> 0025 #include <KoColorSpaceRegistry.h> 0026 0027 #include <kis_datamanager.h> 0028 #include <kis_fixed_paint_device.h> 0029 #include <kis_paint_device.h> 0030 #include <brushengine/kis_paint_information.h> 0031 #include <kis_mask_generator.h> 0032 #include <kis_boundary.h> 0033 #include <brushengine/kis_paintop_lod_limitations.h> 0034 #include <kis_brush_mask_applicator_base.h> 0035 #include "kis_algebra_2d.h" 0036 #include <KisOptimizedBrushOutline.h> 0037 0038 #if defined(_WIN32) || defined(_WIN64) 0039 #include <stdlib.h> 0040 #define srand48 srand 0041 inline double drand48() 0042 { 0043 return double(rand()) / RAND_MAX; 0044 } 0045 #endif 0046 0047 struct KisAutoBrush::Private { 0048 Private() 0049 : randomness(0) 0050 , density(1.0) 0051 , idealThreadCountCached(1) 0052 {} 0053 0054 Private(const Private &rhs) 0055 : shape(rhs.shape->clone()) 0056 , randomness(rhs.randomness) 0057 , density(rhs.density) 0058 , idealThreadCountCached(rhs.idealThreadCountCached) 0059 { 0060 } 0061 0062 0063 QScopedPointer<KisMaskGenerator> shape; 0064 qreal randomness; 0065 qreal density; 0066 int idealThreadCountCached; 0067 }; 0068 0069 KisAutoBrush::KisAutoBrush(KisMaskGenerator* as, qreal angle, qreal randomness, qreal density) 0070 : KisBrush(), 0071 d(new Private) 0072 { 0073 d->shape.reset(as); 0074 d->randomness = randomness; 0075 d->density = density; 0076 d->idealThreadCountCached = QThread::idealThreadCount(); 0077 setBrushType(MASK); 0078 0079 { 0080 /** 0081 * Here is a two-stage process of initialization of the brush size. 0082 * It is done so for the backward compatibility reasons when the size 0083 * was set to the size of brushTipImage(), which initialization is now 0084 * skipped for efficiency reasons. 0085 */ 0086 0087 setWidth(qMax(qreal(1.0), d->shape->width())); 0088 setHeight(qMax(qreal(1.0), d->shape->height())); 0089 0090 const int width = maskWidth(KisDabShape(), 0.0, 0.0, KisPaintInformation()); 0091 const int height = maskHeight(KisDabShape(), 0.0, 0.0, KisPaintInformation()); 0092 0093 setWidth(qMax(1, width)); 0094 setHeight(qMax(1, height)); 0095 } 0096 0097 // We don't initialize setBrushTipImage(), bacause 0098 // auto brush doesn't use image pyramid. And generation 0099 // of a full-scaled QImage may cause a significant delay 0100 // in the beginning of the stroke 0101 0102 setAngle(angle); 0103 setImage(createBrushPreview(128)); 0104 } 0105 0106 KisAutoBrush::~KisAutoBrush() 0107 { 0108 } 0109 0110 bool KisAutoBrush::isEphemeral() const 0111 { 0112 return true; 0113 } 0114 0115 bool KisAutoBrush::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) 0116 { 0117 Q_UNUSED(dev); 0118 Q_UNUSED(resourcesInterface); 0119 return false; 0120 } 0121 0122 bool KisAutoBrush::saveToDevice(QIODevice *dev) const 0123 { 0124 Q_UNUSED(dev); 0125 return false; 0126 } 0127 0128 bool KisAutoBrush::isPiercedApprox() const 0129 { 0130 bool result = false; 0131 0132 if (d->shape->id() == SoftId.id()) { 0133 result = d->shape->valueAt(0,0) > 0.05 * 255; 0134 } 0135 0136 return result; 0137 } 0138 0139 KisFixedPaintDeviceSP KisAutoBrush::outlineSourceImage() const 0140 { 0141 KisFixedPaintDeviceSP dev; 0142 KisDabShape inverseTransform(1.0 / scale(), 1.0, -angle()); 0143 0144 const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); 0145 dev = new KisFixedPaintDevice(cs); 0146 mask(dev, KoColor(Qt::black, cs), inverseTransform, KisPaintInformation()); 0147 0148 return dev; 0149 } 0150 0151 qreal KisAutoBrush::userEffectiveSize() const 0152 { 0153 return d->shape->diameter(); 0154 } 0155 0156 void KisAutoBrush::setUserEffectiveSize(qreal value) 0157 { 0158 d->shape->setDiameter(value); 0159 } 0160 0161 KisAutoBrush::KisAutoBrush(const KisAutoBrush& rhs) 0162 : KisBrush(rhs) 0163 , d(new Private(*rhs.d)) 0164 { 0165 } 0166 0167 KoResourceSP KisAutoBrush::clone() const 0168 { 0169 return KoResourceSP(new KisAutoBrush(*this)); 0170 } 0171 0172 /* It's difficult to predict the mask height exactly when there are 0173 * more than 2 spikes, so we return an upperbound instead. */ 0174 static KisDabShape lieAboutDabShape(KisDabShape const& shape, int spikes) 0175 { 0176 return spikes > 2 ? KisDabShape(shape.scale(), 1.0, shape.rotation()) : shape; 0177 } 0178 0179 qint32 KisAutoBrush::maskHeight(KisDabShape const& shape, 0180 qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const 0181 { 0182 return KisBrush::maskHeight( 0183 lieAboutDabShape(shape, maskGenerator()->spikes()), subPixelX, subPixelY, info); 0184 } 0185 0186 qint32 KisAutoBrush::maskWidth(KisDabShape const& shape, 0187 qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const 0188 { 0189 return KisBrush::maskWidth( 0190 lieAboutDabShape(shape, maskGenerator()->spikes()), subPixelX, subPixelY, info); 0191 } 0192 0193 QSizeF KisAutoBrush::characteristicSize(KisDabShape const& shape) const 0194 { 0195 return KisBrush::characteristicSize(lieAboutDabShape(shape, maskGenerator()->spikes())); 0196 } 0197 0198 0199 inline void fillPixelOptimized_4bytes(quint8 *color, quint8 *buf, int size) 0200 { 0201 /** 0202 * This version of filling uses low granularity of data transfers 0203 * (32-bit chunks) and internal processor's parallelism. It reaches 0204 * 25% better performance in KisStrokeBenchmark in comparison to 0205 * per-pixel memcpy version (tested on Sandy Bridge). 0206 */ 0207 0208 int block1 = size / 8; 0209 int block2 = size % 8; 0210 0211 quint32 *src = reinterpret_cast<quint32*>(color); 0212 quint32 *dst = reinterpret_cast<quint32*>(buf); 0213 0214 // check whether all buffers are 4 bytes aligned 0215 // (uncomment if experience some problems) 0216 // Q_ASSERT(((qint64)src & 3) == 0); 0217 // Q_ASSERT(((qint64)dst & 3) == 0); 0218 0219 for (int i = 0; i < block1; i++) { 0220 *dst = *src; 0221 *(dst + 1) = *src; 0222 *(dst + 2) = *src; 0223 *(dst + 3) = *src; 0224 *(dst + 4) = *src; 0225 *(dst + 5) = *src; 0226 *(dst + 6) = *src; 0227 *(dst + 7) = *src; 0228 0229 dst += 8; 0230 } 0231 0232 for (int i = 0; i < block2; i++) { 0233 *dst = *src; 0234 dst++; 0235 } 0236 } 0237 0238 inline void fillPixelOptimized_general(quint8 *color, quint8 *buf, int size, int pixelSize) 0239 { 0240 /** 0241 * This version uses internal processor's parallelism and gives 0242 * 20% better performance in KisStrokeBenchmark in comparison to 0243 * per-pixel memcpy version (tested on Sandy Bridge (+20%) and 0244 * on Merom (+10%)). 0245 */ 0246 0247 int block1 = size / 8; 0248 int block2 = size % 8; 0249 0250 for (int i = 0; i < block1; i++) { 0251 quint8 *d1 = buf; 0252 quint8 *d2 = buf + pixelSize; 0253 quint8 *d3 = buf + 2 * pixelSize; 0254 quint8 *d4 = buf + 3 * pixelSize; 0255 quint8 *d5 = buf + 4 * pixelSize; 0256 quint8 *d6 = buf + 5 * pixelSize; 0257 quint8 *d7 = buf + 6 * pixelSize; 0258 quint8 *d8 = buf + 7 * pixelSize; 0259 0260 for (int j = 0; j < pixelSize; j++) { 0261 *(d1 + j) = color[j]; 0262 *(d2 + j) = color[j]; 0263 *(d3 + j) = color[j]; 0264 *(d4 + j) = color[j]; 0265 *(d5 + j) = color[j]; 0266 *(d6 + j) = color[j]; 0267 *(d7 + j) = color[j]; 0268 *(d8 + j) = color[j]; 0269 } 0270 0271 buf += 8 * pixelSize; 0272 } 0273 0274 for (int i = 0; i < block2; i++) { 0275 memcpy(buf, color, pixelSize); 0276 buf += pixelSize; 0277 } 0278 } 0279 0280 void KisAutoBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, 0281 KisBrush::ColoringInformation* coloringInformation, 0282 KisDabShape const& shape, 0283 const KisPaintInformation& info, 0284 double subPixelX , double subPixelY, qreal softnessFactor, qreal lightnessStrength) const 0285 { 0286 Q_UNUSED(info); 0287 Q_UNUSED(lightnessStrength); 0288 0289 // Generate the paint device from the mask 0290 const KoColorSpace* cs = dst->colorSpace(); 0291 quint32 pixelSize = cs->pixelSize(); 0292 0293 // mask dimension methods already includes KisBrush::angle() 0294 int dstWidth = maskWidth(shape, subPixelX, subPixelY, info); 0295 int dstHeight = maskHeight(shape, subPixelX, subPixelY, info); 0296 QPointF hotSpot = this->hotSpot(shape, info); 0297 0298 // mask size and hotSpot function take the KisBrush rotation into account 0299 qreal angle = shape.rotation() + KisBrush::angle(); 0300 0301 // if there's coloring information, we merely change the alpha: in that case, 0302 // the dab should be big enough! 0303 if (coloringInformation) { 0304 // new bounds. we don't care if there is some extra memory occcupied. 0305 dst->setRect(QRect(0, 0, dstWidth, dstHeight)); 0306 dst->lazyGrowBufferWithoutInitialization(); 0307 } 0308 else { 0309 KIS_SAFE_ASSERT_RECOVER_RETURN(dst->bounds().width() >= dstWidth && 0310 dst->bounds().height() >= dstHeight); 0311 } 0312 0313 KIS_SAFE_ASSERT_RECOVER_RETURN(coloringInformation); 0314 0315 quint8* dabPointer = dst->data(); 0316 0317 quint8* color = 0; 0318 if (dynamic_cast<PlainColoringInformation*>(coloringInformation)) { 0319 color = const_cast<quint8*>(coloringInformation->color()); 0320 } 0321 0322 double centerX = hotSpot.x() - 0.5 + subPixelX; 0323 double centerY = hotSpot.y() - 0.5 + subPixelY; 0324 0325 d->shape->setSoftness(softnessFactor); // softness must be set first 0326 d->shape->setScale(shape.scaleX(), shape.scaleY()); 0327 0328 if (!color) { 0329 for (int y = 0; y < dstHeight; y++) { 0330 for (int x = 0; x < dstWidth; x++) { 0331 memcpy(dabPointer, coloringInformation->color(), pixelSize); 0332 coloringInformation->nextColumn(); 0333 dabPointer += pixelSize; 0334 } 0335 coloringInformation->nextRow(); 0336 } 0337 } 0338 0339 MaskProcessingData data(dst, cs, color, 0340 d->randomness, d->density, 0341 centerX, centerY, 0342 angle); 0343 0344 const QRect rect(0, 0, dstWidth, dstHeight); 0345 KisBrushMaskApplicatorBase *applicator = d->shape->applicator(); 0346 applicator->initializeData(&data); 0347 applicator->process(rect); 0348 } 0349 0350 void KisAutoBrush::notifyBrushIsGoingToBeClonedForStroke() 0351 { 0352 // do nothing, since we don't use the pyramid! 0353 } 0354 0355 void KisAutoBrush::coldInitBrush() 0356 { 0357 generateOutlineCache(); 0358 } 0359 0360 void KisAutoBrush::toXML(QDomDocument& doc, QDomElement& e) const 0361 { 0362 QDomElement shapeElt = doc.createElement("MaskGenerator"); 0363 d->shape->toXML(doc, shapeElt); 0364 e.appendChild(shapeElt); 0365 e.setAttribute("type", "auto_brush"); 0366 e.setAttribute("spacing", QString::number(spacing())); 0367 e.setAttribute("useAutoSpacing", QString::number(autoSpacingActive())); 0368 e.setAttribute("autoSpacingCoeff", QString::number(autoSpacingCoeff())); 0369 e.setAttribute("angle", QString::number(KisBrush::angle())); 0370 e.setAttribute("randomness", QString::number(d->randomness)); 0371 e.setAttribute("density", QString::number(d->density)); 0372 KisBrush::toXML(doc, e); 0373 } 0374 0375 QImage KisAutoBrush::createBrushPreview(int maxSize) 0376 { 0377 KisDabShape shape; 0378 0379 int width = maskWidth(KisDabShape(), 0.0, 0.0, KisPaintInformation()); 0380 int height = maskHeight(KisDabShape(), 0.0, 0.0, KisPaintInformation()); 0381 0382 QSize size(width, height); 0383 0384 if (maxSize > 0 && KisAlgebra2D::maxDimension(size) > maxSize) { 0385 size.scale(128, 128, Qt::KeepAspectRatio); 0386 0387 qreal scale = 1.0; 0388 0389 if (width > height) { 0390 scale = qreal(size.width()) / width; 0391 } else { 0392 scale = qreal(size.height()) / height; 0393 } 0394 0395 shape = KisDabShape(scale, 1.0, 0.0); 0396 width = maskWidth(shape, 0.0, 0.0, KisPaintInformation()); 0397 height = maskHeight(shape, 0.0, 0.0, KisPaintInformation()); 0398 } 0399 0400 KisPaintInformation info(QPointF(width * 0.5, height * 0.5), 0.5, 0, 0, angle(), 0, 0, 0, 0); 0401 0402 KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); 0403 fdev->setRect(QRect(0, 0, width, height)); 0404 fdev->initialize(); 0405 0406 mask(fdev, KoColor(Qt::black, fdev->colorSpace()), shape, info); 0407 return fdev->convertToQImage(0); 0408 } 0409 0410 0411 const KisMaskGenerator* KisAutoBrush::maskGenerator() const 0412 { 0413 return d->shape.data(); 0414 } 0415 0416 qreal KisAutoBrush::density() const 0417 { 0418 return d->density; 0419 } 0420 0421 qreal KisAutoBrush::randomness() const 0422 { 0423 return d->randomness; 0424 } 0425 0426 KisOptimizedBrushOutline KisAutoBrush::outline(bool forcePreciseOutline) const 0427 { 0428 const bool requiresComplexOutline = d->shape->spikes() > 2; 0429 if (!requiresComplexOutline && !forcePreciseOutline) { 0430 QPainterPath path; 0431 QRectF brushBoundingbox(0, 0, width(), height()); 0432 if (maskGenerator()->type() == KisMaskGenerator::CIRCLE) { 0433 path.addEllipse(brushBoundingbox); 0434 } 0435 else { // if (maskGenerator()->type() == KisMaskGenerator::RECTANGLE) 0436 path.addRect(brushBoundingbox); 0437 } 0438 0439 return path; 0440 } 0441 0442 return KisBrush::outline(); 0443 } 0444 0445 void KisAutoBrush::lodLimitations(KisPaintopLodLimitations *l) const 0446 { 0447 KisBrush::lodLimitations(l); 0448 0449 if (!qFuzzyCompare(density(), 1.0)) { 0450 l->limitations << KoID("auto-brush-density", i18nc("PaintOp instant preview limitation", "Brush Density recommended value 100.0")); 0451 } 0452 0453 if (!qFuzzyCompare(randomness(), 0.0)) { 0454 l->limitations << KoID("auto-brush-randomness", i18nc("PaintOp instant preview limitation", "Brush Randomness recommended value 0.0")); 0455 } 0456 } 0457 0458 bool KisAutoBrush::supportsCaching() const 0459 { 0460 return qFuzzyCompare(density(), 1.0) && qFuzzyCompare(randomness(), 0.0); 0461 }