File indexing completed on 2024-05-19 04:24:14
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(), because 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 KisFixedPaintDeviceSP KisAutoBrush::paintDevice(const KoColorSpace *, const KisDabShape &, const KisPaintInformation &, double, double) const { 0199 return 0; // The autobrush does NOT support images! 0200 } 0201 0202 0203 inline void fillPixelOptimized_4bytes(quint8 *color, quint8 *buf, int size) 0204 { 0205 /** 0206 * This version of filling uses low granularity of data transfers 0207 * (32-bit chunks) and internal processor's parallelism. It reaches 0208 * 25% better performance in KisStrokeBenchmark in comparison to 0209 * per-pixel memcpy version (tested on Sandy Bridge). 0210 */ 0211 0212 int block1 = size / 8; 0213 int block2 = size % 8; 0214 0215 quint32 *src = reinterpret_cast<quint32*>(color); 0216 quint32 *dst = reinterpret_cast<quint32*>(buf); 0217 0218 // check whether all buffers are 4 bytes aligned 0219 // (uncomment if experience some problems) 0220 // Q_ASSERT(((qint64)src & 3) == 0); 0221 // Q_ASSERT(((qint64)dst & 3) == 0); 0222 0223 for (int i = 0; i < block1; i++) { 0224 *dst = *src; 0225 *(dst + 1) = *src; 0226 *(dst + 2) = *src; 0227 *(dst + 3) = *src; 0228 *(dst + 4) = *src; 0229 *(dst + 5) = *src; 0230 *(dst + 6) = *src; 0231 *(dst + 7) = *src; 0232 0233 dst += 8; 0234 } 0235 0236 for (int i = 0; i < block2; i++) { 0237 *dst = *src; 0238 dst++; 0239 } 0240 } 0241 0242 inline void fillPixelOptimized_general(quint8 *color, quint8 *buf, int size, int pixelSize) 0243 { 0244 /** 0245 * This version uses internal processor's parallelism and gives 0246 * 20% better performance in KisStrokeBenchmark in comparison to 0247 * per-pixel memcpy version (tested on Sandy Bridge (+20%) and 0248 * on Merom (+10%)). 0249 */ 0250 0251 int block1 = size / 8; 0252 int block2 = size % 8; 0253 0254 for (int i = 0; i < block1; i++) { 0255 quint8 *d1 = buf; 0256 quint8 *d2 = buf + pixelSize; 0257 quint8 *d3 = buf + 2 * pixelSize; 0258 quint8 *d4 = buf + 3 * pixelSize; 0259 quint8 *d5 = buf + 4 * pixelSize; 0260 quint8 *d6 = buf + 5 * pixelSize; 0261 quint8 *d7 = buf + 6 * pixelSize; 0262 quint8 *d8 = buf + 7 * pixelSize; 0263 0264 for (int j = 0; j < pixelSize; j++) { 0265 *(d1 + j) = color[j]; 0266 *(d2 + j) = color[j]; 0267 *(d3 + j) = color[j]; 0268 *(d4 + j) = color[j]; 0269 *(d5 + j) = color[j]; 0270 *(d6 + j) = color[j]; 0271 *(d7 + j) = color[j]; 0272 *(d8 + j) = color[j]; 0273 } 0274 0275 buf += 8 * pixelSize; 0276 } 0277 0278 for (int i = 0; i < block2; i++) { 0279 memcpy(buf, color, pixelSize); 0280 buf += pixelSize; 0281 } 0282 } 0283 0284 void KisAutoBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, 0285 KisBrush::ColoringInformation* coloringInformation, 0286 KisDabShape const& shape, 0287 const KisPaintInformation& info, 0288 double subPixelX , double subPixelY, qreal softnessFactor, qreal lightnessStrength) const 0289 { 0290 Q_UNUSED(info); 0291 Q_UNUSED(lightnessStrength); 0292 0293 // Generate the paint device from the mask 0294 const KoColorSpace* cs = dst->colorSpace(); 0295 quint32 pixelSize = cs->pixelSize(); 0296 0297 // mask dimension methods already includes KisBrush::angle() 0298 int dstWidth = maskWidth(shape, subPixelX, subPixelY, info); 0299 int dstHeight = maskHeight(shape, subPixelX, subPixelY, info); 0300 QPointF hotSpot = this->hotSpot(shape, info); 0301 0302 // mask size and hotSpot function take the KisBrush rotation into account 0303 qreal angle = shape.rotation() + KisBrush::angle(); 0304 0305 // if there's coloring information, we merely change the alpha: in that case, 0306 // the dab should be big enough! 0307 if (coloringInformation) { 0308 // new bounds. we don't care if there is some extra memory occupied. 0309 dst->setRect(QRect(0, 0, dstWidth, dstHeight)); 0310 dst->lazyGrowBufferWithoutInitialization(); 0311 } 0312 else { 0313 KIS_SAFE_ASSERT_RECOVER_RETURN(dst->bounds().width() >= dstWidth && 0314 dst->bounds().height() >= dstHeight); 0315 } 0316 0317 KIS_SAFE_ASSERT_RECOVER_RETURN(coloringInformation); 0318 0319 quint8* dabPointer = dst->data(); 0320 0321 quint8* color = 0; 0322 if (dynamic_cast<PlainColoringInformation*>(coloringInformation)) { 0323 color = const_cast<quint8*>(coloringInformation->color()); 0324 } 0325 0326 double centerX = hotSpot.x() - 0.5 + subPixelX; 0327 double centerY = hotSpot.y() - 0.5 + subPixelY; 0328 0329 d->shape->setSoftness(softnessFactor); // softness must be set first 0330 d->shape->setScale(shape.scaleX(), shape.scaleY()); 0331 0332 if (!color) { 0333 for (int y = 0; y < dstHeight; y++) { 0334 for (int x = 0; x < dstWidth; x++) { 0335 memcpy(dabPointer, coloringInformation->color(), pixelSize); 0336 coloringInformation->nextColumn(); 0337 dabPointer += pixelSize; 0338 } 0339 coloringInformation->nextRow(); 0340 } 0341 } 0342 0343 MaskProcessingData data(dst, cs, color, 0344 d->randomness, d->density, 0345 centerX, centerY, 0346 angle); 0347 0348 const QRect rect(0, 0, dstWidth, dstHeight); 0349 KisBrushMaskApplicatorBase *applicator = d->shape->applicator(); 0350 applicator->initializeData(&data); 0351 applicator->process(rect); 0352 } 0353 0354 void KisAutoBrush::notifyBrushIsGoingToBeClonedForStroke() 0355 { 0356 // do nothing, since we don't use the pyramid! 0357 } 0358 0359 void KisAutoBrush::coldInitBrush() 0360 { 0361 generateOutlineCache(); 0362 } 0363 0364 void KisAutoBrush::toXML(QDomDocument& doc, QDomElement& e) const 0365 { 0366 QDomElement shapeElt = doc.createElement("MaskGenerator"); 0367 d->shape->toXML(doc, shapeElt); 0368 e.appendChild(shapeElt); 0369 e.setAttribute("type", "auto_brush"); 0370 e.setAttribute("spacing", QString::number(spacing())); 0371 e.setAttribute("useAutoSpacing", QString::number(autoSpacingActive())); 0372 e.setAttribute("autoSpacingCoeff", QString::number(autoSpacingCoeff())); 0373 e.setAttribute("angle", QString::number(KisBrush::angle())); 0374 e.setAttribute("randomness", QString::number(d->randomness)); 0375 e.setAttribute("density", QString::number(d->density)); 0376 KisBrush::toXML(doc, e); 0377 } 0378 0379 QImage KisAutoBrush::createBrushPreview(int maxSize) 0380 { 0381 KisDabShape shape; 0382 0383 int width = maskWidth(KisDabShape(), 0.0, 0.0, KisPaintInformation()); 0384 int height = maskHeight(KisDabShape(), 0.0, 0.0, KisPaintInformation()); 0385 0386 QSize size(width, height); 0387 0388 if (maxSize > 0 && KisAlgebra2D::maxDimension(size) > maxSize) { 0389 size.scale(128, 128, Qt::KeepAspectRatio); 0390 0391 qreal scale = 1.0; 0392 0393 if (width > height) { 0394 scale = qreal(size.width()) / width; 0395 } else { 0396 scale = qreal(size.height()) / height; 0397 } 0398 0399 shape = KisDabShape(scale, 1.0, 0.0); 0400 width = maskWidth(shape, 0.0, 0.0, KisPaintInformation()); 0401 height = maskHeight(shape, 0.0, 0.0, KisPaintInformation()); 0402 } 0403 0404 KisPaintInformation info(QPointF(width * 0.5, height * 0.5), 0.5, 0, 0, angle(), 0, 0, 0, 0); 0405 0406 KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); 0407 fdev->setRect(QRect(0, 0, width, height)); 0408 fdev->initialize(); 0409 0410 mask(fdev, KoColor(Qt::black, fdev->colorSpace()), shape, info); 0411 return fdev->convertToQImage(0); 0412 } 0413 0414 0415 const KisMaskGenerator* KisAutoBrush::maskGenerator() const 0416 { 0417 return d->shape.data(); 0418 } 0419 0420 qreal KisAutoBrush::density() const 0421 { 0422 return d->density; 0423 } 0424 0425 qreal KisAutoBrush::randomness() const 0426 { 0427 return d->randomness; 0428 } 0429 0430 KisOptimizedBrushOutline KisAutoBrush::outline(bool forcePreciseOutline) const 0431 { 0432 const bool requiresComplexOutline = d->shape->spikes() > 2; 0433 if (!requiresComplexOutline && !forcePreciseOutline) { 0434 QPainterPath path; 0435 QRectF brushBoundingbox(0, 0, width(), height()); 0436 if (maskGenerator()->type() == KisMaskGenerator::CIRCLE) { 0437 path.addEllipse(brushBoundingbox); 0438 } 0439 else { // if (maskGenerator()->type() == KisMaskGenerator::RECTANGLE) 0440 path.addRect(brushBoundingbox); 0441 } 0442 0443 return path; 0444 } 0445 0446 return KisBrush::outline(); 0447 } 0448 0449 void KisAutoBrush::lodLimitations(KisPaintopLodLimitations *l) const 0450 { 0451 KisBrush::lodLimitations(l); 0452 0453 if (!qFuzzyCompare(density(), 1.0)) { 0454 l->limitations << KoID("auto-brush-density", i18nc("PaintOp instant preview limitation", "Brush Density recommended value 100.0")); 0455 } 0456 0457 if (!qFuzzyCompare(randomness(), 0.0)) { 0458 l->limitations << KoID("auto-brush-randomness", i18nc("PaintOp instant preview limitation", "Brush Randomness recommended value 0.0")); 0459 } 0460 } 0461 0462 bool KisAutoBrush::supportsCaching() const 0463 { 0464 return qFuzzyCompare(density(), 1.0) && qFuzzyCompare(randomness(), 0.0); 0465 }