1 /*
2 * This file is part of Krita
3 *
4 * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
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 <compositeops/KoVcMultiArchBuildSupport.h> //MSVC requires that Vc come first
22 #include "kis_unsharp_filter.h"
23 #include <QBitArray>
24
25 #include <kis_mask_generator.h>
26 #include <kis_convolution_kernel.h>
27 #include <kis_convolution_painter.h>
28 #include <kis_gaussian_kernel.h>
29 #include <filter/kis_filter_category_ids.h>
30 #include <filter/kis_filter_configuration.h>
31 #include <kis_processing_information.h>
32 #include <KoProgressUpdater.h>
33 #include <KoUpdater.h>
34 #include <KoConvolutionOp.h>
35 #include <kis_paint_device.h>
36 #include "kis_lod_transform.h"
37
38 #include "kis_wdg_unsharp.h"
39 #include "ui_wdgunsharp.h"
40 #include "KoColorSpaceTraits.h"
41 #include <KisSequentialIteratorProgress.h>
42
43
KisUnsharpFilter()44 KisUnsharpFilter::KisUnsharpFilter() : KisFilter(id(), FiltersCategoryEnhanceId, i18n("&Unsharp Mask..."))
45 {
46 setSupportsPainting(true);
47 setSupportsAdjustmentLayers(true);
48 setSupportsThreading(true);
49
50 /**
51 * Officially Unsharp Mask doesn't support LoD, because it
52 * generates subtle artifacts when the unsharp radius is smaller
53 * than current zoom level. But LoD devices can still appear when
54 * the filter is used in Adjustment Layer. So the actual LoD is
55 * still counted on.
56 */
57 setSupportsLevelOfDetail(false);
58 setColorSpaceIndependence(FULLY_INDEPENDENT);
59 }
60
createConfigurationWidget(QWidget * parent,const KisPaintDeviceSP,bool) const61 KisConfigWidget * KisUnsharpFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const
62 {
63 return new KisWdgUnsharp(parent);
64 }
65
defaultConfiguration() const66 KisFilterConfigurationSP KisUnsharpFilter::defaultConfiguration() const
67 {
68 KisFilterConfigurationSP config = factoryConfiguration();
69 config->setProperty("halfSize", 1);
70 config->setProperty("amount", 0.5);
71 config->setProperty("threshold", 0);
72 config->setProperty("lightnessOnly", true);
73 return config;
74 }
75
processImpl(KisPaintDeviceSP device,const QRect & applyRect,const KisFilterConfigurationSP _config,KoUpdater * progressUpdater) const76 void KisUnsharpFilter::processImpl(KisPaintDeviceSP device,
77 const QRect& applyRect,
78 const KisFilterConfigurationSP _config,
79 KoUpdater* progressUpdater
80 ) const
81 {
82
83 QPointer<KoUpdater> filterUpdater = 0;
84 QPointer<KoUpdater> convolutionUpdater = 0;
85 QScopedPointer<KoProgressUpdater> updater;
86
87 if (progressUpdater) {
88 updater.reset(new KoProgressUpdater(progressUpdater));
89 updater->start(100, i18n("Unsharp Mask"));
90 // Two sub-sub tasks that each go from 0 to 100.
91 convolutionUpdater = updater->startSubtask();
92 filterUpdater = updater->startSubtask();
93 }
94
95 KisFilterConfigurationSP config = _config ? _config : new KisFilterConfiguration(id().id(), 1);
96
97 QVariant value;
98
99 KisLodTransformScalar t(device);
100
101 const qreal halfSize = t.scale(config->getProperty("halfSize", value) ? value.toDouble() : 1.0);
102 const qreal amount = (config->getProperty("amount", value)) ? value.toDouble() : 0.5;
103 const uint threshold = (config->getProperty("threshold", value)) ? value.toUInt() : 0;
104 const uint lightnessOnly = (config->getProperty("lightnessOnly", value)) ? value.toBool() : true;
105
106 QBitArray channelFlags = config->channelFlags();
107 KisGaussianKernel::applyGaussian(device, applyRect,
108 halfSize, halfSize,
109 channelFlags,
110 convolutionUpdater);
111
112 qreal weights[2];
113 qreal factor = 128;
114
115 weights[0] = factor * (1. + amount);
116 weights[1] = -factor * amount;
117
118 if (lightnessOnly) {
119 processLightnessOnly(device, applyRect, threshold, weights, factor, channelFlags, filterUpdater);
120 } else {
121 processRaw(device, applyRect, threshold, weights, factor, channelFlags, filterUpdater);
122 }
123 }
124
processRaw(KisPaintDeviceSP device,const QRect & rect,quint8 threshold,qreal weights[2],qreal factor,const QBitArray & channelFlags,KoUpdater * progressUpdater) const125 void KisUnsharpFilter::processRaw(KisPaintDeviceSP device,
126 const QRect &rect,
127 quint8 threshold,
128 qreal weights[2],
129 qreal factor,
130 const QBitArray &channelFlags,
131 KoUpdater *progressUpdater) const
132 {
133 const KoColorSpace *cs = device->colorSpace();
134 const int pixelSize = cs->pixelSize();
135 KoConvolutionOp * convolutionOp = cs->convolutionOp();
136
137 quint8 *colors[2];
138 colors[0] = new quint8[pixelSize];
139 colors[1] = new quint8[pixelSize];
140
141 KisSequentialIteratorProgress dstIt(device, rect, progressUpdater);
142
143 while (dstIt.nextPixel()) {
144 quint8 diff = 0;
145 if (threshold == 1) {
146 if (memcmp(dstIt.oldRawData(), dstIt.rawDataConst(), cs->pixelSize()) == 0) {
147 diff = 1;
148 }
149 }
150 else {
151 diff = cs->difference(dstIt.oldRawData(), dstIt.rawDataConst());
152 }
153
154 if (diff >= threshold) {
155 memcpy(colors[0], dstIt.oldRawData(), pixelSize);
156 memcpy(colors[1], dstIt.rawDataConst(), pixelSize);
157 convolutionOp->convolveColors(colors, weights, dstIt.rawData(), factor, 0, 2, channelFlags);
158 } else {
159 memcpy(dstIt.rawData(), dstIt.oldRawData(), pixelSize);
160 }
161 }
162
163 delete[] colors[0];
164 delete[] colors[1];
165 }
166
processLightnessOnly(KisPaintDeviceSP device,const QRect & rect,quint8 threshold,qreal weights[2],qreal factor,const QBitArray &,KoUpdater * progressUpdater) const167 void KisUnsharpFilter::processLightnessOnly(KisPaintDeviceSP device,
168 const QRect &rect,
169 quint8 threshold,
170 qreal weights[2],
171 qreal factor,
172 const QBitArray & /*channelFlags*/,
173 KoUpdater *progressUpdater) const
174 {
175 const KoColorSpace *cs = device->colorSpace();
176 const int pixelSize = cs->pixelSize();
177
178 quint16 labColorSrc[4];
179 quint16 labColorDst[4];
180
181 const int posL = 0;
182 const int posAplha = 3;
183
184 const qreal factorInv = 1.0 / factor;
185
186 KisSequentialIteratorProgress dstIt(device, rect, progressUpdater);
187
188 while (dstIt.nextPixel()) {
189 quint8 diff = cs->differenceA(dstIt.oldRawData(), dstIt.rawDataConst());
190 if (diff >= threshold) {
191 cs->toLabA16(dstIt.oldRawData(), (quint8*)labColorSrc, 1);
192 cs->toLabA16(dstIt.rawDataConst(), (quint8*)labColorDst, 1);
193
194 qint32 valueL = (labColorSrc[posL] * weights[0] + labColorDst[posL] * weights[1]) * factorInv;
195 labColorSrc[posL] = CLAMP(valueL,
196 KoColorSpaceMathsTraits<quint16>::min,
197 KoColorSpaceMathsTraits<quint16>::max);
198
199 qint32 valueAlpha = (labColorSrc[posAplha] * weights[0] + labColorDst[posAplha] * weights[1]) * factorInv;
200 labColorSrc[posAplha] = CLAMP(valueAlpha,
201 KoColorSpaceMathsTraits<quint16>::min,
202 KoColorSpaceMathsTraits<quint16>::max);
203
204 cs->fromLabA16((quint8*)labColorSrc, dstIt.rawData(), 1);
205 } else {
206 memcpy(dstIt.rawData(), dstIt.oldRawData(), pixelSize);
207 }
208 }
209 }
210
neededRect(const QRect & rect,const KisFilterConfigurationSP config,int lod) const211 QRect KisUnsharpFilter::neededRect(const QRect & rect, const KisFilterConfigurationSP config, int lod) const
212 {
213 KisLodTransformScalar t(lod);
214
215 QVariant value;
216 const qreal halfSize = t.scale(config->getProperty("halfSize", value) ? value.toDouble() : 1.0);
217
218 return rect.adjusted(-halfSize * 2, -halfSize * 2, halfSize * 2, halfSize * 2);
219 }
220
changedRect(const QRect & rect,const KisFilterConfigurationSP config,int lod) const221 QRect KisUnsharpFilter::changedRect(const QRect & rect, const KisFilterConfigurationSP config, int lod) const
222 {
223 KisLodTransformScalar t(lod);
224
225 QVariant value;
226 const qreal halfSize = t.scale(config->getProperty("halfSize", value) ? value.toDouble() : 1.0);
227
228 return rect.adjusted( -halfSize, -halfSize, halfSize, halfSize);
229 }
230