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 }