File: | /home/gilles/devel/GIT/8.x/core/libs/dimg/filters/lc/localcontrastfilter.cpp |
Warning: | line 335, column 50 The left operand of '+' is a garbage value |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* ============================================================ | |||
2 | * | |||
3 | * This file is a part of digiKam project | |||
4 | * https://www.digikam.org | |||
5 | * | |||
6 | * Date : 2009-08-09 | |||
7 | * Description : Enhance image with local contrasts (as human eye does). | |||
8 | * LDR ToneMapper <zynaddsubfx.sourceforge.net/other/tonemapping> | |||
9 | * | |||
10 | * SPDX-FileCopyrightText: 2009 by Nasca Octavian Paul <zynaddsubfx at yahoo dot com> | |||
11 | * SPDX-FileCopyrightText: 2009 by Julien Pontabry <julien dot pontabry at gmail dot com> | |||
12 | * SPDX-FileCopyrightText: 2009-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> | |||
13 | * SPDX-FileCopyrightText: 2010 by Martin Klapetek <martin dot klapetek at gmail dot com> | |||
14 | * | |||
15 | * SPDX-License-Identifier: GPL-2.0-or-later | |||
16 | * | |||
17 | * ============================================================ */ | |||
18 | ||||
19 | #include "localcontrastfilter.h" | |||
20 | ||||
21 | // Qt includes | |||
22 | ||||
23 | #include <QtMath> | |||
24 | #include <QtConcurrent> // krazy:exclude=includes | |||
25 | ||||
26 | // Local includes | |||
27 | ||||
28 | #include "digikam_debug.h" | |||
29 | #include "randomnumbergenerator.h" | |||
30 | #include "digikam_globals_p.h" // For KF6::Ki18n deprecated | |||
31 | ||||
32 | namespace Digikam | |||
33 | { | |||
34 | ||||
35 | class Q_DECL_HIDDEN__attribute__((visibility("hidden"))) LocalContrastFilter::Private | |||
36 | { | |||
37 | public: | |||
38 | ||||
39 | Private() = default; | |||
40 | ||||
41 | /// preprocessed values | |||
42 | float current_process_power_value = 20.0F; | |||
43 | ||||
44 | LocalContrastContainer par; | |||
45 | ||||
46 | RandomNumberGenerator generator; | |||
47 | }; | |||
48 | ||||
49 | LocalContrastFilter::LocalContrastFilter(QObject* const parent) | |||
50 | : DImgThreadedFilter(parent), | |||
51 | d (new Private) | |||
52 | { | |||
53 | initFilter(); | |||
54 | } | |||
55 | ||||
56 | LocalContrastFilter::LocalContrastFilter(DImg* const image, QObject* const parent, const LocalContrastContainer& par) | |||
57 | : DImgThreadedFilter(image, parent, QLatin1String("LocalContrast")), | |||
58 | d (new Private) | |||
59 | { | |||
60 | d->par = par; | |||
61 | d->generator.seedByTime(); | |||
62 | initFilter(); | |||
63 | } | |||
64 | ||||
65 | LocalContrastFilter::~LocalContrastFilter() | |||
66 | { | |||
67 | cancelFilter(); | |||
68 | ||||
69 | delete d; | |||
70 | } | |||
71 | ||||
72 | QString LocalContrastFilter::DisplayableName() | |||
73 | { | |||
74 | return QString::fromUtf8(I18N_NOOP("Local Contrast Filter")kli18n("Local Contrast Filter").untranslatedText()); | |||
75 | } | |||
76 | ||||
77 | void LocalContrastFilter::filterImage() | |||
78 | { | |||
79 | if (!m_orgImage.isNull()) | |||
| ||||
80 | { | |||
81 | int size = m_orgImage.width() * m_orgImage.height() * 3; | |||
82 | int i, j; | |||
83 | ||||
84 | d->generator.reseed(); | |||
85 | ||||
86 | if (m_orgImage.sixteenBit()) | |||
87 | { | |||
88 | // sixteen bit image | |||
89 | ||||
90 | QScopedArrayPointer<unsigned short> data(new unsigned short[size]); | |||
91 | unsigned short* dataImg = reinterpret_cast<unsigned short*>(m_orgImage.bits()); | |||
92 | ||||
93 | for (i = 0, j = 0 ; runningFlag() && (i < size) ; i += 3, j += 4) | |||
94 | { | |||
95 | data[i] = dataImg[j]; | |||
96 | data[i + 1] = dataImg[j + 1]; | |||
97 | data[i + 2] = dataImg[j + 2]; | |||
98 | } | |||
99 | ||||
100 | postProgress(10); | |||
101 | ||||
102 | process16bitRgbImage(data.data(), m_orgImage.width(), m_orgImage.height()); | |||
103 | ||||
104 | for (uint x = 0 ; runningFlag() && (x < m_orgImage.width()) ; ++x) | |||
105 | { | |||
106 | for (uint y = 0 ; runningFlag() && (y < m_orgImage.height()) ; ++y) | |||
107 | { | |||
108 | i = (m_orgImage.width() * y + x) * 3; | |||
109 | m_destImage.setPixelColor(x, y, DColor((unsigned short)data[i + 2], | |||
110 | (unsigned short)data[i + 1], | |||
111 | (unsigned short)data[i], | |||
112 | 65535, true)); | |||
113 | } | |||
114 | } | |||
115 | ||||
116 | postProgress(90); | |||
117 | } | |||
118 | else | |||
119 | { | |||
120 | // eight bit image | |||
121 | ||||
122 | QScopedArrayPointer<uchar> data(new uchar[size]); | |||
123 | ||||
124 | for (i = 0, j = 0 ; runningFlag() && (i < size) ; i += 3, j += 4) | |||
125 | { | |||
126 | data[i] = m_orgImage.bits()[j]; | |||
127 | data[i + 1] = m_orgImage.bits()[j + 1]; | |||
128 | data[i + 2] = m_orgImage.bits()[j + 2]; | |||
129 | } | |||
130 | ||||
131 | postProgress(10); | |||
132 | ||||
133 | process8bitRgbImage(data.data(), m_orgImage.width(), m_orgImage.height()); | |||
134 | ||||
135 | for (uint x = 0 ; runningFlag() && (x < m_orgImage.width()) ; ++x) | |||
136 | { | |||
137 | for (uint y = 0 ; runningFlag() && (y < m_orgImage.height()) ; ++y) | |||
138 | { | |||
139 | i = (m_orgImage.width() * y + x) * 3; | |||
140 | m_destImage.setPixelColor(x, y, DColor(data[i + 2], data[i + 1], data[i], 255, false)); | |||
141 | } | |||
142 | } | |||
143 | ||||
144 | postProgress(90); | |||
145 | } | |||
146 | } | |||
147 | ||||
148 | postProgress(100); | |||
149 | } | |||
150 | ||||
151 | void LocalContrastFilter::process8bitRgbImage(unsigned char* const img, int sizex, int sizey) | |||
152 | { | |||
153 | int size = sizex * sizey; | |||
154 | QScopedArrayPointer<float> tmpimage(new float[size * 3]{}); | |||
155 | ||||
156 | for (int i = 0 ; runningFlag() && (i < size * 3) ; ++i) | |||
157 | { | |||
158 | // convert to floating point | |||
159 | ||||
160 | tmpimage[i] = (float)(img[i] / 255.0); | |||
161 | } | |||
162 | ||||
163 | postProgress(20); | |||
164 | ||||
165 | processRgbImage(tmpimage.data(), sizex, sizey); | |||
166 | ||||
167 | // convert back to 8 bits (with dithering) | |||
168 | ||||
169 | int pos = 0; | |||
170 | ||||
171 | for (int i = 0 ; runningFlag() && (i < size) ; ++i) | |||
172 | { | |||
173 | float dither = d->generator.number(0.0, 1.0); | |||
174 | img[pos] = (int)(tmpimage[pos] * 255.0 + dither); | |||
175 | img[pos + 1] = (int)(tmpimage[pos + 1] * 255.0 + dither); | |||
176 | img[pos + 2] = (int)(tmpimage[pos + 2] * 255.0 + dither); | |||
177 | pos += 3; | |||
178 | } | |||
179 | ||||
180 | postProgress(80); | |||
181 | } | |||
182 | ||||
183 | void LocalContrastFilter::process16bitRgbImage(unsigned short* const img, int sizex, int sizey) | |||
184 | { | |||
185 | int size = sizex * sizey; | |||
186 | QScopedArrayPointer<float> tmpimage(new float[size * 3]); | |||
187 | ||||
188 | for (int i = 0 ; runningFlag() && (i < size * 3) ; ++i) | |||
189 | { | |||
190 | // convert to floating point | |||
191 | ||||
192 | tmpimage[i] = (float)(img[i] / 65535.0); | |||
193 | } | |||
194 | ||||
195 | postProgress(20); | |||
196 | ||||
197 | processRgbImage(tmpimage.data(), sizex, sizey); | |||
198 | ||||
199 | // convert back to 16 bits (with dithering) | |||
200 | ||||
201 | int pos = 0; | |||
202 | ||||
203 | for (int i = 0 ; runningFlag() && (i < size) ; ++i) | |||
204 | { | |||
205 | float dither = d->generator.number(0.0, 1.0); | |||
206 | img[pos] = (int)(tmpimage[pos] * 65535.0 + dither); | |||
207 | img[pos + 1] = (int)(tmpimage[pos + 1] * 65535.0 + dither); | |||
208 | img[pos + 2] = (int)(tmpimage[pos + 2] * 65535.0 + dither); | |||
209 | pos += 3; | |||
210 | } | |||
211 | ||||
212 | postProgress(80); | |||
213 | } | |||
214 | ||||
215 | float LocalContrastFilter::func(float x1, float x2) | |||
216 | { | |||
217 | float result = 0.5; | |||
218 | float p; | |||
219 | ||||
220 | switch (d->par.functionId) | |||
221 | { | |||
222 | case 0: // power function | |||
223 | { | |||
224 | p = (float)(qPow((double)10.0, (double)qFabs((x2 * 2.0 - 1.0)) * d->current_process_power_value * 0.02)); | |||
225 | ||||
226 | if (x2 >= 0.5) | |||
227 | { | |||
228 | result = qPow(x1, p); | |||
229 | } | |||
230 | else | |||
231 | { | |||
232 | result = (float)(1.0 - qPow((double)1.0 - x1, (double)p)); | |||
233 | } | |||
234 | ||||
235 | break; | |||
236 | } | |||
237 | ||||
238 | case 1: // linear function | |||
239 | { | |||
240 | p = (float)(1.0 / (1 + qExp(-(x2 * 2.0 - 1.0) * d->current_process_power_value * 0.04))); | |||
241 | result = (x1 < p) ? (float)(x1 * (1.0 - p) / p) : (float)((1.0 - p) + (x1 - p) * p / (1.0 - p)); | |||
242 | break; | |||
243 | } | |||
244 | } | |||
245 | ||||
246 | return result; | |||
247 | } | |||
248 | ||||
249 | void LocalContrastFilter::blurMultithreaded(uint start, uint stop, float* const img, float* const blurimage) | |||
250 | { | |||
251 | uint pos = start * 3; | |||
252 | ||||
253 | for (uint i = start ; runningFlag() && (i < stop) ; ++i) | |||
254 | { | |||
255 | float src_r = img[pos]; | |||
256 | float src_g = img[pos + 1]; | |||
257 | float src_b = img[pos + 2]; | |||
258 | ||||
259 | float blur = blurimage[i]; | |||
260 | ||||
261 | float dest_r = func(src_r, blur); | |||
262 | float dest_g = func(src_g, blur); | |||
263 | float dest_b = func(src_b, blur); | |||
264 | ||||
265 | img[pos] = dest_r; | |||
266 | img[pos + 1] = dest_g; | |||
267 | img[pos + 2] = dest_b; | |||
268 | ||||
269 | pos += 3; | |||
270 | } | |||
271 | } | |||
272 | ||||
273 | void LocalContrastFilter::saturationMultithreaded(uint start, uint stop, float* const img, float* const srcimg) | |||
274 | { | |||
275 | float src_h, src_s, src_v; | |||
276 | float dest_h, dest_s, dest_v; | |||
277 | float destSaturation, s1; | |||
278 | ||||
279 | uint pos = start * 3; | |||
280 | int highSaturationValue = 100 - d->par.highSaturation; | |||
281 | int lowSaturationValue = 100 - d->par.lowSaturation; | |||
282 | ||||
283 | for (uint i = start ; runningFlag() && (i < stop) ; ++i) | |||
284 | { | |||
285 | rgb2hsv(srcimg[pos], srcimg[pos + 1], srcimg[pos + 2], src_h, src_s, src_v); | |||
286 | rgb2hsv(img[pos], img[pos + 1], img[pos + 2], dest_h, dest_s, dest_v); | |||
287 | ||||
288 | destSaturation = (float)((src_s * highSaturationValue + dest_s * (100.0 - highSaturationValue)) * 0.01); | |||
289 | ||||
290 | if (dest_v > src_v) | |||
291 | { | |||
292 | s1 = (float)(destSaturation * src_v / (dest_v + 1.0 / 255.0)); | |||
293 | destSaturation = (float)((lowSaturationValue * s1 + d->par.lowSaturation * destSaturation) * 0.01); | |||
294 | } | |||
295 | ||||
296 | hsv2rgb(dest_h, destSaturation, dest_v, img[pos], img[pos + 1], img[pos + 2]); | |||
297 | ||||
298 | pos += 3; | |||
299 | } | |||
300 | } | |||
301 | ||||
302 | void LocalContrastFilter::processRgbImage(float* const img, int sizex, int sizey) | |||
303 | { | |||
304 | int size = sizex * sizey; | |||
305 | QScopedArrayPointer<float> blurimage(new float[size]); | |||
306 | QScopedArrayPointer<float> srcimg(new float[size * 3]); | |||
307 | ||||
308 | for (int i = 0 ; i < (size * 3) ; ++i) | |||
309 | { | |||
310 | srcimg[i] = img[i]; | |||
311 | } | |||
312 | ||||
313 | postProgress(30); | |||
314 | ||||
315 | if (d->par.stretchContrast) | |||
316 | { | |||
317 | stretchContrast(img, size * 3); | |||
318 | } | |||
319 | ||||
320 | postProgress(40); | |||
321 | ||||
322 | QList<int> vals = multithreadedSteps(size); | |||
323 | int pos = 0; | |||
324 | ||||
325 | for (int nstage = 0 ; runningFlag() && (nstage < TONEMAPPING_MAX_STAGES4) ; ++nstage) | |||
326 | { | |||
327 | if (d->par.stage[nstage].enabled) | |||
328 | { | |||
329 | // compute the desatured image | |||
330 | ||||
331 | pos = 0; | |||
332 | ||||
333 | for (int i = 0 ; runningFlag() && (i < size) ; ++i) | |||
334 | { | |||
335 | blurimage[i] = (float)((img[pos] + img[pos + 1] + img[pos + 2]) / 3.0); | |||
| ||||
336 | pos += 3; | |||
337 | } | |||
338 | ||||
339 | d->current_process_power_value = d->par.getPower(nstage); | |||
340 | ||||
341 | // blur | |||
342 | ||||
343 | inplaceBlur(blurimage.data(), sizex, sizey, d->par.getBlur(nstage)); | |||
344 | ||||
345 | QList <QFuture<void> > tasks; | |||
346 | ||||
347 | for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) | |||
348 | { | |||
349 | tasks.append(QtConcurrent::run( | |||
350 | ||||
351 | #if (QT_VERSION((6<<16)|(7<<8)|(0)) >= QT_VERSION_CHECK(6, 0, 0)((6<<16)|(0<<8)|(0))) | |||
352 | ||||
353 | &LocalContrastFilter::blurMultithreaded, this, | |||
354 | ||||
355 | #else | |||
356 | ||||
357 | this, &LocalContrastFilter::blurMultithreaded, | |||
358 | ||||
359 | #endif | |||
360 | ||||
361 | vals[j], | |||
362 | vals[j+1], | |||
363 | img, | |||
364 | blurimage.data() | |||
365 | ) | |||
366 | ); | |||
367 | } | |||
368 | ||||
369 | Q_FOREACH (QFuture<void> t, tasks)for (auto _container_369 = QtPrivate::qMakeForeachContainer(tasks ); _container_369.i != _container_369.e; ++_container_369.i) if (QFuture<void> t = *_container_369.i; false) {} else | |||
370 | { | |||
371 | t.waitForFinished(); | |||
372 | } | |||
373 | } | |||
374 | ||||
375 | postProgress(50 + nstage * 5); | |||
376 | } | |||
377 | ||||
378 | if ((d->par.highSaturation != 100) || (d->par.lowSaturation != 100)) | |||
379 | { | |||
380 | qCDebug(DIGIKAM_DIMG_LOG)for (QLoggingCategoryMacroHolder<QtDebugMsg> qt_category ((DIGIKAM_DIMG_LOG)()); qt_category; qt_category.control = false ) QMessageLogger(static_cast<const char *>("/home/gilles/devel/GIT/8.x/core/libs/dimg/filters/lc/localcontrastfilter.cpp" ), 380, static_cast<const char *>(__PRETTY_FUNCTION__), qt_category.name()).debug() << "highSaturation : " << d->par.highSaturation; | |||
381 | qCDebug(DIGIKAM_DIMG_LOG)for (QLoggingCategoryMacroHolder<QtDebugMsg> qt_category ((DIGIKAM_DIMG_LOG)()); qt_category; qt_category.control = false ) QMessageLogger(static_cast<const char *>("/home/gilles/devel/GIT/8.x/core/libs/dimg/filters/lc/localcontrastfilter.cpp" ), 381, static_cast<const char *>(__PRETTY_FUNCTION__), qt_category.name()).debug() << "lowSaturation : " << d->par.lowSaturation; | |||
382 | ||||
383 | QList <QFuture<void> > tasks; | |||
384 | ||||
385 | for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) | |||
386 | { | |||
387 | tasks.append(QtConcurrent::run( | |||
388 | ||||
389 | #if (QT_VERSION((6<<16)|(7<<8)|(0)) >= QT_VERSION_CHECK(6, 0, 0)((6<<16)|(0<<8)|(0))) | |||
390 | ||||
391 | &LocalContrastFilter::saturationMultithreaded, this, | |||
392 | ||||
393 | #else | |||
394 | ||||
395 | this, &LocalContrastFilter::saturationMultithreaded, | |||
396 | ||||
397 | #endif | |||
398 | ||||
399 | vals[j], | |||
400 | vals[j+1], | |||
401 | img, | |||
402 | srcimg.data() | |||
403 | ) | |||
404 | ); | |||
405 | } | |||
406 | ||||
407 | Q_FOREACH (QFuture<void> t, tasks)for (auto _container_407 = QtPrivate::qMakeForeachContainer(tasks ); _container_407.i != _container_407.e; ++_container_407.i) if (QFuture<void> t = *_container_407.i; false) {} else | |||
408 | { | |||
409 | t.waitForFinished(); | |||
410 | } | |||
411 | } | |||
412 | ||||
413 | postProgress(70); | |||
414 | } | |||
415 | ||||
416 | void LocalContrastFilter::inplaceBlurYMultithreaded(const Args& prm) | |||
417 | { | |||
418 | for (uint y = prm.start ; runningFlag() && (y < prm.stop) ; ++y) | |||
419 | { | |||
420 | uint pos = y * prm.sizex; | |||
421 | float old = prm.data[pos]; | |||
422 | ++pos; | |||
423 | ||||
424 | for (int x = 1 ; runningFlag() && (x < prm.sizex) ; ++x) | |||
425 | { | |||
426 | old = (prm.data[pos] * (1 - prm.a) + old * prm.a) + prm.denormal_remove; | |||
427 | prm.data[pos] = old; | |||
428 | ++pos; | |||
429 | } | |||
430 | ||||
431 | pos = y * prm.sizex + prm.sizex - 1; | |||
432 | ||||
433 | for (int x = 1 ; runningFlag() && (x < prm.sizex) ; ++x) | |||
434 | { | |||
435 | old = (prm.data[pos] * (1 - prm.a) + old * prm.a) + prm.denormal_remove; | |||
436 | prm.data[pos] = old; | |||
437 | pos--; | |||
438 | } | |||
439 | } | |||
440 | } | |||
441 | ||||
442 | void LocalContrastFilter::inplaceBlurXMultithreaded(const Args& prm) | |||
443 | { | |||
444 | for (uint x = prm.start ; runningFlag() && (x < prm.stop) ; ++x) | |||
445 | { | |||
446 | uint pos = x; | |||
447 | float old = prm.data[pos]; | |||
448 | ||||
449 | for (int y = 1 ; runningFlag() && (y < prm.sizey) ; ++y) | |||
450 | { | |||
451 | old = (prm.data[pos] * (1 - prm.a) + old * prm.a) + prm.denormal_remove; | |||
452 | prm.data[pos] = old; | |||
453 | pos += prm.sizex; | |||
454 | } | |||
455 | ||||
456 | pos = x + prm.sizex * (prm.sizey - 1); | |||
457 | ||||
458 | for (int y = 1 ; runningFlag() && (y < prm.sizey) ; ++y) | |||
459 | { | |||
460 | old = (prm.data[pos] * (1 - prm.a) + old * prm.a) + prm.denormal_remove; | |||
461 | prm.data[pos] = old; | |||
462 | pos -= prm.sizex; | |||
463 | } | |||
464 | } | |||
465 | } | |||
466 | ||||
467 | void LocalContrastFilter::inplaceBlur(float* const data, int sizex, int sizey, float blur) | |||
468 | { | |||
469 | if (blur < 0.3) | |||
470 | { | |||
471 | return; | |||
472 | } | |||
473 | ||||
474 | Args prm; | |||
475 | ||||
476 | prm.a = (float)(qExp(log(0.25) / blur)); | |||
477 | ||||
478 | if ((prm.a <= 0.0) || (prm.a >= 1.0)) | |||
479 | { | |||
480 | return; | |||
481 | } | |||
482 | ||||
483 | prm.a *= prm.a; | |||
484 | prm.data = data; | |||
485 | prm.sizex = sizex; | |||
486 | prm.sizey = sizey; | |||
487 | prm.blur = blur; | |||
488 | prm.denormal_remove = (float)(1e-15); | |||
489 | ||||
490 | QList<int> valsx = multithreadedSteps(prm.sizex); | |||
491 | QList<int> valsy = multithreadedSteps(prm.sizey); | |||
492 | ||||
493 | for (uint stage = 0 ; runningFlag() && (stage < 2) ; ++stage) | |||
494 | { | |||
495 | QList <QFuture<void> > tasks; | |||
496 | ||||
497 | for (int j = 0 ; runningFlag() && (j < valsy.count()-1) ; ++j) | |||
498 | { | |||
499 | prm.start = valsy[j]; | |||
500 | prm.stop = valsy[j+1]; | |||
501 | ||||
502 | tasks.append(QtConcurrent::run( | |||
503 | ||||
504 | #if (QT_VERSION((6<<16)|(7<<8)|(0)) >= QT_VERSION_CHECK(6, 0, 0)((6<<16)|(0<<8)|(0))) | |||
505 | ||||
506 | &LocalContrastFilter::inplaceBlurYMultithreaded, this, | |||
507 | ||||
508 | #else | |||
509 | ||||
510 | this, &LocalContrastFilter::inplaceBlurYMultithreaded, | |||
511 | ||||
512 | #endif | |||
513 | ||||
514 | prm | |||
515 | ) | |||
516 | ); | |||
517 | } | |||
518 | ||||
519 | Q_FOREACH (QFuture<void> t, tasks)for (auto _container_519 = QtPrivate::qMakeForeachContainer(tasks ); _container_519.i != _container_519.e; ++_container_519.i) if (QFuture<void> t = *_container_519.i; false) {} else | |||
520 | { | |||
521 | t.waitForFinished(); | |||
522 | } | |||
523 | ||||
524 | tasks.clear(); | |||
525 | ||||
526 | for (int j = 0 ; runningFlag() && (j < valsx.count()-1) ; ++j) | |||
527 | { | |||
528 | prm.start = valsx[j]; | |||
529 | prm.stop = valsx[j+1]; | |||
530 | ||||
531 | tasks.append(QtConcurrent::run( | |||
532 | ||||
533 | #if (QT_VERSION((6<<16)|(7<<8)|(0)) >= QT_VERSION_CHECK(6, 0, 0)((6<<16)|(0<<8)|(0))) | |||
534 | ||||
535 | &LocalContrastFilter::inplaceBlurXMultithreaded, this, | |||
536 | ||||
537 | #else | |||
538 | ||||
539 | this, &LocalContrastFilter::inplaceBlurXMultithreaded, | |||
540 | ||||
541 | #endif | |||
542 | ||||
543 | prm | |||
544 | ) | |||
545 | ); | |||
546 | } | |||
547 | ||||
548 | Q_FOREACH (QFuture<void> t, tasks)for (auto _container_548 = QtPrivate::qMakeForeachContainer(tasks ); _container_548.i != _container_548.e; ++_container_548.i) if (QFuture<void> t = *_container_548.i; false) {} else | |||
549 | { | |||
550 | t.waitForFinished(); | |||
551 | } | |||
552 | } | |||
553 | } | |||
554 | ||||
555 | void LocalContrastFilter::stretchContrast(float* const data, int datasize) | |||
556 | { | |||
557 | // stretch the contrast | |||
558 | ||||
559 | const unsigned int histogram_size = 256; | |||
560 | ||||
561 | // first, we compute the histogram | |||
562 | ||||
563 | unsigned int histogram[histogram_size] = { 0 }; | |||
564 | ||||
565 | for (unsigned int i = 0 ; runningFlag() && (i < (unsigned int)datasize) ; ++i) | |||
566 | { | |||
567 | int m = (int)(data[i] * (histogram_size - 1)); | |||
568 | ||||
569 | if (m < 0) | |||
570 | { | |||
571 | m = 0; | |||
572 | } | |||
573 | ||||
574 | if (m > (int)(histogram_size - 1)) | |||
575 | { | |||
576 | m = histogram_size - 1; | |||
577 | } | |||
578 | ||||
579 | histogram[m]++; | |||
580 | } | |||
581 | ||||
582 | // I want to strip the lowest and upper 0.1 percents (in the histogram) of the pixels | |||
583 | int min = 0; | |||
584 | int max = 255; | |||
585 | unsigned int desired_sum = datasize / 1000; | |||
586 | unsigned int sum_min = 0; | |||
587 | unsigned int sum_max = 0; | |||
588 | ||||
589 | for (unsigned int i = 0 ; runningFlag() && (i < histogram_size) ; ++i) | |||
590 | { | |||
591 | sum_min += histogram[i]; | |||
592 | ||||
593 | if (sum_min > desired_sum) | |||
594 | { | |||
595 | min = i; | |||
596 | break; | |||
597 | } | |||
598 | } | |||
599 | ||||
600 | for (int i = histogram_size - 1 ; runningFlag() && (i >= 0) ; --i) | |||
601 | { | |||
602 | sum_max += histogram[i]; | |||
603 | ||||
604 | if (sum_max > desired_sum) | |||
605 | { | |||
606 | max = i; | |||
607 | break; | |||
608 | } | |||
609 | } | |||
610 | ||||
611 | if (min >= max) | |||
612 | { | |||
613 | min = 0; | |||
614 | max = 255; | |||
615 | } | |||
616 | ||||
617 | float min_src_val = (float)(min / 255.0); | |||
618 | float max_src_val = (float)(max / 255.0); | |||
619 | ||||
620 | for (int i = 0 ; runningFlag() && (i < datasize) ; ++i) | |||
621 | { | |||
622 | // stretch the contrast | |||
623 | ||||
624 | float x = data[i]; | |||
625 | x = (x - min_src_val) / (max_src_val - min_src_val); | |||
626 | ||||
627 | if (x < 0.0) | |||
628 | { | |||
629 | x = 0.0; | |||
630 | } | |||
631 | ||||
632 | if (x > 1.0) | |||
633 | { | |||
634 | x = 1.0; | |||
635 | } | |||
636 | ||||
637 | data[i] = x; | |||
638 | } | |||
639 | } | |||
640 | ||||
641 | void LocalContrastFilter::rgb2hsv(const float& r, const float& g, const float& b, float& h, float& s, float& v) | |||
642 | { | |||
643 | float maxrg = (r > g) ? r : g; | |||
644 | float max = (maxrg > b) ? maxrg : b; | |||
645 | float minrg = (r < g) ? r : g; | |||
646 | float min = (minrg < b) ? minrg : b; | |||
647 | float delta = max - min; | |||
648 | ||||
649 | // hue | |||
650 | ||||
651 | if (min == max) | |||
652 | { | |||
653 | h = 0.0; | |||
654 | } | |||
655 | else | |||
656 | { | |||
657 | if (max == r) | |||
658 | { | |||
659 | h = (float)(fmod(60.0 * (g - b) / delta + 360.0, 360.0)); | |||
660 | } | |||
661 | else | |||
662 | { | |||
663 | if (max == g) | |||
664 | { | |||
665 | h = (float)(60.0 * (b - r) / delta + 120.0); | |||
666 | } | |||
667 | else | |||
668 | { | |||
669 | // max==b | |||
670 | h = (float)(60.0 * (r - g) / delta + 240.0); | |||
671 | } | |||
672 | } | |||
673 | } | |||
674 | ||||
675 | // saturation | |||
676 | ||||
677 | if (max < 1e-6) | |||
678 | { | |||
679 | s = 0; | |||
680 | } | |||
681 | else | |||
682 | { | |||
683 | s = (float)(1.0 - min / max); | |||
684 | } | |||
685 | ||||
686 | // value | |||
687 | ||||
688 | v = max; | |||
689 | } | |||
690 | ||||
691 | void LocalContrastFilter::hsv2rgb(const float& h, const float& s, const float& v, float& r, float& g, float& b) | |||
692 | { | |||
693 | float hfi = (float)(floor(h / 60.0)); | |||
694 | float f = (float)((h / 60.0) - hfi); | |||
695 | int hi = ((int)hfi) % 6; | |||
696 | float p = (float)(v * (1.0 - s)); | |||
697 | float q = (float)(v * (1.0 - f * s)); | |||
698 | float t = (float)(v * (1.0 - (1.0 - f) * s)); | |||
699 | ||||
700 | switch (hi) | |||
701 | { | |||
702 | case 0: | |||
703 | { | |||
704 | r = v; | |||
705 | g = t; | |||
706 | b = p; | |||
707 | break; | |||
708 | } | |||
709 | ||||
710 | case 1: | |||
711 | { | |||
712 | r = q; | |||
713 | g = v; | |||
714 | b = p; | |||
715 | break; | |||
716 | } | |||
717 | ||||
718 | case 2: | |||
719 | { | |||
720 | r = p; | |||
721 | g = v; | |||
722 | b = t; | |||
723 | break; | |||
724 | } | |||
725 | ||||
726 | case 3: | |||
727 | { | |||
728 | r = p; | |||
729 | g = q; | |||
730 | b = v; | |||
731 | break; | |||
732 | } | |||
733 | ||||
734 | case 4: | |||
735 | { | |||
736 | r = t; | |||
737 | g = p; | |||
738 | b = v; | |||
739 | break; | |||
740 | } | |||
741 | ||||
742 | case 5: | |||
743 | { | |||
744 | r = v; | |||
745 | g = p; | |||
746 | b = q; | |||
747 | break; | |||
748 | } | |||
749 | } | |||
750 | } | |||
751 | ||||
752 | FilterAction LocalContrastFilter::filterAction() | |||
753 | { | |||
754 | FilterAction action(FilterIdentifier(), CurrentVersion()); | |||
755 | action.setDisplayableName(DisplayableName()); | |||
756 | ||||
757 | action.addParameter(QLatin1String("functionId"), d->par.functionId); | |||
758 | action.addParameter(QLatin1String("highSaturation"), d->par.highSaturation); | |||
759 | action.addParameter(QLatin1String("lowSaturation"), d->par.lowSaturation); | |||
760 | action.addParameter(QLatin1String("stretchContrast"), d->par.stretchContrast); | |||
761 | ||||
762 | for (int nstage = 0 ; nstage < TONEMAPPING_MAX_STAGES4 ; ++nstage) | |||
763 | { | |||
764 | QString stage = QString::fromLatin1("stage[%1]:").arg(nstage); | |||
765 | action.addParameter(stage + QLatin1String("enabled"), d->par.stage[nstage].enabled); | |||
766 | ||||
767 | if (d->par.stage[nstage].enabled) | |||
768 | { | |||
769 | action.addParameter(stage + QLatin1String("power"), d->par.stage[nstage].power); | |||
770 | action.addParameter(stage + QLatin1String("blur"), d->par.stage[nstage].blur); | |||
771 | } | |||
772 | } | |||
773 | ||||
774 | action.addParameter(QLatin1String("randomSeed"), d->generator.currentSeed()); | |||
775 | ||||
776 | return action; | |||
777 | } | |||
778 | ||||
779 | void LocalContrastFilter::readParameters(const FilterAction& action) | |||
780 | { | |||
781 | d->par.functionId = action.parameter(QLatin1String("functionId")).toInt(); | |||
782 | d->par.highSaturation = action.parameter(QLatin1String("highSaturation")).toInt(); | |||
783 | d->par.lowSaturation = action.parameter(QLatin1String("lowSaturation")).toInt(); | |||
784 | d->par.stretchContrast = action.parameter(QLatin1String("stretchContrast")).toBool(); | |||
785 | ||||
786 | for (int nstage = 0 ; nstage < TONEMAPPING_MAX_STAGES4 ; ++nstage) | |||
787 | { | |||
788 | QString stage = QString::fromLatin1("stage[%1]:").arg(nstage); | |||
789 | d->par.stage[nstage].enabled = action.parameter(stage + QLatin1String("enabled")).toBool(); | |||
790 | ||||
791 | if (d->par.stage[nstage].enabled) | |||
792 | { | |||
793 | d->par.stage[nstage].power = action.parameter(stage + QLatin1String("power")).toFloat(); | |||
794 | d->par.stage[nstage].blur = action.parameter(stage + QLatin1String("blur")).toFloat(); | |||
795 | } | |||
796 | } | |||
797 | ||||
798 | d->generator.seed(action.parameter(QLatin1String("randomSeed")).toUInt()); | |||
799 | } | |||
800 | ||||
801 | } // namespace Digikam | |||
802 | ||||
803 | #include "moc_localcontrastfilter.cpp" |
1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #ifndef QSCOPEDPOINTER_H |
5 | #define QSCOPEDPOINTER_H |
6 | |
7 | #include <QtCore/qglobal.h> |
8 | |
9 | #include <stdlib.h> |
10 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | template <typename T> |
14 | struct QScopedPointerDeleter |
15 | { |
16 | static inline void cleanup(T *pointer) noexcept |
17 | { |
18 | // Enforce a complete type. |
19 | // If you get a compile error here, read the section on forward declared |
20 | // classes in the QScopedPointer documentation. |
21 | typedef char IsIncompleteType[ sizeof(T) ? 1 : -1 ]; |
22 | (void) sizeof(IsIncompleteType); |
23 | |
24 | delete pointer; |
25 | } |
26 | void operator()(T *pointer) const noexcept |
27 | { |
28 | cleanup(pointer); |
29 | } |
30 | }; |
31 | |
32 | template <typename T> |
33 | struct QScopedPointerArrayDeleter |
34 | { |
35 | static inline void cleanup(T *pointer) noexcept |
36 | { |
37 | // Enforce a complete type. |
38 | // If you get a compile error here, read the section on forward declared |
39 | // classes in the QScopedPointer documentation. |
40 | typedef char IsIncompleteType[ sizeof(T) ? 1 : -1 ]; |
41 | (void) sizeof(IsIncompleteType); |
42 | |
43 | delete[] pointer; |
44 | } |
45 | void operator()(T *pointer) const noexcept |
46 | { |
47 | cleanup(pointer); |
48 | } |
49 | }; |
50 | |
51 | struct QScopedPointerPodDeleter |
52 | { |
53 | static inline void cleanup(void *pointer) noexcept { free(pointer); } |
54 | void operator()(void *pointer) const noexcept { cleanup(pointer); } |
55 | }; |
56 | |
57 | #ifndef QT_NO_QOBJECT |
58 | template <typename T> |
59 | struct QScopedPointerObjectDeleteLater |
60 | { |
61 | static inline void cleanup(T *pointer) { if (pointer) pointer->deleteLater(); } |
62 | void operator()(T *pointer) const { cleanup(pointer); } |
63 | }; |
64 | |
65 | class QObject; |
66 | typedef QScopedPointerObjectDeleteLater<QObject> QScopedPointerDeleteLater; |
67 | #endif |
68 | |
69 | template <typename T, typename Cleanup = QScopedPointerDeleter<T> > |
70 | class QScopedPointer |
71 | { |
72 | public: |
73 | Q_NODISCARD_CTOR[[nodiscard]] |
74 | explicit QScopedPointer(T *p = nullptr) noexcept : d(p) |
75 | { |
76 | } |
77 | |
78 | inline ~QScopedPointer() |
79 | { |
80 | T *oldD = this->d; |
81 | Cleanup::cleanup(oldD); |
82 | } |
83 | |
84 | inline T &operator*() const |
85 | { |
86 | Q_ASSERT(d)((d) ? static_cast<void>(0) : qt_assert("d", "/opt/qt6/include/QtCore/qscopedpointer.h" , 86)); |
87 | return *d; |
88 | } |
89 | |
90 | T *operator->() const noexcept |
91 | { |
92 | return d; |
93 | } |
94 | |
95 | bool operator!() const noexcept |
96 | { |
97 | return !d; |
98 | } |
99 | |
100 | explicit operator bool() const |
101 | { |
102 | return !isNull(); |
103 | } |
104 | |
105 | T *data() const noexcept |
106 | { |
107 | return d; |
108 | } |
109 | |
110 | T *get() const noexcept |
111 | { |
112 | return d; |
113 | } |
114 | |
115 | bool isNull() const noexcept |
116 | { |
117 | return !d; |
118 | } |
119 | |
120 | void reset(T *other = nullptr) noexcept(noexcept(Cleanup::cleanup(std::declval<T *>()))) |
121 | { |
122 | if (d == other) |
123 | return; |
124 | T *oldD = std::exchange(d, other); |
125 | Cleanup::cleanup(oldD); |
126 | } |
127 | |
128 | #if QT_DEPRECATED_SINCE(6, 1)(((6<<16)|(1<<8)|(0)) > 0x050E00) |
129 | QT_DEPRECATED_VERSION_X_6_1("Use std::unique_ptr instead, and call release().") |
130 | T *take() noexcept |
131 | { |
132 | T *oldD = std::exchange(d, nullptr); |
133 | return oldD; |
134 | } |
135 | #endif |
136 | |
137 | #if QT_DEPRECATED_SINCE(6, 2)(((6<<16)|(2<<8)|(0)) > 0x050E00) |
138 | QT_DEPRECATED_VERSION_X_6_2("Use std::unique_ptr instead of QScopedPointer.") |
139 | void swap(QScopedPointer<T, Cleanup> &other) noexcept |
140 | { |
141 | qt_ptr_swap(d, other.d); |
142 | } |
143 | #endif |
144 | |
145 | typedef T *pointer; |
146 | |
147 | friend bool operator==(const QScopedPointer<T, Cleanup> &lhs, const QScopedPointer<T, Cleanup> &rhs) noexcept |
148 | { |
149 | return lhs.data() == rhs.data(); |
150 | } |
151 | |
152 | friend bool operator!=(const QScopedPointer<T, Cleanup> &lhs, const QScopedPointer<T, Cleanup> &rhs) noexcept |
153 | { |
154 | return lhs.data() != rhs.data(); |
155 | } |
156 | |
157 | friend bool operator==(const QScopedPointer<T, Cleanup> &lhs, std::nullptr_t) noexcept |
158 | { |
159 | return lhs.isNull(); |
160 | } |
161 | |
162 | friend bool operator==(std::nullptr_t, const QScopedPointer<T, Cleanup> &rhs) noexcept |
163 | { |
164 | return rhs.isNull(); |
165 | } |
166 | |
167 | friend bool operator!=(const QScopedPointer<T, Cleanup> &lhs, std::nullptr_t) noexcept |
168 | { |
169 | return !lhs.isNull(); |
170 | } |
171 | |
172 | friend bool operator!=(std::nullptr_t, const QScopedPointer<T, Cleanup> &rhs) noexcept |
173 | { |
174 | return !rhs.isNull(); |
175 | } |
176 | |
177 | #if QT_DEPRECATED_SINCE(6, 2)(((6<<16)|(2<<8)|(0)) > 0x050E00) |
178 | QT_DEPRECATED_VERSION_X_6_2("Use std::unique_ptr instead of QScopedPointer.") |
179 | friend void swap(QScopedPointer<T, Cleanup> &p1, QScopedPointer<T, Cleanup> &p2) noexcept |
180 | { p1.swap(p2); } |
181 | #endif |
182 | |
183 | protected: |
184 | T *d; |
185 | |
186 | private: |
187 | Q_DISABLE_COPY_MOVE(QScopedPointer)QScopedPointer(const QScopedPointer &) = delete; QScopedPointer &operator=(const QScopedPointer &) = delete; QScopedPointer (QScopedPointer &&) = delete; QScopedPointer &operator =(QScopedPointer &&) = delete; |
188 | }; |
189 | |
190 | template <typename T, typename Cleanup = QScopedPointerArrayDeleter<T> > |
191 | class QScopedArrayPointer : public QScopedPointer<T, Cleanup> |
192 | { |
193 | template <typename Ptr> |
194 | using if_same_type = typename std::enable_if<std::is_same<typename std::remove_cv<T>::type, Ptr>::value, bool>::type; |
195 | public: |
196 | Q_NODISCARD_CTOR[[nodiscard]] |
197 | inline QScopedArrayPointer() : QScopedPointer<T, Cleanup>(nullptr) {} |
198 | inline ~QScopedArrayPointer() = default; |
199 | |
200 | template <typename D, if_same_type<D> = true> |
201 | Q_NODISCARD_CTOR[[nodiscard]] |
202 | explicit QScopedArrayPointer(D *p) |
203 | : QScopedPointer<T, Cleanup>(p) |
204 | { |
205 | } |
206 | |
207 | T &operator[](qsizetype i) |
208 | { |
209 | return this->d[i]; |
210 | } |
211 | |
212 | const T &operator[](qsizetype i) const |
213 | { |
214 | return this->d[i]; |
215 | } |
216 | |
217 | #if QT_DEPRECATED_SINCE(6, 2)(((6<<16)|(2<<8)|(0)) > 0x050E00) |
218 | QT_DEPRECATED_VERSION_X_6_2("Use std::unique_ptr instead of QScopedArrayPointer.") |
219 | void swap(QScopedArrayPointer &other) noexcept // prevent QScopedPointer <->QScopedArrayPointer swaps |
220 | { QScopedPointer<T, Cleanup>::swap(other); } |
221 | #endif |
222 | |
223 | private: |
224 | explicit inline QScopedArrayPointer(void *) |
225 | { |
226 | // Enforce the same type. |
227 | |
228 | // If you get a compile error here, make sure you declare |
229 | // QScopedArrayPointer with the same template type as you pass to the |
230 | // constructor. See also the QScopedPointer documentation. |
231 | |
232 | // Storing a scalar array as a pointer to a different type is not |
233 | // allowed and results in undefined behavior. |
234 | } |
235 | |
236 | Q_DISABLE_COPY_MOVE(QScopedArrayPointer)QScopedArrayPointer(const QScopedArrayPointer &) = delete ; QScopedArrayPointer &operator=(const QScopedArrayPointer &) = delete; QScopedArrayPointer(QScopedArrayPointer && ) = delete; QScopedArrayPointer &operator=(QScopedArrayPointer &&) = delete; |
237 | }; |
238 | |
239 | #if QT_DEPRECATED_SINCE(6, 2)(((6<<16)|(2<<8)|(0)) > 0x050E00) |
240 | template <typename T, typename Cleanup> |
241 | QT_DEPRECATED_VERSION_X_6_2("Use std::unique_ptr instead of QScopedArrayPointer.") |
242 | inline void swap(QScopedArrayPointer<T, Cleanup> &lhs, QScopedArrayPointer<T, Cleanup> &rhs) noexcept |
243 | { lhs.swap(rhs); } |
244 | #endif |
245 | |
246 | QT_END_NAMESPACE |
247 | |
248 | #endif // QSCOPEDPOINTER_H |