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