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

0001 /*
0002  *  SPDX-FileCopyrightText: 2020 Peter Schatz <voronwe13@gmail.com>
0003  *  SPDX-FileCopyrightText: 2021 Dmitry Kazakov <dimula73@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include <KoCompositeOpRegistry.h>
0009 #include "KisColorSmudgeStrategyBase.h"
0010 #include "kis_painter.h"
0011 #include "kis_fixed_paint_device.h"
0012 #include "kis_paint_device.h"
0013 #include "KisColorSmudgeSampleUtils.h"
0014 
0015 /**********************************************************************************/
0016 /*                 DabColoringStrategyMask                                        */
0017 /**********************************************************************************/
0018 
0019 bool KisColorSmudgeStrategyBase::DabColoringStrategyMask::supportsFusedDullingBlending() const
0020 {
0021     return true;
0022 }
0023 
0024 void KisColorSmudgeStrategyBase::DabColoringStrategyMask::blendInFusedBackgroundAndColorRateWithDulling(
0025         KisFixedPaintDeviceSP dst, KisColorSmudgeSourceSP src, const QRect &dstRect,
0026         const KoColor &preparedDullingColor, const KoCompositeOp *smearOp, const quint8 smudgeRateOpacity,
0027         const KoColor &paintColor, const KoCompositeOp *colorRateOp, const quint8 colorRateOpacity) const
0028 {
0029     KoColor dullingFillColor(preparedDullingColor);
0030 
0031     KIS_SAFE_ASSERT_RECOVER_RETURN(*paintColor.colorSpace() == *colorRateOp->colorSpace());
0032     colorRateOp->composite(dullingFillColor.data(), 1, paintColor.data(), 1, 0, 0, 1, 1, colorRateOpacity);
0033 
0034     if (smearOp->id() == COMPOSITE_COPY && smudgeRateOpacity == OPACITY_OPAQUE_U8) {
0035         dst->fill(dst->bounds(), dullingFillColor);
0036     } else {
0037         src->readBytes(dst->data(), dstRect);
0038         smearOp->composite(dst->data(), dstRect.width() * dst->pixelSize(),
0039                            dullingFillColor.data(), 0,
0040                            0, 0,
0041                            1, dstRect.width() * dstRect.height(),
0042                            smudgeRateOpacity);
0043     }
0044 }
0045 
0046 void KisColorSmudgeStrategyBase::DabColoringStrategyMask::blendInColorRate(const KoColor &paintColor,
0047                                                                            const KoCompositeOp *colorRateOp,
0048                                                                            quint8 colorRateOpacity,
0049                                                                            KisFixedPaintDeviceSP dstDevice,
0050                                                                            const QRect &dstRect) const
0051 {
0052     KIS_SAFE_ASSERT_RECOVER_RETURN(*paintColor.colorSpace() == *colorRateOp->colorSpace());
0053 
0054     colorRateOp->composite(dstDevice->data(), dstRect.width() * dstDevice->pixelSize(),
0055                            paintColor.data(), 0,
0056                            0, 0,
0057                            dstRect.height(), dstRect.width(),
0058                            colorRateOpacity);
0059 }
0060 
0061 /**********************************************************************************/
0062 /*                 DabColoringStrategyStamp                                       */
0063 /**********************************************************************************/
0064 
0065 void KisColorSmudgeStrategyBase::DabColoringStrategyStamp::setStampDab(KisFixedPaintDeviceSP device)
0066 {
0067     m_origDab = device;
0068 }
0069 
0070 void KisColorSmudgeStrategyBase::DabColoringStrategyStamp::blendInColorRate(const KoColor &paintColor,
0071                                                                             const KoCompositeOp *colorRateOp,
0072                                                                             quint8 colorRateOpacity,
0073                                                                             KisFixedPaintDeviceSP dstDevice,
0074                                                                             const QRect &dstRect) const
0075 {
0076     Q_UNUSED(paintColor);
0077 
0078     // TODO: check correctness for composition source device (transparency masks)
0079     KIS_ASSERT_RECOVER_RETURN(*dstDevice->colorSpace() == *m_origDab->colorSpace());
0080 
0081     colorRateOp->composite(dstDevice->data(), dstRect.width() * dstDevice->pixelSize(),
0082                            m_origDab->data(), dstRect.width() * m_origDab->pixelSize(),
0083                            0, 0,
0084                            dstRect.height(), dstRect.width(),
0085                            colorRateOpacity);
0086 }
0087 
0088 bool KisColorSmudgeStrategyBase::DabColoringStrategyStamp::supportsFusedDullingBlending() const
0089 {
0090     return false;
0091 }
0092 
0093 void KisColorSmudgeStrategyBase::DabColoringStrategyStamp::blendInFusedBackgroundAndColorRateWithDulling(
0094         KisFixedPaintDeviceSP dst, KisColorSmudgeSourceSP src, const QRect &dstRect,
0095         const KoColor &preparedDullingColor, const KoCompositeOp *smearOp, const quint8 smudgeRateOpacity,
0096         const KoColor &paintColor, const KoCompositeOp *colorRateOp, const quint8 colorRateOpacity) const
0097 {
0098     Q_UNUSED(dst);
0099     Q_UNUSED(src);
0100     Q_UNUSED(dstRect);
0101     Q_UNUSED(preparedDullingColor);
0102     Q_UNUSED(smearOp);
0103     Q_UNUSED(smudgeRateOpacity);
0104     Q_UNUSED(paintColor);
0105     Q_UNUSED(colorRateOp);
0106     Q_UNUSED(colorRateOpacity);
0107 }
0108 
0109 /**********************************************************************************/
0110 /*                 KisColorSmudgeStrategyBase                                     */
0111 /**********************************************************************************/
0112 
0113 KisColorSmudgeStrategyBase::KisColorSmudgeStrategyBase(bool useDullingMode)
0114         : m_useDullingMode(useDullingMode)
0115 {
0116 }
0117 
0118 void KisColorSmudgeStrategyBase::initializePaintingImpl(const KoColorSpace *dstColorSpace, bool smearAlpha,
0119                                                         const QString &colorRateCompositeOpId)
0120 {
0121     m_blendDevice = new KisFixedPaintDevice(dstColorSpace, m_memoryAllocator);
0122     m_smearOp = dstColorSpace->compositeOp(smearCompositeOp(smearAlpha));
0123     m_colorRateOp = dstColorSpace->compositeOp(colorRateCompositeOpId);
0124     m_preparedDullingColor.convertTo(dstColorSpace);
0125 }
0126 
0127 const KoColorSpace *KisColorSmudgeStrategyBase::preciseColorSpace() const
0128 {
0129     // verify that initialize() has already been called!
0130     KIS_ASSERT_RECOVER_RETURN_VALUE(m_smearOp, KoColorSpaceRegistry::instance()->rgb8());
0131 
0132     return m_smearOp->colorSpace();
0133 }
0134 
0135 QString KisColorSmudgeStrategyBase::smearCompositeOp(bool smearAlpha) const
0136 {
0137     return smearAlpha ? COMPOSITE_COPY : COMPOSITE_OVER;
0138 }
0139 
0140 QString KisColorSmudgeStrategyBase::finalCompositeOp(bool smearAlpha) const
0141 {
0142     Q_UNUSED(smearAlpha);
0143     return COMPOSITE_COPY;
0144 }
0145 
0146 quint8 KisColorSmudgeStrategyBase::finalPainterOpacity(qreal opacity, qreal smudgeRateValue)
0147 {
0148     Q_UNUSED(opacity);
0149     Q_UNUSED(smudgeRateValue);
0150 
0151     return OPACITY_OPAQUE_U8;
0152 }
0153 
0154 quint8 KisColorSmudgeStrategyBase::colorRateOpacity(qreal opacity, qreal smudgeRateValue, qreal colorRateValue,
0155                                                     qreal maxPossibleSmudgeRateValue)
0156 {
0157     Q_UNUSED(smudgeRateValue);
0158     Q_UNUSED(maxPossibleSmudgeRateValue);
0159     return qRound(colorRateValue * colorRateValue * opacity * 255.0);
0160 }
0161 
0162 quint8 KisColorSmudgeStrategyBase::dullingRateOpacity(qreal opacity, qreal smudgeRateValue)
0163 {
0164     return qRound(0.8 * smudgeRateValue * opacity * 255.0);
0165 }
0166 
0167 quint8 KisColorSmudgeStrategyBase::smearRateOpacity(qreal opacity, qreal smudgeRateValue)
0168 {
0169     return qRound(smudgeRateValue * opacity * 255.0);
0170 }
0171 
0172 void KisColorSmudgeStrategyBase::sampleDullingColor(const QRect &srcRect, qreal sampleRadiusValue,
0173                                                     KisColorSmudgeSourceSP sourceDevice,
0174                                                     KisFixedPaintDeviceSP tempFixedDevice,
0175                                                     KisFixedPaintDeviceSP maskDab, KoColor *resultColor)
0176 {
0177     using namespace KisColorSmudgeSampleUtils;
0178     sampleColor<WeightedSampleWrapper>(srcRect, sampleRadiusValue,
0179                                        sourceDevice, tempFixedDevice,
0180                                        maskDab, resultColor);
0181 }
0182 
0183 void
0184 KisColorSmudgeStrategyBase::blendBrush(const QVector<KisPainter *> dstPainters, KisColorSmudgeSourceSP srcSampleDevice,
0185                                        KisFixedPaintDeviceSP maskDab, bool preserveMaskDab, const QRect &srcRect,
0186                                        const QRect &dstRect, const KoColor &currentPaintColor, qreal opacity,
0187                                        qreal smudgeRateValue, qreal maxPossibleSmudgeRateValue, qreal colorRateValue,
0188                                        qreal smudgeRadiusValue)
0189 {
0190     const quint8 colorRateOpacity = this->colorRateOpacity(opacity, smudgeRateValue, colorRateValue, maxPossibleSmudgeRateValue);
0191 
0192     if (m_useDullingMode) {
0193         this->sampleDullingColor(srcRect,
0194                                  smudgeRadiusValue,
0195                                  srcSampleDevice, m_blendDevice,
0196                                  maskDab, &m_preparedDullingColor);
0197 
0198         KIS_SAFE_ASSERT_RECOVER(*m_preparedDullingColor.colorSpace() == *m_colorRateOp->colorSpace()) {
0199             m_preparedDullingColor.convertTo(m_colorRateOp->colorSpace());
0200         }
0201     }
0202 
0203     m_blendDevice->setRect(dstRect);
0204     m_blendDevice->lazyGrowBufferWithoutInitialization();
0205 
0206     DabColoringStrategy &coloringStrategy = this->coloringStrategy();
0207 
0208     const quint8 dullingRateOpacity = this->dullingRateOpacity(opacity, smudgeRateValue);
0209 
0210     if (colorRateOpacity > 0 &&
0211         m_useDullingMode &&
0212         coloringStrategy.supportsFusedDullingBlending() &&
0213         ((m_smearOp->id() == COMPOSITE_OVER &&
0214           m_colorRateOp->id() == COMPOSITE_OVER) ||
0215          (m_smearOp->id() == COMPOSITE_COPY &&
0216           dullingRateOpacity == OPACITY_OPAQUE_U8))) {
0217 
0218         coloringStrategy.blendInFusedBackgroundAndColorRateWithDulling(m_blendDevice,
0219                                                                        srcSampleDevice,
0220                                                                        dstRect,
0221                                                                        m_preparedDullingColor,
0222                                                                        m_smearOp,
0223                                                                        dullingRateOpacity,
0224                                                                        currentPaintColor.convertedTo(
0225                                                                                m_preparedDullingColor.colorSpace()),
0226                                                                        m_colorRateOp,
0227                                                                        colorRateOpacity);
0228 
0229     } else {
0230         if (!m_useDullingMode) {
0231             const quint8 smudgeRateOpacity = this->smearRateOpacity(opacity, smudgeRateValue);
0232             blendInBackgroundWithSmearing(m_blendDevice, srcSampleDevice,
0233                                           srcRect, dstRect, smudgeRateOpacity);
0234         } else {
0235             blendInBackgroundWithDulling(m_blendDevice, srcSampleDevice,
0236                                          dstRect,
0237                                          m_preparedDullingColor, dullingRateOpacity);
0238         }
0239 
0240         if (colorRateOpacity > 0) {
0241             coloringStrategy.blendInColorRate(
0242                     currentPaintColor.convertedTo(m_preparedDullingColor.colorSpace()),
0243                     m_colorRateOp,
0244                     colorRateOpacity,
0245                     m_blendDevice, dstRect);
0246         }
0247     }
0248 
0249     const bool preserveDab = preserveMaskDab && dstPainters.size() > 1;
0250 
0251     Q_FOREACH (KisPainter *dstPainter, dstPainters) {
0252         dstPainter->setOpacity(finalPainterOpacity(opacity, smudgeRateValue));
0253 
0254         dstPainter->bltFixedWithFixedSelection(dstRect.x(), dstRect.y(),
0255                                                m_blendDevice, maskDab,
0256                                                maskDab->bounds().x(), maskDab->bounds().y(),
0257                                                m_blendDevice->bounds().x(), m_blendDevice->bounds().y(),
0258                                                dstRect.width(), dstRect.height());
0259         dstPainter->renderMirrorMaskSafe(dstRect, m_blendDevice, maskDab, preserveDab);
0260     }
0261 
0262 }
0263 
0264 void KisColorSmudgeStrategyBase::blendInBackgroundWithSmearing(KisFixedPaintDeviceSP dst, KisColorSmudgeSourceSP src,
0265                                                                const QRect &srcRect, const QRect &dstRect,
0266                                                                const quint8 smudgeRateOpacity)
0267 {
0268     if (m_smearOp->id() == COMPOSITE_COPY && smudgeRateOpacity == OPACITY_OPAQUE_U8) {
0269         src->readBytes(dst->data(), srcRect);
0270     } else {
0271         src->readBytes(dst->data(), dstRect);
0272 
0273         KisFixedPaintDevice tempDevice(src->colorSpace(), m_memoryAllocator);
0274         tempDevice.setRect(srcRect);
0275         tempDevice.lazyGrowBufferWithoutInitialization();
0276 
0277         src->readBytes(tempDevice.data(), srcRect);
0278         m_smearOp->composite(dst->data(), dstRect.width() * dst->pixelSize(),
0279                              tempDevice.data(), dstRect.width() * tempDevice.pixelSize(), // stride should be random non-zero
0280                              0, 0,
0281                              1, dstRect.width() * dstRect.height(),
0282                              smudgeRateOpacity);
0283     }
0284 }
0285 
0286 void KisColorSmudgeStrategyBase::blendInBackgroundWithDulling(KisFixedPaintDeviceSP dst, KisColorSmudgeSourceSP src,
0287                                                               const QRect &dstRect, const KoColor &preparedDullingColor,
0288                                                               const quint8 smudgeRateOpacity)
0289 {
0290     Q_UNUSED(preparedDullingColor);
0291 
0292     if (m_smearOp->id() == COMPOSITE_COPY && smudgeRateOpacity == OPACITY_OPAQUE_U8) {
0293         dst->fill(dst->bounds(), m_preparedDullingColor);
0294     } else {
0295         src->readBytes(dst->data(), dstRect);
0296         m_smearOp->composite(dst->data(), dstRect.width() * dst->pixelSize(),
0297                              m_preparedDullingColor.data(), 0,
0298                              0, 0,
0299                              1, dstRect.width() * dstRect.height(),
0300                              smudgeRateOpacity);
0301     }
0302 }