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 }