1 /* ***** BEGIN LICENSE BLOCK *****
2  * This file is part of openfx-io <https://github.com/MrKepzie/openfx-io>,
3  * Copyright (C) 2013-2018 INRIA
4  *
5  * openfx-io 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-io 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-io.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
17  * ***** END LICENSE BLOCK ***** */
18 
19 /*
20  * OFX GenericReader plugin.
21  * A base class for all OpenFX-based decoders.
22  */
23 
24 #include "GenericReader.h"
25 
26 #include <climits>
27 #include <cmath>
28 #include <cfloat> // DBL_MAX
29 #include <memory>
30 #include <algorithm>
31 #include <fstream>
32 #if defined(DEBUG) && defined(DEBUG_READER)
33 #include <cstdio>
34 #define DBG(x) x
35 #else
36 #define DBG(x) (void)0
37 #endif
38 
39 #ifdef _WIN32
40 #include <windows.h>
41 #endif
42 
43 #include "ofxsLog.h"
44 #include "ofxsCopier.h"
45 #include "ofxsCoords.h"
46 #include "ofxsMacros.h"
47 
48 #ifdef OFX_EXTENSIONS_TUTTLE
49 #include <tuttle/ofxReadWrite.h>
50 #endif
51 #include <ofxNatron.h>
52 #include <ofxsMultiPlane.h>
53 
54 #include "SequenceParsing/SequenceParsing.h"
55 #ifdef OFX_IO_USING_OCIO
56 #include "GenericOCIO.h"
57 #endif
58 #include "IOUtility.h"
59 
60 #ifdef OFX_IO_USING_OCIO
61 namespace OCIO = OCIO_NAMESPACE;
62 #endif
63 
64 using std::string;
65 using std::size_t;
66 
67 NAMESPACE_OFX_ENTER
68 NAMESPACE_OFX_IO_ENTER
69 
70 #define kPluginGrouping "Image/Readers"
71 
72 // in the Reader context, the script name must be kOfxImageEffectFileParamName, @see kOfxImageEffectContextReader
73 #define kParamFilename kOfxImageEffectFileParamName
74 #define kParamFilenameLabel "File"
75 #define kParamFilenameHint "The input image sequence/video stream file(s)."
76 
77 #define kParamProxy kOfxImageEffectProxyParamName
78 #define kParamProxyLabel "Proxy File"
79 #define kParamProxyHint \
80     "Filename of the proxy images. They will be used instead of the images read from the File parameter " \
81     "when the proxy mode (downscaling of the images) is activated."
82 
83 #define kParamProxyThreshold "proxyThreshold"
84 #define kParamProxyThresholdLabel "Proxy threshold"
85 #define kParamProxyThresholdHint \
86     "The scale of the proxy images. By default it will be automatically computed out of the " \
87     "images headers when you set the proxy file(s) path. When the render scale (proxy) is set to " \
88     "a scale lower or equal to this value then the proxy image files will be used instead of the " \
89     "original images. You can change this parameter by checking the \"Custom scale\" checkbox " \
90     "so that you can change the scale at which the proxy images should be used instead of the original images."
91 
92 #define kParamOriginalProxyScale "originalProxyScale"
93 #define kParamOriginalProxyScaleLabel "Original Proxy Scale"
94 #define kParamOriginalProxyScaleHint \
95     "The original scale of the proxy image."
96 
97 #define kParamCustomProxyScale "customProxyScale"
98 #define kParamCustomProxyScaleLabel "Custom Proxy Scale"
99 #define kParamCustomProxyScaleHint \
100     "Check to enable the Proxy scale edition."
101 
102 #define kParamOnMissingFrame "onMissingFrame"
103 #define kParamOnMissingFrameLabel "On Missing Frame"
104 #define kParamOnMissingFrameHint \
105     "What to do when a frame is missing from the sequence/stream."
106 
107 #define kParamFrameMode "frameMode"
108 #define kParamFrameModeLabel "Frame Mode"
109 enum FrameModeEnum
110 {
111     eFrameModeStartingTime,
112     eFrameModeTimeOffset,
113 };
114 
115 #define kParamFrameModeOptionStartingTime "Starting Time", "Set at what output frame the first sequence frame is output. The sequence frame designated by the firstFrame parameter is output at frame timeOffset.", "startingTime"
116 #define kParamFrameModeOptionTimeOffset "Time Offset", "Set an offset to be applied as a number of frames. The sequence frame designated by the firstFrame parameter is output at frame firstFrame+timeOffset.", "timeOffset"
117 
118 #define kParamTimeOffset "timeOffset"
119 #define kParamTimeOffsetLabel "Time Offset"
120 #define kParamTimeOffsetHint \
121     "Offset applied to the sequence in time units (i.e. frames)."
122 
123 #define kParamStartingTime "startingTime"
124 #define kParamStartingTimeLabel "Starting Time"
125 #define kParamStartingTimeHint \
126     "At what time (on the timeline) should this sequence/video start."
127 
128 #define kParamOriginalFrameRange "originalFrameRange"
129 #define kParamOriginalFrameRangeLabel "Original Range"
130 
131 #define kParamFirstFrame "firstFrame"
132 #define kParamFirstFrameLabel "First Frame"
133 #define kParamFirstFrameHint \
134     "The first frame number to read from this image sequence or video file. This cannot be less " \
135     " than the first frame of the image sequence or video file, and cannot be greater than the last" \
136     " frame of the image sequence or video file. The first frame of a video file is numbered 1. " \
137     "If startingTime is 1 or timeOffset is 0, this is also the first output frame."
138 
139 #define kParamLastFrame "lastFrame"
140 #define kParamLastFrameLabel "Last Frame"
141 #define kParamLastFrameHint \
142     "The last frame number to read from this image sequence or video file. This cannot be less " \
143     "than the first frame of the image sequence or video file, and cannot be greater than the last " \
144     "frame of the image sequence or video file. The first frame of a video file is numbered 1. "\
145     "If startingTime is 1 or timeOffset is 0, this is also the last output frame."
146 
147 #define kParamBefore "before"
148 #define kParamBeforeLabel "Before"
149 #define kParamBeforeHint \
150     "What to do before the first frame of the sequence."
151 
152 #define kParamAfter "after"
153 #define kParamAfterLabel "After"
154 #define kParamAfterHint \
155     "What to do after the last frame of the sequence."
156 
157 #define kParamTimeDomainUserEdited "timeDomainUserEdited"
158 
159 
160 enum MissingEnum
161 {
162     eMissingPrevious,
163     eMissingNext,
164     eMissingNearest,
165     eMissingError,
166     eMissingBlack,
167 };
168 
169 #define kReaderOptionHold "Hold", "While before the sequence, load the first frame.", "hold"
170 #define kReaderOptionLoop "Loop", "Repeat the sequence before the first frame", "loop"
171 #define kReaderOptionBounce "Bounce", "Repeat the sequence in reverse before the first frame", "bounce"
172 #define kReaderOptionBlack "Black", "Render a black image", "black"
173 #define kReaderOptionError "Error", "Report an error", "error"
174 #define kReaderOptionPrevious "Hold previous", "Try to load the previous frame in the sequence/stream, if any.", "previous"
175 #define kReaderOptionNext "Load next", "Try to load the next frame in the sequence/stream, if any.", "next"
176 #define kReaderOptionNearest "Load nearest", "Try to load the nearest frame in the sequence/stream, if any.", "nearest"
177 
178 #define kParamFilePremult "filePremult"
179 #define kParamFilePremultLabel "File Premult"
180 #define kParamFilePremultHint \
181     "The image file being read is considered to have this premultiplication state.\n" \
182     "To get UnPremultiplied (or \"unassociated alpha\") images, set the \"Output Premult\" parameter to Unpremultiplied. \n" \
183     "By default the value should be correctly be guessed by the image file, but this parameter can be edited if the metadatas inside the file are wrong.\n" \
184     "- Opaque means that the alpha channel is considered to be 1 (one), and it is not taken into account in colorspace conversion.\n" \
185     "- Premultiplied, red, green and blue channels are divided by the alpha channel " \
186     "before applying the colorspace conversion, and re-multiplied by alpha after colorspace conversion.\n" \
187     "- UnPremultiplied, means that red, green and blue channels are not modified " \
188     "before applying the colorspace conversion, and are multiplied by alpha after colorspace conversion.\n" \
189     "This is set automatically from the image file and the plugin, but can be adjusted if this information is wrong in the file metadata.\n" \
190     "RGB images can only be Opaque, and Alpha images can only be Premultiplied (the value of this parameter doesn't matter)."
191 #define kParamFilePremultOptionOpaqueHint \
192     "The image is opaque and so has no premultiplication state, as if the alpha component in all pixels were set to the white point.", "opaque"
193 #define kParamFilePremultOptionPreMultipliedHint \
194     "The image is premultiplied by its alpha (also called \"associated alpha\").", "premult"
195 #define kParamFilePremultOptionUnPreMultipliedHint \
196     "The image is unpremultiplied (also called \"unassociated alpha\").", "unpremult"
197 
198 #define kParamOutputPremult "outputPremult"
199 #define kParamOutputPremultLabel "Output Premult"
200 #define kParamOutputPremultHint "The alpha premultiplication in output of this node will have this state."
201 
202 #define kParamOutputComponents "outputComponents"
203 #define kParamOutputComponentsLabel "Output Components"
204 #define kParamOutputComponentsHint "What type of components this effect should output when the main color plane is requested." \
205     " For the Read node it will map (in number of components) the Output Layer choice to these."
206 #define kParamOutputComponentsOptionRGBA "RGBA"
207 #define kParamOutputComponentsOptionRGB "RGB"
208 #define kParamOutputComponentsOptionXY "RG"
209 #define kParamOutputComponentsOptionAlpha "Alpha"
210 
211 #define kParamInputSpaceLabel "File Colorspace"
212 
213 #define kParamFrameRate "frameRate"
214 #define kParamFrameRateLabel "Frame rate"
215 #define kParamFrameRateHint "By default this value is guessed from the file. You can override it by checking the Custom fps parameter. " \
216     "The value of this parameter is what will be visible by the effects down-stream."
217 
218 #define kParamCustomFps "customFps"
219 #define kParamCustomFpsLabel "Custom FPS"
220 #define kParamCustomFpsHint "If checked, you can freely force the value of the frame rate parameter. The frame-rate is just the meta-data that will be passed " \
221     "downstream to the graph, no retime will actually take place."
222 
223 #define kParamGuessedParams "ParamExistingInstance" // was guessParamsFromFilename already successfully called once on this instance
224 
225 #ifdef OFX_IO_USING_OCIO
226 #define kParamInputSpaceSet "ocioInputSpaceSet" // was the input colorspace set by user?
227 #endif
228 
229 #define MISSING_FRAME_NEAREST_RANGE 100
230 
231 #define kSupportsMultiResolution 1
232 #define kSupportsRenderScale 1 // GenericReader supports render scale: it scales images and uses proxy image when applicable
233 
234 #define GENERIC_READER_USE_MULTI_THREAD
235 
236 static bool gHostIsNatron   = false;
237 static bool gHostSupportsRGBA   = false;
238 static bool gHostSupportsRGB    = false;
239 static bool gHostSupportsXY    = false;
240 static bool gHostSupportsAlpha  = false;
241 static const char*
premultString(PreMultiplicationEnum e)242 premultString(PreMultiplicationEnum e)
243 {
244     switch (e) {
245     case eImageOpaque:
246 
247         return "Opaque";
248     case eImagePreMultiplied:
249 
250         return "PreMultiplied";
251     case eImageUnPreMultiplied:
252 
253         return "UnPreMultiplied";
254     }
255 
256     return "Unknown";
257 }
258 
GenericReaderPlugin(OfxImageEffectHandle handle,const std::vector<string> & extensions,bool supportsRGBA,bool supportsRGB,bool supportsXY,bool supportsAlpha,bool supportsTiles,bool isMultiPlanar)259 GenericReaderPlugin::GenericReaderPlugin(OfxImageEffectHandle handle,
260                                          const std::vector<string>& extensions,
261                                          bool supportsRGBA,
262                                          bool supportsRGB,
263                                          bool supportsXY,
264                                          bool supportsAlpha,
265                                          bool supportsTiles,
266                                          bool isMultiPlanar)
267     : ImageEffect(handle)
268     , _missingFrameParam(NULL)
269 #ifdef OFX_IO_USING_OCIO
270     , _inputSpaceSet(NULL)
271     , _ocio( new GenericOCIO(this) )
272 #endif
273     , _syncClip(NULL)
274     , _outputClip(NULL)
275     , _fileParam(NULL)
276     , _firstFrame(NULL)
277     , _timeOffset(NULL)
278     , _startingTime(NULL)
279     , _originalFrameRange(NULL)
280     , _proxyFileParam(NULL)
281     , _proxyThreshold(NULL)
282     , _originalProxyScale(NULL)
283     , _enableCustomScale(NULL)
284     , _beforeFirst(NULL)
285     , _lastFrame(NULL)
286     , _afterLast(NULL)
287     , _frameMode(NULL)
288     , _outputComponents(NULL)
289     , _filePremult(NULL)
290     , _outputPremult(NULL)
291     , _timeDomainUserSet(NULL)
292     , _customFPS(NULL)
293     , _fps(NULL)
294     , _sublabel(NULL)
295     , _guessedParams(NULL)
296     , _extensions(extensions)
297     , _supportsRGBA(supportsRGBA)
298     , _supportsRGB(supportsRGB)
299     , _supportsXY(supportsXY)
300     , _supportsAlpha(supportsAlpha)
301     , _supportsTiles(supportsTiles)
302     , _isMultiPlanar(isMultiPlanar)
303 {
304     _syncClip = fetchClip(kOfxImageEffectSimpleSourceClipName);
305     _outputClip = fetchClip(kOfxImageEffectOutputClipName);
306 
307     _fileParam = fetchStringParam(kParamFilename);
308     _proxyFileParam = fetchStringParam(kParamProxy);
309     _proxyThreshold = fetchDouble2DParam(kParamProxyThreshold);
310     _originalProxyScale = fetchDouble2DParam(kParamOriginalProxyScale);
311     _enableCustomScale = fetchBooleanParam(kParamCustomProxyScale);
312     _missingFrameParam = fetchChoiceParam(kParamOnMissingFrame);
313     _firstFrame = fetchIntParam(kParamFirstFrame);
314     _beforeFirst = fetchChoiceParam(kParamBefore);
315     _lastFrame = fetchIntParam(kParamLastFrame);
316     _afterLast = fetchChoiceParam(kParamAfter);
317     _frameMode = fetchChoiceParam(kParamFrameMode);
318     _timeOffset = fetchIntParam(kParamTimeOffset);
319     _startingTime = fetchIntParam(kParamStartingTime);
320     _originalFrameRange = fetchInt2DParam(kParamOriginalFrameRange);
321     _timeDomainUserSet = fetchBooleanParam(kParamTimeDomainUserEdited);
322     _outputComponents = fetchChoiceParam(kParamOutputComponents);
323     _filePremult = fetchChoiceParam(kParamFilePremult);
324     _outputPremult = fetchChoiceParam(kParamOutputPremult);
325     _customFPS = fetchBooleanParam(kParamCustomFps);
326     _fps = fetchDoubleParam(kParamFrameRate);
327     if (gHostIsNatron) {
328         _sublabel = fetchStringParam(kNatronOfxParamStringSublabelName);
329         assert(_sublabel);
330     }
331     _guessedParams = fetchBooleanParam(kParamGuessedParams);
332 
333 #ifdef OFX_IO_USING_OCIO
334     _inputSpaceSet = fetchBooleanParam(kParamInputSpaceSet);
335 #endif
336 
337     // must be in sync with GenericReaderDescribeInContextBegin
338     int i = 0;
339 
340     if (gHostSupportsRGBA && supportsRGBA) {
341         _outputComponentsTable[i] = ePixelComponentRGBA;
342         ++i;
343     }
344     if (gHostSupportsRGB && supportsRGB) {
345         _outputComponentsTable[i] = ePixelComponentRGB;
346         ++i;
347     }
348     if (gHostSupportsXY && supportsXY) {
349         _outputComponentsTable[i] = ePixelComponentXY;
350         ++i;
351     }
352 
353     if (gHostSupportsAlpha && supportsAlpha) {
354         _outputComponentsTable[i] = ePixelComponentAlpha;
355         ++i;
356     }
357     _outputComponentsTable[i] = ePixelComponentNone;
358 }
359 
~GenericReaderPlugin()360 GenericReaderPlugin::~GenericReaderPlugin()
361 {
362 }
363 
364 void
refreshSubLabel(OfxTime time)365 GenericReaderPlugin::refreshSubLabel(OfxTime time)
366 {
367     assert(_sublabel);
368     double sequenceTime;
369     GetSequenceTimeRetEnum getTimeRet = getSequenceTime(time, &sequenceTime);
370     if ( (getTimeRet == eGetSequenceTimeWithinSequence) ||
371          ( getTimeRet == eGetSequenceTimeBeforeSequence) ||
372          ( getTimeRet == eGetSequenceTimeAfterSequence) ) {
373         string filename;
374         GetFilenameRetCodeEnum getFileNameRet = getFilenameAtSequenceTime(sequenceTime, false, false, &filename);
375         if (getFileNameRet == eGetFileNameReturnedFullRes) {
376             _sublabel->setValue( basename(filename) );
377         } else {
378             _sublabel->setValue("");
379         }
380     } else {
381         _sublabel->setValue("");
382     }
383 }
384 
385 /**
386  * @brief Restore any state from the parameters set
387  * Called from createInstance() and changedParam() (via changedFilename()), must restore the
388  * state of the Reader, such as Choice param options, data members and non-persistent param values.
389  * We don't do this in the ctor of the plug-in since we can't call virtuals yet.
390  * Any derived implementation must call GenericReaderPlugin::restoreStateFromParams() first
391  **/
392 void
restoreStateFromParams()393 GenericReaderPlugin::restoreStateFromParams()
394 {
395     int frameMode_i;
396 
397     _frameMode->getValue(frameMode_i);
398     FrameModeEnum frameMode = FrameModeEnum(frameMode_i);
399     switch (frameMode) {
400     case eFrameModeStartingTime:     //starting frame
401         _startingTime->setIsSecretAndDisabled(false);
402         _timeOffset->setIsSecretAndDisabled(true);
403         break;
404     case eFrameModeTimeOffset:     //time offset
405         _startingTime->setIsSecretAndDisabled(true);
406         _timeOffset->setIsSecretAndDisabled(false);
407         break;
408     }
409 
410     ///Detect the scale of the proxy.
411     string proxyFile;
412     _proxyFileParam->getValue(proxyFile);
413     if ( !proxyFile.empty() ) {
414         _proxyThreshold->setIsSecretAndDisabled(false);
415         _enableCustomScale->setIsSecretAndDisabled(false);
416     } else {
417         _proxyThreshold->setIsSecretAndDisabled(true);
418         _enableCustomScale->setIsSecretAndDisabled(true);
419     }
420 
421     bool customFps = _customFPS->getValue();
422     _fps->setEnabled(customFps);
423 
424     if (gHostIsNatron) {
425         refreshSubLabel( timeLineGetTime() );
426     }
427 }
428 
429 bool
getTimeDomain(OfxRangeD & range)430 GenericReaderPlugin::getTimeDomain(OfxRangeD &range)
431 {
432     OfxRangeI rangeI;
433     bool ret = getSequenceTimeDomainInternal(rangeI, false);
434 
435     if (ret) {
436         ///these are the value held by the "First frame" and "Last frame" param
437         OfxRangeI sequenceTimeDomain;
438         sequenceTimeDomain.min = _firstFrame->getValue();
439         sequenceTimeDomain.max = _lastFrame->getValue();
440         int startingTime = _startingTime->getValue();
441         timeDomainFromSequenceTimeDomain(sequenceTimeDomain, startingTime, &rangeI);
442         range.min = rangeI.min;
443         range.max = rangeI.max;
444     }
445 
446     return ret;
447 }
448 
449 bool
getSequenceTimeDomainInternal(OfxRangeI & range,bool canSetOriginalFrameRange)450 GenericReaderPlugin::getSequenceTimeDomainInternal(OfxRangeI& range,
451                                                    bool canSetOriginalFrameRange)
452 {
453     ////first-off check if the original frame range param has valid values, in which
454     ///case we don't bother calculating the frame range
455     int originalMin, originalMax;
456 
457     _originalFrameRange->getValue(originalMin, originalMax);
458 
459     // test if the host (probably Natron) set kParamOriginalFrameRange
460     if ( (originalMin != INT_MIN) && (originalMax != INT_MAX) ) {
461         range.min = originalMin;
462         range.max = originalMax;
463 
464         return true;
465     }
466 
467     string filename;
468     _fileParam->getValue(filename);
469 
470     ///call the plugin specific getTimeDomain (if it is a video-stream , it is responsible to
471     ///find-out the time domain. If this function return false, it means this is an image sequence
472     ///in which case our sequence parser will give us the sequence range
473     bool gotRange = getSequenceTimeDomain(filename, range);
474 
475     if (!gotRange) {
476         // POOR MAN's solution: try to guess the range
477 
478         SequenceParsing::FileNameContent content(filename);
479         string pattern;
480         ///We try to match all the files in the same directory that match the pattern with the frame number
481         ///assumed to be in the last part of the filename. This is a harsh assumption but we can't just verify
482         ///everything as it would take too much time.
483         string noStr;
484         int nbFrameNumbers = content.getPotentialFrameNumbersCount();
485         content.getNumberByIndex(nbFrameNumbers - 1, &noStr);
486 
487         int numHashes = content.getLeadingZeroes();
488         string noStrWithoutZeroes;
489         for (std::size_t i = 0; i < noStr.size(); ++i) {
490             if ( (noStr[i] == '0') && noStrWithoutZeroes.empty() ) {
491                 continue;
492             }
493             noStrWithoutZeroes.push_back(noStr[i]);
494         }
495 
496         if ( (int)noStr.size() > numHashes ) {
497             numHashes += noStrWithoutZeroes.size();
498         } else {
499             numHashes = 1;
500         }
501         content.generatePatternWithFrameNumberAtIndex(nbFrameNumbers - 1,
502                                                       numHashes,
503                                                       &pattern);
504 
505 
506         SequenceParsing::SequenceFromPattern sequenceFromFiles;
507         SequenceParsing::filesListFromPattern_slow(pattern, &sequenceFromFiles);
508 
509         range.min = range.max = 1;
510         if (sequenceFromFiles.size() > 1) {
511             range.min = sequenceFromFiles.begin()->first;
512             range.max = sequenceFromFiles.rbegin()->first;
513         }
514     }
515 
516 
517     //// From http://openfx.sourceforge.net/Documentation/1.3/ofxProgrammingReference.html#SettingParams
518     //    Plugins are free to set parameters in limited set of circumstances, typically relating to user interaction. You can only set parameters in the following actions passed to the plug-in's main entry function...
519     //
520     //    The Create Instance Action
521     //    The The Begin Instance Changed Action
522     //    The The Instance Changed Action
523     //    The The End Instance Changed Action
524     //    The The Sync Private Data Action
525     if (!filename.empty() && canSetOriginalFrameRange) {
526         _originalFrameRange->setValue(range.min, range.max);
527     }
528 
529     return true;
530 } // GenericReaderPlugin::getSequenceTimeDomainInternal
531 
532 void
timeDomainFromSequenceTimeDomain(const OfxRangeI & sequenceTimeDomain,int startingTime,OfxRangeI * timeDomain)533 GenericReaderPlugin::timeDomainFromSequenceTimeDomain(const OfxRangeI& sequenceTimeDomain,
534                                                       int startingTime,
535                                                       OfxRangeI* timeDomain)
536 {
537     timeDomain->min = startingTime;
538     timeDomain->max = startingTime + (sequenceTimeDomain.max - sequenceTimeDomain.min);
539 }
540 
541 GenericReaderPlugin::GetSequenceTimeRetEnum
getSequenceTimeBefore(const OfxRangeI & sequenceTimeDomain,BeforeAfterEnum beforeChoice,double * sequenceTime) const542 GenericReaderPlugin::getSequenceTimeBefore(const OfxRangeI& sequenceTimeDomain,
543                                            BeforeAfterEnum beforeChoice,
544                                            double *sequenceTime) const
545 {
546     ///get the offset from the starting time of the sequence in case we bounce or loop
547     int timeOffsetFromStart = (int)*sequenceTime -  sequenceTimeDomain.min;
548     int seqFrames = sequenceTimeDomain.max - sequenceTimeDomain.min + 1;
549 
550     switch (beforeChoice) {
551     case eBeforeAfterHold:     //hold
552         *sequenceTime = sequenceTimeDomain.min;
553 
554         return eGetSequenceTimeBeforeSequence;
555 
556     case eBeforeAfterLoop: {     //loop
557         if (seqFrames <= 1) {
558             *sequenceTime = sequenceTimeDomain.min;
559         } else {
560             // positive modulo
561             timeOffsetFromStart = (timeOffsetFromStart % seqFrames + seqFrames) % seqFrames;
562             *sequenceTime = sequenceTimeDomain.min + timeOffsetFromStart;
563         }
564 
565         return eGetSequenceTimeBeforeSequence;
566     }
567     case eBeforeAfterBounce: {     //bounce
568         if (seqFrames <= 1) {
569             *sequenceTime = sequenceTimeDomain.min;
570         } else {
571             // number of frames in a loop
572             int loopFrames = seqFrames * 2 - 2;
573             // positive modulo
574             timeOffsetFromStart = (timeOffsetFromStart % loopFrames + loopFrames) % loopFrames;
575             // bounce
576             if (timeOffsetFromStart >= seqFrames) {
577                 timeOffsetFromStart = loopFrames - timeOffsetFromStart;
578             }
579             *sequenceTime = sequenceTimeDomain.min + timeOffsetFromStart;
580         }
581 
582         return eGetSequenceTimeBeforeSequence;
583     }
584     case eBeforeAfterBlack:     //black
585         return eGetSequenceTimeBlack;
586 
587     case eBeforeAfterError:     //error
588 
589         //setPersistentMessage(Message::eMessageError, "", "Out of frame range");
590         return eGetSequenceTimeError;
591     }
592 
593     return eGetSequenceTimeError;
594 }
595 
596 GenericReaderPlugin::GetSequenceTimeRetEnum
getSequenceTimeAfter(const OfxRangeI & sequenceTimeDomain,BeforeAfterEnum afterChoice,double * sequenceTime) const597 GenericReaderPlugin::getSequenceTimeAfter(const OfxRangeI& sequenceTimeDomain,
598                                           BeforeAfterEnum afterChoice,
599                                           double *sequenceTime) const
600 {
601     ///get the offset from the starting time of the sequence in case we bounce or loop
602     int timeOffsetFromStart = (int)*sequenceTime -  sequenceTimeDomain.min;
603     int seqFrames = sequenceTimeDomain.max - sequenceTimeDomain.min + 1;
604 
605     switch (afterChoice) {
606     case eBeforeAfterHold: {     //hold
607         *sequenceTime = sequenceTimeDomain.max;
608 
609         return eGetSequenceTimeAfterSequence;
610     }
611     case eBeforeAfterLoop: {     //loop
612         if (seqFrames <= 1) {
613             *sequenceTime = sequenceTimeDomain.min;
614         } else {
615             // positive modulo
616             timeOffsetFromStart = (timeOffsetFromStart % seqFrames + seqFrames) % seqFrames;
617             *sequenceTime = sequenceTimeDomain.min + timeOffsetFromStart;
618         }
619 
620         return eGetSequenceTimeAfterSequence;
621     }
622     case eBeforeAfterBounce: {     //bounce
623         if (seqFrames <= 1) {
624             *sequenceTime = sequenceTimeDomain.min;
625         } else {
626             // number of frames in a loop
627             int loopFrames = seqFrames * 2 - 2;
628             // positive modulo
629             timeOffsetFromStart = (timeOffsetFromStart % loopFrames + loopFrames) % loopFrames;
630             // bounce
631             if (timeOffsetFromStart >= seqFrames) {
632                 timeOffsetFromStart = loopFrames - timeOffsetFromStart;
633             }
634             *sequenceTime = sequenceTimeDomain.min + timeOffsetFromStart;
635         }
636 
637         return eGetSequenceTimeAfterSequence;
638     }
639     case eBeforeAfterBlack: {     //black
640         return eGetSequenceTimeBlack;
641     }
642     case eBeforeAfterError: {     //error
643         //setPersistentMessage(Message::eMessageError, "", "Out of frame range");
644 
645         return eGetSequenceTimeError;
646     }
647     }
648 
649     return eGetSequenceTimeError;
650 } // GenericReaderPlugin::getSequenceTimeAfter
651 
652 #if 0
653 GenericReaderPlugin::GetSequenceTimeRetEnum
654 GenericReaderPlugin::getSequenceTimeHold(double t,
655                                          double *sequenceTime) const
656 {
657     int timeOffset;
658 
659     _timeOffset->getValue(timeOffset);
660 
661 
662     ///get the time sequence domain
663     OfxRangeI sequenceTimeDomain;
664     _firstFrame->getValue(sequenceTimeDomain.min);
665     _lastFrame->getValue(sequenceTimeDomain.max);
666 
667 
668     ///the return value
669     *sequenceTime = t - timeOffset;
670 
671     ///if the time given is before the sequence
672     if ( (sequenceTimeDomain.min <= *sequenceTime) && (*sequenceTime <= sequenceTimeDomain.max) ) {
673         return eGetSequenceTimeWithinSequence;
674     } else if (*sequenceTime < sequenceTimeDomain.min) {
675         return getSequenceTimeBefore(sequenceTimeDomain, eBeforeAfterHold, sequenceTime);
676     } else {
677         assert(*sequenceTime > sequenceTimeDomain.max); ///the time given is after the sequence
678         return getSequenceTimeAfter(sequenceTimeDomain, eBeforeAfterHold, sequenceTime);
679     }
680 
681     return eGetSequenceTimeError;
682 }
683 #endif
684 
685 GenericReaderPlugin::GetSequenceTimeRetEnum
getSequenceTime(double t,double * sequenceTime) const686 GenericReaderPlugin::getSequenceTime(double t,
687                                      double *sequenceTime) const
688 {
689     int timeOffset;
690 
691     _timeOffset->getValue(timeOffset);
692 
693 
694     ///get the time sequence domain
695     OfxRangeI sequenceTimeDomain;
696     _firstFrame->getValue(sequenceTimeDomain.min);
697     _lastFrame->getValue(sequenceTimeDomain.max);
698 
699 
700     ///the return value
701     *sequenceTime = t - timeOffset;
702 
703     ///if the time given is before the sequence
704     if ( (sequenceTimeDomain.min <= *sequenceTime) && (*sequenceTime <= sequenceTimeDomain.max) ) {
705         return eGetSequenceTimeWithinSequence;
706     } else if (*sequenceTime < sequenceTimeDomain.min) {
707         /////if we're before the first frame
708         int beforeChoice_i;
709         _beforeFirst->getValue(beforeChoice_i);
710         BeforeAfterEnum beforeChoice = BeforeAfterEnum(beforeChoice_i);
711 
712         return getSequenceTimeBefore(sequenceTimeDomain, beforeChoice, sequenceTime);
713     } else {
714         assert(*sequenceTime > sequenceTimeDomain.max); ///the time given is after the sequence
715         /////if we're after the last frame
716         int afterChoice_i;
717         _afterLast->getValue(afterChoice_i);
718         BeforeAfterEnum afterChoice = BeforeAfterEnum(afterChoice_i);
719 
720         return getSequenceTimeAfter(sequenceTimeDomain, afterChoice, sequenceTime);
721     }
722 
723     return eGetSequenceTimeError;
724 }
725 
726 #ifdef _WIN32
727 std::wstring
utf8ToUtf16(const string & str)728 utf8ToUtf16 (const string& str)
729 {
730     std::wstring native;
731 
732     native.resize( MultiByteToWideChar (CP_UTF8, 0, str.c_str(), -1, NULL, 0) );
733     MultiByteToWideChar ( CP_UTF8, 0, str.c_str(), -1, &native[0], (int)native.size() );
734 
735     return native;
736 }
737 
738 #endif
739 
740 static bool
checkIfFileExists(const string & path)741 checkIfFileExists (const string& path)
742 {
743 #ifdef _WIN32
744     WIN32_FIND_DATAW FindFileData;
745     std::wstring wpath = utf8ToUtf16 (path);
746     HANDLE handle = FindFirstFileW(wpath.c_str(), &FindFileData);
747     if (handle != INVALID_HANDLE_VALUE) {
748         FindClose(handle);
749 
750         return true;
751     }
752 
753     return false;
754 #else
755     // on Unix platforms passing in UTF-8 works
756     std::ifstream fs( path.c_str() );
757 
758     return fs.is_open() && fs.good();
759 #endif
760 }
761 
762 GenericReaderPlugin::GetFilenameRetCodeEnum
getFilenameAtSequenceTime(double sequenceTime,bool proxyFiles,bool checkForExistingFile,string * filename) const763 GenericReaderPlugin::getFilenameAtSequenceTime(double sequenceTime,
764                                                bool proxyFiles,
765                                                bool checkForExistingFile,
766                                                string *filename) const
767 {
768     GetFilenameRetCodeEnum ret;
769     const MissingEnum missingFrame = (MissingEnum)_missingFrameParam->getValue();
770     string filename0;
771     bool filenameGood = true;
772     int offset = 0;
773 
774     sequenceTime = std::floor(sequenceTime + 0.5); // round to the nearest frame
775 
776     const bool searchOtherFrame = ( (missingFrame == eMissingPrevious) ||
777                                     (missingFrame == eMissingNearest) ||
778                                     (missingFrame == eMissingNext) ||
779                                     (missingFrame == eMissingNearest) );
780     do {
781         _fileParam->getValueAtTime(sequenceTime + offset, *filename); // the time in _fileParam is the *file* time
782         if (offset == 0) {
783             filename0 = *filename; // for error reporting
784         }
785         if ( filename->empty() ) {
786             //filenameGood = false;
787             return eGetFileNameBlack; // if filename is empty, just return a black frame. this happens eg when the plugin is created
788         } else {
789             if (checkForExistingFile) {
790                 filenameGood = checkIfFileExists(*filename);
791             }
792         }
793         if (filenameGood) {
794             ret = eGetFileNameReturnedFullRes;
795             // now, try the proxy file
796             if (proxyFiles) {
797                 string proxyFileName;
798                 bool proxyGood = true;
799                 _proxyFileParam->getValueAtTime(sequenceTime + offset, proxyFileName);
800                 if ( proxyFileName.empty() ) {
801                     proxyGood = false;
802                 } else {
803                     if (checkForExistingFile) {
804                         proxyGood = checkIfFileExists(proxyFileName);
805                     }
806                 }
807                 if (proxyGood) {
808                     // proxy file exists, replace the filename with the proxy name
809                     *filename = proxyFileName;
810                     ret = eGetFileNameReturnedProxy;
811                 }
812             }
813         }
814         if (missingFrame == eMissingPrevious) {
815             --offset;
816         } else if (missingFrame == eMissingNext) {
817             ++offset;
818         } else if (missingFrame == eMissingNearest) {
819             if (offset <= 0) {
820                 offset = -offset + 1;
821             } else if (sequenceTime - offset >= 0) {
822                 offset = -offset;
823             } else {
824                 ++offset;
825             }
826         }
827     } while ( searchOtherFrame &&                     // only loop if searchOtherFrame
828               !filenameGood &&                       // and no valid file was found
829               std::abs(offset) <= MISSING_FRAME_NEAREST_RANGE && // and we are within range
830 
831               (sequenceTime + offset >= 0) ); // and index is positive
832     if (filenameGood) {
833         // ret is already set (see above)
834     } else {
835         *filename = filename0; // reset to the original frame name;
836         switch (missingFrame) {
837         case eMissingPrevious:     // Hold previous
838         case eMissingNext:        // Load next
839         case eMissingNearest:     // Load nearest
840         case eMissingError:       // Error
841             /// For images sequences, we can safely say this is  a missing frame. For video-streams we do not know and the derived class
842             // will have to handle the case itself.
843             ret = eGetFileNameFailed;
844             // return a black image
845             break;
846         case eMissingBlack:      // Black image
847             /// For images sequences, we can safely say this is  a missing frame. For video-streams we do not know and the derived class
848             // will have to handle the case itself.
849             ret = eGetFileNameBlack;
850             break;
851         }
852     }
853 
854     return ret;
855 } // GenericReaderPlugin::getFilenameAtSequenceTime
856 
857 OfxStatus
getFilenameAtTime(double t,string * filename) const858 GenericReaderPlugin::getFilenameAtTime(double t,
859                                        string *filename) const
860 {
861     double sequenceTime;
862     GetSequenceTimeRetEnum getSequenceTimeRet = getSequenceTime(t, &sequenceTime);
863 
864     switch (getSequenceTimeRet) {
865     case eGetSequenceTimeBlack:
866 
867         return kOfxStatReplyDefault;
868 
869     case eGetSequenceTimeError:
870 
871         return kOfxStatFailed;
872 
873     case eGetSequenceTimeBeforeSequence:
874     case eGetSequenceTimeWithinSequence:
875     case eGetSequenceTimeAfterSequence:
876         break;
877     }
878     GetFilenameRetCodeEnum getFilenameAtSequenceTimeRet = getFilenameAtSequenceTime(sequenceTime, false, true, filename);
879     switch (getFilenameAtSequenceTimeRet) {
880     case eGetFileNameFailed:
881 
882         // do not setPersistentMessage()!
883         return kOfxStatFailed;
884 
885     case eGetFileNameBlack:
886 
887         return kOfxStatReplyDefault;
888 
889     case eGetFileNameReturnedFullRes:
890     case eGetFileNameReturnedProxy:
891         break;
892     }
893 
894     return kOfxStatOK;
895 }
896 
897 int
getStartingTime() const898 GenericReaderPlugin::getStartingTime() const
899 {
900     int startingTime;
901 
902     _startingTime->getValue(startingTime);
903 
904     return startingTime;
905 }
906 
907 void
copyPixelData(const OfxRectI & renderWindow,const void * srcPixelData,const OfxRectI & srcBounds,PixelComponentEnum srcPixelComponents,int srcPixelComponentCount,BitDepthEnum srcBitDepth,int srcRowBytes,void * dstPixelData,const OfxRectI & dstBounds,PixelComponentEnum dstPixelComponents,int dstPixelComponentCount,BitDepthEnum dstBitDepth,int dstRowBytes)908 GenericReaderPlugin::copyPixelData(const OfxRectI& renderWindow,
909                                    const void *srcPixelData,
910                                    const OfxRectI& srcBounds,
911                                    PixelComponentEnum srcPixelComponents,
912                                    int srcPixelComponentCount,
913                                    BitDepthEnum srcBitDepth,
914                                    int srcRowBytes,
915                                    void *dstPixelData,
916                                    const OfxRectI& dstBounds,
917                                    PixelComponentEnum dstPixelComponents,
918                                    int dstPixelComponentCount,
919                                    BitDepthEnum dstBitDepth,
920                                    int dstRowBytes)
921 {
922     assert(srcPixelData && dstPixelData);
923     assert(srcBounds.y1 <= renderWindow.y1 && renderWindow.y1 <= renderWindow.y2 && renderWindow.y2 <= srcBounds.y2);
924     assert(srcBounds.x1 <= renderWindow.x1 && renderWindow.x1 <= renderWindow.x2 && renderWindow.x2 <= srcBounds.x2);
925 
926 #ifdef GENERIC_READER_USE_MULTI_THREAD
927     copyPixels(*this, renderWindow,
928                srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcBitDepth, srcRowBytes,
929                dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
930 #else
931     copyPixelsNT(*this, renderWindow,
932                  srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcBitDepth, srcRowBytes,
933                  dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
934 #endif
935 }
936 
937 // update the window of dst defined by nextRenderWindow by halving the corresponding area in src.
938 // proofread and fixed by F. Devernay on 3/10/2014
939 template <typename PIX>
940 static void
halveWindow(const OfxRectI & nextRenderWindow,const PIX * srcPixels,const OfxRectI & srcBounds,int srcRowBytes,PIX * dstPixels,const OfxRectI & dstBounds,int dstRowBytes,int nComponents)941 halveWindow(const OfxRectI& nextRenderWindow,
942             const PIX* srcPixels,
943             const OfxRectI& srcBounds,
944             int srcRowBytes,
945             PIX* dstPixels,
946             const OfxRectI& dstBounds,
947             int dstRowBytes,
948             int nComponents)
949 {
950     int srcRowSize = srcRowBytes / sizeof(PIX);
951     int dstRowSize = dstRowBytes / sizeof(PIX);
952     const PIX* srcData =  srcPixels - (srcBounds.x1 * nComponents + srcRowSize * srcBounds.y1);
953     PIX* dstData = dstPixels - (dstBounds.x1 * nComponents + dstRowSize * dstBounds.y1);
954 
955     assert(nextRenderWindow.x1 * 2 >= (srcBounds.x1 - 1) && (nextRenderWindow.x2 - 1) * 2 < srcBounds.x2 &&
956            nextRenderWindow.y1 * 2 >= (srcBounds.y1 - 1) && (nextRenderWindow.y2 - 1) * 2 < srcBounds.y2);
957     for (int y = nextRenderWindow.y1; y < nextRenderWindow.y2; ++y) {
958         const PIX* srcLineStart = srcData + y * 2 * srcRowSize;
959         PIX* dstLineStart = dstData + y * dstRowSize;
960         bool pickNextRow = (y * 2) < (srcBounds.y2 - 1);
961         bool pickThisRow = (y * 2) >= (srcBounds.y1);
962         int sumH = (int)pickNextRow + (int)pickThisRow;
963         assert(sumH == 1 || sumH == 2);
964         for (int x = nextRenderWindow.x1; x < nextRenderWindow.x2; ++x) {
965             bool pickNextCol = (x * 2) < (srcBounds.x2 - 1);
966             bool pickThisCol = (x * 2) >= (srcBounds.x1);
967             int sumW = (int)pickThisCol + (int)pickNextCol;
968             assert(sumW == 1 || sumW == 2);
969             for (int k = 0; k < nComponents; ++k) {
970                 ///a b
971                 ///c d
972 
973                 PIX a = (pickThisCol && pickThisRow) ? srcLineStart[x * 2 * nComponents + k] : 0;
974                 PIX b = (pickNextCol && pickThisRow) ? srcLineStart[(x * 2 + 1) * nComponents + k] : 0;
975                 PIX c = (pickThisCol && pickNextRow) ? srcLineStart[(x * 2 * nComponents) + srcRowSize + k] : 0;
976                 PIX d = (pickNextCol && pickNextRow) ? srcLineStart[(x * 2 + 1) * nComponents + srcRowSize + k] : 0;
977 
978                 assert( sumW == 2 || ( sumW == 1 && ( (a == 0 && c == 0) || (b == 0 && d == 0) ) ) );
979                 assert( sumH == 2 || ( sumH == 1 && ( (a == 0 && b == 0) || (c == 0 && d == 0) ) ) );
980                 int sum = sumH * sumW;
981                 // guard against division by zero
982                 assert(sum);
983                 dstLineStart[x * nComponents + k] = sum ? ( (a + b + c + d) / sum ) : 0;
984             }
985         }
986     }
987 }
988 
989 template <typename PIX>
990 static void
buildMipMapLevelGeneric(ImageEffect * instance,const OfxRectI & originalRenderWindow,const OfxRectI & renderWindowFullRes,unsigned int level,const PIX * srcPixels,const OfxRectI & srcBounds,int srcRowBytes,PIX * dstPixels,const OfxRectI & dstBounds,int dstRowBytes,int nComponents)991 buildMipMapLevelGeneric(ImageEffect* instance,
992                         const OfxRectI& originalRenderWindow,
993                         const OfxRectI& renderWindowFullRes,
994                         unsigned int level,
995                         const PIX* srcPixels,
996                         const OfxRectI& srcBounds,
997                         int srcRowBytes,
998                         PIX* dstPixels,
999                         const OfxRectI& dstBounds,
1000                         int dstRowBytes,
1001                         int nComponents)
1002 {
1003     assert(level > 0);
1004 
1005     auto_ptr<ImageMemory> tmpMem;
1006     size_t tmpMemSize = 0;
1007     PIX* nextImg = NULL;
1008     const PIX* previousImg = srcPixels;
1009     OfxRectI previousBounds = srcBounds;
1010     int previousRowBytes = srcRowBytes;
1011     OfxRectI nextRenderWindow = renderWindowFullRes;
1012 
1013     ///Build all the mipmap levels until we reach the one we are interested in
1014     for (unsigned int i = 1; i < level; ++i) {
1015         // loop invariant:
1016         // - previousImg, previousBounds, previousRowBytes describe the data ate the level before i
1017         // - nextRenderWindow contains the renderWindow at the level before i
1018         //
1019         ///Halve the smallest enclosing po2 rect as we need to render a minimum of the renderWindow
1020         nextRenderWindow = downscalePowerOfTwoSmallestEnclosing(nextRenderWindow, 1);
1021 #     ifdef DEBUG
1022         {
1023             // check that doing i times 1 level is the same as doing i levels
1024             OfxRectI nrw = downscalePowerOfTwoSmallestEnclosing(renderWindowFullRes, i);
1025             assert(nrw.x1 == nextRenderWindow.x1 && nrw.x2 == nextRenderWindow.x2 && nrw.y1 == nextRenderWindow.y1 && nrw.y2 == nextRenderWindow.y2);
1026         }
1027 #     endif
1028         ///Allocate a temporary image if necessary, or reuse the previously allocated buffer
1029         int nextRowBytes =  (nextRenderWindow.x2 - nextRenderWindow.x1)  * nComponents * sizeof(PIX);
1030         size_t newMemSize =  (size_t)(nextRenderWindow.y2 - nextRenderWindow.y1) * (size_t)nextRowBytes;
1031         if ( !tmpMem.get() ) {
1032             // there should be enough memory: no need to reallocate
1033             assert(tmpMemSize >= newMemSize);
1034         } else {
1035             tmpMem.reset( new ImageMemory(newMemSize, instance) );
1036             tmpMemSize = newMemSize;
1037         }
1038         nextImg = (float*)tmpMem->lock();
1039 
1040         halveWindow<PIX>(nextRenderWindow, previousImg, previousBounds, previousRowBytes, nextImg, nextRenderWindow, nextRowBytes, nComponents);
1041 
1042         ///Switch for next pass
1043         previousBounds = nextRenderWindow;
1044         previousRowBytes = nextRowBytes;
1045         previousImg = nextImg;
1046     }
1047     // here:
1048     // - previousImg, previousBounds, previousRowBytes describe the data ate the level before 'level'
1049     // - nextRenderWindow contains the renderWindow at the level before 'level'
1050 
1051     ///On the last iteration halve directly into the dstPixels
1052     ///The nextRenderWindow should be equal to the original render window.
1053     nextRenderWindow = downscalePowerOfTwoSmallestEnclosing(nextRenderWindow, 1);
1054     assert(originalRenderWindow.x1 == nextRenderWindow.x1 && originalRenderWindow.x2 == nextRenderWindow.x2 &&
1055            originalRenderWindow.y1 == nextRenderWindow.y1 && originalRenderWindow.y2 == nextRenderWindow.y2);
1056 
1057     halveWindow<PIX>(nextRenderWindow, previousImg, previousBounds, previousRowBytes, dstPixels, dstBounds, dstRowBytes, nComponents);
1058     // mem and tmpMem are freed at destruction
1059 } // buildMipMapLevelGeneric
1060 
1061 // update the window of dst defined by originalRenderWindow by mipmapping the windows of src defined by renderWindowFullRes
1062 // proofread and fixed by F. Devernay on 3/10/2014
1063 template <typename PIX, int nComponents>
1064 static void
buildMipMapLevel(ImageEffect * instance,const OfxRectI & originalRenderWindow,const OfxRectI & renderWindowFullRes,unsigned int level,const PIX * srcPixels,const OfxRectI & srcBounds,int srcRowBytes,PIX * dstPixels,const OfxRectI & dstBounds,int dstRowBytes)1065 buildMipMapLevel(ImageEffect* instance,
1066                  const OfxRectI& originalRenderWindow,
1067                  const OfxRectI& renderWindowFullRes,
1068                  unsigned int level,
1069                  const PIX* srcPixels,
1070                  const OfxRectI& srcBounds,
1071                  int srcRowBytes,
1072                  PIX* dstPixels,
1073                  const OfxRectI& dstBounds,
1074                  int dstRowBytes)
1075 {
1076     buildMipMapLevelGeneric<PIX>(instance, originalRenderWindow, renderWindowFullRes, level, srcPixels, srcBounds, srcRowBytes
1077                                  , dstPixels, dstBounds, dstRowBytes, nComponents);
1078 }
1079 
1080 void
scalePixelData(const OfxRectI & originalRenderWindow,const OfxRectI & renderWindow,unsigned int levels,const void * srcPixelData,PixelComponentEnum srcPixelComponents,int srcPixelComponentCount,BitDepthEnum srcPixelDepth,const OfxRectI & srcBounds,int srcRowBytes,void * dstPixelData,PixelComponentEnum dstPixelComponents,int dstPixelComponentCount,BitDepthEnum dstPixelDepth,const OfxRectI & dstBounds,int dstRowBytes)1081 GenericReaderPlugin::scalePixelData(const OfxRectI& originalRenderWindow,
1082                                     const OfxRectI& renderWindow,
1083                                     unsigned int levels,
1084                                     const void* srcPixelData,
1085                                     PixelComponentEnum srcPixelComponents,
1086                                     int srcPixelComponentCount,
1087                                     BitDepthEnum srcPixelDepth,
1088                                     const OfxRectI& srcBounds,
1089                                     int srcRowBytes,
1090                                     void* dstPixelData,
1091                                     PixelComponentEnum dstPixelComponents,
1092                                     int dstPixelComponentCount,
1093                                     BitDepthEnum dstPixelDepth,
1094                                     const OfxRectI& dstBounds,
1095                                     int dstRowBytes)
1096 {
1097     assert(srcPixelData && dstPixelData);
1098 
1099     // do the rendering
1100     if ( (dstPixelDepth != eBitDepthFloat) ||
1101          ( ( dstPixelComponents != ePixelComponentRGBA) &&
1102            ( dstPixelComponents != ePixelComponentRGB) &&
1103            ( dstPixelComponents != ePixelComponentXY) &&
1104            ( dstPixelComponents != ePixelComponentAlpha) &&
1105            ( dstPixelComponents != ePixelComponentCustom) ) ||
1106          ( dstPixelDepth != srcPixelDepth) ||
1107          ( dstPixelComponents != srcPixelComponents) ||
1108          ( dstPixelComponentCount != srcPixelComponentCount) ) {
1109         throwSuiteStatusException(kOfxStatErrFormat);
1110 
1111         return;
1112     }
1113 
1114     if (dstPixelComponents == ePixelComponentRGBA) {
1115         if (!_supportsRGBA) {
1116             throwSuiteStatusException(kOfxStatErrFormat);
1117 
1118             return;
1119         }
1120         buildMipMapLevel<float, 4>(this, originalRenderWindow, renderWindow, levels, (const float*)srcPixelData,
1121                                    srcBounds, srcRowBytes, (float*)dstPixelData, dstBounds, dstRowBytes);
1122     } else if (dstPixelComponents == ePixelComponentRGB) {
1123         if (!_supportsRGB) {
1124             throwSuiteStatusException(kOfxStatErrFormat);
1125 
1126             return;
1127         }
1128         buildMipMapLevel<float, 3>(this, originalRenderWindow, renderWindow, levels, (const float*)srcPixelData,
1129                                    srcBounds, srcRowBytes, (float*)dstPixelData, dstBounds, dstRowBytes);
1130     } else if (dstPixelComponents == ePixelComponentXY) {
1131         if (!_supportsXY) {
1132             throwSuiteStatusException(kOfxStatErrFormat);
1133 
1134             return;
1135         }
1136         buildMipMapLevel<float, 2>(this, originalRenderWindow, renderWindow, levels, (const float*)srcPixelData,
1137                                    srcBounds, srcRowBytes, (float*)dstPixelData, dstBounds, dstRowBytes);
1138     }  else if (dstPixelComponents == ePixelComponentAlpha) {
1139         if (!_supportsAlpha) {
1140             throwSuiteStatusException(kOfxStatErrFormat);
1141 
1142             return;
1143         }
1144         buildMipMapLevel<float, 1>(this, originalRenderWindow, renderWindow, levels, (const float*)srcPixelData,
1145                                    srcBounds, srcRowBytes, (float*)dstPixelData, dstBounds, dstRowBytes);
1146     } else {
1147         assert(dstPixelComponents == ePixelComponentCustom);
1148 
1149         buildMipMapLevelGeneric<float>(this, originalRenderWindow, renderWindow, levels, (const float*)srcPixelData,
1150                                        srcBounds, srcRowBytes, (float*)dstPixelData, dstBounds, dstRowBytes, dstPixelComponentCount);
1151     }
1152 } // GenericReaderPlugin::scalePixelData
1153 
1154 /* set up and run a copy processor */
1155 static void
setupAndFillWithBlack(PixelProcessorFilterBase & processor,const OfxRectI & renderWindow,void * dstPixelData,const OfxRectI & dstBounds,PixelComponentEnum dstPixelComponents,int dstPixelComponentCount,BitDepthEnum dstPixelDepth,int dstRowBytes)1156 setupAndFillWithBlack(PixelProcessorFilterBase & processor,
1157                       const OfxRectI &renderWindow,
1158                       void *dstPixelData,
1159                       const OfxRectI& dstBounds,
1160                       PixelComponentEnum dstPixelComponents,
1161                       int dstPixelComponentCount,
1162                       BitDepthEnum dstPixelDepth,
1163                       int dstRowBytes)
1164 {
1165     // set the images
1166     processor.setDstImg(dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstPixelDepth, dstRowBytes);
1167 
1168     // set the render window
1169     processor.setRenderWindow(renderWindow);
1170 
1171     // Call the base class process member, this will call the derived templated process code
1172     processor.process();
1173 }
1174 
1175 void
fillWithBlack(const OfxRectI & renderWindow,void * dstPixelData,const OfxRectI & dstBounds,PixelComponentEnum dstPixelComponents,int dstPixelComponentCount,BitDepthEnum dstBitDepth,int dstRowBytes)1176 GenericReaderPlugin::fillWithBlack(const OfxRectI &renderWindow,
1177                                    void *dstPixelData,
1178                                    const OfxRectI& dstBounds,
1179                                    PixelComponentEnum dstPixelComponents,
1180                                    int dstPixelComponentCount,
1181                                    BitDepthEnum dstBitDepth,
1182                                    int dstRowBytes)
1183 {
1184     BlackFiller<float> fred(*this, dstPixelComponentCount);
1185     setupAndFillWithBlack(fred, renderWindow, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
1186 }
1187 
1188 static void
setupAndProcess(PixelProcessorFilterBase & processor,int premultChannel,const OfxRectI & renderWindow,const void * srcPixelData,const OfxRectI & srcBounds,PixelComponentEnum srcPixelComponents,int srcPixelComponentCount,BitDepthEnum srcPixelDepth,int srcRowBytes,void * dstPixelData,const OfxRectI & dstBounds,PixelComponentEnum dstPixelComponents,int dstPixelComponentCount,BitDepthEnum dstPixelDepth,int dstRowBytes)1189 setupAndProcess(PixelProcessorFilterBase & processor,
1190                 int premultChannel,
1191                 const OfxRectI &renderWindow,
1192                 const void *srcPixelData,
1193                 const OfxRectI& srcBounds,
1194                 PixelComponentEnum srcPixelComponents,
1195                 int srcPixelComponentCount,
1196                 BitDepthEnum srcPixelDepth,
1197                 int srcRowBytes,
1198                 void *dstPixelData,
1199                 const OfxRectI& dstBounds,
1200                 PixelComponentEnum dstPixelComponents,
1201                 int dstPixelComponentCount,
1202                 BitDepthEnum dstPixelDepth,
1203                 int dstRowBytes)
1204 {
1205     assert(srcPixelData && dstPixelData);
1206 
1207     // make sure bit depths are sane
1208     if ( (srcPixelDepth != dstPixelDepth) || (srcPixelComponents != dstPixelComponents) ) {
1209         throwSuiteStatusException(kOfxStatErrFormat);
1210 
1211         return;
1212     }
1213 
1214     // set the images
1215     processor.setDstImg(dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstPixelDepth, dstRowBytes);
1216     processor.setSrcImg(srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcPixelDepth, srcRowBytes, 0);
1217 
1218     // set the render window
1219     processor.setRenderWindow(renderWindow);
1220 
1221     processor.setPremultMaskMix(true, premultChannel, 1.);
1222 
1223     // Call the base class process member, this will call the derived templated process code
1224     processor.process();
1225 }
1226 
1227 void
unPremultPixelData(const OfxRectI & renderWindow,const void * srcPixelData,const OfxRectI & srcBounds,PixelComponentEnum srcPixelComponents,int srcPixelComponentCount,BitDepthEnum srcPixelDepth,int srcRowBytes,void * dstPixelData,const OfxRectI & dstBounds,PixelComponentEnum dstPixelComponents,int dstPixelComponentCount,BitDepthEnum dstBitDepth,int dstRowBytes)1228 GenericReaderPlugin::unPremultPixelData(const OfxRectI &renderWindow,
1229                                         const void *srcPixelData,
1230                                         const OfxRectI& srcBounds,
1231                                         PixelComponentEnum srcPixelComponents,
1232                                         int srcPixelComponentCount,
1233                                         BitDepthEnum srcPixelDepth,
1234                                         int srcRowBytes,
1235                                         void *dstPixelData,
1236                                         const OfxRectI& dstBounds,
1237                                         PixelComponentEnum dstPixelComponents,
1238                                         int dstPixelComponentCount,
1239                                         BitDepthEnum dstBitDepth,
1240                                         int dstRowBytes)
1241 {
1242     assert(srcPixelData && dstPixelData);
1243 
1244     // do the rendering
1245     if ( (dstBitDepth != eBitDepthFloat) || ( (dstPixelComponents != ePixelComponentRGBA) && (dstPixelComponents != ePixelComponentRGB) && (dstPixelComponents != ePixelComponentAlpha) ) ) {
1246         throwSuiteStatusException(kOfxStatErrFormat);
1247 
1248         return;
1249     }
1250     if (dstPixelComponents == ePixelComponentRGBA) {
1251         if (!_supportsRGBA) {
1252             throwSuiteStatusException(kOfxStatErrFormat);
1253 
1254             return;
1255         }
1256         PixelCopierUnPremult<float, 4, 1, float, 4, 1> fred(*this);
1257         setupAndProcess(fred, 3, renderWindow, srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcPixelDepth, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
1258     } else {
1259         ///other pixel components means you want to copy only...
1260         assert(false);
1261     }
1262 }
1263 
1264 void
premultPixelData(const OfxRectI & renderWindow,const void * srcPixelData,const OfxRectI & srcBounds,PixelComponentEnum srcPixelComponents,int srcPixelComponentCount,BitDepthEnum srcPixelDepth,int srcRowBytes,void * dstPixelData,const OfxRectI & dstBounds,PixelComponentEnum dstPixelComponents,int dstPixelComponentCount,BitDepthEnum dstBitDepth,int dstRowBytes)1265 GenericReaderPlugin::premultPixelData(const OfxRectI &renderWindow,
1266                                       const void *srcPixelData,
1267                                       const OfxRectI& srcBounds,
1268                                       PixelComponentEnum srcPixelComponents,
1269                                       int srcPixelComponentCount,
1270                                       BitDepthEnum srcPixelDepth,
1271                                       int srcRowBytes,
1272                                       void *dstPixelData,
1273                                       const OfxRectI& dstBounds,
1274                                       PixelComponentEnum dstPixelComponents,
1275                                       int dstPixelComponentCount,
1276                                       BitDepthEnum dstBitDepth,
1277                                       int dstRowBytes)
1278 {
1279     assert(srcPixelData && dstPixelData);
1280 
1281     // do the rendering
1282     if ( (dstBitDepth != eBitDepthFloat) || ( (dstPixelComponents != ePixelComponentRGBA) && (dstPixelComponents != ePixelComponentRGB) && (dstPixelComponents != ePixelComponentAlpha) ) ) {
1283         throwSuiteStatusException(kOfxStatErrFormat);
1284 
1285         return;
1286     }
1287 
1288     if (dstPixelComponents == ePixelComponentRGBA) {
1289         if (!_supportsRGBA) {
1290             throwSuiteStatusException(kOfxStatErrFormat);
1291 
1292             return;
1293         }
1294         PixelCopierPremult<float, 4, 1, float, 4, 1> fred(*this);
1295         setupAndProcess(fred, 3, renderWindow, srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcPixelDepth, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
1296     } else {
1297         ///other pixel components means you want to copy only...
1298         assert(false);
1299     }
1300 }
1301 
1302 bool
getRegionOfDefinition(const RegionOfDefinitionArguments & args,OfxRectD & rod)1303 GenericReaderPlugin::getRegionOfDefinition(const RegionOfDefinitionArguments &args,
1304                                            OfxRectD &rod)
1305 {
1306     if ( !kSupportsRenderScale && ( (args.renderScale.x != 1.) || (args.renderScale.y != 1.) ) ) {
1307         throwSuiteStatusException(kOfxStatFailed);
1308 
1309         return false;
1310     }
1311 
1312     double sequenceTime;
1313     GetSequenceTimeRetEnum getSequenceTimeRet = getSequenceTime(args.time, &sequenceTime);
1314     switch (getSequenceTimeRet) {
1315     case eGetSequenceTimeBlack:
1316 
1317         return false;
1318 
1319     case eGetSequenceTimeError:
1320         throwSuiteStatusException(kOfxStatFailed);
1321 
1322         return false;
1323 
1324     case eGetSequenceTimeBeforeSequence:
1325     case eGetSequenceTimeWithinSequence:
1326     case eGetSequenceTimeAfterSequence:
1327         break;
1328     }
1329 
1330     /*We don't want to use the proxy image for the region of definition*/
1331     string filename;
1332     GetFilenameRetCodeEnum getFilenameAtSequenceTimeRet = getFilenameAtSequenceTime(sequenceTime, false, true, &filename);
1333     switch (getFilenameAtSequenceTimeRet) {
1334     case eGetFileNameFailed:
1335         if ( _syncClip && _syncClip->isConnected() ){
1336             // if the Sync input is connected, just return the RoD from this input
1337             // Because Natron calls getRoD() before render(), so the image may not yet be here.
1338             return false;
1339         }
1340         setPersistentMessage(Message::eMessageError, "", filename + ": Cannot load frame");
1341         throwSuiteStatusException(kOfxStatFailed);
1342 
1343         return false;
1344 
1345     case eGetFileNameBlack:
1346         clearPersistentMessage();
1347 
1348         return false;
1349 
1350     case eGetFileNameReturnedFullRes:
1351     case eGetFileNameReturnedProxy:
1352         clearPersistentMessage();
1353         break;
1354     }
1355 
1356     string error;
1357     OfxRectI bounds, format;
1358     double par = 1.;
1359     int tile_width, tile_height;
1360     bool success = getFrameBounds(filename, sequenceTime, args.view, &bounds, &format, &par, &error, &tile_width, &tile_height);
1361     if (!success) {
1362         setPersistentMessage(Message::eMessageError, "", error);
1363         throwSuiteStatusException(kOfxStatFailed);
1364 
1365         return false;
1366     }
1367     // get the PAR from the clip preferences, since it should be constant over time
1368     par = _outputClip->getPixelAspectRatio();
1369     rod.x1 = bounds.x1 * par;
1370     rod.x2 = bounds.x2 * par;
1371     rod.y1 = bounds.y1;
1372     rod.y2 = bounds.y2;
1373 
1374 //    if (getFilenameAtSequenceTimeRet == eGetFileNameReturnedProxy) {
1375 //        ///upscale the proxy RoD to be in canonical coords.
1376 //        unsigned int mipmapLvl = getLevelFromScale(args.renderScale.x);
1377 //        rod = upscalePowerOfTwo(rod, (double)mipmapLvl);
1378 //    }
1379 
1380     return true;
1381 } // GenericReaderPlugin::getRegionOfDefinition
1382 
1383 class OutputImagesHolder_RAII
1384 {
1385     std::vector<Image*> dstImages;
1386 
1387 public:
1388 
OutputImagesHolder_RAII()1389     OutputImagesHolder_RAII()
1390         : dstImages()
1391     {
1392     }
1393 
appendImage(Image * img)1394     void appendImage(Image* img)
1395     {
1396         dstImages.push_back(img);
1397     }
1398 
getOutputPlanes() const1399     const std::vector<Image*>& getOutputPlanes() const
1400     {
1401         return dstImages;
1402     }
1403 
~OutputImagesHolder_RAII()1404     ~OutputImagesHolder_RAII()
1405     {
1406         for (std::size_t i = 0; i < dstImages.size(); ++i) {
1407             delete dstImages[i];
1408         }
1409     }
1410 };
1411 
1412 void
render(const RenderArguments & args)1413 GenericReaderPlugin::render(const RenderArguments &args)
1414 {
1415     if (!_outputClip) {
1416         throwSuiteStatusException(kOfxStatFailed);
1417 
1418         return;
1419     }
1420 
1421     if ( !kSupportsRenderScale && ( (args.renderScale.x != 1.) || (args.renderScale.y != 1.) ) ) {
1422         throwSuiteStatusException(kOfxStatFailed);
1423 
1424         return;
1425     }
1426 
1427     assert( kSupportsRenderScale || (args.renderScale.x == 1. && args.renderScale.y == 1.) );
1428     ///The image will have the appropriate size since we support the render scale (multi-resolution)
1429     OutputImagesHolder_RAII outputImagesHolder;
1430 #ifdef OFX_EXTENSIONS_NUKE
1431     if ( !isMultiPlanar() ) {
1432 #endif
1433 
1434     Image* dstImg = _outputClip->fetchImage(args.time);
1435     if (!dstImg) {
1436         throwSuiteStatusException(kOfxStatFailed);
1437 
1438         return;
1439     }
1440 #ifndef NDEBUG
1441     if (!_supportsTiles) {
1442         // http://openfx.sourceforge.net/Documentation/1.3/ofxProgrammingReference.html#kOfxImageEffectPropSupportsTiles
1443         //  If a clip or plugin does not support tiled images, then the host should supply full RoD images to the effect whenever it fetches one.
1444         OfxRectI dstRod;     // = dst->getRegionOfDefinition(); //  Nuke's image RoDs are wrong
1445         Coords::toPixelEnclosing(_outputClip->getRegionOfDefinition(args.time), args.renderScale, _outputClip->getPixelAspectRatio(), &dstRod);
1446         const OfxRectI& dstBounds = dstImg->getBounds();
1447 
1448         assert(dstRod.x1 == dstBounds.x1);
1449         assert(dstRod.x2 == dstBounds.x2);
1450         assert(dstRod.y1 == dstBounds.y1);
1451         assert(dstRod.y2 == dstBounds.y2);     // crashes on Natron if kSupportsTiles=0 & kSupportsMultiResolution=1
1452     }
1453 #endif
1454 
1455     outputImagesHolder.appendImage(dstImg);
1456 #ifdef OFX_EXTENSIONS_NUKE
1457 } else {
1458     assert(getImageEffectHostDescription()->isMultiPlanar);
1459 
1460     for (std::list<string>::const_iterator it = args.planes.begin(); it != args.planes.end(); ++it) {
1461         Image* dstPlane = _outputClip->fetchImagePlane( args.time, args.renderView, it->c_str() );
1462         if (!dstPlane) {
1463             throwSuiteStatusException(kOfxStatFailed);
1464 
1465             return;
1466         }
1467         outputImagesHolder.appendImage(dstPlane);
1468     }
1469 }
1470 
1471 #endif
1472 
1473     OfxRectI firstBounds = {0, 0, 0, 0};
1474     BitDepthEnum firstDepth = eBitDepthNone;
1475     bool firstImageSet = false;
1476 
1477     std::list<PlaneToRender> planes;
1478 
1479     const std::vector<Image*>& outputImages = outputImagesHolder.getOutputPlanes();
1480     for (std::size_t i = 0; i < outputImages.size(); ++i) {
1481         if ( (outputImages[i]->getRenderScale().x != args.renderScale.x) ||
1482              ( outputImages[i]->getRenderScale().y != args.renderScale.y) ||
1483              ( outputImages[i]->getField() != args.fieldToRender) ) {
1484             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
1485             throwSuiteStatusException(kOfxStatFailed);
1486 
1487             return;
1488         }
1489 
1490         PlaneToRender plane;
1491         void* dstPixelData = NULL;
1492         OfxRectI bounds;
1493         BitDepthEnum bitDepth;
1494         getImageData(outputImages[i], &dstPixelData, &bounds, &plane.comps, &bitDepth, &plane.rowBytes);
1495         if (bitDepth != eBitDepthFloat) {
1496             throwSuiteStatusException(kOfxStatErrFormat);
1497 
1498             return;
1499         }
1500         if (!firstImageSet) {
1501             firstBounds = bounds;
1502             firstDepth = bitDepth;
1503             firstImageSet = true;
1504         } else {
1505             if ( (firstBounds.x1 != bounds.x1) || (firstBounds.x2 != bounds.x2) || (firstBounds.y1 != bounds.y1) || (firstBounds.y2 != bounds.y2)
1506                  || ( firstDepth != bitDepth) ) {
1507                 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image plane with wrong bounds/bitdepth");
1508                 throwSuiteStatusException(kOfxStatFailed);
1509 
1510                 return;
1511             }
1512         }
1513         plane.pixelData = (float*)dstPixelData;
1514         if (!plane.pixelData) {
1515             setPersistentMessage(Message::eMessageError, "", "OFX Host provided an invalid image buffer");
1516         }
1517 
1518         plane.rawComps = outputImages[i]->getPixelComponentsProperty();
1519 
1520 
1521 #ifdef OFX_EXTENSIONS_NUKE
1522         if (plane.comps != ePixelComponentCustom) {
1523 #endif
1524 #ifdef OFX_EXTENSIONS_NATRON
1525         assert(plane.rawComps == kOfxImageComponentAlpha ||
1526                plane.rawComps == kNatronOfxImageComponentXY ||
1527                plane.rawComps == kOfxImageComponentRGB || plane.rawComps == kOfxImageComponentRGBA);
1528 #else
1529         assert(plane.rawComps == kOfxImageComponentAlpha ||
1530                plane.rawComps == kOfxImageComponentRGB || plane.rawComps == kOfxImageComponentRGBA);
1531 #endif
1532         PixelComponentEnum outputComponents = getOutputComponents();
1533         if (plane.comps != outputComponents) {
1534             setPersistentMessage(Message::eMessageError, "", "OFX Host dit not take into account output components");
1535             throwSuiteStatusException(kOfxStatErrImageFormat);
1536 
1537             return;
1538         }
1539 #ifdef OFX_EXTENSIONS_NUKE
1540     }
1541 #endif
1542 
1543         plane.numChans = outputImages[i]->getPixelComponentCount();
1544         planes.push_back(plane);
1545     }
1546 
1547 
1548     // are we in the image bounds
1549     if ( (args.renderWindow.x1 < firstBounds.x1) || (args.renderWindow.x1 >= firstBounds.x2) || (args.renderWindow.y1 < firstBounds.y1) || (args.renderWindow.y1 >= firstBounds.y2) ||
1550          ( args.renderWindow.x2 <= firstBounds.x1) || ( args.renderWindow.x2 > firstBounds.x2) || ( args.renderWindow.y2 <= firstBounds.y1) || ( args.renderWindow.y2 > firstBounds.y2) ) {
1551         throwSuiteStatusException(kOfxStatErrValue);
1552 
1553         //throw std::runtime_error("render window outside of image bounds");
1554         return;
1555     }
1556 
1557     double sequenceTime;
1558     GetSequenceTimeRetEnum getSequenceTimeRet = getSequenceTime(args.time, &sequenceTime);
1559     switch (getSequenceTimeRet) {
1560     case eGetSequenceTimeBlack:
1561         for (std::list<PlaneToRender>::iterator it = planes.begin(); it != planes.end(); ++it) {
1562             fillWithBlack(args.renderWindow, it->pixelData, firstBounds, it->comps, it->numChans, firstDepth, it->rowBytes);
1563         }
1564 
1565         return;
1566 
1567     case eGetSequenceTimeError:
1568         throwSuiteStatusException(kOfxStatFailed);
1569 
1570         return;
1571 
1572     case eGetSequenceTimeBeforeSequence:
1573     case eGetSequenceTimeWithinSequence:
1574     case eGetSequenceTimeAfterSequence:
1575         break;
1576     }
1577 
1578     bool useProxy = false;
1579     OfxPointD proxyScaleThreshold;
1580     _proxyThreshold->getValue(proxyScaleThreshold.x, proxyScaleThreshold.y);
1581 
1582     OfxPointD proxyOriginalScale;
1583     _originalProxyScale->getValue(proxyOriginalScale.x, proxyOriginalScale.y);
1584 
1585     ///We only support downscaling at a power of two.
1586     unsigned int renderMipmapLevel = getLevelFromScale( std::min(args.renderScale.x, args.renderScale.y) );
1587     unsigned int proxyMipMapThresholdLevel = (proxyScaleThreshold.x == 0 || proxyScaleThreshold.y == 0) ? renderMipmapLevel :  getLevelFromScale( std::min(proxyScaleThreshold.x, proxyScaleThreshold.y) );
1588     unsigned int originalProxyMipMapLevel = (proxyOriginalScale.x == 0 || proxyOriginalScale.y == 0) ? renderMipmapLevel : getLevelFromScale( std::min(proxyOriginalScale.x, proxyOriginalScale.y) );
1589 
1590     if ( kSupportsRenderScale && (renderMipmapLevel >= proxyMipMapThresholdLevel) ) {
1591         useProxy = true;
1592     }
1593 
1594     string filename;
1595     GetFilenameRetCodeEnum getFilenameAtSequenceTimeRet = getFilenameAtSequenceTime(sequenceTime, false, true, &filename);
1596     switch (getFilenameAtSequenceTimeRet) {
1597     case eGetFileNameFailed:
1598         setPersistentMessage(Message::eMessageError, "", filename + ": Cannot load frame");
1599         throwSuiteStatusException(kOfxStatFailed);
1600 
1601         return;
1602 
1603     case eGetFileNameBlack:
1604         clearPersistentMessage();
1605         for (std::list<PlaneToRender>::iterator it = planes.begin(); it != planes.end(); ++it) {
1606             fillWithBlack(args.renderWindow, it->pixelData, firstBounds, it->comps, it->numChans, firstDepth, it->rowBytes);
1607         }
1608 
1609         return;
1610 
1611     case eGetFileNameReturnedFullRes:
1612         clearPersistentMessage();
1613         break;
1614 
1615     case eGetFileNameReturnedProxy:
1616         // we didn't ask for proxy!
1617         assert(false);
1618         throwSuiteStatusException(kOfxStatFailed);
1619 
1620         return;
1621     }
1622 
1623     string proxyFile;
1624     if (useProxy) {
1625         ///Use the proxy only if getFilenameAtSequenceTime returned a valid proxy filename different from the original file
1626         GetFilenameRetCodeEnum getFilenameAtSequenceTimeRetPx = getFilenameAtSequenceTime(sequenceTime, true, true, &proxyFile);
1627         switch (getFilenameAtSequenceTimeRetPx) {
1628         case eGetFileNameFailed:
1629             // should never happen: it should return at least the full res frame
1630             assert(false);
1631             throwSuiteStatusException(kOfxStatFailed);
1632 
1633             return;
1634 
1635         case eGetFileNameBlack:
1636             // should never happen: it should return at least the full res frame
1637             assert(false);
1638             for (std::list<PlaneToRender>::iterator it = planes.begin(); it != planes.end(); ++it) {
1639                 fillWithBlack(args.renderWindow, it->pixelData, firstBounds, it->comps, it->numChans, firstDepth, it->rowBytes);
1640             }
1641 
1642             return;
1643 
1644         case eGetFileNameReturnedFullRes:
1645             assert(proxyFile == filename);
1646             useProxy = false;
1647             break;
1648 
1649         case eGetFileNameReturnedProxy:
1650             assert( !proxyFile.empty() );
1651             useProxy = (proxyFile != filename);
1652             break;
1653         }
1654     }
1655 
1656 
1657     // The args.renderWindow is already in pixels coordinate (render scale is already taken into account).
1658     // If the filename IS NOT a proxy file we have to make sure the renderWindow is
1659     // upscaled to a scale of (1,1). On the other hand if the filename IS a proxy we have to determine the actual RoD
1660     // of the proxy file and adjust the scale so it fits the given scale.
1661     // When in proxy mode renderWindowFullRes is the render window at the proxy mip map level
1662     int downscaleLevels = renderMipmapLevel; // the number of mipmap levels from the actual file (proxy or not) to the renderWindow
1663 
1664     if ( useProxy && !proxyFile.empty() ) {
1665         filename = proxyFile;
1666         downscaleLevels -= originalProxyMipMapLevel;
1667     }
1668     assert(downscaleLevels >= 0);
1669 
1670     if ( filename.empty() || !checkIfFileExists(filename) ) {
1671         for (std::list<PlaneToRender>::iterator it = planes.begin(); it != planes.end(); ++it) {
1672             fillWithBlack(args.renderWindow, it->pixelData, firstBounds, it->comps, it->numChans, firstDepth, it->rowBytes);
1673         }
1674 
1675         return;
1676     }
1677 
1678     OfxRectI renderWindowFullRes, renderWindowNotRounded;
1679     OfxRectI frameBounds, format;
1680     double par = 1.;
1681     int tile_width, tile_height;
1682     string error;
1683 
1684     ///if the plug-in doesn't support tiles, just render the full rod
1685     bool success = getFrameBounds(filename, sequenceTime, args.renderView, &frameBounds, &format, &par, &error, &tile_width, &tile_height);
1686     ///We shouldve checked above for any failure, now this is too late.
1687     if (!success) {
1688         setPersistentMessage(Message::eMessageError, "", error);
1689         throwSuiteStatusException(kOfxStatFailed);
1690 
1691         return;
1692     }
1693 
1694     renderWindowFullRes = upscalePowerOfTwo(args.renderWindow, downscaleLevels); // works even if downscaleLevels == 0
1695 
1696     // Intersect the full res renderwindow to the real rod,
1697     // because we may have gone a few pixels too far (but never more than 2^downscaleLevels-1 pixels)
1698     assert(renderWindowFullRes.x1 >= frameBounds.x1 - std::pow(2., (double)downscaleLevels) + 1 &&
1699            renderWindowFullRes.x2 <= frameBounds.x2 + std::pow(2., (double)downscaleLevels) - 1 &&
1700            renderWindowFullRes.y1 >= frameBounds.y1 - std::pow(2., (double)downscaleLevels) + 1 &&
1701            renderWindowFullRes.y2 <= frameBounds.y2 + std::pow(2., (double)downscaleLevels) - 1);
1702     intersect(renderWindowFullRes, frameBounds, &renderWindowFullRes);
1703 
1704     //See below: we round the render window to the tile size
1705     renderWindowNotRounded = renderWindowFullRes;
1706 
1707     for (std::list<PlaneToRender>::iterator it = planes.begin(); it != planes.end(); ++it) {
1708         // Read into a temporary image, apply colorspace conversion, then copy
1709         bool isOCIOIdentity;
1710 
1711         // if components are custom, remap it to a OFX components with the same number of channels
1712         PixelComponentEnum remappedComponents;
1713 
1714         bool isColor = true;
1715         bool isCustom;
1716         if (it->comps == ePixelComponentCustom) {
1717 
1718             MultiPlane::ImagePlaneDesc plane, pairedPlane;
1719             MultiPlane::ImagePlaneDesc::mapOFXComponentsTypeStringToPlanes(it->rawComps, &plane, &pairedPlane);
1720             const std::vector<std::string>& channels = plane.getChannels();
1721             if (channels.size() < 3 || (channels[0] != "R" && channels[1] != "G" && channels[2] != "B")) {
1722                 isColor = false;
1723             }
1724             isCustom = true;
1725             if (isColor) {
1726 #ifdef OFX_IO_USING_OCIO
1727                 isOCIOIdentity = _ocio->isIdentity(args.time);
1728 #else
1729                 isOCIOIdentity = true;
1730 #endif
1731             } else {
1732                 isOCIOIdentity = true;
1733             }
1734 
1735             if (it->numChans == 3) {
1736                 remappedComponents = ePixelComponentRGB;
1737             } else if (it->numChans == 4) {
1738                 remappedComponents = ePixelComponentRGBA;
1739             } else if (it->numChans == 2) {
1740                 remappedComponents = ePixelComponentXY;
1741             } else {
1742                 remappedComponents = ePixelComponentAlpha;
1743             }
1744         } else {
1745             isCustom = false;
1746 #ifdef OFX_IO_USING_OCIO
1747             isOCIOIdentity = _ocio->isIdentity(args.time);
1748 #else
1749             isOCIOIdentity = true;
1750 #endif
1751             remappedComponents = it->comps;
1752         }
1753 
1754         PreMultiplicationEnum filePremult = eImageUnPreMultiplied;
1755         PreMultiplicationEnum outputPremult = eImageUnPreMultiplied;
1756         if ( (it->comps == ePixelComponentRGB) || (it->comps == ePixelComponentXY) || ( isCustom && isColor && (remappedComponents == ePixelComponentRGB) ) ) {
1757             filePremult = outputPremult = eImageOpaque;
1758         } else if ( (it->comps == ePixelComponentAlpha) || ( isCustom && isColor && (remappedComponents == ePixelComponentAlpha) ) ) {
1759             filePremult = outputPremult = eImagePreMultiplied;
1760         } else if ( (it->comps == ePixelComponentRGBA) || ( isCustom && isColor && (remappedComponents == ePixelComponentRGBA) ) ) {
1761             int premult_i;
1762             _filePremult->getValue(premult_i);
1763             filePremult = (PreMultiplicationEnum)premult_i;
1764 
1765             int oPremult_i;
1766             _outputPremult->getValue(oPremult_i);
1767             outputPremult = (PreMultiplicationEnum)oPremult_i;
1768         }
1769 
1770 
1771         // we have to do the final premultiplication if:
1772         // - pixelComponents is RGBA
1773         //  AND
1774         //   - buffer is PreMultiplied AND OCIO is not identity (OCIO works only on unpremultiplied data)
1775         //   OR
1776         //   - premult is unpremultiplied
1777         bool mustPremult = ( (remappedComponents == ePixelComponentRGBA) &&
1778                              ( (filePremult == eImageUnPreMultiplied || !isOCIOIdentity) && outputPremult == eImagePreMultiplied ) );
1779 
1780 
1781         if ( !mustPremult && isOCIOIdentity && ( !kSupportsRenderScale || (renderMipmapLevel == 0) ) ) {
1782             // no colorspace conversion, no premultiplication, no proxy, just read file
1783             DBG( std::printf("decode (to dst)\n") );
1784 
1785             if (!_isMultiPlanar) {
1786                 decode(filename, sequenceTime, args.renderView, args.sequentialRenderStatus, args.renderWindow, it->pixelData, firstBounds, it->comps, it->numChans, it->rowBytes);
1787             } else {
1788                 decodePlane(filename, sequenceTime, args.renderView, args.sequentialRenderStatus, args.renderWindow, it->pixelData, firstBounds, it->comps, it->numChans, it->rawComps, it->rowBytes);
1789             }
1790         } else {
1791             int pixelBytes;
1792             if (it->comps == ePixelComponentCustom) {
1793                 pixelBytes = it->numChans * sizeof(float);
1794             } else {
1795                 pixelBytes = it->numChans * getComponentBytes(firstDepth);
1796             }
1797             assert(pixelBytes > 0);
1798 
1799             /*
1800                If tile_width and tile_height is set, round the renderWindow to the enclosing tile size to make sure the plug-in has a buffer
1801                large enough to decode tiles. This is needed for OpenImageIO. Note that
1802              */
1803             if ( (tile_width > 0) && (tile_height > 0) ) {
1804                 double frameHeight = frameBounds.y2 - frameBounds.y1;
1805                 if ( isTileOrientationTopDown() ) {
1806                     //invert Y before rounding
1807 
1808                     renderWindowFullRes.y1 = frameHeight -  renderWindowFullRes.y1;
1809                     renderWindowFullRes.y2 = frameHeight  - renderWindowFullRes.y2;
1810                     frameBounds.y1 = frameHeight  - frameBounds.y1;
1811                     frameBounds.y2 = frameHeight  - frameBounds.y2;
1812 
1813                     renderWindowFullRes.x1 = std::min( (double)std::ceil( (double)renderWindowFullRes.x1 / tile_width ) * tile_width, (double)frameBounds.x1 );
1814                     renderWindowFullRes.y1 = std::min( (double)std::ceil( (double)renderWindowFullRes.y1 / tile_height ) * tile_height, (double)frameBounds.y1 );
1815                     renderWindowFullRes.x2 = std::max( (double)std::floor( (double)renderWindowFullRes.x2 / tile_width ) * tile_width, (double)frameBounds.x2 );
1816                     renderWindowFullRes.y2 = std::max( (double)std::floor( (double)renderWindowFullRes.y2 / tile_height ) * tile_height, (double)frameBounds.y2 );
1817                 } else {
1818                     renderWindowFullRes.x1 = std::max( (double)std::floor( (double)renderWindowFullRes.x1 / tile_width ) * tile_width, (double)frameBounds.x1 );
1819                     renderWindowFullRes.y1 = std::max( (double)std::floor( (double)renderWindowFullRes.y1 / tile_height ) * tile_height, (double)frameBounds.y1 );
1820                     renderWindowFullRes.x2 = std::min( (double)std::ceil( (double)renderWindowFullRes.x2 / tile_width ) * tile_width, (double)frameBounds.x2 );
1821                     renderWindowFullRes.y2 = std::min( (double)std::ceil( (double)renderWindowFullRes.y2 / tile_height ) * tile_height, (double)frameBounds.y2 );
1822                 }
1823 
1824                 if ( isTileOrientationTopDown() ) {
1825                     //invert back Y
1826                     renderWindowFullRes.y1 = frameHeight - renderWindowFullRes.y1;
1827                     renderWindowFullRes.y2 = frameHeight  - renderWindowFullRes.y2;
1828                     frameBounds.y1 = frameHeight - frameBounds.y1;
1829                     frameBounds.y2 = frameHeight - frameBounds.y2;
1830                 }
1831             }
1832 
1833             int tmpRowBytes = (renderWindowFullRes.x2 - renderWindowFullRes.x1) * pixelBytes;
1834             size_t memSize = (size_t)(renderWindowFullRes.y2 - renderWindowFullRes.y1) * (size_t)tmpRowBytes;
1835             ImageMemory mem(memSize, this);
1836             float *tmpPixelData = (float*)mem.lock();
1837 
1838             // read file
1839             DBG( std::printf("decode (to tmp)\n") );
1840 
1841             if (!_isMultiPlanar) {
1842                 decode(filename, sequenceTime, args.renderView, args.sequentialRenderStatus, renderWindowFullRes, tmpPixelData, renderWindowFullRes, it->comps, it->numChans, tmpRowBytes);
1843             } else {
1844                 decodePlane(filename, sequenceTime, args.renderView, args.sequentialRenderStatus, renderWindowFullRes, tmpPixelData, renderWindowFullRes, it->comps, it->numChans, it->rawComps, tmpRowBytes);
1845             }
1846 
1847             if ( abort() ) {
1848                 return;
1849             }
1850 
1851             ///do the color-space conversion
1852             if ( !isOCIOIdentity && (it->comps != ePixelComponentAlpha) && (it->comps != ePixelComponentXY) ) {
1853                 if (filePremult == eImagePreMultiplied) {
1854                     assert(remappedComponents == ePixelComponentRGBA);
1855                     DBG( std::printf("unpremult (tmp in-place)\n") );
1856                     //tmpPixelData[0] = tmpPixelData[1] = tmpPixelData[2] = tmpPixelData[3] = 0.5;
1857                     unPremultPixelData(renderWindowNotRounded, tmpPixelData, renderWindowFullRes, remappedComponents, it->numChans, firstDepth, tmpRowBytes, tmpPixelData, renderWindowFullRes, remappedComponents, it->numChans, firstDepth, tmpRowBytes);
1858 
1859                     if ( abort() ) {
1860                         return;
1861                     }
1862 
1863                     //assert(tmpPixelData[0] == 1. && tmpPixelData[1] == 1. && tmpPixelData[2] == 1. && tmpPixelData[3] == 0.5);
1864                 }
1865 #ifdef OFX_IO_USING_OCIO
1866                 DBG( std::printf("OCIO (tmp in-place)\n") );
1867                 _ocio->apply(args.time, renderWindowFullRes, tmpPixelData, renderWindowFullRes, remappedComponents, it->numChans, tmpRowBytes);
1868 #endif
1869             }
1870 
1871             if ( kSupportsRenderScale && (downscaleLevels > 0) ) {
1872                 if (!mustPremult) {
1873                     // we can write directly to dstPixelData
1874                     /// adjust the scale to match the given output image
1875                     DBG( std::printf("scale (no premult, tmp to dst)\n") );
1876                     scalePixelData(args.renderWindow, renderWindowNotRounded, (unsigned int)downscaleLevels, tmpPixelData, remappedComponents,
1877                                    it->numChans, firstDepth, renderWindowFullRes, tmpRowBytes, it->pixelData,
1878                                    remappedComponents, it->numChans, firstDepth, firstBounds, it->rowBytes);
1879                 } else {
1880                     // allocate a temporary image (we must avoid reading from dstPixelData, in case several threads are rendering the same area)
1881                     int mem2RowBytes = (firstBounds.x2 - firstBounds.x1) * pixelBytes;
1882                     size_t mem2Size = (size_t)(firstBounds.y2 - firstBounds.y1) * (size_t)mem2RowBytes;
1883                     ImageMemory mem2(mem2Size, this);
1884                     float *scaledPixelData = (float*)mem2.lock();
1885 
1886                     /// adjust the scale to match the given output image
1887                     DBG( std::printf("scale (tmp to scaled)\n") );
1888                     scalePixelData(args.renderWindow, renderWindowNotRounded, (unsigned int)downscaleLevels, tmpPixelData,
1889                                    remappedComponents, it->numChans, firstDepth,
1890                                    renderWindowFullRes, tmpRowBytes, scaledPixelData,
1891                                    remappedComponents, it->numChans, firstDepth,
1892                                    firstBounds, mem2RowBytes);
1893 
1894                     if ( abort() ) {
1895                         return;
1896                     }
1897 
1898                     // apply premult
1899                     DBG( std::printf("premult (scaled to dst)\n") );
1900                     //scaledPixelData[0] = scaledPixelData[1] = scaledPixelData[2] = 1.; scaledPixelData[3] = 0.5;
1901                     premultPixelData(args.renderWindow, scaledPixelData, firstBounds, remappedComponents,  it->numChans, firstDepth, mem2RowBytes, it->pixelData, firstBounds, remappedComponents, it->numChans, firstDepth, it->rowBytes);
1902                     //assert(dstPixelDataF[0] == 0.5 && dstPixelDataF[1] == 0.5 && dstPixelDataF[2] == 0.5 && dstPixelDataF[3] == 0.5);
1903                 }
1904             } else {
1905                 // copy
1906                 if (mustPremult) {
1907                     DBG( std::printf("premult (no scale, tmp to dst)\n") );
1908                     //tmpPixelData[0] = tmpPixelData[1] = tmpPixelData[2] = 1.; tmpPixelData[3] = 0.5;
1909                     premultPixelData(args.renderWindow, tmpPixelData, renderWindowFullRes, remappedComponents, it->numChans, firstDepth, tmpRowBytes, it->pixelData, firstBounds, remappedComponents, it->numChans, firstDepth, it->rowBytes);
1910                     //assert(dstPixelDataF[0] == 0.5 && dstPixelDataF[1] == 0.5 && dstPixelDataF[2] == 0.5 && dstPixelDataF[3] == 0.5);
1911                 } else {
1912                     DBG( std::printf("copy (no premult no scale, tmp to dst)\n") );
1913                     copyPixelData(args.renderWindow, tmpPixelData, renderWindowFullRes, remappedComponents, it->numChans, firstDepth, tmpRowBytes, it->pixelData, firstBounds, remappedComponents, it->numChans, firstDepth, it->rowBytes);
1914                 }
1915             }
1916             mem.unlock();
1917         }
1918     } // for (std::list<PlaneToRender>::iterator it = planes.begin(); it!=planes.end(); ++it) {
1919 }
1920 
1921 void
decode(const string &,OfxTime,int,bool,const OfxRectI &,float *,const OfxRectI &,PixelComponentEnum,int,int)1922 GenericReaderPlugin::decode(const string& /*filename*/,
1923                             OfxTime /*time*/,
1924                             int /*view*/,
1925                             bool /*isPlayback*/,
1926                             const OfxRectI& /*renderWindow*/,
1927                             float */*pixelData*/,
1928                             const OfxRectI& /*bounds*/,
1929                             PixelComponentEnum /*pixelComponents*/,
1930                             int /*pixelComponentCount*/,
1931                             int /*rowBytes*/)
1932 {
1933     //does nothing
1934 }
1935 
1936 void
decodePlane(const string &,OfxTime,int,bool,const OfxRectI &,float *,const OfxRectI &,PixelComponentEnum,int,const string &,int)1937 GenericReaderPlugin::decodePlane(const string& /*filename*/,
1938                                  OfxTime /*time*/,
1939                                  int /*view*/,
1940                                  bool /*isPlayback*/,
1941                                  const OfxRectI& /*renderWindow*/,
1942                                  float */*pixelData*/,
1943                                  const OfxRectI& /*bounds*/,
1944                                  PixelComponentEnum /*pixelComponents*/,
1945                                  int /*pixelComponentCount*/,
1946                                  const string& /*rawComponents*/,
1947                                  int /*rowBytes*/)
1948 {
1949     //does nothing
1950 }
1951 
1952 bool
checkExtension(const string & ext)1953 GenericReaderPlugin::checkExtension(const string& ext)
1954 {
1955     if ( ext.empty() ) {
1956         // no extension
1957         return false;
1958     }
1959 
1960     return std::find(_extensions.begin(), _extensions.end(), ext) != _extensions.end();
1961 }
1962 
1963 /**
1964  * @brief Called from changedParam() when kParamFilename is changed for any reason other than eChangeTime.
1965  * Calls restoreStateFromParams() to update any non-persistent params that may depend on the filename.
1966  * If reason is eChangeUserEdit and the params where never guessed (see _guessedParams) also sets these from the file contents.
1967  * Any derived implementation must call GenericReaderPlugin::changedFilename() first
1968  **/
1969 void
changedFilename(const InstanceChangedArgs & args)1970 GenericReaderPlugin::changedFilename(const InstanceChangedArgs &args)
1971 {
1972     assert(args.reason != eChangeTime);
1973     if (args.reason == eChangeTime) {
1974         return;
1975     }
1976     string filename;
1977 
1978     _fileParam->getValue(filename);
1979 
1980     clearPersistentMessage();
1981 
1982     if ( filename.empty() ) {
1983         // if the file name is set to an empty string,
1984         // reset so that values are automatically set on next call to changedFilename()
1985         _guessedParams->resetToDefault();
1986 
1987         return;
1988     }
1989 
1990     OfxRangeI sequenceTimeDomain;
1991     bool gotSequenceTimeDomain = getSequenceTimeDomainInternal(sequenceTimeDomain, true);
1992     if (!gotSequenceTimeDomain) {
1993         return;
1994     }
1995 
1996     bool customFps;
1997     _customFPS->getValue(customFps);
1998     if (!customFps) {
1999         double fps;
2000         bool gotFps = getFrameRate(filename, &fps);
2001         if (gotFps) {
2002             _fps->setValue(fps);
2003         }
2004     }
2005 
2006     OfxRangeI timeDomain;
2007 
2008     timeDomainFromSequenceTimeDomain(sequenceTimeDomain, sequenceTimeDomain.min, &timeDomain);
2009 
2010     if (args.reason == eChangeUserEdit) {
2011         _firstFrame->setValue(sequenceTimeDomain.min);
2012         _firstFrame->setDefault(sequenceTimeDomain.min);
2013         _lastFrame->setValue(sequenceTimeDomain.max);
2014         _lastFrame->setDefault(sequenceTimeDomain.max);
2015         _startingTime->setDefault(sequenceTimeDomain.min);
2016     }
2017 
2018     // restore state! let the derived class (which calls GenericReaderPlugin::restoreStateFromParams()) initialize any other parameters or structures
2019     restoreStateFromParams();
2020 
2021     // should we guess some parameters? only if it's user-set and it's the first time
2022     if ( (args.reason == eChangeUserEdit) && !_guessedParams->getValue() ) {
2023         _startingTime->setValue(timeDomain.min);
2024 
2025         ///We call guessParamsFromFilename with the first frame of the sequence so we're almost sure it will work
2026         ///unless the user did a mistake. We are also safe to assume that images specs are the same for
2027         ///all the sequence
2028         _fileParam->getValueAtTime(_firstFrame->getValue(), filename); // the time in _fileParam is the *file* time
2029         if ( filename.empty() ) {
2030             // no need to trigger a useless error or a persistent message
2031 
2032             return;
2033         }
2034 
2035         string colorspace;
2036 
2037 #ifdef OFX_IO_USING_OCIO
2038         _ocio->getInputColorspaceDefault(colorspace);
2039 #else
2040         colorspace = "default";
2041 #endif
2042         PixelComponentEnum components = ePixelComponentNone;
2043         int componentCount = 0;
2044         PreMultiplicationEnum filePremult = eImageOpaque;
2045 
2046         assert( !_guessedParams->getValue() );
2047         bool success = guessParamsFromFilename(filename, &colorspace, &filePremult, &components, &componentCount);
2048         if (!success) {
2049             return;
2050         }
2051 
2052         bool setColorSpace = true;
2053 #ifdef OFX_IO_USING_OCIO
2054         // if inputSpaceSet == true (input space was manually set by user) then setColorSpace = false
2055         if ( _inputSpaceSet->getValue() ) {
2056             setColorSpace = false;
2057         }
2058         // Colorspace parsed from filename overrides the file colorspace,
2059         // following recommendations from http://opencolorio.org/configurations/spi_pipeline.html
2060         // However, as discussed in https://groups.google.com/forum/#!topic/ocio-dev/dfOxq8Nanl8
2061         // the OpenColorIO 1.0.9 implementation fails too often.
2062         // We should wait for the next version, where pull request
2063         // https://github.com/imageworks/OpenColorIO/pull/381 or
2064         // https://github.com/imageworks/OpenColorIO/pull/413 may be merged.
2065         OCIO::ConstConfigRcPtr ocioConfig = _ocio->getConfig();
2066         if (setColorSpace && ocioConfig) {
2067             string name = filename;
2068             (void)SequenceParsing::removePath(name); // only use the file NAME
2069             const char* colorSpaceStr = ocioConfig->parseColorSpaceFromString( name.c_str() );
2070             size_t colorSpaceStrLen = colorSpaceStr ? std::strlen(colorSpaceStr) : 0;
2071             if (colorSpaceStrLen == 0) {
2072                 colorSpaceStr = NULL;
2073             }
2074 #if OCIO_VERSION_HEX > 0x01000900 // more recent than 1.0.9?
2075 #pragma message WARN("OpenColorIO was updated, check that the following code is still necessary")
2076 #endif
2077             if (colorSpaceStr) {
2078                 // Only use this colorspace name if it is the last thing before the extension name,
2079                 // and it is preceded by '_' or '-' or ' ' or '.' (we exclude '/' and '\\'),
2080                 // as in http://opencolorio.org/configurations/spi_pipeline.html
2081                 // https://github.com/imageworks/OpenColorIO/pull/413
2082                 size_t pos = name.find_last_of('.');
2083                 if ( pos == string::npos || pos < (colorSpaceStrLen + 1) ) { // +1 for the delimiter
2084                     // no dot, or the colorspace name and the delimiter cannot hold before the dot
2085                     colorSpaceStr = NULL;
2086                     colorSpaceStrLen = 0;
2087                 } else if ( (name.compare(pos - colorSpaceStrLen, colorSpaceStrLen, colorSpaceStr) != 0) ||
2088                             ( (name[pos - colorSpaceStrLen - 1] != '_') &&
2089                               (name[pos - colorSpaceStrLen - 1] != '-') &&
2090                               (name[pos - colorSpaceStrLen - 1] != ' ') &&
2091                               (name[pos - colorSpaceStrLen - 1] != '.') ) ) {
2092                                // the colorspace name is not before the last dot or is not preceded by a valid delimiter
2093                     colorSpaceStr = NULL;
2094                     colorSpaceStrLen = 0;
2095                 }
2096             }
2097             if (colorSpaceStr) {
2098                 assert( _ocio->hasColorspace(colorSpaceStr) ); // parseColorSpaceFromString always returns an existing colorspace
2099                 // we're lucky
2100                 _ocio->setInputColorspace(colorSpaceStr);
2101                 setColorSpace = false;
2102             }
2103             if (setColorSpace) {
2104                 _ocio->setInputColorspace( colorspace.c_str() );
2105             }
2106         }
2107 
2108         // Refreshing the choice menus of ocio is required since the instanceChanged action
2109         // may not be called recursively during the createInstance action
2110         _ocio->refreshInputAndOutputState(timeDomain.min);
2111 #endif
2112         // RGB is always Opaque, Alpha is always PreMultiplied
2113         if (components == ePixelComponentRGB) {
2114             filePremult = eImageOpaque;
2115         } else if (components == ePixelComponentAlpha) {
2116             filePremult = eImagePreMultiplied;
2117         }
2118         if (components != ePixelComponentNone) {
2119             setOutputComponents(components);
2120         }
2121         _filePremult->setValue( (int)filePremult );
2122 
2123         if (components == ePixelComponentRGB) {
2124             // RGB is always opaque
2125             _outputPremult->setValue(eImageOpaque);
2126         } else if (components == ePixelComponentAlpha) {
2127             // Alpha is always premultiplied
2128             _outputPremult->setValue(eImagePreMultiplied);
2129         }
2130 
2131         _guessedParams->setValue(true); // do not try to guess params anymore on this instance
2132     } // if ( args.reason == eChangeUserEdit && !_guessedParams->getValue() ) {
2133 } // GenericReaderPlugin::changedFilename
2134 
2135 void
changedParam(const InstanceChangedArgs & args,const string & paramName)2136 GenericReaderPlugin::changedParam(const InstanceChangedArgs &args,
2137                                   const string &paramName)
2138 {
2139     const double time = args.time;
2140 
2141     if ( !kSupportsRenderScale && ( (args.renderScale.x != 1.) || (args.renderScale.y != 1.) ) ) {
2142         throwSuiteStatusException(kOfxStatFailed);
2143 
2144         return;
2145     }
2146 
2147     // please check the reason for each parameter when it makes sense!
2148 
2149     if (paramName == kParamFilename) {
2150         if (args.reason != eChangeTime) {
2151             // must clear persistent message, or render() is not called by Nuke after an error
2152             clearPersistentMessage();
2153             changedFilename(args);
2154         }
2155         if ( _sublabel && (args.reason != eChangePluginEdit) ) {
2156             refreshSubLabel(args.time);
2157         }
2158     } else if (paramName == kParamProxy) {
2159         ///Detect the scale of the proxy.
2160         string proxyFile, originalFileName;
2161         double sequenceTime;
2162         GetSequenceTimeRetEnum getSequenceTimeRet = getSequenceTime(args.time, &sequenceTime);
2163         switch (getSequenceTimeRet) {
2164         case eGetSequenceTimeBlack:
2165         case eGetSequenceTimeError:
2166             break;
2167 
2168         case eGetSequenceTimeBeforeSequence:
2169         case eGetSequenceTimeWithinSequence:
2170         case eGetSequenceTimeAfterSequence: {
2171             GetFilenameRetCodeEnum getFilenameAtSequenceTimeRet   = getFilenameAtSequenceTime(sequenceTime, false, true, &originalFileName);
2172             GetFilenameRetCodeEnum getFilenameAtSequenceTimeRetPx = getFilenameAtSequenceTime(sequenceTime, true, true, &proxyFile);
2173 
2174             if ( ( getFilenameAtSequenceTimeRet == eGetFileNameReturnedFullRes) &&
2175                  ( getFilenameAtSequenceTimeRetPx == eGetFileNameReturnedProxy) &&
2176                  ( proxyFile != originalFileName) ) {
2177                 assert( !proxyFile.empty() );
2178                 ///show the scale param
2179                 _proxyThreshold->setIsSecretAndDisabled(false);
2180                 _enableCustomScale->setIsSecretAndDisabled(false);
2181 
2182                 OfxPointD scale = detectProxyScale(originalFileName, proxyFile, args.time);
2183                 _proxyThreshold->setValue(scale.x, scale.y);
2184                 _originalProxyScale->setValue(scale.x, scale.y);
2185             } else {
2186                 _proxyThreshold->setIsSecretAndDisabled(true);
2187                 _enableCustomScale->setIsSecretAndDisabled(true);
2188             }
2189             break;
2190         }
2191         }
2192     } else if (paramName == kParamCustomProxyScale) {
2193         bool enabled = _enableCustomScale->getValueAtTime(time);
2194         _proxyThreshold->setEnabled(enabled);
2195     } else if (paramName == kParamOriginalFrameRange) {
2196         int oFirst, oLast;
2197         _originalFrameRange->getValue(oFirst, oLast);
2198         string filename;
2199         _fileParam->getValueAtTime(oFirst, filename); // the time in _fileParam is the *file* time
2200         if ( isVideoStream(filename) ) {
2201             return;
2202         }
2203 
2204         _firstFrame->setDisplayRange(oFirst, oLast);
2205         _lastFrame->setDisplayRange(oFirst, oLast);
2206 
2207         _firstFrame->setValue(oFirst);
2208         _firstFrame->setDefault(oFirst);
2209         _lastFrame->setValue(oLast);
2210         _lastFrame->setDefault(oLast);
2211 
2212         _startingTime->setValue(oFirst);
2213         _startingTime->setDefault(oFirst);
2214     } else if ( (paramName == kParamFirstFrame) && (args.reason == eChangeUserEdit) ) {
2215         int first;
2216         int oFirst, oLast;
2217         _originalFrameRange->getValue(oFirst, oLast);
2218         _firstFrame->getValue(first);
2219         _lastFrame->setDisplayRange(first, oLast);
2220 
2221         int offset;
2222         _timeOffset->getValue(offset);
2223         _startingTime->setValue(first + offset); // will be called with reason == eChangePluginEdit
2224 
2225         _timeDomainUserSet->setValue(true);
2226     } else if ( (paramName == kParamLastFrame) && (args.reason == eChangeUserEdit) ) {
2227         int first;
2228         int last;
2229         int oFirst, oLast;
2230         _originalFrameRange->getValue(oFirst, oLast);
2231         _firstFrame->getValue(first);
2232         _lastFrame->getValue(last);
2233         _firstFrame->setDisplayRange(oFirst, last);
2234 
2235         _timeDomainUserSet->setValue(true);
2236     } else if ( (paramName == kParamFrameMode) && (args.reason == eChangeUserEdit) ) {
2237         FrameModeEnum frameMode = (FrameModeEnum)_frameMode->getValueAtTime(time);
2238         switch (frameMode) {
2239         case eFrameModeStartingTime:     //starting frame
2240             _startingTime->setIsSecretAndDisabled(false);
2241             _timeOffset->setIsSecretAndDisabled(true);
2242             break;
2243         case eFrameModeTimeOffset:     //time offset
2244             _startingTime->setIsSecretAndDisabled(true);
2245             _timeOffset->setIsSecretAndDisabled(false);
2246             break;
2247         }
2248     } else if ( (paramName == kParamStartingTime) && (args.reason == eChangeUserEdit) ) {
2249         ///recompute the timedomain
2250         OfxRangeI sequenceTimeDomain;
2251         getSequenceTimeDomainInternal(sequenceTimeDomain, true);
2252 
2253         //also update the time offset
2254         int startingTime = _startingTime->getValueAtTime(time);
2255         int firstFrame = _firstFrame->getValueAtTime(time);
2256 
2257         _timeOffset->setValue(startingTime - firstFrame);
2258         _timeDomainUserSet->setValue(true);
2259     } else if ( (paramName == kParamTimeOffset) && (args.reason == eChangeUserEdit) ) {
2260         //also update the starting frame
2261         int offset = _timeOffset->getValueAtTime(time);
2262         int first = _firstFrame->getValueAtTime(time);
2263 
2264         _startingTime->setValue(offset + first);
2265         _timeDomainUserSet->setValue(true);
2266     } else if ( (paramName == kParamOutputComponents) && (args.reason == eChangeUserEdit) ) {
2267         PixelComponentEnum comps = getOutputComponents();
2268         PreMultiplicationEnum premult = (PreMultiplicationEnum)_outputPremult->getValueAtTime(time);
2269         if ( (comps == ePixelComponentRGB) && (premult != eImageOpaque) ) {
2270             // RGB is always opaque
2271             _outputPremult->setValue(eImageOpaque);
2272         } else if ( (comps == ePixelComponentAlpha) && (premult != eImagePreMultiplied) ) {
2273             // Alpha is always premultiplied
2274             _outputPremult->setValue(eImagePreMultiplied);
2275         }
2276     } else if ( (paramName == kParamOutputPremult) && (args.reason == eChangeUserEdit) ) {
2277         PreMultiplicationEnum premult = (PreMultiplicationEnum)_outputPremult->getValueAtTime(time);
2278         PixelComponentEnum comps = getOutputComponents();
2279         // reset to authorized values if necessary
2280         if ( (comps == ePixelComponentRGB) && (premult != eImageOpaque) ) {
2281             // RGB is always opaque
2282             _outputPremult->setValue( (int)eImageOpaque );
2283         } else if ( (comps == ePixelComponentAlpha) && (premult != eImagePreMultiplied) ) {
2284             // Alpha is always premultiplied
2285             _outputPremult->setValue( (int)eImagePreMultiplied );
2286         }
2287     } else if (paramName == kParamCustomFps) {
2288         bool customFps = _customFPS->getValueAtTime(time);
2289         _fps->setEnabled(customFps);
2290 
2291         if (!customFps) {
2292             OfxRangeI sequenceTimeDomain;
2293             if ( getSequenceTimeDomainInternal(sequenceTimeDomain, false) ) {
2294                 OfxRangeI timeDomain;
2295                 ///these are the value held by the "First frame" and "Last frame" param
2296                 sequenceTimeDomain.min = _firstFrame->getValue();
2297                 sequenceTimeDomain.max = _lastFrame->getValue();
2298                 int startingTime = _startingTime->getValue();
2299                 timeDomainFromSequenceTimeDomain(sequenceTimeDomain, startingTime, &timeDomain);
2300                 _startingTime->setValue(timeDomain.min);
2301 
2302                 ///We call guessParamsFromFilename with the first frame of the sequence so we're almost sure it will work
2303                 ///unless the user did a mistake. We are also safe to assume that images specs are the same for
2304                 ///all the sequence
2305                 string filename;
2306                 _fileParam->getValueAtTime(_firstFrame->getValue(), filename); // the time in _fileParam is the *file* time
2307 
2308                 double fps;
2309                 bool gotFps = getFrameRate(filename, &fps);
2310                 if  (gotFps) {
2311                     _fps->setValue(fps);
2312                 }
2313             }
2314         }
2315 #ifdef OFX_IO_USING_OCIO
2316     } else if ( ( (paramName == kOCIOParamInputSpace) || (paramName == kOCIOParamInputSpaceChoice) ) &&
2317                ( args.reason == eChangeUserEdit) ) {
2318         // set the inputSpaceSet param to true https://github.com/MrKepzie/Natron/issues/1492
2319         _inputSpaceSet->setValue(true);
2320 #endif
2321     }
2322 
2323 #ifdef OFX_IO_USING_OCIO
2324     _ocio->changedParam(args, paramName);
2325 #endif
2326 } // GenericReaderPlugin::changedParam
2327 
2328 PixelComponentEnum
getOutputComponents() const2329 GenericReaderPlugin::getOutputComponents() const
2330 {
2331     int outputComponents_i = _outputComponents->getValue();
2332 
2333     return _outputComponentsTable[outputComponents_i];
2334 }
2335 
2336 void
setOutputComponents(PixelComponentEnum comps)2337 GenericReaderPlugin::setOutputComponents(PixelComponentEnum comps)
2338 {
2339     int i;
2340 
2341     for (i = 0; i < 4 && _outputComponentsTable[i] != comps; ++i) {
2342     }
2343     if (i >= 4) {
2344         // not found, set the first supported component
2345         i = 0;
2346     }
2347     assert( i < _outputComponents->getNOptions() );
2348     _outputComponents->setValue(i);
2349 }
2350 
2351 /* Override the clip preferences */
2352 void
getClipPreferences(ClipPreferencesSetter & clipPreferences)2353 GenericReaderPlugin::getClipPreferences(ClipPreferencesSetter &clipPreferences)
2354 {
2355     // if there is only one frame and before/after behaviour is hold, then
2356     // the output is not framevarying
2357     bool frameVarying = true;
2358     OfxRangeI sequenceTimeDomain;
2359     bool gotSequenceTimeDomain = getSequenceTimeDomainInternal(sequenceTimeDomain, false);
2360 
2361     if (sequenceTimeDomain.min == sequenceTimeDomain.max) {
2362         int beforeChoice_i;
2363         _beforeFirst->getValue(beforeChoice_i);
2364         BeforeAfterEnum beforeChoice = BeforeAfterEnum(beforeChoice_i);
2365         int afterChoice_i;
2366         _afterLast->getValue(afterChoice_i);
2367         BeforeAfterEnum afterChoice = BeforeAfterEnum(afterChoice_i);
2368         if ( (beforeChoice == eBeforeAfterHold) && (afterChoice == eBeforeAfterHold) ) {
2369             frameVarying = false;
2370         }
2371     }
2372     clipPreferences.setOutputFrameVarying(frameVarying); // true for readers and frame-varying generators/effects @see kOfxImageEffectFrameVarying
2373 
2374     PixelComponentEnum outputComponents = getOutputComponents();
2375     clipPreferences.setClipComponents(*_outputClip, outputComponents);
2376 
2377     int premult_i;
2378     _outputPremult->getValue(premult_i);
2379     PreMultiplicationEnum premult = (PreMultiplicationEnum)premult_i;
2380     switch (outputComponents) {
2381     case ePixelComponentAlpha:
2382         if (!_supportsAlpha) {
2383             throwSuiteStatusException(kOfxStatErrFormat);
2384 
2385             return;
2386         }
2387         // alpha is always premultiplied
2388         premult = eImagePreMultiplied;
2389         break;
2390 
2391     case ePixelComponentRGBA:
2392         break;
2393     default:
2394         if (!_supportsRGB) {
2395             throwSuiteStatusException(kOfxStatErrFormat);
2396 
2397             return;
2398         }
2399         // RGB is always Opaque
2400         premult = eImageOpaque;
2401         break;
2402     }
2403     clipPreferences.setOutputPremultiplication(premult);
2404 
2405     // get the pixel aspect ratio from the first frame
2406     if (gotSequenceTimeDomain) {
2407         OfxRangeI timeDomain;
2408         ///these are the value held by the "First frame" and "Last frame" param
2409         sequenceTimeDomain.min = _firstFrame->getValue();
2410         sequenceTimeDomain.max = _lastFrame->getValue();
2411         int startingTime = _startingTime->getValue();
2412         timeDomainFromSequenceTimeDomain(sequenceTimeDomain, startingTime, &timeDomain);
2413 
2414         string filename;
2415         GetFilenameRetCodeEnum e = getFilenameAtSequenceTime(timeDomain.min, false, true, &filename);
2416         if (e == eGetFileNameReturnedFullRes) {
2417             OfxRectI bounds, format;
2418             double par = 1.;
2419             string error;
2420             int tile_width, tile_height;
2421             bool success = getFrameBounds(filename, timeDomain.min, /*view=*/0, &bounds, &format, &par, &error, &tile_width, &tile_height);
2422             if (success) {
2423                 clipPreferences.setPixelAspectRatio(*_outputClip, par);
2424                 clipPreferences.setOutputFormat(format);
2425             }
2426 
2427             bool customFPS;
2428             _customFPS->getValue(customFPS);
2429 
2430             double fps;
2431             if (customFPS) {
2432                 _fps->getValue(fps);
2433                 clipPreferences.setOutputFrameRate(fps);
2434             } else {
2435                 success = getFrameRate(filename, &fps);
2436                 if (success) {
2437                     clipPreferences.setOutputFrameRate(fps);
2438                 }
2439             }
2440         }
2441     }
2442 } // GenericReaderPlugin::getClipPreferences
2443 
2444 void
purgeCaches()2445 GenericReaderPlugin::purgeCaches()
2446 {
2447     clearAnyCache();
2448 #ifdef OFX_IO_USING_OCIO
2449     _ocio->purgeCaches();
2450 #endif
2451 }
2452 
2453 bool
isIdentity(const IsIdentityArguments & args,Clip * & identityClip,double & identityTime,int &,std::string &)2454 GenericReaderPlugin::isIdentity(const IsIdentityArguments &args,
2455                                 Clip * &identityClip,
2456                                 double &identityTime
2457                                 , int& /*view*/, std::string& /*plane*/)
2458 {
2459     if ( !kSupportsRenderScale && ( (args.renderScale.x != 1.) || (args.renderScale.y != 1.) ) ) {
2460         throwSuiteStatusException(kOfxStatFailed);
2461 
2462         return false;
2463     }
2464 
2465     if (!gHostIsNatron) {
2466         // only Natron supports setting the identityClip to the output clip
2467         return false;
2468     }
2469 
2470     double sequenceTime;
2471     GetSequenceTimeRetEnum getSequenceTimeRet = getSequenceTime(args.time, &sequenceTime);
2472     switch (getSequenceTimeRet) {
2473     case eGetSequenceTimeBlack:
2474 
2475         return false;
2476 
2477     case eGetSequenceTimeError:
2478         throwSuiteStatusException(kOfxStatFailed);
2479 
2480         return false;
2481 
2482     case eGetSequenceTimeBeforeSequence:
2483     case eGetSequenceTimeAfterSequence: {
2484         ///Transform the sequence time to "real" time
2485         int timeOffset;
2486         _timeOffset->getValue(timeOffset);
2487         identityTime = std::floor(sequenceTime + 0.5) + timeOffset;     // round to the nearest frame
2488         identityClip = _outputClip;
2489 
2490         return true;
2491     }
2492     case eGetSequenceTimeWithinSequence: {
2493         if (sequenceTime == (int)sequenceTime) {
2494             return false;
2495         }
2496         // fractional time, round to nearest frame
2497         sequenceTime = std::floor(sequenceTime + 0.5);     // round to the nearest frame
2498         ///Transform the sequence time to "real" time
2499         int timeOffset;
2500         _timeOffset->getValue(timeOffset);
2501         identityTime = sequenceTime + timeOffset;
2502         identityClip = _outputClip;
2503 
2504         return true;
2505     }
2506     }
2507 
2508     return false;
2509 } // GenericReaderPlugin::isIdentity
2510 
2511 OfxPointD
detectProxyScale(const string & originalFileName,const string & proxyFileName,OfxTime time)2512 GenericReaderPlugin::detectProxyScale(const string& originalFileName,
2513                                       const string& proxyFileName,
2514                                       OfxTime time)
2515 {
2516     OfxRectI originalBounds, proxyBounds, originalFormat, proxyFormat;
2517     string error;
2518     double originalPAR = 1., proxyPAR = 1.;
2519     int tile_width, tile_height;
2520     bool success = getFrameBounds(originalFileName, time, /*view=*/0, &originalBounds, &originalFormat, &originalPAR, &error, &tile_width, &tile_height);
2521 
2522     proxyBounds.x1 = proxyBounds.x2 = proxyBounds.y1 = proxyBounds.y2 = 0.f;
2523     success = success && getFrameBounds(proxyFileName, time, /*view=*/0, &proxyBounds, &proxyFormat, &proxyPAR, &error, &tile_width, &tile_height);
2524     OfxPointD ret;
2525     if ( !success ||
2526          (originalBounds.x1 == originalBounds.x2) ||
2527          (originalBounds.y1 == originalBounds.y2) ||
2528          (proxyBounds.x1 == proxyBounds.x2) ||
2529          (proxyBounds.y1 == proxyBounds.y2) ) {
2530         ret.x = 1.;
2531         ret.y = 1.;
2532         setPersistentMessage(Message::eMessageError, "", "Cannot read the proxy file.");
2533 
2534         return ret;
2535     }
2536     assert(originalBounds.x2 - originalBounds.x1);
2537     assert(originalBounds.y2 - originalBounds.y1);
2538     ret.x = ( (proxyBounds.x2 - proxyBounds.x1)  * proxyPAR ) / ( (originalBounds.x2 - originalBounds.x1) * originalPAR );
2539     ret.y = (proxyBounds.y2 - proxyBounds.y1) / (double)(originalBounds.y2 - originalBounds.y1);
2540 
2541     return ret;
2542 }
2543 
2544 template<typename SRCPIX, int srcMaxValue, int nSrcComp, int nDstComp>
2545 class PixelConverterProcessor
2546     : public PixelProcessor
2547 {
2548     const SRCPIX* _srcPixelData;
2549     int _dstBufferRowBytes;
2550     int _srcBufferRowBytes;
2551     OfxRectI _srcBufferBounds;
2552 
2553 public:
2554     // ctor
PixelConverterProcessor(ImageEffect & instance)2555     PixelConverterProcessor(ImageEffect &instance)
2556         : PixelProcessor(instance)
2557         , _srcPixelData(NULL)
2558         , _dstBufferRowBytes(0)
2559         , _srcBufferRowBytes(0)
2560     {
2561         assert(srcMaxValue);
2562         _srcBufferBounds.x1 = _srcBufferBounds.y1 = _srcBufferBounds.x2 = _srcBufferBounds.y2 = 0;
2563     }
2564 
setValues(const SRCPIX * srcPixelData,const OfxRectI & srcBufferBounds,int srcBufferRowBytes,float * dstPixelData,int dstBufferRowBytes,const OfxRectI & dstBufferBounds)2565     void setValues(const SRCPIX* srcPixelData,
2566                    const OfxRectI &srcBufferBounds,
2567                    int srcBufferRowBytes,
2568                    float* dstPixelData,
2569                    int dstBufferRowBytes,
2570                    const OfxRectI &dstBufferBounds)
2571     {
2572         _srcPixelData = srcPixelData;
2573         _srcBufferBounds = srcBufferBounds;
2574         _srcBufferRowBytes = srcBufferRowBytes;
2575         _dstBufferRowBytes = dstBufferRowBytes;
2576 
2577         _dstBounds = dstBufferBounds;
2578         _dstPixelData = dstPixelData;
2579     }
2580 
2581     // and do some processing
multiThreadProcessImages(OfxRectI procWindow)2582     void multiThreadProcessImages(OfxRectI procWindow)
2583     {
2584         assert(nSrcComp == 1 || nSrcComp == 2 || nSrcComp == 3 || nSrcComp == 4);
2585         assert(nDstComp == 1 || nDstComp == 2 || nDstComp == 3 || nDstComp == 4);
2586 
2587         for (int dsty = procWindow.y1; dsty < procWindow.y2; ++dsty) {
2588             if ( _effect.abort() ) {
2589                 break;
2590             }
2591 
2592             int srcY = _dstBounds.y2 - dsty - 1;
2593             float* dst_pixels = (float*)( (char*)_dstPixelData + (size_t)_dstBufferRowBytes * (dsty - _dstBounds.y1) )
2594                                 + (_dstBounds.x1 * nDstComp);
2595             const SRCPIX* src_pixels = (const SRCPIX*)( (const char*)_srcPixelData + (size_t)_srcBufferRowBytes * (srcY - _srcBufferBounds.y1) )
2596                                        + (_srcBufferBounds.x1 * nSrcComp);
2597 
2598 
2599             assert(dst_pixels && src_pixels);
2600 
2601             for (int x = procWindow.x1; x < procWindow.x2; ++x) {
2602                 int srcCol = x * nSrcComp;
2603                 int dstCol = x * nDstComp;
2604 
2605                 switch (nSrcComp) {
2606                 case 1:
2607                     // alpha
2608                     switch (nDstComp) {
2609                     case 1:
2610                         dst_pixels[dstCol + 0] = src_pixels[srcCol + 0] * (1.f / srcMaxValue);
2611                         break;
2612                     case 2:
2613                         dst_pixels[dstCol + 0] = 0.;
2614                         dst_pixels[dstCol + 1] = 0.;
2615                         break;
2616 
2617                     case 3:
2618                         dst_pixels[dstCol + 0] = 0.;
2619                         dst_pixels[dstCol + 1] = 0.;
2620                         dst_pixels[dstCol + 2] = 0.;
2621                         break;
2622 
2623                     case 4:
2624                         dst_pixels[dstCol + 0] = 0.;
2625                         dst_pixels[dstCol + 1] = 0.;
2626                         dst_pixels[dstCol + 2] = 0.;
2627                         dst_pixels[dstCol + 3] = src_pixels[srcCol + 0] * (1.f / srcMaxValue);
2628                         break;
2629                     default:
2630                         assert(false);
2631 
2632                         return;
2633                     }
2634                     break;
2635                 case 2:
2636                     // XY
2637                     switch (nDstComp) {
2638                     case 1:
2639                         dst_pixels[dstCol + 0] = 0.;
2640                         break;
2641                     case 2:
2642                         dst_pixels[dstCol + 0] = src_pixels[srcCol + 0] * (1.f / srcMaxValue);
2643                         dst_pixels[dstCol + 1] = src_pixels[srcCol + 1] * (1.f / srcMaxValue);
2644                         break;
2645 
2646                     case 3:
2647                         dst_pixels[dstCol + 0] = src_pixels[srcCol + 0] * (1.f / srcMaxValue);
2648                         dst_pixels[dstCol + 1] = src_pixels[srcCol + 1] * (1.f / srcMaxValue);
2649                         dst_pixels[dstCol + 2] = 0;
2650                         break;
2651 
2652                     case 4:
2653                         dst_pixels[dstCol + 0] = src_pixels[srcCol + 0] * (1.f / srcMaxValue);
2654                         dst_pixels[dstCol + 1] = src_pixels[srcCol + 1] * (1.f / srcMaxValue);
2655                         dst_pixels[dstCol + 2] = 0.;
2656                         dst_pixels[dstCol + 3] = 1.f;
2657                         break;
2658                     default:
2659                         assert(false);
2660 
2661                         return;
2662                     }
2663 
2664                     break;
2665                 case 3:
2666                     // RGB
2667                     switch (nDstComp) {
2668                     case 1: {
2669                         dst_pixels[dstCol + 0] = 0.;
2670                         break;
2671                     }
2672                     case 2:
2673                         dst_pixels[dstCol + 0] = src_pixels[srcCol + 0] * (1.f / srcMaxValue);
2674                         dst_pixels[dstCol + 1] = src_pixels[srcCol + 1] * (1.f / srcMaxValue);
2675                         break;
2676 
2677                     case 3:
2678                         dst_pixels[dstCol + 0] = src_pixels[srcCol + 0] * (1.f / srcMaxValue);
2679                         dst_pixels[dstCol + 1] = src_pixels[srcCol + 1] * (1.f / srcMaxValue);
2680                         dst_pixels[dstCol + 2] = src_pixels[srcCol + 2] * (1.f / srcMaxValue);
2681                         break;
2682 
2683                     case 4:
2684                         dst_pixels[dstCol + 0] = src_pixels[srcCol + 0] * (1.f / srcMaxValue);
2685                         dst_pixels[dstCol + 1] = src_pixels[srcCol + 1] * (1.f / srcMaxValue);
2686                         dst_pixels[dstCol + 2] = src_pixels[srcCol + 2] * (1.f / srcMaxValue);
2687                         dst_pixels[dstCol + 3] = 1.f;
2688                         break;
2689                     default:
2690                         assert(false);
2691 
2692                         return;
2693                     }
2694 
2695                     break;
2696 
2697                 case 4:
2698                     switch (nDstComp) {
2699                     case 1:
2700                         dst_pixels[dstCol + 0] = src_pixels[srcCol + 3] * (1.f / srcMaxValue);
2701                         break;
2702                     case 2:
2703                         dst_pixels[dstCol + 0] = src_pixels[srcCol + 0] * (1.f / srcMaxValue);
2704                         dst_pixels[dstCol + 1] = src_pixels[srcCol + 1] * (1.f / srcMaxValue);
2705                         break;
2706 
2707                     case 3:
2708                         dst_pixels[dstCol + 0] = src_pixels[srcCol + 0] * (1.f / srcMaxValue);
2709                         dst_pixels[dstCol + 1] = src_pixels[srcCol + 1] * (1.f / srcMaxValue);
2710                         dst_pixels[dstCol + 2] = src_pixels[srcCol + 2] * (1.f / srcMaxValue);
2711                         break;
2712 
2713                     case 4:
2714                         dst_pixels[dstCol + 0] = src_pixels[srcCol + 0] * (1.f / srcMaxValue);
2715                         dst_pixels[dstCol + 1] = src_pixels[srcCol + 1] * (1.f / srcMaxValue);
2716                         dst_pixels[dstCol + 2] = src_pixels[srcCol + 2] * (1.f / srcMaxValue);
2717                         dst_pixels[dstCol + 3] = src_pixels[srcCol + 3] * (1.f / srcMaxValue);
2718                         break;
2719                     default:
2720                         assert(false);
2721 
2722                         return;
2723                     }
2724 
2725                     break;
2726                 default:
2727                     assert(false);
2728 
2729                     return;
2730                 } // switch
2731             }
2732         }
2733     } // multiThreadProcessImages
2734 };
2735 
2736 template<typename SRCPIX, int srcMaxValue, int nSrcComp, int nDstComp>
2737 void
convertForDstNComps(ImageEffect * effect,const SRCPIX * srcPixelData,const OfxRectI & renderWindow,const OfxRectI & srcBounds,int srcRowBytes,float * dstPixelData,const OfxRectI & dstBounds,int dstRowBytes)2738 convertForDstNComps(ImageEffect* effect,
2739                     const SRCPIX* srcPixelData,
2740                     const OfxRectI& renderWindow,
2741                     const OfxRectI& srcBounds,
2742                     int srcRowBytes,
2743                     float *dstPixelData,
2744                     const OfxRectI& dstBounds,
2745                     int dstRowBytes)
2746 {
2747     PixelConverterProcessor<SRCPIX, srcMaxValue, nSrcComp, nDstComp> p(*effect);
2748     p.setValues(srcPixelData, srcBounds, srcRowBytes, dstPixelData,  dstRowBytes,  dstBounds);
2749     p.setRenderWindow(renderWindow);
2750     p.process();
2751 }
2752 
2753 template<typename SRCPIX, int srcMaxValue, int nSrcComp>
2754 void
convertForSrcNComps(ImageEffect * effect,const SRCPIX * srcPixelData,const OfxRectI & renderWindow,const OfxRectI & srcBounds,int srcRowBytes,float * dstPixelData,const OfxRectI & dstBounds,PixelComponentEnum dstPixelComponents,int dstRowBytes)2755 convertForSrcNComps(ImageEffect* effect,
2756                     const SRCPIX* srcPixelData,
2757                     const OfxRectI& renderWindow,
2758                     const OfxRectI& srcBounds,
2759                     int srcRowBytes,
2760                     float *dstPixelData,
2761                     const OfxRectI& dstBounds,
2762                     PixelComponentEnum dstPixelComponents,
2763                     int dstRowBytes)
2764 {
2765     switch (dstPixelComponents) {
2766     case ePixelComponentAlpha: {
2767         convertForDstNComps<SRCPIX, srcMaxValue, nSrcComp, 1>(effect, srcPixelData, renderWindow, srcBounds, srcRowBytes, dstPixelData, dstBounds, dstRowBytes);
2768         break;
2769     }
2770     case ePixelComponentXY: {
2771         convertForDstNComps<SRCPIX, srcMaxValue, nSrcComp, 2>(effect, srcPixelData, renderWindow, srcBounds, srcRowBytes, dstPixelData, dstBounds, dstRowBytes);
2772         break;
2773     }
2774     case ePixelComponentRGB: {
2775         convertForDstNComps<SRCPIX, srcMaxValue, nSrcComp, 3>(effect, srcPixelData, renderWindow, srcBounds, srcRowBytes, dstPixelData, dstBounds, dstRowBytes);
2776         break;
2777     }
2778     case ePixelComponentRGBA: {
2779         convertForDstNComps<SRCPIX, srcMaxValue, nSrcComp, 4>(effect, srcPixelData, renderWindow, srcBounds, srcRowBytes, dstPixelData, dstBounds, dstRowBytes);
2780         break;
2781     }
2782     default:
2783         assert(false);
2784         break;
2785     }
2786 }
2787 
2788 template<typename SRCPIX, int srcMaxValue>
2789 void
convertForDepth(ImageEffect * effect,const SRCPIX * srcPixelData,const OfxRectI & renderWindow,const OfxRectI & srcBounds,PixelComponentEnum srcPixelComponents,int srcRowBytes,float * dstPixelData,const OfxRectI & dstBounds,PixelComponentEnum dstPixelComponents,int dstRowBytes)2790 convertForDepth(ImageEffect* effect,
2791                 const SRCPIX* srcPixelData,
2792                 const OfxRectI& renderWindow,
2793                 const OfxRectI& srcBounds,
2794                 PixelComponentEnum srcPixelComponents,
2795                 int srcRowBytes,
2796                 float *dstPixelData,
2797                 const OfxRectI& dstBounds,
2798                 PixelComponentEnum dstPixelComponents,
2799                 int dstRowBytes)
2800 {
2801     switch (srcPixelComponents) {
2802     case ePixelComponentAlpha:
2803         convertForSrcNComps<SRCPIX, srcMaxValue, 1>(effect, srcPixelData, renderWindow, srcBounds, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstRowBytes);
2804         break;
2805     case ePixelComponentXY:
2806         convertForSrcNComps<SRCPIX, srcMaxValue, 2>(effect, srcPixelData, renderWindow, srcBounds, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstRowBytes);
2807         break;
2808     case ePixelComponentRGB:
2809         convertForSrcNComps<SRCPIX, srcMaxValue, 3>(effect, srcPixelData, renderWindow, srcBounds, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstRowBytes);
2810         break;
2811     case ePixelComponentRGBA:
2812         convertForSrcNComps<SRCPIX, srcMaxValue, 4>(effect, srcPixelData, renderWindow, srcBounds, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstRowBytes);
2813         break;
2814     default:
2815         assert(false);
2816         break;
2817     }
2818 }
2819 
2820 void
convertDepthAndComponents(const void * srcPixelData,const OfxRectI & renderWindow,const OfxRectI & srcBounds,PixelComponentEnum srcPixelComponents,BitDepthEnum srcBitDepth,int srcRowBytes,float * dstPixelData,const OfxRectI & dstBounds,PixelComponentEnum dstPixelComponents,int dstRowBytes)2821 GenericReaderPlugin::convertDepthAndComponents(const void* srcPixelData,
2822                                                const OfxRectI& renderWindow,
2823                                                const OfxRectI& srcBounds,
2824                                                PixelComponentEnum srcPixelComponents,
2825                                                BitDepthEnum srcBitDepth,
2826                                                int srcRowBytes,
2827                                                float *dstPixelData,
2828                                                const OfxRectI& dstBounds,
2829                                                PixelComponentEnum dstPixelComponents,
2830                                                int dstRowBytes)
2831 {
2832     switch (srcBitDepth) {
2833     case eBitDepthFloat:
2834         convertForDepth<float, 1>(this, (const float*)srcPixelData, renderWindow, srcBounds, srcPixelComponents, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstRowBytes);
2835         break;
2836     case eBitDepthUShort:
2837         convertForDepth<unsigned short, 65535>(this, (const unsigned short*)srcPixelData, renderWindow, srcBounds, srcPixelComponents, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstRowBytes);
2838         break;
2839     case eBitDepthUByte:
2840         convertForDepth<unsigned char, 255>(this, (const unsigned char*)srcPixelData, renderWindow, srcBounds, srcPixelComponents, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstRowBytes);
2841         break;
2842     default:
2843         assert(false);
2844         break;
2845     }
2846 }
2847 
2848 void
GenericReaderDescribe(ImageEffectDescriptor & desc,const std::vector<string> & extensions,int evaluation,bool supportsTiles,bool multiPlanar)2849 GenericReaderDescribe(ImageEffectDescriptor &desc,
2850                       const std::vector<string>& extensions,
2851                       int evaluation,
2852                       bool supportsTiles,
2853                       bool multiPlanar)
2854 {
2855     desc.setPluginGrouping(kPluginGrouping);
2856 
2857 #ifdef OFX_EXTENSIONS_TUTTLE
2858     desc.addSupportedContext(eContextReader);
2859 #endif
2860     desc.addSupportedContext(eContextGenerator);
2861     desc.addSupportedContext(eContextGeneral);
2862 
2863     // add supported pixel depths
2864     //desc.addSupportedBitDepth( eBitDepthUByte );
2865     //desc.addSupportedBitDepth( eBitDepthUShort );
2866     desc.addSupportedBitDepth(eBitDepthFloat);
2867 
2868     // set a few flags
2869     desc.setSingleInstance(false);
2870     desc.setHostFrameThreading(false);
2871 
2872     desc.setSupportsMultiResolution(kSupportsMultiResolution);
2873 
2874     desc.setSupportsTiles(supportsTiles);
2875     desc.setTemporalClipAccess(false); // say we will not be doing random time access on clips
2876     desc.setRenderTwiceAlways(false);
2877     desc.setSupportsMultipleClipPARs(true); // plugin may setPixelAspectRatio on output clip
2878     desc.setRenderThreadSafety(eRenderFullySafe);
2879 
2880 #ifdef OFX_EXTENSIONS_NUKE
2881     if (getImageEffectHostDescription()
2882         && getImageEffectHostDescription()->isMultiPlanar) {
2883         desc.setIsMultiPlanar(multiPlanar);
2884         if (multiPlanar) {
2885             //We let all un-rendered planes pass-through so that they can be retrieved below by a shuffle node
2886             desc.setPassThroughForNotProcessedPlanes(ePassThroughLevelPassThroughNonRenderedPlanes);
2887         }
2888         desc.setIsViewAware(true);
2889         desc.setIsViewInvariant(eViewInvarianceAllViewsVariant);
2890     }
2891 #endif
2892 #ifdef OFX_EXTENSIONS_TUTTLE
2893     desc.addSupportedExtensions(extensions);
2894     desc.setPluginEvaluation(evaluation);
2895 #endif
2896 #ifdef OFX_EXTENSIONS_NATRON
2897     desc.setChannelSelector(ePixelComponentNone);
2898 #endif
2899 }
2900 
2901 PageParamDescriptor *
GenericReaderDescribeInContextBegin(ImageEffectDescriptor & desc,ContextEnum,bool,bool supportsRGBA,bool supportsRGB,bool supportsXY,bool supportsAlpha,bool supportsTiles,bool addSeparatorAfterLastParameter)2902 GenericReaderDescribeInContextBegin(ImageEffectDescriptor &desc,
2903                                     ContextEnum /*context*/,
2904                                     bool /*isVideoStreamPlugin*/,
2905                                     bool supportsRGBA,
2906                                     bool supportsRGB,
2907                                     bool supportsXY,
2908                                     bool supportsAlpha,
2909                                     bool supportsTiles,
2910                                     bool addSeparatorAfterLastParameter)
2911 {
2912     gHostIsNatron = (getImageEffectHostDescription()->isNatron);
2913 
2914     for (ImageEffectHostDescription::PixelComponentArray::const_iterator it = getImageEffectHostDescription()->_supportedComponents.begin();
2915          it != getImageEffectHostDescription()->_supportedComponents.end();
2916          ++it) {
2917         switch (*it) {
2918         case ePixelComponentRGBA:
2919             gHostSupportsRGBA  = true;
2920             break;
2921         case ePixelComponentRGB:
2922             gHostSupportsRGB = true;
2923             break;
2924         case ePixelComponentXY:
2925             gHostSupportsXY = true;
2926             break;
2927         case ePixelComponentAlpha:
2928             gHostSupportsAlpha = true;
2929             break;
2930         default:
2931             // other components are not supported by this plugin
2932             break;
2933         }
2934     }
2935 
2936     // make some pages and to things in
2937     PageParamDescriptor *page = desc.definePageParam("Controls");
2938 
2939     // create the optional source clip
2940     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
2941     if (supportsRGBA) {
2942         srcClip->addSupportedComponent(ePixelComponentRGBA);
2943     }
2944     if (supportsRGB) {
2945         srcClip->addSupportedComponent(ePixelComponentRGB);
2946     }
2947     if (supportsXY) {
2948         srcClip->addSupportedComponent(ePixelComponentXY);
2949     }
2950     if (supportsAlpha) {
2951         srcClip->addSupportedComponent(ePixelComponentAlpha);
2952     }
2953     srcClip->setSupportsTiles(supportsTiles);
2954     srcClip->setOptional(true);
2955 
2956     // create the mandated output clip
2957     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
2958     if (supportsRGBA) {
2959         dstClip->addSupportedComponent(ePixelComponentRGBA);
2960     }
2961     if (supportsRGB) {
2962         dstClip->addSupportedComponent(ePixelComponentRGB);
2963     }
2964     if (supportsXY) {
2965         dstClip->addSupportedComponent(ePixelComponentXY);
2966     }
2967     if (supportsAlpha) {
2968         dstClip->addSupportedComponent(ePixelComponentAlpha);
2969     }
2970     dstClip->setSupportsTiles(supportsTiles);
2971 
2972 
2973     //////////Input file
2974     {
2975         StringParamDescriptor* param = desc.defineStringParam(kParamFilename);
2976         param->setLabelAndHint(kParamFilenameLabel, kParamFilenameHint);
2977         param->setStringType(eStringTypeFilePath);
2978         param->setFilePathExists(true);
2979         // in the Reader context, the script name must be kOfxImageEffectFileParamName, @see kOfxImageEffectContextReader
2980         param->setScriptName(kParamFilename);
2981         param->setAnimates(false);
2982         desc.addClipPreferencesSlaveParam(*param);
2983         if (page) {
2984             page->addChild(*param);
2985         }
2986     }
2987 
2988     //////////First-frame
2989     {
2990         IntParamDescriptor* param = desc.defineIntParam(kParamFirstFrame);
2991         param->setLabelAndHint(kParamFirstFrameLabel, kParamFirstFrameHint);
2992         param->setDefault(0);
2993         param->setAnimates(false);
2994         param->setLayoutHint(eLayoutHintNoNewLine, 1);
2995         if (page) {
2996             page->addChild(*param);
2997         }
2998     }
2999 
3000     ///////////Before first
3001     {
3002         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamBefore);
3003         param->setLabelAndHint(kParamBeforeLabel, kParamBeforeHint);
3004         assert(param->getNOptions() == GenericReaderPlugin::eBeforeAfterHold);
3005         param->appendOption(kReaderOptionHold);
3006         assert(param->getNOptions() == GenericReaderPlugin::eBeforeAfterLoop);
3007         param->appendOption(kReaderOptionLoop);
3008         assert(param->getNOptions() == GenericReaderPlugin::eBeforeAfterBounce);
3009         param->appendOption(kReaderOptionBounce);
3010         assert(param->getNOptions() == GenericReaderPlugin::eBeforeAfterBlack);
3011         param->appendOption(kReaderOptionBlack);
3012         assert(param->getNOptions() == GenericReaderPlugin::eBeforeAfterError);
3013         param->appendOption(kReaderOptionError);
3014         param->setAnimates(true);
3015         param->setDefault(GenericReaderPlugin::eBeforeAfterHold);
3016         if (page) {
3017             page->addChild(*param);
3018         }
3019     }
3020 
3021     //////////Last-frame
3022     {
3023         IntParamDescriptor* param = desc.defineIntParam(kParamLastFrame);
3024         param->setLabelAndHint(kParamLastFrameLabel, kParamLastFrameHint);
3025         param->setDefault(0);
3026         param->setAnimates(false);
3027         param->setLayoutHint(eLayoutHintNoNewLine, 1);
3028         if (page) {
3029             page->addChild(*param);
3030         }
3031     }
3032 
3033     ///////////After first
3034     {
3035         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamAfter);
3036         param->setLabel(kParamAfterLabel);
3037         param->setHint(kParamAfterHint);
3038         assert(param->getNOptions() == GenericReaderPlugin::eBeforeAfterHold);
3039         param->appendOption(kReaderOptionHold);
3040         assert(param->getNOptions() == GenericReaderPlugin::eBeforeAfterLoop);
3041         param->appendOption(kReaderOptionLoop);
3042         assert(param->getNOptions() == GenericReaderPlugin::eBeforeAfterBounce);
3043         param->appendOption(kReaderOptionBounce);
3044         assert(param->getNOptions() == GenericReaderPlugin::eBeforeAfterBlack);
3045         param->appendOption(kReaderOptionBlack);
3046         assert(param->getNOptions() == GenericReaderPlugin::eBeforeAfterError);
3047         param->appendOption(kReaderOptionError);
3048         param->setAnimates(true);
3049         param->setDefault(GenericReaderPlugin::eBeforeAfterHold);
3050         if (page) {
3051             page->addChild(*param);
3052         }
3053     }
3054 
3055     ///////////Missing frame choice
3056     {
3057         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamOnMissingFrame);
3058         param->setLabel(kParamOnMissingFrameLabel);
3059         param->setHint(kParamOnMissingFrameHint);
3060         assert(param->getNOptions() == eMissingPrevious);
3061         param->appendOption(kReaderOptionPrevious);
3062         assert(param->getNOptions() == eMissingNext);
3063         param->appendOption(kReaderOptionNext);
3064         assert(param->getNOptions() == eMissingNearest);
3065         param->appendOption(kReaderOptionNearest);
3066         assert(param->getNOptions() == eMissingError);
3067         param->appendOption(kReaderOptionError);
3068         assert(param->getNOptions() == eMissingBlack);
3069         param->appendOption(kReaderOptionBlack);
3070         param->setAnimates(true);
3071         param->setDefault(eMissingError);
3072         if (page) {
3073             page->addChild(*param);
3074         }
3075     }
3076 
3077     ///////////Frame-mode
3078     {
3079         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamFrameMode);
3080         param->setLabel(kParamFrameModeLabel);
3081         assert(param->getNOptions() == eFrameModeStartingTime);
3082         param->appendOption(kParamFrameModeOptionStartingTime);
3083         assert(param->getNOptions() == eFrameModeTimeOffset);
3084         param->appendOption(kParamFrameModeOptionTimeOffset);
3085         param->setAnimates(false);
3086         param->setDefault(eFrameModeStartingTime);
3087         param->setLayoutHint(eLayoutHintNoNewLine, 1);
3088         if (page) {
3089             page->addChild(*param);
3090         }
3091     }
3092 
3093     ///////////Starting frame
3094     {
3095         IntParamDescriptor* param = desc.defineIntParam(kParamStartingTime);
3096         param->setLabel(kParamStartingTimeLabel);
3097         param->setHint(kParamStartingTimeHint);
3098         param->setDefault(0);
3099         param->setAnimates(false);
3100         param->setLayoutHint(eLayoutHintNoNewLine, 1);
3101         if (page) {
3102             page->addChild(*param);
3103         }
3104     }
3105 
3106     ///////////Time offset
3107     {
3108         IntParamDescriptor* param = desc.defineIntParam(kParamTimeOffset);
3109         param->setLabel(kParamTimeOffsetLabel);
3110         param->setHint(kParamTimeOffsetHint);
3111         param->setDefault(0);
3112         param->setAnimates(false);
3113         //param->setIsSecretAndDisabled(true); // done in the plugin constructor
3114         if (page) {
3115             page->addChild(*param);
3116         }
3117     }
3118 
3119     /////////// Secret param set to true if the time domain was edited by the user
3120     {
3121         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamTimeDomainUserEdited);
3122         param->setLabel(kParamTimeDomainUserEdited);
3123         param->setIsSecretAndDisabled(true); // always secret
3124         param->setDefault(false);
3125         param->setAnimates(false);
3126         if (page) {
3127             page->addChild(*param);
3128         }
3129     }
3130 
3131     ///////////Original frame range
3132     {
3133         Int2DParamDescriptor* param = desc.defineInt2DParam(kParamOriginalFrameRange);
3134         param->setLabel(kParamOriginalFrameRangeLabel);
3135         param->setDefault(INT_MIN, INT_MAX);
3136         param->setAnimates(true);
3137         param->setIsSecretAndDisabled(true); // always secret
3138         param->setIsPersistent(false);
3139         if (page) {
3140             page->addChild(*param);
3141         }
3142     }
3143 
3144     //////////Input proxy file
3145     {
3146         StringParamDescriptor* param = desc.defineStringParam(kParamProxy);
3147         param->setLabel(kParamProxyLabel);
3148         param->setStringType(eStringTypeFilePath);
3149         param->setFilePathExists(true);
3150         param->setHint(kParamProxyHint);
3151         // in the Reader context, the script name must be kOfxImageEffectFileParamName, @see kOfxImageEffectContextReader
3152         param->setScriptName(kParamProxy);
3153         param->setAnimates(false);
3154         desc.addClipPreferencesSlaveParam(*param);
3155         if (page) {
3156             page->addChild(*param);
3157         }
3158     }
3159 
3160     ////Proxy original scale
3161     {
3162         Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamOriginalProxyScale);
3163         param->setLabel(kParamOriginalProxyScaleLabel);
3164         param->setDefault(1., 1.);
3165         param->setRange(0., 0., 1., 1.);
3166         param->setDisplayRange(0., 0., 1., 1.);
3167         param->setIsSecretAndDisabled(true); // always secret
3168         param->setHint(kParamOriginalProxyScaleHint);
3169         // param->setLayoutHint(eLayoutHintNoNewLine, 1);
3170         param->setDoubleType(eDoubleTypeScale);
3171         param->setAnimates(true);
3172         if (page) {
3173             page->addChild(*param);
3174         }
3175     }
3176 
3177     ////Proxy  scale threshold
3178     {
3179         Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamProxyThreshold);
3180         param->setLabel(kParamProxyThresholdLabel);
3181         param->setDefault(1., 1.);
3182         param->setRange(0.01, 0.01, 1., 1.);
3183         param->setDisplayRange(0.01, 0.01, 1., 1.);
3184         //param->setIsSecretAndDisabled(true); // done in the plugin constructor
3185         param->setHint(kParamProxyThresholdHint);
3186         param->setLayoutHint(eLayoutHintNoNewLine, 1);
3187         param->setDoubleType(eDoubleTypeScale);
3188         param->setAnimates(true);
3189         if (page) {
3190             page->addChild(*param);
3191         }
3192     }
3193 
3194     ///Enable custom proxy scale
3195     {
3196         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamCustomProxyScale);
3197         param->setLabel(kParamCustomProxyScaleLabel);
3198         //param->setIsSecretAndDisabled(true); // done in the plugin constructor
3199         param->setDefault(false);
3200         param->setHint(kParamCustomProxyScaleHint);
3201         param->setAnimates(false);
3202         param->setEvaluateOnChange(false);
3203         if (page) {
3204             page->addChild(*param);
3205         }
3206     }
3207 
3208     //// File premult
3209     {
3210         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamFilePremult);
3211         param->setLabel(kParamFilePremultLabel);
3212         param->setHint(kParamFilePremultHint);
3213         assert(param->getNOptions() == eImageOpaque);
3214         param->appendOption(premultString(eImageOpaque), kParamFilePremultOptionOpaqueHint);
3215         if (gHostSupportsRGBA && supportsRGBA) {
3216             assert(param->getNOptions() == eImagePreMultiplied);
3217             param->appendOption(premultString(eImagePreMultiplied), kParamFilePremultOptionPreMultipliedHint);
3218             assert(param->getNOptions() == eImageUnPreMultiplied);
3219             param->appendOption(premultString(eImageUnPreMultiplied), kParamFilePremultOptionUnPreMultipliedHint);
3220             param->setDefault(eImagePreMultiplied); // images should be premultiplied in a compositing context
3221         }
3222         param->setAnimates(false);
3223         desc.addClipPreferencesSlaveParam(*param);
3224         if (page) {
3225             page->addChild(*param);
3226         }
3227     }
3228 
3229     //// Output premult
3230     {
3231         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamOutputPremult);
3232         param->setLabel(kParamOutputPremultLabel);
3233         param->setHint(kParamOutputPremultHint);
3234         assert(param->getNOptions() == eImageOpaque);
3235         param->appendOption(premultString(eImageOpaque), kParamFilePremultOptionOpaqueHint);
3236         if (gHostSupportsRGBA && supportsRGBA) {
3237             assert(param->getNOptions() == eImagePreMultiplied);
3238             param->appendOption(premultString(eImagePreMultiplied), kParamFilePremultOptionPreMultipliedHint);
3239             assert(param->getNOptions() == eImageUnPreMultiplied);
3240             param->appendOption(premultString(eImageUnPreMultiplied), kParamFilePremultOptionUnPreMultipliedHint);
3241             param->setDefault(eImagePreMultiplied); // images should be premultiplied in a compositing context
3242         }
3243         param->setAnimates(false);
3244         desc.addClipPreferencesSlaveParam(*param);
3245         if (page) {
3246             page->addChild(*param);
3247         }
3248     }
3249 
3250     //// Output components
3251     {
3252         ChoiceParamDescriptor *param = desc.defineChoiceParam(kParamOutputComponents);
3253         param->setLabel(kParamOutputComponentsLabel);
3254         param->setHint(kParamOutputComponentsHint);
3255 
3256         // must be in sync with the building of _outputComponentsTable in the GenericReaderPlugin constructor
3257         if (gHostSupportsRGBA && supportsRGBA) {
3258             param->appendOption(kParamOutputComponentsOptionRGBA);
3259         }
3260         if (gHostSupportsRGB && supportsRGB) {
3261             param->appendOption(kParamOutputComponentsOptionRGB);
3262         }
3263         if (gHostSupportsXY && supportsXY) {
3264             param->appendOption(kParamOutputComponentsOptionXY);
3265         }
3266 
3267         if (gHostSupportsAlpha && supportsAlpha) {
3268             param->appendOption(kParamOutputComponentsOptionAlpha);
3269         }
3270 
3271         param->setDefault(0); // default to the first one available, i.e. the most chromatic
3272         param->setAnimates(false);
3273         desc.addClipPreferencesSlaveParam(*param);
3274         if (page) {
3275             page->addChild(*param);
3276         }
3277     }
3278 
3279     ///Frame rate
3280     {
3281         DoubleParamDescriptor* param = desc.defineDoubleParam(kParamFrameRate);
3282         param->setLabel(kParamFrameRateLabel);
3283         param->setHint(kParamFrameRateHint);
3284         param->setEvaluateOnChange(false);
3285         param->setLayoutHint(eLayoutHintNoNewLine, 1);
3286         //param->setEnabled(false); // done in the restoreStateFromParams()
3287         param->setDefault(24.);
3288         param->setRange(0., DBL_MAX);
3289         param->setDisplayRange(0., 300.);
3290         param->setAnimates(false);
3291         desc.addClipPreferencesSlaveParam(*param);
3292         if (page) {
3293             page->addChild(*param);
3294         }
3295     }
3296 
3297     ///Custom FPS
3298     {
3299         BooleanParamDescriptor* param  = desc.defineBooleanParam(kParamCustomFps);
3300         param->setLabel(kParamCustomFpsLabel);
3301         param->setHint(kParamCustomFpsHint);
3302         param->setEvaluateOnChange(false);
3303         param->setAnimates(false);
3304         desc.addClipPreferencesSlaveParam(*param);
3305         if (addSeparatorAfterLastParameter) {
3306             //param->setLayoutHint(eLayoutHintDivider);
3307         }
3308         if (page) {
3309             page->addChild(*param);
3310         }
3311     }
3312 
3313     // sublabel
3314     if (gHostIsNatron) {
3315         StringParamDescriptor* param = desc.defineStringParam(kNatronOfxParamStringSublabelName);
3316         param->setIsSecretAndDisabled(true); // always secret
3317         param->setIsPersistent(false);
3318         param->setEvaluateOnChange(false);
3319         //param->setDefault();
3320         if (page) {
3321             page->addChild(*param);
3322         }
3323     }
3324 
3325     {
3326         BooleanParamDescriptor* param  = desc.defineBooleanParam(kParamGuessedParams);
3327         param->setEvaluateOnChange(false);
3328         param->setAnimates(false);
3329         param->setIsSecretAndDisabled(true);
3330         param->setDefault(false);
3331         if (page) {
3332             page->addChild(*param);
3333         }
3334     }
3335 
3336     return page;
3337 } // GenericReaderDescribeInContextBegin
3338 
3339 void
GenericReaderDescribeInContextEnd(ImageEffectDescriptor & desc,ContextEnum context,PageParamDescriptor * page,const char * inputSpaceNameDefault,const char * outputSpaceNameDefault)3340 GenericReaderDescribeInContextEnd(ImageEffectDescriptor &desc,
3341                                   ContextEnum context,
3342                                   PageParamDescriptor* page,
3343                                   const char* inputSpaceNameDefault,
3344                                   const char* outputSpaceNameDefault)
3345 {
3346 #ifdef OFX_IO_USING_OCIO
3347     // insert OCIO parameters
3348     GenericOCIO::describeInContextInput(desc, context, page, inputSpaceNameDefault, kParamInputSpaceLabel);
3349     {
3350         BooleanParamDescriptor* param  = desc.defineBooleanParam(kParamInputSpaceSet);
3351         param->setEvaluateOnChange(false);
3352         param->setAnimates(false);
3353         param->setIsSecretAndDisabled(true);
3354         param->setDefault(false);
3355         if (page) {
3356             page->addChild(*param);
3357         }
3358     }
3359     GenericOCIO::describeInContextOutput(desc, context, page, outputSpaceNameDefault);
3360     GenericOCIO::describeInContextContext(desc, context, page);
3361     {
3362         PushButtonParamDescriptor* param = desc.definePushButtonParam(kOCIOHelpButton);
3363         param->setLabel(kOCIOHelpButtonLabel);
3364         param->setHint(kOCIOHelpButtonHint);
3365         if (page) {
3366             page->addChild(*param);
3367         }
3368     }
3369 #endif
3370 }
3371 
3372 NAMESPACE_OFX_IO_EXIT
3373 NAMESPACE_OFX_EXIT
3374 
3375