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 Difference plugin.
21  */
22 
23 #include <cmath>
24 #include <cfloat> // DBL_MAX
25 #include <algorithm>
26 
27 #include "ofxsProcessing.H"
28 #include "ofxsMacros.h"
29 #include "ofxsThreadSuite.h"
30 
31 using namespace OFX;
32 
33 OFXS_NAMESPACE_ANONYMOUS_ENTER
34 
35 #define kPluginName "DifferenceOFX"
36 #define kPluginGrouping "Keyer"
37 #define kPluginDescription "Produce a rough matte from the difference of two input images.\n" \
38     "A is the background without the subject (clean plate). B is the subject with the background. RGB is copied from B, the difference is output to alpha, after applying offset and gain.\n" \
39     "See also: http://opticalenquiry.com/nuke/index.php?title=The_Keyer_Nodes#Difference and http://opticalenquiry.com/nuke/index.php?title=Keying_Tips"
40 
41 #define kPluginIdentifier "net.sf.openfx.DifferencePlugin"
42 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
43 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
44 
45 #define kSupportsTiles 1
46 #define kSupportsMultiResolution 1
47 #define kSupportsRenderScale 1
48 #define kSupportsMultipleClipPARs false
49 #define kSupportsMultipleClipDepths false
50 #define kRenderThreadSafety eRenderFullySafe
51 
52 #define kParamOffset "offset"
53 #define kParamOffsetLabel "Offset"
54 #define kParamOffsetHint "Value subtracted to each pixel of the output"
55 #define kParamGain "gain"
56 #define kParamGainLabel "Gain"
57 #define kParamGainHint "Multiply each pixel of the output by this value"
58 
59 #define kClipA "A"
60 #define kClipAHint "The background without the subject (a clean plate)."
61 #define kClipB "B"
62 #define kClipBHint "The subject with the background."
63 
64 
65 template <class T>
66 inline T
Clamp(T v,int min,int max)67 Clamp(T v,
68       int min,
69       int max)
70 {
71     if ( v < T(min) ) {
72         return T(min);
73     }
74     if ( v > T(max) ) {
75         return T(max);
76     }
77 
78     return v;
79 }
80 
81 class DifferencerBase
82     : public ImageProcessor
83 {
84 protected:
85     const Image *_srcImgA;
86     const Image *_srcImgB;
87     double _offset;
88     double _gain;
89 
90 public:
DifferencerBase(ImageEffect & instance)91     DifferencerBase(ImageEffect &instance)
92         : ImageProcessor(instance)
93         , _srcImgA(NULL)
94         , _srcImgB(NULL)
95         , _offset(0.)
96         , _gain(1.)
97     {
98     }
99 
setSrcImg(const Image * A,const Image * B)100     void setSrcImg(const Image *A,
101                    const Image *B) {_srcImgA = A; _srcImgB = B; }
102 
setValues(double offset,double gain)103     void setValues(double offset,
104                    double gain)
105     {
106         _offset = offset;
107         _gain = gain;
108     }
109 };
110 
111 
112 template <class PIX, int nComponents, int maxValue>
113 class Differencer
114     : public DifferencerBase
115 {
116 public:
Differencer(ImageEffect & instance)117     Differencer(ImageEffect &instance)
118         : DifferencerBase(instance)
119     {
120     }
121 
122 private:
multiThreadProcessImages(OfxRectI procWindow)123     void multiThreadProcessImages(OfxRectI procWindow)
124     {
125         for (int y = procWindow.y1; y < procWindow.y2; y++) {
126             if ( _effect.abort() ) {
127                 break;
128             }
129 
130             PIX *dstPix = (PIX *) _dstImg->getPixelAddress(procWindow.x1, y);
131 
132             for (int x = procWindow.x1; x < procWindow.x2; x++) {
133                 const PIX *srcPixA = (const PIX *)  (_srcImgA ? _srcImgA->getPixelAddress(x, y) : 0);
134                 const PIX *srcPixB = (const PIX *)  (_srcImgB ? _srcImgB->getPixelAddress(x, y) : 0);
135 
136                 if (srcPixA && srcPixB) {
137                     double diff = 0.;
138                     if (nComponents > 1) {
139                         for (int c = 0; c < nComponents - 1; ++c) {
140                             dstPix[c] = srcPixB[c];
141                             double d = srcPixB[c] - srcPixA[c];
142                             diff += d * d;
143                         }
144                     }
145                     diff = _gain * diff - _offset; // this seems to be the formula used in Nuke
146                     dstPix[nComponents - 1] = (PIX)std::max( 0., std::min(diff, (double)maxValue) );
147                 } else if (srcPixB && !srcPixA) {
148                     for (int c = 0; c < nComponents; ++c) {
149                         dstPix[c] = srcPixB[c];
150                     }
151                 } else {
152                     for (int c = 0; c < nComponents; ++c) {
153                         dstPix[c] = 0;
154                     }
155                 }
156                 dstPix += nComponents;
157             }
158         }
159     }
160 };
161 
162 ////////////////////////////////////////////////////////////////////////////////
163 /** @brief The plugin that does our work */
164 class DifferencePlugin
165     : public ImageEffect
166 {
167 public:
168 
169     /** @brief ctor */
DifferencePlugin(OfxImageEffectHandle handle)170     DifferencePlugin(OfxImageEffectHandle handle)
171         : ImageEffect(handle)
172         , _dstClip(NULL)
173         , _srcClipA(NULL)
174         , _srcClipB(NULL)
175     {
176         _dstClip = fetchClip(kOfxImageEffectOutputClipName);
177         assert( _dstClip && (!_dstClip->isConnected() || _dstClip->getPixelComponents() == ePixelComponentRGB || _dstClip->getPixelComponents() == ePixelComponentRGBA || _dstClip->getPixelComponents() == ePixelComponentAlpha) );
178         _srcClipA = fetchClip(kClipA);
179         assert( _srcClipA && (!_srcClipA->isConnected() || _srcClipA->getPixelComponents() == ePixelComponentRGB || _srcClipA->getPixelComponents() == ePixelComponentRGBA || _srcClipA->getPixelComponents() == ePixelComponentAlpha) );
180         _srcClipB = fetchClip(kClipB);
181         assert( _srcClipB && (!_srcClipB->isConnected() || _srcClipB->getPixelComponents() == ePixelComponentRGB || _srcClipB->getPixelComponents() == ePixelComponentRGBA || _srcClipB->getPixelComponents() == ePixelComponentAlpha) );
182         _offset = fetchDoubleParam(kParamOffset);
183         assert(_offset);
184         _gain = fetchDoubleParam(kParamGain);
185         assert(_gain);
186     }
187 
188 private:
189     /* Override the render */
190     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
191     virtual void getClipPreferences(ClipPreferencesSetter &clipPreferences) OVERRIDE FINAL;
192 
193     template <int nComponents>
194     void renderInternal(const RenderArguments &args, BitDepthEnum dstBitDepth);
195 
196     /* set up and run a processor */
197     void setupAndProcess(DifferencerBase &, const RenderArguments &args);
198 
199 private:
200     // do not need to delete these, the ImageEffect is managing them for us
201     Clip *_dstClip;
202     Clip *_srcClipA;
203     Clip *_srcClipB;
204     DoubleParam *_offset;
205     DoubleParam *_gain;
206 };
207 
208 ////////////////////////////////////////////////////////////////////////////////
209 /** @brief render for the filter */
210 
211 /* set up and run a processor */
212 void
setupAndProcess(DifferencerBase & processor,const RenderArguments & args)213 DifferencePlugin::setupAndProcess(DifferencerBase &processor,
214                                   const RenderArguments &args)
215 {
216     auto_ptr<Image> dst( _dstClip->fetchImage(args.time) );
217 
218     if ( !dst.get() ) {
219         throwSuiteStatusException(kOfxStatFailed);
220     }
221     BitDepthEnum dstBitDepth    = dst->getPixelDepth();
222     PixelComponentEnum dstComponents  = dst->getPixelComponents();
223     if ( ( dstBitDepth != _dstClip->getPixelDepth() ) ||
224          ( dstComponents != _dstClip->getPixelComponents() ) ) {
225         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
226         throwSuiteStatusException(kOfxStatFailed);
227     }
228     if ( (dst->getRenderScale().x != args.renderScale.x) ||
229          ( dst->getRenderScale().y != args.renderScale.y) ||
230          ( ( dst->getField() != eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
231         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
232         throwSuiteStatusException(kOfxStatFailed);
233     }
234     auto_ptr<const Image> srcA( ( _srcClipA && _srcClipA->isConnected() ) ?
235                                      _srcClipA->fetchImage(args.time) : 0 );
236     auto_ptr<const Image> srcB( ( _srcClipB && _srcClipB->isConnected() ) ?
237                                      _srcClipB->fetchImage(args.time) : 0 );
238     if ( srcA.get() ) {
239         if ( (srcA->getRenderScale().x != args.renderScale.x) ||
240              ( srcA->getRenderScale().y != args.renderScale.y) ||
241              ( ( srcA->getField() != eFieldNone) /* for DaVinci Resolve */ && ( srcA->getField() != args.fieldToRender) ) ) {
242             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
243             throwSuiteStatusException(kOfxStatFailed);
244         }
245         BitDepthEnum srcBitDepth      = srcA->getPixelDepth();
246         PixelComponentEnum srcComponents = srcA->getPixelComponents();
247         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
248             throwSuiteStatusException(kOfxStatErrImageFormat);
249         }
250     }
251 
252     if ( srcB.get() ) {
253         if ( (srcB->getRenderScale().x != args.renderScale.x) ||
254              ( srcB->getRenderScale().y != args.renderScale.y) ||
255              ( ( srcB->getField() != eFieldNone) /* for DaVinci Resolve */ && ( srcB->getField() != args.fieldToRender) ) ) {
256             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
257             throwSuiteStatusException(kOfxStatFailed);
258         }
259         BitDepthEnum srcBitDepth      = srcB->getPixelDepth();
260         PixelComponentEnum srcComponents = srcB->getPixelComponents();
261         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
262             throwSuiteStatusException(kOfxStatErrImageFormat);
263         }
264     }
265 
266     double offset;
267     double gain;
268     _offset->getValueAtTime(args.time, offset);
269     _gain->getValueAtTime(args.time, gain);
270     processor.setValues(offset, gain);
271     processor.setDstImg( dst.get() );
272     processor.setSrcImg( srcA.get(), srcB.get() );
273     processor.setRenderWindow(args.renderWindow);
274 
275     processor.process();
276 } // DifferencePlugin::setupAndProcess
277 
278 // the internal render function
279 template <int nComponents>
280 void
renderInternal(const RenderArguments & args,BitDepthEnum dstBitDepth)281 DifferencePlugin::renderInternal(const RenderArguments &args,
282                                  BitDepthEnum dstBitDepth)
283 {
284     switch (dstBitDepth) {
285     case eBitDepthUByte: {
286         Differencer<unsigned char, nComponents, 255> fred(*this);
287         setupAndProcess(fred, args);
288         break;
289     }
290     case eBitDepthUShort: {
291         Differencer<unsigned short, nComponents, 65535> fred(*this);
292         setupAndProcess(fred, args);
293         break;
294     }
295     case eBitDepthFloat: {
296         Differencer<float, nComponents, 1> fred(*this);
297         setupAndProcess(fred, args);
298         break;
299     }
300     default:
301         throwSuiteStatusException(kOfxStatErrUnsupported);
302     }
303 }
304 
305 // the overridden render function
306 void
render(const RenderArguments & args)307 DifferencePlugin::render(const RenderArguments &args)
308 {
309     // instantiate the render code based on the pixel depth of the dst clip
310     BitDepthEnum dstBitDepth    = _dstClip->getPixelDepth();
311     PixelComponentEnum dstComponents  = _dstClip->getPixelComponents();
312 
313     assert( kSupportsMultipleClipPARs   || _srcClipA->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
314     assert( kSupportsMultipleClipDepths || _srcClipA->getPixelDepth()       == _dstClip->getPixelDepth() );
315     assert( kSupportsMultipleClipPARs   || _srcClipB->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
316     assert( kSupportsMultipleClipDepths || _srcClipB->getPixelDepth()       == _dstClip->getPixelDepth() );
317     // do the rendering
318     if (dstComponents == ePixelComponentRGBA) {
319         renderInternal<4>(args, dstBitDepth);
320     } else if (dstComponents == ePixelComponentRGB) {
321         renderInternal<3>(args, dstBitDepth);
322 #ifdef OFX_EXTENSIONS_NATRON
323     } else if (dstComponents == ePixelComponentXY) {
324         renderInternal<2>(args, dstBitDepth);
325 #endif
326     } else {
327         assert(dstComponents == ePixelComponentAlpha);
328         renderInternal<1>(args, dstBitDepth);
329     }
330 }
331 
332 void
getClipPreferences(ClipPreferencesSetter & clipPreferences)333 DifferencePlugin::getClipPreferences(ClipPreferencesSetter &clipPreferences)
334 {
335     PixelComponentEnum outputComps = getDefaultOutputClipComponents();
336 
337     clipPreferences.setClipComponents(*_srcClipA, outputComps);
338     clipPreferences.setClipComponents(*_srcClipB, outputComps);
339 }
340 
341 mDeclarePluginFactory(DifferencePluginFactory, {ofxsThreadSuiteCheck();}, {});
342 void
describe(ImageEffectDescriptor & desc)343 DifferencePluginFactory::describe(ImageEffectDescriptor &desc)
344 {
345     // basic labels
346     desc.setLabel(kPluginName);
347     desc.setPluginGrouping(kPluginGrouping);
348     desc.setPluginDescription(kPluginDescription);
349 
350     //desc.addSupportedContext(eContextFilter);
351     desc.addSupportedContext(eContextGeneral);
352     desc.addSupportedBitDepth(eBitDepthUByte);
353     desc.addSupportedBitDepth(eBitDepthUShort);
354     desc.addSupportedBitDepth(eBitDepthFloat);
355 
356     // set a few flags
357     desc.setSingleInstance(false);
358     desc.setHostFrameThreading(false);
359     desc.setSupportsMultiResolution(kSupportsMultiResolution);
360     desc.setSupportsTiles(kSupportsTiles);
361     desc.setTemporalClipAccess(false);
362     desc.setRenderTwiceAlways(false);
363     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
364     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
365     desc.setRenderThreadSafety(kRenderThreadSafety);
366 #ifdef OFX_EXTENSIONS_NATRON
367     desc.setChannelSelector(ePixelComponentAlpha);
368 #endif
369 }
370 
371 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum)372 DifferencePluginFactory::describeInContext(ImageEffectDescriptor &desc,
373                                            ContextEnum /*context*/)
374 {
375     ClipDescriptor* srcClipB = desc.defineClip(kClipB);
376 
377     srcClipB->setHint(kClipBHint);
378     srcClipB->addSupportedComponent( ePixelComponentRGBA );
379     srcClipB->addSupportedComponent( ePixelComponentRGB );
380     srcClipB->addSupportedComponent( ePixelComponentXY );
381     srcClipB->addSupportedComponent( ePixelComponentAlpha );
382     srcClipB->setTemporalClipAccess(false);
383     srcClipB->setSupportsTiles(kSupportsTiles);
384     srcClipB->setOptional(false);
385 
386     ClipDescriptor* srcClipA = desc.defineClip(kClipA);
387     srcClipA->setHint(kClipAHint);
388     srcClipA->addSupportedComponent( ePixelComponentRGBA );
389     srcClipA->addSupportedComponent( ePixelComponentRGB );
390     srcClipA->addSupportedComponent( ePixelComponentXY );
391     srcClipA->addSupportedComponent( ePixelComponentAlpha );
392     srcClipA->setTemporalClipAccess(false);
393     srcClipA->setSupportsTiles(kSupportsTiles);
394     srcClipA->setOptional(false);
395 
396     // create the mandated output clip
397     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
398     dstClip->addSupportedComponent(ePixelComponentRGBA);
399     dstClip->addSupportedComponent(ePixelComponentRGB);
400 #ifdef OFX_EXTENSIONS_NATRON
401     dstClip->addSupportedComponent(ePixelComponentXY);
402 #endif
403     dstClip->addSupportedComponent(ePixelComponentAlpha);
404     dstClip->setSupportsTiles(kSupportsTiles);
405 
406     // make some pages and to things in
407     PageParamDescriptor *page = desc.definePageParam("Controls");
408 
409     // offset
410     {
411         DoubleParamDescriptor *param = desc.defineDoubleParam(kParamOffset);
412         param->setLabel(kParamOffsetLabel);
413         param->setHint(kParamOffsetHint);
414         param->setDefault(0.);
415         param->setIncrement(0.005);
416         param->setRange(-DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
417         param->setDisplayRange(0., 1.);
418         if (page) {
419             page->addChild(*param);
420         }
421     }
422 
423     // gain
424     {
425         DoubleParamDescriptor *param = desc.defineDoubleParam(kParamGain);
426         param->setLabel(kParamGainLabel);
427         param->setHint(kParamGainHint);
428         param->setDefault(1.);
429         param->setIncrement(0.005);
430         param->setRange(-DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
431         param->setDisplayRange(0., 1.);
432         param->setDoubleType(eDoubleTypeScale);
433         if (page) {
434             page->addChild(*param);
435         }
436     }
437 } // DifferencePluginFactory::describeInContext
438 
439 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)440 DifferencePluginFactory::createInstance(OfxImageEffectHandle handle,
441                                         ContextEnum /*context*/)
442 {
443     return new DifferencePlugin(handle);
444 }
445 
446 static DifferencePluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
447 mRegisterPluginFactoryInstance(p)
448 
449 OFXS_NAMESPACE_ANONYMOUS_EXIT
450