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 ColorMatrix plugin.
21 */
22
23 #include <cmath>
24 #include <cstring>
25
26 #include "ofxsProcessing.H"
27 #include "ofxsMaskMix.h"
28 #include "ofxsCoords.h"
29 #include "ofxsMacros.h"
30 #include "ofxsThreadSuite.h"
31
32 using namespace OFX;
33
34 OFXS_NAMESPACE_ANONYMOUS_ENTER
35
36 #define kPluginName "ColorMatrixOFX"
37 #define kPluginGrouping "Color/Math"
38 #define kPluginDescription "Multiply the RGBA channels by an arbitrary 4x4 matrix."
39 #define kPluginIdentifier "net.sf.openfx.ColorMatrixPlugin"
40 // History:
41 // version 1.0: initial version
42 // version 2.0: use kNatronOfxParamProcess* parameters
43 #define kPluginVersionMajor 2 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
44 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
45
46 #define kSupportsTiles 1
47 #define kSupportsMultiResolution 1
48 #define kSupportsRenderScale 1
49 #define kSupportsMultipleClipPARs false
50 #define kSupportsMultipleClipDepths false
51 #define kRenderThreadSafety eRenderFullySafe
52
53 #ifdef OFX_EXTENSIONS_NATRON
54 #define kParamProcessR kNatronOfxParamProcessR
55 #define kParamProcessRLabel kNatronOfxParamProcessRLabel
56 #define kParamProcessRHint kNatronOfxParamProcessRHint
57 #define kParamProcessG kNatronOfxParamProcessG
58 #define kParamProcessGLabel kNatronOfxParamProcessGLabel
59 #define kParamProcessGHint kNatronOfxParamProcessGHint
60 #define kParamProcessB kNatronOfxParamProcessB
61 #define kParamProcessBLabel kNatronOfxParamProcessBLabel
62 #define kParamProcessBHint kNatronOfxParamProcessBHint
63 #define kParamProcessA kNatronOfxParamProcessA
64 #define kParamProcessALabel kNatronOfxParamProcessALabel
65 #define kParamProcessAHint kNatronOfxParamProcessAHint
66 #else
67 #define kParamProcessR "processR"
68 #define kParamProcessRLabel "R"
69 #define kParamProcessRHint "Process red component."
70 #define kParamProcessG "processG"
71 #define kParamProcessGLabel "G"
72 #define kParamProcessGHint "Process green component."
73 #define kParamProcessB "processB"
74 #define kParamProcessBLabel "B"
75 #define kParamProcessBHint "Process blue component."
76 #define kParamProcessA "processA"
77 #define kParamProcessALabel "A"
78 #define kParamProcessAHint "Process alpha component."
79 #endif
80
81 #define kParamOutputRedName "outputRed"
82 #define kParamOutputRedLabel "Output Red"
83 #define kParamOutputRedHint "values for red output component."
84
85 #define kParamOutputGreenName "outputGreen"
86 #define kParamOutputGreenLabel "Output Green"
87 #define kParamOutputGreenHint "values for green output component."
88
89 #define kParamOutputBlueName "outputBlue"
90 #define kParamOutputBlueLabel "Output Blue"
91 #define kParamOutputBlueHint "values for blue output component."
92
93 #define kParamOutputAlphaName "outputAlpha"
94 #define kParamOutputAlphaLabel "Output Alpha"
95 #define kParamOutputAlphaHint "values for alpha output component."
96
97 #define kParamClampBlack "clampBlack"
98 #define kParamClampBlackLabel "Clamp Black"
99 #define kParamClampBlackHint "All colors below 0 on output are set to 0."
100
101 #define kParamClampWhite "clampWhite"
102 #define kParamClampWhiteLabel "Clamp White"
103 #define kParamClampWhiteHint "All colors above 1 on output are set to 1."
104
105 #define kParamPremultChanged "premultChanged"
106
107 #ifdef OFX_EXTENSIONS_NATRON
108 #define OFX_COMPONENTS_OK(c) ((c)== ePixelComponentAlpha || (c) == ePixelComponentXY || (c) == ePixelComponentRGB || (c) == ePixelComponentRGBA)
109 #else
110 #define OFX_COMPONENTS_OK(c) ((c)== ePixelComponentAlpha || (c) == ePixelComponentRGB || (c) == ePixelComponentRGBA)
111 #endif
112
113
114 struct RGBAValues
115 {
116 double r, g, b, a;
RGBAValuesRGBAValues117 RGBAValues(double v) : r(v), g(v), b(v), a(v) {}
118
RGBAValuesRGBAValues119 RGBAValues() : r(0), g(0), b(0), a(0) {}
120 };
121
122 class ColorMatrixProcessorBase
123 : public ImageProcessor
124 {
125 protected:
126 const Image *_srcImg;
127 const Image *_maskImg;
128 bool _processR;
129 bool _processG;
130 bool _processB;
131 bool _processA;
132 RGBAValues _matrix[4];
133 bool _clampBlack;
134 bool _clampWhite;
135 bool _premult;
136 int _premultChannel;
137 bool _doMasking;
138 double _mix;
139 bool _maskInvert;
140
141 public:
142
ColorMatrixProcessorBase(ImageEffect & instance)143 ColorMatrixProcessorBase(ImageEffect &instance)
144 : ImageProcessor(instance)
145 , _srcImg(NULL)
146 , _maskImg(NULL)
147 , _processR(true)
148 , _processG(true)
149 , _processB(true)
150 , _processA(false)
151 , _clampBlack(true)
152 , _clampWhite(true)
153 , _premult(false)
154 , _premultChannel(3)
155 , _doMasking(false)
156 , _mix(1.)
157 , _maskInvert(false)
158 {
159 }
160
setSrcImg(const Image * v)161 void setSrcImg(const Image *v) {_srcImg = v; }
162
setMaskImg(const Image * v,bool maskInvert)163 void setMaskImg(const Image *v,
164 bool maskInvert) { _maskImg = v; _maskInvert = maskInvert; }
165
doMasking(bool v)166 void doMasking(bool v) {_doMasking = v; }
167
setValues(bool processR,bool processG,bool processB,bool processA,const RGBAValues & outputRed,const RGBAValues & outputGreen,const RGBAValues & outputBlue,const RGBAValues & outputAlpha,bool clampBlack,bool clampWhite,bool premult,int premultChannel,double mix)168 void setValues(bool processR,
169 bool processG,
170 bool processB,
171 bool processA,
172 const RGBAValues& outputRed,
173 const RGBAValues& outputGreen,
174 const RGBAValues& outputBlue,
175 const RGBAValues& outputAlpha,
176 bool clampBlack,
177 bool clampWhite,
178 bool premult,
179 int premultChannel,
180 double mix)
181 {
182 _processR = processR;
183 _processG = processG;
184 _processB = processB;
185 _processA = processA;
186 _matrix[0] = outputRed;
187 _matrix[1] = outputGreen;
188 _matrix[2] = outputBlue;
189 _matrix[3] = outputAlpha;
190 _clampBlack = clampBlack;
191 _clampWhite = clampWhite;
192 _premult = premult;
193 _premultChannel = premultChannel;
194 _mix = mix;
195 }
196
197 private:
198 };
199
200
201 template <class PIX, int nComponents, int maxValue>
202 class ColorMatrixProcessor
203 : public ColorMatrixProcessorBase
204 {
205 public:
ColorMatrixProcessor(ImageEffect & instance)206 ColorMatrixProcessor(ImageEffect &instance)
207 : ColorMatrixProcessorBase(instance)
208 {
209 }
210
211 private:
212
apply(int c,double inR,double inG,double inB,double inA)213 double apply(int c,
214 double inR,
215 double inG,
216 double inB,
217 double inA)
218 {
219 double comp = _matrix[c].r * inR + _matrix[c].g * inG + _matrix[c].b * inB + _matrix[c].a * inA;
220
221 if ( _clampBlack && (comp < 0.) ) {
222 comp = 0.;
223 } else if ( _clampWhite && (comp > maxValue) ) {
224 comp = maxValue;
225 }
226
227 return comp;
228 }
229
multiThreadProcessImages(OfxRectI procWindow)230 void multiThreadProcessImages(OfxRectI procWindow)
231 {
232 # ifndef __COVERITY__ // too many coverity[dead_error_line] errors
233 const bool r = _processR && (nComponents != 1);
234 const bool g = _processG && (nComponents >= 2);
235 const bool b = _processB && (nComponents >= 3);
236 const bool a = _processA && (nComponents == 1 || nComponents == 4);
237 if (r) {
238 if (g) {
239 if (b) {
240 if (a) {
241 return process<true, true, true, true >(procWindow); // RGBA
242 } else {
243 return process<true, true, true, false>(procWindow); // RGBa
244 }
245 } else {
246 if (a) {
247 return process<true, true, false, true >(procWindow); // RGbA
248 } else {
249 return process<true, true, false, false>(procWindow); // RGba
250 }
251 }
252 } else {
253 if (b) {
254 if (a) {
255 return process<true, false, true, true >(procWindow); // RgBA
256 } else {
257 return process<true, false, true, false>(procWindow); // RgBa
258 }
259 } else {
260 if (a) {
261 return process<true, false, false, true >(procWindow); // RgbA
262 } else {
263 return process<true, false, false, false>(procWindow); // Rgba
264 }
265 }
266 }
267 } else {
268 if (g) {
269 if (b) {
270 if (a) {
271 return process<false, true, true, true >(procWindow); // rGBA
272 } else {
273 return process<false, true, true, false>(procWindow); // rGBa
274 }
275 } else {
276 if (a) {
277 return process<false, true, false, true >(procWindow); // rGbA
278 } else {
279 return process<false, true, false, false>(procWindow); // rGba
280 }
281 }
282 } else {
283 if (b) {
284 if (a) {
285 return process<false, false, true, true >(procWindow); // rgBA
286 } else {
287 return process<false, false, true, false>(procWindow); // rgBa
288 }
289 } else {
290 if (a) {
291 return process<false, false, false, true >(procWindow); // rgbA
292 } else {
293 return process<false, false, false, false>(procWindow); // rgba
294 }
295 }
296 }
297 }
298 # endif // ifndef __COVERITY__
299 } // multiThreadProcessImages
300
301 template<bool processR, bool processG, bool processB, bool processA>
process(const OfxRectI & procWindow)302 void process(const OfxRectI& procWindow)
303 {
304 assert(nComponents == 1 || nComponents == 3 || nComponents == 4);
305 assert(_dstImg);
306 float unpPix[4];
307 float tmpPix[4];
308 for (int y = procWindow.y1; y < procWindow.y2; y++) {
309 if ( _effect.abort() ) {
310 break;
311 }
312
313 PIX *dstPix = (PIX *) _dstImg->getPixelAddress(procWindow.x1, y);
314
315 for (int x = procWindow.x1; x < procWindow.x2; x++) {
316 const PIX *srcPix = (const PIX *) (_srcImg ? _srcImg->getPixelAddress(x, y) : 0);
317 ofxsUnPremult<PIX, nComponents, maxValue>(srcPix, unpPix, _premult, _premultChannel);
318 for (int c = 0; c < 4; ++c) { // tmpPix has 4 components
319 if ( ( processR && (c == 0) ) ||
320 ( processG && ( c == 1) ) ||
321 ( processB && ( c == 2) ) ||
322 ( processA && ( c == 3) ) ) {
323 tmpPix[c] = (float)apply(c, unpPix[0], unpPix[1], unpPix[2], unpPix[3]);
324 } else {
325 tmpPix[c] = unpPix[c];
326 }
327 }
328 ofxsPremultMaskMixPix<PIX, nComponents, maxValue, true>(tmpPix, _premult, _premultChannel, x, y, srcPix, _doMasking, _maskImg, (float)_mix, _maskInvert, dstPix);
329 // copy back original values from unprocessed channels
330 if (nComponents == 1) {
331 if (!processA) {
332 dstPix[0] = srcPix ? srcPix[0] : PIX();
333 }
334 } else if ( (nComponents == 3) || (nComponents == 4) ) {
335 if (!processR) {
336 dstPix[0] = srcPix ? srcPix[0] : PIX();
337 }
338 if (!processG) {
339 dstPix[1] = srcPix ? srcPix[1] : PIX();
340 }
341 if (!processB) {
342 dstPix[2] = srcPix ? srcPix[2] : PIX();
343 }
344 if ( !processA && (nComponents == 4) ) {
345 dstPix[3] = srcPix ? srcPix[3] : PIX();
346 }
347 }
348 // increment the dst pixel
349 dstPix += nComponents;
350 }
351 }
352 } // process
353 };
354
355
356 ////////////////////////////////////////////////////////////////////////////////
357 /** @brief The plugin that does our work */
358 class ColorMatrixPlugin
359 : public ImageEffect
360 {
361 public:
362 /** @brief ctor */
ColorMatrixPlugin(OfxImageEffectHandle handle)363 ColorMatrixPlugin(OfxImageEffectHandle handle)
364 : ImageEffect(handle)
365 , _dstClip(NULL)
366 , _srcClip(NULL)
367 , _maskClip(NULL)
368 , _premultChanged(NULL)
369 {
370 _dstClip = fetchClip(kOfxImageEffectOutputClipName);
371 assert( _dstClip && (!_dstClip->isConnected() || _dstClip->getPixelComponents() == ePixelComponentRGB ||
372 _dstClip->getPixelComponents() == ePixelComponentRGBA ||
373 _dstClip->getPixelComponents() == ePixelComponentAlpha) );
374 _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
375 assert( (!_srcClip && getContext() == eContextGenerator) ||
376 ( _srcClip && (!_srcClip->isConnected() || _srcClip->getPixelComponents() == ePixelComponentRGB ||
377 _srcClip->getPixelComponents() == ePixelComponentRGBA ||
378 _srcClip->getPixelComponents() == ePixelComponentAlpha) ) );
379 _maskClip = fetchClip(getContext() == eContextPaint ? "Brush" : "Mask");
380 assert(!_maskClip || !_maskClip->isConnected() || _maskClip->getPixelComponents() == ePixelComponentAlpha);
381 _processR = fetchBooleanParam(kParamProcessR);
382 _processG = fetchBooleanParam(kParamProcessG);
383 _processB = fetchBooleanParam(kParamProcessB);
384 _processA = fetchBooleanParam(kParamProcessA);
385 assert(_processR && _processG && _processB && _processA);
386 _outputRed = fetchRGBAParam(kParamOutputRedName);
387 _outputGreen = fetchRGBAParam(kParamOutputGreenName);
388 _outputBlue = fetchRGBAParam(kParamOutputBlueName);
389 _outputAlpha = fetchRGBAParam(kParamOutputAlphaName);
390 _clampBlack = fetchBooleanParam(kParamClampBlack);
391 _clampWhite = fetchBooleanParam(kParamClampWhite);
392 assert(_outputRed && _outputGreen && _outputBlue && _outputAlpha && _clampBlack && _clampWhite);
393 _premult = fetchBooleanParam(kParamPremult);
394 _premultChannel = fetchChoiceParam(kParamPremultChannel);
395 assert(_premult && _premultChannel);
396 _mix = fetchDoubleParam(kParamMix);
397 _maskApply = ( ofxsMaskIsAlwaysConnected( OFX::getImageEffectHostDescription() ) && paramExists(kParamMaskApply) ) ? fetchBooleanParam(kParamMaskApply) : 0;
398 _maskInvert = fetchBooleanParam(kParamMaskInvert);
399 assert(_mix && _maskInvert);
400 _premultChanged = fetchBooleanParam(kParamPremultChanged);
401 assert(_premultChanged);
402 }
403
404 private:
405 /* Override the render */
406 virtual void render(const RenderArguments &args) OVERRIDE FINAL;
407
408 template <int nComponents>
409 void renderInternal(const RenderArguments &args, BitDepthEnum dstBitDepth);
410
411 /* set up and run a processor */
412 void setupAndProcess(ColorMatrixProcessorBase &, const RenderArguments &args);
413
414 virtual bool isIdentity(const IsIdentityArguments &args, Clip * &identityClip, double &identityTime, int& view, std::string& plane) OVERRIDE FINAL;
415
416 /** @brief called when a clip has just been changed in some way (a rewire maybe) */
417 virtual void changedClip(const InstanceChangedArgs &args, const std::string &clipName) OVERRIDE FINAL;
418 virtual void changedParam(const InstanceChangedArgs &args, const std::string ¶mName) OVERRIDE FINAL;
419
420 private:
421 // do not need to delete these, the ImageEffect is managing them for us
422 Clip *_dstClip;
423 Clip *_srcClip;
424 Clip *_maskClip;
425 BooleanParam* _processR;
426 BooleanParam* _processG;
427 BooleanParam* _processB;
428 BooleanParam* _processA;
429 RGBAParam *_outputRed;
430 RGBAParam *_outputGreen;
431 RGBAParam *_outputBlue;
432 RGBAParam *_outputAlpha;
433 BooleanParam* _clampBlack;
434 BooleanParam* _clampWhite;
435 BooleanParam* _premult;
436 ChoiceParam* _premultChannel;
437 DoubleParam* _mix;
438 BooleanParam* _maskApply;
439 BooleanParam* _maskInvert;
440 BooleanParam* _premultChanged; // set to true the first time the user connects src
441 };
442
443
444 ////////////////////////////////////////////////////////////////////////////////
445 /** @brief render for the filter */
446
447 ////////////////////////////////////////////////////////////////////////////////
448 // basic plugin render function, just a skelington to instantiate templates from
449
450 /* set up and run a processor */
451 void
setupAndProcess(ColorMatrixProcessorBase & processor,const RenderArguments & args)452 ColorMatrixPlugin::setupAndProcess(ColorMatrixProcessorBase &processor,
453 const RenderArguments &args)
454 {
455 auto_ptr<Image> dst( _dstClip->fetchImage(args.time) );
456
457 if ( !dst.get() ) {
458 throwSuiteStatusException(kOfxStatFailed);
459 }
460 BitDepthEnum dstBitDepth = dst->getPixelDepth();
461 PixelComponentEnum dstComponents = dst->getPixelComponents();
462 if ( ( dstBitDepth != _dstClip->getPixelDepth() ) ||
463 ( dstComponents != _dstClip->getPixelComponents() ) ) {
464 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
465 throwSuiteStatusException(kOfxStatFailed);
466 }
467 if ( (dst->getRenderScale().x != args.renderScale.x) ||
468 ( dst->getRenderScale().y != args.renderScale.y) ||
469 ( ( dst->getField() != eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
470 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
471 throwSuiteStatusException(kOfxStatFailed);
472 }
473 auto_ptr<const Image> src( ( _srcClip && _srcClip->isConnected() ) ?
474 _srcClip->fetchImage(args.time) : 0 );
475 if ( src.get() ) {
476 if ( (src->getRenderScale().x != args.renderScale.x) ||
477 ( src->getRenderScale().y != args.renderScale.y) ||
478 ( ( src->getField() != eFieldNone) /* for DaVinci Resolve */ && ( src->getField() != args.fieldToRender) ) ) {
479 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
480 throwSuiteStatusException(kOfxStatFailed);
481 }
482 BitDepthEnum srcBitDepth = src->getPixelDepth();
483 PixelComponentEnum srcComponents = src->getPixelComponents();
484 if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
485 throwSuiteStatusException(kOfxStatErrImageFormat);
486 }
487 }
488 bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(args.time) ) && _maskClip && _maskClip->isConnected() );
489 auto_ptr<const Image> mask(doMasking ? _maskClip->fetchImage(args.time) : 0);
490 // do we do masking
491 if (doMasking) {
492 if ( mask.get() ) {
493 if ( (mask->getRenderScale().x != args.renderScale.x) ||
494 ( mask->getRenderScale().y != args.renderScale.y) ||
495 ( ( mask->getField() != eFieldNone) /* for DaVinci Resolve */ && ( mask->getField() != args.fieldToRender) ) ) {
496 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
497 throwSuiteStatusException(kOfxStatFailed);
498 }
499 }
500 bool maskInvert;
501 _maskInvert->getValueAtTime(args.time, maskInvert);
502 processor.doMasking(true);
503 processor.setMaskImg(mask.get(), maskInvert);
504 }
505
506 // set the images
507 processor.setDstImg( dst.get() );
508 processor.setSrcImg( src.get() );
509 // set the render window
510 processor.setRenderWindow(args.renderWindow);
511
512 bool processR, processG, processB, processA;
513 _processR->getValueAtTime(args.time, processR);
514 _processG->getValueAtTime(args.time, processG);
515 _processB->getValueAtTime(args.time, processB);
516 _processA->getValueAtTime(args.time, processA);
517 RGBAValues r, g, b, a;
518 _outputRed->getValueAtTime(args.time, r.r, r.g, r.b, r.a);
519 _outputGreen->getValueAtTime(args.time, g.r, g.g, g.b, g.a);
520 _outputBlue->getValueAtTime(args.time, b.r, b.g, b.b, b.a);
521 _outputAlpha->getValueAtTime(args.time, a.r, a.g, a.b, a.a);
522 bool clampBlack, clampWhite;
523 _clampBlack->getValueAtTime(args.time, clampBlack);
524 _clampWhite->getValueAtTime(args.time, clampWhite);
525 bool premult;
526 int premultChannel;
527 _premult->getValueAtTime(args.time, premult);
528 _premultChannel->getValueAtTime(args.time, premultChannel);
529 double mix;
530 _mix->getValueAtTime(args.time, mix);
531 processor.setValues(processR, processG, processB, processA,
532 r, g, b, a, clampBlack, clampWhite, premult, premultChannel, mix);
533
534 // Call the base class process member, this will call the derived templated process code
535 processor.process();
536 } // ColorMatrixPlugin::setupAndProcess
537
538 // the internal render function
539 template <int nComponents>
540 void
renderInternal(const RenderArguments & args,BitDepthEnum dstBitDepth)541 ColorMatrixPlugin::renderInternal(const RenderArguments &args,
542 BitDepthEnum dstBitDepth)
543 {
544 switch (dstBitDepth) {
545 case eBitDepthUByte: {
546 ColorMatrixProcessor<unsigned char, nComponents, 255> fred(*this);
547 setupAndProcess(fred, args);
548 break;
549 }
550 case eBitDepthUShort: {
551 ColorMatrixProcessor<unsigned short, nComponents, 65535> fred(*this);
552 setupAndProcess(fred, args);
553 break;
554 }
555 case eBitDepthFloat: {
556 ColorMatrixProcessor<float, nComponents, 1> fred(*this);
557 setupAndProcess(fred, args);
558 break;
559 }
560 default:
561 throwSuiteStatusException(kOfxStatErrUnsupported);
562 }
563 }
564
565 // the overridden render function
566 void
render(const RenderArguments & args)567 ColorMatrixPlugin::render(const RenderArguments &args)
568 {
569 // instantiate the render code based on the pixel depth of the dst clip
570 BitDepthEnum dstBitDepth = _dstClip->getPixelDepth();
571 PixelComponentEnum dstComponents = _dstClip->getPixelComponents();
572
573 assert( kSupportsMultipleClipPARs || !_srcClip || _srcClip->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
574 assert( kSupportsMultipleClipDepths || !_srcClip || _srcClip->getPixelDepth() == _dstClip->getPixelDepth() );
575 assert(OFX_COMPONENTS_OK(dstComponents));
576 // do the rendering
577 if (dstComponents == ePixelComponentRGBA) {
578 renderInternal<4>(args, dstBitDepth);
579 } else if (dstComponents == ePixelComponentRGB) {
580 renderInternal<3>(args, dstBitDepth);
581 #ifdef OFX_EXTENSIONS_NATRON
582 } else if (dstComponents == ePixelComponentXY) {
583 renderInternal<2>(args, dstBitDepth);
584 #endif
585 } else {
586 assert(dstComponents == ePixelComponentAlpha);
587 renderInternal<1>(args, dstBitDepth);
588 }
589 }
590
591 bool
isIdentity(const IsIdentityArguments & args,Clip * & identityClip,double &,int &,std::string &)592 ColorMatrixPlugin::isIdentity(const IsIdentityArguments &args,
593 Clip * &identityClip,
594 double & /*identityTime*/
595 , int& /*view*/, std::string& /*plane*/)
596 {
597 double mix;
598
599 _mix->getValueAtTime(args.time, mix);
600
601 if (mix == 0. /*|| (!processR && !processG && !processB && !processA)*/) {
602 identityClip = _srcClip;
603
604 return true;
605 }
606
607 bool clampBlack, clampWhite;
608 _clampBlack->getValueAtTime(args.time, clampBlack);
609 _clampWhite->getValueAtTime(args.time, clampWhite);
610 if (clampBlack || clampWhite) {
611 return false;
612 }
613 bool processR, processG, processB, processA;
614 _processR->getValueAtTime(args.time, processR);
615 _processG->getValueAtTime(args.time, processG);
616 _processB->getValueAtTime(args.time, processB);
617 _processA->getValueAtTime(args.time, processA);
618 RGBAValues r, g, b, a;
619 _outputRed->getValueAtTime(args.time, r.r, r.g, r.b, r.a);
620 _outputGreen->getValueAtTime(args.time, g.r, g.g, g.b, g.a);
621 _outputBlue->getValueAtTime(args.time, b.r, b.g, b.b, b.a);
622 _outputAlpha->getValueAtTime(args.time, a.r, a.g, a.b, a.a);
623 if ( ( !processR || ( (r.r == 1.) && (r.g == 0.) && (r.b == 0.) && (r.a == 0.) ) ) &&
624 ( !processG || ( ( g.r == 0.) && ( g.g == 1.) && ( g.b == 0.) && ( g.a == 0.) ) ) &&
625 ( !processB || ( ( b.r == 0.) && ( b.g == 0.) && ( b.b == 1.) && ( b.a == 0.) ) ) &&
626 ( !processA || ( ( a.r == 0.) && ( a.g == 0.) && ( a.b == 0.) && ( a.a == 1.) ) ) ) {
627 identityClip = _srcClip;
628
629 return true;
630 }
631
632 bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(args.time) ) && _maskClip && _maskClip->isConnected() );
633 if (doMasking) {
634 bool maskInvert;
635 _maskInvert->getValueAtTime(args.time, maskInvert);
636 if (!maskInvert) {
637 OfxRectI maskRoD;
638 if (getImageEffectHostDescription()->supportsMultiResolution) {
639 // In Sony Catalyst Edit, clipGetRegionOfDefinition returns the RoD in pixels instead of canonical coordinates.
640 // In hosts that do not support multiResolution (e.g. Sony Catalyst Edit), all inputs have the same RoD anyway.
641 Coords::toPixelEnclosing(_maskClip->getRegionOfDefinition(args.time), args.renderScale, _maskClip->getPixelAspectRatio(), &maskRoD);
642 // effect is identity if the renderWindow doesn't intersect the mask RoD
643 if ( !Coords::rectIntersection<OfxRectI>(args.renderWindow, maskRoD, 0) ) {
644 identityClip = _srcClip;
645
646 return true;
647 }
648 }
649 }
650 }
651
652 return false;
653 } // ColorMatrixPlugin::isIdentity
654
655 void
changedClip(const InstanceChangedArgs & args,const std::string & clipName)656 ColorMatrixPlugin::changedClip(const InstanceChangedArgs &args,
657 const std::string &clipName)
658 {
659 if ( (clipName == kOfxImageEffectSimpleSourceClipName) &&
660 _srcClip && _srcClip->isConnected() &&
661 !_premultChanged->getValue() &&
662 ( args.reason == eChangeUserEdit) ) {
663 if (_srcClip->getPixelComponents() != ePixelComponentRGBA) {
664 _premult->setValue(false);
665 } else {
666 switch ( _srcClip->getPreMultiplication() ) {
667 case eImageOpaque:
668 _premult->setValue(false);
669 break;
670 case eImagePreMultiplied:
671 _premult->setValue(true);
672 break;
673 case eImageUnPreMultiplied:
674 _premult->setValue(false);
675 break;
676 }
677 }
678 }
679 }
680
681 void
changedParam(const InstanceChangedArgs & args,const std::string & paramName)682 ColorMatrixPlugin::changedParam(const InstanceChangedArgs &args,
683 const std::string ¶mName)
684 {
685 if ( (paramName == kParamPremult) && (args.reason == eChangeUserEdit) ) {
686 _premultChanged->setValue(true);
687 }
688 }
689
690 mDeclarePluginFactory(ColorMatrixPluginFactory, {ofxsThreadSuiteCheck();}, {});
691 void
describe(ImageEffectDescriptor & desc)692 ColorMatrixPluginFactory::describe(ImageEffectDescriptor &desc)
693 {
694 // basic labels
695 desc.setLabel(kPluginName);
696 desc.setPluginGrouping(kPluginGrouping);
697 desc.setPluginDescription(kPluginDescription);
698
699 desc.addSupportedContext(eContextFilter);
700 desc.addSupportedContext(eContextGeneral);
701 desc.addSupportedContext(eContextPaint);
702 desc.addSupportedBitDepth(eBitDepthUByte);
703 desc.addSupportedBitDepth(eBitDepthUShort);
704 desc.addSupportedBitDepth(eBitDepthFloat);
705
706 // set a few flags
707 desc.setSingleInstance(false);
708 desc.setHostFrameThreading(false);
709 desc.setSupportsMultiResolution(kSupportsMultiResolution);
710 desc.setSupportsTiles(kSupportsTiles);
711 desc.setTemporalClipAccess(false);
712 desc.setRenderTwiceAlways(false);
713 desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
714 desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
715 desc.setRenderThreadSafety(kRenderThreadSafety);
716
717 #ifdef OFX_EXTENSIONS_NATRON
718 desc.setChannelSelector(ePixelComponentNone); // we have our own channel selector
719 #endif
720 }
721
722 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)723 ColorMatrixPluginFactory::describeInContext(ImageEffectDescriptor &desc,
724 ContextEnum context)
725 {
726 // Source clip only in the filter context
727 // create the mandated source clip
728 ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
729
730 srcClip->addSupportedComponent(ePixelComponentRGBA);
731 srcClip->addSupportedComponent(ePixelComponentRGB);
732 #ifdef OFX_EXTENSIONS_NATRON
733 srcClip->addSupportedComponent(ePixelComponentXY);
734 #endif
735 srcClip->addSupportedComponent(ePixelComponentAlpha);
736 srcClip->setTemporalClipAccess(false);
737 srcClip->setSupportsTiles(kSupportsTiles);
738 srcClip->setIsMask(false);
739
740 // create the mandated output clip
741 ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
742 dstClip->addSupportedComponent(ePixelComponentRGBA);
743 dstClip->addSupportedComponent(ePixelComponentRGB);
744 #ifdef OFX_EXTENSIONS_NATRON
745 dstClip->addSupportedComponent(ePixelComponentXY);
746 #endif
747 dstClip->addSupportedComponent(ePixelComponentAlpha);
748 dstClip->setSupportsTiles(kSupportsTiles);
749
750 ClipDescriptor *maskClip = (context == eContextPaint) ? desc.defineClip("Brush") : desc.defineClip("Mask");
751 maskClip->addSupportedComponent(ePixelComponentAlpha);
752 maskClip->setTemporalClipAccess(false);
753 if (context != eContextPaint) {
754 maskClip->setOptional(true);
755 }
756 maskClip->setSupportsTiles(kSupportsTiles);
757 maskClip->setIsMask(true);
758
759 // make some pages and to things in
760 PageParamDescriptor *page = desc.definePageParam("Controls");
761
762 {
763 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessR);
764 param->setLabel(kParamProcessRLabel);
765 param->setHint(kParamProcessRHint);
766 param->setDefault(true);
767 param->setLayoutHint(eLayoutHintNoNewLine, 1);
768 if (page) {
769 page->addChild(*param);
770 }
771 }
772 {
773 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessG);
774 param->setLabel(kParamProcessGLabel);
775 param->setHint(kParamProcessGHint);
776 param->setDefault(true);
777 param->setLayoutHint(eLayoutHintNoNewLine, 1);
778 if (page) {
779 page->addChild(*param);
780 }
781 }
782 {
783 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessB);
784 param->setLabel(kParamProcessBLabel);
785 param->setHint(kParamProcessBHint);
786 param->setDefault(true);
787 param->setLayoutHint(eLayoutHintNoNewLine, 1);
788 if (page) {
789 page->addChild(*param);
790 }
791 }
792 {
793 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessA);
794 param->setLabel(kParamProcessALabel);
795 param->setHint(kParamProcessAHint);
796 param->setDefault(true);
797 if (page) {
798 page->addChild(*param);
799 }
800 }
801
802 {
803 RGBAParamDescriptor *param = desc.defineRGBAParam(kParamOutputRedName);
804 param->setLabel(kParamOutputRedLabel);
805 param->setHint(kParamOutputRedHint);
806 param->setDefault(1.0, 0.0, 0.0, 0.0);
807 param->setAnimates(true); // can animate
808 if (page) {
809 page->addChild(*param);
810 }
811 }
812 {
813 RGBAParamDescriptor *param = desc.defineRGBAParam(kParamOutputGreenName);
814 param->setLabel(kParamOutputGreenLabel);
815 param->setHint(kParamOutputGreenHint);
816 param->setDefault(0.0, 1.0, 0.0, 0.0);
817 param->setAnimates(true); // can animate
818 if (page) {
819 page->addChild(*param);
820 }
821 }
822 {
823 RGBAParamDescriptor *param = desc.defineRGBAParam(kParamOutputBlueName);
824 param->setLabel(kParamOutputBlueLabel);
825 param->setHint(kParamOutputBlueHint);
826 param->setDefault(0.0, 0.0, 1.0, 0.0);
827 param->setAnimates(true); // can animate
828 if (page) {
829 page->addChild(*param);
830 }
831 }
832 {
833 RGBAParamDescriptor *param = desc.defineRGBAParam(kParamOutputAlphaName);
834 param->setLabel(kParamOutputAlphaLabel);
835 param->setHint(kParamOutputAlphaHint);
836 param->setDefault(0.0, 0.0, 0.0, 1.0);
837 param->setAnimates(true); // can animate
838 if (page) {
839 page->addChild(*param);
840 }
841 }
842 {
843 BooleanParamDescriptor *param = desc.defineBooleanParam(kParamClampBlack);
844 param->setLabel(kParamClampBlackLabel);
845 param->setHint(kParamClampBlackHint);
846 param->setDefault(true);
847 param->setAnimates(true);
848 param->setLayoutHint(eLayoutHintNoNewLine, 0);
849 if (page) {
850 page->addChild(*param);
851 }
852 }
853 {
854 BooleanParamDescriptor *param = desc.defineBooleanParam(kParamClampWhite);
855 param->setLabel(kParamClampWhiteLabel);
856 param->setHint(kParamClampWhiteHint);
857 param->setDefault(false);
858 param->setAnimates(true);
859 if (page) {
860 page->addChild(*param);
861 }
862 }
863
864 ofxsPremultDescribeParams(desc, page);
865 ofxsMaskMixDescribeParams(desc, page);
866
867 {
868 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamPremultChanged);
869 param->setDefault(false);
870 param->setIsSecretAndDisabled(true);
871 param->setAnimates(false);
872 param->setEvaluateOnChange(false);
873 if (page) {
874 page->addChild(*param);
875 }
876 }
877 } // ColorMatrixPluginFactory::describeInContext
878
879 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)880 ColorMatrixPluginFactory::createInstance(OfxImageEffectHandle handle,
881 ContextEnum /*context*/)
882 {
883 return new ColorMatrixPlugin(handle);
884 }
885
886 static ColorMatrixPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
887 mRegisterPluginFactoryInstance(p)
888
889 OFXS_NAMESPACE_ANONYMOUS_EXIT
890