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 GenericWriter plugin.
21  * A base class for all OpenFX-based encoders.
22  */
23 
24 #include "GenericWriter.h"
25 
26 #include <cfloat> // DBL_MAX
27 #include <cstring> // memset
28 #include <locale>
29 #include <sstream>
30 #include <algorithm>
31 
32 #include "ofxsLog.h"
33 #include "ofxsCopier.h"
34 #include "ofxsCoords.h"
35 #include "ofxsFileOpen.h"
36 
37 #include "ofxsMultiPlane.h"
38 
39 #ifdef OFX_EXTENSIONS_TUTTLE
40 #include <tuttle/ofxReadWrite.h>
41 #endif
42 #include "ofxsFormatResolution.h"
43 
44 #include "SequenceParsing/SequenceParsing.h"
45 #ifdef OFX_IO_USING_OCIO
46 #include "GenericOCIO.h"
47 #endif
48 
49 #ifdef OFX_IO_USING_OCIO
50 namespace OCIO = OCIO_NAMESPACE;
51 #endif
52 
53 using std::string;
54 using std::size_t;
55 using std::stringstream;
56 using std::vector;
57 using std::map;
58 
59 NAMESPACE_OFX_ENTER
60 NAMESPACE_OFX_IO_ENTER
61 
62 #define kPluginGrouping "Image/Writers"
63 
64 #define kSupportsTiles 0
65 #define kSupportsMultiResolution 1
66 #define kSupportsRenderScale 0 // Writers do not support render scale: all images must be rendered/written at full resolution
67 
68 // in the Writer context, the script name must be kOfxImageEffectFileParamName, @see kOfxImageEffectContextWriter
69 #define kParamFilename kOfxImageEffectFileParamName
70 #define kParamFilenameLabel "File"
71 #define kParamFilenameHint \
72     "The output image sequence/video stream file(s). " \
73     "The string must match the following format: " \
74     "path/sequenceName###.ext where the number of " \
75     "# (hashes) will define the number of digits to append to each " \
76     "file. For example path/mySequence###.jpg will be translated to " \
77     "path/mySequence000.jpg, path/mySequence001.jpg, etc. " \
78     "%d printf-like notation can also be used instead of the hashes, for example path/sequenceName%03d.ext will achieve the same than the example aforementionned. " \
79     "there will be at least 2 digits). The file name may not contain any # (hash) in which case it  will be overriden everytimes. " \
80     "Views can be specified using the \"long\" view notation %V or the \"short\" notation using %v."
81 
82 #define kParamOverwrite "overwrite"
83 #define kParamOverwriteLabelAndHint "Overwrite", "Overwrite existing files when rendering."
84 
85 #define kParamOutputFormat kNatronParamFormatChoice
86 #define kParamOutputFormatLabel "Format"
87 #define kParamOutputFormatHint \
88     "The output format to render"
89 
90 #define kParamFormatType "formatType"
91 #define kParamFormatTypeLabel "Format Type"
92 #define kParamFormatTypeHint \
93     "Determines which rectangle of pixels will be written in output."
94 #define kParamFormatTypeOptionInput "Input Format", "Renders the pixels included in the input format", "input"
95 #define kParamFormatTypeOptionProject "Project Format", "Renders the pixels included in the project format", "project"
96 #define kParamFormatTypeOptionFixed "Fixed Format", "Renders the pixels included in the format indicated by the " kParamOutputFormatLabel " parameter.", "fixed"
97 enum FormatTypeEnum
98 {
99     eFormatTypeInput = 0,
100     eFormatTypeProject,
101     eFormatTypeFixed,
102 };
103 
104 #define kParamFormatSize kNatronParamFormatSize
105 
106 #define kParamFormatPar kNatronParamFormatPar
107 
108 #define kParamFrameRange "frameRange"
109 #define kParamFrameRangeLabel "Frame Range"
110 #define kParamFrameRangeHint \
111     "What frame range should be rendered."
112 #define kParamFrameRangeOptionUnion "Union of input ranges", "The union of all inputs frame ranges will be rendered.", "union"
113 #define kParamFrameRangeOptionBounds "Project frame range", "The frame range delimited by the frame range of the project will be rendered.", "project"
114 #define kParamFrameRangeOptionManual "Manual", "The frame range will be the one defined by the first frame and last frame parameters.", "manual"
115 
116 #define kParamFirstFrame "firstFrame"
117 #define kParamFirstFrameLabel "First Frame"
118 
119 #define kParamLastFrame "lastFrame"
120 #define kParamLastFrameLabel "Last Frame"
121 
122 #define kParamInputPremult "inputPremult"
123 #define kParamInputPremultLabel "Input Premult"
124 #define kParamInputPremultHint \
125     "Input is considered to have this premultiplication state.\n" \
126     "If it is Premultiplied, red, green and blue channels are divided by the alpha channel " \
127     "before applying the colorspace conversion.\n" \
128     "This is set automatically from the input stream information, but can be adjusted if this information is wrong."
129 #define kParamInputPremultOptionOpaqueHint "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"
130 #define kParamInputPremultOptionPreMultipliedHint "The image is premultiplied by its alpha (also called \"associated alpha\").", "premult"
131 #define kParamInputPremultOptionUnPreMultipliedHint "The image is unpremultiplied (also called \"unassociated alpha\").", "unpremult"
132 
133 #define kParamClipInfo "clipInfo"
134 #define kParamClipInfoLabel "Clip Info..."
135 #define kParamClipInfoHint "Display information about the inputs"
136 
137 #define kParamOutputSpaceLabel "File Colorspace"
138 
139 #define kParamClipToRoD "clipToRoD"
140 #define kParamClipToRoDLabel "Clip To RoD"
141 #define kParamClipToRoDHint "When checked, the portion of the image written will be the region of definition of the image in input and not the format " \
142     "selected by the Output Format parameter.\n" \
143     "For the EXR file format, this will distinguish the data window (size of the image in input) from the display window (the format specified by Output Format)."
144 
145 #define kParamProcessHint  "When checked, this channel of the layer will be written to the file otherwise it will be skipped. Most file formats will " \
146     "pack the channels into the first N channels of the file. If for some reason it's not possible, the channel will be filled with 0."
147 
148 #define kParamOutputComponents "outputComponents"
149 #define kParamOutputComponentsLabel "Output Components"
150 #define kParamOutputComponentsHint "Map the input layer to this type of components before writing it to the output file."
151 
152 #define kParamGuessedParams "ParamExistingInstance" // was guessParamsFromFilename already successfully called once on this instance
153 
154 #ifdef OFX_IO_USING_OCIO
155 #define kParamOutputSpaceSet "ocioOutputSpaceSet" // was the output colorspace set by user?
156 #endif
157 
158 static bool gHostIsNatron   = false;
159 static bool gHostIsNatronVersion3OrGreater = false;
160 static bool gHostIsMultiPlanar = false;
161 static bool gHostIsMultiView = false;
162 
163 template<typename T>
164 static inline void
unused(const T &)165 unused(const T&) {}
166 
167 
GenericWriterPlugin(OfxImageEffectHandle handle,const vector<string> & extensions,bool supportsRGBA,bool supportsRGB,bool supportsXY,bool supportsAlpha)168 GenericWriterPlugin::GenericWriterPlugin(OfxImageEffectHandle handle,
169                                          const vector<string>& extensions,
170                                          bool supportsRGBA,
171                                          bool supportsRGB,
172                                          bool supportsXY,
173                                          bool supportsAlpha)
174     : MultiPlane::MultiPlaneEffect(handle)
175     , _inputClip(NULL)
176     , _outputClip(NULL)
177     , _fileParam(NULL)
178     , _overwrite(NULL)
179     , _frameRange(NULL)
180     , _firstFrame(NULL)
181     , _lastFrame(NULL)
182     , _outputFormatType(NULL)
183     , _outputFormat(NULL)
184     , _outputFormatSize(NULL)
185     , _outputFormatPar(NULL)
186     , _premult(NULL)
187     , _clipToRoD(NULL)
188     , _sublabel(NULL)
189     , _processChannels()
190     , _outputComponents(NULL)
191     , _guessedParams(NULL)
192 #ifdef OFX_IO_USING_OCIO
193     , _outputSpaceSet(NULL)
194     , _ocio( new GenericOCIO(this) )
195 #endif
196     , _extensions(extensions)
197     , _supportsRGBA(supportsRGBA)
198     , _supportsRGB(supportsRGB)
199     , _supportsXY(supportsXY)
200     , _supportsAlpha(supportsAlpha)
201     , _outputComponentsTable()
202 {
203     _inputClip = fetchClip(kOfxImageEffectSimpleSourceClipName);
204     _outputClip = fetchClip(kOfxImageEffectOutputClipName);
205 
206     _fileParam = fetchStringParam(kParamFilename);
207     _overwrite = fetchBooleanParam(kParamOverwrite);
208     _frameRange = fetchChoiceParam(kParamFrameRange);
209     _firstFrame = fetchIntParam(kParamFirstFrame);
210     _lastFrame = fetchIntParam(kParamLastFrame);
211 
212     _outputFormatType = fetchChoiceParam(kParamFormatType);
213     _outputFormat = fetchChoiceParam(kParamOutputFormat);
214     _outputFormatSize = fetchInt2DParam(kParamFormatSize);
215     _outputFormatPar = fetchDoubleParam(kParamFormatPar);
216 
217     _premult = fetchChoiceParam(kParamInputPremult);
218 
219 
220     ///Param does not necessarily exist for all IO plugins
221     if ( paramExists(kParamClipToRoD) ) {
222         _clipToRoD = fetchBooleanParam(kParamClipToRoD);
223     }
224 
225     if (gHostIsNatron) {
226         _sublabel = fetchStringParam(kNatronOfxParamStringSublabelName);
227         assert(_sublabel);
228     }
229 
230     _processChannels[0] = fetchBooleanParam(kNatronOfxParamProcessR);
231     _processChannels[1] = fetchBooleanParam(kNatronOfxParamProcessG);
232     _processChannels[2] = fetchBooleanParam(kNatronOfxParamProcessB);
233     _processChannels[3] = fetchBooleanParam(kNatronOfxParamProcessA);
234     _outputComponents = fetchChoiceParam(kParamOutputComponents);
235     assert(_processChannels[0] && _processChannels[1] && _processChannels[2] && _processChannels[3] && _outputComponents);
236 
237     _guessedParams = fetchBooleanParam(kParamGuessedParams);
238 
239 #ifdef OFX_IO_USING_OCIO
240     _outputSpaceSet = fetchBooleanParam(kParamOutputSpaceSet);
241 #endif
242 
243     int frameRangeChoice;
244     _frameRange->getValue(frameRangeChoice);
245     double first, last;
246     timeLineGetBounds(first, last);
247     if (frameRangeChoice == 2) {
248         _firstFrame->setIsSecretAndDisabled(false);
249         _lastFrame->setIsSecretAndDisabled(false);
250     } else {
251         _firstFrame->setIsSecretAndDisabled(true);
252         _lastFrame->setIsSecretAndDisabled(true);
253     }
254     FormatTypeEnum outputFormat = (FormatTypeEnum)_outputFormatType->getValue();
255     if (_clipToRoD) {
256         string filename;
257         _fileParam->getValue(filename);
258         _clipToRoD->setIsSecretAndDisabled( outputFormat != eFormatTypeProject || !displayWindowSupportedByFormat(filename) );
259     }
260     if ( (outputFormat == eFormatTypeInput) || (outputFormat == eFormatTypeProject) ) {
261         _outputFormat->setIsSecretAndDisabled(true);
262     } else {
263         _outputFormat->setIsSecretAndDisabled(false);
264     }
265 
266 
267     // must be in sync with GenericWriterDescribeInContextBegin
268     if (supportsAlpha) {
269         _outputComponentsTable.push_back(ePixelComponentAlpha);
270     }
271     if (supportsRGB) {
272         _outputComponentsTable.push_back(ePixelComponentRGB);
273     }
274     if (supportsRGBA) {
275         _outputComponentsTable.push_back(ePixelComponentRGBA);
276     }
277 }
278 
~GenericWriterPlugin()279 GenericWriterPlugin::~GenericWriterPlugin()
280 {
281 }
282 
283 /**
284  * @brief Restore any state from the parameters set
285  * Called from createInstance() and changedParam() (via outputFileChanged()), must restore the
286  * state of the Reader, such as Choice param options, data members and non-persistent param values.
287  * We don't do this in the ctor of the plug-in since we can't call virtuals yet.
288  * Any derived implementation must call GenericWriterPlugin::restoreStateFromParams() first
289  **/
290 void
restoreStateFromParams()291 GenericWriterPlugin::restoreStateFromParams()
292 {
293     // Natron explicitly set the value of filename before instanciating a Writer.
294     // We need to know if all parameters were setup already and we are just loading a project
295     // or if we are creating a new Writer from scratch and need toa djust parameters.
296     bool writerExisted = _guessedParams->getValue();
297 
298     outputFileChanged(eChangePluginEdit, writerExisted, false);
299     if (!writerExisted) {
300         _guessedParams->setValue(true);
301     }
302 }
303 
304 bool
checkExtension(const string & ext)305 GenericWriterPlugin::checkExtension(const string& ext)
306 {
307     if ( ext.empty() ) {
308         // no extension
309         return false;
310     }
311 
312     return std::find(_extensions.begin(), _extensions.end(), ext) != _extensions.end();
313 }
314 
315 bool
isIdentity(const IsIdentityArguments & args,Clip * &,double &,int &,std::string &)316 GenericWriterPlugin::isIdentity(const IsIdentityArguments &args,
317                                 Clip * & /*identityClip*/,
318                                 double & /*identityTime*/
319                                 , int& /*view*/, std::string& /*plane*/)
320 {
321     if ( !kSupportsRenderScale && ( (args.renderScale.x != 1.) || (args.renderScale.y != 1.) ) ) {
322         throwSuiteStatusException(kOfxStatFailed);
323     }
324 
325     return false;
326 }
327 
InputImagesHolder()328 GenericWriterPlugin::InputImagesHolder::InputImagesHolder()
329 {
330 }
331 
332 void
addImage(const Image * img)333 GenericWriterPlugin::InputImagesHolder::addImage(const Image* img)
334 {
335     _imgs.push_back(img);
336 }
337 
338 void
addMemory(ImageMemory * mem)339 GenericWriterPlugin::InputImagesHolder::addMemory(ImageMemory* mem)
340 {
341     _mems.push_back(mem);
342 }
343 
~InputImagesHolder()344 GenericWriterPlugin::InputImagesHolder::~InputImagesHolder()
345 {
346     for (std::list<const Image*>::iterator it = _imgs.begin(); it != _imgs.end(); ++it) {
347         delete *it;
348     }
349     for (std::list<ImageMemory*>::iterator it = _mems.begin(); it != _mems.end(); ++it) {
350         delete *it;
351     }
352 }
353 
354 static int
getPixelsComponentsCount(const string & rawComponents,PixelComponentEnum * mappedComponents)355 getPixelsComponentsCount(const string& rawComponents,
356                          PixelComponentEnum* mappedComponents)
357 {
358     string layer, pairedLayer;
359 
360     MultiPlane::ImagePlaneDesc plane, pairedPlane;
361     MultiPlane::ImagePlaneDesc::mapOFXComponentsTypeStringToPlanes(rawComponents, &plane, &pairedPlane);
362     switch ( plane.getNumComponents() ) {
363     case 0:
364         *mappedComponents = ePixelComponentNone;
365         break;
366     case 1:
367         *mappedComponents = ePixelComponentAlpha;
368         break;
369     case 2:
370         *mappedComponents = ePixelComponentXY;
371         break;
372     case 3:
373         *mappedComponents = ePixelComponentRGB;
374         break;
375     case 4:
376         *mappedComponents = ePixelComponentRGBA;
377         break;
378     default:
379         assert(false);
380         break;
381     }
382 
383     return (int)plane.getNumComponents();
384 }
385 
386 void
fetchPlaneConvertAndCopy(const string & plane,bool failIfNoSrcImg,int view,int renderRequestedView,double time,const OfxRectI & renderWindow,const OfxPointD & renderScale,FieldEnum fieldToRender,PreMultiplicationEnum pluginExpectedPremult,PreMultiplicationEnum userPremult,const bool isOCIOIdentity,const bool doAnyPacking,const bool packingContiguous,const vector<int> & packingMapping,InputImagesHolder * srcImgsHolder,OfxRectI * bounds,ImageMemory ** tmpMem,const Image ** inputImage,float ** tmpMemPtr,int * rowBytes,PixelComponentEnum * mappedComponents,int * mappedComponentsCount)387 GenericWriterPlugin::fetchPlaneConvertAndCopy(const string& plane,
388                                               bool failIfNoSrcImg,
389                                               int view,
390                                               int renderRequestedView,
391                                               double time,
392                                               const OfxRectI& renderWindow,
393                                               const OfxPointD& renderScale,
394                                               FieldEnum fieldToRender,
395                                               PreMultiplicationEnum pluginExpectedPremult,
396                                               PreMultiplicationEnum userPremult,
397                                               const bool isOCIOIdentity,
398                                               const bool doAnyPacking,
399                                               const bool packingContiguous,
400                                               const vector<int>& packingMapping,
401                                               InputImagesHolder* srcImgsHolder, // must be deleted by caller
402                                               OfxRectI* bounds,
403                                               ImageMemory** tmpMem, // owned by srcImgsHolder
404                                               const Image** inputImage, // owned by srcImgsHolder
405                                               float** tmpMemPtr, // owned by srcImgsHolder
406                                               int* rowBytes,
407                                               PixelComponentEnum* mappedComponents,
408                                               int* mappedComponentsCount)
409 {
410     *inputImage = 0;
411     *tmpMem = 0;
412     *tmpMemPtr = 0;
413     *mappedComponentsCount = 0;
414 
415     const void* srcPixelData = 0;
416     PixelComponentEnum pixelComponents;
417     BitDepthEnum bitDepth;
418     int srcRowBytes;
419     OfxRectD inputBounds;
420     double inputPar = _inputClip->getPixelAspectRatio();
421     Coords::toCanonical(renderWindow, renderScale, inputPar, &inputBounds);
422     const Image* srcImg = _inputClip->fetchImagePlane(time, view, plane.c_str(), inputBounds);
423     *inputImage = srcImg;
424     if (!srcImg) {
425         if (failIfNoSrcImg) {
426             stringstream ss;
427             ss << "Input layer ";
428             MultiPlane::ImagePlaneDesc planeToFetch;
429             if (plane == kFnOfxImagePlaneColour) {
430                 planeToFetch = MultiPlane::ImagePlaneDesc::mapNCompsToColorPlane(_inputClip->getPixelComponentCount());
431             } else {
432                 planeToFetch = MultiPlane::ImagePlaneDesc::mapOFXPlaneStringToPlane(plane);
433             }
434             ss << planeToFetch.getPlaneLabel();
435             ss << " could not be fetched";
436 
437             setPersistentMessage( Message::eMessageError, "", ss.str() );
438             throwSuiteStatusException(kOfxStatFailed);
439         }
440 
441         return;
442     } else {
443         ///Add it to the holder so we are sure it gets released if an exception occurs below
444         srcImgsHolder->addImage(srcImg);
445     }
446 
447     if ( (srcImg->getRenderScale().x != renderScale.x) ||
448          ( srcImg->getRenderScale().y != renderScale.y) ||
449          ( srcImg->getField() != fieldToRender) ) {
450         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
451         throwSuiteStatusException(kOfxStatFailed);
452 
453         return;
454     }
455 
456     getImageData(srcImg, &srcPixelData, bounds, &pixelComponents, &bitDepth, &srcRowBytes);
457 
458     if (bitDepth != eBitDepthFloat) {
459         throwSuiteStatusException(kOfxStatErrFormat);
460 
461         return;
462     }
463 
464 
465     // premultiplication/unpremultiplication is only useful for RGBA data
466     bool noPremult = (pixelComponents != ePixelComponentRGBA) || (userPremult == eImageOpaque);
467     PixelComponentEnum srcMappedComponents;
468     const int srcMappedComponentsCount = getPixelsComponentsCount(srcImg->getPixelComponentsProperty(), &srcMappedComponents);
469 
470     *mappedComponents = srcMappedComponents;
471     *mappedComponentsCount = srcMappedComponentsCount;
472     assert(srcMappedComponentsCount != 0 && srcMappedComponents != ePixelComponentNone);
473 
474     bool renderWindowIsBounds = renderWindow.x1 == bounds->x1 &&
475                                 renderWindow.y1 == bounds->y1 &&
476                                 renderWindow.x2 == bounds->x2 &&
477                                 renderWindow.y2 == bounds->y2;
478 
479 
480     if ( renderWindowIsBounds &&
481          isOCIOIdentity &&
482          ( noPremult || ( userPremult == pluginExpectedPremult) ) ) {
483         // Render window is of the same size as the input image and we don't need to apply colorspace conversion
484         // or premultiplication operations.
485 
486         *tmpMemPtr = (float*)srcPixelData;
487         *rowBytes = srcRowBytes;
488 
489         // copy to dstImg if necessary
490         if ( (renderRequestedView == view) && _outputClip && _outputClip->isConnected() ) {
491             auto_ptr<Image> dstImg( _outputClip->fetchImagePlane( time, renderRequestedView, plane.c_str() ) );
492             if ( !dstImg.get() ) {
493                 setPersistentMessage(Message::eMessageError, "", "Output image could not be fetched");
494                 throwSuiteStatusException(kOfxStatFailed);
495 
496                 return;
497             }
498             if ( (dstImg->getRenderScale().x != renderScale.x) ||
499                  ( dstImg->getRenderScale().y != renderScale.y) ||
500                  ( dstImg->getField() != fieldToRender) ) {
501                 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
502                 throwSuiteStatusException(kOfxStatFailed);
503 
504                 return;
505             }
506             if (dstImg->getPixelComponents() != pixelComponents) {
507                 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong components");
508                 throwSuiteStatusException(kOfxStatFailed);
509 
510                 return;
511             }
512 
513             // copy the source image (the writer is a no-op)
514             copyPixelData( renderWindow,
515                            srcPixelData,
516                            renderWindow,
517                            pixelComponents /* could also be srcMappedComponents */,
518                            srcMappedComponentsCount,
519                            bitDepth,
520                            srcRowBytes,
521                            dstImg.get() );
522         }
523     } else {
524         // generic case: some conversions are needed.
525 
526         // allocate
527         int pixelBytes = srcMappedComponentsCount * getComponentBytes(bitDepth);
528         int tmpRowBytes = (renderWindow.x2 - renderWindow.x1) * pixelBytes;
529         *rowBytes = tmpRowBytes;
530         size_t memSize = (size_t)(renderWindow.y2 - renderWindow.y1) * (size_t)tmpRowBytes;
531         *tmpMem = new ImageMemory(memSize, this);
532         srcImgsHolder->addMemory(*tmpMem);
533         *tmpMemPtr = (float*)(*tmpMem)->lock();
534         if (!*tmpMemPtr) {
535             throwSuiteStatusException(kOfxStatErrMemory);
536 
537             return;
538         }
539 
540         float* tmpPixelData = *tmpMemPtr;
541 
542         // Set to black and transparant so that outside the portion defined by the image there's nothing.
543         if (!renderWindowIsBounds) {
544             std::memset(tmpPixelData, 0, memSize);
545         }
546 
547         // Clip the render window to the bounds of the source image.
548         OfxRectI renderWindowClipped;
549         if ( !intersect(renderWindow, *bounds, &renderWindowClipped) ) {
550             // Nothing to do, exit
551             return;
552         }
553 
554         if (isOCIOIdentity) {
555             // bypass OCIO
556 
557             if ( noPremult || (userPremult == pluginExpectedPremult) ) {
558                 if ( (userPremult == eImageOpaque) && ( (srcMappedComponents == ePixelComponentRGBA) ||
559                                                         ( srcMappedComponents == ePixelComponentAlpha) ) ) {
560                     // Opaque: force the alpha channel to 1
561                     copyPixelsOpaque(*this, renderWindowClipped,
562                                      srcPixelData, *bounds, srcMappedComponents, srcMappedComponentsCount, bitDepth, srcRowBytes,
563                                      tmpPixelData, renderWindow, srcMappedComponents, srcMappedComponentsCount, bitDepth, tmpRowBytes);
564                 } else {
565                     // copy the whole raw src image
566                     copyPixels(*this, renderWindowClipped,
567                                srcPixelData, *bounds, srcMappedComponents, srcMappedComponentsCount, bitDepth, srcRowBytes,
568                                tmpPixelData, renderWindow, srcMappedComponents, srcMappedComponentsCount, bitDepth, tmpRowBytes);
569                 }
570             } else if (userPremult == eImagePreMultiplied) {
571                 assert(pluginExpectedPremult == eImageUnPreMultiplied);
572                 unPremultPixelData(renderWindow, srcPixelData, *bounds, srcMappedComponents, srcMappedComponentsCount
573                                    , bitDepth, srcRowBytes, tmpPixelData, renderWindow, srcMappedComponents, srcMappedComponentsCount, bitDepth, tmpRowBytes);
574             } else {
575                 assert(userPremult == eImageUnPreMultiplied);
576                 assert(pluginExpectedPremult == eImagePreMultiplied);
577                 premultPixelData(renderWindow, srcPixelData, *bounds, srcMappedComponents, srcMappedComponentsCount
578                                  , bitDepth, srcRowBytes, tmpPixelData, renderWindow, srcMappedComponents, srcMappedComponentsCount, bitDepth, tmpRowBytes);
579             }
580         } else {
581             assert(!isOCIOIdentity);
582             // OCIO expects unpremultiplied input
583             if ( noPremult || (userPremult == eImageUnPreMultiplied) ) {
584                 if ( (userPremult == eImageOpaque) && ( (srcMappedComponents == ePixelComponentRGBA) ||
585                                                         ( srcMappedComponents == ePixelComponentAlpha) ) ) {
586                     // Opaque: force the alpha channel to 1
587                     copyPixelsOpaque(*this, renderWindowClipped,
588                                      srcPixelData, *bounds, srcMappedComponents, srcMappedComponentsCount, bitDepth, srcRowBytes,
589                                      tmpPixelData, renderWindow, srcMappedComponents, srcMappedComponentsCount, bitDepth, tmpRowBytes);
590                 } else {
591                     // copy the whole raw src image
592                     copyPixels(*this, renderWindowClipped,
593                                srcPixelData, *bounds, srcMappedComponents, srcMappedComponentsCount, bitDepth, srcRowBytes,
594                                tmpPixelData, renderWindow, srcMappedComponents, srcMappedComponentsCount, bitDepth, tmpRowBytes);
595                 }
596             } else {
597                 assert(userPremult == eImagePreMultiplied);
598                 unPremultPixelData(renderWindow, srcPixelData, *bounds, srcMappedComponents, srcMappedComponentsCount
599                                    , bitDepth, srcRowBytes, tmpPixelData, renderWindow, srcMappedComponents, srcMappedComponentsCount, bitDepth, tmpRowBytes);
600             }
601 #         ifdef OFX_IO_USING_OCIO
602             // do the color-space conversion
603             if ( (srcMappedComponents == ePixelComponentRGB) || (srcMappedComponents == ePixelComponentRGBA) ) {
604                 _ocio->apply(time, renderWindowClipped, tmpPixelData, renderWindow, srcMappedComponents, srcMappedComponentsCount, tmpRowBytes);
605             }
606 #         endif
607 
608             ///If needed, re-premult the image for the plugin to work correctly
609             if ( (pluginExpectedPremult == eImagePreMultiplied) && (srcMappedComponents == ePixelComponentRGBA) ) {
610                 premultPixelData(renderWindow, tmpPixelData, renderWindow, srcMappedComponents, srcMappedComponentsCount
611                                  , bitDepth, tmpRowBytes, tmpPixelData, renderWindow, srcMappedComponents, srcMappedComponentsCount, bitDepth, tmpRowBytes);
612             }
613         } // if (isOCIOIdentity) {
614 
615         // copy to dstImg if necessary
616         if ( (renderRequestedView == view) && _outputClip && _outputClip->isConnected() ) {
617             auto_ptr<Image> dstImg( _outputClip->fetchImagePlane( time, renderRequestedView, plane.c_str() ) );
618             if ( !dstImg.get() ) {
619                 setPersistentMessage(Message::eMessageError, "", "Output image could not be fetched");
620                 throwSuiteStatusException(kOfxStatFailed);
621 
622                 return;
623             }
624             if ( (dstImg->getRenderScale().x != renderScale.x) ||
625                  ( dstImg->getRenderScale().y != renderScale.y) ||
626                  ( dstImg->getField() != fieldToRender) ) {
627                 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
628                 throwSuiteStatusException(kOfxStatFailed);
629 
630                 return;
631             }
632 
633             PixelComponentEnum dstMappedComponents;
634             const int dstMappedComponentsCount = getPixelsComponentsCount(dstImg->getPixelComponentsProperty(), &dstMappedComponents);
635 
636             // copy the source image (the writer is a no-op)
637             if (srcMappedComponentsCount == dstMappedComponentsCount) {
638                 copyPixelData( renderWindow, srcPixelData, *bounds, pixelComponents, srcMappedComponentsCount, bitDepth, srcRowBytes, dstImg.get() );
639             } else {
640                 void* dstPixelData;
641                 OfxRectI dstBounds;
642                 PixelComponentEnum dstPixelComponents;
643                 BitDepthEnum dstBitDetph;
644                 int dstRowBytes;
645 
646                 getImageData(dstImg.get(), &dstPixelData, &dstBounds, &dstPixelComponents, &dstBitDetph, &dstRowBytes);
647                 // Be careful: src may have more components than dst (eg dst is RGB, src is RGBA).
648                 // In this case, only copy the first components (thus the std::min)
649 
650                 assert( ( /*dstPixelComponentStartIndex=*/ 0 + /*desiredSrcNComps=*/ std::min(srcMappedComponentsCount, dstMappedComponentsCount) ) <= /*dstPixelComponentCount=*/ dstMappedComponentsCount );
651                 interleavePixelBuffers(renderWindowClipped,
652                                        srcPixelData,
653                                        *bounds,
654                                        pixelComponents,
655                                        srcMappedComponentsCount,
656                                        0, // srcNCompsStartIndex
657                                        std::min(srcMappedComponentsCount, dstMappedComponentsCount), // desiredSrcNComps
658                                        bitDepth,
659                                        srcRowBytes,
660                                        dstBounds,
661                                        dstPixelComponents,
662                                        0, // dstPixelComponentStartIndex
663                                        dstMappedComponentsCount,
664                                        dstRowBytes,
665                                        dstPixelData);
666             }
667         }
668         *bounds = renderWindow;
669     } // if (renderWindowIsBounds && isOCIOIdentity && (noPremult || userPremult == pluginExpectedPremult))
670 
671 
672     if ( doAnyPacking && ( !packingContiguous || ( (int)packingMapping.size() != srcMappedComponentsCount ) ) ) {
673         int pixelBytes = packingMapping.size() * getComponentBytes(bitDepth);
674         int tmpRowBytes = (renderWindow.x2 - renderWindow.x1) * pixelBytes;
675         size_t memSize = (size_t)(renderWindow.y2 - renderWindow.y1) * (size_t)tmpRowBytes;
676         ImageMemory *packingBufferMem = new ImageMemory(memSize, this);
677         srcImgsHolder->addMemory(packingBufferMem);
678         float* packingBufferData = (float*)packingBufferMem->lock();
679         if (!packingBufferData) {
680             throwSuiteStatusException(kOfxStatErrMemory);
681 
682             return;
683         }
684 
685         packPixelBuffer(renderWindow, *tmpMemPtr, *bounds, bitDepth, *rowBytes, srcMappedComponents, packingMapping, tmpRowBytes, packingBufferData);
686 
687         *tmpMemPtr = packingBufferData;
688         *rowBytes = tmpRowBytes;
689         *bounds = renderWindow;
690         *mappedComponentsCount = packingMapping.size();
691         switch ( packingMapping.size() ) {
692         case 1:
693             *mappedComponents = ePixelComponentAlpha;
694             break;
695         case 2:
696             *mappedComponents = ePixelComponentXY;
697             break;
698         case 3:
699             *mappedComponents = ePixelComponentRGB;
700             break;
701         case 4:
702             *mappedComponents = ePixelComponentRGBA;
703             break;
704         default:
705             assert(false);
706             break;
707         }
708     }
709 } // GenericWriterPlugin::fetchPlaneConvertAndCopy
710 
711 struct ImageData
712 {
713     float* srcPixelData;
714     int rowBytes;
715     OfxRectI bounds;
716     PixelComponentEnum pixelComponents;
717     int pixelComponentsCount;
718 };
719 
720 void
getPackingOptions(bool * allCheckboxHidden,vector<int> * packingMapping) const721 GenericWriterPlugin::getPackingOptions(bool *allCheckboxHidden,
722                                        vector<int>* packingMapping) const
723 {
724     bool processChannels[4] = {true, true, true, true};
725     bool processCheckboxSecret[4];
726 
727     *allCheckboxHidden = true;
728     for (int i = 0; i < 4; ++i) {
729         processCheckboxSecret[i] = _processChannels[i]->getIsSecret();
730         if (!processCheckboxSecret[i]) {
731             *allCheckboxHidden = false;
732         }
733     }
734     if (!*allCheckboxHidden) {
735         for (int i = 0; i < 4; ++i) {
736             if (!processCheckboxSecret[i]) {
737                 _processChannels[i]->getValue(processChannels[i]);
738             } else {
739                 processChannels[i] = false;
740             }
741             if (processChannels[i]) {
742                 packingMapping->push_back(i);
743             }
744         }
745     }
746 }
747 
748 void
render(const RenderArguments & args)749 GenericWriterPlugin::render(const RenderArguments &args)
750 {
751     const double time = args.time;
752 
753     if ( !kSupportsRenderScale && ( (args.renderScale.x != 1.) || (args.renderScale.y != 1.) ) ) {
754         throwSuiteStatusException(kOfxStatFailed);
755 
756         return;
757     }
758 
759     if (!_inputClip) {
760         throwSuiteStatusException(kOfxStatFailed);
761 
762         return;
763     }
764 
765     if ( args.planes.empty() ) {
766         setPersistentMessage(Message::eMessageError, "", "Host did not requested any layer to render");
767         throwSuiteStatusException(kOfxStatFailed);
768     }
769 
770     string filename;
771     _fileParam->getValueAtTime(time, filename);
772     // filename = filenameFromPattern(filename, time);
773     {
774         string ext = extension(filename);
775         if ( !checkExtension(ext) ) {
776             setPersistentMessage(Message::eMessageError, "", string("Unsupported file extension: ") + ext);
777             throwSuiteStatusException(kOfxStatErrImageFormat);
778         }
779     }
780 
781     // If "Overwrite" is not checked, and the file already exists, render does nothing (and outputs a black image)
782     if ( !_overwrite->getValueAtTime(time) ) {
783         // check if output file exists
784         string filename;
785         _fileParam->getValueAtTime(time, filename);
786         // filename = filenameFromPattern(filename, time);
787         std::FILE *image = fopen_utf8(filename.c_str(), "rb");
788         if (image) {
789             fclose(image);
790             // file exists
791             if (_outputClip && _outputClip->isConnected() ) {
792                 auto_ptr<Image> dstImg( _outputClip->fetchImage(time) );
793                 // fill output with black
794                 fillBlack( *this, args.renderWindow, dstImg.get() );
795             }
796             // nothing else to do!
797 
798             return;
799         }
800     }
801 
802     double pixelAspectRatio;
803     getOutputRoD(time, args.renderView, 0, &pixelAspectRatio);
804 
805     ///This is automatically the same generally as inputClip premultiplication but can differ is the user changed it.
806     int userPremult_i;
807     _premult->getValueAtTime(time, userPremult_i);
808     PreMultiplicationEnum userPremult = (PreMultiplicationEnum)userPremult_i;
809 
810     ///This is what the plug-in expects to be passed to the encode function.
811     PreMultiplicationEnum pluginExpectedPremult = getExpectedInputPremultiplication();
812 
813 
814     ///This is the mapping of destination channels onto source channels if packing happens
815     vector<int> packingMapping;
816     bool allCheckboxHidden;
817     getPackingOptions(&allCheckboxHidden, &packingMapping);
818 
819     const bool doAnyPacking = args.planes.size() == 1 && !allCheckboxHidden;
820 
821     //Packing is required if channels are not contiguous, e.g: the user unchecked G but left R,B,A checked
822     bool packingContiguous = true;
823 
824     if (doAnyPacking) {
825         PixelComponentEnum clipComps = _inputClip->getPixelComponents();
826 
827         if ( packingMapping.empty() ) {
828             setPersistentMessage(Message::eMessageError, "", "Nothing to render: At least 1 channel checkbox must be checked");
829             throwSuiteStatusException(kOfxStatFailed);
830         }
831         if ( (clipComps == ePixelComponentAlpha) && (packingMapping.size() != 1) ) {
832             setPersistentMessage(Message::eMessageError, "", "Output Components selected is Alpha: select only one single channel checkbox");
833             throwSuiteStatusException(kOfxStatFailed);
834         }
835 
836         if ( (packingMapping.size() == 1) && !_supportsAlpha ) {
837             if (_supportsXY) {
838                 packingMapping.push_back(-1);
839             } else if (_supportsRGB) {
840                 for (int i = 0; i < 2; ++i) {
841                     packingMapping.push_back(-1);
842                 }
843             } else if (_supportsRGBA) {
844                 for (int i = 0; i < 3; ++i) {
845                     packingMapping.push_back(-1);
846                 }
847             } else {
848                 setPersistentMessage(Message::eMessageError, "", "Plug-in does not know how to render single-channel images");
849                 throwSuiteStatusException(kOfxStatFailed);
850             }
851         } else if ( (packingMapping.size() == 2) && !_supportsXY ) {
852             if (_supportsRGB) {
853                 packingMapping.push_back(-1);
854             } else if (_supportsRGBA) {
855                 for (int i = 0; i < 2; ++i) {
856                     packingMapping.push_back(-1);
857                 }
858             } else {
859                 setPersistentMessage(Message::eMessageError, "", "Plug-in does not know how to render 2-channel images");
860                 throwSuiteStatusException(kOfxStatFailed);
861             }
862         } else if ( (packingMapping.size() == 3) && !_supportsRGB ) {
863             if (_supportsRGBA) {
864                 packingMapping.push_back(-1);
865             } else {
866                 setPersistentMessage(Message::eMessageError, "", "Plug-in does not know how to render 3-channel images");
867                 throwSuiteStatusException(kOfxStatFailed);
868             }
869         } else if ( (packingMapping.size() == 4) && !_supportsRGBA ) {
870             setPersistentMessage(Message::eMessageError, "", "Plug-in does not know how to render 4-channel images");
871             throwSuiteStatusException(kOfxStatFailed);
872         }
873         int prevChannel = -1;
874         for (std::size_t i = 0; i < packingMapping.size(); ++i) {
875             if (i > 0) {
876                 if (packingMapping[i] != prevChannel + 1) {
877                     packingContiguous = false;
878                     break;
879                 }
880             }
881             prevChannel = packingMapping[i];
882         }
883     }
884 
885 
886     // The following (commented out) code is not fully-safe, because the same instance may be have
887     // two threads running on the same area of the same frame, and the apply()
888     // calls both read and write dstImg.
889     // This results in colorspace conversion being applied several times.
890     //
891     //if (dstImg.get()) {
892     //// do the color-space conversion on dstImg
893     //getImageData(dstImg.get(), &pixelData, &bounds, &pixelComponents, &rowBytes);
894     //_ocio->apply(time, args.renderWindow, pixelData, bounds, pixelComponents, rowBytes);
895     //encode(filename, time, pixelData, bounds, pixelComponents, rowBytes);
896     //}
897     //
898     // The only viable solution (below) is to do the conversion in a temporary space,
899     // and finally copy the result.
900     //
901 
902 
903 #ifdef OFX_IO_USING_OCIO
904     bool isOCIOIdentity = _ocio->isIdentity(time);
905 #else
906     bool isOCIOIdentity = true;
907 #endif
908 
909     //The host required that we render all views into 1 file. This is for now only supported by EXR.
910 
911     bool doDefaultView = false;
912     map<int, string> viewNames;
913     int viewToRender = getViewToRender();
914 
915     if (viewToRender == kGenericWriterViewAll) {
916         if (args.renderView != 0) {
917             return; // nothing to do, except for the main view
918         }
919         int nViews = getViewCount();
920         for (int v = 0; v < nViews; ++v) {
921             string view = getViewName(v);
922             viewNames[v] = view;
923         }
924     } else {
925         int viewToRender = getViewToRender();
926         if ( (viewToRender >= 0) && (viewToRender != args.renderView) ) {
927             setPersistentMessage(Message::eMessageError, "", "Inconsistent view to render requested");
928             throwSuiteStatusException(kOfxStatFailed);
929 
930             return;
931         }
932 
933         if (viewToRender == kGenericWriterViewAll) {
934             /*
935                We might be in this situation if the user requested %V or %v in the filename, so the host didn't request -1 as render view.
936                We might also be here if the host never requests -1 as render view
937                Just fallback to the default view
938              */
939             doDefaultView = true;
940         } else if (viewToRender == kGenericWriterViewDefault) {
941             doDefaultView = true;
942         } else {
943             string view;
944             view = getViewName(viewToRender);
945             viewNames[viewToRender] = view;
946         }
947     }
948 
949     if (viewNames.empty() || doDefaultView) {
950         string view;
951         if (gHostIsMultiView) {
952             view = getViewName(args.renderView);
953         } else {
954             view = "Main";
955         }
956         viewNames[args.renderView] = view;
957     }
958     assert( !viewNames.empty() );
959 
960     //This controls how we split into parts
961     LayerViewsPartsEnum partsSplit = getPartsSplittingPreference();
962 
963     if ( (viewNames.size() == 1) && (args.planes.size() == 1) ) {
964         //Regular case, just do a simple part
965         int viewIndex = viewNames.begin()->first;
966         InputImagesHolder dataHolder; // owns srcImg and tmpMem
967         const Image* srcImg; // owned by dataHolder, no need to delete
968         ImageMemory *tmpMem; // owned by dataHolder, no need to delete
969         ImageData data;
970         // NOTE: failIfNoSrcImg=true causes the writer to fail if the src RoD is empty, see https://github.com/MrKepzie/Natron/issues/1617
971         fetchPlaneConvertAndCopy(args.planes.front(), /*failIfNoSrcImg=*/ false, viewIndex, args.renderView, time, args.renderWindow, args.renderScale, args.fieldToRender, pluginExpectedPremult, userPremult, isOCIOIdentity, doAnyPacking, packingContiguous, packingMapping, &dataHolder, &data.bounds, &tmpMem, &srcImg, &data.srcPixelData, &data.rowBytes, &data.pixelComponents, &data.pixelComponentsCount);
972 
973         int dstNComps = doAnyPacking ? packingMapping.size() : data.pixelComponentsCount;
974         int dstNCompsStartIndex = doAnyPacking ? packingMapping[0] : 0;
975 
976         encode(filename, time, viewNames[0], data.srcPixelData, args.renderWindow, pixelAspectRatio, data.pixelComponentsCount, dstNCompsStartIndex, dstNComps, data.rowBytes);
977     } else {
978         /*
979            Use the beginEncodeParts/encodePart/endEncodeParts API when there are multiple views/planes to render
980            Note that the number of times that we call encodePart depends on the LayerViewsPartsEnum value
981          */
982         assert(gHostIsMultiPlanar);
983         EncodePlanesLocalData_RAII encodeData(this);
984 
985         if ( (partsSplit == eLayerViewsSplitViews) &&
986              ( args.planes.size() == 1) ) {
987             /*
988                Splitting views but only a single layer per view is equivalent to split views/layers: code path is shorter in later case
989              */
990             partsSplit = eLayerViewsSplitViewsLayers;
991         }
992 
993         switch (partsSplit) {
994         case eLayerViewsSinglePart: {
995             /*
996                We have to aggregate all views/layers into a single buffer and write it all at once.
997              */
998             int nChannels = 0;
999             InputImagesHolder dataHolder;     // owns all tmpMem and srcImg
1000             std::list<ImageData> planesData;
1001 
1002             // The list of actual planes that could be fetched
1003             std::list<string> actualPlanes;
1004 
1005             for (map<int, string>::const_iterator view = viewNames.begin(); view != viewNames.end(); ++view) {
1006                 // The first view determines the planes that could be fetched. Other views just attempt to fetch the exact same planes.
1007                 const std::list<string> *planesToFetch = 0;
1008                 if ( view == viewNames.begin() ) {
1009                     planesToFetch = &args.planes;
1010                 } else {
1011                     planesToFetch = &actualPlanes;
1012                 }
1013 
1014                 for (std::list<string>::const_iterator plane = planesToFetch->begin(); plane != planesToFetch->end(); ++plane) {
1015                     ImageMemory *tmpMem;     // owned by dataHolder, no need to delete
1016                     const Image* srcImg;     // owned by dataHolder, no need to delete
1017                     ImageData data;
1018                     fetchPlaneConvertAndCopy(*plane, /*failIfNoSrcImg=*/ false, view->first, args.renderView, time, args.renderWindow, args.renderScale, args.fieldToRender, pluginExpectedPremult, userPremult, isOCIOIdentity, doAnyPacking, packingContiguous, packingMapping, &dataHolder, &data.bounds, &tmpMem, &srcImg, &data.srcPixelData, &data.rowBytes, &data.pixelComponents, &data.pixelComponentsCount);
1019                     if (!data.srcPixelData) {
1020                         continue;
1021                     }
1022 
1023                     if ( view == viewNames.begin() ) {
1024                         actualPlanes.push_back(*plane);
1025                     }
1026 
1027                     assert(data.pixelComponentsCount != 0 && data.pixelComponents != ePixelComponentNone);
1028 
1029                     planesData.push_back(data);
1030                     int dstNComps = doAnyPacking ? packingMapping.size() : data.pixelComponentsCount;
1031                     nChannels += dstNComps;
1032                 }    // for each plane
1033             }     // for each view
1034             if (nChannels == 0) {
1035                 setPersistentMessage(Message::eMessageError, "", "Failed to fetch input layers");
1036                 throwSuiteStatusException(kOfxStatFailed);
1037 
1038                 return;
1039             }
1040             int pixelBytes = nChannels * getComponentBytes(eBitDepthFloat);
1041             int tmpRowBytes = (args.renderWindow.x2 - args.renderWindow.x1) * pixelBytes;
1042             size_t memSize = (size_t)(args.renderWindow.y2 - args.renderWindow.y1) * (size_t)tmpRowBytes;
1043             ImageMemory interleavedMem(memSize, this);
1044             float* tmpMemPtr = (float*)interleavedMem.lock();
1045             if (!tmpMemPtr) {
1046                 throwSuiteStatusException(kOfxStatErrMemory);
1047 
1048                 return;
1049             }
1050 
1051             ///Set to 0 everywhere since the render window might be bigger than the src img bounds
1052             std::memset(tmpMemPtr, 0, memSize);
1053 
1054             int interleaveIndex = 0;
1055             for (std::list<ImageData>::iterator it = planesData.begin(); it != planesData.end(); ++it) {
1056                 assert(interleaveIndex < nChannels);
1057 
1058                 int dstNComps = doAnyPacking ? packingMapping.size() : it->pixelComponentsCount;
1059                 int dstNCompsStartIndex = doAnyPacking ? packingMapping[0] : 0;
1060                 OfxRectI intersection;
1061                 if ( Coords::rectIntersection(args.renderWindow, it->bounds, &intersection) ) {
1062                     assert( (/*dstPixelComponentStartIndex=*/ interleaveIndex + /*desiredSrcNComps=*/ dstNComps) <= /*dstPixelComponentCount=*/ nChannels );
1063                     interleavePixelBuffers(intersection,
1064                                            it->srcPixelData,
1065                                            it->bounds,
1066                                            it->pixelComponents,
1067                                            it->pixelComponentsCount,
1068                                            dstNCompsStartIndex,     // srcNCompsStartIndex
1069                                            dstNComps,     // desiredSrcNComps
1070                                            eBitDepthFloat,
1071                                            it->rowBytes,
1072                                            args.renderWindow,     // dstBounds
1073                                            ePixelComponentNone,     // dstPixelComponents
1074                                            interleaveIndex,     // dstPixelComponentStartIndex
1075                                            nChannels,     // dstPixelComponentCount
1076                                            tmpRowBytes,     // dstRowBytes
1077                                            tmpMemPtr);     // dstPixelData
1078                 }
1079                 interleaveIndex += dstNComps;
1080             }
1081 
1082             beginEncodeParts(encodeData.getData(), filename, time, pixelAspectRatio, partsSplit, viewNames, actualPlanes, doAnyPacking && !packingContiguous, packingMapping, args.renderWindow);
1083             encodePart(encodeData.getData(), filename, tmpMemPtr, nChannels, 0, tmpRowBytes);
1084 
1085             break;
1086         }
1087         case eLayerViewsSplitViews: {
1088             /*
1089                Write each view into a single part but aggregate all layers for each view
1090              */
1091             // The list of actual planes that could be fetched
1092             std::list<string> actualPlanes;
1093 
1094 
1095             int partIndex = 0;
1096             for (map<int, string>::const_iterator view = viewNames.begin(); view != viewNames.end(); ++view) {
1097                 // The first view determines the planes that could be fetched. Other views just attempt to fetch the exact same planes.
1098                 const std::list<string> *planesToFetch = 0;
1099                 if ( view == viewNames.begin() ) {
1100                     planesToFetch = &args.planes;
1101                 } else {
1102                     planesToFetch = &actualPlanes;
1103                 }
1104 
1105                 int nChannels = 0;
1106                 InputImagesHolder dataHolder;     // owns all tmpMem and srcImg
1107 
1108                 std::list<ImageData> planesData;
1109                 for (std::list<string>::const_iterator plane = planesToFetch->begin(); plane != planesToFetch->end(); ++plane) {
1110                     ImageMemory *tmpMem;     // owned by dataHolder, no need to delete
1111                     const Image* srcImg;     // owned by dataHolder, no need to delete
1112                     ImageData data;
1113                     fetchPlaneConvertAndCopy(*plane, /*failIfNoSrcImg=*/ false, view->first, args.renderView, time, args.renderWindow, args.renderScale, args.fieldToRender, pluginExpectedPremult, userPremult, isOCIOIdentity, doAnyPacking, packingContiguous, packingMapping, &dataHolder, &data.bounds, &tmpMem, &srcImg, &data.srcPixelData, &data.rowBytes, &data.pixelComponents, &data.pixelComponentsCount);
1114                     if (!data.srcPixelData) {
1115                         continue;
1116                     }
1117 
1118                     if ( view == viewNames.begin() ) {
1119                         actualPlanes.push_back(*plane);
1120                     }
1121 
1122                     assert(data.pixelComponentsCount != 0 && data.pixelComponents != ePixelComponentNone);
1123 
1124                     planesData.push_back(data);
1125 
1126                     int dstNComps = doAnyPacking ? packingMapping.size() : data.pixelComponentsCount;
1127                     nChannels += dstNComps;
1128                 }
1129                 if (nChannels == 0) {
1130                     setPersistentMessage(Message::eMessageError, "", "Failed to fetch input layers");
1131                     throwSuiteStatusException(kOfxStatFailed);
1132 
1133                     return;
1134                 }
1135                 int pixelBytes = nChannels * getComponentBytes(eBitDepthFloat);
1136                 int tmpRowBytes = (args.renderWindow.x2 - args.renderWindow.x1) * pixelBytes;
1137                 size_t memSize = (size_t)(args.renderWindow.y2 - args.renderWindow.y1) * (size_t)tmpRowBytes;
1138                 ImageMemory interleavedMem(memSize, this);
1139                 float* tmpMemPtr = (float*)interleavedMem.lock();
1140                 if (!tmpMemPtr) {
1141                     throwSuiteStatusException(kOfxStatErrMemory);
1142 
1143                     return;
1144                 }
1145 
1146                 ///Set to 0 everywhere since the render window might be bigger than the src img bounds
1147                 std::memset(tmpMemPtr, 0, memSize);
1148 
1149                 int interleaveIndex = 0;
1150                 for (std::list<ImageData>::iterator it = planesData.begin(); it != planesData.end(); ++it) {
1151                     assert(interleaveIndex < nChannels);
1152 
1153                     OfxRectI intersection;
1154                     int dstNComps = doAnyPacking ? packingMapping.size() : it->pixelComponentsCount;
1155                     int dstNCompsStartIndex = doAnyPacking ? packingMapping[0] : 0;
1156                     if ( Coords::rectIntersection(args.renderWindow, it->bounds, &intersection) ) {
1157                         assert( (/*dstPixelComponentStartIndex=*/ interleaveIndex + /*desiredSrcNComps=*/ dstNComps) <= /*dstPixelComponentCount=*/ nChannels );
1158                         interleavePixelBuffers(intersection,
1159                                                it->srcPixelData,
1160                                                it->bounds,
1161                                                it->pixelComponents,
1162                                                it->pixelComponentsCount,
1163                                                dstNCompsStartIndex,     // srcNCompsStartIndex
1164                                                dstNComps,     // desiredSrcNComps
1165                                                eBitDepthFloat,
1166                                                it->rowBytes,
1167                                                args.renderWindow,
1168                                                ePixelComponentNone,     // dstPixelComponents
1169                                                interleaveIndex,     // dstPixelComponentStartIndex
1170                                                nChannels,     // dstPixelComponentCount
1171                                                tmpRowBytes,
1172                                                tmpMemPtr);
1173                     }
1174                     interleaveIndex += dstNComps;
1175                 }
1176 
1177                 if ( view == viewNames.begin() ) {
1178                     beginEncodeParts(encodeData.getData(), filename, time, pixelAspectRatio, partsSplit, viewNames, actualPlanes, doAnyPacking && !packingContiguous, packingMapping, args.renderWindow);
1179                 }
1180 
1181                 encodePart(encodeData.getData(), filename, tmpMemPtr, nChannels, partIndex, tmpRowBytes);
1182 
1183                 ++partIndex;
1184             }     // for each view
1185 
1186             break;
1187         }
1188         case eLayerViewsSplitViewsLayers: {
1189             /*
1190                Write each layer of each view in an independent part
1191              */
1192             // The list of actual planes that could be fetched
1193             std::list<string> actualPlanes;
1194 
1195             int partIndex = 0;
1196             for (map<int, string>::const_iterator view = viewNames.begin(); view != viewNames.end(); ++view) {
1197                 InputImagesHolder dataHolder;     // owns all tmpMem and srcImg
1198                 vector<ImageData> datas;
1199 
1200                 // The first view determines the planes that could be fetched. Other views just attempt to fetch the exact same planes.
1201                 const std::list<string> *planesToFetch = 0;
1202                 if ( view == viewNames.begin() ) {
1203                     planesToFetch = &args.planes;
1204                 } else {
1205                     planesToFetch = &actualPlanes;
1206                 }
1207 
1208                 for (std::list<string>::const_iterator plane = planesToFetch->begin(); plane != planesToFetch->end(); ++plane) {
1209                     ImageMemory *tmpMem;     // owned by dataHolder, no need to delete
1210                     const Image* srcImg;     // owned by dataHolder, no need to delete
1211                     ImageData data;
1212                     fetchPlaneConvertAndCopy(*plane, /*failIfNoSrcImg=*/ false, view->first, args.renderView, time, args.renderWindow, args.renderScale, args.fieldToRender, pluginExpectedPremult, userPremult, isOCIOIdentity, doAnyPacking, packingContiguous, packingMapping, &dataHolder, &data.bounds, &tmpMem, &srcImg, &data.srcPixelData, &data.rowBytes, &data.pixelComponents, &data.pixelComponentsCount);
1213                     if (!data.srcPixelData) {
1214                         continue;
1215                     }
1216                     datas.push_back(data);
1217                     if ( view == viewNames.begin() ) {
1218                         actualPlanes.push_back(*plane);
1219                     }
1220                 }     // for each plane
1221 
1222                 if ( view == viewNames.begin() ) {
1223                     beginEncodeParts(encodeData.getData(), filename, time, pixelAspectRatio, partsSplit, viewNames, actualPlanes, doAnyPacking && !packingContiguous, packingMapping, args.renderWindow);
1224                 }
1225                 for (vector<ImageData>::iterator it = datas.begin(); it != datas.end(); ++it) {
1226                     encodePart(encodeData.getData(), filename, it->srcPixelData, it->pixelComponentsCount, partIndex, it->rowBytes);
1227                     ++partIndex;
1228                 }
1229             }     // for each view
1230 
1231             break;
1232         }
1233         } // switch
1234         ;
1235 
1236         endEncodeParts( encodeData.getData() );
1237     }
1238 
1239     clearPersistentMessage();
1240 } // GenericWriterPlugin::render
1241 
1242 class PackPixelsProcessorBase
1243     : public PixelProcessorFilterBase
1244 {
1245 protected:
1246 
1247     vector<int> _mapping;
1248 
1249 public:
PackPixelsProcessorBase(ImageEffect & instance)1250     PackPixelsProcessorBase(ImageEffect& instance)
1251         : PixelProcessorFilterBase(instance)
1252         , _mapping()
1253     {
1254     }
1255 
setMapping(const vector<int> & mapping)1256     void setMapping(const vector<int>& mapping)
1257     {
1258         _mapping = mapping;
1259     }
1260 };
1261 
1262 
1263 template <typename PIX, int maxValue, int srcNComps>
1264 class PackPixelsProcessor
1265     : public PackPixelsProcessorBase
1266 {
1267 public:
1268 
PackPixelsProcessor(ImageEffect & instance)1269     PackPixelsProcessor(ImageEffect& instance)
1270         : PackPixelsProcessorBase(instance)
1271     {
1272     }
1273 
multiThreadProcessImages(OfxRectI procWindow)1274     virtual void multiThreadProcessImages(OfxRectI procWindow) OVERRIDE FINAL
1275     {
1276         assert(_srcBounds.x1 < _srcBounds.x2 && _srcBounds.y1 < _srcBounds.y2);
1277         assert( (int)_mapping.size() == _dstPixelComponentCount );
1278 
1279         PIX *dstPix = (PIX *)getDstPixelAddress(procWindow.x1, procWindow.y1);
1280         assert(dstPix);
1281 
1282         const PIX *srcPix = (const PIX *) getSrcPixelAddress(procWindow.x1, procWindow.y1);
1283         assert(srcPix);
1284 
1285         const int srcRowElements = _srcRowBytes / sizeof(PIX);
1286         const int dstRowElements = _dstRowBytes / sizeof(PIX);
1287         const int procWidth = procWindow.x2 - procWindow.x1;
1288 
1289         for (int y = procWindow.y1; y < procWindow.y2; ++y,
1290              srcPix += (srcRowElements - procWidth * srcNComps), // Move to next row and substract what was done on last iteration
1291              dstPix += (dstRowElements - procWidth * _dstPixelComponentCount)
1292              ) {
1293             if ( (y % 100 == 0) && _effect.abort() ) {
1294                 //check for abort only every 100 lines
1295                 break;
1296             }
1297 
1298             for (int x = procWindow.x1; x < procWindow.x2; ++x,
1299                  srcPix += srcNComps,
1300                  dstPix += _dstPixelComponentCount
1301                  ) {
1302                 assert( srcPix == ( (const PIX*)getSrcPixelAddress(x, y) ) );
1303                 for (int c = 0; c < _dstPixelComponentCount; ++c) {
1304                     int srcCol = _mapping[c];
1305                     if (srcCol == -1) {
1306                         dstPix[c] = c != 3 ? 0 : maxValue;
1307                     } else {
1308                         if (srcCol < srcNComps) {
1309                             dstPix[c] = srcPix[srcCol];
1310                         } else {
1311                             if (srcNComps == 1) {
1312                                 dstPix[c] = *srcPix;
1313                             } else {
1314                                 dstPix[c] = c != 3 ? 0 : maxValue;
1315                             }
1316                         }
1317                     }
1318                 }
1319             }
1320         }
1321     }
1322 };
1323 
1324 
1325 template <typename PIX, int maxValue>
1326 void
packPixelBufferForDepth(ImageEffect * instance,const OfxRectI & renderWindow,const void * srcPixelData,const OfxRectI & bounds,BitDepthEnum bitDepth,int srcRowBytes,PixelComponentEnum srcPixelComponents,const vector<int> & channelsMapping,int dstRowBytes,void * dstPixelData)1327 packPixelBufferForDepth(ImageEffect* instance,
1328                         const OfxRectI& renderWindow,
1329                         const void *srcPixelData,
1330                         const OfxRectI& bounds,
1331                         BitDepthEnum bitDepth,
1332                         int srcRowBytes,
1333                         PixelComponentEnum srcPixelComponents,
1334                         const vector<int>& channelsMapping,
1335                         int dstRowBytes,
1336                         void* dstPixelData)
1337 {
1338     assert(channelsMapping.size() <= 4);
1339     auto_ptr<PackPixelsProcessorBase> p;
1340     int srcNComps = 0;
1341     switch (srcPixelComponents) {
1342     case ePixelComponentAlpha:
1343         p.reset( new PackPixelsProcessor<PIX, maxValue, 1>(*instance) );
1344         srcNComps = 1;
1345         break;
1346     case ePixelComponentXY:
1347         p.reset( new PackPixelsProcessor<PIX, maxValue, 2>(*instance) );
1348         srcNComps = 2;
1349         break;
1350     case ePixelComponentRGB:
1351         p.reset( new PackPixelsProcessor<PIX, maxValue, 3>(*instance) );
1352         srcNComps = 3;
1353         break;
1354     case ePixelComponentRGBA:
1355         p.reset( new PackPixelsProcessor<PIX, maxValue, 4>(*instance) );
1356         srcNComps = 4;
1357         break;
1358     default:
1359         //Unsupported components
1360         throwSuiteStatusException(kOfxStatFailed);
1361         break;
1362     }
1363     ;
1364 
1365     p->setSrcImg(srcPixelData, bounds, srcPixelComponents, srcNComps, bitDepth, srcRowBytes, 0);
1366     p->setDstImg(dstPixelData, bounds, srcPixelComponents /*this argument is meaningless*/, channelsMapping.size(), bitDepth, dstRowBytes);
1367     p->setRenderWindow(renderWindow);
1368 
1369     p->setMapping(channelsMapping);
1370 
1371     p->process();
1372 }
1373 
1374 void
packPixelBuffer(const OfxRectI & renderWindow,const void * srcPixelData,const OfxRectI & bounds,BitDepthEnum bitDepth,int srcRowBytes,PixelComponentEnum srcPixelComponents,const vector<int> & channelsMapping,int dstRowBytes,void * dstPixelData)1375 GenericWriterPlugin::packPixelBuffer(const OfxRectI& renderWindow,
1376                                      const void *srcPixelData,
1377                                      const OfxRectI& bounds,
1378                                      BitDepthEnum bitDepth,
1379                                      int srcRowBytes,
1380                                      PixelComponentEnum srcPixelComponents,
1381                                      const vector<int>& channelsMapping, //maps dst channels to input channels
1382                                      int dstRowBytes,
1383                                      void* dstPixelData)
1384 {
1385     assert(renderWindow.x1 >= bounds.x1 && renderWindow.x2 <= bounds.x2 &&
1386            renderWindow.y1 >= bounds.y1 && renderWindow.y2 <= bounds.y2);
1387     switch (bitDepth) {
1388     case eBitDepthFloat:
1389         packPixelBufferForDepth<float, 1>(this, renderWindow, (const float*)srcPixelData, bounds, bitDepth, srcRowBytes, srcPixelComponents, channelsMapping, dstRowBytes, (float*)dstPixelData);
1390         break;
1391     case eBitDepthUByte:
1392         packPixelBufferForDepth<unsigned char, 255>(this, renderWindow, (const unsigned char*)srcPixelData, bounds, bitDepth, srcRowBytes, srcPixelComponents, channelsMapping, dstRowBytes, (unsigned char*)dstPixelData);
1393         break;
1394     case eBitDepthUShort:
1395         packPixelBufferForDepth<unsigned short, 65535>(this, renderWindow, (const unsigned short*)srcPixelData, bounds, bitDepth, srcRowBytes, srcPixelComponents, channelsMapping, dstRowBytes, (unsigned short*)dstPixelData);
1396         break;
1397     default:
1398         //unknown pixel depth
1399         throwSuiteStatusException(kOfxStatFailed);
1400         break;
1401     }
1402 }
1403 
1404 class InterleaveProcessorBase
1405     : public PixelProcessorFilterBase
1406 {
1407 protected:
1408 
1409     int _dstStartIndex;
1410     int _desiredSrcNComps;
1411     int _srcNCompsStartIndex;
1412 
1413 public:
InterleaveProcessorBase(ImageEffect & instance)1414     InterleaveProcessorBase(ImageEffect& instance)
1415         : PixelProcessorFilterBase(instance)
1416         , _dstStartIndex(-1)
1417         , _desiredSrcNComps(-1)
1418         , _srcNCompsStartIndex(0)
1419     {
1420     }
1421 
setValues(int dstStartIndex,int desiredSrcNComps,int srcNCompsStartIndex)1422     void setValues(int dstStartIndex,
1423                    int desiredSrcNComps,
1424                    int srcNCompsStartIndex)
1425     {
1426         _dstStartIndex = dstStartIndex;
1427         _desiredSrcNComps = desiredSrcNComps;
1428         _srcNCompsStartIndex = srcNCompsStartIndex;
1429     }
1430 };
1431 
1432 template <typename PIX, int maxValue, int srcNComps>
1433 class InterleaveProcessor
1434     : public InterleaveProcessorBase
1435 {
1436 public:
1437 
InterleaveProcessor(ImageEffect & instance)1438     InterleaveProcessor(ImageEffect& instance)
1439         : InterleaveProcessorBase(instance)
1440     {
1441     }
1442 
multiThreadProcessImages(OfxRectI procWindow)1443     virtual void multiThreadProcessImages(OfxRectI procWindow) OVERRIDE FINAL
1444     {
1445         assert(_srcBounds.x1 < _srcBounds.x2 && _srcBounds.y1 < _srcBounds.y2);
1446         assert(_dstStartIndex >= 0);
1447         assert(_dstStartIndex + _desiredSrcNComps <= _dstPixelComponentCount); // inner loop must not overrun dstPix
1448         PIX *dstPix = (PIX *)getDstPixelAddress(procWindow.x1, procWindow.y1);
1449         assert(dstPix);
1450         dstPix += _dstStartIndex;
1451 
1452         const PIX *srcPix = (const PIX *) getSrcPixelAddress(procWindow.x1, procWindow.y1);
1453         assert(srcPix);
1454 
1455         const int srcRowElements = _srcRowBytes / sizeof(PIX);
1456         const int dstRowElements = _dstRowBytes / sizeof(PIX);
1457         const int procWidth = procWindow.x2 - procWindow.x1;
1458 
1459         for (int y = procWindow.y1; y < procWindow.y2; ++y,
1460              srcPix += (srcRowElements - procWidth * srcNComps), // Move to next row and substract what was done on last iteration
1461              dstPix += (dstRowElements - procWidth * _dstPixelComponentCount)
1462              ) {
1463             if ( (y % 10 == 0) && _effect.abort() ) {
1464                 //check for abort only every 10 lines
1465                 break;
1466             }
1467 
1468             for (int x = procWindow.x1; x < procWindow.x2; ++x,
1469                  srcPix += srcNComps,
1470                  dstPix += _dstPixelComponentCount
1471                  ) {
1472                 assert(dstPix == ( (PIX*)getDstPixelAddress(x, y) ) + _dstStartIndex);
1473                 assert( srcPix == ( (const PIX*)getSrcPixelAddress(x, y) ) );
1474 
1475                 for (int c = 0; c < _desiredSrcNComps; ++c) {
1476                     dstPix[c] = srcPix[c + _srcNCompsStartIndex];
1477                 }
1478             }
1479         }
1480     }
1481 };
1482 
1483 template <typename PIX, int maxValue>
1484 void
interleavePixelBuffersForDepth(ImageEffect * instance,const OfxRectI & renderWindow,const PIX * srcPixelData,const OfxRectI & bounds,const PixelComponentEnum srcPixelComponents,const int srcPixelComponentCount,const int srcNCompsStartIndex,const int desiredSrcNComps,const BitDepthEnum bitDepth,const int srcRowBytes,const OfxRectI & dstBounds,const PixelComponentEnum dstPixelComponents,const int dstPixelComponentStartIndex,const int dstPixelComponentCount,const int dstRowBytes,PIX * dstPixelData)1485 interleavePixelBuffersForDepth(ImageEffect* instance,
1486                                const OfxRectI& renderWindow,
1487                                const PIX *srcPixelData,
1488                                const OfxRectI& bounds,
1489                                const PixelComponentEnum srcPixelComponents,
1490                                const int srcPixelComponentCount,
1491                                const int srcNCompsStartIndex,
1492                                const int desiredSrcNComps,
1493                                const BitDepthEnum bitDepth,
1494                                const int srcRowBytes,
1495                                const OfxRectI& dstBounds,
1496                                const PixelComponentEnum dstPixelComponents,      // ignored, may be ePixelComponentNone
1497                                const int dstPixelComponentStartIndex,
1498                                const int dstPixelComponentCount,
1499                                const int dstRowBytes,
1500                                PIX* dstPixelData)
1501 {
1502     assert( (dstPixelComponentStartIndex + desiredSrcNComps) <= dstPixelComponentCount );
1503     auto_ptr<InterleaveProcessorBase> p;
1504     switch (srcPixelComponentCount) {
1505     case 1:
1506         p.reset( new InterleaveProcessor<PIX, maxValue, 1>(*instance) );
1507         break;
1508     case 2:
1509         p.reset( new InterleaveProcessor<PIX, maxValue, 2>(*instance) );
1510         break;
1511     case 3:
1512         p.reset( new InterleaveProcessor<PIX, maxValue, 3>(*instance) );
1513         break;
1514     case 4:
1515         p.reset( new InterleaveProcessor<PIX, maxValue, 4>(*instance) );
1516         break;
1517     default:
1518         //Unsupported components
1519         throwSuiteStatusException(kOfxStatFailed);
1520         break;
1521     }
1522     ;
1523     p->setSrcImg(srcPixelData, bounds, srcPixelComponents, srcPixelComponentCount, bitDepth, srcRowBytes, 0);
1524     p->setDstImg(dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, bitDepth, dstRowBytes);
1525     p->setRenderWindow(renderWindow);
1526     p->setValues(dstPixelComponentStartIndex, desiredSrcNComps, srcNCompsStartIndex);
1527 
1528     p->process();
1529 }
1530 
1531 void
interleavePixelBuffers(const OfxRectI & renderWindow,const void * srcPixelData,const OfxRectI & bounds,const PixelComponentEnum srcPixelComponents,const int srcPixelComponentCount,const int srcNCompsStartIndex,const int desiredSrcNComps,const BitDepthEnum bitDepth,const int srcRowBytes,const OfxRectI & dstBounds,const PixelComponentEnum dstPixelComponents,const int dstPixelComponentStartIndex,const int dstPixelComponentCount,const int dstRowBytes,void * dstPixelData)1532 GenericWriterPlugin::interleavePixelBuffers(const OfxRectI& renderWindow,
1533                                             const void *srcPixelData,
1534                                             const OfxRectI& bounds,
1535                                             const PixelComponentEnum srcPixelComponents,
1536                                             const int srcPixelComponentCount,
1537                                             const int srcNCompsStartIndex,
1538                                             const int desiredSrcNComps,
1539                                             const BitDepthEnum bitDepth,
1540                                             const int srcRowBytes,
1541                                             const OfxRectI& dstBounds,
1542                                             const PixelComponentEnum dstPixelComponents, // ignored, may be ePixelComponentNone
1543                                             const int dstPixelComponentStartIndex,
1544                                             const int dstPixelComponentCount,
1545                                             const int dstRowBytes,
1546                                             void* dstPixelData)
1547 {
1548     assert( (dstPixelComponentStartIndex + desiredSrcNComps) <= dstPixelComponentCount );
1549     assert(renderWindow.x1 >= bounds.x1 && renderWindow.x2 <= bounds.x2 &&
1550            renderWindow.y1 >= bounds.y1 && renderWindow.y2 <= bounds.y2);
1551     assert(renderWindow.x1 >= dstBounds.x1 && renderWindow.x2 <= dstBounds.x2 &&
1552            renderWindow.y1 >= dstBounds.y1 && renderWindow.y2 <= dstBounds.y2);
1553     switch (bitDepth) {
1554     case eBitDepthFloat:
1555         interleavePixelBuffersForDepth<float, 1>(this, renderWindow, (const float*)srcPixelData, bounds, srcPixelComponents, srcPixelComponentCount, srcNCompsStartIndex, desiredSrcNComps, bitDepth, srcRowBytes, dstBounds, dstPixelComponents, dstPixelComponentStartIndex, dstPixelComponentCount, dstRowBytes, (float*)dstPixelData);
1556         break;
1557     case eBitDepthUByte:
1558         interleavePixelBuffersForDepth<unsigned char, 255>(this, renderWindow, (const unsigned char*)srcPixelData, bounds, srcPixelComponents, srcPixelComponentCount, srcNCompsStartIndex, desiredSrcNComps, bitDepth, srcRowBytes, dstBounds, dstPixelComponents, dstPixelComponentStartIndex, dstPixelComponentCount, dstRowBytes, (unsigned char*)dstPixelData);
1559         break;
1560     case eBitDepthUShort:
1561         interleavePixelBuffersForDepth<unsigned short, 65535>(this, renderWindow, (const unsigned short*)srcPixelData, bounds, srcPixelComponents, srcPixelComponentCount, srcNCompsStartIndex, desiredSrcNComps, bitDepth, srcRowBytes, dstBounds, dstPixelComponents, dstPixelComponentStartIndex, dstPixelComponentCount, dstRowBytes, (unsigned short*)dstPixelData);
1562         break;
1563     default:
1564         //unknown pixel depth
1565         throwSuiteStatusException(kOfxStatFailed);
1566         break;
1567     }
1568 }
1569 
1570 void
beginSequenceRender(const BeginSequenceRenderArguments & args)1571 GenericWriterPlugin::beginSequenceRender(const BeginSequenceRenderArguments &args)
1572 {
1573     if ( !kSupportsRenderScale && ( (args.renderScale.x != 1.) || (args.renderScale.y != 1.) ) ) {
1574         throwSuiteStatusException(kOfxStatFailed);
1575 
1576         return;
1577     }
1578 
1579     string filename;
1580     _fileParam->getValue(filename);
1581     {
1582         string ext = extension(filename);
1583         if ( !checkExtension(ext) ) {
1584             setPersistentMessage(Message::eMessageError, "", string("Unsupported file extension: ") + ext);
1585             throwSuiteStatusException(kOfxStatErrImageFormat);
1586         }
1587     }
1588 
1589     OfxRectD rod;
1590     double par;
1591     getOutputRoD(args.frameRange.min, args.view, &rod, &par);
1592 
1593     ////Since the generic writer doesn't support tiles and multi-resolution, the RoD is necesserarily the
1594     ////output image size.
1595     OfxRectI rodPixel;
1596     Coords::toPixelEnclosing(rod, args.renderScale, par, &rodPixel);
1597 
1598     beginEncode(filename, rodPixel, par, args);
1599 }
1600 
1601 void
endSequenceRender(const EndSequenceRenderArguments & args)1602 GenericWriterPlugin::endSequenceRender(const EndSequenceRenderArguments &args)
1603 {
1604     if ( !kSupportsRenderScale && ( (args.renderScale.x != 1.) || (args.renderScale.y != 1.) ) ) {
1605         throwSuiteStatusException(kOfxStatFailed);
1606 
1607         return;
1608     }
1609 
1610     endEncode(args);
1611 }
1612 
1613 ////////////////////////////////////////////////////////////////////////////////
1614 /** @brief render for the filter */
1615 
1616 ////////////////////////////////////////////////////////////////////////////////
1617 // basic plugin render function, just a skelington to instantiate templates from
1618 
1619 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)1620 setupAndProcess(PixelProcessorFilterBase & processor,
1621                 int premultChannel,
1622                 const OfxRectI &renderWindow,
1623                 const void *srcPixelData,
1624                 const OfxRectI& srcBounds,
1625                 PixelComponentEnum srcPixelComponents,
1626                 int srcPixelComponentCount,
1627                 BitDepthEnum srcPixelDepth,
1628                 int srcRowBytes,
1629                 void *dstPixelData,
1630                 const OfxRectI& dstBounds,
1631                 PixelComponentEnum dstPixelComponents,
1632                 int dstPixelComponentCount,
1633                 BitDepthEnum dstPixelDepth,
1634                 int dstRowBytes)
1635 {
1636     assert(srcPixelData && dstPixelData);
1637 
1638     // make sure bit depths are sane
1639     if ( (srcPixelDepth != dstPixelDepth) || (srcPixelComponents != dstPixelComponents) ) {
1640         throwSuiteStatusException(kOfxStatErrFormat);
1641 
1642         return;
1643     }
1644 
1645     // set the images
1646     processor.setDstImg(dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstPixelDepth, dstRowBytes);
1647     processor.setSrcImg(srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcPixelDepth, srcRowBytes, 0);
1648 
1649     // set the render window
1650     processor.setRenderWindow(renderWindow);
1651 
1652     processor.setPremultMaskMix(true, premultChannel, 1.);
1653 
1654     // Call the base class process member, this will call the derived templated process code
1655     processor.process();
1656 }
1657 
1658 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)1659 GenericWriterPlugin::unPremultPixelData(const OfxRectI &renderWindow,
1660                                         const void *srcPixelData,
1661                                         const OfxRectI& srcBounds,
1662                                         PixelComponentEnum srcPixelComponents,
1663                                         int srcPixelComponentCount,
1664                                         BitDepthEnum srcPixelDepth,
1665                                         int srcRowBytes,
1666                                         void *dstPixelData,
1667                                         const OfxRectI& dstBounds,
1668                                         PixelComponentEnum dstPixelComponents,
1669                                         int dstPixelComponentCount,
1670                                         BitDepthEnum dstBitDepth,
1671                                         int dstRowBytes)
1672 {
1673     assert(srcPixelData && dstPixelData);
1674 
1675     // do the rendering
1676     if ( (dstBitDepth != eBitDepthFloat) || ( (dstPixelComponents != ePixelComponentRGBA) && (dstPixelComponents != ePixelComponentRGB) && (dstPixelComponents != ePixelComponentAlpha) ) ) {
1677         throwSuiteStatusException(kOfxStatErrFormat);
1678 
1679         return;
1680     }
1681     if (dstPixelComponents == ePixelComponentRGBA) {
1682         PixelCopierUnPremult<float, 4, 1, float, 4, 1> fred(*this);
1683         setupAndProcess(fred, 3, renderWindow, srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcPixelDepth, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
1684     } else {
1685         ///other pixel components means you want to copy only...
1686         assert(false);
1687     }
1688 }
1689 
1690 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)1691 GenericWriterPlugin::premultPixelData(const OfxRectI &renderWindow,
1692                                       const void *srcPixelData,
1693                                       const OfxRectI& srcBounds,
1694                                       PixelComponentEnum srcPixelComponents,
1695                                       int srcPixelComponentCount,
1696                                       BitDepthEnum srcPixelDepth,
1697                                       int srcRowBytes,
1698                                       void *dstPixelData,
1699                                       const OfxRectI& dstBounds,
1700                                       PixelComponentEnum dstPixelComponents,
1701                                       int dstPixelComponentCount,
1702                                       BitDepthEnum dstBitDepth,
1703                                       int dstRowBytes)
1704 {
1705     assert(srcPixelData && dstPixelData);
1706 
1707     // do the rendering
1708     if ( (dstBitDepth != eBitDepthFloat) || ( (dstPixelComponents != ePixelComponentRGBA) && (dstPixelComponents != ePixelComponentRGB) && (dstPixelComponents != ePixelComponentAlpha) ) ) {
1709         throwSuiteStatusException(kOfxStatErrFormat);
1710 
1711         return;
1712     }
1713 
1714     if (dstPixelComponents == ePixelComponentRGBA) {
1715         PixelCopierPremult<float, 4, 1, float, 4, 1> fred(*this);
1716         setupAndProcess(fred, 3, renderWindow, srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcPixelDepth, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
1717     } else {
1718         ///other pixel components means you want to copy only...
1719         assert(false);
1720     }
1721 }
1722 
1723 void
getSelectedOutputFormat(OfxRectI * format,double * par)1724 GenericWriterPlugin::getSelectedOutputFormat(OfxRectI* format,
1725                                              double* par)
1726 {
1727     FormatTypeEnum formatType = (FormatTypeEnum)_outputFormatType->getValue();
1728 
1729     switch (formatType) {
1730     case eFormatTypeInput: {
1731         // union RoD across all views
1732         OfxRectI inputFormat;
1733         _inputClip->getFormat(inputFormat);
1734         double inputPar = _inputClip->getPixelAspectRatio();
1735         format->x1 = inputFormat.x1;
1736         format->x2 = inputFormat.x2;
1737         format->y1 = inputFormat.y1;
1738         format->y2 = inputFormat.y2;
1739         *par = inputPar;
1740         break;
1741     }
1742     case eFormatTypeProject: {
1743         OfxPointD size = getProjectSize();
1744         OfxPointD offset = getProjectOffset();
1745         double projectPar = getProjectPixelAspectRatio();
1746         *par = projectPar;
1747         format->x1 = std::floor(offset.x * projectPar + 0.5);
1748         format->y1 = offset.y;
1749         format->x2 = std::floor((offset.x + size.x) * projectPar + 0.5);
1750         format->y2 = offset.y + size.y;
1751         break;
1752     }
1753     case eFormatTypeFixed: {
1754         int w, h;
1755         _outputFormatSize->getValue(w, h);
1756         _outputFormatPar->getValue(*par);
1757         format->x1 = format->y1 = 0;
1758         format->x2 = w;
1759         format->y2 = h;
1760         break;
1761     }
1762     }
1763 } // getSelectedOutputFormat
1764 
1765 void
getOutputRoD(OfxTime time,int view,OfxRectD * rod,double * par)1766 GenericWriterPlugin::getOutputRoD(OfxTime time,
1767                                   int view,
1768                                   OfxRectD* rod,
1769                                   double* par)
1770 {
1771     assert(rod || par);
1772 
1773     bool clipToRoD = false;
1774     if ( _clipToRoD && !_clipToRoD->getIsSecret() ) {
1775         _clipToRoD->getValue(clipToRoD);
1776     }
1777     // user wants RoD written, don't care parameters
1778     if (clipToRoD) {
1779         if (rod) {
1780             *rod = _inputClip->getRegionOfDefinition(time, view);
1781         }
1782         if (par) {
1783             *par = _inputClip->getPixelAspectRatio();
1784         }
1785     } else {
1786         OfxRectI format;
1787         double formatPar;
1788         getSelectedOutputFormat(&format, &formatPar);
1789         if (rod) {
1790             OfxPointD renderScale = {1., 1.};
1791             Coords::toCanonical(format, renderScale, formatPar, rod);
1792         }
1793         if (par) {
1794             *par = formatPar;
1795         }
1796     }
1797 }
1798 
1799 bool
getRegionOfDefinition(const RegionOfDefinitionArguments & args,OfxRectD & rod)1800 GenericWriterPlugin::getRegionOfDefinition(const RegionOfDefinitionArguments &args,
1801                                            OfxRectD &rod)
1802 {
1803     if ( !kSupportsRenderScale && ( (args.renderScale.x != 1.) || (args.renderScale.y != 1.) ) ) {
1804         throwSuiteStatusException(kOfxStatFailed);
1805 
1806         return false;
1807     }
1808     getOutputRoD(args.time, args.view, &rod, 0);
1809 
1810     return true;
1811 }
1812 
1813 
1814 void
getRegionsOfInterest(const OFX::RegionsOfInterestArguments & args,OFX::RegionOfInterestSetter & rois)1815 GenericWriterPlugin::getRegionsOfInterest(const OFX::RegionsOfInterestArguments &args,
1816                                           OFX::RegionOfInterestSetter &rois)
1817 {
1818     const double time = args.time;
1819 
1820     // A writer does not support tiled rendering, but still does not ask for the full
1821     // RoD of its inputs. We just ask for the renderwindow.
1822 
1823     // If "Overwrite" is not checked, and the file already exists, render does nothing (and outputs a black image)
1824     if ( _overwrite->getValueAtTime(time) ) {
1825         // set the RoI, since the default is the full input RoD
1826         rois.setRegionOfInterest(*_inputClip, args.regionOfInterest);
1827     } else {
1828         // check if output file exists
1829         string filename;
1830         _fileParam->getValueAtTime(time, filename);
1831         // filename = filenameFromPattern(filename, time);
1832         {
1833             std::FILE *image = fopen_utf8(filename.c_str(), "rb");
1834             if (!image) {
1835                 // set the RoI, since the default is the full input RoD
1836                 rois.setRegionOfInterest(*_inputClip, args.regionOfInterest);
1837             } else {
1838                 fclose(image);
1839                 // file exists, do not ask for anything on input
1840                 const OfxRectD emptyRoI = {0., 0., 0., 0.};
1841                 rois.setRegionOfInterest(*_inputClip, emptyRoI);
1842             }
1843         }
1844     }
1845 }
1846 
1847 void
encode(const string &,const OfxTime,const string &,const float *,const OfxRectI &,const float,const int,const int,const int,const int)1848 GenericWriterPlugin::encode(const string& /*filename*/,
1849                             const OfxTime /*time*/,
1850                             const string& /*viewName*/,
1851                             const float */*pixelData*/,
1852                             const OfxRectI& /*bounds*/,
1853                             const float /*pixelAspectRatio*/,
1854                             const int /*pixelDataNComps*/,
1855                             const int /*dstNCompsStartIndex*/,
1856                             const int /*dstNComps*/,
1857                             const int /*rowBytes*/)
1858 {
1859     /// Does nothing
1860 }
1861 
1862 void
beginEncodeParts(void *,const string &,OfxTime,float,LayerViewsPartsEnum,const map<int,string> &,const std::list<string> &,const bool,const vector<int> &,const OfxRectI &)1863 GenericWriterPlugin::beginEncodeParts(void* /*user_data*/,
1864                                       const string& /*filename*/,
1865                                       OfxTime /*time*/,
1866                                       float /*pixelAspectRatio*/,
1867                                       LayerViewsPartsEnum /*partsSplitting*/,
1868                                       const map<int, string>& /*viewsToRender*/,
1869                                       const std::list<string>& /*planes*/,
1870                                       const bool /*packingRequired*/,
1871                                       const vector<int>& /*packingMapping*/,
1872                                       const OfxRectI& /*bounds*/)
1873 {
1874     /// Does nothing
1875 }
1876 
1877 void
encodePart(void *,const string &,const float *,int,int,int)1878 GenericWriterPlugin::encodePart(void* /*user_data*/,
1879                                 const string& /*filename*/,
1880                                 const float */*pixelData*/,
1881                                 int /*pixelDataNComps*/,
1882                                 int /*planeIndex*/,
1883                                 int /*rowBytes*/)
1884 {
1885     /// Does nothing
1886 }
1887 
1888 bool
getTimeDomain(OfxRangeD & range)1889 GenericWriterPlugin::getTimeDomain(OfxRangeD &range)
1890 {
1891     int choice;
1892 
1893     _frameRange->getValue(choice);
1894     if (choice == 0) {
1895         ///let the default be applied
1896         return false;
1897     } else if (choice == 1) {
1898         timeLineGetBounds(range.min, range.max);
1899 
1900         return true;
1901     } else {
1902         int first;
1903         _firstFrame->getValue(first);
1904         range.min = first;
1905 
1906         int last;
1907         _lastFrame->getValue(last);
1908         range.max = last;
1909 
1910         return true;
1911     }
1912 }
1913 
1914 static string
imageFormatString(PixelComponentEnum components,BitDepthEnum bitDepth)1915 imageFormatString(PixelComponentEnum components,
1916                   BitDepthEnum bitDepth)
1917 {
1918     string s;
1919 
1920     switch (components) {
1921     case ePixelComponentRGBA:
1922         s += "RGBA";
1923         break;
1924     case ePixelComponentRGB:
1925         s += "RGB";
1926         break;
1927     case ePixelComponentAlpha:
1928         s += "Alpha";
1929         break;
1930     case ePixelComponentCustom:
1931         s += "Custom";
1932         break;
1933     case ePixelComponentNone:
1934         s += "None";
1935         break;
1936     default:
1937         s += "[unknown components]";
1938         break;
1939     }
1940     switch (bitDepth) {
1941     case eBitDepthUByte:
1942         s += "8u";
1943         break;
1944     case eBitDepthUShort:
1945         s += "16u";
1946         break;
1947     case eBitDepthFloat:
1948         s += "32f";
1949         break;
1950     case eBitDepthCustom:
1951         s += "x";
1952         break;
1953     case eBitDepthNone:
1954         s += "0";
1955         break;
1956     default:
1957         s += "[unknown bit depth]";
1958         break;
1959     }
1960 
1961     return s;
1962 }
1963 
1964 static string
premultString(PreMultiplicationEnum e)1965 premultString(PreMultiplicationEnum e)
1966 {
1967     switch (e) {
1968     case eImageOpaque:
1969 
1970         return "Opaque";
1971     case eImagePreMultiplied:
1972 
1973         return "PreMultiplied";
1974     case eImageUnPreMultiplied:
1975 
1976         return "UnPreMultiplied";
1977     }
1978 
1979     return "Unknown";
1980 }
1981 
1982 void
setOutputComponentsParam(PixelComponentEnum components)1983 GenericWriterPlugin::setOutputComponentsParam(PixelComponentEnum components)
1984 {
1985     assert(components == ePixelComponentRGB || components == ePixelComponentRGBA || components == ePixelComponentAlpha);
1986 }
1987 
1988 void
outputFileChanged(InstanceChangeReason reason,bool restoreExistingWriter,bool throwErrors)1989 GenericWriterPlugin::outputFileChanged(InstanceChangeReason reason,
1990                                        bool restoreExistingWriter,
1991                                        bool throwErrors)
1992 {
1993     string filename;
1994 
1995     _fileParam->getValue(filename);
1996 
1997     if ( filename.empty() ) {
1998         // if the file name is set to an empty string,
1999         // reset so that values are automatically set on next call to outputFileChanged()
2000         _guessedParams->resetToDefault();
2001 
2002         return;
2003     }
2004 
2005     // only set perstistent params if not restoring
2006     //bool setPersistentValues = !restoreExistingWriter && (reason == eChangeUserEdit);
2007     unused(restoreExistingWriter);
2008 
2009     {
2010         string ext = extension(filename);
2011         if ( !checkExtension(ext) ) {
2012             if (throwErrors) {
2013                 if (reason == eChangeUserEdit) {
2014                     sendMessage(Message::eMessageError, "", string("Unsupported file extension: ") + ext);
2015                 } else {
2016                     setPersistentMessage(Message::eMessageError, "", string("Unsupported file extension: ") + ext);
2017                 }
2018                 throwSuiteStatusException(kOfxStatErrImageFormat);
2019             } else {
2020                 return;
2021             }
2022         }
2023     }
2024     bool setColorSpace = true;
2025 #     ifdef OFX_IO_USING_OCIO
2026     // if outputSpaceSet == true (output space was manually set by user) then setColorSpace = false
2027     if ( _outputSpaceSet->getValue() ) {
2028         setColorSpace = false;
2029     }
2030     // We should always try to parse from string first,
2031     // following recommendations from http://opencolorio.org/configurations/spi_pipeline.html
2032     // However, as discussed in https://groups.google.com/forum/#!topic/ocio-dev/dfOxq8Nanl8
2033     // the OpenColorIO 1.0.9 implementation fails too often.
2034     // We should wait for the next version, where pull request
2035     // https://github.com/imageworks/OpenColorIO/pull/381 or
2036     // https://github.com/imageworks/OpenColorIO/pull/413 may be merged.
2037     OCIO::ConstConfigRcPtr ocioConfig = _ocio->getConfig();
2038     if (setColorSpace && ocioConfig) {
2039         string name = filename;
2040         (void)SequenceParsing::removePath(name); // only use the file NAME
2041         const char* colorSpaceStr = ocioConfig->parseColorSpaceFromString( name.c_str() );
2042         size_t colorSpaceStrLen = colorSpaceStr ? std::strlen(colorSpaceStr) : 0;
2043         if (colorSpaceStrLen == 0) {
2044             colorSpaceStr = NULL;
2045         }
2046 #if OCIO_VERSION_HEX > 0x01000900 // more recent than 1.0.9?
2047 #pragma message WARN("OpenColorIO was updated, check that the following code is still necessary")
2048 #endif
2049         if (colorSpaceStr) {
2050             // Only use this colorspace name if it is the last thing before the extension name,
2051             // and it is preceded by '_' or '-' or ' ' or '.' (we exclude '/' and '\\'),
2052             // as in http://opencolorio.org/configurations/spi_pipeline.html
2053             // https://github.com/imageworks/OpenColorIO/pull/413
2054             size_t pos = name.find_last_of('.');
2055             if ( pos == string::npos || pos < (colorSpaceStrLen + 1) ) { // +1 for the delimiter
2056                 // no dot, or the colorspace name and the delimiter cannot hold before the dot
2057                 colorSpaceStr = NULL;
2058                 colorSpaceStrLen = 0;
2059             } else if ( (name.compare(pos - colorSpaceStrLen, colorSpaceStrLen, colorSpaceStr) != 0) ||
2060                        ( (name[pos - colorSpaceStrLen - 1] != '_') &&
2061                         (name[pos - colorSpaceStrLen - 1] != '-') &&
2062                         (name[pos - colorSpaceStrLen - 1] != ' ') &&
2063                         (name[pos - colorSpaceStrLen - 1] != '.') ) ) {
2064                            // the colorspace name is not before the last dot or is not preceded by a valid delimiter
2065                            colorSpaceStr = NULL;
2066                            colorSpaceStrLen = 0;
2067                        }
2068         }
2069         if (colorSpaceStr) {
2070             assert( _ocio->hasColorspace(colorSpaceStr) ); // parseColorSpaceFromString always returns an existing colorspace
2071             // we're lucky
2072             _ocio->setOutputColorspace(colorSpaceStr);
2073             setColorSpace = false;
2074         }
2075     }
2076 #     endif
2077 
2078     // give the derived class a chance to initialize any data structure it may need
2079     onOutputFileChanged(filename, setColorSpace);
2080 #     ifdef OFX_IO_USING_OCIO
2081     _ocio->refreshInputAndOutputState(0);
2082 #     endif
2083 
2084     if (_clipToRoD) {
2085         _clipToRoD->setIsSecretAndDisabled( !displayWindowSupportedByFormat(filename) );
2086     }
2087 
2088 
2089     if (reason == eChangeUserEdit) {
2090         // mark that most parameters should not be set automagically if the filename is changed
2091         // (the user must keep control over what happens)
2092         _guessedParams->setValue(true);
2093     }
2094 } // GenericWriterPlugin::outputFileChanged
2095 
2096 void
changedParam(const InstanceChangedArgs & args,const string & paramName)2097 GenericWriterPlugin::changedParam(const InstanceChangedArgs &args,
2098                                   const string &paramName)
2099 {
2100     // must clear persistent message, or render() is not called by Nuke after an error
2101     clearPersistentMessage();
2102     if (paramName == kParamFrameRange) {
2103         int choice;
2104         double first, last;
2105         timeLineGetBounds(first, last);
2106         _frameRange->getValue(choice);
2107         if (choice == 2) {
2108             _firstFrame->setIsSecretAndDisabled(false);
2109             int curFirstFrame;
2110             _firstFrame->getValue(curFirstFrame);
2111             // if first-frame has never been set by the user, set it
2112             if ( (first != curFirstFrame) && (curFirstFrame == 0) ) {
2113                 _firstFrame->setValue( (int)first );
2114             }
2115             _lastFrame->setIsSecretAndDisabled(false);
2116             int curLastFrame;
2117             _lastFrame->getValue(curLastFrame);
2118             // if last-frame has never been set by the user, set it
2119             if ( (last != curLastFrame) && (curLastFrame == 0) ) {
2120                 _lastFrame->setValue( (int)last );
2121             }
2122         } else {
2123             _firstFrame->setIsSecretAndDisabled(true);
2124             _lastFrame->setIsSecretAndDisabled(true);
2125         }
2126     } else if (paramName == kParamFilename) {
2127         outputFileChanged(args.reason, _guessedParams->getValue(), true);
2128     } else if (paramName == kParamFormatType) {
2129         FormatTypeEnum type = (FormatTypeEnum)_outputFormatType->getValue();
2130         if (_clipToRoD) {
2131             string filename;
2132             _fileParam->getValue(filename);
2133             _clipToRoD->setIsSecretAndDisabled( !displayWindowSupportedByFormat(filename) );
2134         }
2135         if ( (type == eFormatTypeInput) || (type == eFormatTypeProject) ) {
2136             _outputFormat->setIsSecretAndDisabled(true);
2137         } else {
2138             _outputFormat->setIsSecretAndDisabled(false);
2139         }
2140     } else if (paramName == kParamOutputFormat) {
2141         //the host does not handle the format itself, do it ourselves
2142         int format_i;
2143         _outputFormat->getValue(format_i);
2144         int w = 0, h = 0;
2145         double par = -1;
2146         getFormatResolution( (EParamFormat)format_i, &w, &h, &par );
2147         assert(par != -1);
2148         _outputFormatPar->setValue(par);
2149         _outputFormatSize->setValue(w, h);
2150     } else if ( (paramName == kParamClipInfo) && (args.reason == eChangeUserEdit) ) {
2151         string msg;
2152         msg += "Input: ";
2153         if (!_inputClip) {
2154             msg += "N/A";
2155         } else {
2156             msg += imageFormatString( _inputClip->getPixelComponents(), _inputClip->getPixelDepth() );
2157             msg += " ";
2158             msg += premultString( _inputClip->getPreMultiplication() );
2159         }
2160         msg += "\n";
2161         msg += "Output: ";
2162         if (!_outputClip) {
2163             msg += "N/A";
2164         } else {
2165             msg += imageFormatString( _outputClip->getPixelComponents(), _outputClip->getPixelDepth() );
2166             msg += " ";
2167             msg += premultString( _outputClip->getPreMultiplication() );
2168         }
2169         msg += "\n";
2170         sendMessage(Message::eMessageMessage, "", msg);
2171 #ifdef OFX_IO_USING_OCIO
2172     } else if ( ( (paramName == kOCIOParamOutputSpace) || (paramName == kOCIOParamOutputSpaceChoice) ) &&
2173                 ( args.reason == eChangeUserEdit) ) {
2174         // set the outputSpaceSet param to true https://github.com/MrKepzie/Natron/issues/1492
2175         _outputSpaceSet->setValue(true);
2176 #endif
2177     }
2178 
2179 #ifdef OFX_IO_USING_OCIO
2180     _ocio->changedParam(args, paramName);
2181 #endif
2182 
2183     MultiPlaneEffect::changedParam(args, paramName);
2184 } // GenericWriterPlugin::changedParam
2185 
2186 void
changedClip(const InstanceChangedArgs & args,const string & clipName)2187 GenericWriterPlugin::changedClip(const InstanceChangedArgs &args,
2188                                  const string &clipName)
2189 {
2190     // must clear persistent message, or render() is not called by Nuke after an error
2191     clearPersistentMessage();
2192     MultiPlaneEffect::changedClip(args, clipName);
2193     if ( (clipName == kOfxImageEffectSimpleSourceClipName) && _inputClip && (args.reason == eChangeUserEdit) ) {
2194         PreMultiplicationEnum premult = _inputClip->getPreMultiplication();
2195 #     ifdef DEBUG
2196         if ( _inputClip->isConnected() ) {
2197             PixelComponentEnum components = _inputClip->getPixelComponents();
2198             assert( (components == ePixelComponentAlpha && premult != eImageOpaque) ||
2199                     (components == ePixelComponentRGB && premult == eImageOpaque) ||
2200                     (components == ePixelComponentRGBA) ||
2201                     ( (components == ePixelComponentCustom ||
2202                        components == ePixelComponentMotionVectors ||
2203                        components == ePixelComponentStereoDisparity) && gHostIsMultiPlanar ) );
2204 
2205 
2206             int index = -1;
2207             for (std::size_t i = 0; i < _outputComponentsTable.size(); ++i) {
2208                 if (_outputComponentsTable[i] == components) {
2209                     index = i;
2210                     break;
2211                 }
2212             }
2213             assert(index != -1);
2214             if (index != -1) {
2215                 _outputComponents->setValue(index);
2216             }
2217         }
2218 #      endif
2219         _premult->setValue(premult);
2220 
2221         double fps = _inputClip->getFrameRate();
2222         setOutputFrameRate(fps);
2223     } else if (clipName == kOfxImageEffectOutputClipName) {
2224         // Natron 3 calls changedClip on the output clip when metadata are changed. This is because
2225         // the getClipPreferences action is used to pull values its results are cached.
2226         refreshRGBAParamsFromOutputComponents();
2227     }
2228 }
2229 
2230 void
refreshRGBAParamsFromOutputComponents()2231 GenericWriterPlugin::refreshRGBAParamsFromOutputComponents()
2232 {
2233     int index;
2234     _outputComponents->getValue(index);
2235     assert( index >= 0 && index < (int)_outputComponentsTable.size() );
2236     PixelComponentEnum comps = _outputComponentsTable[index];
2237 
2238 
2239     vector<string> checkboxesLabels;
2240     if (comps == ePixelComponentAlpha) {
2241         checkboxesLabels.push_back("A");
2242     } else if (comps == ePixelComponentRGB) {
2243         checkboxesLabels.push_back("R");
2244         checkboxesLabels.push_back("G");
2245         checkboxesLabels.push_back("B");
2246     } else if (comps == ePixelComponentRGBA) {
2247         checkboxesLabels.push_back("R");
2248         checkboxesLabels.push_back("G");
2249         checkboxesLabels.push_back("B");
2250         checkboxesLabels.push_back("A");
2251     }
2252 
2253     if (checkboxesLabels.size() == 1) {
2254         for (int i = 0; i < 3; ++i) {
2255             _processChannels[i]->setIsSecretAndDisabled(true);
2256         }
2257         _processChannels[3]->setIsSecretAndDisabled(false);
2258         _processChannels[3]->setLabel(checkboxesLabels[0]);
2259     } else {
2260         for (int i = 0; i < 4; ++i) {
2261             if ( i < (int)checkboxesLabels.size() ) {
2262                 _processChannels[i]->setIsSecretAndDisabled(false);
2263                 _processChannels[i]->setLabel(checkboxesLabels[i]);
2264             } else {
2265                 _processChannels[i]->setIsSecretAndDisabled(true);
2266             }
2267         }
2268     }
2269 
2270 } // refreshRGBAParamsFromOutputComponents
2271 
2272 void
getClipPreferences(ClipPreferencesSetter & clipPreferences)2273 GenericWriterPlugin::getClipPreferences(ClipPreferencesSetter &clipPreferences)
2274 {
2275     MultiPlaneEffect::getClipPreferences(clipPreferences);
2276     if ( !_outputComponents->getIsSecret() ) {
2277         int index;
2278         _outputComponents->getValue(index);
2279         assert( index >= 0 && index < (int)_outputComponentsTable.size() );
2280         PixelComponentEnum comps = _outputComponentsTable[index];
2281 
2282         if (!gHostIsNatronVersion3OrGreater) {
2283             refreshRGBAParamsFromOutputComponents();
2284         }
2285 
2286         //Set output pixel components to match what will be output if the choice is not All
2287         clipPreferences.setClipComponents(*_inputClip, comps);
2288         clipPreferences.setClipComponents(*_outputClip, comps);
2289         PreMultiplicationEnum premult = _inputClip->getPreMultiplication();
2290         switch (comps) {
2291         case ePixelComponentAlpha:
2292             premult = eImageUnPreMultiplied;
2293             break;
2294         case ePixelComponentXY:
2295             premult = eImageOpaque;
2296             break;
2297         case ePixelComponentRGB:
2298             premult = eImageOpaque;
2299             break;
2300         default:
2301             break;
2302         }
2303 
2304         clipPreferences.setOutputPremultiplication(premult);
2305     }
2306 } // GenericWriterPlugin::getClipPreferences
2307 
2308 void
getFrameViewsNeeded(const FrameViewsNeededArguments & args,FrameViewsNeededSetter & frameViews)2309 GenericWriterPlugin::getFrameViewsNeeded(const FrameViewsNeededArguments& args,
2310                                          FrameViewsNeededSetter& frameViews)
2311 {
2312     OfxRangeD r;
2313 
2314     r.min = r.max = args.time;
2315 
2316     if (!gHostIsMultiView) {
2317         //As whats requested
2318         frameViews.addFrameViewsNeeded(*_inputClip, r, args.view);
2319     } else {
2320         int viewsToRender = getViewToRender();
2321         if (viewsToRender == kGenericWriterViewAll) {
2322             if (args.view != 0) {
2323                 // any view other than view 0 does nothing and requires no input
2324                 return;
2325             }
2326             // rendering view 0 requires all views, and writes them to file
2327             int nViews = getViewCount();
2328             for (int i = 0; i < nViews; ++i) {
2329                 frameViews.addFrameViewsNeeded(*_inputClip, r, i);
2330             }
2331         } else {
2332             // default behavior
2333             if (viewsToRender == kGenericWriterViewDefault) {
2334                 viewsToRender = args.view;
2335             }
2336             frameViews.addFrameViewsNeeded(*_inputClip, r, viewsToRender);
2337         }
2338     }
2339 }
2340 
2341 void
purgeCaches()2342 GenericWriterPlugin::purgeCaches()
2343 {
2344     clearAnyCache();
2345 #ifdef OFX_IO_USING_OCIO
2346     _ocio->purgeCaches();
2347 #endif
2348 }
2349 
2350 using namespace OFX;
2351 
2352 /**
2353  * @brief Override this to describe the writer.
2354  * You should call the base-class version at the end like this:
2355  * GenericWriterPluginFactory<YOUR_FACTORY>::describe(desc);
2356  **/
2357 void
GenericWriterDescribe(ImageEffectDescriptor & desc,RenderSafetyEnum safety,const vector<string> & extensions,int evaluation,bool isMultiPlanar,bool isMultiView)2358 GenericWriterDescribe(ImageEffectDescriptor &desc,
2359                       RenderSafetyEnum safety,
2360                       const vector<string>& extensions, // list of supported extensions
2361                       int evaluation, // plugin quality from 0 (bad) to 100 (perfect) or -1 if not evaluated
2362                       bool isMultiPlanar,
2363                       bool isMultiView)
2364 {
2365     desc.setPluginGrouping(kPluginGrouping);
2366 
2367 #ifdef OFX_EXTENSIONS_TUTTLE
2368     desc.addSupportedContext(eContextWriter);
2369     desc.addSupportedExtensions(extensions);
2370     desc.setPluginEvaluation(evaluation);
2371 #endif
2372     desc.addSupportedContext(eContextGeneral);
2373 
2374     // OCIO is only supported for float images.
2375     //desc.addSupportedBitDepth(eBitDepthUByte);
2376     //desc.addSupportedBitDepth(eBitDepthUShort);
2377     desc.addSupportedBitDepth(eBitDepthFloat);
2378 
2379     // set a few flags
2380     desc.setSingleInstance(false);
2381     desc.setHostFrameThreading(false);
2382     desc.setSupportsMultiResolution(kSupportsMultiResolution);
2383     desc.setSupportsTiles(kSupportsTiles);
2384     desc.setTemporalClipAccess(false); // say we will be doing random time access on clips
2385     desc.setRenderTwiceAlways(false);
2386     desc.setSupportsMultipleClipPARs(false);
2387     desc.setRenderThreadSafety(safety);
2388 
2389 
2390 #ifdef OFX_EXTENSIONS_NUKE
2391     if (getImageEffectHostDescription()
2392         && getImageEffectHostDescription()->isMultiPlanar) {
2393         desc.setIsMultiPlanar(isMultiPlanar);
2394         if (isMultiPlanar) {
2395             gHostIsMultiPlanar = true;
2396             //We let all un-rendered planes pass-through so that they can be retrieved below by a shuffle node
2397             desc.setPassThroughForNotProcessedPlanes(ePassThroughLevelPassThroughNonRenderedPlanes);
2398         }
2399     }
2400     if ( isMultiView && fetchSuite(kFnOfxImageEffectPlaneSuite, 2, true) ) {
2401         gHostIsMultiView = true;
2402         desc.setIsViewAware(true);
2403         desc.setIsViewInvariant(eViewInvarianceAllViewsVariant);
2404     }
2405 #endif
2406 
2407 #ifdef OFX_EXTENSIONS_NATRON
2408     desc.setChannelSelector(ePixelComponentNone); // we have our own channel selector
2409 #endif
2410 }
2411 
2412 /**
2413  * @brief Override this to describe in context the writer.
2414  * You should call the base-class version at the end like this:
2415  * GenericWriterPluginFactory<YOUR_FACTORY>::describeInContext(desc,context);
2416  **/
2417 PageParamDescriptor*
GenericWriterDescribeInContextBegin(ImageEffectDescriptor & desc,ContextEnum context,bool supportsRGBA,bool supportsRGB,bool supportsXY,bool supportsAlpha,const char * inputSpaceNameDefault,const char * outputSpaceNameDefault,bool supportsDisplayWindow)2418 GenericWriterDescribeInContextBegin(ImageEffectDescriptor &desc,
2419                                     ContextEnum context,
2420                                     bool supportsRGBA,
2421                                     bool supportsRGB,
2422                                     bool supportsXY,
2423                                     bool supportsAlpha,
2424                                     const char* inputSpaceNameDefault,
2425                                     const char* outputSpaceNameDefault,
2426                                     bool supportsDisplayWindow)
2427 {
2428     gHostIsNatron = (getImageEffectHostDescription()->isNatron);
2429     if (gHostIsNatron) {
2430         gHostIsNatronVersion3OrGreater = getImageEffectHostDescription()->versionMajor >= 3;
2431     }
2432 
2433     // create the mandated source clip
2434     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
2435     if (supportsRGBA) {
2436         srcClip->addSupportedComponent(ePixelComponentRGBA);
2437     }
2438     if (supportsRGB) {
2439         srcClip->addSupportedComponent(ePixelComponentRGB);
2440     }
2441     if (supportsAlpha) {
2442         srcClip->addSupportedComponent(ePixelComponentAlpha);
2443     }
2444 #ifdef OFX_EXTENSIONS_NATRON
2445     if (supportsXY && gHostIsNatron) {
2446         srcClip->addSupportedComponent(ePixelComponentXY);
2447     }
2448 #endif
2449     srcClip->setSupportsTiles(kSupportsTiles);
2450 
2451     // create the mandated output clip
2452     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
2453     if (supportsRGBA) {
2454         dstClip->addSupportedComponent(ePixelComponentRGBA);
2455     }
2456     if (supportsRGB) {
2457         dstClip->addSupportedComponent(ePixelComponentRGB);
2458     }
2459     if (supportsAlpha) {
2460         dstClip->addSupportedComponent(ePixelComponentAlpha);
2461     }
2462 #ifdef OFX_EXTENSIONS_NATRON
2463     if (supportsXY && gHostIsNatron) {
2464         dstClip->addSupportedComponent(ePixelComponentXY);
2465     }
2466 #endif
2467     dstClip->setSupportsTiles(kSupportsTiles);//< we don't support tiles in output!
2468 
2469 
2470     // make some pages and to things in
2471     PageParamDescriptor *page = desc.definePageParam("Controls");
2472 
2473     {
2474         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamOutputComponents);
2475         param->setLabel(kParamOutputComponentsLabel);
2476         param->setHint(kParamOutputComponentsHint);
2477         // must be in sync with the end of the plugin construction
2478         if (supportsAlpha) {
2479             param->appendOption("Alpha");
2480         }
2481         if (supportsRGB) {
2482             param->appendOption("RGB");
2483         }
2484         if (supportsRGBA) {
2485             param->appendOption("RGBA");
2486         }
2487         param->setLayoutHint(eLayoutHintNoNewLine);
2488         param->setDefault(param->getNOptions() - 1);
2489         desc.addClipPreferencesSlaveParam(*param);
2490         if (page) {
2491             page->addChild(*param);
2492         }
2493     }
2494 
2495     {
2496         BooleanParamDescriptor* param = desc.defineBooleanParam(kNatronOfxParamProcessR);
2497         param->setLabel(kNatronOfxParamProcessRLabel);
2498         param->setHint(kParamProcessHint);
2499         param->setDefault(true);
2500         param->setLayoutHint(eLayoutHintNoNewLine, 1);
2501         if (page) {
2502             page->addChild(*param);
2503         }
2504     }
2505     {
2506         BooleanParamDescriptor* param = desc.defineBooleanParam(kNatronOfxParamProcessG);
2507         param->setLabel(kNatronOfxParamProcessGLabel);
2508         param->setHint(kParamProcessHint);
2509         param->setDefault(true);
2510         param->setLayoutHint(eLayoutHintNoNewLine, 1);
2511         if (page) {
2512             page->addChild(*param);
2513         }
2514     }
2515     {
2516         BooleanParamDescriptor* param = desc.defineBooleanParam(kNatronOfxParamProcessB);
2517         param->setLabel(kNatronOfxParamProcessBLabel);
2518         param->setHint(kParamProcessHint);
2519         param->setDefault(true);
2520         param->setLayoutHint(eLayoutHintNoNewLine, 1);
2521         if (page) {
2522             page->addChild(*param);
2523         }
2524     }
2525     {
2526         BooleanParamDescriptor* param = desc.defineBooleanParam(kNatronOfxParamProcessA);
2527         param->setLabel(kNatronOfxParamProcessALabel);
2528         param->setHint(kParamProcessHint);
2529         param->setDefault(true);
2530         if (page) {
2531             page->addChild(*param);
2532         }
2533     }
2534 
2535     //////////Output file
2536     {
2537         StringParamDescriptor* param = desc.defineStringParam(kParamFilename);
2538         param->setLabel(kParamFilenameLabel);
2539         param->setStringType(eStringTypeFilePath);
2540         param->setFilePathExists(false);
2541         param->setHint(kParamFilenameHint);
2542         // in the Writer context, the script name should be kOfxImageEffectFileParamName, for consistency with the reader nodes @see kOfxImageEffectContextReader
2543         param->setScriptName(kParamFilename);
2544         param->setAnimates(false);
2545         desc.addClipPreferencesSlaveParam(*param);
2546         if (page) {
2547             page->addChild(*param);
2548         }
2549     }
2550 
2551     //////////Overwrite
2552     {
2553         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamOverwrite);
2554         param->setLabelAndHint(kParamOverwriteLabelAndHint);
2555         param->setDefault(true);
2556         param->setAnimates(false);
2557         if (page) {
2558             page->addChild(*param);
2559         }
2560     }
2561 
2562     //////////// Output type
2563     {
2564         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamFormatType);
2565         param->setLabel(kParamFormatTypeLabel);
2566         assert(param->getNOptions() == (int)eFormatTypeInput);
2567         param->appendOption(kParamFormatTypeOptionInput);
2568         assert(param->getNOptions() == (int)eFormatTypeProject);
2569         param->appendOption(kParamFormatTypeOptionProject);
2570         assert(param->getNOptions() == (int)eFormatTypeFixed);
2571         param->appendOption(kParamFormatTypeOptionFixed);
2572         param->setDefault( (int)eFormatTypeProject );
2573         param->setAnimates(false);
2574         param->setHint(kParamFormatTypeHint);
2575         param->setLayoutHint(eLayoutHintNoNewLine, 1);
2576         if (page) {
2577             page->addChild(*param);
2578         }
2579     }
2580 
2581 
2582     //////////// Output format
2583     {
2584         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamOutputFormat);
2585         param->setLabel(kParamOutputFormatLabel);
2586         param->setAnimates(true);
2587         //param->setIsSecret(true); // done in the plugin constructor
2588         param->setHint(kParamOutputFormatHint);
2589         assert(param->getNOptions() == eParamFormatPCVideo);
2590         param->appendOption(kParamFormatPCVideoLabel, "", kParamFormatPCVideo);
2591         assert(param->getNOptions() == eParamFormatNTSC);
2592         param->appendOption(kParamFormatNTSCLabel, "", kParamFormatNTSC);
2593         assert(param->getNOptions() == eParamFormatPAL);
2594         param->appendOption(kParamFormatPALLabel, "", kParamFormatPAL);
2595         assert(param->getNOptions() == eParamFormatNTSC169);
2596         param->appendOption(kParamFormatNTSC169Label, "", kParamFormatNTSC169);
2597         assert(param->getNOptions() == eParamFormatPAL169);
2598         param->appendOption(kParamFormatPAL169Label, "", kParamFormatPAL169);
2599         assert(param->getNOptions() == eParamFormatHD720);
2600         param->appendOption(kParamFormatHD720Label, "", kParamFormatHD720);
2601         assert(param->getNOptions() == eParamFormatHD);
2602         param->appendOption(kParamFormatHDLabel, "", kParamFormatHD);
2603         assert(param->getNOptions() == eParamFormatUHD4K);
2604         param->appendOption(kParamFormatUHD4KLabel, "", kParamFormatUHD4K);
2605         assert(param->getNOptions() == eParamFormat1kSuper35);
2606         param->appendOption(kParamFormat1kSuper35Label, "", kParamFormat1kSuper35);
2607         assert(param->getNOptions() == eParamFormat1kCinemascope);
2608         param->appendOption(kParamFormat1kCinemascopeLabel, "", kParamFormat1kCinemascope);
2609         assert(param->getNOptions() == eParamFormat2kSuper35);
2610         param->appendOption(kParamFormat2kSuper35Label, "", kParamFormat2kSuper35);
2611         assert(param->getNOptions() == eParamFormat2kCinemascope);
2612         param->appendOption(kParamFormat2kCinemascopeLabel, "", kParamFormat2kCinemascope);
2613         assert(param->getNOptions() == eParamFormat2kDCP);
2614         param->appendOption(kParamFormat2kDCPLabel, "", kParamFormat2kDCP);
2615         assert(param->getNOptions() == eParamFormat4kSuper35);
2616         param->appendOption(kParamFormat4kSuper35Label, "", kParamFormat4kSuper35);
2617         assert(param->getNOptions() == eParamFormat4kCinemascope);
2618         param->appendOption(kParamFormat4kCinemascopeLabel, "", kParamFormat4kCinemascope);
2619         assert(param->getNOptions() == eParamFormat4kDCP);
2620         param->appendOption(kParamFormat4kDCPLabel, "", kParamFormat4kDCP);
2621         assert(param->getNOptions() == eParamFormatSquare256);
2622         param->appendOption(kParamFormatSquare256Label, "", kParamFormatSquare256);
2623         assert(param->getNOptions() == eParamFormatSquare512);
2624         param->appendOption(kParamFormatSquare512Label, "", kParamFormatSquare512);
2625         assert(param->getNOptions() == eParamFormatSquare1k);
2626         param->appendOption(kParamFormatSquare1kLabel, "", kParamFormatSquare1k);
2627         assert(param->getNOptions() == eParamFormatSquare2k);
2628         param->appendOption(kParamFormatSquare2kLabel, "", kParamFormatSquare2k);
2629         param->setDefault(eParamFormatHD);
2630         if (page) {
2631             page->addChild(*param);
2632         }
2633     }
2634 
2635     {
2636         // secret parameters to handle custom formats
2637         int w = 0, h = 0;
2638         double par = -1;
2639         getFormatResolution(eParamFormatHD, &w, &h, &par);
2640         assert(par != -1);
2641         {
2642             Int2DParamDescriptor* param = desc.defineInt2DParam(kParamFormatSize);
2643             param->setIsSecretAndDisabled(true);
2644             param->setDefault(w, h);
2645             if (page) {
2646                 page->addChild(*param);
2647             }
2648         }
2649 
2650         {
2651             DoubleParamDescriptor* param = desc.defineDoubleParam(kParamFormatPar);
2652             param->setIsSecretAndDisabled(true);
2653             param->setRange(0., DBL_MAX);
2654             param->setDisplayRange(0.5, 2.);
2655             param->setDefault(par);
2656             if (page) {
2657                 page->addChild(*param);
2658             }
2659         }
2660     }
2661 
2662 
2663     /////////// Clip to project
2664     if (supportsDisplayWindow) {
2665         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamClipToRoD);
2666         param->setLabel(kParamClipToRoDLabel);
2667         param->setHint(kParamClipToRoDHint);
2668         param->setDefault(true);
2669         if (page) {
2670             page->addChild(*param);
2671         }
2672     }
2673 
2674 #ifdef OFX_IO_USING_OCIO
2675     // insert OCIO parameters
2676     GenericOCIO::describeInContextInput(desc, context, page, inputSpaceNameDefault);
2677     GenericOCIO::describeInContextOutput(desc, context, page, outputSpaceNameDefault, kParamOutputSpaceLabel);
2678     {
2679         BooleanParamDescriptor* param  = desc.defineBooleanParam(kParamOutputSpaceSet);
2680         param->setEvaluateOnChange(false);
2681         param->setAnimates(false);
2682         param->setIsSecretAndDisabled(true);
2683         param->setDefault(false);
2684         if (page) {
2685             page->addChild(*param);
2686         }
2687     }
2688     GenericOCIO::describeInContextContext(desc, context, page);
2689     {
2690         PushButtonParamDescriptor* param = desc.definePushButtonParam(kOCIOHelpButton);
2691         param->setLabel(kOCIOHelpButtonLabel);
2692         param->setHint(kOCIOHelpButtonHint);
2693         if (page) {
2694             page->addChild(*param);
2695         }
2696     }
2697 #endif
2698 
2699     {
2700         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamInputPremult);
2701         param->setLabel(kParamInputPremultLabel);
2702         param->setAnimates(true);
2703         param->setHint(kParamInputPremultHint);
2704         assert(param->getNOptions() == eImageOpaque);
2705         param->appendOption(premultString(eImageOpaque), kParamInputPremultOptionOpaqueHint);
2706         assert(param->getNOptions() == eImagePreMultiplied);
2707         param->appendOption(premultString(eImagePreMultiplied), kParamInputPremultOptionPreMultipliedHint);
2708         assert(param->getNOptions() == eImageUnPreMultiplied);
2709         param->appendOption(premultString(eImageUnPreMultiplied), kParamInputPremultOptionUnPreMultipliedHint);
2710         param->setDefault(eImagePreMultiplied); // images should be premultiplied in a compositing context
2711         param->setLayoutHint(eLayoutHintNoNewLine, 1);
2712         if (page) {
2713             page->addChild(*param);
2714         }
2715     }
2716 
2717     {
2718         PushButtonParamDescriptor *param = desc.definePushButtonParam(kParamClipInfo);
2719         param->setLabel(kParamClipInfoLabel);
2720         param->setHint(kParamClipInfoHint);
2721         if (page) {
2722             page->addChild(*param);
2723         }
2724     }
2725 
2726     ///////////Frame range choosal
2727     {
2728         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamFrameRange);
2729         param->setLabel(kParamFrameRangeLabel);
2730         param->setHint(kParamFrameRangeHint);
2731         param->appendOption(kParamFrameRangeOptionUnion);
2732         param->appendOption(kParamFrameRangeOptionBounds);
2733         param->appendOption(kParamFrameRangeOptionManual);
2734         param->setAnimates(true);
2735         param->setDefault(1);
2736         if (page) {
2737             page->addChild(*param);
2738         }
2739     }
2740 
2741     /////////////First frame
2742     {
2743         IntParamDescriptor* param = desc.defineIntParam(kParamFirstFrame);
2744         param->setLabel(kParamFirstFrameLabel);
2745         //param->setIsSecretAndDisabled(true); // done in the plugin constructor
2746         param->setAnimates(true);
2747         if (page) {
2748             page->addChild(*param);
2749         }
2750     }
2751 
2752     ////////////Last frame
2753     {
2754         IntParamDescriptor* param = desc.defineIntParam(kParamLastFrame);
2755         param->setLabel(kParamLastFrameLabel);
2756         //param->setIsSecretAndDisabled(true); // done in the plugin constructor
2757         param->setAnimates(true);
2758         if (page) {
2759             page->addChild(*param);
2760         }
2761     }
2762 
2763     // sublabel
2764     if (gHostIsNatron) {
2765         StringParamDescriptor* param = desc.defineStringParam(kNatronOfxParamStringSublabelName);
2766         param->setIsSecretAndDisabled(true); // always secret
2767         param->setIsPersistent(false);
2768         param->setEvaluateOnChange(false);
2769         //param->setDefault();
2770         if (page) {
2771             page->addChild(*param);
2772         }
2773     }
2774 
2775     {
2776         BooleanParamDescriptor* param  = desc.defineBooleanParam(kParamGuessedParams);
2777         param->setEvaluateOnChange(false);
2778         param->setAnimates(false);
2779         param->setIsSecretAndDisabled(true);
2780         param->setDefault(false);
2781         if (page) {
2782             page->addChild(*param);
2783         }
2784     }
2785 
2786 
2787     return page;
2788 } // GenericWriterDescribeInContextBegin
2789 
2790 void
GenericWriterDescribeInContextEnd(ImageEffectDescriptor &,ContextEnum,PageParamDescriptor *)2791 GenericWriterDescribeInContextEnd(ImageEffectDescriptor & /*desc*/,
2792                                   ContextEnum /*context*/,
2793                                   PageParamDescriptor* /*page*/)
2794 {
2795 }
2796 
2797 NAMESPACE_OFX_IO_EXIT
2798 NAMESPACE_OFX_EXIT
2799