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