1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2005-05-25
7  * Description : Blur FX threaded image filter.
8  *
9  * Copyright 2005-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10  * Copyright 2006-2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
11  *
12  * Original Blur algorithms copyrighted 2004 by
13  * Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot com>.
14  *
15  * This program is free software; you can redistribute it
16  * and/or modify it under the terms of the GNU General
17  * Public License as published by the Free Software Foundation;
18  * either version 2, or (at your option)
19  * any later version.
20  *
21  * This program is distributed in the hope that it will be useful,
22  * but WITHOUT ANY WARRANTY; without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24  * GNU General Public License for more details.
25  *
26  * ============================================================ */
27 
28 #define ANGLE_RATIO  0.017453292519943295769236907685
29 
30 #include "blurfxfilter.h"
31 
32 // C++ includes
33 
34 #include <cstdlib>
35 #include <cstring>
36 
37 // Qt includes
38 
39 #include <QDateTime>
40 #include <QtConcurrent>    // krazy:exclude=includes
41 #include <QtMath>
42 
43 // KDE includes
44 
45 #include <klocalizedstring.h>
46 
47 // Local includes
48 
49 #include "dimg.h"
50 #include "blurfilter.h"
51 #include "randomnumbergenerator.h"
52 
53 namespace Digikam
54 {
55 
56 class Q_DECL_HIDDEN BlurFXFilter::Private
57 {
58 public:
59 
Private()60     explicit Private()
61       : blurFXType  (ZoomBlur),
62         distance    (100),
63         level       (45),
64         randomSeed  (RandomNumberGenerator::timeSeed())
65     {
66     }
67 
68     int     blurFXType;
69     int     distance;
70     int     level;
71     quint32 randomSeed;
72 };
73 
BlurFXFilter(QObject * const parent)74 BlurFXFilter::BlurFXFilter(QObject* const parent)
75     : DImgThreadedFilter(parent),
76       d                 (new Private)
77 {
78     initFilter();
79 }
80 
BlurFXFilter(DImg * const orgImage,QObject * const parent,int blurFXType,int distance,int level)81 BlurFXFilter::BlurFXFilter(DImg* const orgImage, QObject* const parent, int blurFXType, int distance, int level)
82     : DImgThreadedFilter(orgImage, parent, QLatin1String("BlurFX")),
83       d                 (new Private)
84 {
85     d->blurFXType = blurFXType;
86     d->distance   = distance;
87     d->level      = level;
88 
89     initFilter();
90 }
91 
~BlurFXFilter()92 BlurFXFilter::~BlurFXFilter()
93 {
94     cancelFilter();
95     delete d;
96 }
97 
DisplayableName()98 QString BlurFXFilter::DisplayableName()
99 {
100     return QString::fromUtf8(I18N_NOOP("Blur FX Filter"));
101 }
102 
filterImage()103 void BlurFXFilter::filterImage()
104 {
105     int w = m_orgImage.width();
106     int h = m_orgImage.height();
107 
108     switch (d->blurFXType)
109     {
110         case ZoomBlur:
111             zoomBlur(&m_orgImage, &m_destImage, w / 2, h / 2, d->distance);
112             break;
113 
114         case RadialBlur:
115             radialBlur(&m_orgImage, &m_destImage, w / 2, h / 2, d->distance);
116             break;
117 
118         case FarBlur:
119             farBlur(&m_orgImage, &m_destImage, d->distance);
120             break;
121 
122         case MotionBlur:
123             motionBlur(&m_orgImage, &m_destImage, d->distance, (double)d->level);
124             break;
125 
126         case SoftenerBlur:
127             softenerBlur(&m_orgImage, &m_destImage);
128             break;
129 
130         case ShakeBlur:
131             shakeBlur(&m_orgImage, &m_destImage, d->distance);
132             break;
133 
134         case FocusBlur:
135             focusBlur(&m_orgImage, &m_destImage, w / 2, h / 2, d->distance, d->level * 10);
136             break;
137 
138         case SmartBlur:
139             smartBlur(&m_orgImage, &m_destImage, d->distance, d->level);
140             break;
141 
142         case FrostGlass:
143             frostGlass(&m_orgImage, &m_destImage, d->distance);
144             break;
145 
146         case Mosaic:
147             mosaic(&m_orgImage, &m_destImage, d->distance, d->distance);
148             break;
149     }
150 }
151 
zoomBlurMultithreaded(const Args & prm)152 void BlurFXFilter::zoomBlurMultithreaded(const Args& prm)
153 {
154     int nh, nw;
155     int sumR, sumG, sumB, nCount=0;
156     double lfRadius, lfNewRadius, lfAngle;
157 
158     DColor color;
159     int offset;
160 
161     int Width       = prm.orgImage->width();
162     int Height      = prm.orgImage->height();
163     uchar* data     = prm.orgImage->bits();
164     bool sixteenBit = prm.orgImage->sixteenBit();
165     int bytesDepth  = prm.orgImage->bytesDepth();
166     uchar* pResBits = prm.destImage->bits();
167 
168     double lfRadMax = qSqrt(Height * Height + Width * Width);
169 
170     for (uint w = prm.start; runningFlag() && (w < prm.stop); ++w)
171     {
172         // ...we enter this loop to sum the bits
173 
174         // we initialize the variables
175         sumR = sumG = sumB = nCount = 0;
176 
177         nw = prm.X - w;
178         nh = prm.Y - prm.h;
179 
180         lfRadius    = qSqrt(nw * nw + nh * nh);
181         lfAngle     = qAtan2((double)nh, (double)nw);
182         lfNewRadius = (lfRadius * prm.Distance) / lfRadMax;
183 
184         for (int r = 0; runningFlag() && (r <= lfNewRadius); ++r)
185         {
186             // we need to calc the positions
187             nw = (int)(prm.X - (lfRadius - r) * cos(lfAngle));
188             nh = (int)(prm.Y - (lfRadius - r) * sin(lfAngle));
189 
190             if (IsInside(Width, Height, nw, nh))
191             {
192                 // read color
193                 offset = GetOffset(Width, nw, nh, bytesDepth);
194                 color.setColor(data + offset, sixteenBit);
195 
196                 // we sum the bits
197                 sumR += color.red();
198                 sumG += color.green();
199                 sumB += color.blue();
200                 ++nCount;
201             }
202         }
203 
204         if (nCount == 0)
205         {
206             nCount = 1;
207         }
208 
209         // calculate pointer
210         offset = GetOffset(Width, w, prm.h, bytesDepth);
211         // read color to preserve alpha
212         color.setColor(data + offset, sixteenBit);
213 
214         // now, we have to calc the arithmetic average
215         color.setRed(sumR   / nCount);
216         color.setGreen(sumG / nCount);
217         color.setBlue(sumB  / nCount);
218 
219         // write color to destination
220         color.setPixel(pResBits + offset);
221     }
222 }
223 
224 /* Function to apply the ZoomBlur effect backported from ImageProcessing version 2
225  *
226  * data             => The image data in RGBA mode.
227  * Width            => Width of image.
228  * Height           => Height of image.
229  * X, Y             => Center of zoom in the image
230  * Distance         => Distance value
231  * pArea            => Preview area.
232  *
233  * Theory           => Here we have a effect similar to RadialBlur mode Zoom from
234  *                     Photoshop. The theory is very similar to RadialBlur, but has one
235  *                     difference. Instead we use pixels with the same radius and
236  *                     near angles, we take pixels with the same angle but near radius
237  *                     This radius is always from the center to out of the image, we
238  *                     calc a proportional radius from the center.
239  */
zoomBlur(DImg * const orgImage,DImg * const destImage,int X,int Y,int Distance,const QRect & pArea)240 void BlurFXFilter::zoomBlur(DImg* const orgImage, DImg* const destImage, int X, int Y, int Distance, const QRect& pArea)
241 {
242     if (Distance <= 1)
243     {
244         return;
245     }
246 
247     int progress;
248 
249     // We working on full image.
250     int xMin = 0;
251     int xMax = orgImage->width();
252     int yMin = 0;
253     int yMax = orgImage->height();
254 
255     // If we working in preview mode, else we using the preview area.
256     if (pArea.isValid())
257     {
258         xMin = pArea.x();
259         xMax = pArea.x() + pArea.width();
260         yMin = pArea.y();
261         yMax = pArea.y() + pArea.height();
262     }
263 
264     QList<int> vals = multithreadedSteps(xMax, xMin);
265     QList <QFuture<void> > tasks;
266 
267     Args prm;
268     prm.orgImage  = orgImage;
269     prm.destImage = destImage;
270     prm.X         = X;
271     prm.Y         = Y;
272     prm.Distance  = Distance;
273 
274     // we have reached the main loop
275     for (int h = yMin; runningFlag() && (h < yMax); ++h)
276     {
277         for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
278         {
279             prm.start = vals[j];
280             prm.stop  = vals[j+1];
281             prm.h     = h;
282             tasks.append(QtConcurrent::run(this,
283                                            &BlurFXFilter::zoomBlurMultithreaded,
284                                            prm
285                                           ));
286         }
287 
288         foreach (QFuture<void> t, tasks)
289             t.waitForFinished();
290 
291         // Update the progress bar in dialog.
292         progress = (int)(((double)(h - yMin) * 100.0) / (yMax - yMin));
293 
294         if (progress % 5 == 0)
295         {
296             postProgress(progress);
297         }
298     }
299 }
300 
radialBlurMultithreaded(const Args & prm)301 void BlurFXFilter::radialBlurMultithreaded(const Args& prm)
302 {
303     int Width       = prm.orgImage->width();
304     int Height      = prm.orgImage->height();
305     uchar* data     = prm.orgImage->bits();
306     bool sixteenBit = prm.orgImage->sixteenBit();
307     int bytesDepth  = prm.orgImage->bytesDepth();
308     uchar* pResBits = prm.destImage->bits();
309 
310     int sumR, sumG, sumB, nw, nh;
311     double Radius, Angle, AngleRad;
312 
313     DColor color;
314     int offset;
315 
316     QScopedArrayPointer<double> nMultArray(new double[prm.Distance * 2 + 1]);
317 
318     for (int i = -prm.Distance; i <= prm.Distance; ++i)
319     {
320         nMultArray[i + prm.Distance] = i * ANGLE_RATIO;
321     }
322 
323     // number of added pixels
324     int nCount = 0;
325 
326     for (uint w = prm.start; runningFlag() && (w < prm.stop); ++w)
327     {
328         // ...we enter this loop to sum the bits
329 
330         // we initialize the variables
331         sumR = sumG = sumB = nCount = 0;
332 
333         nw = prm.X - w;
334         nh = prm.Y - prm.h;
335 
336         Radius   = qSqrt(nw * nw + nh * nh);
337         AngleRad = qAtan2((double)nh, (double)nw);
338 
339         for (int a = -prm.Distance; runningFlag() && (a <= prm.Distance); ++a)
340         {
341             Angle = AngleRad + nMultArray[a + prm.Distance];
342             // we need to calc the positions
343             nw = (int)(prm.X - Radius * qCos(Angle));
344             nh = (int)(prm.Y - Radius * qSin(Angle));
345 
346             if (IsInside(Width, Height, nw, nh))
347             {
348                 // read color
349                 offset = GetOffset(Width, nw, nh, bytesDepth);
350                 color.setColor(data + offset, sixteenBit);
351 
352                 // we sum the bits
353                 sumR += color.red();
354                 sumG += color.green();
355                 sumB += color.blue();
356                 ++nCount;
357             }
358         }
359 
360         if (nCount == 0)
361         {
362             nCount = 1;
363         }
364 
365         // calculate pointer
366         offset = GetOffset(Width, w, prm.h, bytesDepth);
367         // read color to preserve alpha
368         color.setColor(data + offset, sixteenBit);
369 
370         // now, we have to calc the arithmetic average
371         color.setRed(sumR   / nCount);
372         color.setGreen(sumG / nCount);
373         color.setBlue(sumB  / nCount);
374 
375         // write color to destination
376         color.setPixel(pResBits + offset);
377     }
378 }
379 
380 /* Function to apply the radialBlur effect backported from ImageProcessing version 2
381  *
382  * data             => The image data in RGBA mode.
383  * Width            => Width of image.
384  * Height           => Height of image.
385  * X, Y             => Center of radial in the image
386  * Distance         => Distance value
387  * pArea            => Preview area.
388  *
389  * Theory           => Similar to RadialBlur from Photoshop, its an amazing effect
390  *                     Very easy to understand but a little hard to implement.
391  *                     We have all the image and find the center pixel. Now, we analyze
392  *                     all the pixels and calc the radius from the center and find the
393  *                     angle. After this, we sum this pixel with others with the same
394  *                     radius, but different angles. Here I'm using degrees angles.
395  */
radialBlur(DImg * const orgImage,DImg * const destImage,int X,int Y,int Distance,const QRect & pArea)396 void BlurFXFilter::radialBlur(DImg* const orgImage, DImg* const destImage, int X, int Y, int Distance, const QRect& pArea)
397 {
398     if (Distance <= 1)
399     {
400         return;
401     }
402 
403     int progress;
404 
405     // We working on full image.
406     int xMin = 0;
407     int xMax = orgImage->width();
408     int yMin = 0;
409     int yMax = orgImage->height();
410 
411     // If we working in preview mode, else we using the preview area.
412     if (pArea.isValid())
413     {
414         xMin = pArea.x();
415         xMax = pArea.x() + pArea.width();
416         yMin = pArea.y();
417         yMax = pArea.y() + pArea.height();
418     }
419 
420     QList<int> vals = multithreadedSteps(xMax, xMin);
421     QList <QFuture<void> > tasks;
422 
423     Args prm;
424     prm.orgImage  = orgImage;
425     prm.destImage = destImage;
426     prm.X         = X;
427     prm.Y         = Y;
428     prm.Distance  = Distance;
429 
430     // we have reached the main loop
431 
432     for (int h = yMin; runningFlag() && (h < yMax); ++h)
433     {
434         for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
435         {
436             prm.start = vals[j];
437             prm.stop  = vals[j+1];
438             prm.h     = h;
439             tasks.append(QtConcurrent::run(this,
440                                            &BlurFXFilter::radialBlurMultithreaded,
441                                            prm
442                                           ));
443         }
444 
445         foreach (QFuture<void> t, tasks)
446             t.waitForFinished();
447 
448         // Update the progress bar in dialog.
449         progress = (int)(((double)(h - yMin) * 100.0) / (yMax - yMin));
450 
451         if (progress % 5 == 0)
452         {
453             postProgress(progress);
454         }
455     }
456 }
457 
458 /* Function to apply the farBlur effect backported from ImageProcessing version 2
459  *
460  * data             => The image data in RGBA mode.
461  * Width            => Width of image.
462  * Height           => Height of image.
463  * Distance         => Distance value
464  *
465  * Theory           => This is an interesting effect, the blur is applied in that
466  *                     way: (the value "1" means pixel to be used in a blur calc, ok?)
467  *                     e.g. With distance = 2
468  *                                            |1|1|1|1|1|
469  *                                            |1|0|0|0|1|
470  *                                            |1|0|C|0|1|
471  *                                            |1|0|0|0|1|
472  *                                            |1|1|1|1|1|
473  *                     We sum all the pixels with value = 1 and apply at the pixel with*
474  *                     the position "C".
475  */
farBlur(DImg * const orgImage,DImg * const destImage,int Distance)476 void BlurFXFilter::farBlur(DImg* const orgImage, DImg* const destImage, int Distance)
477 {
478     if (Distance < 1)
479     {
480         return;
481     }
482 
483     // we need to create our kernel
484     // e.g. distance = 3, so kernel={3 1 1 2 1 1 3}
485 
486     QScopedArrayPointer<int> nKern(new int[Distance * 2 + 1]);
487 
488     for (int i = 0; i < Distance * 2 + 1; ++i)
489     {
490         // the first element is 3
491         if (i == 0)
492         {
493             nKern[i] = 2;
494         }
495         // the center element is 2
496         else if (i == Distance)
497         {
498             nKern[i] = 3;
499         }
500         // the last element is 3
501         else if (i == Distance * 2)
502         {
503             nKern[i] = 3;
504         }
505         // all other elements will be 1
506         else
507         {
508             nKern[i] = 1;
509         }
510     }
511 
512     // now, we apply a convolution with kernel
513     MakeConvolution(orgImage, destImage, Distance, nKern.data());
514 }
515 
motionBlurMultithreaded(const Args & prm)516 void BlurFXFilter::motionBlurMultithreaded(const Args& prm)
517 {
518     int Width       = prm.orgImage->width();
519     int Height      = prm.orgImage->height();
520     uchar* data     = prm.orgImage->bits();
521     bool sixteenBit = prm.orgImage->sixteenBit();
522     int bytesDepth  = prm.orgImage->bytesDepth();
523     uchar* pResBits = prm.destImage->bits();
524     int nCount      = prm.nCount;
525 
526     DColor color;
527     int offset, sumR, sumG, sumB, nw, nh;
528 
529     for (uint w = prm.start; runningFlag() && (w < prm.stop); ++w)
530     {
531         // we initialize the variables
532         sumR = sumG = sumB = 0;
533 
534         // ...we enter this loop to sum the bits
535         for (int a = -prm.Distance; runningFlag() && (a <= prm.Distance); ++a)
536         {
537             // we need to calc the positions
538             nw = w     + prm.lpXArray[a + prm.Distance];
539             nh = prm.h + prm.lpYArray[a + prm.Distance];
540 
541             offset = GetOffsetAdjusted(Width, Height, nw, nh, bytesDepth);
542             color.setColor(data + offset, sixteenBit);
543 
544             // we sum the bits
545             sumR += color.red();
546             sumG += color.green();
547             sumB += color.blue();
548         }
549 
550         if (nCount == 0)
551         {
552             nCount = 1;
553         }
554 
555         // calculate pointer
556         offset = GetOffset(Width, w, prm.h, bytesDepth);
557         // read color to preserve alpha
558         color.setColor(data + offset, sixteenBit);
559 
560         // now, we have to calc the arithmetic average
561         color.setRed(sumR   / nCount);
562         color.setGreen(sumG / nCount);
563         color.setBlue(sumB  / nCount);
564 
565         // write color to destination
566         color.setPixel(pResBits + offset);
567     }
568 }
569 
570 /* Function to apply the motionBlur effect backported from ImageProcessing version 2
571  *
572  * data             => The image data in RGBA mode.
573  * Width            => Width of image.
574  * Height           => Height of image.
575  * Distance         => Distance value
576  * Angle            => Angle direction (degrees)
577  *
578  * Theory           => Similar to MotionBlur from Photoshop, the engine is very
579  *                     simple to understand, we take a pixel (duh!), with the angle we
580  *                     will taking near pixels. After this we blur (add and do a
581  *                     division).
582  */
motionBlur(DImg * const orgImage,DImg * const destImage,int Distance,double Angle)583 void BlurFXFilter::motionBlur(DImg* const orgImage, DImg* const destImage, int Distance, double Angle)
584 {
585     if (Distance == 0)
586     {
587         return;
588     }
589 
590     int progress;
591 
592     // we try to avoid division by 0 (zero)
593     if (Angle == 0.0)
594     {
595         Angle = 360.0;
596     }
597 
598     // we initialize cos and sin for a best performance
599     double nAngX = cos((2.0 * M_PI) / (360.0 / Angle));
600     double nAngY = sin((2.0 * M_PI) / (360.0 / Angle));
601 
602     // total of bits to be taken is given by this formula
603     int nCount = Distance * 2 + 1;
604 
605     // we will alloc size and calc the possible results
606     QScopedArrayPointer<int> lpXArray(new int[nCount]);
607     QScopedArrayPointer<int> lpYArray(new int[nCount]);
608 
609     for (int i = 0; i < nCount; ++i)
610     {
611         lpXArray[i] = lround((double)(i - Distance) * nAngX);
612         lpYArray[i] = lround((double)(i - Distance) * nAngY);
613     }
614 
615     QList<int> vals = multithreadedSteps(orgImage->width());
616     QList <QFuture<void> > tasks;
617 
618     Args prm;
619     prm.orgImage  = orgImage;
620     prm.destImage = destImage;
621     prm.Distance  = Distance;
622     prm.nCount    = nCount;
623     prm.lpXArray  = lpXArray.data();
624     prm.lpYArray  = lpYArray.data();
625 
626     // we have reached the main loop
627 
628     for (uint h = 0; runningFlag() && (h < orgImage->height()); ++h)
629     {
630         for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
631         {
632             prm.start = vals[j];
633             prm.stop  = vals[j+1];
634             prm.h     = h;
635             tasks.append(QtConcurrent::run(this,
636                                            &BlurFXFilter::motionBlurMultithreaded,
637                                            prm
638                                           ));
639         }
640 
641         foreach (QFuture<void> t, tasks)
642             t.waitForFinished();
643 
644         // Update the progress bar in dialog.
645         progress = (int)(((double)h * 100.0) / orgImage->height());
646 
647         if (progress % 5 == 0)
648         {
649             postProgress(progress);
650         }
651     }
652 }
653 
softenerBlurMultithreaded(const Args & prm)654 void BlurFXFilter::softenerBlurMultithreaded(const Args& prm)
655 {
656     int Width       = prm.orgImage->width();
657     int Height      = prm.orgImage->height();
658     uchar* data     = prm.orgImage->bits();
659     bool sixteenBit = prm.orgImage->sixteenBit();
660     int bytesDepth  = prm.orgImage->bytesDepth();
661     uchar* pResBits = prm.destImage->bits();
662 
663     int SomaR = 0, SomaG = 0, SomaB = 0;
664     int Gray;
665 
666     DColor color, colorSoma;
667     int offset, offsetSoma;
668 
669     int grayLimit = sixteenBit ? 32767 : 127;
670 
671     for (uint w = prm.start; runningFlag() && (w < prm.stop); ++w)
672     {
673         SomaR = SomaG = SomaB = 0;
674 
675         offset = GetOffset(Width, w, prm.h, bytesDepth);
676         color.setColor(data + offset, sixteenBit);
677 
678         Gray = (color.red() + color.green() + color.blue()) / 3;
679 
680         if (Gray > grayLimit)
681         {
682             // 7x7
683             for (int a = -3; runningFlag() && (a <= 3); ++a)
684             {
685                 for (int b = -3; runningFlag() && (b <= 3); ++b)
686                 {
687                     if ((((int)prm.h + a) < 0) || (((int)w + b) < 0))
688                     {
689                         offsetSoma = offset;
690                     }
691                     else
692                     {
693                         offsetSoma = GetOffset(Width, (w + Lim_Max(w, b, Width)),
694                                                (prm.h + Lim_Max(prm.h, a, Height)), bytesDepth);
695                     }
696 
697                     colorSoma.setColor(data + offsetSoma, sixteenBit);
698 
699                     SomaR += colorSoma.red();
700                     SomaG += colorSoma.green();
701                     SomaB += colorSoma.blue();
702                 }
703             }
704 
705             // 7*7 = 49
706             color.setRed(SomaR   / 49);
707             color.setGreen(SomaG / 49);
708             color.setBlue(SomaB  / 49);
709             color.setPixel(pResBits + offset);
710         }
711         else
712         {
713             // 3x3
714             for (int a = -1; runningFlag() && (a <= 1); ++a)
715             {
716                 for (int b = -1; runningFlag() && (b <= 1); ++b)
717                 {
718                     if ((((int)prm.h + a) < 0) || (((int)w + b) < 0))
719                     {
720                         offsetSoma = offset;
721                     }
722                     else
723                     {
724                         offsetSoma = GetOffset(Width, (w + Lim_Max(w, b, Width)),
725                                                (prm.h + Lim_Max(prm.h, a, Height)), bytesDepth);
726                     }
727 
728                     colorSoma.setColor(data + offsetSoma, sixteenBit);
729 
730                     SomaR += colorSoma.red();
731                     SomaG += colorSoma.green();
732                     SomaB += colorSoma.blue();
733                 }
734             }
735 
736             // 3*3 = 9
737             color.setRed(SomaR   / 9);
738             color.setGreen(SomaG / 9);
739             color.setBlue(SomaB  / 9);
740             color.setPixel(pResBits + offset);
741         }
742     }
743 }
744 
745 /* Function to apply the softenerBlur effect
746  *
747  * data             => The image data in RGBA mode.
748  * Width            => Width of image.
749  * Height           => Height of image.
750  *
751  * Theory           => An interesting blur-like function. In dark tones we apply a
752  *                     blur with 3x3 dimensions, in light tones, we apply a blur with
753  *                     5x5 dimensions. Easy, hun?
754  */
softenerBlur(DImg * const orgImage,DImg * const destImage)755 void BlurFXFilter::softenerBlur(DImg* const orgImage, DImg* const destImage)
756 {
757     int progress;
758 
759     QList<int> vals = multithreadedSteps(orgImage->width());
760     QList <QFuture<void> > tasks;
761 
762     Args prm;
763     prm.orgImage  = orgImage;
764     prm.destImage = destImage;
765 
766     // we have reached the main loop
767 
768     for (uint h = 0; runningFlag() && (h < orgImage->height()); ++h)
769     {
770         for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
771         {
772             prm.start = vals[j];
773             prm.stop  = vals[j+1];
774             prm.h     = h;
775             tasks.append(QtConcurrent::run(this,
776                                            &BlurFXFilter::softenerBlurMultithreaded,
777                                            prm
778                                           ));
779         }
780 
781         foreach (QFuture<void> t, tasks)
782             t.waitForFinished();
783 
784         // Update the progress bar in dialog.
785         progress = (int)(((double)h * 100.0) / orgImage->height());
786 
787         if (progress % 5 == 0)
788         {
789             postProgress(progress);
790         }
791     }
792 }
793 
shakeBlurStage1Multithreaded(const Args & prm)794 void BlurFXFilter::shakeBlurStage1Multithreaded(const Args& prm)
795 {
796     uint Width      = prm.orgImage->width();
797     uint Height     = prm.orgImage->height();
798     uchar* data     = prm.orgImage->bits();
799     bool sixteenBit = prm.orgImage->sixteenBit();
800     int bytesDepth  = prm.orgImage->bytesDepth();
801 
802     DColor color;
803     int offset, offsetLayer;
804     int nw, nh;
805 
806     for (uint w = prm.start; runningFlag() && (w < prm.stop); ++w)
807     {
808         offsetLayer = GetOffset(Width, w, prm.h, bytesDepth);
809 
810         nh = ((prm.h + prm.Distance) >= Height) ? Height - 1 : prm.h + prm.Distance;
811         offset = GetOffset(Width, w, nh, bytesDepth);
812         color.setColor(data + offset, sixteenBit);
813         color.setPixel(prm.layer1 + offsetLayer);
814 
815         nh = (((int)prm.h - prm.Distance) < 0) ? 0 : prm.h - prm.Distance;
816         offset = GetOffset(Width, w, nh, bytesDepth);
817         color.setColor(data + offset, sixteenBit);
818         color.setPixel(prm.layer2 + offsetLayer);
819 
820         nw = ((w + prm.Distance) >= Width) ? Width - 1 : w + prm.Distance;
821         offset = GetOffset(Width, nw, prm.h, bytesDepth);
822         color.setColor(data + offset, sixteenBit);
823         color.setPixel(prm.layer3 + offsetLayer);
824 
825         nw = (((int)w - prm.Distance) < 0) ? 0 : w - prm.Distance;
826         offset = GetOffset(Width, nw, prm.h, bytesDepth);
827         color.setColor(data + offset, sixteenBit);
828         color.setPixel(prm.layer4 + offsetLayer);
829     }
830 }
831 
shakeBlurStage2Multithreaded(const Args & prm)832 void BlurFXFilter::shakeBlurStage2Multithreaded(const Args& prm)
833 {
834     int Width       = prm.orgImage->width();
835     uchar* data     = prm.orgImage->bits();
836     bool sixteenBit = prm.orgImage->sixteenBit();
837     int bytesDepth  = prm.orgImage->bytesDepth();
838     uchar* pResBits = prm.destImage->bits();
839 
840     DColor color, color1, color2, color3, color4;
841     int offset;
842 
843     for (uint w = prm.start; runningFlag() && (w < prm.stop); ++w)
844     {
845         offset = GetOffset(Width, w, prm.h, bytesDepth);
846         // read original data to preserve alpha
847         color.setColor(data + offset, sixteenBit);
848         // read colors from all four layers
849         color1.setColor(prm.layer1 + offset, sixteenBit);
850         color2.setColor(prm.layer2 + offset, sixteenBit);
851         color3.setColor(prm.layer3 + offset, sixteenBit);
852         color4.setColor(prm.layer4 + offset, sixteenBit);
853 
854         // set color components of resulting color
855         color.setRed((color1.red()     + color2.red()   + color3.red()   + color4.red())   / 4);
856         color.setGreen((color1.green() + color2.green() + color3.green() + color4.green()) / 4);
857         color.setBlue((color1.blue()   + color2.blue()  + color3.blue()  + color4.blue())  / 4);
858 
859         color.setPixel(pResBits + offset);
860     }
861 }
862 
863 /* Function to apply the shake blur effect
864  *
865  * data             => The image data in RGBA mode.
866  * Width            => Width of image.
867  * Height           => Height of image.
868  * Distance         => Distance between layers (from origin)
869  *
870  * Theory           => Similar to Fragment effect from Photoshop. We create 4 layers
871  *                    each one has the same distance from the origin, but have
872  *                    different positions (top, button, left and right), with these 4
873  *                    layers, we join all the pixels.
874  */
shakeBlur(DImg * const orgImage,DImg * const destImage,int Distance)875 void BlurFXFilter::shakeBlur(DImg* const orgImage, DImg* const destImage, int Distance)
876 {
877     int progress;
878 
879     quint64 numBytes = orgImage->numBytes();
880     QScopedArrayPointer<uchar> layer1(new uchar[numBytes]);
881     QScopedArrayPointer<uchar> layer2(new uchar[numBytes]);
882     QScopedArrayPointer<uchar> layer3(new uchar[numBytes]);
883     QScopedArrayPointer<uchar> layer4(new uchar[numBytes]);
884 
885     QList<int> vals = multithreadedSteps(orgImage->width());
886     QList <QFuture<void> > tasks;
887 
888     Args prm;
889     prm.orgImage  = orgImage;
890     prm.destImage = destImage;
891     prm.Distance  = Distance;
892     prm.layer1    = layer1.data();
893     prm.layer2    = layer2.data();
894     prm.layer3    = layer3.data();
895     prm.layer4    = layer4.data();
896 
897     // we have reached the main loop
898 
899     for (uint h = 0; runningFlag() && (h < orgImage->height()); ++h)
900     {
901         for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
902         {
903             prm.start = vals[j];
904             prm.stop  = vals[j+1];
905             prm.h     = h;
906             tasks.append(QtConcurrent::run(this,
907                                            &BlurFXFilter::shakeBlurStage1Multithreaded,
908                                            prm
909                                           ));
910         }
911 
912         foreach (QFuture<void> t, tasks)
913             t.waitForFinished();
914 
915         // Update the progress bar in dialog.
916         progress = (int)(((double)h * 50.0) / orgImage->height());
917 
918         if (progress % 5 == 0)
919         {
920             postProgress(progress);
921         }
922     }
923 
924     tasks.clear();
925 
926     for (uint h = 0; runningFlag() && (h < orgImage->height()); ++h)
927     {
928         for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
929         {
930             prm.start = vals[j];
931             prm.stop  = vals[j+1];
932             prm.h     = h;
933             tasks.append(QtConcurrent::run(this,
934                                            &BlurFXFilter::shakeBlurStage2Multithreaded,
935                                            prm
936                                           ));
937         }
938 
939         foreach (QFuture<void> t, tasks)
940             t.waitForFinished();
941 
942         // Update the progress bar in dialog.
943         progress = (int)(50.0 + ((double)h * 50.0) / orgImage->height());
944 
945         if (progress % 5 == 0)
946         {
947             postProgress(progress);
948         }
949     }
950 }
951 
focusBlurMultithreaded(const Args & prm)952 void BlurFXFilter::focusBlurMultithreaded(const Args& prm)
953 {
954     int    nBlendFactor;
955     double lfRadius;
956     int    offset;
957 
958     DColor colorOrgImage, colorBlurredImage;
959     int    alpha;
960     uchar* ptr = nullptr;
961 
962     // get composer for default blending
963     DColorComposer* const composer = DColorComposer::getComposer(DColorComposer::PorterDuffNone);
964 
965     int Width       = prm.orgImage->width();
966     uchar* data     = prm.orgImage->bits();
967     bool sixteenBit = prm.orgImage->sixteenBit();
968     int bytesDepth  = prm.orgImage->bytesDepth();
969     uchar* pResBits = prm.destImage->bits();
970 
971     int nw = 0;
972     int nh = prm.Y - prm.h;
973 
974     for (uint w = prm.start; runningFlag() && (w < prm.stop); ++w)
975     {
976         nw = prm.X - w;
977 
978         lfRadius = qSqrt(nh * nh + nw * nw);
979 
980         if (sixteenBit)
981         {
982             nBlendFactor = CLAMP065535((int)(65535.0 * lfRadius / (double)prm.BlendRadius));
983         }
984         else
985         {
986             nBlendFactor = (uchar)CLAMP0255((int)(255.0 * lfRadius / (double)prm.BlendRadius));
987         }
988 
989         // Read color values
990         offset = GetOffset(Width, w, prm.h, bytesDepth);
991         ptr    = pResBits + offset;
992         colorOrgImage.setColor(data + offset, sixteenBit);
993         colorBlurredImage.setColor(ptr, sixteenBit);
994 
995         // Preserve alpha
996         alpha = colorOrgImage.alpha();
997 
998         // In normal mode, the image is focused in the middle
999         // and less focused towards the border.
1000         // In inverse mode, the image is more focused towards the edge
1001         // and less focused in the middle.
1002         // This is achieved by swapping src and dest while blending.
1003         if (prm.bInversed)
1004         {
1005             // set blending alpha value as src alpha. Original value is stored above.
1006             colorOrgImage.setAlpha(nBlendFactor);
1007             // compose colors, writing to dest - colorBlurredImage
1008             composer->compose(colorBlurredImage, colorOrgImage);
1009             // restore alpha
1010             colorBlurredImage.setAlpha(alpha);
1011             // write color to destination
1012             colorBlurredImage.setPixel(ptr);
1013         }
1014         else
1015         {
1016             // set blending alpha value as src alpha. Original value is stored above.
1017             colorBlurredImage.setAlpha(nBlendFactor);
1018             // compose colors, writing to dest - colorOrgImage
1019             composer->compose(colorOrgImage, colorBlurredImage);
1020             // restore alpha
1021             colorOrgImage.setAlpha(alpha);
1022             // write color to destination
1023             colorOrgImage.setPixel(ptr);
1024         }
1025     }
1026 
1027     delete composer;
1028 }
1029 
1030 /* Function to apply the focusBlur effect backported from ImageProcessing version 2
1031  *
1032  * data             => The image data in RGBA mode.
1033  * Width            => Width of image.
1034  * Height           => Height of image.
1035  * BlurRadius       => Radius of blurred image.
1036  * BlendRadius      => Radius of blending effect.
1037  * bInversed        => If true, invert focus effect.
1038  * pArea            => Preview area.
1039  *
1040  */
focusBlur(DImg * const orgImage,DImg * const destImage,int X,int Y,int BlurRadius,int BlendRadius,bool bInversed,const QRect & pArea)1041 void BlurFXFilter::focusBlur(DImg* const orgImage, DImg* const destImage,
1042                              int X, int Y, int BlurRadius, int BlendRadius,
1043                              bool bInversed, const QRect& pArea)
1044 {
1045     int progress;
1046 
1047     // We working on full image.
1048 
1049     int xMin = 0;
1050     int xMax = orgImage->width();
1051     int yMin = 0;
1052     int yMax = orgImage->height();
1053 
1054     // If we working in preview mode, else we using the preview area.
1055 
1056     if (pArea.isValid())
1057     {
1058         xMin = pArea.x();
1059         xMax = pArea.x() + pArea.width();
1060         yMin = pArea.y();
1061         yMax = pArea.y() + pArea.height();
1062 
1063         // We do not have access to the loop of the Gaussian blur,
1064         // so we have to cut the image that we run the effect on.
1065 
1066         int xMinBlur = xMin - BlurRadius;
1067         int xMaxBlur = xMax + BlurRadius;
1068         int yMinBlur = yMin - BlurRadius;
1069         int yMaxBlur = yMax + BlurRadius;
1070         DImg areaImage = orgImage->copy(xMinBlur, yMaxBlur, xMaxBlur - xMinBlur, yMaxBlur - yMinBlur);
1071 
1072         // cppcheck-suppress unusedScopedObject
1073         BlurFilter(this, *orgImage, *destImage, 10, 75, BlurRadius);
1074 
1075         // I am unsure about differences of 1 pixel
1076 
1077         destImage->bitBltImage(&areaImage, xMinBlur, yMinBlur);
1078         destImage->bitBltImage(orgImage, 0, 0, orgImage->width(), yMinBlur, 0, 0);
1079         destImage->bitBltImage(orgImage, 0, yMinBlur, xMinBlur, yMaxBlur - yMinBlur, 0, yMinBlur);
1080         destImage->bitBltImage(orgImage, xMaxBlur + 1, yMinBlur, orgImage->width() - xMaxBlur - 1, yMaxBlur - yMinBlur, yMaxBlur, yMinBlur);
1081         destImage->bitBltImage(orgImage, 0, yMaxBlur + 1, orgImage->width(), orgImage->height() - yMaxBlur - 1, 0, yMaxBlur);
1082 
1083         postProgress(80);
1084     }
1085     else
1086     {
1087         // copy bits for blurring
1088 
1089         memcpy(destImage->bits(), orgImage->bits(), orgImage->numBytes());
1090 
1091         // Gaussian blur using the BlurRadius parameter.
1092 
1093         BlurFilter(this, *orgImage, *destImage, 10, 80, BlurRadius);
1094     }
1095 
1096     // Blending results.
1097 
1098     QList<int> vals = multithreadedSteps(xMax, xMin);
1099     QList <QFuture<void> > tasks;
1100 
1101     Args prm;
1102     prm.orgImage    = orgImage;
1103     prm.destImage   = destImage;
1104     prm.X           = X;
1105     prm.Y           = Y;
1106     prm.BlendRadius = BlendRadius;
1107     prm.bInversed   = bInversed;
1108 
1109     // we have reached the main loop
1110 
1111     for (int h = yMin ; runningFlag() && (h < yMax) ; ++h)
1112     {
1113         for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
1114         {
1115             prm.start = vals[j];
1116             prm.stop  = vals[j+1];
1117             prm.h     = h;
1118             tasks.append(QtConcurrent::run(this,
1119                                            &BlurFXFilter::focusBlurMultithreaded,
1120                                            prm
1121                                           ));
1122         }
1123 
1124         foreach (QFuture<void> t, tasks)
1125         {
1126             t.waitForFinished();
1127         }
1128 
1129         // Update the progress bar in dialog.
1130 
1131         progress = (int)(80.0 + ((double)(h - yMin) * 20.0) / (yMax - yMin));
1132 
1133         if (progress % 5 == 0)
1134         {
1135             postProgress(progress);
1136         }
1137     }
1138 }
1139 
smartBlurStage1Multithreaded(const Args & prm)1140 void BlurFXFilter::smartBlurStage1Multithreaded(const Args& prm)
1141 {
1142     int Width       = prm.orgImage->width();
1143     int Height      = prm.orgImage->height();
1144     uchar* data     = prm.orgImage->bits();
1145     bool sixteenBit = prm.orgImage->sixteenBit();
1146     int bytesDepth  = prm.orgImage->bytesDepth();
1147 
1148     int sumR, sumG, sumB, nCount;
1149     DColor color, radiusColor, radiusColorBlur;
1150     int offset, loopOffset;
1151 
1152     for (uint w = prm.start ; runningFlag() && (w < prm.stop) ; ++w)
1153     {
1154         // we initialize the variables
1155         sumR = sumG = sumB = nCount = 0;
1156 
1157         // read color
1158         offset = GetOffset(Width, w, prm.h, bytesDepth);
1159         color.setColor(data + offset, sixteenBit);
1160 
1161         // ...we enter this loop to sum the bits
1162         for (int a = -prm.Radius ; runningFlag() && (a <= prm.Radius) ; ++a)
1163         {
1164             // verify if is inside the rect
1165             if (IsInside(Width, Height, w + a, prm.h))
1166             {
1167                 // read color
1168                 loopOffset = GetOffset(Width, w + a, prm.h, bytesDepth);
1169                 radiusColor.setColor(data + loopOffset, sixteenBit);
1170 
1171                 // now, we have to check if is inside the sensibility filter
1172                 if (IsColorInsideTheRange(color.red(), color.green(), color.blue(),
1173                                           radiusColor.red(), radiusColor.green(), radiusColor.blue(),
1174                                           prm.StrengthRange))
1175                 {
1176                     // finally we sum the bits
1177                     sumR += radiusColor.red();
1178                     sumG += radiusColor.green();
1179                     sumB += radiusColor.blue();
1180                 }
1181                 else
1182                 {
1183                     // finally we sum the bits
1184                     sumR += color.red();
1185                     sumG += color.green();
1186                     sumB += color.blue();
1187                 }
1188 
1189                 // increment counter
1190                 ++nCount;
1191             }
1192         }
1193 
1194         if (nCount == 0)
1195         {
1196             nCount = 1;
1197         }
1198 
1199         // now, we have to calc the arithmetic average
1200         color.setRed(sumR   / nCount);
1201         color.setGreen(sumG / nCount);
1202         color.setBlue(sumB  / nCount);
1203 
1204         // write color to destination
1205         color.setPixel(prm.pBlur + offset);
1206     }
1207 }
1208 
smartBlurStage2Multithreaded(const Args & prm)1209 void BlurFXFilter::smartBlurStage2Multithreaded(const Args& prm)
1210 {
1211     int Width       = prm.orgImage->width();
1212     int Height      = prm.orgImage->height();
1213     uchar* data     = prm.orgImage->bits();
1214     bool sixteenBit = prm.orgImage->sixteenBit();
1215     int bytesDepth  = prm.orgImage->bytesDepth();
1216     uchar* pResBits = prm.destImage->bits();
1217 
1218     int sumR, sumG, sumB, nCount;
1219     DColor color, radiusColor, radiusColorBlur;
1220     int offset, loopOffset;
1221 
1222     for (uint h = prm.start ; runningFlag() && (h < prm.stop) ; ++h)
1223     {
1224         // we initialize the variables
1225         sumR = sumG = sumB = nCount = 0;
1226 
1227         // read color
1228         offset = GetOffset(Width, prm.w, h, bytesDepth);
1229         color.setColor(data + offset, sixteenBit);
1230 
1231         // ...we enter this loop to sum the bits
1232         for (int a = -prm.Radius ; runningFlag() && (a <= prm.Radius) ; ++a)
1233         {
1234             // verify if is inside the rect
1235             if (IsInside(Width, Height, prm.w, h + a))
1236             {
1237                 // read color
1238                 loopOffset = GetOffset(Width, prm.w, h + a, bytesDepth);
1239                 radiusColor.setColor(data + loopOffset, sixteenBit);
1240 
1241                 // now, we have to check if is inside the sensibility filter
1242                 if (IsColorInsideTheRange(color.red(), color.green(), color.blue(),
1243                                           radiusColor.red(), radiusColor.green(), radiusColor.blue(),
1244                                           prm.StrengthRange))
1245                 {
1246                     radiusColorBlur.setColor(prm.pBlur + loopOffset, sixteenBit);
1247                     // finally we sum the bits
1248                     sumR += radiusColorBlur.red();
1249                     sumG += radiusColorBlur.green();
1250                     sumB += radiusColorBlur.blue();
1251                 }
1252                 else
1253                 {
1254                     // finally we sum the bits
1255                     sumR += color.red();
1256                     sumG += color.green();
1257                     sumB += color.blue();
1258                 }
1259 
1260                 // increment counter
1261                 ++nCount;
1262             }
1263         }
1264 
1265         if (nCount == 0)
1266         {
1267             nCount = 1;
1268         }
1269 
1270         // now, we have to calc the arithmetic average
1271         color.setRed(sumR   / nCount);
1272         color.setGreen(sumG / nCount);
1273         color.setBlue(sumB  / nCount);
1274 
1275         // write color to destination
1276         color.setPixel(pResBits + offset);
1277     }
1278 }
1279 
1280 /* Function to apply the SmartBlur effect
1281  *
1282  * data             => The image data in RGBA mode.
1283  * Width            => Width of image.
1284  * Height           => Height of image.
1285  * Radius           => blur matrix radius.
1286  * Strength         => Color strength.
1287  *
1288  * Theory           => Similar to SmartBlur from Photoshop, this function has the
1289  *                     same engine as Blur function, but, in a matrix with n
1290  *                     dimensions, we take only colors that pass by sensibility filter
1291  *                     The result is a clean image, not totally blurred, but a image
1292  *                     with correction between pixels.
1293  */
1294 
smartBlur(DImg * const orgImage,DImg * const destImage,int Radius,int Strength)1295 void BlurFXFilter::smartBlur(DImg* const orgImage, DImg* const destImage, int Radius, int Strength)
1296 {
1297     if (Radius <= 0)
1298     {
1299         return;
1300     }
1301 
1302     int progress;
1303     int StrengthRange = Strength;
1304 
1305     if (orgImage->sixteenBit())
1306     {
1307         StrengthRange = (StrengthRange + 1) * 256 - 1;
1308     }
1309 
1310     QScopedArrayPointer<uchar> pBlur(new uchar[orgImage->numBytes()]);
1311 
1312     // We need to copy our bits to blur bits
1313 
1314     memcpy(pBlur.data(), orgImage->bits(), orgImage->numBytes());
1315 
1316     QList<int> valsw = multithreadedSteps(orgImage->width());
1317     QList<int> valsh = multithreadedSteps(orgImage->height());
1318     QList <QFuture<void> > tasks;
1319 
1320     Args prm;
1321     prm.orgImage      = orgImage;
1322     prm.destImage     = destImage;
1323     prm.StrengthRange = StrengthRange;
1324     prm.pBlur         = pBlur.data();
1325     prm.Radius        = Radius;
1326 
1327     // we have reached the main loop
1328 
1329     for (uint h = 0 ; runningFlag() && (h < orgImage->height()) ; ++h)
1330     {
1331         for (int j = 0 ; runningFlag() && (j < valsw.count()-1) ; ++j)
1332         {
1333             prm.start = valsw[j];
1334             prm.stop  = valsw[j+1];
1335             prm.h     = h;
1336             tasks.append(QtConcurrent::run(this,
1337                                            &BlurFXFilter::smartBlurStage1Multithreaded,
1338                                            prm
1339                                           ));
1340         }
1341 
1342         foreach (QFuture<void> t, tasks)
1343             t.waitForFinished();
1344 
1345         // Update the progress bar in dialog.
1346         progress = (int)(((double)h * 50.0) / orgImage->height());
1347 
1348         if (progress % 5 == 0)
1349         {
1350             postProgress(progress);
1351         }
1352     }
1353 
1354     // we have reached the second part of main loop
1355 
1356     tasks.clear();
1357 
1358     for (uint w = 0 ; runningFlag() && (w < orgImage->width()) ; ++w)
1359     {
1360         for (int j = 0 ; runningFlag() && (j < valsh.count()-1) ; ++j)
1361         {
1362             prm.start = valsh[j];
1363             prm.stop  = valsh[j+1];
1364             prm.w     = w;
1365             tasks.append(QtConcurrent::run(this,
1366                                            &BlurFXFilter::smartBlurStage2Multithreaded,
1367                                            prm
1368                                           ));
1369         }
1370 
1371         foreach (QFuture<void> t, tasks)
1372             t.waitForFinished();
1373 
1374         // Update the progress bar in dialog.
1375         progress = (int)(50.0 + ((double)w * 50.0) / orgImage->width());
1376 
1377         if (progress % 5 == 0)
1378         {
1379             postProgress(progress);
1380         }
1381     }
1382 }
1383 
1384 // NOTE: there is no gain to parallelize this method due to non re-entrancy of RandomColor()
1385 //       (dixit RandomNumberGenerator which non re-entrant - Boost lib problem).
1386 
1387 /* Function to apply the frostGlass effect
1388  *
1389  * data             => The image data in RGBA mode.
1390  * Width            => Width of image.
1391  * Height           => Height of image.
1392  * Frost            => Frost value
1393  *
1394  * Theory           => Similar to Diffuse effect, but the random byte is defined
1395  *                     in a matrix. Diffuse uses a random diagonal byte.
1396  */
frostGlass(DImg * const orgImage,DImg * const destImage,int Frost)1397 void BlurFXFilter::frostGlass(DImg* const orgImage, DImg* const destImage, int Frost)
1398 {
1399     int progress;
1400 
1401     int Width       = orgImage->width();
1402     int Height      = orgImage->height();
1403     uchar* data     = orgImage->bits();
1404     bool sixteenBit = orgImage->sixteenBit();
1405     int bytesDepth  = orgImage->bytesDepth();
1406     uchar* pResBits = destImage->bits();
1407 
1408     Frost = (Frost < 1) ? 1 : (Frost > 10) ? 10 : Frost;
1409 
1410     int h, w;
1411 
1412     DColor color;
1413     int offset;
1414 
1415     // Randomize.
1416     RandomNumberGenerator generator;
1417     generator.seed(d->randomSeed);
1418 
1419     int range = sixteenBit ? 65535 : 255;
1420 
1421     // it is a huge optimization to allocate these here once
1422     QScopedArrayPointer<uchar> IntensityCount(new uchar[range + 1]);
1423     QScopedArrayPointer<uint> AverageColorR(new uint[range + 1]);
1424     QScopedArrayPointer<uint> AverageColorG(new uint[range + 1]);
1425     QScopedArrayPointer<uint> AverageColorB(new uint[range + 1]);
1426 
1427     for (h = 0; runningFlag() && (h < Height); ++h)
1428     {
1429         for (w = 0; runningFlag() && (w < Width); ++w)
1430         {
1431             offset = GetOffset(Width, w, h, bytesDepth);
1432             // read color to preserve alpha
1433             color.setColor(data + offset, sixteenBit);
1434 
1435             // get random color from surrounding of w|h
1436             color = RandomColor(data, Width, Height, sixteenBit, bytesDepth,
1437                                 w, h, Frost, color.alpha(), generator, range, IntensityCount.data(),
1438                                 AverageColorR.data(), AverageColorG.data(), AverageColorB.data());
1439 
1440             // write color to destination
1441             color.setPixel(pResBits + offset);
1442         }
1443 
1444         // Update the progress bar in dialog.
1445         progress = (int)(((double)h * 100.0) / Height);
1446 
1447         if (progress % 5 == 0)
1448         {
1449             postProgress(progress);
1450         }
1451     }
1452 }
1453 
mosaicMultithreaded(const Args & prm)1454 void BlurFXFilter::mosaicMultithreaded(const Args& prm)
1455 {
1456     int Width       = prm.orgImage->width();
1457     int Height      = prm.orgImage->height();
1458     uchar* data     = prm.orgImage->bits();
1459     bool sixteenBit = prm.orgImage->sixteenBit();
1460     int bytesDepth  = prm.orgImage->bytesDepth();
1461     uchar* pResBits = prm.destImage->bits();
1462 
1463     DColor color;
1464     int offsetCenter, offset;
1465 
1466     for (uint w = prm.start; runningFlag() && (w < prm.stop); w += prm.SizeW)
1467     {
1468         // we have to find the center pixel for mosaic's rectangle
1469 
1470         offsetCenter = GetOffsetAdjusted(Width, Height, w + (prm.SizeW / 2), prm.h + (prm.SizeH / 2), bytesDepth);
1471         color.setColor(data + offsetCenter, sixteenBit);
1472 
1473         // now, we fill the mosaic's rectangle with the center pixel color
1474 
1475         for (uint subw = w; runningFlag() && (subw <= w + prm.SizeW); ++subw)
1476         {
1477             for (uint subh = prm.h; runningFlag() && (subh <= prm.h + prm.SizeH); ++subh)
1478             {
1479                 // if is inside...
1480                 if (IsInside(Width, Height, subw, subh))
1481                 {
1482                     // set color
1483                     offset = GetOffset(Width, subw, subh, bytesDepth);
1484                     color.setPixel(pResBits + offset);
1485                 }
1486             }
1487         }
1488     }
1489 }
1490 
1491 /* Function to apply the mosaic effect backported from ImageProcessing version 2
1492  *
1493  * data             => The image data in RGBA mode.
1494  * Width            => Width of image.
1495  * Height           => Height of image.
1496  * Size             => Size of mosaic.
1497  *
1498  * Theory           => Ok, you can find some mosaic effects on PSC, but this one
1499  *                     has a great feature, if you see a mosaic in other code you will
1500  *                     see that the corner pixel doesn't change. The explanation is
1501  *                     simple, the color of the mosaic is the same as the first pixel
1502  *                     get. Here, the color of the mosaic is the same as the mosaic
1503  *                     center pixel.
1504  *                     Now the function scan the rows from the top (like photoshop).
1505  */
mosaic(DImg * const orgImage,DImg * const destImage,int SizeW,int SizeH)1506 void BlurFXFilter::mosaic(DImg* const orgImage, DImg* const destImage, int SizeW, int SizeH)
1507 {
1508     int progress;
1509 
1510     // we need to check for valid values
1511     if (SizeW < 1)
1512     {
1513         SizeW = 1;
1514     }
1515 
1516     if (SizeH < 1)
1517     {
1518         SizeH = 1;
1519     }
1520 
1521     if ((SizeW == 1) && (SizeH == 1))
1522     {
1523         return;
1524     }
1525 
1526     QList<int> vals = multithreadedSteps(orgImage->width());
1527     QList <QFuture<void> > tasks;
1528 
1529     Args prm;
1530     prm.orgImage  = orgImage;
1531     prm.destImage = destImage;
1532     prm.SizeW     = SizeW;
1533     prm.SizeH     = SizeH;
1534 
1535     // this loop will never look for transparent colors
1536 
1537     for (uint h = 0; runningFlag() && (h < orgImage->height()); h += SizeH)
1538     {
1539         for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
1540         {
1541             prm.start = vals[j];
1542             prm.stop  = vals[j+1];
1543             prm.h     = h;
1544             tasks.append(QtConcurrent::run(this,
1545                                            &BlurFXFilter::mosaicMultithreaded,
1546                                            prm
1547                                           ));
1548         }
1549 
1550         foreach (QFuture<void> t, tasks)
1551             t.waitForFinished();
1552 
1553         // Update the progress bar in dialog.
1554         progress = (int)(((double)h * 100.0) / orgImage->height());
1555 
1556         if (progress % 5 == 0)
1557         {
1558             postProgress(progress);
1559         }
1560     }
1561 }
1562 
1563 /* Function to get a color in a matrix with a determined size
1564  *
1565  * Bits              => Bits array
1566  * Width             => Image width
1567  * Height            => Image height
1568  * X                 => Position horizontal
1569  * Y                 => Position vertical
1570  * Radius            => The radius of the matrix to be created
1571  *
1572  * Theory            => This function takes from a distinct matrix a random color
1573  */
RandomColor(uchar * const Bits,int Width,int Height,bool sixteenBit,int bytesDepth,int X,int Y,int Radius,int alpha,RandomNumberGenerator & generator,int range,uchar * const IntensityCount,uint * const AverageColorR,uint * const AverageColorG,uint * const AverageColorB)1574 DColor BlurFXFilter::RandomColor(uchar* const Bits, int Width, int Height, bool sixteenBit, int bytesDepth,
1575                                  int X, int Y, int Radius,
1576                                  int alpha, RandomNumberGenerator& generator, int range, uchar* const IntensityCount,
1577                                  uint* const AverageColorR, uint* const AverageColorG, uint* const AverageColorB)
1578 {
1579     DColor color;
1580     int offset;
1581 
1582     int w, h, counter = 0;
1583 
1584     int I;
1585 
1586     // For 16 bit we have a problem here because this takes 255 times longer,
1587     // and the algorithm is really slow for 16 bit, but I think this cannot be avoided.
1588     memset(IntensityCount, 0, (range + 1) * sizeof(uchar));
1589     memset(AverageColorR,  0, (range + 1) * sizeof(uint));
1590     memset(AverageColorG,  0, (range + 1) * sizeof(uint));
1591     memset(AverageColorB,  0, (range + 1) * sizeof(uint));
1592 
1593     for (w = X - Radius; runningFlag() && (w <= X + Radius); ++w)
1594     {
1595         for (h = Y - Radius; runningFlag() && (h <= Y + Radius); ++h)
1596         {
1597             if ((w >= 0) && (w < Width) && (h >= 0) && (h < Height))
1598             {
1599                 offset = GetOffset(Width, w, h, bytesDepth);
1600                 color.setColor(Bits + offset, sixteenBit);
1601                 I = GetIntensity(color.red(), color.green(), color.blue());
1602                 IntensityCount[I]++;
1603                 ++counter;
1604 
1605                 if (IntensityCount[I] == 1)
1606                 {
1607                     AverageColorR[I] = color.red();
1608                     AverageColorG[I] = color.green();
1609                     AverageColorB[I] = color.blue();
1610                 }
1611                 else
1612                 {
1613                     AverageColorR[I] += color.red();
1614                     AverageColorG[I] += color.green();
1615                     AverageColorB[I] += color.blue();
1616                 }
1617             }
1618         }
1619     }
1620 
1621     // check for runningFlag here before entering the do loop (will crash with SIGFPE otherwise)
1622     if (!runningFlag())
1623     {
1624         return DColor(0, 0, 0, 0, sixteenBit);
1625     }
1626 
1627     int RandNumber, count, Index, ErrorCount = 0;
1628     int J;
1629 
1630     do
1631     {
1632         RandNumber = generator.number(0, counter);
1633 
1634         count = 0;
1635         Index = 0;
1636 
1637         do
1638         {
1639             count += IntensityCount[Index];
1640             ++Index;
1641         }
1642         while (runningFlag() && (count < RandNumber));
1643 
1644         J = Index - 1;
1645         ++ErrorCount;
1646     }
1647     while (runningFlag() && (IntensityCount[J] == 0) && (ErrorCount <= counter));
1648 
1649     if (!runningFlag())
1650     {
1651         return DColor(0, 0, 0, 0, sixteenBit);
1652     }
1653 
1654     color.setSixteenBit(sixteenBit);
1655     color.setAlpha(alpha);
1656     int clampMax = sixteenBit ? 655535 : 255;
1657 
1658     if (ErrorCount >= counter)
1659     {
1660         if (counter == 0)
1661         {
1662             counter = 1;
1663         }
1664 
1665         color.setRed(CLAMP((int)(AverageColorR[J] / counter), 0, clampMax));
1666         color.setGreen(CLAMP((int)(AverageColorG[J] / counter), 0, clampMax));
1667         color.setBlue(CLAMP((int)(AverageColorB[J] / counter), 0, clampMax));
1668     }
1669     else
1670     {
1671         if (IntensityCount[J] == 0)
1672         {
1673             IntensityCount[J] = 1;
1674         }
1675 
1676         color.setRed(CLAMP((int)(AverageColorR[J] / IntensityCount[J]), 0, clampMax));
1677         color.setGreen(CLAMP((int)(AverageColorG[J] / IntensityCount[J]), 0, clampMax));
1678         color.setBlue(CLAMP((int)(AverageColorB[J] / IntensityCount[J]), 0, clampMax));
1679     }
1680 
1681     return color;
1682 }
1683 
MakeConvolutionStage1Multithreaded(const Args & prm)1684 void BlurFXFilter::MakeConvolutionStage1Multithreaded(const Args& prm)
1685 {
1686     int Width       = prm.orgImage->width();
1687     int Height      = prm.orgImage->height();
1688     uchar* data     = prm.orgImage->bits();
1689     bool sixteenBit = prm.orgImage->sixteenBit();
1690     int bytesDepth  = prm.orgImage->bytesDepth();
1691 
1692     int n;
1693 
1694     int nSumR, nSumG, nSumB, nCount;
1695     DColor color;
1696     int offset;
1697 
1698     for (uint w = prm.start; runningFlag() && (w < prm.stop); ++w)
1699     {
1700         // initialize the variables
1701         nSumR = nSumG = nSumB = nCount = 0;
1702 
1703         // first of all, we need to blur the horizontal lines
1704 
1705         for (n = -prm.Radius; runningFlag() && (n <= prm.Radius); ++n)
1706         {
1707             // if is inside...
1708             if (IsInside(Width, Height, w + n, prm.h))
1709             {
1710                 // read color from orgImage
1711                 offset = GetOffset(Width, w + n, prm.h, bytesDepth);
1712                 color.setColor(data + offset, sixteenBit);
1713 
1714                 // finally, we sum the pixels using a method similar to assigntables
1715                 nSumR += prm.arrMult[n + prm.Radius][color.red()];
1716                 nSumG += prm.arrMult[n + prm.Radius][color.green()];
1717                 nSumB += prm.arrMult[n + prm.Radius][color.blue()];
1718 
1719                 // we need to add the kernel value to the counter
1720                 nCount += prm.Kernel[n + prm.Radius];
1721             }
1722         }
1723 
1724         if (nCount == 0)
1725         {
1726             nCount = 1;
1727         }
1728 
1729         // calculate pointer
1730         offset = GetOffset(Width, w, prm.h, bytesDepth);
1731         // read color from orgImage to preserve alpha
1732         color.setColor(data + offset, sixteenBit);
1733 
1734         // now, we have to calc the arithmetic average
1735         if (sixteenBit)
1736         {
1737             color.setRed(CLAMP065535(nSumR   / nCount));
1738             color.setGreen(CLAMP065535(nSumG / nCount));
1739             color.setBlue(CLAMP065535(nSumB  / nCount));
1740         }
1741         else
1742         {
1743             color.setRed((uchar)CLAMP0255(nSumR   / nCount));
1744             color.setGreen((uchar)CLAMP0255(nSumG / nCount));
1745             color.setBlue((uchar)CLAMP0255(nSumB  / nCount));
1746         }
1747 
1748         // write color to blur bits
1749         color.setPixel(prm.pBlur + offset);
1750     }
1751 }
1752 
MakeConvolutionStage2Multithreaded(const Args & prm)1753 void BlurFXFilter::MakeConvolutionStage2Multithreaded(const Args& prm)
1754 {
1755     int Width       = prm.orgImage->width();
1756     int Height      = prm.orgImage->height();
1757     uchar* data     = prm.orgImage->bits();
1758     bool sixteenBit = prm.orgImage->sixteenBit();
1759     int bytesDepth  = prm.orgImage->bytesDepth();
1760     uchar* pOutBits = prm.destImage->bits();
1761 
1762     int n;
1763 
1764     int nSumR, nSumG, nSumB, nCount;
1765     DColor color;
1766     int offset;
1767 
1768     for (uint h = prm.start; runningFlag() && (h < prm.stop); ++h)
1769     {
1770         // initialize the variables
1771         nSumR = nSumG = nSumB = nCount = 0;
1772 
1773         // first of all, we need to blur the vertical lines
1774         for (n = -prm.Radius; runningFlag() && (n <= prm.Radius); ++n)
1775         {
1776             // if is inside...
1777             if (IsInside(Width, Height, prm.w, h + n))
1778             {
1779                 // read color from blur bits
1780                 offset = GetOffset(Width, prm.w, h + n, bytesDepth);
1781                 color.setColor(prm.pBlur + offset, sixteenBit);
1782 
1783                 // finally, we sum the pixels using a method similar to assigntables
1784                 nSumR += prm.arrMult[n + prm.Radius][color.red()];
1785                 nSumG += prm.arrMult[n + prm.Radius][color.green()];
1786                 nSumB += prm.arrMult[n + prm.Radius][color.blue()];
1787 
1788                 // we need to add the kernel value to the counter
1789                 nCount += prm.Kernel[n + prm.Radius];
1790             }
1791         }
1792 
1793         if (nCount == 0)
1794         {
1795             nCount = 1;
1796         }
1797 
1798         // calculate pointer
1799         offset = GetOffset(Width, prm.w, h, bytesDepth);
1800         // read color from orgImage to preserve alpha
1801         color.setColor(data + offset, sixteenBit);
1802 
1803         // now, we have to calc the arithmetic average
1804         if (sixteenBit)
1805         {
1806             color.setRed(CLAMP065535(nSumR   / nCount));
1807             color.setGreen(CLAMP065535(nSumG / nCount));
1808             color.setBlue(CLAMP065535(nSumB  / nCount));
1809         }
1810         else
1811         {
1812             color.setRed((uchar)CLAMP0255(nSumR   / nCount));
1813             color.setGreen((uchar)CLAMP0255(nSumG / nCount));
1814             color.setBlue((uchar)CLAMP0255(nSumB  / nCount));
1815         }
1816 
1817         // write color to destination
1818         color.setPixel(pOutBits + offset);
1819     }
1820 }
1821 
1822 /* Function to simple convolve a unique pixel with a determined radius
1823  *
1824  * data             => The image data in RGBA mode.
1825  * Width            => Width of image.
1826  * Height           => Height of image.
1827  * Radius           => kernel radius, e.g. rad=1, so array will be 3X3
1828  * Kernel           => kernel array to apply.
1829  *
1830  * Theory           => I've worked hard here, but I think this is a very smart
1831  *                     way to convolve an array, its very hard to explain how I reach
1832  *                     this, but the trick here its to store the sum used by the
1833  *                     previous pixel, so we sum with the other pixels that wasn't get
1834  */
MakeConvolution(DImg * const orgImage,DImg * const destImage,int Radius,int Kernel[])1835 void BlurFXFilter::MakeConvolution(DImg* const orgImage, DImg* const destImage, int Radius, int Kernel[])
1836 {
1837     if (Radius <= 0)
1838     {
1839         return;
1840     }
1841 
1842     int progress;
1843     int nKernelWidth = Radius * 2 + 1;
1844     int range = orgImage->sixteenBit() ? 65536 : 256;
1845 
1846     QScopedArrayPointer<uchar> pBlur(new uchar[orgImage->numBytes()]);
1847 
1848     // We need to copy our bits to blur bits
1849 
1850     memcpy(pBlur.data(), orgImage->bits(), orgImage->numBytes());
1851 
1852     // We need to alloc a 2d array to help us to store the values
1853 
1854     int** const arrMult = Alloc2DArray(nKernelWidth, range);
1855 
1856     for (int i = 0; i < nKernelWidth; ++i)
1857     {
1858         for (int j = 0; j < range; ++j)
1859         {
1860             arrMult[i][j] = j * Kernel[i];
1861         }
1862     }
1863 
1864     QList<int> valsw = multithreadedSteps(orgImage->width());
1865     QList<int> valsh = multithreadedSteps(orgImage->height());
1866     QList <QFuture<void> > tasks;
1867 
1868     Args prm;
1869     prm.orgImage  = orgImage;
1870     prm.destImage = destImage;
1871     prm.Radius    = Radius;
1872     prm.Kernel    = Kernel;
1873     prm.arrMult   = arrMult;
1874     prm.pBlur     = pBlur.data();
1875 
1876     // Now, we enter in the first loop
1877 
1878     for (uint h = 0; runningFlag() && (h < orgImage->height()); ++h)
1879     {
1880         for (int j = 0 ; runningFlag() && (j < valsw.count()-1) ; ++j)
1881         {
1882             prm.start = valsw[j];
1883             prm.stop  = valsw[j+1];
1884             prm.h     = h;
1885             tasks.append(QtConcurrent::run(this,
1886                                            &BlurFXFilter::MakeConvolutionStage1Multithreaded,
1887                                            prm
1888                                           ));
1889         }
1890 
1891         foreach (QFuture<void> t, tasks)
1892             t.waitForFinished();
1893 
1894         // Update the progress bar in dialog.
1895         progress = (int)(((double)h * 50.0) / orgImage->height());
1896 
1897         if (progress % 5 == 0)
1898         {
1899             postProgress(progress);
1900         }
1901     }
1902 
1903     // We enter in the second main loop
1904 
1905     tasks.clear();
1906 
1907     for (uint w = 0; runningFlag() && (w < orgImage->width()); ++w)
1908     {
1909         for (int j = 0 ; runningFlag() && (j < valsh.count()-1) ; ++j)
1910         {
1911             prm.start = valsh[j];
1912             prm.stop  = valsh[j+1];
1913             prm.w     = w;
1914             tasks.append(QtConcurrent::run(this,
1915                                            &BlurFXFilter::MakeConvolutionStage2Multithreaded,
1916                                            prm
1917                                           ));
1918         }
1919 
1920         foreach (QFuture<void> t, tasks)
1921             t.waitForFinished();
1922 
1923         // Update the progress bar in dialog.
1924         progress = (int)(50.0 + ((double)w * 50.0) / orgImage->width());
1925 
1926         if (progress % 5 == 0)
1927         {
1928             postProgress(progress);
1929         }
1930     }
1931 
1932     // now, we must free memory
1933     Free2DArray(arrMult, nKernelWidth);
1934 }
1935 
filterAction()1936 FilterAction BlurFXFilter::filterAction()
1937 {
1938     FilterAction action(FilterIdentifier(), CurrentVersion());
1939     action.setDisplayableName(DisplayableName());
1940 
1941     action.addParameter(QLatin1String("type"),     d->blurFXType);
1942     action.addParameter(QLatin1String("distance"), d->distance);
1943     action.addParameter(QLatin1String("level"),    d->level);
1944 
1945     if (d->blurFXType == FrostGlass)
1946     {
1947         action.addParameter(QLatin1String("randomSeed"), d->randomSeed);
1948     }
1949 
1950     return action;
1951 }
1952 
readParameters(const FilterAction & action)1953 void BlurFXFilter::readParameters(const FilterAction& action)
1954 {
1955     d->blurFXType = action.parameter(QLatin1String("type")).toInt();
1956     d->distance   = action.parameter(QLatin1String("distance")).toInt();
1957     d->level      = action.parameter(QLatin1String("level")).toInt();
1958 
1959     if (d->blurFXType == FrostGlass)
1960     {
1961         d->randomSeed = action.parameter(QLatin1String("randomSeed")).toUInt();
1962     }
1963 }
1964 
1965 } // namespace Digikam
1966