File indexing completed on 2024-05-26 04:27:38

0001 /*
0002  *  SPDX-FileCopyrightText: 2016 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_colorize_stroke_strategy.h"
0008 
0009 #include <QBitArray>
0010 
0011 #include "krita_utils.h"
0012 #include "kis_paint_device.h"
0013 #include "kis_lazy_fill_tools.h"
0014 #include "kis_gaussian_kernel.h"
0015 #include "kis_painter.h"
0016 #include "kis_default_bounds_base.h"
0017 #include "kis_lod_transform.h"
0018 #include "kis_node.h"
0019 #include "kis_image_config.h"
0020 #include "KisWatershedWorker.h"
0021 #include "kis_processing_visitor.h"
0022 
0023 #include "kis_transaction.h"
0024 
0025 #include <KisRunnableStrokeJobData.h>
0026 #include <KisRunnableStrokeJobUtils.h>
0027 #include <KisRunnableStrokeJobsInterface.h>
0028 
0029 using namespace KisLazyFillTools;
0030 
0031 struct KisColorizeStrokeStrategy::Private
0032 {
0033     Private() : filteredSourceValid(false) {}
0034     Private(const Private &rhs, int _levelOfDetail)
0035         : progressNode(rhs.progressNode)
0036         , src(rhs.src)
0037         , dst(rhs.dst)
0038         , filteredSource(rhs.filteredSource)
0039         , internalFilteredSource(rhs.internalFilteredSource)
0040         , filteredSourceValid(rhs.filteredSourceValid)
0041         , boundingRect(rhs.boundingRect)
0042         , prefilterOnly(rhs.prefilterOnly)
0043         , levelOfDetail(_levelOfDetail)
0044         , keyStrokes(rhs.keyStrokes)
0045         , filteringOptions(rhs.filteringOptions)
0046     {}
0047 
0048     KisNodeSP progressNode;
0049     QSharedPointer<KisProcessingVisitor::ProgressHelper> progressHelper;
0050     KisPaintDeviceSP src;
0051     KisPaintDeviceSP dst;
0052     KisPaintDeviceSP filteredSource;
0053     KisPaintDeviceSP heightMap;
0054     KisPaintDeviceSP internalFilteredSource;
0055     bool filteredSourceValid;
0056     QRect boundingRect;
0057 
0058     bool prefilterOnly = false;
0059     int levelOfDetail = 0;
0060 
0061     QVector<KeyStroke> keyStrokes;
0062 
0063     // default values: disabled
0064     FilteringOptions filteringOptions;
0065 };
0066 
0067 KisColorizeStrokeStrategy::KisColorizeStrokeStrategy(KisPaintDeviceSP src,
0068                                                      KisPaintDeviceSP dst,
0069                                                      KisPaintDeviceSP filteredSource,
0070                                                      bool filteredSourceValid,
0071                                                      const QRect &boundingRect,
0072                                                      KisNodeSP progressNode,
0073                                                      bool prefilterOnly)
0074     : KisRunnableBasedStrokeStrategy(QLatin1String("colorize-stroke"), prefilterOnly ? kundo2_i18n("Prefilter Colorize Mask") : kundo2_i18n("Colorize")),
0075       m_d(new Private)
0076 {
0077     m_d->progressNode = progressNode;
0078     m_d->src = src;
0079     m_d->dst = dst;
0080     m_d->filteredSource = filteredSource;
0081     m_d->boundingRect = boundingRect;
0082     m_d->filteredSourceValid = filteredSourceValid;
0083     m_d->prefilterOnly = prefilterOnly;
0084 
0085     enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
0086     enableJob(JOB_DOSTROKE, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
0087     enableJob(JOB_CANCEL, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
0088 
0089     setNeedsExplicitCancel(true);
0090     setRequestsOtherStrokesToEnd(false);
0091     setClearsRedoOnStart(false);
0092 }
0093 
0094 KisColorizeStrokeStrategy::KisColorizeStrokeStrategy(const KisColorizeStrokeStrategy &rhs, int levelOfDetail)
0095     : KisRunnableBasedStrokeStrategy(rhs),
0096       m_d(new Private(*rhs.m_d, levelOfDetail))
0097 {
0098     KisLodTransform t(levelOfDetail);
0099     m_d->boundingRect = t.map(rhs.m_d->boundingRect);
0100 }
0101 
0102 KisColorizeStrokeStrategy::~KisColorizeStrokeStrategy()
0103 {
0104 }
0105 
0106 void KisColorizeStrokeStrategy::setFilteringOptions(const FilteringOptions &value)
0107 {
0108     m_d->filteringOptions = value;
0109 }
0110 
0111 FilteringOptions KisColorizeStrokeStrategy::filteringOptions() const
0112 {
0113     return m_d->filteringOptions;
0114 }
0115 
0116 void KisColorizeStrokeStrategy::addKeyStroke(KisPaintDeviceSP dev, const KoColor &color)
0117 {
0118     KoColor convertedColor(color);
0119     convertedColor.convertTo(m_d->dst->colorSpace());
0120 
0121     m_d->keyStrokes << KeyStroke(dev, convertedColor);
0122 }
0123 
0124 void KisColorizeStrokeStrategy::initStrokeCallback()
0125 {
0126     using namespace KritaUtils;
0127 
0128     QVector<KisRunnableStrokeJobData*> jobs;
0129 
0130     const QVector<QRect> patchRects =
0131         splitRectIntoPatches(m_d->boundingRect, optimalPatchSize());
0132 
0133     if (!m_d->filteredSourceValid) {
0134         // TODO: make this conversion concurrent!!!
0135         KisPaintDeviceSP filteredMainDev = KisPainter::convertToAlphaAsAlpha(m_d->src);
0136         filteredMainDev->setDefaultBounds(m_d->src->defaultBounds());
0137 
0138         struct PrefilterSharedState {
0139             QRect boundingRect;
0140             KisPaintDeviceSP filteredMainDev;
0141             KisPaintDeviceSP filteredMainDevSavedCopy;
0142             QScopedPointer<KisTransaction> activeTransaction;
0143             FilteringOptions filteringOptions;
0144         };
0145 
0146         QSharedPointer<PrefilterSharedState> state(new PrefilterSharedState());
0147         state->boundingRect = m_d->boundingRect;
0148         state->filteredMainDev = filteredMainDev;
0149         state->filteringOptions = m_d->filteringOptions;
0150 
0151         if (m_d->filteringOptions.useEdgeDetection &&
0152             m_d->filteringOptions.edgeDetectionSize > 0.0) {
0153 
0154             addJobSequential(jobs, [state] () {
0155                 state->activeTransaction.reset(new KisTransaction(state->filteredMainDev));
0156             });
0157 
0158             Q_FOREACH (const QRect &rc, patchRects) {
0159                 addJobConcurrent(jobs, [state, rc] () {
0160                     KisLodTransformScalar t(state->filteredMainDev);
0161                     KisGaussianKernel::applyLoG(state->filteredMainDev,
0162                                                 rc,
0163                                                 t.scale(0.5 * state->filteringOptions.edgeDetectionSize),
0164                                                 -1.0,
0165                                                 QBitArray(), 0);
0166                 });
0167             }
0168 
0169             addJobSequential(jobs, [state] () {
0170                 state->activeTransaction.reset();
0171                 normalizeAlpha8Device(state->filteredMainDev, state->boundingRect);
0172                 state->activeTransaction.reset(new KisTransaction(state->filteredMainDev));
0173             });
0174 
0175             Q_FOREACH (const QRect &rc, patchRects) {
0176                 addJobConcurrent(jobs, [state, rc] () {
0177                     KisLodTransformScalar t(state->filteredMainDev);
0178                     KisGaussianKernel::applyGaussian(state->filteredMainDev,
0179                                                      rc,
0180                                                      t.scale(state->filteringOptions.edgeDetectionSize),
0181                                                      t.scale(state->filteringOptions.edgeDetectionSize),
0182                                                      QBitArray(), 0);
0183                 });
0184             }
0185 
0186             addJobSequential(jobs, [state] () {
0187                 state->activeTransaction.reset();
0188             });
0189         }
0190 
0191         if (m_d->filteringOptions.fuzzyRadius > 0) {
0192 
0193             addJobSequential(jobs, [state] () {
0194                 state->filteredMainDevSavedCopy = new KisPaintDevice(*state->filteredMainDev);
0195                 state->activeTransaction.reset(new KisTransaction(state->filteredMainDev));
0196             });
0197 
0198             Q_FOREACH (const QRect &rc, patchRects) {
0199                 addJobConcurrent(jobs, [state, rc] () {
0200                     KisLodTransformScalar t(state->filteredMainDev);
0201                     KisGaussianKernel::applyGaussian(state->filteredMainDev,
0202                                                      rc,
0203                                                      t.scale(state->filteringOptions.fuzzyRadius),
0204                                                      t.scale(state->filteringOptions.fuzzyRadius),
0205                                                      QBitArray(), 0);
0206                     KisPainter gc(state->filteredMainDev);
0207                     gc.bitBlt(rc.topLeft(), state->filteredMainDevSavedCopy, rc);
0208                 });
0209             }
0210 
0211             addJobSequential(jobs, [state] () {
0212                 state->activeTransaction.reset();
0213             });
0214         }
0215 
0216         addJobSequential(jobs, [this, state] () {
0217             normalizeAndInvertAlpha8Device(state->filteredMainDev, state->boundingRect);
0218 
0219             KisDefaultBoundsBaseSP oldBounds = m_d->filteredSource->defaultBounds();
0220             m_d->filteredSource->makeCloneFrom(state->filteredMainDev, m_d->boundingRect);
0221             m_d->filteredSource->setDefaultBounds(oldBounds);
0222             m_d->filteredSourceValid = true;
0223         });
0224     }
0225 
0226     if (!m_d->prefilterOnly) {
0227         addJobSequential(jobs, [this] () {
0228             m_d->heightMap = new KisPaintDevice(*m_d->filteredSource);
0229         });
0230 
0231         Q_FOREACH (const QRect &rc, patchRects) {
0232             addJobConcurrent(jobs, [this, rc] () {
0233                 KritaUtils::filterAlpha8Device(m_d->heightMap, rc,
0234                                                [](quint8 pixel) {
0235                                                    return quint8(255 - pixel);
0236                                                });
0237             });
0238         }
0239 
0240         addJobSequential(jobs, [this] () {
0241             m_d->progressHelper.reset(new KisProcessingVisitor::ProgressHelper(m_d->progressNode));
0242 
0243             KisWatershedWorker worker(m_d->heightMap, m_d->dst, m_d->boundingRect, m_d->progressHelper->updater());
0244             Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) {
0245                 KoColor color =
0246                     !stroke.isTransparent ?
0247                     stroke.color : KoColor::createTransparent(m_d->dst->colorSpace());
0248 
0249                 worker.addKeyStroke(stroke.dev, color);
0250             }
0251             worker.run(m_d->filteringOptions.cleanUpAmount);
0252             m_d->progressHelper.reset();
0253         });
0254     }
0255 
0256     addJobSequential(jobs, [this] () {
0257         emit sigFinished(m_d->prefilterOnly);
0258     });
0259 
0260     runnableJobsInterface()->addRunnableJobs(jobs);
0261 }
0262 
0263 void KisColorizeStrokeStrategy::cancelStrokeCallback()
0264 {
0265     emit sigCancelled();
0266 }
0267 
0268 void KisColorizeStrokeStrategy::tryCancelCurrentStrokeJobAsync()
0269 {
0270     // NOTE: this method may be called by the GUI thread asynchronously!
0271     QSharedPointer<KisProcessingVisitor::ProgressHelper> helper = m_d->progressHelper;
0272     if (helper) {
0273         helper->cancel();
0274     }
0275 }
0276 
0277 KisStrokeStrategy* KisColorizeStrokeStrategy::createLodClone(int levelOfDetail)
0278 {
0279     KisImageConfig cfg(true);
0280     if (!cfg.useLodForColorizeMask()) return 0;
0281 
0282     KisColorizeStrokeStrategy *clone = new KisColorizeStrokeStrategy(*this, levelOfDetail);
0283     return clone;
0284 }