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