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