1 /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3  * This file is part of openfx-supportext <https://github.com/devernay/openfx-supportext>,
4  * Copyright (C) 2013-2018 INRIA
5  *
6  * openfx-supportext is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * openfx-supportext is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with openfx-supportext.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
18  * ***** END LICENSE BLOCK ***** */
19 
20 /*
21  * OFX Transform plugin.
22  */
23 
24 #ifndef MISC_TRANSFORMPROCESSOR_H
25 #define MISC_TRANSFORMPROCESSOR_H
26 
27 #include <algorithm>
28 
29 #include "ofxsProcessing.H"
30 #include "ofxsMatrix2D.h"
31 #include "ofxsFilter.h"
32 #include "ofxsMaskMix.h"
33 #include "ofxsMacros.h"
34 
35 // constants for the motion blur algorithm (may depend on _motionblur)
36 #define kTransform3x3ProcessorMotionBlurMaxError (_motionblur * maxValue / 1000.)
37 #define kTransform3x3ProcessorMotionBlurMinIterations ( (std::max)( 13, (int)(kTransform3x3ProcessorMotionBlurMaxIterations / 3) ) )
38 #define kTransform3x3ProcessorMotionBlurMaxIterations ( (int)(_motionblur * 40) )
39 
40 namespace OFX {
41 class Transform3x3ProcessorBase
42     : public OFX::ImageProcessor
43 {
44 protected:
45     const OFX::Image *_srcImg;
46     const OFX::Image *_maskImg;
47     // NON-GENERIC PARAMETERS:
48     const OFX::Matrix3x3* _invtransform; // the set of transforms to sample from (in PIXEL coords)
49     const double* _invtransformalpha; // blending factor for each tranform, or NULL for uniform blending
50     size_t _invtransformsize;
51     // GENERIC PARAMETERS:
52     bool _blackOutside;
53     double _motionblur; // quality of the motion blur. 0 means disabled
54     bool _domask;
55     double _mix;
56     bool _maskInvert;
57 
58 public:
59 
Transform3x3ProcessorBase(OFX::ImageEffect & instance)60     Transform3x3ProcessorBase(OFX::ImageEffect &instance)
61         : OFX::ImageProcessor(instance)
62         , _srcImg(NULL)
63         , _maskImg(NULL)
64         , _invtransform()
65         , _invtransformalpha(NULL)
66         , _invtransformsize(0)
67         , _blackOutside(false)
68         , _motionblur(0.)
69         , _domask(false)
70         , _mix(1.0)
71         , _maskInvert(false)
72     {
73     }
74 
75     virtual FilterEnum getFilter() const = 0;
76     virtual bool getClamp() const = 0;
77 
78     /** @brief set the src image */
setSrcImg(const OFX::Image * v)79     void setSrcImg(const OFX::Image *v)
80     {
81         _srcImg = v;
82     }
83 
84     /** @brief set the optional mask image */
setMaskImg(const OFX::Image * v,bool maskInvert)85     void setMaskImg(const OFX::Image *v,
86                     bool maskInvert)
87     {
88         _maskImg = v; _maskInvert = maskInvert;
89     }
90 
91     // Are we masking. We can't derive this from the mask image being set as NULL is a valid value for an input image
doMasking(bool v)92     void doMasking(bool v)
93     {
94         _domask = v;
95     }
96 
setValues(const OFX::Matrix3x3 * invtransform,double * invtransformalpha,size_t invtransformsize,bool blackOutside,double motionblur,double mix)97     void setValues(const OFX::Matrix3x3* invtransform, //!< non-generic - must be in PIXEL coords
98                    double* invtransformalpha,
99                    size_t invtransformsize,
100                    // all generic parameters below
101                    bool blackOutside, //!< generic
102                    double motionblur,
103                    double mix)          //!< generic
104     {
105         // NON-GENERIC
106         assert(invtransform);
107         _invtransform = invtransform;
108         _invtransformalpha = invtransformalpha;
109         _invtransformsize = invtransformsize;
110         // GENERIC
111         _blackOutside = blackOutside;
112         _motionblur = motionblur;
113         _mix = mix;
114     }
115 };
116 
117 
118 // The "masked", "filter" and "clamp" template parameters allow filter-specific optimization
119 // by the compiler, using the same generic code for all filters.
120 template <class PIX, int nComponents, int maxValue, bool masked, FilterEnum filter, bool clamp>
121 class Transform3x3Processor
122     : public Transform3x3ProcessorBase
123 {
124 public:
Transform3x3Processor(OFX::ImageEffect & instance)125     Transform3x3Processor(OFX::ImageEffect &instance)
126         : Transform3x3ProcessorBase(instance)
127     {
128     }
129 
130 private:
getFilter()131     virtual FilterEnum getFilter() const OVERRIDE FINAL
132     {
133         return filter;
134     }
135 
getClamp()136     virtual bool getClamp() const OVERRIDE FINAL
137     {
138         return clamp;
139     }
140 
multiThreadProcessImages(OfxRectI procWindow)141     void multiThreadProcessImages(OfxRectI procWindow) OVERRIDE
142     {
143         assert(_invtransform);
144         if (_motionblur == 0.) { // no motion blur
145             return multiThreadProcessImagesNoBlur(procWindow);
146         } else { // motion blur
147             return multiThreadProcessImagesMotionBlur(procWindow);
148         }
149     } // multiThreadProcessImages
150 
151 private:
multiThreadProcessImagesNoBlur(const OfxRectI & procWindow)152     void multiThreadProcessImagesNoBlur(const OfxRectI &procWindow)
153     {
154         float tmpPix[nComponents];
155         const OFX::Matrix3x3 & H = _invtransform[0];
156         const int x1 = _srcImg ? _srcImg->getBounds().x1 : 0;
157         const int x2 = _srcImg ? _srcImg->getBounds().x2 : 0;
158         const int y1 = _srcImg ? _srcImg->getBounds().y1 : 0;
159         const int y2 = _srcImg ? _srcImg->getBounds().y2 : 0;
160 
161         for (int y = procWindow.y1; y < procWindow.y2; ++y) {
162             if ( _effect.abort() ) {
163                 break;
164             }
165 
166             PIX *dstPix = (PIX *) _dstImg->getPixelAddress(procWindow.x1, y);
167 
168             // the coordinates of the center of the pixel in canonical coordinates
169             // see http://openfx.sourceforge.net/Documentation/1.3/ofxProgrammingReference.html#CanonicalCoordinates
170             OFX::Point3D canonicalCoords;
171             canonicalCoords.z = 1;
172             canonicalCoords.y = (double)y + 0.5;
173 
174             for (int x = procWindow.x1; x < procWindow.x2; ++x, dstPix += nComponents) {
175                 // NON-GENERIC TRANSFORM
176 
177                 // the coordinates of the center of the pixel in canonical coordinates
178                 // see http://openfx.sourceforge.net/Documentation/1.3/ofxProgrammingReference.html#CanonicalCoordinates
179                 canonicalCoords.x = (double)x + 0.5;
180                 OFX::Point3D transformed = H * canonicalCoords;
181                 if ( !_srcImg || (transformed.z <= 0.) ) {
182                     // the back-transformed point is at infinity (==0) or behind the camera (<0)
183                     for (int c = 0; c < nComponents; ++c) {
184                         tmpPix[c] = 0;
185                     }
186                 } else {
187                     double fx = transformed.z != 0 ? transformed.x / transformed.z : transformed.x;
188                     double fy = transformed.z != 0 ? transformed.y / transformed.z : transformed.y;
189                     if (filter == eFilterImpulse) {
190                         ofxsFilterInterpolate2D<PIX, nComponents, filter, clamp>(fx, fy, _srcImg, _blackOutside, tmpPix);
191                     } else {
192                         bool xinside = (x1 <= fx + 0.5 && fx - 0.5 < x2);
193                         bool yinside = (y1 <= fy + 0.5 && fy - 0.5 < y2);
194                         if ( _blackOutside && !(xinside && yinside) ) {
195                             xinside = yinside = false;
196                         }
197 
198                         double Jxx = xinside ? (H(0,0) * transformed.z - transformed.x * H(2,0)) / (transformed.z * transformed.z) : 0.;
199                         double Jxy = xinside ? (H(0,1) * transformed.z - transformed.x * H(2,1)) / (transformed.z * transformed.z) : 0.;
200                         double Jyx = yinside ? (H(1,0) * transformed.z - transformed.y * H(2,0)) / (transformed.z * transformed.z) : 0;
201                         double Jyy = yinside ? (H(1,1) * transformed.z - transformed.y * H(2,1)) / (transformed.z * transformed.z) : 0.;
202                         ofxsFilterInterpolate2DSuper<PIX, nComponents, filter, clamp>(fx, fy, Jxx, Jxy, Jyx, Jyy, _srcImg, _blackOutside, tmpPix);
203                     }
204                 }
205 
206                 ofxsMaskMix<PIX, nComponents, maxValue, masked>(tmpPix, x, y, _srcImg, _domask, _maskImg, (float)_mix, _maskInvert, dstPix);
207             }
208         }
209     } // multiThreadProcessImagesNoBlur
210 
multiThreadProcessImagesMotionBlur(const OfxRectI & procWindow)211     void multiThreadProcessImagesMotionBlur(const OfxRectI &procWindow)
212     {
213         float tmpPix[nComponents];
214         const double maxErr2 = kTransform3x3ProcessorMotionBlurMaxError * kTransform3x3ProcessorMotionBlurMaxError; // maximum expected squared error
215         const int maxIt = kTransform3x3ProcessorMotionBlurMaxIterations; // maximum number of iterations
216         const int x1 = _srcImg ? _srcImg->getBounds().x1 : 0;
217         const int x2 = _srcImg ? _srcImg->getBounds().x2 : 0;
218         const int y1 = _srcImg ? _srcImg->getBounds().y1 : 0;
219         const int y2 = _srcImg ? _srcImg->getBounds().y2 : 0;
220 
221         // Monte Carlo integration, starting with at least 13 regularly spaced samples, and then low discrepancy
222         // samples from the van der Corput sequence.
223         for (int y = procWindow.y1; y < procWindow.y2; ++y) {
224             if ( _effect.abort() ) {
225                 break;
226             }
227 
228             PIX *dstPix = (PIX *) _dstImg->getPixelAddress(procWindow.x1, y);
229 
230             // the coordinates of the center of the pixel in canonical coordinates
231             // see http://openfx.sourceforge.net/Documentation/1.3/ofxProgrammingReference.html#CanonicalCoordinates
232             OFX::Point3D canonicalCoords;
233             canonicalCoords.z = 1;
234             canonicalCoords.y = (double)y + 0.5;
235 
236             for (int x = procWindow.x1; x < procWindow.x2; ++x, dstPix += nComponents) {
237                 double acc;
238                 double accPix[nComponents];
239                 double accPix2[nComponents];
240                 double mean[nComponents];
241                 double var[nComponents];
242                 for (int c = 0; c < nComponents; ++c) {
243                     acc = 0.;
244                     accPix[c] = 0;
245                     accPix2[c] = 0;
246                     mean[c] = 0.;
247                     var[c] = (double)maxValue * maxValue;
248                 }
249                 unsigned int seed = (unsigned int)( hash(hash( x + (unsigned int)(0x10000 * _motionblur) ) + y) );
250                 int sample = 0;
251                 const int minsamples = kTransform3x3ProcessorMotionBlurMinIterations; // minimum number of samples (at most maxIt/3
252                 int maxsamples = minsamples;
253                 while (sample < maxsamples) {
254                     for (; sample < maxsamples; ++sample, ++seed) {
255                         //int t = 0.5*(van_der_corput<2>(seed1) + van_der_corput<3>(seed2)) * _invtransform.size();
256                         int t;
257                         if (sample < minsamples) {
258                             // distribute the first samples evenly over the interval
259                             t = (int)( ( sample  + van_der_corput<2>(seed) ) * _invtransformsize / (double)minsamples );
260                         } else {
261                             t = (int)(van_der_corput<2>(seed) * _invtransformsize);
262                         }
263                         // NON-GENERIC TRANSFORM
264 
265                         // the coordinates of the center of the pixel in canonical coordinates
266                         // see http://openfx.sourceforge.net/Documentation/1.3/ofxProgrammingReference.html#CanonicalCoordinates
267                         canonicalCoords.x = (double)x + 0.5;
268                         const OFX::Matrix3x3& H = _invtransform[t];
269                         OFX::Point3D transformed = H * canonicalCoords;
270                         if ( !_srcImg || (transformed.z <= 0.) ) {
271                             // the back-transformed point is at infinity (==0) or behind the camera (<0)
272                             for (int c = 0; c < nComponents; ++c) {
273                                 tmpPix[c] = 0;
274                             }
275                         } else {
276                             double fx = transformed.z != 0 ? transformed.x / transformed.z : transformed.x;
277                             double fy = transformed.z != 0 ? transformed.y / transformed.z : transformed.y;
278                             if (filter == eFilterImpulse) {
279                                 ofxsFilterInterpolate2D<PIX, nComponents, filter, clamp>(fx, fy, _srcImg, _blackOutside, tmpPix);
280                             } else {
281                                 bool xinside = (x1 <= fx + 0.5 && fx - 0.5 < x2);
282                                 bool yinside = (y1 <= fy + 0.5 && fy - 0.5 < y2);
283                                 if ( _blackOutside && !(xinside && yinside) ) {
284                                     xinside = yinside = false;
285                                 }
286 
287                                 double Jxx = xinside ? (H(0,0) * transformed.z - transformed.x * H(2,0)) / (transformed.z * transformed.z) : 0.;
288                                 double Jxy = xinside ? (H(0,1) * transformed.z - transformed.x * H(2,1)) / (transformed.z * transformed.z) : 0.;
289                                 double Jyx = yinside ? (H(1,0) * transformed.z - transformed.y * H(2,0)) / (transformed.z * transformed.z) : 0;
290                                 double Jyy = yinside ? (H(1,1) * transformed.z - transformed.y * H(2,1)) / (transformed.z * transformed.z) : 0.;
291                                 ofxsFilterInterpolate2DSuper<PIX, nComponents, filter, clamp>(fx, fy, Jxx, Jxy, Jyx, Jyy, _srcImg, _blackOutside, tmpPix);
292                             }
293                         }
294                         if (!_invtransformalpha) {
295                             for (int c = 0; c < nComponents; ++c) {
296                                 accPix[c] += tmpPix[c];
297                                 accPix2[c] += tmpPix[c] * tmpPix[c];
298                             }
299                         } else {
300                             acc += _invtransformalpha[t];
301                             for (int c = 0; c < nComponents; ++c) {
302                                 accPix[c] += tmpPix[c] * _invtransformalpha[t];
303                                 accPix2[c] += tmpPix[c] * tmpPix[c] * _invtransformalpha[t];
304                             }
305                         }
306                     }
307                     if (!_invtransformalpha) {
308                         // compute mean and variance (unbiased)
309                         for (int c = 0; c < nComponents; ++c) {
310                             mean[c] = accPix[c] / sample;
311                             if (sample <= 1) {
312                                 var[c] = (double)maxValue * maxValue;
313                             } else {
314                                 var[c] = (accPix2[c] - mean[c] * mean[c] * sample) / (sample - 1);
315                                 // the variance of the mean is var[c]/n, so compute n so that it falls below some threashold (maxErr2).
316                                 // Note that this could be improved/optimized further by variance reduction and importance sampling
317                                 // http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-17-monte-carlo-methods-in-practice/variance-reduction-methods-a-quick-introduction-to-importance-sampling/
318                                 // http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-xx-introduction-to-importance-sampling/
319                                 // The threshold is computed by a simple rule of thumb:
320                                 // - the error should be less than motionblur*maxValue/100
321                                 // - the total number of iterations should be less than motionblur*100
322                                 if (maxsamples < maxIt) {
323                                     maxsamples = (std::max)( maxsamples, (std::min)( (int)(var[c] / maxErr2), maxIt ) );
324                                 }
325                             }
326                         }
327                     } else if (acc > 0.) {
328                         // compute mean and variance (biased)
329                         for (int c = 0; c < nComponents; ++c) {
330                             mean[c] = accPix[c] / acc;
331                             if (sample <= 1) {
332                                 var[c] = (double)maxValue * maxValue;
333                             } else {
334                                 var[c] = accPix2[c] / acc - mean[c] * mean[c];
335                                 // the variance of the mean is var[c]/n, so compute n so that it falls below some threashold (maxErr2).
336                                 // Note that this could be improved/optimized further by variance reduction and importance sampling
337                                 // http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-17-monte-carlo-methods-in-practice/variance-reduction-methods-a-quick-introduction-to-importance-sampling/
338                                 // http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-xx-introduction-to-importance-sampling/
339                                 // The threshold is computed by a simple rule of thumb:
340                                 // - the error should be less than motionblur*maxValue/100
341                                 // - the total number of iterations should be less than motionblur*100
342                                 if (maxsamples < maxIt) {
343                                     maxsamples = (std::max)( maxsamples, (std::min)( (int)(var[c] / maxErr2), maxIt ) );
344                                 }
345                             }
346                         }
347                     }
348                 }
349                 for (int c = 0; c < nComponents; ++c) {
350                     tmpPix[c] = (float)mean[c];
351                 }
352                 ofxsMaskMix<PIX, nComponents, maxValue, masked>(tmpPix, x, y, _srcImg, _domask, _maskImg, (float)_mix, _maskInvert, dstPix);
353             }
354         }
355     } // multiThreadProcessImagesMotionBlur
356 
357     // Compute the /seed/th element of the van der Corput sequence
358     // see http://en.wikipedia.org/wiki/Van_der_Corput_sequence
359     template <int base>
van_der_corput(unsigned int seed)360     double van_der_corput(unsigned int seed)
361     {
362         double base_inv;
363         int digit;
364         double r;
365 
366         r = 0.0;
367 
368         base_inv = 1.0 / ( (double)base );
369 
370         while (seed != 0) {
371             digit = seed % base;
372             r = r + ( (double)digit ) * base_inv;
373             base_inv = base_inv / ( (double)base );
374             seed = seed / base;
375         }
376 
377         return r;
378     }
379 
hash(unsigned int a)380     unsigned int hash(unsigned int a)
381     {
382         a = (a ^ 61) ^ (a >> 16);
383         a = a + (a << 3);
384         a = a ^ (a >> 4);
385         a = a * 0x27d4eb2d;
386         a = a ^ (a >> 15);
387 
388         return a;
389     }
390 };
391 } // namespace OFX
392 
393 #endif // MISC_TRANSFORM_H
394