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 }