1 /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * This file is part of openfx-supportext <https://github.com/devernay/openfx-supportext>,
4 * Copyright (C) 2013-2018 INRIA
5 *
6 * openfx-supportext is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * openfx-supportext is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with openfx-supportext. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
18 * ***** END LICENSE BLOCK ***** */
19
20 /*
21 * Helper functions to implement plug-ins that support kFnOfxImageEffectPlaneSuite v2
22 * In order to use these functions the following condition must be met:
23 *#if defined(OFX_EXTENSIONS_NUKE) && defined(OFX_EXTENSIONS_NATRON)
24
25 if (fetchSuite(kFnOfxImageEffectPlaneSuite, 2) && // for clipGetImagePlane
26 getImageEffectHostDescription()->supportsDynamicChoices && // for dynamic layer choices
27 getImageEffectHostDescription()->isMultiPlanar) // for clipGetImagePlane
28 ... this is ok...
29 *#endif
30 */
31 #include "ofxsMultiPlane.h"
32
33 #include <algorithm>
34 #include <set>
35
36 using namespace OFX;
37
38 using std::vector;
39 using std::string;
40 using std::map;
41 using std::set;
42
43 static bool gHostSupportsMultiPlaneV1 = false;
44 static bool gHostSupportsMultiPlaneV2 = false;
45 static bool gHostSupportsDynamicChoices = false;
46 static bool gHostIsNatron3OrGreater = false;
47
48 static const char* rgbaComps[4] = {"R", "G", "B", "A"};
49 static const char* rgbComps[3] = {"R", "G", "B"};
50 static const char* alphaComps[1] = {"A"};
51 static const char* motionComps[2] = {"U", "V"};
52 static const char* disparityComps[2] = {"X", "Y"};
53 static const char* xyComps[2] = {"X", "Y"};
54
55 namespace OFX {
56 namespace MultiPlane {
57
58
ImagePlaneDesc()59 ImagePlaneDesc::ImagePlaneDesc()
60 : _planeID("none")
61 , _planeLabel("none")
62 , _channels()
63 , _channelsLabel("none")
64 {
65 }
66
ImagePlaneDesc(const std::string & planeID,const std::string & planeLabel,const std::string & channelsLabel,const std::vector<std::string> & channels)67 ImagePlaneDesc::ImagePlaneDesc(const std::string& planeID,
68 const std::string& planeLabel,
69 const std::string& channelsLabel,
70 const std::vector<std::string>& channels)
71 : _planeID(planeID)
72 , _planeLabel(planeLabel)
73 , _channels(channels)
74 , _channelsLabel(channelsLabel)
75 {
76 if (planeLabel.empty()) {
77 // Plane label is the ID if empty
78 _planeLabel = _planeID;
79 }
80 if ( channelsLabel.empty() ) {
81 // Channels label is the concatenation of all channels
82 for (std::size_t i = 0; i < channels.size(); ++i) {
83 _channelsLabel.append(channels[i]);
84 }
85 }
86 }
87
ImagePlaneDesc(const std::string & planeName,const std::string & planeLabel,const std::string & channelsLabel,const char ** channels,int count)88 ImagePlaneDesc::ImagePlaneDesc(const std::string& planeName,
89 const std::string& planeLabel,
90 const std::string& channelsLabel,
91 const char** channels,
92 int count)
93 : _planeID(planeName)
94 , _planeLabel(planeLabel)
95 , _channels()
96 , _channelsLabel(channelsLabel)
97 {
98 _channels.resize(count);
99 for (int i = 0; i < count; ++i) {
100 _channels[i] = channels[i];
101 }
102
103 if (planeLabel.empty()) {
104 // Plane label is the ID if empty
105 _planeLabel = _planeID;
106 }
107 if ( channelsLabel.empty() ) {
108 // Channels label is the concatenation of all channels
109 for (std::size_t i = 0; i < _channels.size(); ++i) {
110 _channelsLabel.append(channels[i]);
111 }
112 }
113 }
114
ImagePlaneDesc(const ImagePlaneDesc & other)115 ImagePlaneDesc::ImagePlaneDesc(const ImagePlaneDesc& other)
116 {
117 *this = other;
118 }
119
120 ImagePlaneDesc&
operator =(const ImagePlaneDesc & other)121 ImagePlaneDesc::operator=(const ImagePlaneDesc& other)
122 {
123 _planeID = other._planeID;
124 _planeLabel = other._planeLabel;
125 _channels = other._channels;
126 _channelsLabel = other._channelsLabel;
127 return *this;
128 }
129
~ImagePlaneDesc()130 ImagePlaneDesc::~ImagePlaneDesc()
131 {
132 }
133
134 bool
isColorPlane(const std::string & planeID)135 ImagePlaneDesc::isColorPlane(const std::string& planeID)
136 {
137 return planeID == kOfxMultiplaneColorPlaneID;
138 }
139
140 bool
isColorPlane() const141 ImagePlaneDesc::isColorPlane() const
142 {
143 return ImagePlaneDesc::isColorPlane(_planeID);
144 }
145
146
147
148 bool
operator ==(const ImagePlaneDesc & other) const149 ImagePlaneDesc::operator==(const ImagePlaneDesc& other) const
150 {
151 if ( _channels.size() != other._channels.size() ) {
152 return false;
153 }
154 return _planeID == other._planeID;
155 }
156
157 bool
operator <(const ImagePlaneDesc & other) const158 ImagePlaneDesc::operator<(const ImagePlaneDesc& other) const
159 {
160 return _planeID < other._planeID;
161 }
162
163 int
getNumComponents() const164 ImagePlaneDesc::getNumComponents() const
165 {
166 return (int)_channels.size();
167 }
168
169 const std::string&
getPlaneID() const170 ImagePlaneDesc::getPlaneID() const
171 {
172 return _planeID;
173 }
174
175 const std::string&
getPlaneLabel() const176 ImagePlaneDesc::getPlaneLabel() const
177 {
178 return _planeLabel;
179 }
180
181 const std::string&
getChannelsLabel() const182 ImagePlaneDesc::getChannelsLabel() const
183 {
184 return _channelsLabel;
185 }
186
187 const std::vector<std::string>&
getChannels() const188 ImagePlaneDesc::getChannels() const
189 {
190 return _channels;
191 }
192
193 const ImagePlaneDesc&
getNoneComponents()194 ImagePlaneDesc::getNoneComponents()
195 {
196 static const ImagePlaneDesc comp;
197 return comp;
198 }
199
200 const ImagePlaneDesc&
getRGBAComponents()201 ImagePlaneDesc::getRGBAComponents()
202 {
203 static const ImagePlaneDesc comp(kOfxMultiplaneColorPlaneID, kOfxMultiplaneColorPlaneLabel, "", rgbaComps, 4);
204
205 return comp;
206 }
207
208 const ImagePlaneDesc&
getRGBComponents()209 ImagePlaneDesc::getRGBComponents()
210 {
211 static const ImagePlaneDesc comp(kOfxMultiplaneColorPlaneID, kOfxMultiplaneColorPlaneLabel, "", rgbComps, 3);
212
213 return comp;
214 }
215
216
217 const ImagePlaneDesc&
getXYComponents()218 ImagePlaneDesc::getXYComponents()
219 {
220 static const ImagePlaneDesc comp(kOfxMultiplaneColorPlaneID, kOfxMultiplaneColorPlaneLabel, "XY", xyComps, 2);
221
222 return comp;
223 }
224
225 const ImagePlaneDesc&
getAlphaComponents()226 ImagePlaneDesc::getAlphaComponents()
227 {
228 static const ImagePlaneDesc comp(kOfxMultiplaneColorPlaneID, kOfxMultiplaneColorPlaneLabel, "Alpha", alphaComps, 1);
229
230 return comp;
231 }
232
233 const ImagePlaneDesc&
getBackwardMotionComponents()234 ImagePlaneDesc::getBackwardMotionComponents()
235 {
236 static const ImagePlaneDesc comp(kOfxMultiplaneBackwardMotionVectorsPlaneID, kOfxMultiplaneBackwardMotionVectorsPlaneLabel, kOfxMultiplaneMotionComponentsLabel, motionComps, 2);
237
238 return comp;
239 }
240
241 const ImagePlaneDesc&
getForwardMotionComponents()242 ImagePlaneDesc::getForwardMotionComponents()
243 {
244 static const ImagePlaneDesc comp(kOfxMultiplaneForwardMotionVectorsPlaneID, kOfxMultiplaneForwardMotionVectorsPlaneLabel, kOfxMultiplaneMotionComponentsLabel, motionComps, 2);
245
246 return comp;
247 }
248
249 const ImagePlaneDesc&
getDisparityLeftComponents()250 ImagePlaneDesc::getDisparityLeftComponents()
251 {
252 static const ImagePlaneDesc comp(kOfxMultiplaneDisparityLeftPlaneID, kOfxMultiplaneDisparityLeftPlaneLabel, kOfxMultiplaneDisparityComponentsLabel, disparityComps, 2);
253
254 return comp;
255 }
256
257 const ImagePlaneDesc&
getDisparityRightComponents()258 ImagePlaneDesc::getDisparityRightComponents()
259 {
260 static const ImagePlaneDesc comp(kOfxMultiplaneDisparityRightPlaneID, kOfxMultiplaneDisparityRightPlaneLabel, kOfxMultiplaneDisparityComponentsLabel, disparityComps, 2);
261
262 return comp;
263 }
264
265
266 void
getChannelOption(int channelIndex,std::string * optionID,std::string * optionLabel) const267 ImagePlaneDesc::getChannelOption(int channelIndex, std::string* optionID, std::string* optionLabel) const
268 {
269 if (channelIndex < 0 || channelIndex >= (int)_channels.size()) {
270 assert(false);
271 return;
272 }
273
274 *optionLabel += _planeLabel;
275 *optionID += _planeID;
276 if ( !optionLabel->empty() ) {
277 *optionLabel += '.';
278 }
279 if (!optionID->empty()) {
280 *optionID += '.';
281 }
282
283 // For the option label, append the name of the channel
284 *optionLabel += _channels[channelIndex];
285 *optionID += _channels[channelIndex];
286 }
287
288 void
getPlaneOption(std::string * optionID,std::string * optionLabel) const289 ImagePlaneDesc::getPlaneOption(std::string* optionID, std::string* optionLabel) const
290 {
291 // The option ID is always the name of the layer, this ensures for the Color plane that even if the components type changes, the choice stays
292 // the same in the parameter.
293 *optionLabel = _planeLabel + "." + _channelsLabel;
294 *optionID = _planeID;
295 }
296
297 const ImagePlaneDesc&
mapNCompsToColorPlane(int nComps)298 ImagePlaneDesc::mapNCompsToColorPlane(int nComps)
299 {
300 switch (nComps) {
301 case 1:
302 return ImagePlaneDesc::getAlphaComponents();
303 case 2:
304 return ImagePlaneDesc::getXYComponents();
305 case 3:
306 return ImagePlaneDesc::getRGBComponents();
307 case 4:
308 return ImagePlaneDesc::getRGBAComponents();
309 default:
310 return ImagePlaneDesc::getNoneComponents();
311 }
312 }
313
314 static ImagePlaneDesc
ofxCustomCompToNatronComp(const std::string & comp)315 ofxCustomCompToNatronComp(const std::string& comp)
316 {
317 std::string planeID, planeLabel, channelsLabel;
318 std::vector<std::string> channels;
319 if (!extractCustomPlane(comp, &planeID, &planeLabel, &channelsLabel, &channels)) {
320 return ImagePlaneDesc::getNoneComponents();
321 }
322
323 return ImagePlaneDesc(planeID, planeLabel, channelsLabel, channels);
324 }
325
326 ImagePlaneDesc
mapOFXPlaneStringToPlane(const std::string & ofxPlane)327 ImagePlaneDesc::mapOFXPlaneStringToPlane(const std::string& ofxPlane)
328 {
329 assert(ofxPlane != kFnOfxImagePlaneColour);
330 if (ofxPlane == kFnOfxImagePlaneBackwardMotionVector) {
331 return ImagePlaneDesc::getBackwardMotionComponents();
332 } else if (ofxPlane == kFnOfxImagePlaneForwardMotionVector) {
333 return ImagePlaneDesc::getForwardMotionComponents();
334 } else if (ofxPlane == kFnOfxImagePlaneStereoDisparityLeft) {
335 return ImagePlaneDesc::getDisparityLeftComponents();
336 } else if (ofxPlane == kFnOfxImagePlaneStereoDisparityRight) {
337 return ImagePlaneDesc::getDisparityRightComponents();
338 } else {
339 return ofxCustomCompToNatronComp(ofxPlane);
340 }
341 }
342
343 void
mapOFXComponentsTypeStringToPlanes(const std::string & ofxComponents,ImagePlaneDesc * plane,ImagePlaneDesc * pairedPlane)344 ImagePlaneDesc::mapOFXComponentsTypeStringToPlanes(const std::string& ofxComponents, ImagePlaneDesc* plane, ImagePlaneDesc* pairedPlane)
345 {
346 if (ofxComponents == kOfxImageComponentRGBA) {
347 *plane = ImagePlaneDesc::getRGBAComponents();
348 } else if (ofxComponents == kOfxImageComponentAlpha) {
349 *plane = ImagePlaneDesc::getAlphaComponents();
350 } else if (ofxComponents == kOfxImageComponentRGB) {
351 *plane = ImagePlaneDesc::getRGBComponents();
352 }else if (ofxComponents == kNatronOfxImageComponentXY) {
353 *plane = ImagePlaneDesc::getXYComponents();
354 } else if (ofxComponents == kOfxImageComponentNone) {
355 *plane = ImagePlaneDesc::getNoneComponents();
356 } else if (ofxComponents == kFnOfxImageComponentMotionVectors) {
357 *plane = ImagePlaneDesc::getBackwardMotionComponents();
358 *pairedPlane = ImagePlaneDesc::getForwardMotionComponents();
359 } else if (ofxComponents == kFnOfxImageComponentStereoDisparity) {
360 *plane = ImagePlaneDesc::getDisparityLeftComponents();
361 *pairedPlane = ImagePlaneDesc::getDisparityRightComponents();
362 } else {
363 *plane = ofxCustomCompToNatronComp(ofxComponents);
364 }
365
366 } // mapOFXComponentsTypeStringToPlanes
367
368
369 static std::string
natronCustomCompToOfxComp(const ImagePlaneDesc & comp)370 natronCustomCompToOfxComp(const ImagePlaneDesc &comp)
371 {
372 std::stringstream ss;
373 const std::vector<std::string>& channels = comp.getChannels();
374 const std::string& planeID = comp.getPlaneID();
375 const std::string& planeLabel = comp.getPlaneLabel();
376 const std::string& channelsLabel = comp.getChannelsLabel();
377 ss << kNatronOfxImageComponentsPlaneName << planeID;
378 if (!planeLabel.empty()) {
379 ss << kNatronOfxImageComponentsPlaneLabel << planeLabel;
380 }
381 if (!channelsLabel.empty()) {
382 ss << kNatronOfxImageComponentsPlaneChannelsLabel << channelsLabel;
383 }
384 for (std::size_t i = 0; i < channels.size(); ++i) {
385 ss << kNatronOfxImageComponentsPlaneChannel << channels[i];
386 }
387
388 return ss.str();
389 } // natronCustomCompToOfxComp
390
391
392 std::string
mapPlaneToOFXPlaneString(const ImagePlaneDesc & plane)393 ImagePlaneDesc::mapPlaneToOFXPlaneString(const ImagePlaneDesc& plane)
394 {
395 if (plane.isColorPlane()) {
396 return kFnOfxImagePlaneColour;
397 } else if ( plane == ImagePlaneDesc::getBackwardMotionComponents() ) {
398 return kFnOfxImagePlaneBackwardMotionVector;
399 } else if ( plane == ImagePlaneDesc::getForwardMotionComponents()) {
400 return kFnOfxImagePlaneForwardMotionVector;
401 } else if ( plane == ImagePlaneDesc::getDisparityLeftComponents()) {
402 return kFnOfxImagePlaneStereoDisparityLeft;
403 } else if ( plane == ImagePlaneDesc::getDisparityRightComponents() ) {
404 return kFnOfxImagePlaneStereoDisparityRight;
405 } else {
406 return natronCustomCompToOfxComp(plane);
407 }
408
409 }
410
411 std::string
mapPlaneToOFXComponentsTypeString(const ImagePlaneDesc & plane)412 ImagePlaneDesc::mapPlaneToOFXComponentsTypeString(const ImagePlaneDesc& plane)
413 {
414 if ( plane == ImagePlaneDesc::getNoneComponents() ) {
415 return kOfxImageComponentNone;
416 } else if ( plane == ImagePlaneDesc::getAlphaComponents() ) {
417 return kOfxImageComponentAlpha;
418 } else if ( plane == ImagePlaneDesc::getRGBComponents() ) {
419 return kOfxImageComponentRGB;
420 } else if ( plane == ImagePlaneDesc::getRGBAComponents() ) {
421 return kOfxImageComponentRGBA;
422 } else if ( plane == ImagePlaneDesc::getXYComponents() ) {
423 return kNatronOfxImageComponentXY;
424 } else if ( plane == ImagePlaneDesc::getBackwardMotionComponents() ||
425 plane == ImagePlaneDesc::getForwardMotionComponents()) {
426 return kFnOfxImageComponentMotionVectors;
427 } else if ( plane == ImagePlaneDesc::getDisparityLeftComponents() ||
428 plane == ImagePlaneDesc::getDisparityRightComponents()) {
429 return kFnOfxImageComponentStereoDisparity;
430 } else {
431 return natronCustomCompToOfxComp(plane);
432 }
433 }
434
435 } // namespace MultiPlane
436 } // namespace OFX
437
438 namespace {
439
440 void
getHardCodedPlanes(bool onlyColorPlane,std::vector<const MultiPlane::ImagePlaneDesc * > * planesToAdd)441 getHardCodedPlanes(bool onlyColorPlane, std::vector<const MultiPlane::ImagePlaneDesc*>* planesToAdd)
442 {
443 const MultiPlane::ImagePlaneDesc& rgbaPlane = MultiPlane::ImagePlaneDesc::getRGBAComponents();
444 const MultiPlane::ImagePlaneDesc& disparityLeftPlane = MultiPlane::ImagePlaneDesc::getDisparityLeftComponents();
445 const MultiPlane::ImagePlaneDesc& disparityRightPlane = MultiPlane::ImagePlaneDesc::getDisparityRightComponents();
446 const MultiPlane::ImagePlaneDesc& motionBwPlane = MultiPlane::ImagePlaneDesc::getBackwardMotionComponents();
447 const MultiPlane::ImagePlaneDesc& motionFwPlane = MultiPlane::ImagePlaneDesc::getForwardMotionComponents();
448
449 planesToAdd->push_back(&rgbaPlane);
450 if (!onlyColorPlane) {
451 planesToAdd->push_back(&disparityLeftPlane);
452 planesToAdd->push_back(&disparityRightPlane);
453 planesToAdd->push_back(&motionBwPlane);
454 planesToAdd->push_back(&motionFwPlane);
455 }
456
457 }
458
459 struct ChoiceOption
460 {
461 string name, label, hint;
462
463 };
464
465 struct ChoiceOption_Compare
466 {
operator ()__anond0baf4ef0111::ChoiceOption_Compare467 bool operator() (const ChoiceOption& lhs, const ChoiceOption& rhs) const
468 {
469 return lhs.name < rhs.name;
470 }
471 };
472
473 void
getHardCodedPlaneOptions(const vector<string> & clips,bool addConstants,bool onlyColorPlane,vector<ChoiceOption> * options)474 getHardCodedPlaneOptions(const vector<string>& clips,
475 bool addConstants,
476 bool onlyColorPlane,
477 vector<ChoiceOption>* options)
478 {
479
480
481 std::vector<const MultiPlane::ImagePlaneDesc*> planesToAdd;
482 getHardCodedPlanes(onlyColorPlane, &planesToAdd);
483
484 for (std::size_t c = 0; c < clips.size(); ++c) {
485 const string& clipName = clips[c];
486
487 for (std::size_t p = 0; p < planesToAdd.size(); ++p) {
488 const std::string& planeLabel = planesToAdd[p]->getPlaneLabel();
489
490 const std::vector<std::string>& planeChannels = planesToAdd[p]->getChannels();
491
492 for (std::size_t i = 0; i < planeChannels.size(); ++i) {
493 ChoiceOption option;
494
495 // Prefix the clip name if there are multiple clip channels to read from
496 if (clips.size() > 1) {
497 option.name.append(clipName);
498 option.name.push_back('.');
499 option.label.append(clipName);
500 option.label.push_back('.');
501 }
502
503 // Prefix the plane name
504 option.name.append(planesToAdd[p]->getPlaneID());
505 option.name.push_back('.');
506 option.label.append(planeLabel);
507 option.label.push_back('.');
508
509
510 option.name.append(planeChannels[i]);
511 option.label.append(planeChannels[i]);
512
513 // Make up some tooltip
514 option.hint.append(planeChannels[i]);
515 option.hint.append(" channel from input ");
516 option.hint.append(clipName);
517 options->push_back(option);
518
519 }
520 }
521
522 if ( addConstants && (c == 0) ) {
523 {
524 string opt, hint;
525 opt.append(kMultiPlaneChannelParamOption0);
526 hint.append(kMultiPlaneChannelParamOption0Hint);
527
528 ChoiceOption choice = {opt, opt, hint};
529 options->push_back(choice);
530 }
531 {
532 string opt, hint;
533 opt.append(kMultiPlaneChannelParamOption1);
534 hint.append(kMultiPlaneChannelParamOption1Hint);
535
536 ChoiceOption choice = {opt, opt, hint};
537 options->push_back(choice);
538 }
539 }
540 }
541
542 } // getHardCodedPlanes
543
544 template <typename T>
545 void
addInputChannelOptionsRGBAInternal(T * param,const vector<string> & clips,bool addConstants,bool onlyColorPlane,vector<ChoiceOption> * optionsParam)546 addInputChannelOptionsRGBAInternal(T* param,
547 const vector<string>& clips,
548 bool addConstants,
549 bool onlyColorPlane,
550 vector<ChoiceOption>* optionsParam)
551 {
552 vector<ChoiceOption> options;
553 getHardCodedPlaneOptions(clips, addConstants, onlyColorPlane, &options);
554 if (optionsParam) {
555 *optionsParam = options;
556 }
557
558 if (param) {
559 for (std::size_t i = 0; i < options.size(); ++i) {
560 param->appendOption(options[i].label, options[i].hint, options[i].name);
561 }
562 }
563 } // addInputChannelOptionsRGBAInternal
564
565
566 } // anonymous namespace
567
568 namespace OFX {
569 namespace MultiPlane {
570
571 namespace Factory {
572 void
addInputChannelOptionsRGBA(ChoiceParamDescriptor * param,const vector<string> & clips,bool addConstants,bool onlyColorPlane)573 addInputChannelOptionsRGBA(ChoiceParamDescriptor* param,
574 const vector<string>& clips,
575 bool addConstants,
576 bool onlyColorPlane)
577 {
578 addInputChannelOptionsRGBAInternal<ChoiceParamDescriptor>(param, clips, addConstants, onlyColorPlane, 0);
579 }
580
581 void
addInputChannelOptionsRGBA(const vector<string> & clips,bool addConstants,bool onlyColorPlane)582 addInputChannelOptionsRGBA(const vector<string>& clips,
583 bool addConstants,
584 bool onlyColorPlane)
585 {
586 addInputChannelOptionsRGBAInternal<ChoiceParam>(0, clips, addConstants, onlyColorPlane, 0);
587 }
588 } // factory
589
590
591 /**
592 * @brief For each choice param, the list of clips it "depends on" (that is the clip available planes that will be visible in the choice)
593 **/
594 struct ChoiceParamClips
595 {
596 // The choice parameter containing the planes or channels.
597 ChoiceParam* param;
598
599 // True if the menu should contain any entry for each channel of each plane
600 bool splitPlanesIntoChannels;
601
602 // True if we should add a "None" option
603 bool addNoneOption;
604
605 // True if we should add 0 and 1 options
606 bool addConstantOptions;
607
608 bool isOutput;
609
610 bool hideIfClipDisconnected;
611
612 vector<Clip*> clips;
613 vector<string> clipNames;
614
ChoiceParamClipsOFX::MultiPlane::ChoiceParamClips615 ChoiceParamClips()
616 : param(NULL)
617 , splitPlanesIntoChannels(false)
618 , addNoneOption(false)
619 , addConstantOptions(false)
620 , isOutput(false)
621 , hideIfClipDisconnected(false)
622 , clips()
623 , clipNames()
624
625 {
626 }
627 };
628
629
630
631 struct MultiPlaneEffectPrivate
632 {
633 // Pointer to the public interface
634 MultiPlaneEffect* _publicInterface;
635
636 // A map of each dynamic choice parameters containing planes/channels
637 map<string, ChoiceParamClips> params;
638
639 // If true, all planes have to be processed
640 BooleanParam* allPlanesCheckbox;
641
642 Clip* dstClip;
643
644 // Stores for each clip its available planes
645 // This is to avoid a recursion when calling getPlanesPresent
646 // on the output clip.
647 std::map<Clip*, std::list<ImagePlaneDesc> > perClipPlanesAvailable;
648
MultiPlaneEffectPrivateOFX::MultiPlane::MultiPlaneEffectPrivate649 MultiPlaneEffectPrivate(MultiPlaneEffect* publicInterface)
650 : _publicInterface(publicInterface)
651 , params()
652 , allPlanesCheckbox(NULL)
653 , dstClip(publicInterface->fetchClip(kOfxImageEffectOutputClipName))
654 , perClipPlanesAvailable()
655 {
656 }
657
658 /**
659 * @brief The instanceChanged handler for the "All Planes" checkbox if the parameter was defined with
660 **/
661 void handleAllPlanesCheckboxParamChanged();
662
663 /**
664 * @brief To be called in createInstance and clipChanged to refresh visibility of input channel/plane selectors.
665 **/
666 void refreshSelectorsVisibility();
667
668
669 /**
670 * @brief Rebuild all choice parameters depending on the clips planes present.
671 * This function is supposed to be called in the clipChanged action on the output clip.
672 **/
673 void buildChannelMenus();
674 };
675
MultiPlaneEffect(OfxImageEffectHandle handle)676 MultiPlaneEffect::MultiPlaneEffect(OfxImageEffectHandle handle)
677 : ImageEffect(handle)
678 , _imp( new MultiPlaneEffectPrivate(this) )
679 {
680 }
681
~MultiPlaneEffect()682 MultiPlaneEffect::~MultiPlaneEffect()
683 {
684 }
685
686 void
fetchDynamicMultiplaneChoiceParameter(const string & paramName,const FetchChoiceParamOptions & args)687 MultiPlaneEffect::fetchDynamicMultiplaneChoiceParameter(const string& paramName,
688 const FetchChoiceParamOptions& args)
689 {
690 ChoiceParamClips& paramData = _imp->params[paramName];
691
692 paramData.param = fetchChoiceParam(paramName);
693 paramData.splitPlanesIntoChannels = args.splitPlanesIntoChannelOptions;
694 paramData.addNoneOption = args.addNoneOption;
695 paramData.addConstantOptions = args.addConstantOptions;
696 paramData.clips = args.dependsClips;
697
698 for (std::size_t i = 0; i < args.dependsClips.size(); ++i) {
699 // A choice menu cannot depend on the planes present on an output clip, since we actually may need the value
700 // of the choice to return the planes present in output!
701 assert(args.dependsClips[i]->name() != kOfxImageEffectOutputClipName);
702 paramData.clipNames.push_back( args.dependsClips[i]->name() );
703 }
704
705 paramData.isOutput = args.isOutputPlaneChoice;
706 paramData.hideIfClipDisconnected = args.hideIfClipDisconnected;
707
708 if (args.isOutputPlaneChoice && !_imp->allPlanesCheckbox && paramExists(kMultiPlaneProcessAllPlanesParam)) {
709 _imp->allPlanesCheckbox = fetchBooleanParam(kMultiPlaneProcessAllPlanesParam);
710 }
711
712 if (_imp->allPlanesCheckbox) {
713 bool allPlanesSelected = _imp->allPlanesCheckbox->getValue();
714 paramData.param->setIsSecretAndDisabled(allPlanesSelected);
715 }
716
717 }
718
719
720
721 void
buildChannelMenus()722 MultiPlaneEffectPrivate::buildChannelMenus()
723 {
724 perClipPlanesAvailable.clear();
725
726 // If no dynamic choices support, only add built-in planes.
727 if (!gHostSupportsDynamicChoices) {
728 vector<const MultiPlane::ImagePlaneDesc*> planesToAdd;
729 getHardCodedPlanes(!gHostSupportsMultiPlaneV1, &planesToAdd);
730
731 for (map<string, ChoiceParamClips>::iterator it = params.begin(); it != params.end(); ++it) {
732 for (std::size_t c = 0; c < it->second.clips.size(); ++c) {
733
734 // For the output plane selector, map the clip planes against the output clip even though the user provided a
735 // source clip as pass-through clip
736 Clip* clip = it->second.isOutput ? dstClip : it->second.clips[c];
737 map<Clip*, std::list<ImagePlaneDesc> >::iterator foundClip = perClipPlanesAvailable.find(clip);
738 if (foundClip != perClipPlanesAvailable.end()) {
739 continue;
740 } else {
741 std::list<ImagePlaneDesc>& clipPlanes = perClipPlanesAvailable[clip];
742 for (vector<const MultiPlane::ImagePlaneDesc*>::const_iterator it2 = planesToAdd.begin(); it2 != planesToAdd.end(); ++it2) {
743 clipPlanes.push_back(**it2);
744 }
745 }
746 }
747 }
748 return;
749 }
750
751 // This code requires dynamic choice parameters support.
752
753
754 // For each parameter to refresh
755 std::vector<std::pair<ChoiceParam*,std::vector<ChoiceOption> > > perParamOptions;
756 for (map<string, ChoiceParamClips>::iterator it = params.begin(); it != params.end(); ++it) {
757
758 vector<ChoiceOption> options;
759 set<ChoiceOption, ChoiceOption_Compare> optionsSorted;
760
761 if (it->second.splitPlanesIntoChannels) {
762 // Add built-in hard-coded options A.R, A.G, ... 0, 1, B.R, B.G ...
763 getHardCodedPlaneOptions(it->second.clipNames, it->second.addConstantOptions, true /*onlyColorPlane*/, &options);
764 optionsSorted.insert(options.begin(), options.end());
765 } else {
766 // For plane selectors, we might want a "None" option to select an input plane.
767 if (it->second.addNoneOption) {
768 ChoiceOption opt = {kMultiPlanePlaneParamOptionNone, kMultiPlanePlaneParamOptionNoneLabel, ""};
769 options.push_back(opt);
770 optionsSorted.insert(opt);
771 }
772 }
773
774 // We don't use a map here to keep the clips in the order of what the user passed them in fetchDynamicMultiplaneChoiceParameter
775 std::list<std::pair<Clip*, std::list<ImagePlaneDesc>* > > perClipPlanes;
776 for (std::size_t c = 0; c < it->second.clips.size(); ++c) {
777
778 Clip* clip = it->second.clips[c];
779
780 // Did we fetch the clip available planes already ? This speeds it up in the case where we have multiple choice parameters
781 // accessing the same clip.
782 std::list<ImagePlaneDesc>* availableClipPlanes = 0;
783
784 // For the output plane selector, map the clip planes against the output clip even though the user provided a
785 // source clip as pass-through clip so the extraneous planes returned by getExtraneousPlanesCreated are not added for
786 // the available planes on the source clip
787 Clip* clipToMap = it->second.isOutput ? dstClip : clip;
788
789 map<Clip*, std::list<ImagePlaneDesc> >::iterator foundClip = perClipPlanesAvailable.find(clipToMap);
790 if (foundClip != perClipPlanesAvailable.end()) {
791 availableClipPlanes = &foundClip->second;
792 } else {
793
794 availableClipPlanes = &(perClipPlanesAvailable)[clipToMap];
795
796 // Fetch planes presents from the clip and map them to ImagePlaneDesc
797 // Note that the clip cannot bethe output clip: the host may call recursively the getClipComponents() action during the call to getPlanesPresent()
798 // to find out the components present in output of this effect.
799 //
800 // Instead the plug-in should read planes from the pass-through clip (the same that is set in the implementation of getClipComponents)
801 // to populate the output menu.
802 vector<string> clipPlaneStrings;
803 clip->getPlanesPresent(&clipPlaneStrings);
804
805 // If this is the output menu, add user created planes from a user interface
806 if (it->second.isOutput) {
807 vector<string> extraPlanes;
808 _publicInterface->getExtraneousPlanesCreated(&extraPlanes);
809 clipPlaneStrings.insert(clipPlaneStrings.end(), extraPlanes.begin(), extraPlanes.end());
810 }
811
812 for (std::size_t i = 0; i < clipPlaneStrings.size(); ++i) {
813 ImagePlaneDesc plane;
814 if (clipPlaneStrings[i] == kOfxMultiplaneColorPlaneID) {
815 plane = ImagePlaneDesc::mapNCompsToColorPlane(clip->getPixelComponentCount());
816 } else {
817 plane = ImagePlaneDesc::mapOFXPlaneStringToPlane(clipPlaneStrings[i]);
818 }
819 availableClipPlanes->push_back(plane);
820 }
821
822 }
823
824 perClipPlanes.push_back(std::make_pair(clip, availableClipPlanes));
825 } // for each clip
826
827 for (std::list<std::pair<Clip*, std::list<ImagePlaneDesc>* > >::const_iterator it2 = perClipPlanes.begin(); it2 != perClipPlanes.end(); ++it2) {
828
829 const std::list<ImagePlaneDesc>* planes = it2->second;
830
831 for (std::list<ImagePlaneDesc>::const_iterator it3 = planes->begin(); it3 != planes->end(); ++it3) {
832 if (it->second.splitPlanesIntoChannels) {
833 // User wants per-channel options
834 int nChannels = it3->getNumComponents();
835 for (int k = 0; k < nChannels; ++k) {
836
837 ChoiceOption opt;
838 it3->getChannelOption(k, &opt.name, &opt.label);
839
840 // Prefix the clip name if there are multiple clip channels to read from
841 if (it->second.clips.size() > 1) {
842 opt.name = it2->first->name() + '.' + opt.name;
843 opt.label = it2->first->name() + '.' + opt.label;
844 }
845
846 if (optionsSorted.find(opt) == optionsSorted.end()) {
847 options.push_back(opt);
848 optionsSorted.insert(opt);
849 }
850
851 }
852 } else {
853 // User wants planes in options
854 ChoiceOption opt;
855 it3->getPlaneOption(&opt.name, &opt.label);
856
857 // Prefix the clip name if there are multiple clip channels to read from
858 if (it->second.clips.size() > 1) {
859 opt.name = it2->first->name() + '.' + opt.name;
860 opt.label = it2->first->name() + '.' + opt.label;
861 }
862 if (optionsSorted.find(opt) == optionsSorted.end()) {
863 options.push_back(opt);
864 optionsSorted.insert(opt);
865 }
866 }
867 } // for each plane
868
869 } // for each clip planes available
870
871 // Set the new choice menu
872 perParamOptions.push_back(std::make_pair(it->second.param,options));
873
874
875 } // for all choice parameters
876
877
878 // Reset all choice options in the same pass, once the perClipPlanesAvailable is full, because the resetOptions call may recursively call
879 // getClipComponents and thus getPlaneNeeded which relies on it.
880 for (std::vector<std::pair<ChoiceParam*,std::vector<ChoiceOption> > >::const_iterator it = perParamOptions.begin(); it != perParamOptions.end(); ++it) {
881 vector<string> labels(it->second.size()), hints(it->second.size()), enums(it->second.size());
882 for (std::size_t i = 0; i < it->second.size(); ++i) {
883 labels[i] = it->second[i].label;
884 hints[i] = it->second[i].hint;
885 enums[i] = it->second[i].name;
886 }
887 it->first->resetOptions(labels, hints, enums);
888 }
889 } // buildChannelMenus
890
891 void
handleAllPlanesCheckboxParamChanged()892 MultiPlaneEffectPrivate::handleAllPlanesCheckboxParamChanged()
893 {
894 bool allPlanesSelected = allPlanesCheckbox->getValue();
895 for (map<string, ChoiceParamClips>::const_iterator it = params.begin(); it != params.end(); ++it) {
896 if (!it->second.splitPlanesIntoChannels) {
897 it->second.param->setIsSecretAndDisabled(allPlanesSelected);
898 }
899 }
900 }
901
902 void
refreshSelectorsVisibility()903 MultiPlaneEffectPrivate::refreshSelectorsVisibility()
904 {
905 for (map<string, ChoiceParamClips>::iterator it = params.begin(); it != params.end(); ++it) {
906 if ( it->second.isOutput || !it->second.hideIfClipDisconnected) {
907 continue;
908 }
909 bool hasClipVisible = false;
910 for (std::size_t i = 0; i < it->second.clips.size(); ++i) {
911 if (it->second.clips[i]->isConnected()) {
912 hasClipVisible = true;
913 break;
914 }
915 }
916 it->second.param->setIsSecretAndDisabled(!hasClipVisible);
917 }
918 }
919
920 void
onAllParametersFetched()921 MultiPlaneEffect::onAllParametersFetched()
922 {
923 _imp->refreshSelectorsVisibility();
924 }
925
926 void
refreshPlaneChoiceMenus()927 MultiPlaneEffect::refreshPlaneChoiceMenus()
928 {
929 _imp->buildChannelMenus();
930 }
931
932 void
changedParam(const InstanceChangedArgs &,const std::string & paramName)933 MultiPlaneEffect::changedParam(const InstanceChangedArgs & /*args*/, const std::string ¶mName)
934 {
935 if (_imp->allPlanesCheckbox && paramName == _imp->allPlanesCheckbox->getName()) {
936 _imp->handleAllPlanesCheckboxParamChanged();
937 }
938 }
939
940 void
changedClip(const InstanceChangedArgs &,const std::string & clipName)941 MultiPlaneEffect::changedClip(const InstanceChangedArgs & /*args*/, const std::string &clipName)
942 {
943 _imp->refreshSelectorsVisibility();
944
945 if (gHostIsNatron3OrGreater && clipName == kOfxImageEffectOutputClipName) {
946 _imp->buildChannelMenus();
947 }
948 }
949
950 void
getClipPreferences(ClipPreferencesSetter & clipPreferences)951 MultiPlaneEffect::getClipPreferences(ClipPreferencesSetter &clipPreferences)
952 {
953 // Refresh the channel menus on Natron < 3 or if it has never been refreshed, otherwise this is done in clipChanged in Natron >= 3
954 if (!gHostIsNatron3OrGreater) {
955 _imp->buildChannelMenus();
956 }
957
958
959 // By default set the clip preferences according to what is selected with the output plane selector.
960 for (map<string, ChoiceParamClips>::iterator it = _imp->params.begin(); it != _imp->params.end(); ++it) {
961 if (!it->second.isOutput) {
962 continue;
963 }
964 MultiPlane::ImagePlaneDesc dstPlane;
965 {
966 OFX::Clip* clip = 0;
967 int channelIndex = -1;
968 MultiPlane::MultiPlaneEffect::GetPlaneNeededRetCodeEnum stat = getPlaneNeeded(it->second.param->getName(), &clip, &dstPlane, &channelIndex);
969 if (stat != MultiPlane::MultiPlaneEffect::eGetPlaneNeededRetCodeReturnedPlane) {
970 dstPlane = MultiPlane::ImagePlaneDesc::getNoneComponents();
971 }
972 }
973
974 // Get clip preferences expects a hard coded components string but here we may have any kind of plane.
975 // To respect OpenFX, we map our plane number of components to the components of the corresponding color plane
976 // e.g: if our plane is Toto.XYZ and has 3 channels, it becomes RGB
977 MultiPlane::ImagePlaneDesc colorPlaneMapped = MultiPlane::ImagePlaneDesc::mapNCompsToColorPlane(dstPlane.getNumComponents());
978 PixelComponentEnum dstPixelComps = mapStrToPixelComponentEnum(MultiPlane::ImagePlaneDesc::mapPlaneToOFXComponentsTypeString(colorPlaneMapped));
979
980 clipPreferences.setClipComponents(*it->second.clips[0], dstPixelComps);
981
982 }
983 } // getClipPreferences
984
985 OfxStatus
getClipComponents(const ClipComponentsArguments & args,ClipComponentsSetter & clipComponents)986 MultiPlaneEffect::getClipComponents(const ClipComponentsArguments& args, ClipComponentsSetter& clipComponents)
987 {
988
989 assert(gHostSupportsMultiPlaneV2 || gHostSupportsMultiPlaneV1);
990
991
992 // Record clips that have already had their planes set because multipla parameters can reference the same clip
993 std::map<Clip*, std::set<std::string> > clipMap;
994 bool passThroughClipSet = false;
995 for (map<string, ChoiceParamClips>::iterator it = _imp->params.begin(); it != _imp->params.end(); ++it) {
996 MultiPlane::ImagePlaneDesc plane;
997 OFX::Clip* clip = 0;
998 int channelIndex = -1;
999 MultiPlane::MultiPlaneEffect::GetPlaneNeededRetCodeEnum stat = getPlaneNeeded(it->second.param->getName(), &clip, &plane, &channelIndex);
1000 if (stat == MultiPlane::MultiPlaneEffect::eGetPlaneNeededRetCodeFailed) {
1001 return kOfxStatFailed;
1002 }
1003 if (stat == MultiPlane::MultiPlaneEffect::eGetPlaneNeededRetCodeReturnedConstant0 ||
1004 stat == MultiPlane::MultiPlaneEffect::eGetPlaneNeededRetCodeReturnedConstant1 ||
1005 stat == MultiPlane::MultiPlaneEffect::eGetPlaneNeededRetCodeReturnedAllPlanes) {
1006 continue;
1007 }
1008 std::set<std::string>& availablePlanes = clipMap[clip];
1009
1010 std::string ofxComponentsStr = MultiPlane::ImagePlaneDesc::mapPlaneToOFXPlaneString(plane);
1011 std::pair<std::set<std::string>::iterator, bool> ret = availablePlanes.insert(ofxComponentsStr);
1012 if (ret.second) {
1013 clipComponents.addClipPlane(*clip, ofxComponentsStr);
1014 }
1015
1016 // Set the pass-through clip to the first encountered source clip
1017 if (!passThroughClipSet && clip->name() != kOfxImageEffectOutputClipName) {
1018 passThroughClipSet = true;
1019 clipComponents.setPassThroughClip(clip, args.time, args.view);
1020 }
1021 }
1022 return kOfxStatOK;
1023 } // getClipComponents
1024
findBuiltInSelectedChannel(const std::string & selectedOptionID,bool compareWithID,const ChoiceParamClips & param,MultiPlaneEffect::GetPlaneNeededRetCodeEnum * retCode,OFX::Clip ** clip,ImagePlaneDesc * plane,int * channelIndexInPlane)1025 static bool findBuiltInSelectedChannel(const std::string& selectedOptionID,
1026 bool compareWithID,
1027 const ChoiceParamClips& param,
1028 MultiPlaneEffect::GetPlaneNeededRetCodeEnum* retCode,
1029 OFX::Clip** clip,
1030 ImagePlaneDesc* plane,
1031 int* channelIndexInPlane)
1032 {
1033 if (selectedOptionID == kMultiPlaneChannelParamOption0) {
1034 *retCode = MultiPlaneEffect::eGetPlaneNeededRetCodeReturnedConstant0;
1035 return true;
1036 }
1037
1038 if (selectedOptionID == kMultiPlaneChannelParamOption1) {
1039 *retCode = MultiPlaneEffect::eGetPlaneNeededRetCodeReturnedConstant1;
1040 return true;
1041 }
1042
1043 if (param.addNoneOption && selectedOptionID == kMultiPlanePlaneParamOptionNone) {
1044 *plane = ImagePlaneDesc::getNoneComponents();
1045 *retCode = MultiPlaneEffect::eGetPlaneNeededRetCodeReturnedPlane;
1046 return true;
1047 }
1048
1049 // The option must have a clip name prepended if there are multiple clips, find the clip
1050 std::string optionWithoutClipPrefix;
1051
1052 if (param.clips.size() == 1) {
1053 *clip = param.clips[0];
1054 optionWithoutClipPrefix = selectedOptionID;
1055 } else {
1056 for (std::size_t c = 0; c < param.clipNames.size(); ++c) {
1057 const std::string& clipName = param.clipNames[c];
1058 if (selectedOptionID.substr(0, clipName.size()) == clipName) {
1059 *clip = param.clips[c];
1060 optionWithoutClipPrefix = selectedOptionID.substr(clipName.size() + 1); // + 1 to skip the dot
1061 break;
1062 }
1063 }
1064 }
1065
1066 if (!*clip) {
1067 // We did not find the corresponding clip.
1068 *retCode = MultiPlaneEffect::eGetPlaneNeededRetCodeFailed;
1069 return false;
1070 }
1071
1072
1073 // Find a hard-coded option
1074
1075 std::vector<const MultiPlane::ImagePlaneDesc*> planesToAdd;
1076 getHardCodedPlanes(false /*onlyColorPlane*/, &planesToAdd);
1077 for (std::size_t p = 0; p < planesToAdd.size(); ++p) {
1078
1079 const vector<string>& planeChannels = planesToAdd[p]->getChannels();
1080 for (std::size_t c = 0; c < planeChannels.size(); ++c) {
1081 std::string channelOptionID;
1082
1083 if (compareWithID) {
1084 channelOptionID = planesToAdd[p]->getPlaneID() + '.' + planeChannels[c];
1085 } else {
1086 channelOptionID = planesToAdd[p]->getPlaneLabel() + '.' + planeChannels[c];
1087 }
1088
1089 if (channelOptionID == optionWithoutClipPrefix) {
1090
1091 int chanIndex = (int)c;
1092 // If the hard-coded plane is the color plane, the channel may not exist actually in the available components,
1093 // e.g: Alpha may be present in the choice but the components may be RGB
1094 // In this case, return 1 instead for Alpha and 0 for any other channel.
1095 if (planesToAdd[p]->isColorPlane()) {
1096 int clipComponentsCount = (*clip)->getPixelComponentCount();
1097
1098 // For the color plane, Color.A is channel index 0 when the plane is Color.Alpha
1099 if (clipComponentsCount == 1 && chanIndex == 3) {
1100 chanIndex = 0;
1101 }
1102 if ((int)chanIndex >= clipComponentsCount) {
1103 if (chanIndex == 3) {
1104 *retCode = MultiPlaneEffect::eGetPlaneNeededRetCodeReturnedConstant1;
1105 } else {
1106 *retCode = MultiPlaneEffect::eGetPlaneNeededRetCodeReturnedConstant0;
1107 }
1108 return true;
1109 }
1110 }
1111 *plane = *planesToAdd[p];
1112 *channelIndexInPlane = chanIndex;
1113 *retCode = MultiPlaneEffect::eGetPlaneNeededRetCodeReturnedChannelInPlane;
1114 return true;
1115 }
1116 }
1117
1118 } // for each built-in plane
1119
1120
1121
1122 return false;
1123 } // findBuiltInSelectedChannel
1124
1125 MultiPlaneEffect::GetPlaneNeededRetCodeEnum
getPlaneNeeded(const std::string & paramName,OFX::Clip ** clip,ImagePlaneDesc * plane,int * channelIndexInPlane)1126 MultiPlaneEffect::getPlaneNeeded(const std::string& paramName,
1127 OFX::Clip** clip,
1128 ImagePlaneDesc* plane,
1129 int* channelIndexInPlane)
1130 {
1131 map<string, ChoiceParamClips>::iterator found = _imp->params.find(paramName);
1132 assert( found != _imp->params.end() );
1133
1134 OFX::Clip* retClip = 0;
1135 ImagePlaneDesc retPlane;
1136 int retChannelIndexInPlane = -1;
1137
1138 if ( found == _imp->params.end() ) {
1139 if (clip) {
1140 *clip = retClip;
1141 }
1142 if (plane) {
1143 *plane = retPlane;
1144 }
1145 if (channelIndexInPlane) {
1146 *channelIndexInPlane = retChannelIndexInPlane;
1147 }
1148
1149 return eGetPlaneNeededRetCodeFailed;
1150 }
1151
1152
1153 if (found->second.isOutput && _imp->allPlanesCheckbox) {
1154 bool processAll = _imp->allPlanesCheckbox->getValue();
1155 if (processAll) {
1156 if (clip) {
1157 *clip = retClip;
1158 }
1159 if (plane) {
1160 *plane = retPlane;
1161 }
1162 if (channelIndexInPlane) {
1163 *channelIndexInPlane = retChannelIndexInPlane;
1164 }
1165
1166 return eGetPlaneNeededRetCodeReturnedAllPlanes;
1167 }
1168 }
1169
1170 // Get the selected option
1171 string selectedOptionID;
1172
1173 // By default compare option IDs, except if the host does not support it.
1174 bool compareWithID = true;
1175 {
1176 int choice_i;
1177 found->second.param->getValue(choice_i);
1178
1179 if ( (0 <= choice_i) && ( choice_i < found->second.param->getNOptions() ) ) {
1180 #ifdef OFX_EXTENSIONS_NATRON
1181 found->second.param->getEnum(choice_i, selectedOptionID);
1182 if (selectedOptionID.empty()) {
1183 #endif
1184 found->second.param->getOption(choice_i, selectedOptionID);
1185 compareWithID = false;
1186 #ifdef OFX_EXTENSIONS_NATRON
1187 }
1188 #endif
1189 } else {
1190 if (clip) {
1191 *clip = retClip;
1192 }
1193 if (plane) {
1194 *plane = retPlane;
1195 }
1196 if (channelIndexInPlane) {
1197 *channelIndexInPlane = retChannelIndexInPlane;
1198 }
1199
1200 return eGetPlaneNeededRetCodeFailed;
1201 }
1202 if ( selectedOptionID.empty() ) {
1203 if (clip) {
1204 *clip = retClip;
1205 }
1206 if (plane) {
1207 *plane = retPlane;
1208 }
1209 if (channelIndexInPlane) {
1210 *channelIndexInPlane = retChannelIndexInPlane;
1211 }
1212
1213 return eGetPlaneNeededRetCodeFailed;
1214 }
1215 }
1216
1217
1218 // If the choice is split by channels, check for hard coded options
1219 if (found->second.splitPlanesIntoChannels) {
1220 MultiPlaneEffect::GetPlaneNeededRetCodeEnum retCode;
1221 if (findBuiltInSelectedChannel(selectedOptionID, compareWithID, found->second, &retCode, &retClip, &retPlane, &retChannelIndexInPlane)) {
1222 if (clip) {
1223 *clip = retClip;
1224 }
1225 if (plane) {
1226 *plane = retPlane;
1227 }
1228 if (channelIndexInPlane) {
1229 *channelIndexInPlane = retChannelIndexInPlane;
1230 }
1231
1232 return retCode;
1233 }
1234 } else {
1235 if (found->second.addNoneOption && selectedOptionID == kMultiPlanePlaneParamOptionNone) {
1236 retPlane = ImagePlaneDesc::getNoneComponents();
1237 if (clip) {
1238 *clip = retClip;
1239 }
1240 if (plane) {
1241 *plane = retPlane;
1242 }
1243 if (channelIndexInPlane) {
1244 *channelIndexInPlane = retChannelIndexInPlane;
1245 }
1246
1247 return MultiPlaneEffect::eGetPlaneNeededRetCodeReturnedPlane;
1248 }
1249 } // found->second.splitPlanesIntoChannels
1250
1251
1252 // This is not a hard-coded option, check for dynamic planes
1253 // The option must have a clip name prepended if there are multiple clips, find the clip
1254 std::string optionWithoutClipPrefix;
1255 if (found->second.clips.size() == 1) {
1256 retClip = found->second.clips[0];
1257 optionWithoutClipPrefix = selectedOptionID;
1258 } else {
1259 for (std::size_t c = 0; c < found->second.clipNames.size(); ++c) {
1260 const std::string& clipName = found->second.clipNames[c];
1261 if (selectedOptionID.substr(0, clipName.size()) == clipName) {
1262 retClip = found->second.clips[c];
1263 optionWithoutClipPrefix = selectedOptionID.substr(clipName.size() + 1); // + 1 to skip the dot
1264 break;
1265 }
1266 }
1267 }
1268
1269 if (!retClip) {
1270 // We did not find the corresponding clip.
1271 if (clip) {
1272 *clip = retClip;
1273 }
1274 if (plane) {
1275 *plane = retPlane;
1276 }
1277 if (channelIndexInPlane) {
1278 *channelIndexInPlane = retChannelIndexInPlane;
1279 }
1280
1281 return MultiPlaneEffect::eGetPlaneNeededRetCodeFailed;
1282 }
1283
1284 // For the output plane selector, map the clip planes against the output clip even though the user provided a
1285 // source clip as pass-through clip so the extraneous planes returned by getExtraneousPlanesCreated are not added for
1286 // the available planes on the source clip
1287 retClip = found->second.isOutput ? _imp->dstClip : retClip;
1288
1289 std::map<Clip*, std::list<ImagePlaneDesc> >::iterator foundPlanesPresentForClip = _imp->perClipPlanesAvailable.find(retClip);
1290 if (foundPlanesPresentForClip == _imp->perClipPlanesAvailable.end()) {
1291 // No components available for this clip...
1292 if (clip) {
1293 *clip = retClip;
1294 }
1295 if (plane) {
1296 *plane = retPlane;
1297 }
1298 if (channelIndexInPlane) {
1299 *channelIndexInPlane = retChannelIndexInPlane;
1300 }
1301
1302 return MultiPlaneEffect::eGetPlaneNeededRetCodeFailed;
1303 }
1304
1305 for (std::list<ImagePlaneDesc>::const_iterator it = foundPlanesPresentForClip->second.begin(); it != foundPlanesPresentForClip->second.end(); ++it) {
1306 if (found->second.splitPlanesIntoChannels) {
1307 // User wants per-channel options
1308 int nChannels = it->getNumComponents();
1309 for (int k = 0; k < nChannels; ++k) {
1310 std::string optionID, optionLabel;
1311 it->getChannelOption(k, &optionID, &optionLabel);
1312
1313 bool foundPlane;
1314 if (compareWithID) {
1315 foundPlane = optionWithoutClipPrefix == optionID;
1316 } else {
1317 foundPlane = optionWithoutClipPrefix == optionLabel;
1318 }
1319 if (foundPlane) {
1320 retPlane = *it;
1321 retChannelIndexInPlane = k;
1322 if (clip) {
1323 *clip = retClip;
1324 }
1325 if (plane) {
1326 *plane = retPlane;
1327 }
1328 if (channelIndexInPlane) {
1329 *channelIndexInPlane = retChannelIndexInPlane;
1330 }
1331
1332 return eGetPlaneNeededRetCodeReturnedChannelInPlane;
1333 }
1334 }
1335 } else {
1336 // User wants planes in options
1337 std::string optionID, optionLabel;
1338 it->getPlaneOption(&optionID, &optionLabel);
1339 bool foundPlane;
1340 if (compareWithID) {
1341 foundPlane = optionWithoutClipPrefix == optionID;
1342 } else {
1343 foundPlane = optionWithoutClipPrefix == optionLabel;
1344 }
1345 if (foundPlane) {
1346 retPlane = *it;
1347 if (clip) {
1348 *clip = retClip;
1349 }
1350 if (plane) {
1351 *plane = retPlane;
1352 }
1353 if (channelIndexInPlane) {
1354 *channelIndexInPlane = retChannelIndexInPlane;
1355 }
1356
1357 return eGetPlaneNeededRetCodeReturnedPlane;
1358 }
1359 }
1360 } // for each plane available on this clip
1361
1362 if (clip) {
1363 *clip = retClip;
1364 }
1365 if (plane) {
1366 *plane = retPlane;
1367 }
1368 if (channelIndexInPlane) {
1369 *channelIndexInPlane = retChannelIndexInPlane;
1370 }
1371
1372 return eGetPlaneNeededRetCodeFailed;
1373 } // MultiPlaneEffect::getPlaneNeededForParam
1374
1375
refreshHostFlags()1376 static void refreshHostFlags()
1377 {
1378 gHostSupportsDynamicChoices = false;
1379 gHostIsNatron3OrGreater = false;
1380 gHostSupportsMultiPlaneV1 = false;
1381 gHostSupportsMultiPlaneV2 = false;
1382
1383 #ifdef OFX_EXTENSIONS_NATRON
1384 if (getImageEffectHostDescription()->supportsDynamicChoices) {
1385 gHostSupportsDynamicChoices = true;
1386 }
1387 if (getImageEffectHostDescription()->isNatron && getImageEffectHostDescription()->versionMajor >= 3) {
1388 gHostIsNatron3OrGreater = true;
1389 }
1390
1391 #endif
1392 #ifdef OFX_EXTENSIONS_NUKE
1393 if (getImageEffectHostDescription()->isMultiPlanar && fetchSuite(kFnOfxImageEffectPlaneSuite, 1)) {
1394 gHostSupportsMultiPlaneV1 = true;
1395 }
1396 if (getImageEffectHostDescription()->isMultiPlanar && gHostSupportsDynamicChoices && fetchSuite(kFnOfxImageEffectPlaneSuite, 2)) {
1397 gHostSupportsMultiPlaneV2 = true;
1398 }
1399 #endif
1400 }
1401
1402 namespace Factory {
1403 ChoiceParamDescriptor*
describeInContextAddPlaneChoice(ImageEffectDescriptor & desc,PageParamDescriptor * page,const std::string & name,const std::string & label,const std::string & hint)1404 describeInContextAddPlaneChoice(ImageEffectDescriptor &desc,
1405 PageParamDescriptor* page,
1406 const std::string& name,
1407 const std::string& label,
1408 const std::string& hint)
1409 {
1410
1411 refreshHostFlags();
1412 if (!gHostSupportsMultiPlaneV2 && !gHostSupportsMultiPlaneV1) {
1413 throw std::runtime_error("Hosts does not meet requirements");
1414 }
1415 ChoiceParamDescriptor *ret;
1416 {
1417 ChoiceParamDescriptor *param = desc.defineChoiceParam(name);
1418 param->setLabel(label);
1419 param->setHint(hint);
1420 if (!gHostSupportsMultiPlaneV2) {
1421 // Add hard-coded planes
1422 const MultiPlane::ImagePlaneDesc& rgbaPlane = MultiPlane::ImagePlaneDesc::getRGBAComponents();
1423 const MultiPlane::ImagePlaneDesc& disparityLeftPlane = MultiPlane::ImagePlaneDesc::getDisparityLeftComponents();
1424 const MultiPlane::ImagePlaneDesc& disparityRightPlane = MultiPlane::ImagePlaneDesc::getDisparityRightComponents();
1425 const MultiPlane::ImagePlaneDesc& motionBwPlane = MultiPlane::ImagePlaneDesc::getBackwardMotionComponents();
1426 const MultiPlane::ImagePlaneDesc& motionFwPlane = MultiPlane::ImagePlaneDesc::getForwardMotionComponents();
1427
1428 std::vector<const MultiPlane::ImagePlaneDesc*> planesToAdd;
1429 planesToAdd.push_back(&rgbaPlane);
1430 planesToAdd.push_back(&disparityLeftPlane);
1431 planesToAdd.push_back(&disparityRightPlane);
1432 planesToAdd.push_back(&motionBwPlane);
1433 planesToAdd.push_back(&motionFwPlane);
1434
1435 for (std::size_t i = 0; i < planesToAdd.size(); ++i) {
1436 std::string optionID, optionLabel;
1437 planesToAdd[i]->getPlaneOption(&optionID, &optionLabel);
1438 param->appendOption(optionLabel, "", optionID);
1439 }
1440
1441 }
1442 param->setDefault(0);
1443 param->setAnimates(false);
1444 desc.addClipPreferencesSlaveParam(*param); // < the menu is built in getClipPreferences
1445 if (page) {
1446 page->addChild(*param);
1447 }
1448 ret = param;
1449 }
1450
1451 return ret;
1452 }
1453
1454 OFX::BooleanParamDescriptor*
describeInContextAddAllPlanesOutputCheckbox(OFX::ImageEffectDescriptor & desc,OFX::PageParamDescriptor * page)1455 describeInContextAddAllPlanesOutputCheckbox(OFX::ImageEffectDescriptor &desc, OFX::PageParamDescriptor* page)
1456 {
1457 refreshHostFlags();
1458 if (!gHostSupportsMultiPlaneV2 && !gHostSupportsMultiPlaneV1) {
1459 throw std::runtime_error("Hosts does not meet requirements");
1460 }
1461 BooleanParamDescriptor* param = desc.defineBooleanParam(kMultiPlaneProcessAllPlanesParam);
1462 param->setLabel(kMultiPlaneProcessAllPlanesParamLabel);
1463 param->setHint(kMultiPlaneProcessAllPlanesParamHint);
1464 desc.addClipPreferencesSlaveParam(*param);
1465 param->setAnimates(false);
1466 if (page) {
1467 page->addChild(*param);
1468 }
1469 return param;
1470 }
1471
1472 ChoiceParamDescriptor*
describeInContextAddPlaneChannelChoice(ImageEffectDescriptor & desc,PageParamDescriptor * page,const vector<string> & clips,const string & name,const string & label,const string & hint,bool addConstants)1473 describeInContextAddPlaneChannelChoice(ImageEffectDescriptor &desc,
1474 PageParamDescriptor* page,
1475 const vector<string>& clips,
1476 const string& name,
1477 const string& label,
1478 const string& hint,
1479 bool addConstants)
1480
1481 {
1482
1483 refreshHostFlags();
1484 if (!gHostSupportsMultiPlaneV2 && !gHostSupportsMultiPlaneV1) {
1485 throw std::runtime_error("Hosts does not meet requirements");
1486 }
1487
1488 ChoiceParamDescriptor *ret;
1489 {
1490 ChoiceParamDescriptor *param = desc.defineChoiceParam(name);
1491 param->setLabel(label);
1492 desc.addClipPreferencesSlaveParam(*param);
1493 param->setHint(hint);
1494 param->setAnimates(false);
1495 addInputChannelOptionsRGBA(param, clips, addConstants /*addContants*/, gHostSupportsMultiPlaneV2 /*onlyColorPlane*/);
1496
1497 if (page) {
1498 page->addChild(*param);
1499 }
1500 ret = param;
1501 }
1502
1503 return ret;
1504 }
1505 } // Factory
1506 } // namespace MultiPlane
1507 } // namespace OFX
1508