1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2005-07-18
7  * Description : Distortion FX threaded image filter.
8  *
9  * Copyright (C) 2005-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10  * Copyright (C) 2006-2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
11  * Copyright (C) 2010      by Martin Klapetek <martin dot klapetek at gmail dot com>
12  *
13  * Original Distortion algorithms copyrighted 2004-2005 by
14  * Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot com>.
15  *
16  * This program is free software; you can redistribute it
17  * and/or modify it under the terms of the GNU General
18  * Public License as published by the Free Software Foundation;
19  * either version 2, or (at your option)
20  * any later version.
21  *
22  * This program is distributed in the hope that it will be useful,
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25  * GNU General Public License for more details.
26  *
27  * ============================================================ */
28 
29 #define ANGLE_RATIO 0.017453292519943295769236907685
30 
31 #include "distortionfxfilter.h"
32 
33 // C++ includes
34 
35 #include <cstdlib>
36 
37 // Qt includes
38 
39 #include <QDateTime>
40 #include <QSize>
41 #include <QMutex>
42 #include <QtConcurrent>    // krazy:exclude=includes
43 #include <QtMath>
44 
45 // KDE includes
46 
47 #include <klocalizedstring.h>
48 
49 // Local includes
50 
51 #include "dimg.h"
52 #include "dpixelsaliasfilter.h"
53 #include "randomnumbergenerator.h"
54 
55 namespace Digikam
56 {
57 
58 class Q_DECL_HIDDEN DistortionFXFilter::Private
59 {
60 public:
61 
Private()62     explicit Private()
63       : antiAlias(true),
64         level(0),
65         iteration(0),
66         effectType(0),
67         randomSeed(0),
68         globalProgress(0)
69     {
70     }
71 
72     bool                   antiAlias;
73 
74     int                    level;
75     int                    iteration;
76     int                    effectType;
77     quint32                randomSeed;
78 
79     RandomNumberGenerator generator;
80 
81     int                   globalProgress;
82 
83     QMutex                lock;
84     QMutex                lock2;   // RandomNumberGenerator is not re-entrant (dixit Boost lib)
85 };
86 
DistortionFXFilter(QObject * const parent)87 DistortionFXFilter::DistortionFXFilter(QObject* const parent)
88     : DImgThreadedFilter(parent),
89       d(new Private)
90 {
91     initFilter();
92 }
93 
DistortionFXFilter(DImg * const orgImage,QObject * const parent,int effectType,int level,int iteration,bool antialiaqSing)94 DistortionFXFilter::DistortionFXFilter(DImg* const orgImage, QObject* const parent, int effectType,
95                                        int level, int iteration, bool antialiaqSing)
96     : DImgThreadedFilter(orgImage, parent, QLatin1String("DistortionFX")),
97       d(new Private)
98 {
99     d->effectType = effectType;
100     d->level      = level;
101     d->iteration  = iteration;
102     d->antiAlias  = antialiaqSing;
103     d->randomSeed = RandomNumberGenerator::timeSeed();
104     initFilter();
105 }
106 
~DistortionFXFilter()107 DistortionFXFilter::~DistortionFXFilter()
108 {
109     cancelFilter();
110     delete d;
111 }
112 
DisplayableName()113 QString DistortionFXFilter::DisplayableName()
114 {
115     return QString::fromUtf8(I18N_NOOP("Distortion Effect"));
116 }
117 
filterImage()118 void DistortionFXFilter::filterImage()
119 {
120     int w = m_orgImage.width();
121     int h = m_orgImage.height();
122     int l = d->level;
123     int f = d->iteration;
124 
125     switch (d->effectType)
126     {
127         case FishEye:
128             fisheye(&m_orgImage, &m_destImage, (double)(l / 5.0), d->antiAlias);
129             break;
130 
131         case Twirl:
132             twirl(&m_orgImage, &m_destImage, l, d->antiAlias);
133             break;
134 
135         case CilindricalHor:
136             cilindrical(&m_orgImage, &m_destImage, (double)l, true, false, d->antiAlias);
137             break;
138 
139         case CilindricalVert:
140             cilindrical(&m_orgImage, &m_destImage, (double)l, false, true, d->antiAlias);
141             break;
142 
143         case CilindricalHV:
144             cilindrical(&m_orgImage, &m_destImage, (double)l, true, true, d->antiAlias);
145             break;
146 
147         case Caricature:
148             fisheye(&m_orgImage, &m_destImage, (double)(-l / 5.0), d->antiAlias);
149             break;
150 
151         case MultipleCorners:
152             multipleCorners(&m_orgImage, &m_destImage, l, d->antiAlias);
153             break;
154 
155         case WavesHorizontal:
156             waves(&m_orgImage, &m_destImage, l, f, true, true);
157             break;
158 
159         case WavesVertical:
160             waves(&m_orgImage, &m_destImage, l, f, true, false);
161             break;
162 
163         case BlockWaves1:
164             blockWaves(&m_orgImage, &m_destImage, l, f, false);
165             break;
166 
167         case BlockWaves2:
168             blockWaves(&m_orgImage, &m_destImage, l, f, true);
169             break;
170 
171         case CircularWaves1:
172             circularWaves(&m_orgImage, &m_destImage, w / 2, h / 2, (double)l, (double)f, 0.0, false, d->antiAlias);
173             break;
174 
175         case CircularWaves2:
176             circularWaves(&m_orgImage, &m_destImage, w / 2, h / 2, (double)l, (double)f, 25.0, true, d->antiAlias);
177             break;
178 
179         case PolarCoordinates:
180             polarCoordinates(&m_orgImage, &m_destImage, true, d->antiAlias);
181             break;
182 
183         case UnpolarCoordinates:
184             polarCoordinates(&m_orgImage, &m_destImage, false, d->antiAlias);
185             break;
186 
187         case Tile:
188             tile(&m_orgImage, &m_destImage, 210 - f, 210 - f, l);
189             break;
190     }
191 }
192 
fisheyeMultithreaded(const Args & prm)193 void DistortionFXFilter::fisheyeMultithreaded(const Args& prm)
194 {
195     int Width       = prm.orgImage->width();
196     int Height      = prm.orgImage->height();
197     uchar* data     = prm.orgImage->bits();
198     bool sixteenBit = prm.orgImage->sixteenBit();
199     int bytesDepth  = prm.orgImage->bytesDepth();
200     uchar* pResBits = prm.destImage->bits();
201 
202     double nh, nw, tw;
203 
204     DColor color;
205     int offset;
206 
207     int nHalfW         = Width  / 2;
208     int nHalfH         = Height / 2;
209     double lfXScale    = 1.0;
210     double lfYScale    = 1.0;
211     double lfCoeffStep = prm.Coeff / 1000.0;
212     double lfRadius, lfAngle;
213 
214     if (Width > Height)
215     {
216         lfYScale = (double)Width / (double)Height;
217     }
218     else if (Height > Width)
219     {
220         lfXScale = (double)Height / (double)Width;
221     }
222 
223     double lfRadMax = (double)qMax(Height, Width) / 2.0;
224     double lfCoeff  = lfRadMax / qLn(qFabs(lfCoeffStep) * lfRadMax + 1.0);
225     double th       = lfYScale * (double)(prm.h - nHalfH);
226 
227     for (int w = prm.start; runningFlag() && (w < prm.stop); ++w)
228     {
229         tw = lfXScale * (double)(w - nHalfW);
230 
231         // we find the distance from the center
232         lfRadius = qSqrt(th * th + tw * tw);
233 
234         if (lfRadius < lfRadMax)
235         {
236             lfAngle = qAtan2(th, tw);
237 
238             if (prm.Coeff > 0.0)
239             {
240                 lfRadius = (qExp(lfRadius / lfCoeff) - 1.0) / lfCoeffStep;
241             }
242             else
243             {
244                 lfRadius = lfCoeff * qLn(1.0 + (-1.0 * lfCoeffStep) * lfRadius);
245             }
246 
247             nw = (double)nHalfW + (lfRadius / lfXScale) * qCos(lfAngle);
248             nh = (double)nHalfH + (lfRadius / lfYScale) * qSin(lfAngle);
249 
250             setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, prm.h, nw, nh, prm.AntiAlias);
251         }
252         else
253         {
254             // copy pixel
255             offset = getOffset(Width, w, prm.h, bytesDepth);
256             color.setColor(data + offset, sixteenBit);
257             color.setPixel(pResBits + offset);
258         }
259     }
260 }
261 
262 /* Function to apply the fisheye effect backported from ImageProcesqSing version 2
263  *
264  * data             => The image data in RGBA mode.
265  * Width            => Width of image.
266  * Height           => Height of image.
267  * Coeff            => Distortion effect coeff. Positive value render 'Fish Eyes' effect,
268  *                     and negative values render 'Caricature' effect.
269  * Antialias        => Smart blurring result.
270  *
271  * Theory           => This is a great effect if you take employee photos
272  *                     Its pure trigonometry. I think if you study hard the code you
273  *                     understand very well.
274  */
fisheye(DImg * orgImage,DImg * destImage,double Coeff,bool AntiAlias)275 void DistortionFXFilter::fisheye(DImg* orgImage, DImg* destImage, double Coeff, bool AntiAlias)
276 {
277     if (Coeff == 0.0)
278     {
279         return;
280     }
281 
282     int progress;
283 
284     QList<int> vals = multithreadedSteps(orgImage->width());
285     QList <QFuture<void> > tasks;
286 
287     Args prm;
288     prm.orgImage  = orgImage;
289     prm.destImage = destImage;
290     prm.Coeff     = Coeff;
291     prm.AntiAlias = AntiAlias;
292 
293     // main loop
294 
295     for (int h = 0; runningFlag() && (h < (int)orgImage->height()); ++h)
296     {
297         for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
298         {
299             prm.start = vals[j];
300             prm.stop  = vals[j+1];
301             prm.h     = h;
302             tasks.append(QtConcurrent::run(this,
303                                            &DistortionFXFilter::fisheyeMultithreaded,
304                                            prm
305                                           ));
306         }
307 
308         foreach (QFuture<void> t, tasks)
309             t.waitForFinished();
310 
311         // Update the progress bar in dialog.
312         progress = (int)(((double)(h) * 100.0) / orgImage->height());
313 
314         if (progress % 5 == 0)
315         {
316             postProgress(progress);
317         }
318     }
319 }
320 
twirlMultithreaded(const Args & prm)321 void DistortionFXFilter::twirlMultithreaded(const Args& prm)
322 {
323     int Width       = prm.orgImage->width();
324     int Height      = prm.orgImage->height();
325     uchar* data     = prm.orgImage->bits();
326     bool sixteenBit = prm.orgImage->sixteenBit();
327     int bytesDepth  = prm.orgImage->bytesDepth();
328     uchar* pResBits = prm.destImage->bits();
329 
330     DColor color;
331     int offset;
332 
333     int    nHalfW   = Width / 2;
334     int    nHalfH   = Height / 2;
335     double lfXScale = 1.0;
336     double lfYScale = 1.0;
337     double lfAngle, lfNewAngle, lfAngleSum, lfCurrentRadius;
338     double tw, nh, nw;
339 
340     if (Width > Height)
341     {
342         lfYScale = (double)Width / (double)Height;
343     }
344     else if (Height > Width)
345     {
346         lfXScale = (double)Height / (double)Width;
347     }
348 
349     // the angle step is dist divided by 10000
350     double lfAngleStep = prm.dist / 10000.0;
351     // now, we get the minimum radius
352     double lfRadMax    = (double)qMax(Width, Height) / 2.0;
353 
354     double th          = lfYScale * (double)(prm.h - nHalfH);
355 
356     for (int w = prm.start; runningFlag() && (w < prm.stop); ++w)
357     {
358         tw = lfXScale * (double)(w - nHalfW);
359 
360         // now, we get the distance
361         lfCurrentRadius = qSqrt(th * th + tw * tw);
362 
363         // if distance is less than maximum radius...
364         if (lfCurrentRadius < lfRadMax)
365         {
366             // we find the angle from the center
367             lfAngle = qAtan2(th, tw);
368             // we get the accumulated angle
369             lfAngleSum = lfAngleStep * (-1.0 * (lfCurrentRadius - lfRadMax));
370             // ok, we sum angle with accumulated to find a new angle
371             lfNewAngle = lfAngle + lfAngleSum;
372 
373             // now we find the exact position's x and y
374             nw = (double)nHalfW + qCos(lfNewAngle) * (lfCurrentRadius / lfXScale);
375             nh = (double)nHalfH + qSin(lfNewAngle) * (lfCurrentRadius / lfYScale);
376 
377             setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, prm.h, nw, nh, prm.AntiAlias);
378         }
379         else
380         {
381             // copy pixel
382             offset = getOffset(Width, w, prm.h, bytesDepth);
383             color.setColor(data + offset, sixteenBit);
384             color.setPixel(pResBits + offset);
385         }
386     }
387 }
388 
389 /* Function to apply the twirl effect backported from ImageProcesqSing version 2
390  *
391  * data             => The image data in RGBA mode.
392  * Width            => Width of image.
393  * Height           => Height of image.
394  * dist             => Distance value.
395  * Antialias        => Smart blurring result.
396  *
397  * Theory           => Take spiral studies, you will understand better, I'm studying
398  *                     hard on this effect, because it is not too fast.
399  */
twirl(DImg * orgImage,DImg * destImage,int dist,bool AntiAlias)400 void DistortionFXFilter::twirl(DImg* orgImage, DImg* destImage, int dist, bool AntiAlias)
401 {
402     // if dist value is zero, we do nothing
403 
404     if (dist == 0)
405     {
406         return;
407     }
408 
409     int progress;
410 
411     QList<int> vals = multithreadedSteps(orgImage->width());
412     QList <QFuture<void> > tasks;
413 
414     Args prm;
415     prm.orgImage  = orgImage;
416     prm.destImage = destImage;
417     prm.dist      = dist;
418     prm.AntiAlias = AntiAlias;
419 
420     // main loop
421 
422     for (int h = 0; runningFlag() && (h < (int)orgImage->height()); ++h)
423     {
424         for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
425         {
426             prm.start = vals[j];
427             prm.stop  = vals[j+1];
428             prm.h     = h;
429             tasks.append(QtConcurrent::run(this,
430                                            &DistortionFXFilter::twirlMultithreaded,
431                                            prm
432                                           ));
433         }
434 
435         foreach (QFuture<void> t, tasks)
436             t.waitForFinished();
437 
438         // Update the progress bar in dialog.
439         progress = (int)(((double)h * 100.0) / orgImage->height());
440 
441         if (progress % 5 == 0)
442         {
443             postProgress(progress);
444         }
445     }
446 }
447 
cilindricalMultithreaded(const Args & prm)448 void DistortionFXFilter::cilindricalMultithreaded(const Args& prm)
449 {
450     int Width       = prm.orgImage->width();
451     int Height      = prm.orgImage->height();
452     uchar* data     = prm.orgImage->bits();
453     bool sixteenBit = prm.orgImage->sixteenBit();
454     int bytesDepth  = prm.orgImage->bytesDepth();
455     uchar* pResBits = prm.destImage->bits();
456 
457     double nh, nw;
458 
459     int    nHalfW      = Width / 2;
460     int    nHalfH      = Height / 2;
461     double lfCoeffX    = 1.0;
462     double lfCoeffY    = 1.0;
463     double lfCoeffStep = prm.Coeff / 1000.0;
464 
465     if (prm.Horizontal)
466     {
467         lfCoeffX = (double)nHalfW / qLn(qFabs(lfCoeffStep) * nHalfW + 1.0);
468     }
469 
470     if (prm.Vertical)
471     {
472         lfCoeffY = (double)nHalfH / qLn(qFabs(lfCoeffStep) * nHalfH + 1.0);
473     }
474 
475     for (int w = prm.start; runningFlag() && (w < prm.stop); ++w)
476     {
477         // we find the distance from the center
478         nh = qFabs((double)(prm.h - nHalfH));
479         nw = qFabs((double)(w - nHalfW));
480 
481         if (prm.Horizontal)
482         {
483             if (prm.Coeff > 0.0)
484             {
485                 nw = (qExp(nw / lfCoeffX) - 1.0) / lfCoeffStep;
486             }
487             else
488             {
489                 nw = lfCoeffX * qLn(1.0 + (-1.0 * lfCoeffStep) * nw);
490             }
491         }
492 
493         if (prm.Vertical)
494         {
495             if (prm.Coeff > 0.0)
496             {
497                 nh = (qExp(nh / lfCoeffY) - 1.0) / lfCoeffStep;
498             }
499             else
500             {
501                 nh = lfCoeffY * qLn(1.0 + (-1.0 * lfCoeffStep) * nh);
502             }
503         }
504 
505         nw = (double)nHalfW + ((w >= nHalfW)     ? nw : -nw);
506         nh = (double)nHalfH + ((prm.h >= nHalfH) ? nh : -nh);
507 
508         setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, prm.h, nw, nh, prm.AntiAlias);
509     }
510 }
511 
512 /* Function to apply the Cylindrical effect backported from ImageProcessing version 2
513  *
514  * data             => The image data in RGBA mode.
515  * Width            => Width of image.
516  * Height           => Height of image.
517  * Coeff            => Cylindrical value.
518  * Horizontal       => Apply horizontally.
519  * Vertical         => Apply vertically.
520  * Antialias        => Smart blurring result.
521  *
522  * Theory           => This is a great effect, similar to Spherize (Photoshop).
523  *                     If you understand FishEye, you will understand Cylindrical
524  *                     FishEye apply a logarithm function using a sphere radius,
525  *                     Spherize use the same function but in a rectangular
526  *                     environment.
527  */
cilindrical(DImg * orgImage,DImg * destImage,double Coeff,bool Horizontal,bool Vertical,bool AntiAlias)528 void DistortionFXFilter::cilindrical(DImg* orgImage, DImg* destImage, double Coeff,
529                                      bool Horizontal, bool Vertical, bool AntiAlias)
530 
531 {
532     if ((Coeff == 0.0) || (!(Horizontal || Vertical)))
533     {
534         return;
535     }
536 
537     int progress;
538 
539     // initial copy
540     memcpy(destImage->bits(), orgImage->bits(), orgImage->numBytes());
541 
542     QList<int> vals = multithreadedSteps(orgImage->width());
543     QList <QFuture<void> > tasks;
544 
545     Args prm;
546     prm.orgImage   = orgImage;
547     prm.destImage  = destImage;
548     prm.Coeff      = Coeff;
549     prm.Horizontal = Horizontal;
550     prm.Vertical   = Vertical;
551     prm.AntiAlias  = AntiAlias;
552 
553     // main loop
554 
555     for (int h = 0; runningFlag() && (h < (int)orgImage->height()); ++h)
556     {
557         for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
558         {
559             prm.start = vals[j];
560             prm.stop  = vals[j+1];
561             prm.h     = h;
562             tasks.append(QtConcurrent::run(this,
563                                            &DistortionFXFilter::cilindricalMultithreaded,
564                                            prm
565                                           ));
566         }
567 
568         foreach (QFuture<void> t, tasks)
569             t.waitForFinished();
570 
571         // Update the progress bar in dialog.
572         progress = (int)(((double)h * 100.0) / orgImage->height());
573 
574         if (progress % 5 == 0)
575         {
576             postProgress(progress);
577         }
578     }
579 }
580 
multipleCornersMultithreaded(const Args & prm)581 void DistortionFXFilter::multipleCornersMultithreaded(const Args& prm)
582 {
583     int Width       = prm.orgImage->width();
584     int Height      = prm.orgImage->height();
585     uchar* data     = prm.orgImage->bits();
586     bool sixteenBit = prm.orgImage->sixteenBit();
587     int bytesDepth  = prm.orgImage->bytesDepth();
588     uchar* pResBits = prm.destImage->bits();
589 
590     double nh, nw;
591 
592     int    nHalfW   = Width / 2;
593     int    nHalfH   = Height / 2;
594     double lfRadMax = qSqrt(Height * Height + Width * Width) / 2.0;
595     double lfAngle, lfNewRadius, lfCurrentRadius;
596 
597     for (int w = prm.start; runningFlag() && (w < prm.stop); ++w)
598     {
599         // we find the distance from the center
600         nh = nHalfH - prm.h;
601         nw = nHalfW - w;
602 
603         // now, we get the distance
604         lfCurrentRadius = qSqrt(nh * nh + nw * nw);
605         // we find the angle from the center
606         lfAngle = qAtan2(nh, nw) * (double)prm.Factor;
607 
608         // ok, we sum angle with accumulated to find a new angle
609         lfNewRadius = lfCurrentRadius * lfCurrentRadius / lfRadMax;
610 
611         // now we find the exact position's x and y
612         nw = (double)nHalfW - (qCos(lfAngle) * lfNewRadius);
613         nh = (double)nHalfH - (qSin(lfAngle) * lfNewRadius);
614 
615         setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, prm.h, nw, nh, prm.AntiAlias);
616     }
617 }
618 
619 /* Function to apply the Multiple Corners effect backported from ImageProcessing version 2
620  *
621  * data             => The image data in RGBA mode.
622  * Width            => Width of image.
623  * Height           => Height of image.
624  * Factor           => nb corners.
625  * Antialias        => Smart blurring result.
626  *
627  * Theory           => This is an amazing function, you've never seen this before.
628  *                     I was testing some trigonometric functions, and I saw that if
629  *                     I multiply the angle by 2, the result is an image like this
630  *                     If we multiply by 3, we can create the SixCorners effect.
631  */
multipleCorners(DImg * orgImage,DImg * destImage,int Factor,bool AntiAlias)632 void DistortionFXFilter::multipleCorners(DImg* orgImage, DImg* destImage, int Factor, bool AntiAlias)
633 {
634     if (Factor == 0)
635     {
636         return;
637     }
638 
639     int progress;
640 
641     QList<int> vals = multithreadedSteps(orgImage->width());
642     QList <QFuture<void> > tasks;
643 
644     Args prm;
645     prm.orgImage  = orgImage;
646     prm.destImage = destImage;
647     prm.Factor    = Factor;
648     prm.AntiAlias = AntiAlias;
649 
650     // main loop
651 
652     for (int h = 0; runningFlag() && (h < (int)orgImage->height()); ++h)
653     {
654         for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
655         {
656             prm.start = vals[j];
657             prm.stop  = vals[j+1];
658             prm.h     = h;
659             tasks.append(QtConcurrent::run(this,
660                                            &DistortionFXFilter::multipleCornersMultithreaded,
661                                            prm
662                                           ));
663         }
664 
665         foreach (QFuture<void> t, tasks)
666             t.waitForFinished();
667 
668         // Update the progress bar in dialog.
669         progress = (int)(((double)h * 100.0) / orgImage->height());
670 
671         if (progress % 5 == 0)
672         {
673             postProgress(progress);
674         }
675     }
676 }
677 
wavesHorizontalMultithreaded(const Args & prm)678 void DistortionFXFilter::wavesHorizontalMultithreaded(const Args& prm)
679 {
680     int oldProgress=0, progress=0, tx;
681 
682     for (int h = prm.start; runningFlag() && (h < prm.stop); ++h)
683     {
684         tx = lround(prm.Amplitude * qSin((prm.Frequency * 2) * h * (M_PI / 180)));
685         prm.destImage->bitBltImage(prm.orgImage, 0, h,  prm.orgImage->width(), 1,  tx, h);
686 
687         if (prm.FillSides)
688         {
689             prm.destImage->bitBltImage(prm.orgImage, prm.orgImage->width() - tx, h,  tx, 1,  0, h);
690             prm.destImage->bitBltImage(prm.orgImage, 0, h, prm.orgImage->width() - (prm.orgImage->width() - 2 * prm.Amplitude + tx), 1,  prm.orgImage->width() + tx, h);
691         }
692 
693         // Update the progress bar in dialog.
694         progress = (int)( ( (double)h * (100.0 / QThreadPool::globalInstance()->maxThreadCount()) ) / (prm.stop - prm.start));
695 
696         if ((progress % 5 == 0) && (progress > oldProgress))
697         {
698             d->lock.lock();
699             oldProgress       = progress;
700             d->globalProgress += 5;
701             postProgress(d->globalProgress);
702             d->lock.unlock();
703         }
704     }
705 }
706 
wavesVerticalMultithreaded(const Args & prm)707 void DistortionFXFilter::wavesVerticalMultithreaded(const Args& prm)
708 {
709     int oldProgress=0, progress=0, ty;
710 
711     for (int w = prm.start; runningFlag() && (w < prm.stop); ++w)
712     {
713         ty = lround(prm.Amplitude * qSin((prm.Frequency * 2) * w * (M_PI / 180)));
714         prm.destImage->bitBltImage(prm.orgImage, w, 0, 1, prm.orgImage->height(), w, ty);
715 
716         if (prm.FillSides)
717         {
718             prm.destImage->bitBltImage(prm.orgImage, w, prm.orgImage->height() - ty,  1, ty,  w, 0);
719             prm.destImage->bitBltImage(prm.orgImage, w, 0,  1, prm.orgImage->height() - (prm.orgImage->height() - 2 * prm.Amplitude + ty),  w, prm.orgImage->height() + ty);
720         }
721 
722         // Update the progress bar in dialog.
723         progress = (int)( ( (double)w * (100.0 / QThreadPool::globalInstance()->maxThreadCount()) ) / (prm.stop - prm.start));
724 
725         if ((progress % 5 == 0) && (progress > oldProgress))
726         {
727             d->lock.lock();
728             oldProgress       = progress;
729             d->globalProgress += 5;
730             postProgress(d->globalProgress);
731             d->lock.unlock();
732         }
733     }
734 }
735 
736 /* Function to apply the waves effect
737  *
738  * data             => The image data in RGBA mode.
739  * Width            => Width of image.
740  * Height           => Height of image.
741  * Amplitude        => Sinusoidal maximum height.
742  * Frequency        => Frequency value.
743  * FillSides        => Like a boolean variable.
744  * Direction        => Vertical or horizontal flag.
745  *
746  * Theory           => This is an amazing effect, very funny, and very simple to
747  *                     understand. You just need understand how qSin and qCos works.
748  */
waves(DImg * orgImage,DImg * destImage,int Amplitude,int Frequency,bool FillSides,bool Direction)749 void DistortionFXFilter::waves(DImg* orgImage, DImg* destImage,
750                                int Amplitude, int Frequency,
751                                bool FillSides, bool Direction)
752 {
753     if (Amplitude < 0)
754     {
755         Amplitude = 0;
756     }
757 
758     if (Frequency < 0)
759     {
760         Frequency = 0;
761     }
762 
763     Args prm;
764     prm.orgImage  = orgImage;
765     prm.destImage = destImage;
766     prm.Amplitude = Amplitude;
767     prm.Frequency = Frequency;
768     prm.FillSides = FillSides;
769 
770     if (Direction)        // Horizontal
771     {
772         QList<int> vals = multithreadedSteps(orgImage->height());
773         QList <QFuture<void> > tasks;
774 
775         for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
776         {
777             prm.start = vals[j];
778             prm.stop  = vals[j+1];
779             tasks.append(QtConcurrent::run(this,
780                                            &DistortionFXFilter::wavesHorizontalMultithreaded,
781                                            prm
782                                           ));
783         }
784 
785         foreach (QFuture<void> t, tasks)
786             t.waitForFinished();
787     }
788     else
789     {
790         QList<int> vals = multithreadedSteps(orgImage->width());
791         QList <QFuture<void> > tasks;
792 
793         for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
794         {
795             prm.start = vals[j];
796             prm.stop  = vals[j+1];
797             tasks.append(QtConcurrent::run(this,
798                                            &DistortionFXFilter::wavesVerticalMultithreaded,
799                                            prm
800                                           ));
801         }
802 
803         foreach (QFuture<void> t, tasks)
804             t.waitForFinished();
805     }
806 }
807 
blockWavesMultithreaded(const Args & prm)808 void DistortionFXFilter::blockWavesMultithreaded(const Args& prm)
809 {
810     int Width       = prm.orgImage->width();
811     int Height      = prm.orgImage->height();
812     uchar* data     = prm.orgImage->bits();
813     bool sixteenBit = prm.orgImage->sixteenBit();
814     int bytesDepth  = prm.orgImage->bytesDepth();
815     uchar* pResBits = prm.destImage->bits();
816 
817     int nw, nh;
818 
819     DColor color;
820     int offset, offsetOther;
821 
822     int nHalfW = Width  / 2;
823     int nHalfH = Height / 2;
824 
825     for (int h = prm.start; runningFlag() && (h < prm.stop); ++h)
826     {
827         nw = nHalfW - prm.w;
828         nh = nHalfH - h;
829 
830         if (prm.Mode)
831         {
832             nw = (int)(prm.w + prm.Amplitude * qSin(prm.Frequency * nw * (M_PI / 180)));
833             nh = (int)(h     + prm.Amplitude * qCos(prm.Frequency * nh * (M_PI / 180)));
834         }
835         else
836         {
837             nw = (int)(prm.w + prm.Amplitude * qSin(prm.Frequency * prm.w * (M_PI / 180)));
838             nh = (int)(h     + prm.Amplitude * qCos(prm.Frequency * h     * (M_PI / 180)));
839         }
840 
841         offset      = getOffset(Width, prm.w, h, bytesDepth);
842         offsetOther = getOffsetAdjusted(Width, Height, (int)nw, (int)nh, bytesDepth);
843 
844         // read color
845         color.setColor(data + offsetOther, sixteenBit);
846         // write color to destination
847         color.setPixel(pResBits + offset);
848     }
849 }
850 
851 /* Function to apply the block waves effect
852  *
853  * data             => The image data in RGBA mode.
854  * Width            => Width of image.
855  * Height           => Height of image.
856  * Amplitude        => Sinusoidal maximum height
857  * Frequency        => Frequency value
858  * Mode             => The mode to be applied.
859  *
860  * Theory           => This is an amazing effect, very funny when amplitude and
861  *                     frequency are small values.
862  */
blockWaves(DImg * orgImage,DImg * destImage,int Amplitude,int Frequency,bool Mode)863 void DistortionFXFilter::blockWaves(DImg* orgImage, DImg* destImage,
864                                     int Amplitude, int Frequency, bool Mode)
865 {
866     if (Amplitude < 0)
867     {
868         Amplitude = 0;
869     }
870 
871     if (Frequency < 0)
872     {
873         Frequency = 0;
874     }
875 
876     int progress;
877 
878     QList<int> vals = multithreadedSteps(orgImage->height());
879     QList <QFuture<void> > tasks;
880 
881     Args prm;
882     prm.orgImage  = orgImage;
883     prm.destImage = destImage;
884     prm.Mode      = Mode;
885     prm.Frequency = Frequency;
886     prm.Amplitude = Amplitude;
887 
888     for (int w = 0; runningFlag() && (w < (int)orgImage->width()); ++w)
889     {
890         for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
891         {
892             prm.start = vals[j];
893             prm.stop  = vals[j+1];
894             prm.w     = w;
895             tasks.append(QtConcurrent::run(this,
896                                            &DistortionFXFilter::blockWavesMultithreaded,
897                                            prm
898                                           ));
899         }
900 
901         foreach (QFuture<void> t, tasks)
902             t.waitForFinished();
903 
904         // Update the progress bar in dialog.
905         progress = (int)(((double)w * 100.0) / orgImage->width());
906 
907         if (progress % 5 == 0)
908         {
909             postProgress(progress);
910         }
911     }
912 }
913 
circularWavesMultithreaded(const Args & prm)914 void DistortionFXFilter::circularWavesMultithreaded(const Args& prm)
915 {
916     int Width       = prm.orgImage->width();
917     int Height      = prm.orgImage->height();
918     uchar* data     = prm.orgImage->bits();
919     bool sixteenBit = prm.orgImage->sixteenBit();
920     int bytesDepth  = prm.orgImage->bytesDepth();
921     uchar* pResBits = prm.destImage->bits();
922 
923     double nh, nw;
924 
925     double lfRadius, lfRadMax;
926     double lfNewAmp     = prm.Amplitude;
927     double lfFreqAngle  = prm.Frequency * ANGLE_RATIO;
928     double phase        = prm.Phase     * ANGLE_RATIO;
929     lfRadMax            = qSqrt(Height * Height + Width * Width);
930 
931     for (int w = prm.start; runningFlag() && (w < prm.stop); ++w)
932     {
933         nw = prm.X - w;
934         nh = prm.Y - prm.h;
935 
936         lfRadius = qSqrt(nw * nw + nh * nh);
937 
938         if (prm.WavesType)
939         {
940             lfNewAmp = prm.Amplitude * lfRadius / lfRadMax;
941         }
942 
943         nw = (double)w     + lfNewAmp * qSin(lfFreqAngle * lfRadius + phase);
944         nh = (double)prm.h + lfNewAmp * qCos(lfFreqAngle * lfRadius + phase);
945 
946         setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, prm.h, nw, nh, prm.AntiAlias);
947     }
948 }
949 
950 /* Function to apply the circular waves effect backported from ImageProcesqSing version 2
951  *
952  * data             => The image data in RGBA mode.
953  * Width            => Width of image.
954  * Height           => Height of image.
955  * X, Y             => Position of circle center on the image.
956  * Amplitude        => Sinusoidal maximum height
957  * Frequency        => Frequency value.
958  * Phase            => Phase value.
959  * WavesType        => If true  the amplitude is proportional to radius.
960  * Antialias        => Smart blurring result.
961  *
962  * Theory           => Similar to Waves effect, but here I apply a sinusoidal function
963  *                     with the angle point.
964  */
circularWaves(DImg * orgImage,DImg * destImage,int X,int Y,double Amplitude,double Frequency,double Phase,bool WavesType,bool AntiAlias)965 void DistortionFXFilter::circularWaves(DImg* orgImage, DImg* destImage, int X, int Y, double Amplitude,
966                                        double Frequency, double Phase, bool WavesType, bool AntiAlias)
967 {
968     if (Amplitude < 0.0)
969     {
970         Amplitude = 0.0;
971     }
972 
973     if (Frequency < 0.0)
974     {
975         Frequency = 0.0;
976     }
977 
978     int progress;
979 
980     QList<int> vals = multithreadedSteps(orgImage->width());
981     QList <QFuture<void> > tasks;
982 
983     Args prm;
984     prm.orgImage  = orgImage;
985     prm.destImage = destImage;
986     prm.Phase     = Phase;
987     prm.Frequency = Frequency;
988     prm.Amplitude = Amplitude;
989     prm.WavesType = WavesType;
990     prm.X         = X;
991     prm.Y         = Y;
992     prm.AntiAlias = AntiAlias;
993 
994     for (int h = 0; runningFlag() && (h < (int)orgImage->height()); ++h)
995     {
996         for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
997         {
998             prm.start = vals[j];
999             prm.stop  = vals[j+1];
1000             prm.h     = h;
1001             tasks.append(QtConcurrent::run(this,
1002                                            &DistortionFXFilter::circularWavesMultithreaded,
1003                                            prm
1004                                           ));
1005         }
1006 
1007         foreach (QFuture<void> t, tasks)
1008             t.waitForFinished();
1009 
1010         // Update the progress bar in dialog.
1011         progress = (int)(((double)h * 100.0) / orgImage->height());
1012 
1013         if (progress % 5 == 0)
1014         {
1015             postProgress(progress);
1016         }
1017     }
1018 }
1019 
polarCoordinatesMultithreaded(const Args & prm)1020 void DistortionFXFilter::polarCoordinatesMultithreaded(const Args& prm)
1021 {
1022     int Width       = prm.orgImage->width();
1023     int Height      = prm.orgImage->height();
1024     uchar* data     = prm.orgImage->bits();
1025     bool sixteenBit = prm.orgImage->sixteenBit();
1026     int bytesDepth  = prm.orgImage->bytesDepth();
1027     uchar* pResBits = prm.destImage->bits();
1028 
1029     int nHalfW      = Width / 2;
1030     int nHalfH      = Height / 2;
1031     double lfXScale = 1.0;
1032     double lfYScale = 1.0;
1033     double lfAngle, lfRadius, lfRadMax;
1034     double nh, nw, tw;
1035 
1036     if (Width > Height)
1037     {
1038         lfYScale = (double)Width / (double)Height;
1039     }
1040     else if (Height > Width)
1041     {
1042         lfXScale = (double)Height / (double)Width;
1043     }
1044 
1045     lfRadMax = (double)qMax(Height, Width) / 2.0;
1046 
1047     double th = lfYScale * (double)(prm.h - nHalfH);
1048 
1049     for (int w = prm.start; runningFlag() && (w < prm.stop); ++w)
1050     {
1051         tw = lfXScale * (double)(w - nHalfW);
1052 
1053         if (prm.Type)
1054         {
1055             // now, we get the distance
1056             lfRadius = qSqrt(th * th + tw * tw);
1057             // we find the angle from the center
1058             lfAngle = qAtan2(tw, th);
1059 
1060             // now we find the exact position's x and y
1061             nh = lfRadius * (double) Height / lfRadMax;
1062             nw =  lfAngle * (double)  Width / (2 * M_PI);
1063 
1064             nw = (double)nHalfW + nw;
1065         }
1066         else
1067         {
1068             lfRadius = (double)(prm.h) * lfRadMax   / (double)Height;
1069             lfAngle  = (double)(w)     * (2 * M_PI) / (double) Width;
1070 
1071             nw = (double)nHalfW - (lfRadius / lfXScale) * qSin(lfAngle);
1072             nh = (double)nHalfH - (lfRadius / lfYScale) * qCos(lfAngle);
1073         }
1074 
1075         setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, prm.h, nw, nh, prm.AntiAlias);
1076     }
1077 }
1078 
1079 /* Function to apply the Polar Coordinates effect backported from ImageProcesqSing version 2
1080  *
1081  * data             => The image data in RGBA mode.
1082  * Width            => Width of image.
1083  * Height           => Height of image.
1084  * Type             => if true Polar Coordinate to Polar else inverse.
1085  * Antialias        => Smart blurring result.
1086  *
1087  * Theory           => Similar to PolarCoordinates from Photoshop. We apply the polar
1088  *                     transformation in a proportional (Height and Width) radius.
1089  */
polarCoordinates(DImg * orgImage,DImg * destImage,bool Type,bool AntiAlias)1090 void DistortionFXFilter::polarCoordinates(DImg* orgImage, DImg* destImage, bool Type, bool AntiAlias)
1091 {
1092     int progress;
1093 
1094     QList<int> vals = multithreadedSteps(orgImage->width());
1095     QList <QFuture<void> > tasks;
1096 
1097     Args prm;
1098     prm.orgImage  = orgImage;
1099     prm.destImage = destImage;
1100     prm.Type      = Type;
1101     prm.AntiAlias = AntiAlias;
1102 
1103     // main loop
1104 
1105     for (int h = 0; runningFlag() && (h < (int)orgImage->height()); ++h)
1106     {
1107         for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
1108         {
1109             prm.start = vals[j];
1110             prm.stop  = vals[j+1];
1111             prm.h     = h;
1112             tasks.append(QtConcurrent::run(this,
1113                                            &DistortionFXFilter::polarCoordinatesMultithreaded,
1114                                            prm
1115                                           ));
1116         }
1117 
1118         foreach (QFuture<void> t, tasks)
1119             t.waitForFinished();
1120 
1121         // Update the progress bar in dialog.
1122         progress = (int)(((double)h * 100.0) / orgImage->height());
1123 
1124         if (progress % 5 == 0)
1125         {
1126             postProgress(progress);
1127         }
1128     }
1129 }
1130 
tileMultithreaded(const Args & prm)1131 void DistortionFXFilter::tileMultithreaded(const Args& prm)
1132 {
1133     int tx, ty, progress=0, oldProgress=0;
1134 
1135     for (int h = prm.start; runningFlag() && (h < prm.stop); h += prm.HSize)
1136     {
1137         for (int w = 0; runningFlag() && (w < (int)prm.orgImage->width()); w += prm.WSize)
1138         {
1139             d->lock2.lock();
1140             tx = d->generator.number(-prm.Random / 2, prm.Random / 2);
1141             ty = d->generator.number(-prm.Random / 2, prm.Random / 2);
1142             d->lock2.unlock();
1143             prm.destImage->bitBltImage(prm.orgImage, w, h, prm.WSize, prm.HSize, w + tx, h + ty);
1144         }
1145 
1146         // Update the progress bar in dialog.
1147         progress = (int)( ( (double)h * (100.0 / QThreadPool::globalInstance()->maxThreadCount()) ) / (prm.stop - prm.start));
1148 
1149         if ((progress % 5 == 0) && (progress > oldProgress))
1150         {
1151             d->lock.lock();
1152             oldProgress       = progress;
1153             d->globalProgress += 5;
1154             postProgress(d->globalProgress);
1155             d->lock.unlock();
1156         }
1157     }
1158 }
1159 
1160 /* Function to apply the tile effect
1161  *
1162  * data             => The image data in RGBA mode.
1163  * Width            => Width of image.
1164  * Height           => Height of image.
1165  * WSize            => Tile Width
1166  * HSize            => Tile Height
1167  * Random           => Maximum random value
1168  *
1169  * Theory           => Similar to Tile effect from Photoshop and very easy to
1170  *                     understand. We get a rectangular area uqSing WSize and HSize and
1171  *                     replace in a position with a random distance from the original
1172  *                     position.
1173  */
tile(DImg * orgImage,DImg * destImage,int WSize,int HSize,int Random)1174 void DistortionFXFilter::tile(DImg* orgImage, DImg* destImage,
1175                               int WSize, int HSize, int Random)
1176 {
1177     if (WSize < 1)
1178     {
1179         WSize = 1;
1180     }
1181 
1182     if (HSize < 1)
1183     {
1184         HSize = 1;
1185     }
1186 
1187     if (Random < 1)
1188     {
1189         Random = 1;
1190     }
1191 
1192     Args prm;
1193     prm.orgImage  = orgImage;
1194     prm.destImage = destImage;
1195     prm.WSize     = WSize;
1196     prm.HSize     = HSize;
1197     prm.Random    = Random;
1198 
1199     d->generator.seed(d->randomSeed);
1200 
1201     QList<int> vals = multithreadedSteps(orgImage->height());
1202     QList <QFuture<void> > tasks;
1203 
1204     for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
1205     {
1206         prm.start = vals[j];
1207         prm.stop  = vals[j+1];
1208         tasks.append(QtConcurrent::run(this,
1209                                        &DistortionFXFilter::tileMultithreaded,
1210                                        prm
1211                                       ));
1212     }
1213 
1214     foreach (QFuture<void> t, tasks)
1215         t.waitForFinished();
1216 }
1217 
1218 /*
1219     This code is shared by six methods.
1220     Write value of pixel w|h in data to pixel nw|nh in pResBits.
1221     Antialias if requested.
1222 */
setPixelFromOther(int Width,int Height,bool sixteenBit,int bytesDepth,uchar * data,uchar * pResBits,int w,int h,double nw,double nh,bool AntiAlias)1223 void DistortionFXFilter::setPixelFromOther(int Width, int Height, bool sixteenBit, int bytesDepth,
1224                                            uchar* data, uchar* pResBits,
1225                                            int w, int h, double nw, double nh, bool AntiAlias)
1226 {
1227     DColor color;
1228     int offset = getOffset(Width, w, h, bytesDepth);
1229 
1230     if (AntiAlias)
1231     {
1232         uchar* const ptr = pResBits + offset;
1233 
1234         if (sixteenBit)
1235         {
1236             unsigned short* ptr16 = reinterpret_cast<unsigned short*>(ptr);
1237             DPixelsAliasFilter().pixelAntiAliasing16(reinterpret_cast<unsigned short*>(data), Width, Height, nw, nh,
1238                                                     ptr16 + 3, ptr16 + 2, ptr16 + 1, ptr16);
1239         }
1240         else
1241         {
1242             DPixelsAliasFilter().pixelAntiAliasing(data, Width, Height, nw, nh,
1243                                                   ptr + 3, ptr + 2, ptr + 1, ptr);
1244         }
1245     }
1246     else
1247     {
1248         // we get the position adjusted
1249         int offsetOther = getOffsetAdjusted(Width, Height, (int)nw, (int)nh, bytesDepth);
1250         // read color
1251         color.setColor(data + offsetOther, sixteenBit);
1252         // write color to destination
1253         color.setPixel(pResBits + offset);
1254     }
1255 }
1256 
filterAction()1257 FilterAction DistortionFXFilter::filterAction()
1258 {
1259     FilterAction action(FilterIdentifier(), CurrentVersion());
1260     action.setDisplayableName(DisplayableName());
1261 
1262     action.addParameter(QLatin1String("antiAlias"), d->antiAlias);
1263     action.addParameter(QLatin1String("type"),      d->effectType);
1264     action.addParameter(QLatin1String("iteration"), d->iteration);
1265     action.addParameter(QLatin1String("level"),     d->level);
1266 
1267     if (d->effectType == Tile)
1268     {
1269         action.addParameter(QLatin1String("randomSeed"), d->randomSeed);
1270     }
1271 
1272     return action;
1273 }
1274 
readParameters(const FilterAction & action)1275 void DistortionFXFilter::readParameters(const FilterAction& action)
1276 {
1277     d->antiAlias  = action.parameter(QLatin1String("antiAlias")).toBool();
1278     d->effectType = action.parameter(QLatin1String("type")).toInt();
1279     d->iteration  = action.parameter(QLatin1String("iteration")).toInt();
1280     d->level      = action.parameter(QLatin1String("level")).toInt();
1281 
1282     if (d->effectType == Tile)
1283     {
1284         d->randomSeed = action.parameter(QLatin1String("randomSeed")).toUInt();
1285     }
1286 }
1287 
1288 } // namespace Digikam
1289