1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2012-02-05
7  * Description : film color negative inverter filter
8  *
9  * Copyright (C) 2012 by Matthias Welwarsky <matthias at welwarsky dot de>
10  *
11  * This program is free software; you can redistribute it
12  * and/or modify it under the terms of the GNU General
13  * Public License as published by the Free Software Foundation;
14  * either version 2, or (at your option)
15  * any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * ============================================================ */
23 
24 #include "filmfilter_p.h"
25 
26 // C++ includes
27 
28 #include <cmath>
29 
30 // Qt includes
31 
32 #include <QListWidget>
33 
34 // KDE includes
35 
36 #include <klocalizedstring.h>
37 
38 // Local includes
39 
40 #include "invertfilter.h"
41 
42 namespace Digikam
43 {
44 
FilmContainer()45 FilmContainer::FilmContainer()
46     : d(QSharedPointer<Private>(new Private))
47 {
48 }
49 
FilmContainer(CNFilmProfile profile,double gamma,bool sixteenBit)50 FilmContainer::FilmContainer(CNFilmProfile profile, double gamma, bool sixteenBit)
51     : d(QSharedPointer<Private>(new Private))
52 {
53     d->gamma      = gamma;
54     d->sixteenBit = sixteenBit;
55     d->whitePoint = DColor(QColor("white"), sixteenBit);
56     setCNType(profile);
57 }
58 
setWhitePoint(const DColor & wp)59 void FilmContainer::setWhitePoint(const DColor& wp)
60 {
61     d->whitePoint = wp;
62 }
63 
whitePoint() const64 DColor FilmContainer::whitePoint() const
65 {
66     return d->whitePoint;
67 }
68 
setExposure(double strength)69 void FilmContainer::setExposure(double strength)
70 {
71     d->exposure = strength;
72 }
73 
exposure() const74 double FilmContainer::exposure() const
75 {
76     return d->exposure;
77 }
78 
setSixteenBit(bool val)79 void FilmContainer::setSixteenBit(bool val)
80 {
81     d->sixteenBit = val;
82 }
83 
setGamma(double val)84 void FilmContainer::setGamma(double val)
85 {
86     d->gamma = val;
87 }
gamma() const88 double FilmContainer::gamma() const
89 {
90     return d->gamma;
91 }
92 
setCNType(CNFilmProfile profile)93 void FilmContainer::setCNType(CNFilmProfile profile)
94 {
95     d->cnType = profile;
96 
97     switch (profile)
98     {
99         default:
100             d->profile = FilmProfile(1.0, 1.0, 1.0);
101             d->cnType = CNNeutral;
102             break;
103 
104         case CNKodakGold100:
105             d->profile = FilmProfile(1.53, 2.00, 2.40); // check
106             break;
107 
108         case CNKodakGold200:
109             d->profile = FilmProfile(1.53, 2.00, 2.40); // check
110             break;
111 
112         case CNKodakEktar100:
113             d->profile = FilmProfile(1.40, 1.85, 2.34);
114             break;
115 
116         case CNKodakProfessionalPortra160NC:
117             d->profile = FilmProfile(1.49, 1.96, 2.46); // check
118             break;
119 
120         case CNKodakProfessionalPortra160VC:
121             d->profile = FilmProfile(1.56, 2.03, 2.55); // check
122             break;
123 
124         case CNKodakProfessionalPortra400NC:
125             d->profile = FilmProfile(1.69, 2.15, 2.69); // check
126             break;
127 
128         case CNKodakProfessionalPortra400VC:
129             d->profile = FilmProfile(1.78, 2.21, 2.77); // check
130             break;
131 
132         case CNKodakProfessionalPortra800Box:
133             d->profile = FilmProfile(1.89, 2.29, 2.89); // check
134             break;
135 
136         case CNKodakProfessionalPortra800P1:
137             d->profile = FilmProfile(1.53, 2.01, 2.46); // check
138             break;
139 
140         case CNKodakProfessionalPortra800P2:
141             d->profile = FilmProfile(1.74, 2.22, 2.64); // check
142             break;
143 
144         case CNKodakProfessionalNewPortra160:
145             d->profile = FilmProfile(1.41, 1.88, 2.32);
146             break;
147 
148         case CNKodakProfessionalNewPortra400:
149             d->profile = FilmProfile(1.69, 2.15, 2.68); // check
150             break;
151 
152         case CNKodakFarbwelt100:
153             d->profile = FilmProfile(1.86, 2.33, 2.77); // fix, check
154             break;
155 
156         case CNKodakFarbwelt200:
157             d->profile = FilmProfile(1.55, 2.03, 2.42); // check
158             break;
159 
160         case CNKodakFarbwelt400:
161             d->profile = FilmProfile(1.93, 2.43, 2.95); // fix, check
162             break;
163 
164         case CNKodakRoyalGold400:
165             d->profile = FilmProfile(2.24, 2.76, 3.27); // fix, check
166             break;
167 
168         case CNAgfaphotoVistaPlus200:
169             d->profile = FilmProfile(1.70, 2.13, 2.50);
170             break;
171 
172         case CNAgfaphotoVistaPlus400:
173             d->profile = FilmProfile(1.86, 2.35, 2.67); // fix, check
174             break;
175 
176         case CNFujicolorPro160S:
177             d->profile = FilmProfile(1.73, 2.27, 2.53); // fix, check
178             break;
179 
180         case CNFujicolorPro160C:
181             d->profile = FilmProfile(1.96, 2.46, 2.69); // fix, check
182             break;
183 
184         case CNFujicolorNPL160:
185             d->profile = FilmProfile(2.13, 2.36, 2.92); // fix, check
186             break;
187 
188         case CNFujicolorPro400H:
189             d->profile = FilmProfile(1.95, 2.37, 2.62); // fix, check
190             break;
191 
192         case CNFujicolorPro800Z:
193             d->profile = FilmProfile(2.12, 2.37, 2.56); // fix, check
194             break;
195 
196         case CNFujicolorSuperiaReala:
197             d->profile = FilmProfile(1.79, 2.14, 2.49); // check
198             break;
199 
200         case CNFujicolorSuperia100:
201             d->profile = FilmProfile(2.02, 2.46, 2.81); // fix, check
202             break;
203 
204         case CNFujicolorSuperia200:
205             d->profile = FilmProfile(2.11, 2.50, 2.79); // check
206             break;
207 
208         case CNFujicolorSuperiaXtra400:
209             d->profile = FilmProfile(2.11, 2.58, 2.96); // check
210             break;
211 
212         case CNFujicolorSuperiaXtra800:
213             d->profile = FilmProfile(2.44, 2.83, 3.18); // fix, check
214             break;
215 
216         case CNFujicolorTrueDefinition400:
217             d->profile = FilmProfile(1.93, 2.21, 2.39); // fix, check
218             break;
219 
220         case CNFujicolorSuperia1600:
221             d->profile = FilmProfile(2.35, 2.68, 2.96); // fix, check
222             break;
223     }
224 }
225 
cnType() const226 FilmContainer::CNFilmProfile FilmContainer::cnType() const
227 {
228     return d->cnType;
229 }
230 
setApplyBalance(bool val)231 void FilmContainer::setApplyBalance(bool val)
232 {
233     d->applyBalance = val;
234 }
235 
applyBalance() const236 bool FilmContainer::applyBalance() const
237 {
238     return d->applyBalance;
239 }
240 
whitePointForChannel(int ch) const241 int FilmContainer::whitePointForChannel(int ch) const
242 {
243     int max = d->sixteenBit ? 65535 : 255;
244 
245     switch (ch)
246     {
247         case RedChannel:
248             return d->whitePoint.red();
249 
250         case GreenChannel:
251             return d->whitePoint.green();
252 
253         case BlueChannel:
254             return d->whitePoint.blue();
255 
256         default:
257             return max;
258     }
259 
260     // not reached
261     return max;
262 }
263 
blackPointForChannel(int ch) const264 double FilmContainer::blackPointForChannel(int ch) const
265 {
266     if ((ch == LuminosityChannel) || (ch == AlphaChannel))
267     {
268         return 0.0;
269     }
270 
271     return pow(10, -d->profile.dmax(ch));
272 }
273 
gammaForChannel(int ch) const274 double FilmContainer::gammaForChannel(int ch) const
275 {
276     int max = d->sixteenBit ? 65535 : 255;
277 
278     if ((ch == GreenChannel) || (ch == BlueChannel))
279     {
280         double bpc = blackPointForChannel(ch)*d->exposure;
281         double wpc = (double)whitePointForChannel(ch)/(double)max;
282         double bpr = blackPointForChannel(RedChannel)*d->exposure;
283         double wpr = (double)whitePointForChannel(RedChannel)/(double)max;
284 
285         return (log10(bpc / wpc) / log10(bpr / wpr));
286     }
287 
288     return 1.0;
289 }
290 
toLevels() const291 LevelsContainer FilmContainer::toLevels() const
292 {
293     LevelsContainer l;
294     int max = d->sixteenBit ? 65535 : 255;
295 
296     for (int i = LuminosityChannel ; i <= AlphaChannel ; ++i)
297     {
298         l.lInput[i]  = blackPointForChannel(i) * max * d->exposure;
299         l.hInput[i]  = whitePointForChannel(i) * d->profile.wp(i);
300         l.lOutput[i] = 0;
301         l.hOutput[i] = max;
302 
303         if (d->applyBalance)
304         {
305             l.gamma[i] = gammaForChannel(i);
306         }
307         else
308         {
309             l.gamma[i] = 1.0;
310         }
311     }
312 
313     return l;
314 }
315 
toCB() const316 CBContainer FilmContainer::toCB() const
317 {
318     CBContainer cb;
319 
320     cb.red   = d->profile.balance(RedChannel);
321     cb.green = d->profile.balance(GreenChannel);
322     cb.blue  = d->profile.balance(BlueChannel);
323     cb.gamma = 1.0;
324 
325     return cb;
326 }
327 
profileItemList(QListWidget * view)328 QList<FilmContainer::ListItem*> FilmContainer::profileItemList(QListWidget* view)
329 {
330     QList<FilmContainer::ListItem*> itemList;
331     QMap<int, QString>::ConstIterator it;
332 
333     for (it = profileMap.constBegin() ; it != profileMap.constEnd() ; ++it)
334     {
335         itemList << new ListItem(it.value(), view, (CNFilmProfile)it.key());
336     }
337 
338     return itemList;
339 }
340 
profileMapInitializer()341 QMap<int, QString> FilmContainer::profileMapInitializer()
342 {
343     QMap<int, QString> profileMap;
344 
345     profileMap[CNNeutral]                       = QLatin1String("Neutral");
346     profileMap[CNKodakGold100]                  = QLatin1String("Kodak Gold 100");
347     profileMap[CNKodakGold200]                  = QLatin1String("Kodak Gold 200");
348     profileMap[CNKodakProfessionalNewPortra160] = QLatin1String("Kodak Professional New Portra 160");
349     profileMap[CNKodakProfessionalNewPortra400] = QLatin1String("Kodak Professional New Portra 400");
350     profileMap[CNKodakEktar100]                 = QLatin1String("Kodak Ektar 100");
351     profileMap[CNKodakFarbwelt100]              = QLatin1String("Kodak Farbwelt 100");
352     profileMap[CNKodakFarbwelt200]              = QLatin1String("Kodak Farbwelt 200");
353     profileMap[CNKodakFarbwelt400]              = QLatin1String("Kodak Farbwelt 400");
354     profileMap[CNKodakProfessionalPortra160NC]  = QLatin1String("Kodak Professional Portra 160NC");
355     profileMap[CNKodakProfessionalPortra160VC]  = QLatin1String("Kodak Professional Portra 160VC");
356     profileMap[CNKodakProfessionalPortra400NC]  = QLatin1String("Kodak Professional Portra 400NC");
357     profileMap[CNKodakProfessionalPortra400VC]  = QLatin1String("Kodak Professional Portra 400VC");
358     profileMap[CNKodakProfessionalPortra800Box] = QLatin1String("Kodak Professional Portra 800 (Box Speed");
359     profileMap[CNKodakProfessionalPortra800P1]  = QLatin1String("Kodak Professional Portra 800 (Push 1 stop");
360     profileMap[CNKodakProfessionalPortra800P2]  = QLatin1String("Kodak Professional Portra 800 (Push 2 stop");
361     profileMap[CNKodakRoyalGold400]             = QLatin1String("Kodak Royal Gold 400");
362     profileMap[CNAgfaphotoVistaPlus200]         = QLatin1String("Agfaphoto Vista Plus 200");
363     profileMap[CNAgfaphotoVistaPlus400]         = QLatin1String("Agfaphoto Vista Plus 400");
364     profileMap[CNFujicolorPro160S]              = QLatin1String("Fujicolor Pro 160S");
365     profileMap[CNFujicolorPro160C]              = QLatin1String("Fujicolor Pro 160C");
366     profileMap[CNFujicolorNPL160]               = QLatin1String("Fujicolor NPL 160");
367     profileMap[CNFujicolorPro400H]              = QLatin1String("Fujicolor Pro 400H");
368     profileMap[CNFujicolorPro800Z]              = QLatin1String("Fujicolor Pro 800Z");
369     profileMap[CNFujicolorSuperiaReala]         = QLatin1String("Fujicolor Superia Reala");
370     profileMap[CNFujicolorSuperia100]           = QLatin1String("Fujicolor Superia 100");
371     profileMap[CNFujicolorSuperia200]           = QLatin1String("Fujicolor Superia 200");
372     profileMap[CNFujicolorSuperiaXtra400]       = QLatin1String("Fujicolor Superia X-Tra 400");
373     profileMap[CNFujicolorSuperiaXtra800]       = QLatin1String("Fujicolor Superia X-Tra 800");
374     profileMap[CNFujicolorTrueDefinition400]    = QLatin1String("Fujicolor Superia True Definition 400");
375     profileMap[CNFujicolorSuperia1600]          = QLatin1String("Fujicolor Superia 1600");
376 
377     return profileMap;
378 }
379 
380 const QMap<int, QString> FilmContainer::profileMap = FilmContainer::profileMapInitializer();
381 
382 // ------------------------------------------------------------------
383 
FilmFilter(QObject * const parent)384 FilmFilter::FilmFilter(QObject* const parent)
385     : DImgThreadedFilter(parent, QLatin1String("FilmFilter")),
386       d                 (new Private())
387 {
388     d->film = FilmContainer();
389     initFilter();
390 }
391 
FilmFilter(DImg * const orgImage,QObject * const parent,const FilmContainer & settings)392 FilmFilter::FilmFilter(DImg* const orgImage, QObject* const parent, const FilmContainer& settings)
393     : DImgThreadedFilter(orgImage, parent, QLatin1String("FilmFilter")),
394       d                 (new Private())
395 {
396     d->film = settings;
397     initFilter();
398 }
399 
~FilmFilter()400 FilmFilter::~FilmFilter()
401 {
402     cancelFilter();
403     delete d;
404 }
405 
DisplayableName()406 QString FilmFilter::DisplayableName()
407 {
408     return QString::fromUtf8(I18N_NOOP("Color Negative Inverter"));
409 }
410 
filterImage()411 void FilmFilter::filterImage()
412 {
413     DImg tmpLevel;
414     DImg tmpGamma;
415     DImg tmpInv;
416 
417     LevelsContainer l = d->film.toLevels();
418     CBContainer cb    = d->film.toCB();
419     CBContainer gamma;
420 
421     // level the image first, this removes the orange mask and corrects
422     // colors according to the density ranges of the film profile
423 
424     // cppcheck-suppress unusedScopedObject
425     LevelsFilter(l, this, m_orgImage, tmpLevel, 0, 40);
426 
427     // in case of a linear raw scan, gamma needs to be
428     // applied after leveling the image, otherwise the image will
429     // look too bright. The standard value is 2.2, but 1.8 is also
430     // frequently found in literature
431 
432     gamma.gamma = d->film.gamma();
433 
434     // cppcheck-suppress unusedScopedObject
435     CBFilter(gamma, this, tmpLevel, tmpGamma, 40, 80);
436 
437     // invert the image to have a positive image
438 
439     // cppcheck-suppress unusedScopedObject
440     InvertFilter(this, tmpGamma, tmpInv, 80, 100);
441 
442     m_destImage = tmpInv;
443     postProgress(100);
444 }
445 
filterAction()446 FilterAction FilmFilter::filterAction()
447 {
448     FilterAction action(FilterIdentifier(), CurrentVersion());
449     action.setDisplayableName(DisplayableName());
450 
451     action.addParameter(QLatin1String("CNType"),               d->film.cnType());
452     action.addParameter(QLatin1String("ProfileName"),          FilmContainer::profileMap[d->film.cnType()]);
453     action.addParameter(QLatin1String("Exposure"),             d->film.exposure());
454     action.addParameter(QLatin1String("Gamma"),                d->film.gamma());
455     action.addParameter(QLatin1String("ApplyColorBalance"),    d->film.applyBalance());
456     action.addParameter(QLatin1String("WhitePointRed"),        d->film.whitePoint().red());
457     action.addParameter(QLatin1String("WhitePointGreen"),      d->film.whitePoint().green());
458     action.addParameter(QLatin1String("WhitePointBlue"),       d->film.whitePoint().blue());
459     action.addParameter(QLatin1String("WhitePointAlpha"),      d->film.whitePoint().alpha());
460     action.addParameter(QLatin1String("WhitePointSixteenBit"), d->film.whitePoint().sixteenBit());
461 
462     return action;
463 }
464 
readParameters(const FilterAction & action)465 void FilmFilter::readParameters(const FilterAction& action)
466 {
467     double red   = action.parameter(QLatin1String("WhitePointRed")).toDouble();
468     double green = action.parameter(QLatin1String("WhitePointGreen")).toDouble();
469     double blue  = action.parameter(QLatin1String("WhitePointBlue")).toDouble();
470     double alpha = action.parameter(QLatin1String("WhitePointAlpha")).toDouble();
471     bool sb      = action.parameter(QLatin1String("WhitePointSixteenBit")).toBool();
472     bool balance = action.parameter(QLatin1String("ApplyColorBalance")).toBool();
473 
474     d->film.setWhitePoint(DColor(red, green, blue, alpha, sb));
475     d->film.setExposure(action.parameter(QLatin1String("Exposure")).toDouble());
476     d->film.setGamma(action.parameter(QLatin1String("Gamma")).toDouble());
477     d->film.setCNType((FilmContainer::CNFilmProfile)(action.parameter(QLatin1String("CNType")).toInt()));
478     d->film.setApplyBalance(balance);
479 }
480 
481 } // namespace Digikam
482