File indexing completed on 2024-12-22 04:16:09

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 "hairy_brush.h"
0008 
0009 #include <KoColor.h>
0010 #include <KoColorSpace.h>
0011 #include <KoColorTransformation.h>
0012 #include <KoCompositeOpRegistry.h>
0013 
0014 #include <QVariant>
0015 #include <QHash>
0016 #include <QVector>
0017 
0018 #include <kis_types.h>
0019 #include <kis_random_accessor_ng.h>
0020 #include <kis_cross_device_color_sampler.h>
0021 #include <kis_fixed_paint_device.h>
0022 
0023 
0024 #include <cmath>
0025 #include <ctime>
0026 
0027 
0028 HairyBrush::HairyBrush()
0029 {
0030     m_counter = 0;
0031     m_lastAngle = 0.0;
0032     m_oldPressure = 1.0f;
0033 
0034     m_saturationId = -1;
0035 }
0036 
0037 HairyBrush::~HairyBrush()
0038 {
0039     delete m_transfo;
0040     qDeleteAll(m_bristles.begin(), m_bristles.end());
0041     m_bristles.clear();
0042 }
0043 
0044 
0045 void HairyBrush::initAndCache()
0046 {
0047     m_compositeOp = m_dab->colorSpace()->compositeOp(COMPOSITE_OVER);
0048     m_pixelSize = m_dab->colorSpace()->pixelSize();
0049 
0050     if (m_properties->useSaturation) {
0051         m_transfo = m_dab->colorSpace()->createColorTransformation("hsv_adjustment", m_params);
0052         if (m_transfo) {
0053             m_saturationId = m_transfo->parameterId("s");
0054         }
0055     }
0056 }
0057 
0058 void HairyBrush::fromDabWithDensity(KisFixedPaintDeviceSP dab, qreal density)
0059 {
0060     int width = dab->bounds().width();
0061     int height = dab->bounds().height();
0062 
0063     int centerX = width * 0.5;
0064     int centerY = height * 0.5;
0065 
0066     // make mask
0067     Bristle * bristle = nullptr;
0068     qreal alpha;
0069 
0070     quint8 * dabPointer = dab->data();
0071     quint8 pixelSize = dab->pixelSize();
0072     const KoColorSpace * cs = dab->colorSpace();
0073     KoColor bristleColor(cs);
0074 
0075     KisRandomSource randomSource(0);
0076 
0077     for (int y = 0; y < height; y++) {
0078         for (int x = 0; x < width; x++) {
0079             alpha =  cs->opacityF(dabPointer);
0080             if (alpha != 0.0) {
0081                 if (density == 1.0 || randomSource.generateNormalized() <= density) {
0082                     memcpy(bristleColor.data(), dabPointer, pixelSize);
0083 
0084                     bristle = new Bristle(x - centerX, y - centerY, alpha); // using value from image as length of bristle
0085                     bristle->setColor(bristleColor);
0086 
0087                     m_bristles.append(bristle);
0088                 }
0089             }
0090             dabPointer += pixelSize;
0091         }
0092     }
0093 }
0094 
0095 
0096 void HairyBrush::paintLine(KisPaintDeviceSP dab, KisPaintDeviceSP layer, const KisPaintInformation &pi1, const KisPaintInformation &pi2, qreal scale, qreal rotation)
0097 {
0098     m_counter++;
0099 
0100     qreal x1 = pi1.pos().x();
0101     qreal y1 = pi1.pos().y();
0102 
0103     qreal x2 = pi2.pos().x();
0104     qreal y2 = pi2.pos().y();
0105 
0106     qreal dx = x2 - x1;
0107     qreal dy = y2 - y1;
0108 
0109     // TODO:this angle is different from the drawing angle in sensor (info.angle()). The bug is caused probably due to
0110     // not computing the drag vector properly in paintBezierLine when smoothing is used
0111     //qreal angle = atan2(dy, dx);
0112     qreal angle = rotation;
0113 
0114     qreal mousePressure = 1.0;
0115     if (m_properties->useMousePressure) { // want pressure from mouse movement
0116         qreal distance = sqrt(dx * dx + dy * dy);
0117         mousePressure = (1.0 - computeMousePressure(distance));
0118         scale *= mousePressure;
0119     }
0120     // this pressure controls shear and ink depletion
0121     qreal pressure = mousePressure * (pi2.pressure() * 2);
0122 
0123     Bristle *bristle = 0;
0124     KoColor bristleColor(dab->colorSpace());
0125 
0126     m_dabAccessor = dab->createRandomAccessorNG();
0127 
0128     m_dab = dab;
0129 
0130     // initialization block
0131     if (firstStroke()) {
0132         initAndCache();
0133     }
0134 
0135     /*If this is first time the brush touches the canvas and
0136     we are using soak ink while ink depletion is enabled...*/
0137     if (m_properties->inkDepletionEnabled &&
0138             firstStroke() && m_properties->useSoakInk) {
0139         if (layer) {
0140             colorifyBristles(layer, pi1.pos());
0141         }
0142         else {
0143             dbgKrita << "Can't soak the ink from the layer";
0144         }
0145     }
0146 
0147     KisRandomSourceSP randomSource = pi2.randomSource();
0148 
0149     qreal fx1, fy1, fx2, fy2;
0150     qreal randomX, randomY;
0151     qreal shear;
0152 
0153     float inkDepletion = 0.0;
0154     int inkDepletionSize = m_properties->inkDepletionCurve.size();
0155     int bristleCount = m_bristles.size();
0156     int bristlePathSize;
0157     qreal threshold = 1.0 - pi2.pressure();
0158     for (int i = 0; i < bristleCount; i++) {
0159 
0160         if (!m_bristles.at(i)->enabled()) continue;
0161         bristle = m_bristles[i];
0162 
0163         randomX = (randomSource->generateNormalized() * 2 - 1.0) * m_properties->randomFactor;
0164         randomY = (randomSource->generateNormalized() * 2 - 1.0) * m_properties->randomFactor;
0165 
0166         shear = pressure * m_properties->shearFactor;
0167 
0168         m_transform.reset();
0169         m_transform.rotateRadians(-angle);
0170         m_transform.scale(scale, scale);
0171         m_transform.translate(randomX, randomY);
0172         m_transform.shear(shear, shear);
0173 
0174         if (firstStroke() || (!m_properties->connectedPath)) {
0175             // transform start dab
0176             m_transform.map(bristle->x(), bristle->y(), &fx1, &fy1);
0177             // transform end dab
0178             m_transform.map(bristle->x(), bristle->y(), &fx2, &fy2);
0179         }
0180         else {
0181             // continue the path of the bristle from the previous position
0182             fx1 = bristle->prevX();
0183             fy1 = bristle->prevY();
0184             m_transform.map(bristle->x(), bristle->y(), &fx2, &fy2);
0185         }
0186         // remember the end point
0187         bristle->setPrevX(fx2);
0188         bristle->setPrevY(fy2);
0189 
0190         // all coords relative to device position
0191         fx1 += x1;
0192         fy1 += y1;
0193 
0194         fx2 += x2;
0195         fy2 += y2;
0196 
0197         if (m_properties->threshold && (bristle->length() < threshold)) continue;
0198         // paint between first and last dab
0199         const QVector<QPointF> bristlePath = m_trajectory.getLinearTrajectory(QPointF(fx1, fy1), QPointF(fx2, fy2), 1.0);
0200         bristlePathSize = m_trajectory.size();
0201 
0202         // avoid overlapping bristle caps with antialias on
0203         if (m_properties->antialias) {
0204             bristlePathSize -= 1;
0205         }
0206 
0207         memcpy(bristleColor.data(), bristle->color().data() , m_pixelSize);
0208         for (int i = 0; i < bristlePathSize ; i++) {
0209 
0210             if (m_properties->inkDepletionEnabled) {
0211                 inkDepletion = fetchInkDepletion(bristle, inkDepletionSize);
0212 
0213                 if (m_properties->useSaturation && m_transfo != 0) {
0214                     saturationDepletion(bristle, bristleColor, pressure, inkDepletion);
0215                 }
0216 
0217                 if (m_properties->useOpacity) {
0218                     opacityDepletion(bristle, bristleColor, pressure, inkDepletion);
0219                 }
0220 
0221             }
0222             else {
0223                 if (bristleColor.opacityU8() != 0) {
0224                     bristleColor.setOpacity(bristle->length());
0225                 }
0226             }
0227 
0228             addBristleInk(bristle, bristlePath.at(i), bristleColor);
0229             bristle->setInkAmount(1.0 - inkDepletion);
0230             bristle->upIncrement();
0231         }
0232 
0233     }
0234     m_dab = nullptr;
0235     m_dabAccessor = nullptr;
0236 }
0237 
0238 
0239 inline qreal HairyBrush::fetchInkDepletion(Bristle* bristle, int inkDepletionSize)
0240 {
0241     if (bristle->counter() >= inkDepletionSize - 1) {
0242         return m_properties->inkDepletionCurve[inkDepletionSize - 1];
0243     } else {
0244         return m_properties->inkDepletionCurve[bristle->counter()];
0245     }
0246 }
0247 
0248 
0249 void HairyBrush::saturationDepletion(Bristle * bristle, KoColor &bristleColor, qreal pressure, qreal inkDepletion)
0250 {
0251     qreal saturation;
0252     if (m_properties->useWeights) {
0253         // new weighted way (experiment)
0254         saturation = (
0255                          (pressure * m_properties->pressureWeight) +
0256                          (bristle->length() * m_properties->bristleLengthWeight) +
0257                          (bristle->inkAmount() * m_properties->bristleInkAmountWeight) +
0258                          ((1.0 - inkDepletion) * m_properties->inkDepletionWeight)) - 1.0;
0259     }
0260     else {
0261         // old way of computing saturation
0262         saturation = (
0263                          pressure *
0264                          bristle->length() *
0265                          bristle->inkAmount() *
0266                          (1.0 - inkDepletion)) - 1.0;
0267 
0268     }
0269     m_transfo->setParameter(m_transfo->parameterId("h"), 0.0);
0270     m_transfo->setParameter(m_transfo->parameterId("v"), 0.0);
0271     m_transfo->setParameter(m_saturationId, saturation);
0272     m_transfo->setParameter(3, 1);//sets the type to
0273     m_transfo->setParameter(4, false);//sets the colorize to none.
0274     m_transfo->transform(bristleColor.data(), bristleColor.data() , 1);
0275 }
0276 
0277 void HairyBrush::opacityDepletion(Bristle* bristle, KoColor& bristleColor, qreal pressure, qreal inkDepletion)
0278 {
0279     qreal opacity = OPACITY_OPAQUE_F;
0280     if (m_properties->useWeights) {
0281         opacity = pressure * m_properties->pressureWeight +
0282                   bristle->length() * m_properties->bristleLengthWeight +
0283                   bristle->inkAmount() * m_properties->bristleInkAmountWeight +
0284                   (1.0 - inkDepletion) * m_properties->inkDepletionWeight;
0285     }
0286     else {
0287         opacity =
0288             bristle->length() *
0289             bristle->inkAmount();
0290     }
0291 
0292     opacity = qBound(0.0, opacity, 1.0);
0293     bristleColor.setOpacity(opacity);
0294 }
0295 
0296 inline void HairyBrush::addBristleInk(Bristle *bristle,const QPointF &pos, const KoColor &color)
0297 {
0298     Q_UNUSED(bristle);
0299     if (m_properties->antialias) {
0300         if (m_properties->useCompositing) {
0301             paintParticle(pos, color);
0302         } else {
0303             paintParticle(pos, color, 1.0);
0304         }
0305     }
0306     else {
0307         int ix = qRound(pos.x());
0308         int iy = qRound(pos.y());
0309         if (m_properties->useCompositing) {
0310             plotPixel(ix, iy, color);
0311         }
0312         else {
0313             darkenPixel(ix, iy, color);
0314         }
0315     }
0316 }
0317 
0318 void HairyBrush::paintParticle(QPointF pos, const KoColor& color, qreal weight)
0319 {
0320     // opacity top left, right, bottom left, right
0321     quint8 opacity = color.opacityU8();
0322     opacity *= weight;
0323 
0324     int ipx = int (pos.x());
0325     int ipy = int (pos.y());
0326     qreal fx = qAbs(pos.x() - ipx);
0327     qreal fy = qAbs(pos.y() - ipy);
0328 
0329     quint8 btl = qRound((1.0 - fx) * (1.0 - fy) * opacity);
0330     quint8 btr = qRound((fx)  * (1.0 - fy) * opacity);
0331     quint8 bbl = qRound((1.0 - fx) * (fy)  * opacity);
0332     quint8 bbr = qRound((fx)  * (fy)  * opacity);
0333 
0334     const KoColorSpace * cs = m_dab->colorSpace();
0335 
0336     m_dabAccessor->moveTo(ipx  , ipy);
0337     btl = quint8(qBound<quint16>(OPACITY_TRANSPARENT_U8, btl + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8));
0338     memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize());
0339     cs->setOpacity(m_dabAccessor->rawData(), btl, 1);
0340 
0341     m_dabAccessor->moveTo(ipx + 1, ipy);
0342     btr =  quint8(qBound<quint16>(OPACITY_TRANSPARENT_U8, btr + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8));
0343     memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize());
0344     cs->setOpacity(m_dabAccessor->rawData(), btr, 1);
0345 
0346     m_dabAccessor->moveTo(ipx, ipy + 1);
0347     bbl = quint8(qBound<quint16>(OPACITY_TRANSPARENT_U8, bbl + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8));
0348     memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize());
0349     cs->setOpacity(m_dabAccessor->rawData(), bbl, 1);
0350 
0351     m_dabAccessor->moveTo(ipx + 1, ipy + 1);
0352     bbr = quint8(qBound<quint16>(OPACITY_TRANSPARENT_U8, bbr + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8));
0353     memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize());
0354     cs->setOpacity(m_dabAccessor->rawData(), bbr, 1);
0355 }
0356 
0357 void HairyBrush::paintParticle(QPointF pos, const KoColor& color)
0358 {
0359     // opacity top left, right, bottom left, right
0360     memcpy(m_color.data(), color.data(), m_pixelSize);
0361     quint8 opacity = color.opacityU8();
0362 
0363     int ipx = int (pos.x());
0364     int ipy = int (pos.y());
0365     qreal fx = qAbs(pos.x() - ipx);
0366     qreal fy = qAbs(pos.y() - ipy);
0367 
0368     quint8 btl = qRound((1.0 - fx) * (1.0 - fy) * opacity);
0369     quint8 btr = qRound((fx)  * (1.0 - fy) * opacity);
0370     quint8 bbl = qRound((1.0 - fx) * (fy)  * opacity);
0371     quint8 bbr = qRound((fx)  * (fy)  * opacity);
0372 
0373     m_color.setOpacity(btl);
0374     plotPixel(ipx  , ipy, m_color);
0375 
0376     m_color.setOpacity(btr);
0377     plotPixel(ipx + 1  , ipy, m_color);
0378 
0379     m_color.setOpacity(bbl);
0380     plotPixel(ipx  , ipy + 1, m_color);
0381 
0382     m_color.setOpacity(bbr);
0383     plotPixel(ipx + 1 , ipy + 1, m_color);
0384 }
0385 
0386 
0387 inline void HairyBrush::plotPixel(int wx, int wy, const KoColor &color)
0388 {
0389     m_dabAccessor->moveTo(wx, wy);
0390     m_compositeOp->composite(m_dabAccessor->rawData(), m_pixelSize, color.data() , m_pixelSize, 0, 0, 1, 1, OPACITY_OPAQUE_U8);
0391 }
0392 
0393 inline void HairyBrush::darkenPixel(int wx, int wy, const KoColor &color)
0394 {
0395     m_dabAccessor->moveTo(wx, wy);
0396     if (m_dab->colorSpace()->opacityU8(m_dabAccessor->rawData()) < color.opacityU8()) {
0397         memcpy(m_dabAccessor->rawData(), color.data(), m_pixelSize);
0398     }
0399 }
0400 
0401 double HairyBrush::computeMousePressure(double distance)
0402 {
0403     static const double scale = 20.0;
0404     static const double minPressure = 0.02;
0405 
0406     double oldPressure = m_oldPressure;
0407 
0408     double factor = 1.0 - distance / scale;
0409     if (factor < 0.0) factor = 0.0;
0410 
0411     double result = ((4.0 * oldPressure) + minPressure + factor) / 5.0;
0412 
0413     m_oldPressure = result;
0414     return result;
0415 }
0416 
0417 
0418 void HairyBrush::colorifyBristles(KisPaintDeviceSP source, QPointF point)
0419 {
0420     KoColor bristleColor(m_dab->colorSpace());
0421     KisCrossDeviceColorSamplerInt colorSampler(source, bristleColor);
0422 
0423     Bristle *b = 0;
0424     int size = m_bristles.size();
0425     for (int i = 0; i < size; i++) {
0426         b = m_bristles[i];
0427         int x = qRound(b->x() + point.x());
0428         int y = qRound(b->y() + point.y());
0429 
0430         colorSampler.sampleOldColor(x, y, bristleColor.data());
0431         b->setColor(bristleColor);
0432     }
0433 
0434 }
0435 
0436