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