1 /* ***** BEGIN LICENSE BLOCK *****
2  * This file is part of openfx-misc <https://github.com/devernay/openfx-misc>,
3  * Copyright (C) 2013-2018 INRIA
4  *
5  * openfx-misc is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * openfx-misc is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with openfx-misc.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
17  * ***** END LICENSE BLOCK ***** */
18 
19 /*
20  * OFX SlitScan plugin.
21  */
22 
23 #include <cmath> // for floor
24 #include <climits> // for INT_MAX
25 #include <cassert>
26 #include <set>
27 #include <map>
28 
29 #include "ofxsImageEffect.h"
30 #include "ofxsThreadSuite.h"
31 #include "ofxsMultiThread.h"
32 
33 #include "ofxsPixelProcessor.h"
34 #include "ofxsMaskMix.h"
35 #include "ofxsCoords.h"
36 #include "ofxsCopier.h"
37 #include "ofxsMacros.h"
38 
39 using namespace OFX;
40 
41 OFXS_NAMESPACE_ANONYMOUS_ENTER
42 
43 #define kPluginName "SlitScan"
44 #define kPluginGrouping "Time"
45 #define kPluginDescription \
46     "Apply per-pixel retiming: the time is computed for each pixel from the retime function, which can be either a horizontal ramp, a vertical ramp, or a retime map.\n" \
47     "\n" \
48     "The default retime function corresponds to a horizontal slit: it is a vertical ramp, which is a linear function of y, which is 0 at the center of the bottom image line, and 1 at the center of the top image line. Optionally, a vertical slit may be used (0 at the center of the leftmost image column, 1 at the center of the rightmost image column), or the optional single-channel \"Retime Map\" input may also be used.\n" \
49     "\n" \
50     "This plugin requires to render many frames on input, which may require a lot of memory.\n" \
51     "Note that the results may be on higher quality if the video is slowed fown (e.g. using slowmoVideo)\n" \
52     "\n" \
53     "The parameters are:\n" \
54     "- retime function (default = horizontal slit)\n" \
55     "- offset for the retime function (default = 0)\n" \
56     "- gain for the retime function (default = -10)\n" \
57     "- absolute, a boolean indicating that the time map gives absolute frames rather than relative frames\n" \
58     "- frame range, only used if the retime function is given by a retime map, because the actual frame range cannot be guessed without inspecting the retime map content (default = -10..0). If \"absolute\" is checked, this frame range is absolute, else it is relative to the current frame\n" \
59     "- filter to handle time offsets that \"fall between\" frames. They can be mapped to the nearest frame, or interpolated between the nearest frames (corresponding to a shutter of 1 frame).\n" \
60     "\n" \
61     "References:\n" \
62     "- An Informal Catalogue of Slit-Scan Video Artworks and Research, Golan Levin, http://www.flong.com/texts/lists/slit_scan/"
63 
64 #define kPluginIdentifier "net.sf.openfx.SlitScan"
65 // History:
66 // version 1.0: initial version
67 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
68 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
69 
70 #define kSupportsTiles 1
71 #define kSupportsMultiResolution 1
72 #define kSupportsRenderScale 1
73 #define kSupportsMultipleClipPARs false
74 #define kSupportsMultipleClipDepths false
75 #define kRenderThreadSafety eRenderFullySafe
76 
77 #define kClipRetimeMap "Retime Map"
78 
79 #define kParamRetimeFunction "retimeFunction"
80 #define kParamRetimeFunctionLabel "Retime Function"
81 #define kParamRetimeFunctionHint "The function that gives, for each pixel in the image, its time. The default retime function corresponds to a horizontal slit: it is a vertical ramp (a linear function of y) which is 0 at the center of the bottom image line, and 1 at the center of the top image line. Optionally, a vertical slit may be used (0 at the center of the leftmost image column, 1 at the center of the rightmost image column), or the optional single-channel \"Retime Map\" input may also be used."
82 #define kParamRetimeFunctionOptionHorizontalSlit "Horizontal Slit", "A vertical ramp (a linear function of y) which is 0 at the center of the bottom image line, and 1 at the center of the top image line.", "horizontalslit"
83 #define kParamRetimeFunctionOptionVerticalSlit "Vertical Slit", "A horizontal ramp (alinear function of x) which is 0 at the center of the leftmost image line, and 1 at the center of the rightmost image line.", "verticalslit"
84 #define kParamRetimeFunctionOptionRetimeMap "Retime Map", "The single-channel image from the \"Retime Map\" input (zero if not connected).", "retimemap"
85 #define kParamRetimeFunctionDefault eRetimeFunctionHorizontalSlit
86 enum RetimeFunctionEnum
87 {
88     eRetimeFunctionHorizontalSlit,
89     eRetimeFunctionVerticalSlit,
90     eRetimeFunctionRetimeMap,
91 };
92 
93 #define kParamRetimeOffset "retimeOffset"
94 #define kParamRetimeOffsetLabel "Retime Offset"
95 #define kParamRetimeOffsetHint "Offset to the retime map."
96 #define kParamRetimeOffsetDefault 0.
97 
98 #define kParamRetimeGain "retimeGain"
99 #define kParamRetimeGainLabel "Retime Gain"
100 #define kParamRetimeGainHint "Gain applied to the retime map (after offset). With the horizontal or vertical slits, to get one line or column per frame you should use respectively (height-1) or (width-1)."
101 #define kParamRetimeGainDefault -10
102 
103 #define kParamRetimeAbsolute "retimeAbsolute"
104 #define kParamRetimeAbsoluteLabel "Absolute"
105 #define kParamRetimeAbsoluteHint "If checked, the retime map contains absolute time, if not it is relative to the current frame."
106 #define kParamRetimeAbsoluteDefault false
107 
108 #define kParamFrameRange "frameRange"
109 #define kParamFrameRangeLabel "Max. Frame Range"
110 #define kParamFrameRangeHint "Maximum input frame range to fetch images from (may be relative or absolute, depending on the \"absolute\" parameter). Only used if the Retime Map is used and connected."
111 #define kParamFrameRangeDefault -10, 0
112 
113 #define kParamFilter "filter"
114 #define kParamFilterLabel "Filter"
115 #define kParamFilterHint "How input images are combined to compute the output image."
116 
117 #define kParamFilterOptionNearest "Nearest", "Pick input image with nearest integer time.", "nearest"
118 #define kParamFilterOptionLinear "Linear", "Blend the two nearest images with linear interpolation.", "linear"
119 // TODO:
120 #define kParamFilterOptionBox "Box", "Weighted average of images over the shutter time (shutter time is defined in the output sequence).", "box" // requires shutter parameter
121 
122 enum FilterEnum
123 {
124     //eFilterNone, // nonsensical with SlitScan
125     eFilterNearest,
126     eFilterLinear,
127     //eFilterBox,
128 };
129 
130 #define kParamFilterDefault eFilterNearest
131 
132 class SourceImages
133 {
134 public:
SourceImages(const ImageEffect & effect,Clip * srcClip)135     SourceImages(const ImageEffect& effect,
136                  Clip *srcClip)
137         : _effect(effect)
138         , _srcClip(srcClip)
139     {
140         if (_srcClip && !_srcClip->isConnected()) {
141             _srcClip = NULL;
142         }
143     }
144 
~SourceImages()145     ~SourceImages()
146     {
147         for (ImagesMap::const_iterator it = _images.begin();
148              it != _images.end();
149              ++it) {
150             delete it->second;
151         }
152     }
153 
isConnected() const154     bool isConnected() const
155     {
156         return _srcClip != NULL;
157     }
158 
fetch(double time,bool nofetch=false) const159     const Image* fetch(double time,
160                        bool nofetch = false) const
161     {
162         if (!_srcClip) {
163             return NULL;
164         }
165         ImagesMap::const_iterator it = _images.find(time);
166         if ( it != _images.end() ) {
167             return it->second;
168         }
169         if (nofetch) {
170             assert(false);
171 
172             return NULL;
173         }
174 
175         return _images[time] = _srcClip->fetchImage(time);
176     }
177 
fetchSet(const std::set<double> & times) const178     void fetchSet(const std::set<double> &times) const
179     {
180         for (std::set<double>::const_iterator it = times.begin();
181              it != times.end() && !_effect.abort();
182              ++it) {
183             //printf("fetching %g\n", *it);
184             fetch(*it);
185         }
186     }
187 
188     /** @brief return a pixel pointer, returns NULL if (x,y) is outside the image bounds
189 
190        x and y are in pixel coordinates
191 
192        If the components are custom, then this will return NULL as the support code
193        can't know the pixel size to do the work.
194      */
getPixelAddress(double time,int x,int y) const195     const void * getPixelAddress(double time,
196                                  int x,
197                                  int y) const
198     {
199         const Image* img = fetch(time, true);
200 
201         if (img) {
202             return img->getPixelAddress(x, y);
203         }
204 
205         return NULL;
206     }
207 
208 private:
209     typedef std::map<double, const Image*> ImagesMap;
210     const ImageEffect& _effect;
211     Clip *_srcClip;            /**< @brief Mandated input clips */
212     mutable ImagesMap _images;
213 };
214 
215 class SlitScanProcessorBase;
216 
217 ////////////////////////////////////////////////////////////////////////////////
218 /** @brief The plugin that does our work */
219 class SlitScanPlugin
220     : public ImageEffect
221 {
222 protected:
223     // do not need to delete these, the ImageEffect is managing them for us
224     Clip *_dstClip;            /**< @brief Mandated output clips */
225     Clip *_srcClip;            /**< @brief Mandated input clips */
226     Clip *_retimeMapClip;      /**< @brief Optional retime map */
227     ChoiceParam  *_retimeFunction;
228     DoubleParam  *_retimeOffset;
229     DoubleParam  *_retimeGain;
230     BooleanParam *_retimeAbsolute;
231     Int2DParam *_frameRange;
232     ChoiceParam *_filter;   /**< @brief how images are interpolated (or not). */
233 
234 public:
235     /** @brief ctor */
SlitScanPlugin(OfxImageEffectHandle handle)236     SlitScanPlugin(OfxImageEffectHandle handle)
237         : ImageEffect(handle)
238         , _dstClip(NULL)
239         , _srcClip(NULL)
240         , _retimeMapClip(NULL)
241         , _retimeFunction(NULL)
242         , _retimeOffset(NULL)
243         , _retimeGain(NULL)
244         , _retimeAbsolute(NULL)
245         , _frameRange(NULL)
246         , _filter(NULL)
247     {
248         _dstClip = fetchClip(kOfxImageEffectOutputClipName);
249         _srcClip = fetchClip(kOfxImageEffectSimpleSourceClipName);
250         _retimeMapClip = fetchClip(kClipRetimeMap);
251 
252         _retimeFunction = fetchChoiceParam(kParamRetimeFunction);
253         _retimeOffset = fetchDoubleParam(kParamRetimeOffset);
254         _retimeGain = fetchDoubleParam(kParamRetimeGain);
255         _retimeAbsolute = fetchBooleanParam(kParamRetimeAbsolute);
256         _frameRange = fetchInt2DParam(kParamFrameRange);
257         _filter = fetchChoiceParam(kParamFilter);
258         assert(_retimeFunction && _retimeOffset && _retimeGain && _retimeAbsolute && _frameRange && _filter);
259 
260         // finally
261         syncPrivateData();
262     }
263 
264 private:
265     /* Override the render */
266     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
267 
268     template <int nComponents>
269     void renderForComponents(const RenderArguments &args);
270 
271     /** Override the get frames needed action */
272     virtual void getFramesNeeded(const FramesNeededArguments &args, FramesNeededSetter &frames) OVERRIDE FINAL;
273     virtual bool isIdentity(const IsIdentityArguments &args, Clip * &identityClip, double &identityTime, int& view, std::string& plane) OVERRIDE FINAL;
274     virtual bool getRegionOfDefinition(const RegionOfDefinitionArguments &args, OfxRectD &rod) OVERRIDE FINAL;
changedParam(const InstanceChangedArgs & args,const std::string & paramName)275     virtual void changedParam(const InstanceChangedArgs &args,
276                               const std::string &paramName) OVERRIDE FINAL
277     {
278         if ( (paramName == kParamRetimeFunction) && (args.reason == eChangeUserEdit) ) {
279             updateVisibility();
280         }
281     }
282 
283     /** @brief The sync private data action, called when the effect needs to sync any private data to persistent parameters */
syncPrivateData(void)284     virtual void syncPrivateData(void) OVERRIDE FINAL
285     {
286         updateVisibility();
287     }
288 
289     /* set up and run a processor */
290     void setupAndProcess(SlitScanProcessorBase &, const RenderArguments &args);
291     void getFramesNeededRange(const double time,
292                               OfxRangeD &range);
updateVisibility()293     void updateVisibility()
294     {
295         RetimeFunctionEnum retimeFunction = (RetimeFunctionEnum)_retimeFunction->getValue();
296 
297         _frameRange->setEnabled(retimeFunction == eRetimeFunctionRetimeMap);
298     }
299 };
300 
301 
302 ////////////////////////////////////////////////////////////////////////////////
303 /** @brief render for the filter */
304 
305 ////////////////////////////////////////////////////////////////////////////////
306 // basic plugin render function, just a skelington to instantiate templates from
307 
308 void
getFramesNeeded(const FramesNeededArguments & args,FramesNeededSetter & frames)309 SlitScanPlugin::getFramesNeeded(const FramesNeededArguments &args,
310                                 FramesNeededSetter &frames)
311 {
312     if (!_srcClip || !_srcClip->isConnected()) {
313         return;
314     }
315     const double time = args.time;
316     OfxRangeD range = {time, time};
317     getFramesNeededRange(time, range);
318     frames.setFramesNeeded(*_srcClip, range);
319 }
320 
321 void
getFramesNeededRange(const double time,OfxRangeD & range)322 SlitScanPlugin::getFramesNeededRange(const double time,
323                                      OfxRangeD &range)
324 {
325     double tmin, tmax;
326     bool retimeAbsolute = _retimeAbsolute->getValueAtTime(time);
327     RetimeFunctionEnum retimeFunction = (RetimeFunctionEnum)_retimeFunction->getValueAtTime(time);
328 
329     if (retimeFunction == eRetimeFunctionRetimeMap) {
330         int t1, t2;
331         _frameRange->getValueAtTime(time, t1, t2);
332         if (retimeAbsolute) {
333             tmin = std::min(t1, t2);
334             tmax = std::max(t1, t2);
335         } else {
336             tmin = time + std::min(t1, t2);
337             tmax = time + std::max(t1, t2);
338         }
339     } else {
340         double retimeOffset = _retimeOffset->getValueAtTime(time);
341         double retimeGain = _retimeGain->getValueAtTime(time);
342         tmin = (retimeGain >  0) ? retimeOffset : retimeOffset + retimeGain;
343         tmax = (retimeGain <= 0) ? retimeOffset : retimeOffset + retimeGain;
344         if (!retimeAbsolute) {
345             tmin += time;
346             tmax += time;
347         }
348         FilterEnum filter = (FilterEnum)_filter->getValueAtTime(time);
349         if (filter == eFilterNearest) {
350             tmin = std::floor(tmin + 0.5);
351             tmax = std::floor(tmax + 0.5);
352         } else if (filter == eFilterLinear) {
353             tmin = std::floor(tmin);
354             tmax = std::ceil(tmax);
355         }
356     }
357 
358     range.min = tmin;
359     range.max = tmax;
360 }
361 
362 bool
isIdentity(const IsIdentityArguments & args,Clip * & identityClip,double & identityTime,int &,std::string &)363 SlitScanPlugin::isIdentity(const IsIdentityArguments &args,
364                            Clip * &identityClip,
365                            double &identityTime
366                            , int& /*view*/, std::string& /*plane*/)
367 {
368     const double time = args.time;
369     double retimeGain = _retimeGain->getValueAtTime(time);
370     RetimeFunctionEnum retimeFunction = (RetimeFunctionEnum)_retimeFunction->getValueAtTime(time);
371 
372     if ( (retimeFunction == eRetimeFunctionRetimeMap) && !( _retimeMapClip && _retimeMapClip->isConnected() ) ) {
373         // no retime map, equivalent to value = 0 everywhere
374         retimeGain = 0.;
375     }
376 
377     if (retimeGain == 0.) {
378         double retimeOffset = _retimeOffset->getValueAtTime(time);
379         bool retimeAbsolute = _retimeAbsolute->getValueAtTime(time);
380         identityTime = retimeAbsolute ? retimeOffset : (time + retimeOffset);
381         if (identityTime != (int)identityTime) {
382             FilterEnum filter = (FilterEnum)_filter->getValueAtTime(time);
383             if (filter == eFilterNearest) {
384                 identityTime = std::floor(identityTime + 0.5);
385             } else {
386                 return false; // result is blended
387             }
388         }
389         identityClip = _srcClip;
390 
391         return true;
392     }
393 
394     return false;
395 }
396 
397 bool
getRegionOfDefinition(const RegionOfDefinitionArguments & args,OfxRectD & rod)398 SlitScanPlugin::getRegionOfDefinition(const RegionOfDefinitionArguments &args,
399                                       OfxRectD &rod)
400 {
401     const double time = args.time;
402     double retimeGain;
403 
404     _retimeGain->getValueAtTime(time, retimeGain);
405 
406     RetimeFunctionEnum retimeFunction = (RetimeFunctionEnum)_retimeFunction->getValueAtTime(time);
407     if ( (retimeFunction == eRetimeFunctionRetimeMap) && !( _retimeMapClip && _retimeMapClip->isConnected() ) ) {
408         // no retime map, equivalent to value = 0 everywhere
409         retimeGain = 0.;
410     }
411 
412     if (retimeGain == 0.) {
413         double retimeOffset;
414         _retimeOffset->getValueAtTime(time, retimeOffset);
415         bool retimeAbsolute;
416         _retimeAbsolute->getValueAtTime(time, retimeAbsolute);
417         double identityTime = retimeAbsolute ? retimeOffset : (time + retimeOffset);
418         if (identityTime != (int)identityTime) {
419             FilterEnum filter = (FilterEnum)_filter->getValueAtTime(time);
420             if (filter == eFilterNearest) {
421                 identityTime = std::floor(identityTime + 0.5);
422             } else {
423                 return false; // result is blended
424             }
425         }
426         rod = _srcClip->getRegionOfDefinition(identityTime);
427 
428         return true;
429     }
430 
431     return false;
432 }
433 
434 // build the set of times needed to render renderWindow
435 template<class PIX, int maxValue>
436 void
buildTimes(const ImageEffect & effect,const Image * retimeMap,double time,const OfxRectI & renderWindow,double retimeGain,double retimeOffset,bool retimeAbsolute,FilterEnum filter,std::set<double> * sourceImagesTimes)437 buildTimes(const ImageEffect& effect,
438            const Image* retimeMap,
439            double time,
440            const OfxRectI& renderWindow,
441            double retimeGain,
442            double retimeOffset,
443            bool retimeAbsolute,
444            FilterEnum filter,
445            std::set<double> *sourceImagesTimes)
446 {
447     for (int y = renderWindow.y1; y < renderWindow.y2; ++y) {
448         if ( effect.abort() ) {
449             return;
450         }
451         for (int x = renderWindow.x1; x < renderWindow.x2; ++x) {
452             PIX* mapPix = retimeGain != 0. ? (PIX*)retimeMap->getPixelAddress(x, y) : NULL;
453             double mapVal = mapPix ? (double)(*mapPix) / maxValue  : 0.;
454             double srcTime = retimeGain * mapVal + retimeOffset;
455             if (!retimeAbsolute) {
456                 srcTime += time;
457             }
458             if (srcTime == (int)srcTime) {
459                 sourceImagesTimes->insert(srcTime);
460             } else {
461                 if (filter == eFilterNearest) {
462                     sourceImagesTimes->insert( std::floor(srcTime + 0.5) );
463                 } else {
464                     sourceImagesTimes->insert( std::floor(srcTime) );
465                     sourceImagesTimes->insert( std::ceil(srcTime) );
466                 }
467             }
468         }
469     }
470 }
471 
472 void
buildTimesSlit(double time,double a,double b,double retimeGain,double retimeOffset,bool retimeAbsolute,FilterEnum filter,std::set<double> * sourceImagesTimes)473 buildTimesSlit(double time,
474                double a,
475                double b,
476                double retimeGain,
477                double retimeOffset,
478                bool retimeAbsolute,
479                FilterEnum filter,
480                std::set<double> *sourceImagesTimes)
481 {
482     a = a * retimeGain + retimeOffset;
483     b = b * retimeGain + retimeOffset;
484     if (!retimeAbsolute) {
485         a += time;
486         b += time;
487     }
488     if (a > b) {
489         std::swap(a, b);
490     }
491     int tmin, tmax;
492     switch (filter) {
493     case eFilterNearest: {
494         tmin = (int)std::floor(a + 0.5);
495         tmax = (int)std::floor(b + 0.5);
496         break;
497     }
498     case eFilterLinear: {
499         tmin = (int)std::floor(a);
500         tmax = (int)std::ceil(b);
501         break;
502     }
503     }
504     for (int t = tmin; t <= tmax; ++t) {
505         sourceImagesTimes->insert(t);
506     }
507 }
508 
509 class SlitScanProcessorBase
510     : public PixelProcessor
511 {
512 protected:
513     const SourceImages *_sourceImages;
514     const Image *_retimeMap;
515     double _time;
516     FilterEnum _filter;
517     RetimeFunctionEnum _retimeFunction;
518     double _retimeGain;
519     double _retimeOffset;
520     bool _retimeAbsolute;
521     OfxRectI _srcRoDPixel;
522 
523 public:
524     /** @brief no arg ctor */
SlitScanProcessorBase(ImageEffect & instance)525     SlitScanProcessorBase(ImageEffect &instance)
526         : PixelProcessor(instance)
527         , _sourceImages(NULL)
528         , _retimeMap(NULL)
529         , _time(0.)
530         , _filter(eFilterNearest)
531         , _retimeFunction(eRetimeFunctionHorizontalSlit)
532         , _retimeGain(1.)
533         , _retimeOffset(0.)
534         , _retimeAbsolute(false)
535     {
536         _srcRoDPixel.x1 = _srcRoDPixel.y1 = _srcRoDPixel.x2 = _srcRoDPixel.y2 = 0;
537     }
538 
539     /** @brief set the src images */
setSourceImages(const SourceImages * v)540     void setSourceImages(const SourceImages *v) {_sourceImages = v->isConnected() ? v : NULL;}
541 
setRetimeMap(const Image * v)542     void setRetimeMap(const Image *v)   {_retimeMap = v;}
543 
setValues(double time,FilterEnum filter,RetimeFunctionEnum retimeFunction,double retimeGain,double retimeOffset,bool retimeAbsolute,const OfxRectI & srcRoDPixel)544     void setValues(double time,
545                    FilterEnum filter,
546                    RetimeFunctionEnum retimeFunction,
547                    double retimeGain,
548                    double retimeOffset,
549                    bool retimeAbsolute,
550                    const OfxRectI& srcRoDPixel)
551     {
552         _time = time;
553         _filter = filter;
554         _retimeFunction = retimeFunction;
555         _retimeGain = retimeGain;
556         _retimeOffset = retimeOffset;
557         _retimeAbsolute = retimeAbsolute;
558         _srcRoDPixel = srcRoDPixel;
559     }
560 };
561 
562 /** @brief templated class to blend between two images */
563 template <class PIX, int nComponents, int maxValue>
564 class SlitScanProcessor
565     : public SlitScanProcessorBase
566 {
567 public:
568     // ctor
SlitScanProcessor(ImageEffect & instance)569     SlitScanProcessor(ImageEffect &instance)
570         : SlitScanProcessorBase(instance)
571     {}
572 
Lerp(const PIX & v1,const PIX & v2,float blend)573     static PIX Lerp(const PIX &v1,
574                     const PIX &v2,
575                     float blend)
576     {
577         return PIX( (v2 - v1) * blend + v1 );
578     }
579 
580     // and do some processing
multiThreadProcessImages(OfxRectI procWindow)581     void multiThreadProcessImages(OfxRectI procWindow)
582     {
583         for (int y = procWindow.y1; y < procWindow.y2; y++) {
584             if ( _effect.abort() ) {break;}
585 
586             PIX *dstPix = (PIX *)getDstPixelAddress(procWindow.x1, y);
587             assert(dstPix);
588             if (!dstPix) {
589                 return;
590             }
591             for (int x = procWindow.x1; x < procWindow.x2; x++) {
592                 double retimeVal;
593                 switch (_retimeFunction) {
594                 case eRetimeFunctionHorizontalSlit: {
595                     // A vertical ramp (a linear function of y) which is 0 at the center of the bottom image line, and 1 at the center of the top image line.
596                     retimeVal = (double)(y - _srcRoDPixel.y1) / (_srcRoDPixel.y2 - 1 - _srcRoDPixel.y1);
597                     break;
598                 }
599                 case eRetimeFunctionVerticalSlit: {
600                     // A horizontal ramp (alinear function of x) which is 0 at the center of the leftmost image line, and 1 at the center of the rightmost image line."
601                     retimeVal = (double)(x - _srcRoDPixel.x1) / (_srcRoDPixel.x2 - 1 - _srcRoDPixel.x1);
602                     break;
603                 }
604                 case eRetimeFunctionRetimeMap: {
605                     PIX *retimePix = _retimeMap ? (PIX *)_retimeMap->getPixelAddress(x, y) : NULL;
606                     retimeVal = retimePix ? ( (double)*retimePix / maxValue ) : 0.;
607                     break;
608                 }
609                 }
610                 retimeVal = retimeVal * _retimeGain + _retimeOffset;
611                 if (!_retimeAbsolute) {
612                     retimeVal += _time;
613                 }
614                 if ( (_filter == eFilterNearest) || (retimeVal == (int)retimeVal) ) {
615                     retimeVal = std::floor(retimeVal + 0.5);
616                     PIX* srcPix = _sourceImages ? (PIX*)_sourceImages->getPixelAddress(retimeVal, x, y) : NULL;
617                     if (srcPix) {
618                         std::copy(srcPix, srcPix + nComponents, dstPix);
619                     } else {
620                         std::fill( dstPix, dstPix + nComponents, PIX() );
621                     }
622                 } else {
623                     PIX* fromPix = _sourceImages ? (PIX*)_sourceImages->getPixelAddress(std::floor(retimeVal), x, y) : NULL;
624                     PIX* toPix = _sourceImages ? (PIX*)_sourceImages->getPixelAddress(std::ceil(retimeVal), x, y) : NULL;
625                     float blend = retimeVal - std::floor(retimeVal);
626                     float blendComp = 1.f - blend;
627 
628                     if (fromPix && toPix) {
629                         for (int c = 0; c < nComponents; c++) {
630                             dstPix[c] = Lerp(fromPix[c], toPix[c], blend);
631                         }
632                     } else if (fromPix)    {
633                         for (int c = 0; c < nComponents; c++) {
634                             dstPix[c] = PIX(fromPix[c] * blendComp);
635                         }
636                     } else if (toPix)    {
637                         for (int c = 0; c < nComponents; c++) {
638                             dstPix[c] = PIX(toPix[c] * blend);
639                         }
640                     } else   {
641                         std::fill( dstPix, dstPix + nComponents, PIX() );
642                     }
643                 }
644 
645                 dstPix += nComponents;
646             }
647         }
648     } // multiThreadProcessImages
649 };
650 
651 /* set up and run a processor */
652 void
setupAndProcess(SlitScanProcessorBase & processor,const RenderArguments & args)653 SlitScanPlugin::setupAndProcess(SlitScanProcessorBase &processor,
654                                 const RenderArguments &args)
655 {
656     const double time = args.time;
657 
658     // get a dst image
659     auto_ptr<Image>  dst( _dstClip->fetchImage(time) );
660 
661     if ( !dst.get() ) {
662         throwSuiteStatusException(kOfxStatFailed);
663     }
664     BitDepthEnum dstBitDepth    = dst->getPixelDepth();
665     PixelComponentEnum dstComponents  = dst->getPixelComponents();
666     if ( ( dstBitDepth != _dstClip->getPixelDepth() ) ||
667          ( dstComponents != _dstClip->getPixelComponents() ) ) {
668         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
669         throwSuiteStatusException(kOfxStatFailed);
670     }
671     if ( (dst->getRenderScale().x != args.renderScale.x) ||
672          ( dst->getRenderScale().y != args.renderScale.y) ||
673          ( ( dst->getField() != eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
674         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
675         throwSuiteStatusException(kOfxStatFailed);
676     }
677 
678     double retimeGain = _retimeGain->getValueAtTime(time);
679     RetimeFunctionEnum retimeFunction = (RetimeFunctionEnum)_retimeFunction->getValueAtTime(time);
680     FilterEnum filter = (FilterEnum)_filter->getValueAtTime(time);
681     double retimeOffset = _retimeOffset->getValueAtTime(time);
682     bool retimeAbsolute = _retimeAbsolute->getValueAtTime(time);
683 
684     if ( (retimeFunction == eRetimeFunctionRetimeMap) && !( _retimeMapClip && _retimeMapClip->isConnected() ) ) {
685         // no retime map, equivalent to value = 0 everywhere
686         retimeGain = 0.;
687     }
688 
689     if (retimeGain == 0.) {
690         double identityTime = retimeAbsolute ? retimeOffset : (time + retimeOffset);
691         if (identityTime != (int)identityTime) {
692             if (filter == eFilterNearest) {
693                 identityTime = std::floor(identityTime + 0.5);
694             }
695         }
696         if (identityTime == (int)identityTime) {
697             // should have been caught by isIdentity...
698             auto_ptr<const Image> src( ( _srcClip && _srcClip->isConnected() ) ?
699                                             _srcClip->fetchImage(identityTime) : 0 );
700             if ( src.get() ) {
701                 if ( (src->getRenderScale().x != args.renderScale.x) ||
702                      ( src->getRenderScale().y != args.renderScale.y) ||
703                      ( ( src->getField() != eFieldNone) /* for DaVinci Resolve */ && ( src->getField() != args.fieldToRender) ) ) {
704                     setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
705                     throwSuiteStatusException(kOfxStatFailed);
706                 }
707                 BitDepthEnum srcBitDepth      = src->getPixelDepth();
708                 PixelComponentEnum srcComponents = src->getPixelComponents();
709                 if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
710                     throwSuiteStatusException(kOfxStatErrImageFormat);
711                 }
712             }
713             copyPixels( *this, args.renderWindow, src.get(), dst.get() );
714 
715             return;
716         }
717     }
718 
719 
720     // set the images
721     processor.setDstImg( dst.get() );
722 
723     OfxRectI srcRoDPixel = {0, 0, 0, 0};
724     if (_srcClip) {
725         const OfxRectD& srcRod = _srcClip->getRegionOfDefinition(time);
726         Coords::toPixelEnclosing(srcRod, args.renderScale, _srcClip->getPixelAspectRatio(), &srcRoDPixel);
727     }
728 
729     SourceImages sourceImages(*this, _srcClip);
730     std::set<double> sourceImagesTimes;
731 
732     auto_ptr<const Image> retimeMap;
733 
734     switch (retimeFunction) {
735     case eRetimeFunctionHorizontalSlit: {
736         double a = (double)(args.renderWindow.y1 - srcRoDPixel.y1) / (srcRoDPixel.y2 - 1 - srcRoDPixel.y1);
737         double b = (double)(args.renderWindow.y2 - 1 - srcRoDPixel.y1) / (srcRoDPixel.y2 - 1 - srcRoDPixel.y1);
738         buildTimesSlit(time, a, b, retimeGain, retimeOffset, retimeAbsolute, filter, &sourceImagesTimes);
739         break;
740     }
741     case eRetimeFunctionVerticalSlit: {
742         double a = (double)(args.renderWindow.x1 - srcRoDPixel.y1) / (srcRoDPixel.x2 - 1 - srcRoDPixel.x1);
743         double b = (double)(args.renderWindow.x2 - 1 - srcRoDPixel.x1) / (srcRoDPixel.x2 - 1 - srcRoDPixel.x1);
744         buildTimesSlit(time, a, b, retimeGain, retimeOffset, retimeAbsolute, filter, &sourceImagesTimes);
745         break;
746     }
747     case eRetimeFunctionRetimeMap: {
748         if ( ( retimeGain != 0.) && _retimeMapClip && _retimeMapClip->isConnected() ) {
749             retimeMap.reset( _retimeMapClip->fetchImage(time) );
750         }
751         if ( !retimeMap.get() ) {
752             // empty retimeMap or no gain, we need only one or two images
753             double identityTime = retimeAbsolute ? retimeOffset : (time + retimeOffset);
754             if (identityTime == (int)identityTime) {
755                 sourceImagesTimes.insert(identityTime);
756             } else {
757                 if (filter == eFilterNearest) {
758                     sourceImagesTimes.insert( std::floor(identityTime + 0.5) );
759                 } else {
760                     sourceImagesTimes.insert( std::floor(identityTime) );
761                     sourceImagesTimes.insert( std::ceil(identityTime) );
762                 }
763             }
764         } else {
765             retimeMap.reset(_retimeMapClip ? _retimeMapClip->fetchImage(time) : NULL);
766             assert(retimeMap->getPixelComponents() == ePixelComponentAlpha);
767             processor.setRetimeMap( retimeMap.get() );
768 
769             // scan the renderWindow in map, and gather the necessary image ids
770             // (could be done in a processor)
771             BitDepthEnum retimeMapDepth = retimeMap->getPixelDepth();
772             switch (retimeMapDepth) {
773             case eBitDepthUByte:
774                 buildTimes<unsigned char, 255>(*this,
775                                                retimeMap.get(),
776                                                time,
777                                                args.renderWindow,
778                                                retimeGain,
779                                                retimeOffset,
780                                                retimeAbsolute,
781                                                filter,
782                                                &sourceImagesTimes);
783                 break;
784             case eBitDepthUShort:
785                 buildTimes<unsigned short, 65535>(*this,
786                                                   retimeMap.get(),
787                                                   time,
788                                                   args.renderWindow,
789                                                   retimeGain,
790                                                   retimeOffset,
791                                                   retimeAbsolute,
792                                                   filter,
793                                                   &sourceImagesTimes);
794                 break;
795             case eBitDepthFloat:
796                 buildTimes<float, 1>(*this,
797                                      retimeMap.get(),
798                                      time,
799                                      args.renderWindow,
800                                      retimeGain,
801                                      retimeOffset,
802                                      retimeAbsolute,
803                                      filter,
804                                      &sourceImagesTimes);
805                 break;
806             default:
807                 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
808                 throwSuiteStatusException(kOfxStatFailed);
809                 break;
810             }
811         }
812         break;
813     }
814     } // switch
815     if ( abort() ) {
816         return;
817     }
818     sourceImages.fetchSet(sourceImagesTimes);
819     if ( abort() ) {
820         return;
821     }
822     // set the render window
823     processor.setRenderWindow(args.renderWindow);
824 
825     // set the blend between
826     processor.setSourceImages(&sourceImages);
827 
828 
829     processor.setValues(time, filter, retimeFunction, retimeGain, retimeOffset, retimeAbsolute, srcRoDPixel);
830 
831     // Call the base class process member, this will call the derived templated process code
832     processor.process();
833 } // SlitScanPlugin::setupAndProcessSlitScanLinear
834 
835 template <int nComponents>
836 void
renderForComponents(const RenderArguments & args)837 SlitScanPlugin::renderForComponents(const RenderArguments &args)
838 {
839     BitDepthEnum dstBitDepth    = _dstClip->getPixelDepth();
840 
841     switch (dstBitDepth) {
842     case eBitDepthUByte: {
843         SlitScanProcessor<unsigned char, nComponents, 255> fred(*this);
844         setupAndProcess(fred, args);
845         break;
846     }
847     case eBitDepthUShort: {
848         SlitScanProcessor<unsigned short, nComponents, 65535> fred(*this);
849         setupAndProcess(fred, args);
850         break;
851     }
852     case eBitDepthFloat: {
853         SlitScanProcessor<float, nComponents, 1> fred(*this);
854         setupAndProcess(fred, args);
855         break;
856     }
857     default:
858         throwSuiteStatusException(kOfxStatErrUnsupported);
859     }
860 }
861 
862 // the overridden render function
863 void
render(const RenderArguments & args)864 SlitScanPlugin::render(const RenderArguments &args)
865 {
866     // instantiate the render code based on the pixel depth of the dst clip
867     PixelComponentEnum dstComponents  = _dstClip->getPixelComponents();
868 
869     assert( kSupportsMultipleClipPARs   || !_srcClip || _srcClip->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
870     assert( kSupportsMultipleClipDepths || !_srcClip || _srcClip->getPixelDepth()       == _dstClip->getPixelDepth() );
871 
872     // do the rendering
873     if (dstComponents == ePixelComponentRGBA) {
874         renderForComponents<4>(args);
875     } else if (dstComponents == ePixelComponentRGB) {
876         renderForComponents<3>(args);
877 #ifdef OFX_EXTENSIONS_NATRON
878     } else if (dstComponents == ePixelComponentXY) {
879         renderForComponents<2>(args);
880 #endif
881     } else if (dstComponents == ePixelComponentAlpha) {
882         renderForComponents<1>(args);
883     } else {
884         throwSuiteStatusException(kOfxStatFailed);
885     }
886 }
887 
888 mDeclarePluginFactory(SlitScanPluginFactory,; , {});
889 void
load()890 SlitScanPluginFactory::load()
891 {
892     ofxsThreadSuiteCheck();
893     // we can't be used on hosts that don't perfrom temporal clip access
894     if (!getImageEffectHostDescription()->temporalClipAccess) {
895         throw Exception::HostInadequate("Need random temporal image access to work");
896     }
897 }
898 
899 /** @brief The basic describe function, passed a plugin descriptor */
900 void
describe(ImageEffectDescriptor & desc)901 SlitScanPluginFactory::describe(ImageEffectDescriptor &desc)
902 {
903     // basic labels
904     desc.setLabel(kPluginName);
905     desc.setPluginGrouping(kPluginGrouping);
906     desc.setPluginDescription(kPluginDescription);
907 
908     desc.addSupportedContext(eContextFilter);
909     desc.addSupportedContext(eContextGeneral);
910 
911     // Add supported pixel depths
912     desc.addSupportedBitDepth(eBitDepthUByte);
913     desc.addSupportedBitDepth(eBitDepthUShort);
914     desc.addSupportedBitDepth(eBitDepthFloat);
915 
916     // set a few flags
917     desc.setSingleInstance(false);
918     desc.setHostFrameThreading(true); // specific to SlitScan: host frame threading helps us: we require less input images to render a small area
919     desc.setSupportsMultiResolution(kSupportsMultiResolution);
920     desc.setSupportsTiles(kSupportsTiles);
921     desc.setTemporalClipAccess(true); // say we will be doing random time access on clips
922     desc.setRenderTwiceAlways(true); // each field has to be rendered separately, since it may come from a different time
923     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
924     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
925     desc.setRenderThreadSafety(kRenderThreadSafety);
926 
927     // we can't be used on hosts that don't perfrom temporal clip access
928     if (!getImageEffectHostDescription()->temporalClipAccess) {
929         throw Exception::HostInadequate("Need random temporal image access to work");
930     }
931 #ifdef OFX_EXTENSIONS_NATRON
932     desc.setChannelSelector(ePixelComponentNone);
933 #endif
934 }
935 
936 /** @brief The describe in context function, passed a plugin descriptor and a context */
937 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum)938 SlitScanPluginFactory::describeInContext(ImageEffectDescriptor &desc,
939                                          ContextEnum /*context*/)
940 {
941     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
942 
943     srcClip->addSupportedComponent(ePixelComponentRGBA);
944     srcClip->addSupportedComponent(ePixelComponentRGB);
945 #ifdef OFX_EXTENSIONS_NATRON
946     srcClip->addSupportedComponent(ePixelComponentXY);
947 #endif
948     srcClip->addSupportedComponent(ePixelComponentAlpha);
949     srcClip->setTemporalClipAccess(true); // say we will be doing random time access on this clip
950     srcClip->setSupportsTiles(kSupportsTiles);
951     srcClip->setFieldExtraction(eFieldExtractDoubled); // which is the default anyway
952 
953     ClipDescriptor *retimeMapClip = desc.defineClip(kClipRetimeMap);
954     retimeMapClip->addSupportedComponent(ePixelComponentAlpha);
955     retimeMapClip->setSupportsTiles(kSupportsTiles);
956     retimeMapClip->setOptional(true);
957     retimeMapClip->setIsMask(false);
958 
959     // create the mandated output clip
960     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
961     dstClip->addSupportedComponent(ePixelComponentRGBA);
962     dstClip->addSupportedComponent(ePixelComponentRGB);
963 #ifdef OFX_EXTENSIONS_NATRON
964     dstClip->addSupportedComponent(ePixelComponentXY);
965 #endif
966     dstClip->addSupportedComponent(ePixelComponentAlpha);
967     dstClip->setFieldExtraction(eFieldExtractDoubled); // which is the default anyway
968     dstClip->setSupportsTiles(kSupportsTiles);
969 
970     // make a page to put it in
971     PageParamDescriptor *page = desc.definePageParam("Controls");
972 
973     {
974         ChoiceParamDescriptor *param = desc.defineChoiceParam(kParamRetimeFunction);
975         param->setLabel(kParamRetimeFunctionLabel);
976         param->setHint(kParamRetimeFunctionHint);
977         assert(param->getNOptions() == eRetimeFunctionHorizontalSlit);
978         param->appendOption(kParamRetimeFunctionOptionHorizontalSlit);
979         assert(param->getNOptions() == eRetimeFunctionVerticalSlit);
980         param->appendOption(kParamRetimeFunctionOptionVerticalSlit);
981         assert(param->getNOptions() == eRetimeFunctionRetimeMap);
982         param->appendOption(kParamRetimeFunctionOptionRetimeMap);
983         param->setDefault( (int)kParamRetimeFunctionDefault );
984         if (page) {
985             page->addChild(*param);
986         }
987     }
988 
989     {
990         DoubleParamDescriptor *param = desc.defineDoubleParam(kParamRetimeOffset);
991         param->setDefault(kParamRetimeOffsetDefault);
992         param->setHint(kParamRetimeOffsetHint);
993         param->setLabel(kParamRetimeOffsetLabel);
994         param->setAnimates(true);
995         if (page) {
996             page->addChild(*param);
997         }
998     }
999     {
1000         DoubleParamDescriptor *param = desc.defineDoubleParam(kParamRetimeGain);
1001         param->setDefault(kParamRetimeGainDefault);
1002         param->setHint(kParamRetimeGainHint);
1003         param->setLabel(kParamRetimeGainLabel);
1004         param->setAnimates(true);
1005         if (page) {
1006             page->addChild(*param);
1007         }
1008     }
1009     {
1010         BooleanParamDescriptor *param = desc.defineBooleanParam(kParamRetimeAbsolute);
1011         param->setDefault(kParamRetimeAbsoluteDefault);
1012         param->setHint(kParamRetimeAbsoluteHint);
1013         param->setLabel(kParamRetimeAbsoluteLabel);
1014         if (page) {
1015             page->addChild(*param);
1016         }
1017     }
1018     {
1019         Int2DParamDescriptor *param = desc.defineInt2DParam(kParamFrameRange);
1020         param->setDefault(kParamFrameRangeDefault);
1021         param->setHint(kParamFrameRangeHint);
1022         param->setLabel(kParamFrameRangeLabel);
1023         param->setDimensionLabels("min", "max");
1024         if (page) {
1025             page->addChild(*param);
1026         }
1027     }
1028 
1029     {
1030         ChoiceParamDescriptor *param = desc.defineChoiceParam(kParamFilter);
1031         param->setLabel(kParamFilterLabel);
1032         param->setHint(kParamFilterHint);
1033         assert(param->getNOptions() == eFilterNearest);
1034         param->appendOption(kParamFilterOptionNearest);
1035         assert(param->getNOptions() == eFilterLinear);
1036         param->appendOption(kParamFilterOptionLinear);
1037         //assert(param->getNOptions() == eFilterBox);
1038         //param->appendOption(kParamFilterOptionBox, kParamFilterOptionBoxHint);
1039         param->setDefault( (int)kParamFilterDefault );
1040         if (page) {
1041             page->addChild(*param);
1042         }
1043     }
1044 } // SlitScanPluginFactory::describeInContext
1045 
1046 /** @brief The create instance function, the plugin must return an object derived from the \ref ImageEffect class */
1047 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)1048 SlitScanPluginFactory::createInstance(OfxImageEffectHandle handle,
1049                                       ContextEnum /*context*/)
1050 {
1051     return new SlitScanPlugin(handle);
1052 }
1053 
1054 static SlitScanPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
1055 mRegisterPluginFactoryInstance(p)
1056 
1057 OFXS_NAMESPACE_ANONYMOUS_EXIT
1058 
1059