1 /*
2  * KDE. Krita Project.
3  *
4  * Copyright (c) 2020 Deif Lou <ginoba@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20 
21 #include <QHash>
22 
23 #include <kpluginfactory.h>
24 #include <kis_filter_registry.h>
25 #include <filter/kis_filter_category_ids.h>
26 #include <KoUpdater.h>
27 #include <kis_filter_configuration.h>
28 #include <generator/kis_generator.h>
29 #include <generator/kis_generator_registry.h>
30 #include <KisSequentialIteratorProgress.h>
31 #include <kis_processing_information.h>
32 #include <kis_selection.h>
33 #include <kis_painter.h>
34 #include <KoCompositeOpRegistry.h>
35 #include <KoColorModelStandardIds.h>
36 #include <KoColorSpaceRegistry.h>
37 #include <KoColorProfile.h>
38 
39 #include "KisHalftoneFilter.h"
40 #include "KisHalftoneConfigWidget.h"
41 
42 K_PLUGIN_FACTORY_WITH_JSON(KritaHalftoneFactory, "KritaHalftone.json", registerPlugin<KritaHalftone>();)
43 
KritaHalftone(QObject * parent,const QVariantList &)44 KritaHalftone::KritaHalftone(QObject *parent, const QVariantList &)
45     : QObject(parent)
46 {
47     KisFilterRegistry::instance()->add(new KisHalftoneFilter());
48 }
49 
~KritaHalftone()50 KritaHalftone::~KritaHalftone()
51 {}
52 
KisHalftoneFilter()53 KisHalftoneFilter::KisHalftoneFilter()
54     : KisFilter(id(), FiltersCategoryArtisticId, i18n("&Halftone..."))
55 {
56     setSupportsPainting(true);
57 }
58 
processImpl(KisPaintDeviceSP device,const QRect & applyRect,const KisFilterConfigurationSP config,KoUpdater * progressUpdater) const59 void KisHalftoneFilter::processImpl(KisPaintDeviceSP device,
60                                     const QRect &applyRect,
61                                     const KisFilterConfigurationSP config,
62                                     KoUpdater *progressUpdater) const
63 {
64     const KisHalftoneFilterConfiguration *filterConfig =
65             dynamic_cast<const KisHalftoneFilterConfiguration*>(config.data());
66 
67     Q_ASSERT(device);
68     KIS_SAFE_ASSERT_RECOVER_RETURN(filterConfig);
69 
70     const QString mode = filterConfig->mode();
71     KIS_SAFE_ASSERT_RECOVER_RETURN(
72         mode == KisHalftoneFilterConfiguration::HalftoneMode_Intensity ||
73         mode == KisHalftoneFilterConfiguration::HalftoneMode_IndependentChannels ||
74         mode == KisHalftoneFilterConfiguration::HalftoneMode_Alpha
75     );
76 
77     KIS_SAFE_ASSERT_RECOVER_RETURN(
78         (device->colorSpace()->colorModelId().id() == AlphaColorModelID.id() &&
79          mode == KisHalftoneFilterConfiguration::HalftoneMode_Alpha) ||
80         (device->colorSpace()->colorModelId().id() == GrayColorModelID.id() &&
81          mode == KisHalftoneFilterConfiguration::HalftoneMode_Intensity) ||
82         (device->colorSpace()->colorModelId().id() == GrayAColorModelID.id() &&
83          (mode == KisHalftoneFilterConfiguration::HalftoneMode_Intensity ||
84           mode == KisHalftoneFilterConfiguration::HalftoneMode_Alpha)) ||
85         ((device->colorSpace()->colorModelId().id() == RGBAColorModelID.id() ||
86           device->colorSpace()->colorModelId().id() == XYZAColorModelID.id() ||
87           device->colorSpace()->colorModelId().id() == LABAColorModelID.id() ||
88           device->colorSpace()->colorModelId().id() == CMYKAColorModelID.id() ||
89           device->colorSpace()->colorModelId().id() == YCbCrAColorModelID.id()) &&
90          (mode == KisHalftoneFilterConfiguration::HalftoneMode_Intensity ||
91           mode == KisHalftoneFilterConfiguration::HalftoneMode_IndependentChannels ||
92           mode == KisHalftoneFilterConfiguration::HalftoneMode_Alpha))
93     );
94 
95     if (mode == KisHalftoneFilterConfiguration::HalftoneMode_Intensity) {
96         KIS_SAFE_ASSERT_RECOVER_RETURN(
97             device->colorSpace()->colorModelId().id() != AlphaColorModelID.id()
98         );
99         processIntensity(device, applyRect, filterConfig, progressUpdater);
100     } else if (mode == KisHalftoneFilterConfiguration::HalftoneMode_IndependentChannels) {
101         KIS_SAFE_ASSERT_RECOVER_RETURN(
102             device->colorSpace()->colorModelId().id() != AlphaColorModelID.id() &&
103             device->colorSpace()->colorModelId().id() != GrayColorModelID.id() &&
104             device->colorSpace()->colorModelId().id() != GrayAColorModelID.id()
105         );
106         processChannels(device, applyRect, filterConfig, progressUpdater);
107     } else {
108         KIS_SAFE_ASSERT_RECOVER_RETURN(
109             device->colorSpace()->colorModelId().id() != GrayColorModelID.id()
110         );
111         if (filterConfig->colorModelId() == AlphaColorModelID.id())
112         {
113             // This an Alpha layer (mask layer) but the device passed
114             // here has the composition color space (GrayA)
115             processMask(device, applyRect, filterConfig, progressUpdater);
116         } else {
117             // process the alpha channel of a multichannel device
118             processAlpha(device, applyRect, filterConfig, progressUpdater);
119         }
120     }
121 }
122 
makeHardnessLut(qreal hardness)123 QVector<quint8> KisHalftoneFilter::makeHardnessLut(qreal hardness)
124 {
125     QVector<quint8> hardnessLut(256);
126     if (qFuzzyCompare(hardness, 1.0)) {
127         for (int i = 0; i < 256; ++i) {
128             hardnessLut[i] = i < 128 ? 0 : 255;
129         }
130     } else {
131         qreal m = 1.0 / (1.0 - hardness);
132         qreal b = -m * (hardness / 2.0);
133         for (int i = 0; i < 256; ++i) {
134             hardnessLut[i] = qBound(0, static_cast<int>(qRound((m * (i / 255.0) + b) * 255.0)), 255);
135         }
136     }
137     return hardnessLut;
138 }
139 
makeNoiseWeightLut(qreal hardness)140 QVector<quint8> KisHalftoneFilter::makeNoiseWeightLut(qreal hardness)
141 {
142     QVector<quint8> noiseWeightLut(256);
143     hardness *= 0.99;
144     for (int i = 0; i < 256; ++i) {
145         qreal iNorm = i / 255.0;
146         qreal weight = (2.0 - std::abs(4.0 * iNorm - 2.0)) + hardness;
147         noiseWeightLut[i] = qBound(0, static_cast<int>(qRound(weight * 255.0)), 255);
148     }
149     return noiseWeightLut;
150 }
151 
makeGeneratorPaintDevice(KisPaintDeviceSP prototype,const QString & prefix,const QRect & applyRect,const KisHalftoneFilterConfiguration * config,KoUpdater * progressUpdater) const152 KisPaintDeviceSP KisHalftoneFilter::makeGeneratorPaintDevice(KisPaintDeviceSP prototype,
153                                                              const QString & prefix,
154                                                              const QRect &applyRect,
155                                                              const KisHalftoneFilterConfiguration *config,
156                                                              KoUpdater *progressUpdater) const
157 {
158     const QString generatorId = config->generatorId(prefix);
159     if (generatorId.isEmpty()) {
160         return nullptr;
161     }
162 
163     KisGeneratorSP generator  = KisGeneratorRegistry::instance()->get(generatorId);
164     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(generator, nullptr);
165 
166     KisFilterConfigurationSP generatorConfiguration = config->generatorConfiguration(prefix);
167     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(generatorConfiguration, nullptr);
168 
169     // Fill the generator device
170     KisPaintDeviceSP generatorDevice = m_grayDevicesCache.getDevice(prototype, KoColorSpaceRegistry::instance()->graya8());
171 
172     KisProcessingInformation(generatorDevice, applyRect.topLeft(), KisSelectionSP());
173     generator->generate(
174         KisProcessingInformation(generatorDevice, applyRect.topLeft(),KisSelectionSP()),
175         applyRect.size(),
176         generatorConfiguration,
177         progressUpdater
178     );
179 
180     return generatorDevice;
181 }
182 
checkUpdaterInterruptedAndSetPercent(KoUpdater * progressUpdater,int percent) const183 bool KisHalftoneFilter::checkUpdaterInterruptedAndSetPercent(KoUpdater *progressUpdater, int percent) const
184 {
185     // The updater is null so return false to keep going
186     // with the computations
187     if (!progressUpdater) {
188         return false;
189     }
190 
191     if (progressUpdater->interrupted()) {
192         return true;
193     }
194 
195     progressUpdater->setProgress(percent);
196     return false;
197 }
198 
processIntensity(KisPaintDeviceSP device,const QRect & applyRect,const KisHalftoneFilterConfiguration * config,KoUpdater * progressUpdater) const199 void KisHalftoneFilter::processIntensity(KisPaintDeviceSP device,
200                                          const QRect &applyRect,
201                                          const KisHalftoneFilterConfiguration *config,
202                                          KoUpdater *progressUpdater) const
203 {
204     const QString prefix = "intensity_";
205 
206     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 0)) {
207         return;
208     }
209 
210     // Make the generator device
211     KisPaintDeviceSP generatorDevice = makeGeneratorPaintDevice(device, prefix, applyRect, config, nullptr);
212     if (!generatorDevice) {
213         return;
214     }
215     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 25)) {
216         return;
217     }
218 
219     // Make the hardness and the noise weight LUT
220     const qreal hardness = config->hardness(prefix) / 100.0;
221     const QVector<quint8> hardnessLut = makeHardnessLut(hardness);
222     const QVector<quint8> noiseWeightLut = makeNoiseWeightLut(hardness);
223 
224     // Fill the mask device
225     KisSelectionSP maskDevice = m_selectionsCache.getSelection();
226 
227     {
228         const bool invert = config->invert(prefix);
229 
230         KisSequentialIterator maskIterator(maskDevice->pixelSelection(), applyRect);
231         KisSequentialIterator dstIterator(device, applyRect);
232         KisSequentialIterator srcIterator(generatorDevice, applyRect);
233 
234         if (!invert) {
235             while (maskIterator.nextPixel() && dstIterator.nextPixel() && srcIterator.nextPixel()) {
236                 int dstGray = device->colorSpace()->intensity8(dstIterator.rawData());
237                 int srcGray = srcIterator.rawData()[0];
238                 int srcAlpha = srcIterator.rawData()[1];
239 
240                 // Combine pixels
241                 int result = qBound(0, dstGray + (srcGray - 128) * noiseWeightLut[dstGray] * srcAlpha / 0xFE01, 255);
242 
243                 // Apply hardness
244                 result = hardnessLut[result];
245 
246                 *maskIterator.rawData() = 255 - result;
247             }
248         } else {
249             while (maskIterator.nextPixel() && dstIterator.nextPixel() && srcIterator.nextPixel()) {
250                 int dstGray = device->colorSpace()->intensity8(dstIterator.rawData());
251                 int srcGray = srcIterator.rawData()[0];
252                 int srcAlpha = srcIterator.rawData()[1];
253 
254                 // Combine pixels
255                 int result = qBound(0, dstGray + (srcGray - 128) * noiseWeightLut[dstGray] * srcAlpha / 0xFE01, 255);
256 
257                 // Apply hardness
258                 result = hardnessLut[result];
259 
260                 *maskIterator.rawData() = result;
261             }
262         }
263         m_grayDevicesCache.putDevice(generatorDevice);
264     }
265     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 50)) {
266         return;
267     }
268 
269     // Make the halftone image
270     const KoColorSpace *colorSpace;
271     if (device->colorSpace()->profile()->isLinear()) {
272         colorSpace = KoColorSpaceRegistry::instance()->rgb8();
273     } else {
274         colorSpace = device->colorSpace();
275     }
276     KisPaintDeviceSP halftoneDevice = m_genericDevicesCache.getDevice(device, colorSpace);
277 
278     {
279         KisPaintDeviceSP foregroundDevice = m_genericDevicesCache.getDevice(device, colorSpace);
280         KoColor foregroundColor = config->foregroundColor(prefix);
281         KoColor backgroundColor = config->backgroundColor(prefix);
282         const qreal foregroundOpacity = config->foregroundOpacity(prefix) / 100.0;
283         const qreal backgroundOpacity = config->backgroundOpacity(prefix) / 100.0;
284         foregroundColor.convertTo(colorSpace);
285         backgroundColor.convertTo(colorSpace);
286         foregroundColor.setOpacity(foregroundOpacity);
287         backgroundColor.setOpacity(backgroundOpacity);
288 
289         foregroundDevice->fill(applyRect, foregroundColor);
290         halftoneDevice->fill(applyRect, backgroundColor);
291 
292         KisPainter painter(halftoneDevice, maskDevice);
293         painter.setCompositeOp(COMPOSITE_OVER);
294         painter.bitBlt(applyRect.topLeft(), foregroundDevice, applyRect);
295 
296         m_genericDevicesCache.putDevice(foregroundDevice);
297         m_selectionsCache.putSelection(maskDevice);
298     }
299     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 75)) {
300         return;
301     }
302 
303     // Make the final image
304     {
305         KisPainter painter(halftoneDevice);
306         painter.setCompositeOp(COMPOSITE_DESTINATION_IN);
307         painter.bitBlt(applyRect.topLeft(), device, applyRect);
308     }
309     {
310         KisPainter painter(device);
311         painter.setCompositeOp(COMPOSITE_COPY);
312         painter.bitBlt(applyRect.topLeft(), halftoneDevice, applyRect);
313     }
314     m_genericDevicesCache.putDevice(halftoneDevice);
315 
316     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 100)) {
317         return;
318     }
319 }
320 
321 template <typename ChannelType>
processChannel(KisPaintDeviceSP device,KisPaintDeviceSP generatorDevice,const QRect & applyRect,const KisHalftoneFilterConfiguration * config,const QString & prefix,KoChannelInfo * channelInfo) const322 void KisHalftoneFilter::processChannel(KisPaintDeviceSP device,
323                                        KisPaintDeviceSP generatorDevice,
324                                        const QRect &applyRect,
325                                        const KisHalftoneFilterConfiguration *config,
326                                        const QString & prefix,
327                                        KoChannelInfo * channelInfo) const
328 {
329     const int channelPos = channelInfo->pos() / sizeof(ChannelType);
330     // Make the hardness and the noise weight LUT
331     const qreal hardness = config->hardness(prefix) / 100.0;
332     const QVector<quint8> hardnessLut = makeHardnessLut(hardness);
333     const QVector<quint8> noiseWeightLut = makeNoiseWeightLut(hardness);
334 
335     // Fill the device
336     const bool invert = config->invert(prefix);
337     KisSequentialIterator dstIterator(device, applyRect);
338     KisSequentialIterator srcIterator(generatorDevice, applyRect);
339 
340     const KoColorSpace *colorSpace = device->colorSpace();
341 
342     if (colorSpace->profile()->isLinear()) {
343         if (!invert) {
344             while (dstIterator.nextPixel() && srcIterator.nextPixel()) {
345                 int dst = 255 - device->colorSpace()->scaleToU8(dstIterator.rawData(), channelPos);
346                 int src = srcIterator.rawData()[0];
347                 int srcAlpha = srcIterator.rawData()[1];
348                 KoColor c(QColor(src, src, src, srcAlpha), device->colorSpace());
349                 src = device->colorSpace()->scaleToU8(c.data(), 0);
350                 srcAlpha = device->colorSpace()->scaleToU8(c.data(), device->colorSpace()->alphaPos());
351 
352                 // Combine pixels
353                 int result = qBound(0, dst + (src - 128) * noiseWeightLut[dst] * srcAlpha / 0xFE01, 255);
354 
355                 // Apply hardness
356                 result = hardnessLut[result];
357 
358                 ChannelType *dstPixel = reinterpret_cast<ChannelType*>(dstIterator.rawData());
359                 ChannelType channelMin = static_cast<ChannelType>(channelInfo->getUIMin());
360                 ChannelType channelMax = static_cast<ChannelType>(channelInfo->getUIMax());
361                 dstPixel[channelPos] = static_cast<ChannelType>(mapU8ToRange(255 - result, channelMin, channelMax));
362             }
363         } else {
364             while (dstIterator.nextPixel() && srcIterator.nextPixel()) {
365                 int dst = device->colorSpace()->scaleToU8(dstIterator.rawData(), channelPos);
366                 int src = srcIterator.rawData()[0];
367                 int srcAlpha = srcIterator.rawData()[1];
368                 KoColor c(QColor(src, src, src, srcAlpha), device->colorSpace());
369                 src = device->colorSpace()->scaleToU8(c.data(), 0);
370                 srcAlpha = device->colorSpace()->scaleToU8(c.data(), device->colorSpace()->alphaPos());
371 
372                 // Combine pixels
373                 int result = qBound(0, dst + (src - 128) * noiseWeightLut[dst] * srcAlpha / 0xFE01, 255);
374 
375                 // Apply hardness
376                 result = hardnessLut[result];
377 
378                 ChannelType *dstPixel = reinterpret_cast<ChannelType*>(dstIterator.rawData());
379                 ChannelType channelMin = static_cast<ChannelType>(channelInfo->getUIMin());
380                 ChannelType channelMax = static_cast<ChannelType>(channelInfo->getUIMax());
381                 dstPixel[channelPos] = static_cast<ChannelType>(mapU8ToRange(result, channelMin, channelMax));
382             }
383         }
384     } else {
385         if (!invert) {
386             while (dstIterator.nextPixel() && srcIterator.nextPixel()) {
387                 int dst = 255 - device->colorSpace()->scaleToU8(dstIterator.rawData(), channelPos);
388                 int src = srcIterator.rawData()[0];
389                 int srcAlpha = srcIterator.rawData()[1];
390 
391                 // Combine pixels
392                 int result = qBound(0, dst + (src - 128) * noiseWeightLut[dst] * srcAlpha / 0xFE01, 255);
393 
394                 // Apply hardness
395                 result = hardnessLut[result];
396 
397                 ChannelType *dstPixel = reinterpret_cast<ChannelType*>(dstIterator.rawData());
398                 ChannelType channelMin = static_cast<ChannelType>(channelInfo->getUIMin());
399                 ChannelType channelMax = static_cast<ChannelType>(channelInfo->getUIMax());
400                 dstPixel[channelPos] = static_cast<ChannelType>(mapU8ToRange(255 - result, channelMin, channelMax));
401             }
402         } else {
403             while (dstIterator.nextPixel() && srcIterator.nextPixel()) {
404                 int dst = device->colorSpace()->scaleToU8(dstIterator.rawData(), channelPos);
405                 int src = srcIterator.rawData()[0];
406                 int srcAlpha = srcIterator.rawData()[1];
407 
408                 // Combine pixels
409                 int result = qBound(0, dst + (src - 128) * noiseWeightLut[dst] * srcAlpha / 0xFE01, 255);
410 
411                 // Apply hardness
412                 result = hardnessLut[result];
413 
414                 ChannelType *dstPixel = reinterpret_cast<ChannelType*>(dstIterator.rawData());
415                 ChannelType channelMin = static_cast<ChannelType>(channelInfo->getUIMin());
416                 ChannelType channelMax = static_cast<ChannelType>(channelInfo->getUIMax());
417                 dstPixel[channelPos] = static_cast<ChannelType>(mapU8ToRange(result, channelMin, channelMax));
418             }
419         }
420     }
421 }
422 
processChannels(KisPaintDeviceSP device,const QRect & applyRect,const KisHalftoneFilterConfiguration * config,KoUpdater * progressUpdater) const423 void KisHalftoneFilter::processChannels(KisPaintDeviceSP device,
424                                         const QRect &applyRect,
425                                         const KisHalftoneFilterConfiguration *config,
426                                         KoUpdater *progressUpdater) const
427 {
428     const QList<KoChannelInfo *> channels = device->colorSpace()->channels();
429     const int progressStep = 100 / (channels.count() * 2);
430 
431     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 0)) {
432         return;
433     }
434 
435     // Make the generator devices
436     QVector<KisPaintDeviceSP> generatorDevices(channels.count());
437     for (int i = 0; i < channels.count(); ++i) {
438         if (channels.at(i)->channelType() == KoChannelInfo::ALPHA) {
439             generatorDevices[i] = nullptr;
440         } else {
441             const QString prefix =
442                 device->colorSpace()->colorModelId().id() + "_channel" + QString::number(i) + "_";
443             KisPaintDeviceSP generatorDevice = makeGeneratorPaintDevice(device, prefix, applyRect, config, nullptr);
444             if (generatorDevice) {
445                 generatorDevices[i] = generatorDevice;
446             } else {
447                 generatorDevices[i] = nullptr;
448             }
449         }
450         if (checkUpdaterInterruptedAndSetPercent(progressUpdater, progressUpdater->progress() + progressStep)) {
451             return;
452         }
453     }
454 
455     // process channels
456     for (int i = 0; i < channels.count(); ++i) {
457         if (!generatorDevices[i]) {
458             progressUpdater->setProgress(progressUpdater->progress() + progressStep);
459             continue;
460         }
461 
462         const QString prefix = device->colorSpace()->colorModelId().id() + "_channel" + QString::number(i) + "_";
463 
464         switch (channels.at(i)->channelValueType()) {
465         case KoChannelInfo::UINT8: {
466             processChannel<quint8>(device, generatorDevices[i], applyRect, config, prefix, channels.at(i));
467             break;
468         } case KoChannelInfo::UINT16: {
469             processChannel<quint16>(device, generatorDevices[i], applyRect, config, prefix, channels.at(i));
470             break;
471         } case KoChannelInfo::UINT32: {
472             processChannel<quint32>(device, generatorDevices[i], applyRect, config, prefix, channels.at(i));
473             break;
474         } case KoChannelInfo::FLOAT16: {
475 #ifdef HAVE_OPENEXR
476             processChannel<half>(device, generatorDevices[i], applyRect, config, prefix, channels.at(i));
477 #endif
478             break;
479         } case KoChannelInfo::FLOAT32: {
480             processChannel<float>(device, generatorDevices[i], applyRect, config, prefix, channels.at(i));
481             break;
482         } case KoChannelInfo::FLOAT64: {
483             processChannel<double>(device, generatorDevices[i], applyRect, config, prefix, channels.at(i));
484             break;
485         } case KoChannelInfo::INT8: {
486             processChannel<qint8>(device, generatorDevices[i], applyRect, config, prefix, channels.at(i));
487             break;
488         } case KoChannelInfo::INT16: {
489             processChannel<qint16>(device, generatorDevices[i], applyRect, config, prefix, channels.at(i));
490             break;
491         } default: {
492             break;
493         }
494         }
495 
496         m_grayDevicesCache.putDevice(generatorDevices[i]);
497 
498         if (checkUpdaterInterruptedAndSetPercent(progressUpdater, progressUpdater->progress() + progressStep)) {
499             return;
500         }
501     }
502 
503     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 100)) {
504         return;
505     }
506 }
507 
processAlpha(KisPaintDeviceSP device,const QRect & applyRect,const KisHalftoneFilterConfiguration * config,KoUpdater * progressUpdater) const508 void KisHalftoneFilter::processAlpha(KisPaintDeviceSP device,
509                                      const QRect& applyRect,
510                                      const KisHalftoneFilterConfiguration *config,
511                                      KoUpdater *progressUpdater) const
512 {
513     const QString prefix = "alpha_";
514 
515     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 0)) {
516         return;
517     }
518 
519     // Make the generator device
520     KisPaintDeviceSP generatorDevice = makeGeneratorPaintDevice(device, prefix, applyRect, config, nullptr);
521     if (!generatorDevice) {
522         return;
523     }
524     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 50)) {
525         return;
526     }
527 
528     // Make the hardness and the noise weight LUT
529     const qreal hardness = config->hardness(prefix) / 100.0;
530     const QVector<quint8> hardnessLut = makeHardnessLut(hardness);
531     const QVector<quint8> noiseWeightLut = makeNoiseWeightLut(hardness);
532 
533     // Fill the device
534     const bool invert = config->invert(prefix);
535     KisSequentialIterator dstIterator(device, applyRect);
536     KisSequentialIterator srcIterator(generatorDevice, applyRect);
537 
538     if (!invert) {
539         while (dstIterator.nextPixel() && srcIterator.nextPixel()) {
540             int dst = 255 - device->colorSpace()->opacityU8(dstIterator.rawData());
541             int src = srcIterator.rawData()[0];
542             int srcAlpha = srcIterator.rawData()[1];
543 
544             // Combine pixels
545             int result = qBound(0, dst + (src - 128) * noiseWeightLut[dst] * srcAlpha / 0xFE01, 255);
546 
547             // Apply hardness
548             result = hardnessLut[result];
549 
550             device->colorSpace()->setOpacity(dstIterator.rawData(), static_cast<quint8>(255 - result), 1);
551         }
552     } else {
553         while (dstIterator.nextPixel() && srcIterator.nextPixel()) {
554             int dst = device->colorSpace()->opacityU8(dstIterator.rawData());
555             int src = srcIterator.rawData()[0];
556             int srcAlpha = srcIterator.rawData()[1];
557 
558             // Combine pixels
559             int result = qBound(0, dst + (src - 128) * noiseWeightLut[dst] * srcAlpha / 0xFE01, 255);
560 
561             // Apply hardness
562             result = hardnessLut[result];
563 
564             device->colorSpace()->setOpacity(dstIterator.rawData(), static_cast<quint8>(result), 1);
565         }
566     }
567     m_grayDevicesCache.putDevice(generatorDevice);
568 
569     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 100)) {
570         return;
571     }
572 }
573 
processMask(KisPaintDeviceSP device,const QRect & applyRect,const KisHalftoneFilterConfiguration * config,KoUpdater * progressUpdater) const574 void KisHalftoneFilter::processMask(KisPaintDeviceSP device,
575                                     const QRect& applyRect,
576                                     const KisHalftoneFilterConfiguration *config,
577                                     KoUpdater *progressUpdater) const
578 {
579     const QString prefix = "alpha_";
580 
581     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 0)) {
582         return;
583     }
584 
585     // Make the generator device
586     KisPaintDeviceSP generatorDevice = makeGeneratorPaintDevice(device, prefix, applyRect, config, nullptr);
587     if (!generatorDevice) {
588         return;
589     }
590     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 50)) {
591         return;
592     }
593 
594     // Make the hardness and the noise weight LUT
595     const qreal hardness = config->hardness(prefix) / 100.0;
596     const QVector<quint8> hardnessLut = makeHardnessLut(hardness);
597     const QVector<quint8> noiseWeightLut = makeNoiseWeightLut(hardness);
598 
599     // Fill the device
600     const bool invert = config->invert(prefix);
601     KisSequentialIterator dstIterator(device, applyRect);
602     KisSequentialIterator srcIterator(generatorDevice, applyRect);
603 
604     if (!invert) {
605         while (dstIterator.nextPixel() && srcIterator.nextPixel()) {
606             int dst = 255 - *dstIterator.rawData();
607             int src = *srcIterator.rawData();
608 
609             // Combine pixels
610             int result = qBound(0, dst + (src - 128) * noiseWeightLut[dst] / 0xFF, 255);
611 
612             // Apply hardness
613             result = hardnessLut[result];
614 
615             *dstIterator.rawData() = static_cast<quint8>(255 - result);
616         }
617     } else {
618         while (dstIterator.nextPixel() && srcIterator.nextPixel()) {
619             int dst = *dstIterator.rawData();
620             int src = *srcIterator.rawData();
621 
622             // Combine pixels
623             int result = qBound(0, dst + (src - 128) * noiseWeightLut[dst] / 0xFF, 255);
624 
625             // Apply hardness
626             result = hardnessLut[result];
627 
628             *dstIterator.rawData() =  static_cast<quint8>(result);
629         }
630     }
631     m_grayDevicesCache.putDevice(generatorDevice);
632 
633     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 100)) {
634         return;
635     }
636 }
637 
defaultConfiguration() const638 KisFilterConfigurationSP KisHalftoneFilter::defaultConfiguration() const
639 {
640     KisHalftoneFilterConfigurationSP filterConfig =
641         dynamic_cast<KisHalftoneFilterConfiguration*>(factoryConfiguration().data());
642 
643     filterConfig->setMode(KisHalftoneFilterConfiguration::defaultMode());
644 
645     QString defaultGeneratorId = KisHalftoneFilterConfiguration::defaultGeneratorId();
646     KisGeneratorSP defaultGenerator = KisGeneratorRegistry::instance()->get(defaultGeneratorId);
647 
648     // intensity
649     filterConfig->setGeneratorId("intensity_", defaultGeneratorId);
650     if (defaultGenerator) {
651         KisFilterConfigurationSP defaultGeneratorConfiguration =
652             defaultGenerator->defaultConfiguration();
653         if (defaultGeneratorId == "screentone") {
654             // interpolation = 1 means "sinusoidal"
655             defaultGeneratorConfiguration->setProperty("interpolation", 1);
656             defaultGeneratorConfiguration->setProperty("rotation", 45.0);
657             defaultGeneratorConfiguration->setProperty("contrast", 50.0);
658         }
659         filterConfig->setGeneratorConfiguration("intensity_", defaultGeneratorConfiguration);
660     }
661     filterConfig->setHardness("intensity_", KisHalftoneFilterConfiguration::defaultHardness());
662     filterConfig->setInvert("intensity_", KisHalftoneFilterConfiguration::defaultInvert());
663     filterConfig->setForegroundColor("intensity_", KisHalftoneFilterConfiguration::defaultForegroundColor());
664     filterConfig->setForegroundOpacity("intensity_", KisHalftoneFilterConfiguration::defaultForegroundOpacity());
665     filterConfig->setBackgroundColor("intensity_", KisHalftoneFilterConfiguration::defaultBackgroundColor());
666     filterConfig->setBackgroundOpacity("intensity_", KisHalftoneFilterConfiguration::defaultBackgroundOpacity());
667 
668     // Alpha
669     filterConfig->setGeneratorId("alpha_", "");
670     filterConfig->setHardness("alpha_", KisHalftoneFilterConfiguration::defaultHardness());
671     filterConfig->setInvert("alpha_", KisHalftoneFilterConfiguration::defaultInvert());
672 
673     // The channels only use default generator if it is screentone
674     // because there are predefined ways of presenting the patterns (screen angles)
675 
676     // Map channel prefixes to rotation angle
677     QHash<QString, qreal> channelDict;
678     channelDict.insert("RGBA_channel0_", 15.0);
679     channelDict.insert("RGBA_channel1_", 45.0);
680     channelDict.insert("RGBA_channel2_", 75.0);
681     channelDict.insert("CMYKA_channel0_", 15.0);
682     channelDict.insert("CMYKA_channel1_", 75.0);
683     channelDict.insert("CMYKA_channel2_", 0.0);
684     channelDict.insert("CMYKA_channel3_", 45.0);
685 
686     for (auto i = channelDict.constBegin(); i != channelDict.constEnd(); ++i) {
687         if (defaultGenerator && defaultGeneratorId == "screentone") {
688             filterConfig->setGeneratorId(i.key(), "screentone");
689             KisFilterConfigurationSP defaultGeneratorConfiguration =
690                 defaultGenerator->defaultConfiguration();
691             // interpolation = 1 means "sinusoidal"
692             defaultGeneratorConfiguration->setProperty("interpolation", 1);
693             defaultGeneratorConfiguration->setProperty("rotation", i.value());
694             defaultGeneratorConfiguration->setProperty("contrast", 50.0);
695             filterConfig->setGeneratorConfiguration(i.key(), defaultGeneratorConfiguration);
696         } else {
697             filterConfig->setGeneratorId(i.key(), "");
698         }
699         filterConfig->setHardness(i.key(), KisHalftoneFilterConfiguration::defaultHardness());
700         filterConfig->setInvert(i.key(), KisHalftoneFilterConfiguration::defaultInvert());
701     }
702 
703     return filterConfig;
704 }
705 
factoryConfiguration() const706 KisFilterConfigurationSP KisHalftoneFilter::factoryConfiguration() const
707 {
708     return new KisHalftoneFilterConfiguration("halftone", 1);
709 }
710 
createConfigurationWidget(QWidget * parent,const KisPaintDeviceSP dev,bool useForMasks) const711 KisConfigWidget *KisHalftoneFilter::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool useForMasks) const
712 {
713     Q_UNUSED(useForMasks);
714     return new KisHalftoneConfigWidget(parent, dev);
715 }
716 
717 #include "KisHalftoneFilter.moc"
718