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