File indexing completed on 2024-06-16 04:17:37

0001 /*
0002  *  SPDX-FileCopyrightText: 2008-2010 Lukáš Tvrdý <lukast.dev@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "spray_brush.h"
0008 
0009 #include <KoColor.h>
0010 #include <KoColorSpace.h>
0011 #include <KoColorTransformation.h>
0012 #include <KoCompositeOp.h>
0013 #include <KoMixColorsOp.h>
0014 
0015 #include <brushengine/kis_paintop.h>
0016 
0017 #include <QVariant>
0018 #include <QHash>
0019 #include <QTransform>
0020 #include <QImage>
0021 
0022 #include <kis_random_accessor_ng.h>
0023 #include <kis_random_sub_accessor.h>
0024 
0025 #include <kis_paint_device.h>
0026 
0027 #include <kis_painter.h>
0028 #include <brushengine/kis_paint_information.h>
0029 #include <kis_fixed_paint_device.h>
0030 #include <kis_cross_device_color_sampler.h>
0031 
0032 #include "kis_spray_paintop_settings.h"
0033 
0034 #include <cmath>
0035 #include <ctime>
0036 
0037 #include <QtGlobal>
0038 
0039 SprayBrush::SprayBrush()
0040 {
0041     m_painter = nullptr;
0042     m_transfo = nullptr;
0043 }
0044 
0045 SprayBrush::~SprayBrush()
0046 {
0047     delete m_painter;
0048     delete m_transfo;
0049 }
0050 
0051 void SprayBrush::setProperties(KisSprayOpOptionData * properties,
0052                                KisColorOptionData * colorProperties,
0053                                KisSprayShapeOptionData * shapeProperties,
0054                                KisSprayShapeDynamicsOptionData * shapeDynamicsProperties,
0055                                KisBrushSP brush)
0056 {
0057     m_sprayOpOptionData = properties;
0058     m_sprayOpOption = new KisSprayOpOption(*properties);
0059     m_sprayOpOption->updateDistributions();
0060     m_colorProperties = colorProperties;
0061     m_shapeProperties = shapeProperties;
0062     m_shapeDynamicsProperties = shapeDynamicsProperties;
0063     m_brush = brush;
0064     if (m_brush) {
0065         m_brush->notifyStrokeStarted();
0066     }
0067 }
0068 
0069 qreal SprayBrush::rotationAngle(KisRandomSourceSP randomSource)
0070 {
0071     qreal rotation = 0.0;
0072 
0073     if (m_shapeDynamicsProperties->fixedRotation) {
0074         rotation = deg2rad(m_shapeDynamicsProperties->fixedAngle);
0075     }
0076 
0077     if (m_shapeDynamicsProperties->randomRotation) {
0078 
0079         qreal randomValue = 0.0;
0080 
0081         randomValue = randomSource->generateNormalized();
0082 
0083         rotation =
0084             linearInterpolation(rotation ,
0085                                 M_PI * 2.0 * randomValue,
0086                                 m_shapeDynamicsProperties->randomRotationWeight);
0087     }
0088 
0089     return rotation;
0090 }
0091 
0092 void SprayBrush::paint(KisPaintDeviceSP dab, KisPaintDeviceSP source,
0093                        const KisPaintInformation& info,
0094                        qreal rotation, qreal scale,
0095                        qreal additionalScale,
0096                        const KoColor &color, const KoColor &bgColor)
0097 {
0098     if (m_sprayOpOption->data.angularDistributionType == KisSprayOpOptionData::ParticleDistribution_Uniform) {
0099         paintImpl(dab, source, info, rotation, scale, additionalScale, color, bgColor, m_sprayOpOption->m_uniformDistribution);
0100     } else {
0101         paintImpl(dab, source, info, rotation, scale, additionalScale, color, bgColor, m_sprayOpOption->m_angularCurveBasedDistribution);
0102     }
0103 }
0104 
0105 template <typename AngularDistribution>
0106 void SprayBrush::paintImpl(KisPaintDeviceSP dab, KisPaintDeviceSP source,
0107                            const KisPaintInformation& info,
0108                            qreal rotation, qreal scale,
0109                            qreal additionalScale,
0110                            const KoColor &color,
0111                            const KoColor &bgColor,
0112                            const AngularDistribution &angularDistribution)
0113 {
0114     if (m_sprayOpOption->data.radialDistributionType == KisSprayOpOptionData::ParticleDistribution_Uniform) {
0115         if (m_sprayOpOption->data.radialDistributionCenterBiased) {
0116             paintImpl(dab, source, info, rotation, scale, additionalScale, color, bgColor,
0117                       angularDistribution, m_sprayOpOption->m_uniformDistribution);
0118         } else {
0119             paintImpl(dab, source, info, rotation, scale, additionalScale, color, bgColor,
0120                       angularDistribution, m_sprayOpOption->m_uniformDistributionPolarDistance);
0121         }
0122     } else if (m_sprayOpOption->data.radialDistributionType == KisSprayOpOptionData::ParticleDistribution_Gaussian) {
0123         if (m_sprayOpOption->data.radialDistributionCenterBiased) {
0124             paintImpl(dab, source, info, rotation, scale, additionalScale, color, bgColor,
0125                       angularDistribution, m_sprayOpOption->m_normalDistribution);
0126         } else {
0127             paintImpl(dab, source, info, rotation, scale, additionalScale, color, bgColor,
0128                       angularDistribution, m_sprayOpOption->m_normalDistributionPolarDistance);
0129         }
0130     } else if (m_sprayOpOption->data.radialDistributionType == KisSprayOpOptionData::ParticleDistribution_ClusterBased) {
0131         paintImpl(dab, source, info, rotation, scale, additionalScale, color, bgColor,
0132                   angularDistribution, m_sprayOpOption->m_clusterBasedDistributionPolarDistance);
0133     } else {
0134         paintImpl(dab, source, info, rotation, scale, additionalScale, color, bgColor,
0135                   angularDistribution, m_sprayOpOption->m_radialCurveBasedDistributionPolarDistance);
0136     }
0137 }
0138 
0139 template <typename AngularDistribution, typename RadialDistribution>
0140 void SprayBrush::paintImpl(KisPaintDeviceSP dab, KisPaintDeviceSP source,
0141                            const KisPaintInformation& info,
0142                            qreal rotation, qreal scale,
0143                            qreal additionalScale,
0144                            const KoColor &color,
0145                            const KoColor &bgColor,
0146                            const AngularDistribution &angularDistribution,
0147                            const RadialDistribution &radialDistribution)
0148 {
0149     if (!angularDistribution.isValid() || !radialDistribution.isValid()) {
0150         return;
0151     }
0152 
0153     KisRandomSourceSP randomSource = info.randomSource();
0154 
0155     const QSize effectiveSize = m_shapeProperties->effectiveSize(m_sprayOpOptionData->diameter, m_sprayOpOptionData->scale);
0156 
0157     // initializing painter
0158     if (!m_painter) {
0159         m_painter = new KisPainter(dab);
0160         m_painter->setFillStyle(KisPainter::FillStyleForegroundColor);
0161         m_painter->setMaskImageSize(effectiveSize.width(), effectiveSize.height());
0162         m_dabPixelSize = dab->colorSpace()->pixelSize();
0163         if (m_colorProperties->useRandomHSV) {
0164             m_transfo = dab->colorSpace()->createColorTransformation("hsv_adjustment", QHash<QString, QVariant>());
0165         }
0166 
0167         m_brushQImage = m_shapeProperties->image;
0168         if (!m_brushQImage.isNull()) {
0169             m_brushQImage = m_brushQImage.scaled(effectiveSize);
0170         }
0171         m_imageDevice = new KisPaintDevice(dab->colorSpace());
0172     }
0173 
0174 
0175     qreal x = info.pos().x();
0176     qreal y = info.pos().y();
0177     KisRandomAccessorSP accessor = dab->createRandomAccessorNG();
0178 
0179     Q_ASSERT(color.colorSpace()->pixelSize() == dab->pixelSize());
0180     m_inkColor = color;
0181     KisCrossDeviceColorSampler colorSampler(source, m_inkColor);
0182 
0183     // apply size sensor
0184     m_radius = m_sprayOpOption->data.diameter/2 * scale * additionalScale;
0185 
0186     // jitter movement
0187     if (m_sprayOpOption->data.jitterMovement) {
0188         x = x + ((2 * m_radius * randomSource->generateNormalized()) - m_radius) * m_sprayOpOption->data.jitterAmount;
0189         y = y + ((2 * m_radius * randomSource->generateNormalized()) - m_radius) * m_sprayOpOption->data.jitterAmount;
0190     }
0191 
0192     // this is wrong for every shape except pixel and anti-aliased pixel
0193 
0194 
0195     if (m_sprayOpOption->data.useDensity) {
0196         m_particlesCount = (m_sprayOpOption->data.coverage * (M_PI * pow2(m_radius)) / pow2(additionalScale));
0197     }
0198     else {
0199         m_particlesCount = m_sprayOpOption->data.particleCount;
0200     }
0201 
0202     QHash<QString, QVariant> params;
0203     qreal nx, ny;
0204     int ix, iy;
0205 
0206     qreal angle;
0207     qreal length;
0208     qreal rotationZ = 0.0;
0209     qreal particleScale = 1.0;
0210 
0211     bool shouldColor = true;
0212     if (m_colorProperties->fillBackground) {
0213         m_painter->setPaintColor(bgColor);
0214         paintCircle(m_painter, x, y, m_radius);
0215     }
0216 
0217     QTransform m;
0218     m.reset();
0219     m.rotateRadians(-rotation + deg2rad(m_sprayOpOption->data.brushRotation));
0220     m.scale(m_sprayOpOption->data.scale, m_sprayOpOption->data.scale);
0221 
0222     for (quint32 i = 0; i < m_particlesCount; i++) {
0223         // generate random angle
0224         angle = angularDistribution(randomSource) * M_PI * 2;
0225         // generate random length
0226         length = radialDistribution(randomSource);
0227 
0228         if (m_shapeDynamicsProperties->enabled) {
0229             // rotation
0230             rotationZ = rotationAngle(randomSource);
0231 
0232             if (m_shapeDynamicsProperties->followCursor) {
0233 
0234                 rotationZ = linearInterpolation(rotationZ, angle, m_shapeDynamicsProperties->followCursorWeight);
0235             }
0236 
0237 
0238             if (m_shapeDynamicsProperties->followDrawingAngle) {
0239 
0240                 rotationZ = linearInterpolation(rotationZ, info.drawingAngle(), m_shapeDynamicsProperties->followDrawingAngleWeight);
0241             }
0242 
0243             // random size - scale
0244             if (m_shapeDynamicsProperties->randomSize) {
0245                 particleScale = randomSource->generateNormalized();
0246             }
0247         }
0248         // generate polar coordinate
0249         nx = (m_radius * cos(angle)  * length);
0250         ny = (m_radius * sin(angle)  * length);
0251 
0252         // compute the height of the ellipse
0253         ny *= m_sprayOpOption->data.aspect;
0254 
0255         // transform
0256         m.map(nx, ny, &nx, &ny);
0257 
0258         // color transformation
0259 
0260         if (shouldColor) {
0261             if (m_colorProperties->sampleInputColor) {
0262                 colorSampler.sampleOldColor(nx + x, ny + y, m_inkColor.data());
0263             }
0264 
0265             // mix the color with background color
0266             if (m_colorProperties->mixBgColor) {
0267                 KoMixColorsOp * mixOp = dab->colorSpace()->mixColorsOp();
0268 
0269                 const quint8 *colors[2];
0270                 colors[0] = m_inkColor.data();
0271                 colors[1] = bgColor.data();
0272 
0273                 qint16 colorWeights[2];
0274                 int MAX_16BIT = 255;
0275                 qreal blend = info.pressure();
0276 
0277                 colorWeights[0] = static_cast<quint16>(blend * MAX_16BIT);
0278                 colorWeights[1] = static_cast<quint16>((1.0 - blend) * MAX_16BIT);
0279                 mixOp->mixColors(colors, colorWeights, 2, m_inkColor.data());
0280             }
0281 
0282             if (m_colorProperties->useRandomHSV && m_transfo) {
0283                 params["h"] = (m_colorProperties->hue / 180.0) * randomSource->generateNormalized();
0284                 params["s"] = (m_colorProperties->saturation / 100.0) * randomSource->generateNormalized();
0285                 params["v"] = (m_colorProperties->value / 100.0) * randomSource->generateNormalized();
0286                 m_transfo->setParameters(params);
0287                 m_transfo->setParameter(3, 1);//sets the type to HSV. For some reason 0 is not an option.
0288                 m_transfo->setParameter(4, false);//sets the colorize to false.
0289                 m_transfo->transform(m_inkColor.data(), m_inkColor.data() , 1);
0290             }
0291 
0292             if (m_colorProperties->useRandomOpacity) {
0293                 quint8 alpha = qRound(randomSource->generateNormalized() * OPACITY_OPAQUE_U8);
0294                 m_inkColor.setOpacity(alpha);
0295                 m_painter->setOpacity(alpha);
0296             }
0297 
0298             if (!m_colorProperties->colorPerParticle) {
0299                 shouldColor = false;
0300             }
0301 
0302             m_painter->setPaintColor(m_inkColor);
0303         }
0304 
0305         qreal jitteredWidth = qMax(1.0 * additionalScale, effectiveSize.width() * particleScale * additionalScale);
0306         qreal jitteredHeight = qMax(1.0 * additionalScale, effectiveSize.height() * particleScale * additionalScale);
0307 
0308         if (m_shapeProperties->enabled){
0309         switch (m_shapeProperties->shape){
0310             // ellipse
0311             case 0:
0312             {
0313                 if (effectiveSize.width() == effectiveSize.height()){
0314                     paintCircle(m_painter, nx + x, ny + y, jitteredWidth * 0.5);
0315                 }
0316                 else {
0317                     paintEllipse(m_painter, nx + x, ny + y, jitteredWidth * 0.5 , jitteredHeight * 0.5, rotationZ);
0318                 }
0319                 break;
0320             }
0321             // rectangle
0322             case 1:
0323             {
0324                 paintRectangle(m_painter, nx + x, ny + y, qRound(jitteredWidth) , qRound(jitteredHeight), rotationZ);
0325                 break;
0326             }
0327             // wu-particle
0328             case 2: {
0329                 paintParticle(accessor, m_inkColor, nx + x, ny + y);
0330                 break;
0331             }
0332             // pixel
0333             case 3: {
0334                 ix = qRound(nx + x);
0335                 iy = qRound(ny + y);
0336                 accessor->moveTo(ix, iy);
0337                 memcpy(accessor->rawData(), m_inkColor.data(), m_dabPixelSize);
0338                 break;
0339             }
0340             case 4: {
0341                 if (!m_brushQImage.isNull()) {
0342 
0343                     QTransform m;
0344                     m.rotate(rad2deg(rotationZ));
0345                     m.scale(additionalScale, additionalScale);
0346 
0347                     if (m_shapeDynamicsProperties->randomSize) {
0348                         m.scale(particleScale, particleScale);
0349                     }
0350                     m_transformed = m_brushQImage.transformed(m, Qt::SmoothTransformation);
0351                     m_imageDevice->convertFromQImage(m_transformed, 0);
0352                     KisRandomAccessorSP ac = m_imageDevice->createRandomAccessorNG();
0353                     QRect rc = m_transformed.rect();
0354 
0355                     if (m_colorProperties->useRandomHSV && m_transfo) {
0356 
0357                         for (int y = rc.y(); y < rc.y() + rc.height(); y++) {
0358                             for (int x = rc.x(); x < rc.x() + rc.width(); x++) {
0359                                 ac->moveTo(x, y);
0360                                 m_transfo->transform(ac->rawData(), ac->rawData() , 1);
0361                             }
0362                         }
0363                     }
0364 
0365                     ix = qRound(nx + x - rc.width() * 0.5);
0366                     iy = qRound(ny + y - rc.height() * 0.5);
0367                     m_painter->bitBlt(QPoint(ix, iy), m_imageDevice, rc);
0368                     m_imageDevice->clear();
0369                     break;
0370                 }
0371             }
0372             }
0373             // Auto-brush
0374         }
0375         else {
0376             KisDabShape shape(particleScale * additionalScale, 1.0, -rotationZ);
0377             QPointF hotSpot = m_brush->hotSpot(shape, info);
0378             QPointF pos(nx + x, ny + y);
0379             QPointF pt = pos - hotSpot;
0380 
0381             qint32 ix;
0382             qreal xFraction;
0383             qint32 iy;
0384             qreal yFraction;
0385 
0386             KisPaintOp::splitCoordinate(pt.x(), &ix, &xFraction);
0387             KisPaintOp::splitCoordinate(pt.y(), &iy, &yFraction);
0388 
0389             m_brush->prepareForSeqNo(info, m_dabSeqNo);
0390 
0391             //KisFixedPaintDeviceSP dab;
0392             if (m_brush->brushApplication() == IMAGESTAMP) {
0393                 m_fixedDab = m_brush->paintDevice(m_fixedDab->colorSpace(),
0394                           shape, info, xFraction, yFraction);
0395 
0396                 if (m_colorProperties->useRandomHSV && m_transfo) {
0397                     quint8 * dabPointer = m_fixedDab->data();
0398                     int pixelCount = m_fixedDab->bounds().width() * m_fixedDab->bounds().height();
0399                     m_transfo->transform(dabPointer, dabPointer, pixelCount);
0400                 }
0401 
0402             }
0403             else {
0404                 m_brush->mask(m_fixedDab, m_inkColor, shape,
0405                               info, xFraction, yFraction);
0406             }
0407             m_painter->bltFixed(QPoint(ix, iy), m_fixedDab, m_fixedDab->bounds());
0408         }
0409         if (m_colorProperties->colorPerParticle){
0410             m_inkColor=color;//reset color//
0411         }
0412     }
0413     // recover from jittering of color,
0414     // m_inkColor.opacity is recovered with every paint
0415 }
0416 
0417 
0418 
0419 void SprayBrush::paintParticle(KisRandomAccessorSP &writeAccessor, const KoColor &color, qreal rx, qreal ry)
0420 {
0421     // opacity top left, right, bottom left, right
0422     KoColor pcolor(color);
0423     //int opacity = pcolor.opacityU8();
0424 
0425     int ipx = int (rx);
0426     int ipy = int (ry);
0427     qreal fx = rx - ipx;
0428     qreal fy = ry - ipy;
0429 
0430     qreal btl = (1 - fx) * (1 - fy);
0431     qreal btr = (fx)  * (1 - fy);
0432     qreal bbl = (1 - fx) * (fy);
0433     qreal bbr = (fx)  * (fy);
0434 
0435     // this version overwrite pixels, e.g. when it sprays two particle next
0436     // to each other, the pixel with lower opacity can override other pixel.
0437     // Maybe some kind of compositing using here would be cool
0438 
0439     pcolor.setOpacity(btl);
0440     writeAccessor->moveTo(ipx  , ipy);
0441     memcpy(writeAccessor->rawData(), pcolor.data(), m_dabPixelSize);
0442 
0443     pcolor.setOpacity(btr);
0444     writeAccessor->moveTo(ipx + 1, ipy);
0445     memcpy(writeAccessor->rawData(), pcolor.data(), m_dabPixelSize);
0446 
0447     pcolor.setOpacity(bbl);
0448     writeAccessor->moveTo(ipx, ipy + 1);
0449     memcpy(writeAccessor->rawData(), pcolor.data(), m_dabPixelSize);
0450 
0451     pcolor.setOpacity(bbr);
0452     writeAccessor->moveTo(ipx + 1, ipy + 1);
0453     memcpy(writeAccessor->rawData(), pcolor.data(), m_dabPixelSize);
0454 }
0455 
0456 void SprayBrush::paintCircle(KisPainter* painter, qreal x, qreal y, qreal radius)
0457 {
0458     QPainterPath path;
0459     path.addEllipse(QPointF(x,y),radius,radius);
0460     painter->fillPainterPath(path);
0461 }
0462 
0463 
0464 void SprayBrush::paintEllipse(KisPainter* painter, qreal x, qreal y, qreal a, qreal b, qreal angle)
0465 {
0466     QPainterPath path;
0467     path.addEllipse(QPointF(), a, b);
0468     QTransform t;
0469     t.translate(x, y);
0470     t.rotateRadians(angle);
0471     path = t.map(path);
0472     painter->fillPainterPath(path);
0473 }
0474 
0475 void SprayBrush::paintRectangle(KisPainter* painter, qreal x, qreal y, qreal width, qreal height, qreal angle)
0476 {
0477     QPainterPath path;
0478     path.addRect(QRectF(-0.5 * width, -0.5 * height, width, height));
0479     QTransform t;
0480     t.translate(x, y);
0481     t.rotateRadians(angle);
0482     path = t.map(path);
0483     painter->fillPainterPath(path);
0484 }
0485 
0486 
0487 void SprayBrush::paintOutline(KisPaintDeviceSP dev , const KoColor &outlineColor, qreal posX, qreal posY, qreal radius)
0488 {
0489     QList<QPointF> antiPixels;
0490     KisRandomAccessorSP accessor = dev->createRandomAccessorNG();
0491 
0492     for (int y = -radius + posY; y <= radius + posY; y++) {
0493         for (int x = -radius + posX; x <= radius + posX; x++) {
0494             accessor->moveTo(x, y);
0495             qreal alpha = dev->colorSpace()->opacityU8(accessor->rawData());
0496 
0497             if (alpha != 0) {
0498                 // top left
0499                 accessor->moveTo(x - 1, y - 1);
0500                 if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) {
0501                     antiPixels.append(QPointF(x - 1, y - 1));
0502                     //continue;
0503                 }
0504 
0505                 // top
0506                 accessor->moveTo(x, y - 1);
0507                 if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) {
0508                     antiPixels.append(QPointF(x, y - 1));
0509                     //continue;
0510                 }
0511 
0512                 // top right
0513                 accessor->moveTo(x + 1, y - 1);
0514                 if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) {
0515                     antiPixels.append(QPointF(x + 1, y - 1));
0516                     //continue;
0517                 }
0518 
0519                 //left
0520                 accessor->moveTo(x - 1, y);
0521                 if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) {
0522                     antiPixels.append(QPointF(x - 1, y));
0523                     //continue;
0524                 }
0525 
0526                 //right
0527                 accessor->moveTo(x + 1, y);
0528                 if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) {
0529                     antiPixels.append(QPointF(x + 1, y));
0530                     //continue;
0531                 }
0532 
0533                 // bottom left
0534                 accessor->moveTo(x - 1, y + 1);
0535                 if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) {
0536                     antiPixels.append(QPointF(x - 1, y + 1));
0537                     //continue;
0538                 }
0539 
0540                 // bottom
0541                 accessor->moveTo(x, y + 1);
0542                 if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) {
0543                     antiPixels.append(QPointF(x, y + 1));
0544                     //continue;
0545                 }
0546 
0547                 // bottom right
0548                 accessor->moveTo(x + 1, y + 1);
0549                 if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) {
0550                     antiPixels.append(QPointF(x + 1, y + 1));
0551                     //continue;
0552                 }
0553             }
0554 
0555         }
0556     }
0557 
0558     // anti-alias it
0559     int size = antiPixels.size();
0560     for (int i = 0; i < size; i++) {
0561         accessor->moveTo(antiPixels[i].x(), antiPixels[i].y());
0562         memcpy(accessor->rawData(), outlineColor.data(), dev->colorSpace()->pixelSize());
0563     }
0564 }
0565 
0566 void SprayBrush::setFixedDab(KisFixedPaintDeviceSP dab)
0567 {
0568     m_fixedDab = dab;
0569 }