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 &paramName)
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