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