1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2005-17-07
7  * Description : A Sharpen threaded image filter.
8  *
9  * Copyright (C) 2005-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10  * Copyright (C) 2009      by Matthias Welwarsky <matze at welwarsky dot de>
11  * Copyright (C) 2010      by Martin Klapetek <martin dot klapetek at gmail dot com>
12  * Copyright (C) 2002      by Daniel M. Duley <mosfet at kde dot org>
13  *
14  * This program is free software; you can redistribute it
15  * and/or modify it under the terms of the GNU General
16  * Public License as published by the Free Software Foundation;
17  * either version 2, or (at your option)
18  * any later version.
19  *
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  *
25  * ============================================================ */
26 
27 #include "unsharpmaskfilter.h"
28 
29 // C++ includes
30 
31 #include <cmath>
32 #include <cstdlib>
33 
34 // Qt includes
35 
36 #include <QtConcurrent>    // krazy:exclude=includes
37 
38 // KDE includes
39 
40 #include <klocalizedstring.h>
41 
42 // Local includes
43 
44 #include "dimg.h"
45 #include "digikam_debug.h"
46 #include "dcolor.h"
47 #include "blurfilter.h"
48 
49 namespace Digikam
50 {
51 
UnsharpMaskFilter(QObject * const parent)52 UnsharpMaskFilter::UnsharpMaskFilter(QObject* const parent)
53     : DImgThreadedFilter(parent),
54       m_radius          (1),
55       m_amount          (1.0),
56       m_threshold       (0.05),
57       m_luma            (false)
58 {
59     initFilter();
60 }
61 
UnsharpMaskFilter(DImg * const orgImage,QObject * const parent,double radius,double amount,double threshold,bool luma)62 UnsharpMaskFilter::UnsharpMaskFilter(DImg* const orgImage,
63                                      QObject* const parent,
64                                      double radius,
65                                      double amount,
66                                      double threshold,
67                                      bool luma)
68     : DImgThreadedFilter(orgImage, parent, QLatin1String("UnsharpMask")),
69       m_radius   (radius),
70       m_amount   (amount),
71       m_threshold(threshold),
72       m_luma     (luma)
73 {
74     initFilter();
75 }
76 
~UnsharpMaskFilter()77 UnsharpMaskFilter::~UnsharpMaskFilter()
78 {
79     cancelFilter();
80 }
81 
DisplayableName()82 QString UnsharpMaskFilter::DisplayableName()
83 {
84     return QString::fromUtf8(I18N_NOOP("Unsharp Mask Tool"));
85 }
86 
unsharpMaskMultithreaded(uint start,uint stop,uint y)87 void UnsharpMaskFilter::unsharpMaskMultithreaded(uint start, uint stop, uint y)
88 {
89     long int zero  = 0;
90     double   value = 0.0;
91     DColor   p;
92     DColor   q;
93 
94     long int quantum        = m_destImage.sixteenBit() ? 65535 : 255;
95     double quantumThreshold = quantum * m_threshold;
96     int hp = 0, sp = 0, lp = 0, hq = 0, sq = 0, lq = 0;
97 
98     for (uint x = start ; runningFlag() && (x < stop) ; ++x)
99     {
100         p = m_orgImage.getPixelColor(x, y);
101         q = m_destImage.getPixelColor(x, y);
102 
103         if (m_luma)
104         {
105             p.getHSL(&hp, &sp, &lp);
106             q.getHSL(&hq, &sq, &lq);
107 
108             // luma channel
109 
110             value = (double)(lp) - (double)(lq);
111 
112             if (fabs(2.0 * value) < quantumThreshold)
113             {
114                 value = (double)(lp);
115             }
116             else
117             {
118                 value = (double)(lp) + value * m_amount;
119             }
120 
121             q.setHSL(hp, sp, CLAMP(lround(value), zero, quantum), m_destImage.sixteenBit());
122             q.setAlpha(p.alpha());
123         }
124         else
125         {
126             // Red channel.
127 
128             value = (double)(p.red()) - (double)(q.red());
129 
130             if (fabs(2.0 * value) < quantumThreshold)
131             {
132                 value = (double)(p.red());
133             }
134             else
135             {
136                 value = (double)(p.red()) + value * m_amount;
137             }
138 
139             q.setRed(CLAMP(lround(value), zero, quantum));
140 
141             // Green Channel.
142 
143             value = (double)(p.green()) - (double)(q.green());
144 
145             if (fabs(2.0 * value) < quantumThreshold)
146             {
147                 value = (double)(p.green());
148             }
149             else
150             {
151                 value = (double)(p.green()) + value * m_amount;
152             }
153 
154             q.setGreen(CLAMP(lround(value), zero, quantum));
155 
156             // Blue Channel.
157 
158             value = (double)(p.blue()) - (double)(q.blue());
159 
160             if (fabs(2.0 * value) < quantumThreshold)
161             {
162                 value = (double)(p.blue());
163             }
164             else
165             {
166                 value = (double)(p.blue()) + value * m_amount;
167             }
168 
169             q.setBlue(CLAMP(lround(value), zero, quantum));
170 
171             // Alpha Channel.
172 
173             value = (double)(p.alpha()) - (double)(q.alpha());
174 
175             if (fabs(2.0 * value) < quantumThreshold)
176             {
177                 value = (double)(p.alpha());
178             }
179             else
180             {
181                 value = (double)(p.alpha()) + value * m_amount;
182             }
183 
184             q.setAlpha(CLAMP(lround(value), zero, quantum));
185         }
186 
187         m_destImage.setPixelColor(x, y, q);
188     }
189 }
190 
filterImage()191 void UnsharpMaskFilter::filterImage()
192 {
193     int progress;
194 
195     if (m_orgImage.isNull())
196     {
197         qCWarning(DIGIKAM_DIMG_LOG) << "No image data available!";
198         return;
199     }
200 
201     // cppcheck-suppress unusedScopedObject
202     BlurFilter(this, m_orgImage, m_destImage, 0, 10, (int)(m_radius*10.0));
203 
204     QList<int> vals = multithreadedSteps(m_destImage.width());
205 
206     for (uint y = 0 ; runningFlag() && (y < m_destImage.height()) ; ++y)
207     {
208         QList <QFuture<void> > tasks;
209 
210         for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
211         {
212             tasks.append(QtConcurrent::run(this,
213                                            &UnsharpMaskFilter::unsharpMaskMultithreaded,
214                                            vals[j],
215                                            vals[j+1],
216                                            y));
217         }
218 
219         foreach (QFuture<void> t, tasks)
220         {
221             t.waitForFinished();
222         }
223 
224         progress = (int)(10.0 + ((double)y * 90.0) / m_destImage.height());
225 
226         if ((progress % 5) == 0)
227         {
228             postProgress(progress);
229         }
230     }
231 }
232 
filterAction()233 FilterAction UnsharpMaskFilter::filterAction()
234 {
235     FilterAction action(FilterIdentifier(), CurrentVersion());
236     action.setDisplayableName(DisplayableName());
237 
238     action.addParameter(QLatin1String("amount"),    m_amount);
239     action.addParameter(QLatin1String("radius"),    m_radius);
240     action.addParameter(QLatin1String("threshold"), m_threshold);
241     action.addParameter(QLatin1String("luma"),      m_luma);
242 
243     return action;
244 }
245 
readParameters(const FilterAction & action)246 void UnsharpMaskFilter::readParameters(const FilterAction& action)
247 {
248     m_amount    = action.parameter(QLatin1String("amount")).toDouble();
249     m_radius    = action.parameter(QLatin1String("radius")).toDouble();
250     m_threshold = action.parameter(QLatin1String("threshold")).toDouble();
251     m_luma      = action.parameter(QLatin1String("luma")).toBool();
252 }
253 
254 } // namespace Digikam
255