File indexing completed on 2024-06-16 04:16:40

0001 /*
0002  * KDE. Krita Project.
0003  *
0004  * SPDX-FileCopyrightText: 2020 Deif Lou <ginoba@gmail.com>
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include <QHash>
0010 
0011 #include <kpluginfactory.h>
0012 #include <kis_filter_registry.h>
0013 #include <filter/kis_filter_category_ids.h>
0014 #include <KoUpdater.h>
0015 #include <kis_filter_configuration.h>
0016 #include <generator/kis_generator.h>
0017 #include <generator/kis_generator_registry.h>
0018 #include <KisGlobalResourcesInterface.h>
0019 #include <KisSequentialIteratorProgress.h>
0020 #include <kis_processing_information.h>
0021 #include <kis_selection.h>
0022 #include <kis_painter.h>
0023 #include <KoCompositeOpRegistry.h>
0024 #include <KoColorModelStandardIds.h>
0025 #include <KoColorSpaceRegistry.h>
0026 #include <KoColorProfile.h>
0027 
0028 #include "KisHalftoneFilter.h"
0029 #include "KisHalftoneConfigWidget.h"
0030 
0031 K_PLUGIN_FACTORY_WITH_JSON(KritaHalftoneFactory, "KritaHalftone.json", registerPlugin<KritaHalftone>();)
0032 
0033 KritaHalftone::KritaHalftone(QObject *parent, const QVariantList &)
0034     : QObject(parent)
0035 {
0036     KisFilterRegistry::instance()->add(new KisHalftoneFilter());
0037 }
0038 
0039 KritaHalftone::~KritaHalftone()
0040 {}
0041 
0042 KisHalftoneFilter::KisHalftoneFilter()
0043     : KisFilter(id(), FiltersCategoryArtisticId, i18n("&Halftone..."))
0044 {
0045     setSupportsPainting(true);
0046 }
0047 
0048 void KisHalftoneFilter::processImpl(KisPaintDeviceSP device,
0049                                     const QRect &applyRect,
0050                                     const KisFilterConfigurationSP config,
0051                                     KoUpdater *progressUpdater) const
0052 {
0053     const KisHalftoneFilterConfiguration *filterConfig =
0054             dynamic_cast<const KisHalftoneFilterConfiguration*>(config.data());
0055 
0056     Q_ASSERT(device);
0057     KIS_SAFE_ASSERT_RECOVER_RETURN(filterConfig);
0058     KIS_SAFE_ASSERT_RECOVER_NOOP(filterConfig->hasLocalResourcesSnapshot());
0059 
0060     const QString mode = filterConfig->mode();
0061     KIS_SAFE_ASSERT_RECOVER_RETURN(
0062         mode == KisHalftoneFilterConfiguration::HalftoneMode_Intensity ||
0063         mode == KisHalftoneFilterConfiguration::HalftoneMode_IndependentChannels ||
0064         mode == KisHalftoneFilterConfiguration::HalftoneMode_Alpha
0065     );
0066 
0067     KIS_SAFE_ASSERT_RECOVER_RETURN(
0068         (device->colorSpace()->colorModelId().id() == AlphaColorModelID.id() &&
0069          mode == KisHalftoneFilterConfiguration::HalftoneMode_Alpha) ||
0070         (device->colorSpace()->colorModelId().id() == GrayColorModelID.id() &&
0071          mode == KisHalftoneFilterConfiguration::HalftoneMode_Intensity) ||
0072         (device->colorSpace()->colorModelId().id() == GrayAColorModelID.id() &&
0073          (mode == KisHalftoneFilterConfiguration::HalftoneMode_Intensity ||
0074           mode == KisHalftoneFilterConfiguration::HalftoneMode_Alpha)) ||
0075         ((device->colorSpace()->colorModelId().id() == RGBAColorModelID.id() ||
0076           device->colorSpace()->colorModelId().id() == XYZAColorModelID.id() ||
0077           device->colorSpace()->colorModelId().id() == LABAColorModelID.id() ||
0078           device->colorSpace()->colorModelId().id() == CMYKAColorModelID.id() ||
0079           device->colorSpace()->colorModelId().id() == YCbCrAColorModelID.id()) &&
0080          (mode == KisHalftoneFilterConfiguration::HalftoneMode_Intensity ||
0081           mode == KisHalftoneFilterConfiguration::HalftoneMode_IndependentChannels ||
0082           mode == KisHalftoneFilterConfiguration::HalftoneMode_Alpha))
0083     );
0084     
0085     if (mode == KisHalftoneFilterConfiguration::HalftoneMode_Intensity) {
0086         KIS_SAFE_ASSERT_RECOVER_RETURN(
0087             device->colorSpace()->colorModelId().id() != AlphaColorModelID.id()
0088         );
0089         processIntensity(device, applyRect, filterConfig, progressUpdater);
0090     } else if (mode == KisHalftoneFilterConfiguration::HalftoneMode_IndependentChannels) {
0091         KIS_SAFE_ASSERT_RECOVER_RETURN(
0092             device->colorSpace()->colorModelId().id() != AlphaColorModelID.id() &&
0093             device->colorSpace()->colorModelId().id() != GrayColorModelID.id() &&
0094             device->colorSpace()->colorModelId().id() != GrayAColorModelID.id()
0095         );
0096         processChannels(device, applyRect, filterConfig, progressUpdater);
0097     } else {
0098         KIS_SAFE_ASSERT_RECOVER_RETURN(
0099             device->colorSpace()->colorModelId().id() != GrayColorModelID.id()
0100         );
0101         if (filterConfig->colorModelId() == AlphaColorModelID.id())
0102         {
0103             // This an Alpha layer (mask layer) but the device passed
0104             // here has the composition color space (GrayA)
0105             processMask(device, applyRect, filterConfig, progressUpdater);
0106         } else {
0107             // process the alpha channel of a multichannel device
0108             processAlpha(device, applyRect, filterConfig, progressUpdater);
0109         }
0110     }
0111 }
0112 
0113 QVector<quint8> KisHalftoneFilter::makeHardnessLut(qreal hardness)
0114 {
0115     QVector<quint8> hardnessLut(256);
0116     if (qFuzzyCompare(hardness, 1.0)) {
0117         for (int i = 0; i < 256; ++i) {
0118             hardnessLut[i] = i < 128 ? 0 : 255;
0119         }
0120     } else {
0121         qreal m = 1.0 / (1.0 - hardness);
0122         qreal b = -m * (hardness / 2.0);
0123         for (int i = 0; i < 256; ++i) {
0124             hardnessLut[i] = qBound(0, static_cast<int>(qRound((m * (i / 255.0) + b) * 255.0)), 255);
0125         }
0126     }
0127     return hardnessLut;
0128 }
0129 
0130 QVector<quint8> KisHalftoneFilter::makeNoiseWeightLut(qreal hardness)
0131 {
0132     QVector<quint8> noiseWeightLut(256);
0133     hardness *= 0.99;
0134     for (int i = 0; i < 256; ++i) {
0135         qreal iNorm = i / 255.0;
0136         qreal weight = (2.0 - std::abs(4.0 * iNorm - 2.0)) + hardness;
0137         noiseWeightLut[i] = qBound(0, static_cast<int>(qRound(weight * 255.0)), 255);
0138     }
0139     return noiseWeightLut;
0140 }
0141 
0142 KisPaintDeviceSP KisHalftoneFilter::makeGeneratorPaintDevice(KisPaintDeviceSP prototype,
0143                                                              const QString & prefix,
0144                                                              const QRect &applyRect,
0145                                                              const KisHalftoneFilterConfiguration *config,
0146                                                              KoUpdater *progressUpdater) const
0147 {
0148     const QString generatorId = config->generatorId(prefix);
0149     if (generatorId.isEmpty()) {
0150         return nullptr;
0151     }
0152 
0153     KisGeneratorSP generator  = KisGeneratorRegistry::instance()->get(generatorId);
0154     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(generator, nullptr);
0155 
0156     KisFilterConfigurationSP generatorConfiguration = config->generatorConfiguration(prefix);
0157     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(generatorConfiguration, nullptr);
0158 
0159     // Fill the generator device
0160     KisPaintDeviceSP generatorDevice = m_grayDevicesCache.getDevice(prototype, KoColorSpaceRegistry::instance()->graya8());
0161 
0162     KisProcessingInformation(generatorDevice, applyRect.topLeft(), KisSelectionSP());
0163     generator->generate(
0164         KisProcessingInformation(generatorDevice, applyRect.topLeft(),KisSelectionSP()),
0165         applyRect.size(),
0166         generatorConfiguration,
0167         progressUpdater
0168     );
0169 
0170     return generatorDevice;
0171 }
0172 
0173 bool KisHalftoneFilter::checkUpdaterInterruptedAndSetPercent(KoUpdater *progressUpdater, int percent) const
0174 {
0175     // The updater is null so return false to keep going
0176     // with the computations
0177     if (!progressUpdater) {
0178         return false;
0179     }
0180 
0181     if (progressUpdater->interrupted()) {
0182         return true;
0183     }
0184 
0185     progressUpdater->setProgress(percent);
0186     return false;
0187 }
0188 
0189 void KisHalftoneFilter::processIntensity(KisPaintDeviceSP device,
0190                                          const QRect &applyRect,
0191                                          const KisHalftoneFilterConfiguration *config,
0192                                          KoUpdater *progressUpdater) const
0193 {
0194     const QString prefix = "intensity_";
0195 
0196     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 0)) {
0197         return;
0198     }
0199     
0200     // Make the generator device
0201     KisPaintDeviceSP generatorDevice = makeGeneratorPaintDevice(device, prefix, applyRect, config, nullptr);
0202     if (!generatorDevice) {
0203         return;
0204     }
0205     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 25)) {
0206         return;
0207     }
0208 
0209     // Make the hardness and the noise weight LUT
0210     const qreal hardness = config->hardness(prefix) / 100.0;
0211     const QVector<quint8> hardnessLut = makeHardnessLut(hardness);
0212     const QVector<quint8> noiseWeightLut = makeNoiseWeightLut(hardness);
0213 
0214     // Fill the mask device
0215     KisSelectionSP maskDevice = m_selectionsCache.getSelection();
0216 
0217     {
0218         const bool invert = config->invert(prefix);
0219 
0220         KisSequentialIterator maskIterator(maskDevice->pixelSelection(), applyRect);
0221         KisSequentialIterator dstIterator(device, applyRect);
0222         KisSequentialIterator srcIterator(generatorDevice, applyRect);
0223 
0224         if (!invert) {
0225             while (maskIterator.nextPixel() && dstIterator.nextPixel() && srcIterator.nextPixel()) {
0226                 int dstGray = device->colorSpace()->intensity8(dstIterator.rawData());
0227                 int srcGray = srcIterator.rawData()[0];
0228                 int srcAlpha = srcIterator.rawData()[1];
0229 
0230                 // Combine pixels
0231                 int result = qBound(0, dstGray + (srcGray - 128) * noiseWeightLut[dstGray] * srcAlpha / 0xFE01, 255);
0232 
0233                 // Apply hardness
0234                 result = hardnessLut[result];
0235 
0236                 *maskIterator.rawData() = 255 - result;
0237             }
0238         } else {
0239             while (maskIterator.nextPixel() && dstIterator.nextPixel() && srcIterator.nextPixel()) {
0240                 int dstGray = device->colorSpace()->intensity8(dstIterator.rawData());
0241                 int srcGray = srcIterator.rawData()[0];
0242                 int srcAlpha = srcIterator.rawData()[1];
0243 
0244                 // Combine pixels
0245                 int result = qBound(0, dstGray + (srcGray - 128) * noiseWeightLut[dstGray] * srcAlpha / 0xFE01, 255);
0246 
0247                 // Apply hardness
0248                 result = hardnessLut[result];
0249 
0250                 *maskIterator.rawData() = result;
0251             }
0252         }
0253         m_grayDevicesCache.putDevice(generatorDevice);
0254     }
0255     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 50)) {
0256         return;
0257     }
0258 
0259     // Make the halftone image
0260     const KoColorSpace *colorSpace;
0261     if (device->colorSpace()->profile()->isLinear()) {
0262         colorSpace = KoColorSpaceRegistry::instance()->rgb8();
0263     } else {
0264         colorSpace = device->colorSpace();
0265     }
0266     KisPaintDeviceSP halftoneDevice = m_genericDevicesCache.getDevice(device, colorSpace);
0267     
0268     {
0269         KisPaintDeviceSP foregroundDevice = m_genericDevicesCache.getDevice(device, colorSpace);
0270         KoColor foregroundColor = config->foregroundColor(prefix);
0271         KoColor backgroundColor = config->backgroundColor(prefix);
0272         const qreal foregroundOpacity = config->foregroundOpacity(prefix) / 100.0;
0273         const qreal backgroundOpacity = config->backgroundOpacity(prefix) / 100.0;
0274         foregroundColor.convertTo(colorSpace);
0275         backgroundColor.convertTo(colorSpace);
0276         foregroundColor.setOpacity(foregroundOpacity);
0277         backgroundColor.setOpacity(backgroundOpacity);
0278 
0279         foregroundDevice->fill(applyRect, foregroundColor);
0280         halftoneDevice->fill(applyRect, backgroundColor);
0281 
0282         KisPainter painter(halftoneDevice, maskDevice);
0283         painter.setCompositeOpId(COMPOSITE_OVER);
0284         painter.bitBlt(applyRect.topLeft(), foregroundDevice, applyRect);
0285 
0286         m_genericDevicesCache.putDevice(foregroundDevice);
0287         m_selectionsCache.putSelection(maskDevice);
0288     }
0289     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 75)) {
0290         return;
0291     }
0292 
0293     // Make the final image
0294     {
0295         KisPainter painter(halftoneDevice);
0296         painter.setCompositeOpId(COMPOSITE_DESTINATION_IN);
0297         painter.bitBlt(applyRect.topLeft(), device, applyRect);
0298     }
0299     {
0300         KisPainter painter(device);
0301         painter.setCompositeOpId(COMPOSITE_COPY);
0302         painter.bitBlt(applyRect.topLeft(), halftoneDevice, applyRect);
0303     }
0304     m_genericDevicesCache.putDevice(halftoneDevice);
0305 
0306     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 100)) {
0307         return;
0308     }
0309 }
0310 
0311 template <typename ChannelType>
0312 void KisHalftoneFilter::processChannel(KisPaintDeviceSP device,
0313                                        KisPaintDeviceSP generatorDevice,
0314                                        const QRect &applyRect,
0315                                        const KisHalftoneFilterConfiguration *config,
0316                                        const QString & prefix,
0317                                        KoChannelInfo * channelInfo) const
0318 {
0319     const int channelPos = channelInfo->pos() / sizeof(ChannelType);
0320     // Make the hardness and the noise weight LUT
0321     const qreal hardness = config->hardness(prefix) / 100.0;
0322     const QVector<quint8> hardnessLut = makeHardnessLut(hardness);
0323     const QVector<quint8> noiseWeightLut = makeNoiseWeightLut(hardness);
0324 
0325     // Fill the device
0326     const bool invert = config->invert(prefix);
0327     KisSequentialIterator dstIterator(device, applyRect);
0328     KisSequentialIterator srcIterator(generatorDevice, applyRect);
0329 
0330     const KoColorSpace *colorSpace = device->colorSpace();
0331 
0332     if (colorSpace->profile()->isLinear()) {
0333         if (!invert) {
0334             while (dstIterator.nextPixel() && srcIterator.nextPixel()) {
0335                 int dst = 255 - device->colorSpace()->scaleToU8(dstIterator.rawData(), channelPos);
0336                 int src = srcIterator.rawData()[0];
0337                 int srcAlpha = srcIterator.rawData()[1];
0338                 KoColor c(QColor(src, src, src, srcAlpha), device->colorSpace());
0339                 src = device->colorSpace()->scaleToU8(c.data(), 0);
0340                 srcAlpha = device->colorSpace()->scaleToU8(c.data(), device->colorSpace()->alphaPos());
0341 
0342                 // Combine pixels
0343                 int result = qBound(0, dst + (src - 128) * noiseWeightLut[dst] * srcAlpha / 0xFE01, 255);
0344 
0345                 // Apply hardness
0346                 result = hardnessLut[result];
0347 
0348                 ChannelType *dstPixel = reinterpret_cast<ChannelType*>(dstIterator.rawData());
0349                 ChannelType channelMin = static_cast<ChannelType>(channelInfo->getUIMin());
0350                 ChannelType channelMax = static_cast<ChannelType>(channelInfo->getUIMax());
0351                 dstPixel[channelPos] = static_cast<ChannelType>(mapU8ToRange(255 - result, channelMin, channelMax));
0352             }
0353         } else {
0354             while (dstIterator.nextPixel() && srcIterator.nextPixel()) {
0355                 int dst = device->colorSpace()->scaleToU8(dstIterator.rawData(), channelPos);
0356                 int src = srcIterator.rawData()[0];
0357                 int srcAlpha = srcIterator.rawData()[1];
0358                 KoColor c(QColor(src, src, src, srcAlpha), device->colorSpace());
0359                 src = device->colorSpace()->scaleToU8(c.data(), 0);
0360                 srcAlpha = device->colorSpace()->scaleToU8(c.data(), device->colorSpace()->alphaPos());
0361 
0362                 // Combine pixels
0363                 int result = qBound(0, dst + (src - 128) * noiseWeightLut[dst] * srcAlpha / 0xFE01, 255);
0364 
0365                 // Apply hardness
0366                 result = hardnessLut[result];
0367 
0368                 ChannelType *dstPixel = reinterpret_cast<ChannelType*>(dstIterator.rawData());
0369                 ChannelType channelMin = static_cast<ChannelType>(channelInfo->getUIMin());
0370                 ChannelType channelMax = static_cast<ChannelType>(channelInfo->getUIMax());
0371                 dstPixel[channelPos] = static_cast<ChannelType>(mapU8ToRange(result, channelMin, channelMax));
0372             }
0373         }
0374     } else {
0375         if (!invert) {
0376             while (dstIterator.nextPixel() && srcIterator.nextPixel()) {
0377                 int dst = 255 - device->colorSpace()->scaleToU8(dstIterator.rawData(), channelPos);
0378                 int src = srcIterator.rawData()[0];
0379                 int srcAlpha = srcIterator.rawData()[1];
0380 
0381                 // Combine pixels
0382                 int result = qBound(0, dst + (src - 128) * noiseWeightLut[dst] * srcAlpha / 0xFE01, 255);
0383 
0384                 // Apply hardness
0385                 result = hardnessLut[result];
0386 
0387                 ChannelType *dstPixel = reinterpret_cast<ChannelType*>(dstIterator.rawData());
0388                 ChannelType channelMin = static_cast<ChannelType>(channelInfo->getUIMin());
0389                 ChannelType channelMax = static_cast<ChannelType>(channelInfo->getUIMax());
0390                 dstPixel[channelPos] = static_cast<ChannelType>(mapU8ToRange(255 - result, channelMin, channelMax));
0391             }
0392         } else {
0393             while (dstIterator.nextPixel() && srcIterator.nextPixel()) {
0394                 int dst = device->colorSpace()->scaleToU8(dstIterator.rawData(), channelPos);
0395                 int src = srcIterator.rawData()[0];
0396                 int srcAlpha = srcIterator.rawData()[1];
0397 
0398                 // Combine pixels
0399                 int result = qBound(0, dst + (src - 128) * noiseWeightLut[dst] * srcAlpha / 0xFE01, 255);
0400 
0401                 // Apply hardness
0402                 result = hardnessLut[result];
0403 
0404                 ChannelType *dstPixel = reinterpret_cast<ChannelType*>(dstIterator.rawData());
0405                 ChannelType channelMin = static_cast<ChannelType>(channelInfo->getUIMin());
0406                 ChannelType channelMax = static_cast<ChannelType>(channelInfo->getUIMax());
0407                 dstPixel[channelPos] = static_cast<ChannelType>(mapU8ToRange(result, channelMin, channelMax));
0408             }
0409         }
0410     }
0411 }
0412 
0413 void KisHalftoneFilter::processChannels(KisPaintDeviceSP device,
0414                                         const QRect &applyRect,
0415                                         const KisHalftoneFilterConfiguration *config,
0416                                         KoUpdater *progressUpdater) const
0417 {
0418     const QList<KoChannelInfo *> channels = device->colorSpace()->channels();
0419     const int progressStep = 100 / (channels.count() * 2);
0420 
0421     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 0)) {
0422         return;
0423     }
0424 
0425     // Make the generator devices
0426     QVector<KisPaintDeviceSP> generatorDevices(channels.count());
0427     for (int i = 0; i < channels.count(); ++i) {
0428         if (channels.at(i)->channelType() == KoChannelInfo::ALPHA) {
0429             generatorDevices[i] = nullptr;
0430         } else {
0431             const QString prefix =
0432                 device->colorSpace()->colorModelId().id() + "_channel" + QString::number(i) + "_";
0433             KisPaintDeviceSP generatorDevice = makeGeneratorPaintDevice(device, prefix, applyRect, config, nullptr);
0434             if (generatorDevice) {
0435                 generatorDevices[i] = generatorDevice;
0436             } else {
0437                 generatorDevices[i] = nullptr;
0438             }
0439         }
0440         if (checkUpdaterInterruptedAndSetPercent(progressUpdater, progressUpdater->progress() + progressStep)) {
0441             return;
0442         }
0443     }
0444 
0445     // process channels
0446     for (int i = 0; i < channels.count(); ++i) {
0447         if (!generatorDevices[i]) {
0448             progressUpdater->setProgress(progressUpdater->progress() + progressStep);
0449             continue;
0450         }
0451 
0452         const QString prefix = device->colorSpace()->colorModelId().id() + "_channel" + QString::number(i) + "_";
0453 
0454         switch (channels.at(i)->channelValueType()) {
0455         case KoChannelInfo::UINT8: {
0456             processChannel<quint8>(device, generatorDevices[i], applyRect, config, prefix, channels.at(i));
0457             break;
0458         } case KoChannelInfo::UINT16: {
0459             processChannel<quint16>(device, generatorDevices[i], applyRect, config, prefix, channels.at(i));
0460             break;
0461         } case KoChannelInfo::UINT32: {
0462             processChannel<quint32>(device, generatorDevices[i], applyRect, config, prefix, channels.at(i));
0463             break;
0464         } case KoChannelInfo::FLOAT16: {
0465 #ifdef HAVE_OPENEXR
0466             processChannel<half>(device, generatorDevices[i], applyRect, config, prefix, channels.at(i));
0467 #endif
0468             break;
0469         } case KoChannelInfo::FLOAT32: {
0470             processChannel<float>(device, generatorDevices[i], applyRect, config, prefix, channels.at(i));
0471             break;
0472         } case KoChannelInfo::FLOAT64: {
0473             processChannel<double>(device, generatorDevices[i], applyRect, config, prefix, channels.at(i));
0474             break;
0475         } case KoChannelInfo::INT8: {
0476             processChannel<qint8>(device, generatorDevices[i], applyRect, config, prefix, channels.at(i));
0477             break;
0478         } case KoChannelInfo::INT16: {
0479             processChannel<qint16>(device, generatorDevices[i], applyRect, config, prefix, channels.at(i));
0480             break;
0481         } default: {
0482             break;
0483         }
0484         }
0485 
0486         m_grayDevicesCache.putDevice(generatorDevices[i]);
0487 
0488         if (checkUpdaterInterruptedAndSetPercent(progressUpdater, progressUpdater->progress() + progressStep)) {
0489             return;
0490         }
0491     }
0492 
0493     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 100)) {
0494         return;
0495     }
0496 }
0497 
0498 void KisHalftoneFilter::processAlpha(KisPaintDeviceSP device,
0499                                      const QRect& applyRect,
0500                                      const KisHalftoneFilterConfiguration *config,
0501                                      KoUpdater *progressUpdater) const
0502 {
0503     const QString prefix = "alpha_";
0504     
0505     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 0)) {
0506         return;
0507     }
0508 
0509     // Make the generator device
0510     KisPaintDeviceSP generatorDevice = makeGeneratorPaintDevice(device, prefix, applyRect, config, nullptr);
0511     if (!generatorDevice) {
0512         return;
0513     }
0514     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 50)) {
0515         return;
0516     }
0517 
0518     // Make the hardness and the noise weight LUT
0519     const qreal hardness = config->hardness(prefix) / 100.0;
0520     const QVector<quint8> hardnessLut = makeHardnessLut(hardness);
0521     const QVector<quint8> noiseWeightLut = makeNoiseWeightLut(hardness);
0522 
0523     // Fill the device
0524     const bool invert = config->invert(prefix);
0525     KisSequentialIterator dstIterator(device, applyRect);
0526     KisSequentialIterator srcIterator(generatorDevice, applyRect);
0527 
0528     if (!invert) {
0529         while (dstIterator.nextPixel() && srcIterator.nextPixel()) {
0530             int dst = 255 - device->colorSpace()->opacityU8(dstIterator.rawData());
0531             int src = srcIterator.rawData()[0];
0532             int srcAlpha = srcIterator.rawData()[1];
0533 
0534             // Combine pixels
0535             int result = qBound(0, dst + (src - 128) * noiseWeightLut[dst] * srcAlpha / 0xFE01, 255);
0536 
0537             // Apply hardness
0538             result = hardnessLut[result];
0539 
0540             device->colorSpace()->setOpacity(dstIterator.rawData(), static_cast<quint8>(255 - result), 1);
0541         }
0542     } else {
0543         while (dstIterator.nextPixel() && srcIterator.nextPixel()) {
0544             int dst = device->colorSpace()->opacityU8(dstIterator.rawData());
0545             int src = srcIterator.rawData()[0];
0546             int srcAlpha = srcIterator.rawData()[1];
0547 
0548             // Combine pixels
0549             int result = qBound(0, dst + (src - 128) * noiseWeightLut[dst] * srcAlpha / 0xFE01, 255);
0550 
0551             // Apply hardness
0552             result = hardnessLut[result];
0553 
0554             device->colorSpace()->setOpacity(dstIterator.rawData(), static_cast<quint8>(result), 1);
0555         }
0556     }
0557     m_grayDevicesCache.putDevice(generatorDevice);
0558 
0559     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 100)) {
0560         return;
0561     }
0562 }
0563 
0564 void KisHalftoneFilter::processMask(KisPaintDeviceSP device,
0565                                     const QRect& applyRect,
0566                                     const KisHalftoneFilterConfiguration *config,
0567                                     KoUpdater *progressUpdater) const
0568 {
0569     const QString prefix = "alpha_";
0570     
0571     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 0)) {
0572         return;
0573     }
0574 
0575     // Make the generator device
0576     KisPaintDeviceSP generatorDevice = makeGeneratorPaintDevice(device, prefix, applyRect, config, nullptr);
0577     if (!generatorDevice) {
0578         return;
0579     }
0580     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 50)) {
0581         return;
0582     }
0583 
0584     // Make the hardness and the noise weight LUT
0585     const qreal hardness = config->hardness(prefix) / 100.0;
0586     const QVector<quint8> hardnessLut = makeHardnessLut(hardness);
0587     const QVector<quint8> noiseWeightLut = makeNoiseWeightLut(hardness);
0588 
0589     // Fill the device
0590     const bool invert = config->invert(prefix);
0591     KisSequentialIterator dstIterator(device, applyRect);
0592     KisSequentialIterator srcIterator(generatorDevice, applyRect);
0593 
0594     if (!invert) {
0595         while (dstIterator.nextPixel() && srcIterator.nextPixel()) {
0596             int dst = 255 - *dstIterator.rawData();
0597             int src = *srcIterator.rawData();
0598 
0599             // Combine pixels
0600             int result = qBound(0, dst + (src - 128) * noiseWeightLut[dst] / 0xFF, 255);
0601 
0602             // Apply hardness
0603             result = hardnessLut[result];
0604 
0605             *dstIterator.rawData() = static_cast<quint8>(255 - result);
0606         }
0607     } else {
0608         while (dstIterator.nextPixel() && srcIterator.nextPixel()) {
0609             int dst = *dstIterator.rawData();
0610             int src = *srcIterator.rawData();
0611 
0612             // Combine pixels
0613             int result = qBound(0, dst + (src - 128) * noiseWeightLut[dst] / 0xFF, 255);
0614 
0615             // Apply hardness
0616             result = hardnessLut[result];
0617 
0618             *dstIterator.rawData() =  static_cast<quint8>(result);
0619         }
0620     }
0621     m_grayDevicesCache.putDevice(generatorDevice);
0622 
0623     if (checkUpdaterInterruptedAndSetPercent(progressUpdater, 100)) {
0624         return;
0625     }
0626 }
0627 
0628 KisFilterConfigurationSP KisHalftoneFilter::defaultConfiguration(KisResourcesInterfaceSP resourcesInterface) const
0629 {
0630     KisHalftoneFilterConfigurationSP filterConfig =
0631         dynamic_cast<KisHalftoneFilterConfiguration*>(factoryConfiguration(resourcesInterface).data());
0632         
0633     filterConfig->setMode(KisHalftoneFilterConfiguration::defaultMode());
0634 
0635     QString defaultGeneratorId = KisHalftoneFilterConfiguration::defaultGeneratorId();
0636     KisGeneratorSP defaultGenerator = KisGeneratorRegistry::instance()->get(defaultGeneratorId);
0637 
0638     // intensity
0639     filterConfig->setGeneratorId("intensity_", defaultGeneratorId);
0640     if (defaultGenerator) {
0641         KisFilterConfigurationSP defaultGeneratorConfiguration =
0642             defaultGenerator->defaultConfiguration(resourcesInterface);
0643         if (defaultGeneratorId == "screentone") {
0644             defaultGeneratorConfiguration->setProperty("rotation", 45.0);
0645             defaultGeneratorConfiguration->setProperty("contrast", 50.0);
0646         }
0647         filterConfig->setGeneratorConfiguration("intensity_", defaultGeneratorConfiguration);
0648     }
0649     filterConfig->setHardness("intensity_", KisHalftoneFilterConfiguration::defaultHardness());
0650     filterConfig->setInvert("intensity_", KisHalftoneFilterConfiguration::defaultInvert());
0651     filterConfig->setForegroundColor("intensity_", KisHalftoneFilterConfiguration::defaultForegroundColor());
0652     filterConfig->setForegroundOpacity("intensity_", KisHalftoneFilterConfiguration::defaultForegroundOpacity());
0653     filterConfig->setBackgroundColor("intensity_", KisHalftoneFilterConfiguration::defaultBackgroundColor());
0654     filterConfig->setBackgroundOpacity("intensity_", KisHalftoneFilterConfiguration::defaultBackgroundOpacity());
0655     
0656     // Alpha
0657     filterConfig->setGeneratorId("alpha_", defaultGeneratorId);
0658     if (defaultGenerator) {
0659         KisFilterConfigurationSP defaultGeneratorConfiguration =
0660             defaultGenerator->defaultConfiguration(resourcesInterface);
0661         if (defaultGeneratorId == "screentone") {
0662             defaultGeneratorConfiguration->setProperty("rotation", 45.0);
0663             defaultGeneratorConfiguration->setProperty("contrast", 50.0);
0664         }
0665         filterConfig->setGeneratorConfiguration("alpha_", defaultGeneratorConfiguration);
0666     }
0667     filterConfig->setHardness("alpha_", KisHalftoneFilterConfiguration::defaultHardness());
0668     filterConfig->setInvert("alpha_", KisHalftoneFilterConfiguration::defaultInvert());
0669 
0670     // The channels only use default generator if it is screentone
0671     // because there are predefined ways of presenting the patterns (screen angles)
0672 
0673     // Map channel prefixes to rotation angle
0674     QHash<QString, qreal> channelDict;
0675     channelDict.insert("RGBA_channel0_", 15.0);
0676     channelDict.insert("RGBA_channel1_", 45.0);
0677     channelDict.insert("RGBA_channel2_", 75.0);
0678     channelDict.insert("CMYKA_channel0_", 15.0);
0679     channelDict.insert("CMYKA_channel1_", 75.0);
0680     channelDict.insert("CMYKA_channel2_", 0.0);
0681     channelDict.insert("CMYKA_channel3_", 45.0);
0682 
0683     for (auto i = channelDict.constBegin(); i != channelDict.constEnd(); ++i) {
0684         if (defaultGenerator && defaultGeneratorId == "screentone") {
0685             filterConfig->setGeneratorId(i.key(), "screentone");
0686             KisFilterConfigurationSP defaultGeneratorConfiguration =
0687                 defaultGenerator->defaultConfiguration(resourcesInterface);
0688             defaultGeneratorConfiguration->setProperty("rotation", i.value());
0689             defaultGeneratorConfiguration->setProperty("contrast", 50.0);
0690             filterConfig->setGeneratorConfiguration(i.key(), defaultGeneratorConfiguration);
0691         } else {
0692             filterConfig->setGeneratorId(i.key(), "");
0693         }
0694         filterConfig->setHardness(i.key(), KisHalftoneFilterConfiguration::defaultHardness());
0695         filterConfig->setInvert(i.key(), KisHalftoneFilterConfiguration::defaultInvert());
0696     }
0697     
0698     return filterConfig;
0699 }
0700 
0701 KisFilterConfigurationSP KisHalftoneFilter::factoryConfiguration(KisResourcesInterfaceSP resourcesInterface) const
0702 {
0703     return new KisHalftoneFilterConfiguration("halftone", 1, resourcesInterface);
0704 }
0705 
0706 KisConfigWidget *KisHalftoneFilter::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool useForMasks) const
0707 {
0708     Q_UNUSED(useForMasks);
0709     return new KisHalftoneConfigWidget(parent, dev);
0710 }
0711 
0712 #include "KisHalftoneFilter.moc"