1 /* ***** BEGIN LICENSE BLOCK *****
2  * This file is part of openfx-misc <https://github.com/devernay/openfx-misc>,
3  * Copyright (C) 2013-2018 INRIA
4  *
5  * openfx-misc is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * openfx-misc is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with openfx-misc.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
17  * ***** END LICENSE BLOCK ***** */
18 
19 /*
20  * OFX CopyRectangle plugin.
21  */
22 
23 #include <cmath>
24 #include <cfloat> // DBL_MAX
25 #include <algorithm>
26 
27 #include "ofxsProcessing.H"
28 #include "ofxsCoords.h"
29 #include "ofxsRectangleInteract.h"
30 #include "ofxsMaskMix.h"
31 #include "ofxsMacros.h"
32 #include "ofxsThreadSuite.h"
33 
34 using namespace OFX;
35 
36 OFXS_NAMESPACE_ANONYMOUS_ENTER
37 
38 #define kPluginName "CopyRectangleOFX"
39 #define kPluginGrouping "Merge"
40 #define kPluginDescription "Copies a rectangle from the input A to the input B in output.\n" \
41     "It can be used to limit an effect to a rectangle of the original image by plugging the original image into the input B.\n" \
42     "See also http://opticalenquiry.com/nuke/index.php?title=CopyRectange"
43 
44 #define kPluginIdentifier "net.sf.openfx.CopyRectanglePlugin"
45 // History:
46 // version 1.0: initial version
47 // version 2.0: use kNatronOfxParamProcess* parameters
48 #define kPluginVersionMajor 2 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
49 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
50 
51 #define kSupportsTiles 1
52 #define kSupportsMultiResolution 1
53 #define kSupportsRenderScale 1
54 #define kSupportsMultipleClipPARs false
55 #define kSupportsMultipleClipDepths false
56 #define kRenderThreadSafety eRenderFullySafe
57 
58 #define kClipA "A"
59 #define kClipAHint "The image from which the rectangle is copied."
60 #define kClipB "B"
61 #define kClipBHint "The image onto which the rectangle is copied."
62 
63 #ifdef OFX_EXTENSIONS_NATRON
64 #define kParamProcessR kNatronOfxParamProcessR
65 #define kParamProcessRLabel kNatronOfxParamProcessRLabel
66 #define kParamProcessRHint kNatronOfxParamProcessRHint
67 #define kParamProcessG kNatronOfxParamProcessG
68 #define kParamProcessGLabel kNatronOfxParamProcessGLabel
69 #define kParamProcessGHint kNatronOfxParamProcessGHint
70 #define kParamProcessB kNatronOfxParamProcessB
71 #define kParamProcessBLabel kNatronOfxParamProcessBLabel
72 #define kParamProcessBHint kNatronOfxParamProcessBHint
73 #define kParamProcessA kNatronOfxParamProcessA
74 #define kParamProcessALabel kNatronOfxParamProcessALabel
75 #define kParamProcessAHint kNatronOfxParamProcessAHint
76 #else
77 #define kParamProcessR      "processR"
78 #define kParamProcessRLabel "R"
79 #define kParamProcessRHint  "Process red component."
80 #define kParamProcessG      "processG"
81 #define kParamProcessGLabel "G"
82 #define kParamProcessGHint  "Process green component."
83 #define kParamProcessB      "processB"
84 #define kParamProcessBLabel "B"
85 #define kParamProcessBHint  "Process blue component."
86 #define kParamProcessA      "processA"
87 #define kParamProcessALabel "A"
88 #define kParamProcessAHint  "Process alpha component."
89 #endif
90 
91 #define kParamSoftness "softness"
92 #define kParamSoftnessLabel "Softness"
93 #define kParamSoftnessHint "Size of the fade around edges of the rectangle to apply"
94 
95 // Some hosts (e.g. Resolve) may not support normalized defaults (setDefaultCoordinateSystem(eCoordinatesNormalised))
96 #define kParamDefaultsNormalised "defaultsNormalised"
97 
98 static bool gHostSupportsDefaultCoordinateSystem = true; // for kParamDefaultsNormalised
99 
100 class CopyRectangleProcessorBase
101     : public ImageProcessor
102 {
103 protected:
104     const Image *_srcImgA;
105     const Image *_srcImgB;
106     const Image *_maskImg;
107     double _softness;
108     bool _process[4];
109     OfxRectI _rectangle;
110     bool _doMasking;
111     double _mix;
112     bool _maskInvert;
113 
114 public:
CopyRectangleProcessorBase(ImageEffect & instance)115     CopyRectangleProcessorBase(ImageEffect &instance)
116         : ImageProcessor(instance)
117         , _srcImgA(NULL)
118         , _srcImgB(NULL)
119         , _maskImg(NULL)
120         , _softness(0.)
121         , _doMasking(false)
122         , _mix(1.)
123         , _maskInvert(false)
124     {
125         _process[0] = _process[1] = _process[2] = _process[3] = false;
126         _rectangle.x1 = _rectangle.y1 = _rectangle.x2 = _rectangle.y2 = 0.f;
127     }
128 
129     /** @brief set the src image */
setSrcImgs(const Image * A,const Image * B)130     void setSrcImgs(const Image *A,
131                     const Image* B)
132     {
133         _srcImgA = A;
134         _srcImgB = B;
135     }
136 
setMaskImg(const Image * v,bool maskInvert)137     void setMaskImg(const Image *v,
138                     bool maskInvert) { _maskImg = v; _maskInvert = maskInvert; }
139 
doMasking(bool v)140     void doMasking(bool v) {_doMasking = v; }
141 
setValues(const OfxRectI & rectangle,double softness,bool processR,bool processG,bool processB,bool processA,double mix)142     void setValues(const OfxRectI& rectangle,
143                    double softness,
144                    bool processR,
145                    bool processG,
146                    bool processB,
147                    bool processA,
148                    double mix)
149     {
150         _rectangle = rectangle;
151         _softness = softness;
152         _process[0] = processR;
153         _process[1] = processG;
154         _process[2] = processB;
155         _process[3] = processA;
156         _mix = mix;
157     }
158 };
159 
160 
161 // The "masked", "filter" and "clamp" template parameters allow filter-specific optimization
162 // by the compiler, using the same generic code for all filters.
163 template <class PIX, int nComponents, int maxValue>
164 class CopyRectangleProcessor
165     : public CopyRectangleProcessorBase
166 {
167 public:
CopyRectangleProcessor(ImageEffect & instance)168     CopyRectangleProcessor(ImageEffect &instance)
169         : CopyRectangleProcessorBase(instance)
170     {
171     }
172 
173 private:
multiThreadProcessImages(OfxRectI procWindow)174     void multiThreadProcessImages(OfxRectI procWindow)
175     {
176         assert(nComponents == 1 || nComponents == 3 || nComponents == 4);
177         float yMultiplier, xMultiplier;
178         float tmpPix[nComponents];
179 
180         //assert(filter == _filter);
181         for (int y = procWindow.y1; y < procWindow.y2; ++y) {
182             if ( _effect.abort() ) {
183                 break;
184             }
185 
186             PIX *dstPix = (PIX *) _dstImg->getPixelAddress(procWindow.x1, y);
187 
188             // distance to the nearest rectangle area horizontal edge
189             int yDistance =  std::min(y - _rectangle.y1, _rectangle.y2 - 1 - y);
190 
191             // handle softness
192             bool yInRectangle = y >= _rectangle.y1 && y < _rectangle.y2;
193             if (yInRectangle) {
194                 ///apply softness only within the rectangle
195                 yMultiplier = yDistance < _softness ? yDistance / (float)_softness : 1.f;
196             } else {
197                 yMultiplier = 1.f;
198             }
199 
200             for (int x = procWindow.x1; x < procWindow.x2; ++x, dstPix += nComponents) {
201                 // distance to the nearest rectangle area vertical edge
202                 int xDistance =  std::min(x - _rectangle.x1, _rectangle.x2 - 1 - x);
203                 // handle softness
204                 bool xInRectangle = x >= _rectangle.x1 && x < _rectangle.x2;
205 
206                 if (xInRectangle) {
207                     ///apply softness only within the rectangle
208                     xMultiplier = xDistance < _softness ? xDistance / (float)_softness : 1.f;
209                 } else {
210                     xMultiplier = 1.f;
211                 }
212 
213                 const PIX *srcPixB = _srcImgB ? (const PIX*)_srcImgB->getPixelAddress(x, y) : NULL;
214 
215                 if (xInRectangle && yInRectangle) {
216                     const PIX *srcPixA = _srcImgA ? (const PIX*)_srcImgA->getPixelAddress(x, y) : NULL;
217                     float multiplier = xMultiplier * yMultiplier;
218 
219                     for (int k = 0; k < nComponents; ++k) {
220                         if (!_process[(nComponents) == 1 ? 3 : k]) {
221                             tmpPix[k] = srcPixB ? srcPixB[k] : 0.f;
222                         } else {
223                             PIX A = srcPixA ? srcPixA[k] : PIX();
224                             PIX B = srcPixB ? srcPixB[k] : PIX();
225                             tmpPix[k] = A *  multiplier + B * (1.f - multiplier);
226                         }
227                     }
228                     ofxsMaskMixPix<PIX, nComponents, maxValue, true>(tmpPix, x, y, srcPixB, _doMasking, _maskImg, (float)_mix, _maskInvert, dstPix);
229                 } else {
230                     for (int k = 0; k < nComponents; ++k) {
231                         dstPix[k] = srcPixB ? srcPixB[k] : PIX();
232                     }
233                 }
234             }
235         }
236     } // multiThreadProcessImages
237 };
238 
239 
240 ////////////////////////////////////////////////////////////////////////////////
241 /** @brief The plugin that does our work */
242 class CopyRectanglePlugin
243     : public ImageEffect
244 {
245 public:
246     /** @brief ctor */
CopyRectanglePlugin(OfxImageEffectHandle handle)247     CopyRectanglePlugin(OfxImageEffectHandle handle)
248         : ImageEffect(handle)
249         , _dstClip(NULL)
250         , _srcClipA(NULL)
251         , _srcClipB(NULL)
252         , _btmLeft(NULL)
253         , _size(NULL)
254         , _softness(NULL)
255         , _processR(NULL)
256         , _processG(NULL)
257         , _processB(NULL)
258         , _processA(NULL)
259     {
260         _dstClip = fetchClip(kOfxImageEffectOutputClipName);
261         assert( _dstClip && (!_dstClip->isConnected() || _dstClip->getPixelComponents() == ePixelComponentAlpha || _dstClip->getPixelComponents() == ePixelComponentRGB || _dstClip->getPixelComponents() == ePixelComponentRGBA) );
262         _srcClipA = fetchClip(kClipA);
263         assert( _srcClipA && (!_srcClipA->isConnected() || _srcClipA->getPixelComponents() == ePixelComponentAlpha || _srcClipA->getPixelComponents() == ePixelComponentRGB || _srcClipA->getPixelComponents() == ePixelComponentRGBA) );
264         _srcClipB = fetchClip(kClipB);
265         assert( _srcClipB && (!_srcClipA->isConnected() || _srcClipB->getPixelComponents() == ePixelComponentAlpha || _srcClipB->getPixelComponents() == ePixelComponentRGB || _srcClipB->getPixelComponents() == ePixelComponentRGBA) );
266         _maskClip = fetchClip(getContext() == eContextPaint ? "Brush" : "Mask");
267         assert(!_maskClip || !_maskClip->isConnected() || _maskClip->getPixelComponents() == ePixelComponentAlpha);
268 
269         _btmLeft = fetchDouble2DParam(kParamRectangleInteractBtmLeft);
270         _size = fetchDouble2DParam(kParamRectangleInteractSize);
271         _softness = fetchDoubleParam(kParamSoftness);
272         _processR = fetchBooleanParam(kParamProcessR);
273         _processG = fetchBooleanParam(kParamProcessG);
274         _processB = fetchBooleanParam(kParamProcessB);
275         _processA = fetchBooleanParam(kParamProcessA);
276         assert(_processR && _processG && _processB && _processA);
277         assert(_btmLeft && _size && _softness);
278         _mix = fetchDoubleParam(kParamMix);
279         _maskApply = ( ofxsMaskIsAlwaysConnected( OFX::getImageEffectHostDescription() ) && paramExists(kParamMaskApply) ) ? fetchBooleanParam(kParamMaskApply) : 0;
280         _maskInvert = fetchBooleanParam(kParamMaskInvert);
281         assert(_mix && _maskInvert);
282 
283         // honor kParamDefaultsNormalised
284         if ( paramExists(kParamDefaultsNormalised) ) {
285             // Some hosts (e.g. Resolve) may not support normalized defaults (setDefaultCoordinateSystem(eCoordinatesNormalised))
286             // handle these ourselves!
287             BooleanParam* param = fetchBooleanParam(kParamDefaultsNormalised);
288             assert(param);
289             bool normalised = param->getValue();
290             if (normalised) {
291                 OfxPointD size = getProjectExtent();
292                 OfxPointD origin = getProjectOffset();
293                 OfxPointD p;
294                 // we must denormalise all parameters for which setDefaultCoordinateSystem(eCoordinatesNormalised) couldn't be done
295                 beginEditBlock(kParamDefaultsNormalised);
296                 p = _btmLeft->getValue();
297                 _btmLeft->setValue(p.x * size.x + origin.x, p.y * size.y + origin.y);
298                 p = _size->getValue();
299                 _size->setValue(p.x * size.x, p.y * size.y);
300                 param->setValue(false);
301                 endEditBlock();
302             }
303         }
304     }
305 
306 private:
307     // override the roi call
308     virtual void getRegionsOfInterest(const RegionsOfInterestArguments &args, RegionOfInterestSetter &rois) OVERRIDE FINAL;
309     virtual bool getRegionOfDefinition(const RegionOfDefinitionArguments &args, OfxRectD &rod) OVERRIDE FINAL;
310 
311     /* Override the render */
312     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
313     virtual bool isIdentity(const IsIdentityArguments &args, Clip * &identityClip, double &identityTime, int& view, std::string& plane) OVERRIDE FINAL;
314     virtual void getClipPreferences(ClipPreferencesSetter &clipPreferences) OVERRIDE FINAL;
315 
316     template <int nComponents>
317     void renderInternal(const RenderArguments &args, BitDepthEnum dstBitDepth);
318 
319     /* set up and run a processor */
320     void setupAndProcess(CopyRectangleProcessorBase &, const RenderArguments &args);
321 
322     void getRectanglecanonical(OfxTime time, OfxRectD& rect) const;
323 
324 private:
325     // do not need to delete these, the ImageEffect is managing them for us
326     Clip *_dstClip;
327     Clip *_srcClipA;
328     Clip *_srcClipB;
329     Clip *_maskClip;
330     Double2DParam* _btmLeft;
331     Double2DParam* _size;
332     DoubleParam* _softness;
333     BooleanParam* _processR;
334     BooleanParam* _processG;
335     BooleanParam* _processB;
336     BooleanParam* _processA;
337     DoubleParam* _mix;
338     BooleanParam* _maskApply;
339     BooleanParam* _maskInvert;
340 };
341 
342 void
getRectanglecanonical(OfxTime time,OfxRectD & rect) const343 CopyRectanglePlugin::getRectanglecanonical(OfxTime time,
344                                            OfxRectD& rect) const
345 {
346     _btmLeft->getValueAtTime(time, rect.x1, rect.y1);
347     double w, h;
348     _size->getValueAtTime(time, w, h);
349     rect.x2 = rect.x1 + w;
350     rect.y2 = rect.y1 + h;
351 }
352 
353 ////////////////////////////////////////////////////////////////////////////////
354 /** @brief render for the filter */
355 
356 ////////////////////////////////////////////////////////////////////////////////
357 // basic plugin render function, just a skelington to instantiate templates from
358 
359 /* set up and run a processor */
360 void
setupAndProcess(CopyRectangleProcessorBase & processor,const RenderArguments & args)361 CopyRectanglePlugin::setupAndProcess(CopyRectangleProcessorBase &processor,
362                                      const RenderArguments &args)
363 {
364     auto_ptr<Image> dst( _dstClip->fetchImage(args.time) );
365 
366     if ( !dst.get() ) {
367         throwSuiteStatusException(kOfxStatFailed);
368     }
369     BitDepthEnum dstBitDepth    = dst->getPixelDepth();
370     PixelComponentEnum dstComponents  = dst->getPixelComponents();
371     if ( ( dstBitDepth != _dstClip->getPixelDepth() ) ||
372          ( dstComponents != _dstClip->getPixelComponents() ) ) {
373         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
374         throwSuiteStatusException(kOfxStatFailed);
375     }
376     if ( (dst->getRenderScale().x != args.renderScale.x) ||
377          ( dst->getRenderScale().y != args.renderScale.y) ||
378          ( ( dst->getField() != eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
379         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
380         throwSuiteStatusException(kOfxStatFailed);
381     }
382     auto_ptr<const Image> srcA( ( _srcClipA && _srcClipA->isConnected() ) ?
383                                      _srcClipA->fetchImage(args.time) : 0 );
384     if ( srcA.get() ) {
385         if ( (srcA->getRenderScale().x != args.renderScale.x) ||
386              ( srcA->getRenderScale().y != args.renderScale.y) ||
387              ( ( srcA->getField() != eFieldNone) /* for DaVinci Resolve */ && ( srcA->getField() != args.fieldToRender) ) ) {
388             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
389             throwSuiteStatusException(kOfxStatFailed);
390         }
391         BitDepthEnum srcBitDepth      = srcA->getPixelDepth();
392         PixelComponentEnum srcComponents = srcA->getPixelComponents();
393         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
394             throwSuiteStatusException(kOfxStatFailed);
395         }
396     }
397     auto_ptr<const Image> srcB( ( _srcClipB && _srcClipB->isConnected() ) ?
398                                      _srcClipB->fetchImage(args.time) : 0 );
399     if ( srcB.get() ) {
400         if ( (srcB->getRenderScale().x != args.renderScale.x) ||
401              ( srcB->getRenderScale().y != args.renderScale.y) ||
402              ( ( srcB->getField() != eFieldNone) /* for DaVinci Resolve */ && ( srcB->getField() != args.fieldToRender) ) ) {
403             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
404             throwSuiteStatusException(kOfxStatFailed);
405         }
406         BitDepthEnum srcBitDepth      = srcB->getPixelDepth();
407         PixelComponentEnum srcComponents = srcB->getPixelComponents();
408         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
409             throwSuiteStatusException(kOfxStatFailed);
410         }
411     }
412     bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(args.time) ) && _maskClip && _maskClip->isConnected() );
413     auto_ptr<const Image> mask(doMasking ? _maskClip->fetchImage(args.time) : 0);
414     if ( mask.get() ) {
415         if ( (mask->getRenderScale().x != args.renderScale.x) ||
416              ( mask->getRenderScale().y != args.renderScale.y) ||
417              ( ( mask->getField() != eFieldNone) /* for DaVinci Resolve */ && ( mask->getField() != args.fieldToRender) ) ) {
418             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
419             throwSuiteStatusException(kOfxStatFailed);
420         }
421     }
422     if (doMasking) {
423         bool maskInvert;
424         _maskInvert->getValueAtTime(args.time, maskInvert);
425         processor.doMasking(true);
426         processor.setMaskImg(mask.get(), maskInvert);
427     }
428 
429     // set the images
430     processor.setDstImg( dst.get() );
431     processor.setSrcImgs( srcA.get(), srcB.get() );
432 
433     // set the render window
434     processor.setRenderWindow(args.renderWindow);
435 
436     OfxRectD rectangle;
437     getRectanglecanonical(args.time, rectangle);
438     OfxRectI rectanglePixel;
439     double par = dst->getPixelAspectRatio();
440     Coords::toPixelEnclosing(rectangle, args.renderScale, par, &rectanglePixel);
441     double softness;
442     _softness->getValueAtTime(args.time, softness);
443     softness *= args.renderScale.x;
444 
445     bool processR;
446     bool processG;
447     bool processB;
448     bool processA;
449     _processR->getValueAtTime(args.time, processR);
450     _processG->getValueAtTime(args.time, processG);
451     _processB->getValueAtTime(args.time, processB);
452     _processA->getValueAtTime(args.time, processA);
453 
454     double mix;
455     _mix->getValueAtTime(args.time, mix);
456     processor.setValues(rectanglePixel, softness, processR, processG, processB, processA, mix);
457 
458     // Call the base class process member, this will call the derived templated process code
459     processor.process();
460 } // CopyRectanglePlugin::setupAndProcess
461 
462 // override the roi call
463 // Required if the plugin requires a region from the inputs which is different from the rendered region of the output.
464 // (this is the case here)
465 void
getRegionsOfInterest(const RegionsOfInterestArguments & args,RegionOfInterestSetter & rois)466 CopyRectanglePlugin::getRegionsOfInterest(const RegionsOfInterestArguments &args,
467                                           RegionOfInterestSetter &rois)
468 {
469     if (!getImageEffectHostDescription()->supportsTiles) {
470         return;
471     }
472     OfxRectD rectangle;
473 
474     getRectanglecanonical(args.time, rectangle);
475 
476     // intersect the crop rectangle with args.regionOfInterest
477     Coords::rectIntersection(rectangle, args.regionOfInterest, &rectangle);
478     double mix = 1.;
479     bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(args.time) ) && _maskClip && _maskClip->isConnected() );
480     if (doMasking) {
481         _mix->getValueAtTime(args.time, mix);
482     }
483     if ( doMasking && (mix != 1.) ) {
484         // for masking or mixing, we also need the source image.
485         // compute the bounding box with the default ROI
486         Coords::rectBoundingBox(rectangle, args.regionOfInterest, &rectangle);
487     }
488     rois.setRegionOfInterest(*_srcClipA, rectangle);
489     // no need to set the RoI on _srcClipB, since it's the same as the output RoI
490 }
491 
492 bool
getRegionOfDefinition(const RegionOfDefinitionArguments & args,OfxRectD & rod)493 CopyRectanglePlugin::getRegionOfDefinition(const RegionOfDefinitionArguments &args,
494                                            OfxRectD &rod)
495 {
496     OfxRectD rect;
497 
498     getRectanglecanonical(args.time, rect);
499     const OfxRectD& srcB_rod = _srcClipB->getRegionOfDefinition(args.time);
500     Coords::rectBoundingBox(rect, srcB_rod, &rod);
501 
502     return true;
503 }
504 
505 // the internal render function
506 template <int nComponents>
507 void
renderInternal(const RenderArguments & args,BitDepthEnum dstBitDepth)508 CopyRectanglePlugin::renderInternal(const RenderArguments &args,
509                                     BitDepthEnum dstBitDepth)
510 {
511     switch (dstBitDepth) {
512     case eBitDepthUByte: {
513         CopyRectangleProcessor<unsigned char, nComponents, 255> fred(*this);
514         setupAndProcess(fred, args);
515         break;
516     }
517     case eBitDepthUShort: {
518         CopyRectangleProcessor<unsigned short, nComponents, 65535> fred(*this);
519         setupAndProcess(fred, args);
520         break;
521     }
522     case eBitDepthFloat: {
523         CopyRectangleProcessor<float, nComponents, 1> fred(*this);
524         setupAndProcess(fred, args);
525         break;
526     }
527     default:
528         throwSuiteStatusException(kOfxStatErrUnsupported);
529     }
530 }
531 
532 bool
isIdentity(const IsIdentityArguments & args,Clip * & identityClip,double &,int &,std::string &)533 CopyRectanglePlugin::isIdentity(const IsIdentityArguments &args,
534                                 Clip * &identityClip,
535                                 double & /*identityTime*/
536                                 , int& /*view*/, std::string& /*plane*/)
537 {
538     double mix;
539 
540     _mix->getValueAtTime(args.time, mix);
541 
542     if (mix == 0.) {
543         identityClip = _srcClipB;
544 
545         return true;
546     }
547 
548     bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(args.time) ) && _maskClip && _maskClip->isConnected() );
549     if (doMasking) {
550         bool maskInvert;
551         _maskInvert->getValueAtTime(args.time, maskInvert);
552         if (!maskInvert) {
553             OfxRectI maskRoD;
554             if (getImageEffectHostDescription()->supportsMultiResolution) {
555                 // In Sony Catalyst Edit, clipGetRegionOfDefinition returns the RoD in pixels instead of canonical coordinates.
556                 // In hosts that do not support multiResolution (e.g. Sony Catalyst Edit), all inputs have the same RoD anyway.
557                 Coords::toPixelEnclosing(_maskClip->getRegionOfDefinition(args.time), args.renderScale, _maskClip->getPixelAspectRatio(), &maskRoD);
558                 // effect is identity if the renderWindow doesn't intersect the mask RoD
559                 if ( !Coords::rectIntersection<OfxRectI>(args.renderWindow, maskRoD, 0) ) {
560                     identityClip = _srcClipB;
561 
562                     return true;
563                 }
564             }
565         }
566     }
567 
568     return false;
569 }
570 
571 void
getClipPreferences(ClipPreferencesSetter & clipPreferences)572 CopyRectanglePlugin::getClipPreferences(ClipPreferencesSetter &clipPreferences)
573 {
574     PixelComponentEnum outputComps = getDefaultOutputClipComponents();
575 
576     clipPreferences.setClipComponents(*_srcClipA, outputComps);
577     clipPreferences.setClipComponents(*_srcClipB, outputComps);
578 }
579 
580 // the overridden render function
581 void
render(const RenderArguments & args)582 CopyRectanglePlugin::render(const RenderArguments &args)
583 {
584     // instantiate the render code based on the pixel depth of the dst clip
585     BitDepthEnum dstBitDepth    = _dstClip->getPixelDepth();
586     PixelComponentEnum dstComponents  = _dstClip->getPixelComponents();
587 
588     assert( kSupportsMultipleClipPARs   || _srcClipA->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
589     assert( kSupportsMultipleClipDepths || _srcClipA->getPixelDepth()       == _dstClip->getPixelDepth() );
590     assert( kSupportsMultipleClipPARs   || _srcClipB->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
591     assert( kSupportsMultipleClipDepths || _srcClipB->getPixelDepth()       == _dstClip->getPixelDepth() );
592 #ifdef OFX_EXTENSIONS_NATRON
593     assert(dstComponents == ePixelComponentRGBA || dstComponents == ePixelComponentRGB || dstComponents == ePixelComponentXY || dstComponents == ePixelComponentAlpha);
594 #else
595     assert(dstComponents == ePixelComponentRGBA || dstComponents == ePixelComponentRGB || dstComponents == ePixelComponentAlpha);
596 #endif
597     if (dstComponents == ePixelComponentRGBA) {
598         renderInternal<4>(args, dstBitDepth);
599     } else if (dstComponents == ePixelComponentRGB) {
600         renderInternal<3>(args, dstBitDepth);
601 #ifdef OFX_EXTENSIONS_NATRON
602     } else if (dstComponents == ePixelComponentXY) {
603         renderInternal<2>(args, dstBitDepth);
604 #endif
605     } else {
606         assert(dstComponents == ePixelComponentAlpha);
607         renderInternal<1>(args, dstBitDepth);
608     }
609 }
610 
611 mDeclarePluginFactory(CopyRectanglePluginFactory, {ofxsThreadSuiteCheck();}, {});
612 void
describe(ImageEffectDescriptor & desc)613 CopyRectanglePluginFactory::describe(ImageEffectDescriptor &desc)
614 {
615     // basic labels
616     desc.setLabel(kPluginName);
617     desc.setPluginGrouping(kPluginGrouping);
618     desc.setPluginDescription(kPluginDescription);
619 
620     desc.addSupportedContext(eContextGeneral);
621 
622     desc.addSupportedBitDepth(eBitDepthUByte);
623     desc.addSupportedBitDepth(eBitDepthUShort);
624     desc.addSupportedBitDepth(eBitDepthFloat);
625 
626 
627     desc.setSingleInstance(false);
628     desc.setHostFrameThreading(false);
629     desc.setTemporalClipAccess(false);
630     desc.setRenderTwiceAlways(true);
631     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
632     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
633     desc.setRenderThreadSafety(kRenderThreadSafety);
634 
635     desc.setSupportsTiles(kSupportsTiles);
636 
637     // in order to support multiresolution, render() must take into account the pixelaspectratio and the renderscale
638     // and scale the transform appropriately.
639     // All other functions are usually in canonical coordinates.
640     desc.setSupportsMultiResolution(kSupportsMultiResolution);
641     desc.setOverlayInteractDescriptor(new RectangleOverlayDescriptor);
642 
643 #ifdef OFX_EXTENSIONS_NATRON
644     desc.setChannelSelector(ePixelComponentNone); // we have our own channel selector
645 #endif
646 }
647 
648 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)649 CopyRectanglePluginFactory::createInstance(OfxImageEffectHandle handle,
650                                            ContextEnum /*context*/)
651 {
652     return new CopyRectanglePlugin(handle);
653 }
654 
655 static void
defineComponentParam(ImageEffectDescriptor & desc,PageParamDescriptor * page,const std::string & name,const std::string & label,bool newLine)656 defineComponentParam(ImageEffectDescriptor &desc,
657                      PageParamDescriptor* page,
658                      const std::string& name,
659                      const std::string& label,
660                      bool newLine)
661 {
662     BooleanParamDescriptor* param = desc.defineBooleanParam(name);
663 
664     param->setLabel(label);
665     param->setDefault(true);
666     param->setHint("Copy " + name);
667     if (!newLine) {
668         param->setLayoutHint(eLayoutHintNoNewLine, 1);
669     }
670     if (page) {
671         page->addChild(*param);
672     }
673 }
674 
675 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)676 CopyRectanglePluginFactory::describeInContext(ImageEffectDescriptor &desc,
677                                               ContextEnum context)
678 {
679     ClipDescriptor *srcClipB = desc.defineClip(kClipB);
680 
681     srcClipB->setHint(kClipAHint);
682     srcClipB->addSupportedComponent(ePixelComponentRGBA);
683     srcClipB->addSupportedComponent(ePixelComponentRGB);
684 #ifdef OFX_EXTENSIONS_NATRON
685     srcClipB->addSupportedComponent(ePixelComponentXY);
686 #endif
687     srcClipB->addSupportedComponent(ePixelComponentAlpha);
688     srcClipB->setTemporalClipAccess(false);
689     srcClipB->setSupportsTiles(kSupportsTiles);
690     srcClipB->setIsMask(false);
691 
692     ClipDescriptor *srcClipA = desc.defineClip(kClipA);
693     srcClipA->setHint(kClipAHint);
694     srcClipA->addSupportedComponent(ePixelComponentRGBA);
695     srcClipA->addSupportedComponent(ePixelComponentRGB);
696 #ifdef OFX_EXTENSIONS_NATRON
697     srcClipA->addSupportedComponent(ePixelComponentXY);
698 #endif
699     srcClipA->addSupportedComponent(ePixelComponentAlpha);
700     srcClipA->setTemporalClipAccess(false);
701     srcClipA->setSupportsTiles(kSupportsTiles);
702     srcClipA->setIsMask(false);
703 
704 
705     // create the mandated output clip
706     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
707     dstClip->addSupportedComponent(ePixelComponentRGBA);
708     dstClip->addSupportedComponent(ePixelComponentRGB);
709 #ifdef OFX_EXTENSIONS_NATRON
710     dstClip->addSupportedComponent(ePixelComponentXY);
711 #endif
712     dstClip->addSupportedComponent(ePixelComponentAlpha);
713     dstClip->setSupportsTiles(kSupportsTiles);
714 
715     ClipDescriptor *maskClip = (context == eContextPaint) ? desc.defineClip("Brush") : desc.defineClip("Mask");
716     maskClip->addSupportedComponent(ePixelComponentAlpha);
717     maskClip->setTemporalClipAccess(false);
718     if (context != eContextPaint) {
719         maskClip->setOptional(true);
720     }
721     maskClip->setSupportsTiles(kSupportsTiles);
722     maskClip->setIsMask(true);
723 
724     // make some pages and to things in
725     PageParamDescriptor *page = desc.definePageParam("Controls");
726 
727     // btmLeft
728     {
729         Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamRectangleInteractBtmLeft);
730         param->setLabel(kParamRectangleInteractBtmLeftLabel);
731         param->setDoubleType(eDoubleTypeXYAbsolute);
732         if ( param->supportsDefaultCoordinateSystem() ) {
733             param->setDefaultCoordinateSystem(eCoordinatesNormalised); // no need of kParamDefaultsNormalised
734         } else {
735             gHostSupportsDefaultCoordinateSystem = false; // no multithread here, see kParamDefaultsNormalised
736         }
737         param->setDefault(0., 0.);
738         param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
739         param->setDisplayRange(-10000., -10000., 10000., 10000.); // Resolve requires display range or values are clamped to (-1,1)
740         param->setIncrement(1.);
741         param->setHint(kParamRectangleInteractBtmLeftHint);
742         param->setDigits(0);
743         if (page) {
744             page->addChild(*param);
745         }
746     }
747 
748     // size
749     {
750         Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamRectangleInteractSize);
751         param->setLabel(kParamRectangleInteractSizeLabel);
752         param->setDoubleType(eDoubleTypeXY);
753         if ( param->supportsDefaultCoordinateSystem() ) {
754             param->setDefaultCoordinateSystem(eCoordinatesNormalised); // no need of kParamDefaultsNormalised
755         } else {
756             gHostSupportsDefaultCoordinateSystem = false; // no multithread here, see kParamDefaultsNormalised
757         }
758         param->setDefault(1., 1.);
759         param->setRange(0., 0., DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
760         param->setDisplayRange(0., 0., 10000., 10000.); // Resolve requires display range or values are clamped to (-1,1)
761         param->setDimensionLabels(kParamRectangleInteractSizeDim1, kParamRectangleInteractSizeDim2);
762         param->setHint(kParamRectangleInteractSizeHint);
763         param->setIncrement(1.);
764         param->setDigits(0);
765         if (page) {
766             page->addChild(*param);
767         }
768     }
769 
770     // interactive
771     {
772         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamRectangleInteractInteractive);
773         param->setLabel(kParamRectangleInteractInteractiveLabel);
774         param->setHint(kParamRectangleInteractInteractiveHint);
775         param->setEvaluateOnChange(false);
776         if (page) {
777             page->addChild(*param);
778         }
779     }
780 
781     defineComponentParam(desc, page, kParamProcessR, kParamProcessRLabel, false);
782     defineComponentParam(desc, page, kParamProcessG, kParamProcessGLabel, false);
783     defineComponentParam(desc, page, kParamProcessB, kParamProcessBLabel, false);
784     defineComponentParam(desc, page, kParamProcessA, kParamProcessALabel, true);
785 
786     // softness
787     {
788         DoubleParamDescriptor* param = desc.defineDoubleParam(kParamSoftness);
789         param->setLabel(kParamSoftnessLabel);
790         param->setDefault(0);
791         param->setRange(0., 100.);
792         param->setDisplayRange(0., 100.);
793         param->setIncrement(1.);
794         param->setHint(kParamSoftnessHint);
795         if (page) {
796             page->addChild(*param);
797         }
798     }
799 
800     ofxsMaskMixDescribeParams(desc, page);
801 } // CopyRectanglePluginFactory::describeInContext
802 
803 static CopyRectanglePluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
804 mRegisterPluginFactoryInstance(p)
805 
806 OFXS_NAMESPACE_ANONYMOUS_EXIT
807