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> ×) 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 ¶mName) 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