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