File: | /home/gilles/Devel/8.x/core/libs/dimg/filters/lc/localcontrastfilter.cpp |
Warning: | line 567, column 31 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/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/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
| ||||
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 |