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