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 MixViews plugin.
21  * Mix two views together.
22  */
23 
24 #include "ofxsProcessing.H"
25 #include "ofxsMacros.h"
26 #include "ofxsThreadSuite.h"
27 
28 using namespace OFX;
29 
30 OFXS_NAMESPACE_ANONYMOUS_ENTER
31 
32 #define kPluginName "MixViewsOFX"
33 #define kPluginGrouping "Views/Stereo"
34 #define kPluginDescription "Mix two views together."
35 #define kPluginIdentifier "net.sf.openfx.mixViewsPlugin"
36 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
37 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
38 
39 #define kSupportsTiles 1
40 #define kSupportsMultiResolution 1
41 #define kSupportsRenderScale 1
42 #define kSupportsMultipleClipPARs false
43 #define kSupportsMultipleClipDepths false
44 #define kRenderThreadSafety eRenderFullySafe
45 
46 #define kParamMix "mix"
47 #define kParamMixLabel "Mix"
48 #define kParamMixHint "Mix factor for the right view"
49 
50 // Base class for the RGBA and the Alpha processor
51 class MixViewsBase
52     : public ImageProcessor
53 {
54 protected:
55     const Image *_srcLeftImg;
56     const Image *_srcRightImg;
57     float _mix;
58 
59 public:
60     /** @brief no arg ctor */
MixViewsBase(ImageEffect & instance)61     MixViewsBase(ImageEffect &instance)
62         : ImageProcessor(instance)
63         , _srcLeftImg(NULL)
64         , _srcRightImg(NULL)
65         , _mix(0.f)
66     {
67     }
68 
69     /** @brief set the left src image */
setSrcLeftImg(const Image * v)70     void setSrcLeftImg(const Image *v) {_srcLeftImg = v; }
71 
72     /** @brief set the right src image */
setSrcRightImg(const Image * v)73     void setSrcRightImg(const Image *v) {_srcRightImg = v; }
74 
75     /** @brief set the mix factor of right view */
setMix(float v)76     void setMix(float v) {_mix = v; }
77 };
78 
79 // template to do the RGBA processing
80 template <class PIX, int nComponents, int max>
81 class ViewMixer
82     : public MixViewsBase
83 {
84 public:
85     // ctor
ViewMixer(ImageEffect & instance)86     ViewMixer(ImageEffect &instance)
87         : MixViewsBase(instance)
88     {
89     }
90 
91 private:
92     // and do some processing
multiThreadProcessImages(OfxRectI procWindow)93     void multiThreadProcessImages(OfxRectI procWindow)
94     {
95         for (int y = procWindow.y1; y < procWindow.y2; y++) {
96             if ( _effect.abort() ) {
97                 break;
98             }
99 
100             PIX *dstPix = (PIX *) _dstImg->getPixelAddress(procWindow.x1, y);
101 
102             for (int x = procWindow.x1; x < procWindow.x2; x++) {
103                 const PIX *srcLeftPix = (const PIX *)(_srcLeftImg ? _srcLeftImg->getPixelAddress(x, y) : 0);
104                 const PIX *srcRightPix = (const PIX *)(_srcRightImg ? _srcRightImg->getPixelAddress(x, y) : 0);
105 
106                 for (int c = 0; c < nComponents; c++) {
107                     dstPix[c] = (PIX)(( srcLeftPix ? srcLeftPix[c] : PIX() ) * (1 - _mix) + ( srcRightPix ? srcRightPix[c] : PIX() ) * _mix);
108                 }
109 
110                 // increment the dst pixel
111                 dstPix += nComponents;
112             }
113         }
114     }
115 };
116 
117 
118 ////////////////////////////////////////////////////////////////////////////////
119 /** @brief The plugin that does our work */
120 class MixViewsPlugin
121     : public ImageEffect
122 {
123 public:
124     /** @brief ctor */
MixViewsPlugin(OfxImageEffectHandle handle)125     MixViewsPlugin(OfxImageEffectHandle handle)
126         : ImageEffect(handle)
127         , _dstClip(NULL)
128         , _srcClip(NULL)
129         , _mix(NULL)
130     {
131         _dstClip = fetchClip(kOfxImageEffectOutputClipName);
132         assert( _dstClip && (!_dstClip->isConnected() || _dstClip->getPixelComponents() == ePixelComponentAlpha ||
133                              _dstClip->getPixelComponents() == ePixelComponentRGB ||
134                              _dstClip->getPixelComponents() == ePixelComponentRGBA) );
135         _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
136         assert( (!_srcClip && getContext() == eContextGenerator) ||
137                 ( _srcClip && (!_srcClip->isConnected() || _srcClip->getPixelComponents() ==  ePixelComponentAlpha ||
138                                _srcClip->getPixelComponents() == ePixelComponentRGB ||
139                                _srcClip->getPixelComponents() == ePixelComponentRGBA) ) );
140         _mix  = fetchDoubleParam(kParamMix);
141         assert(_mix);
142     }
143 
144 private:
145     /* Override the render */
146     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
147 
148     template <int nComponents>
149     void renderInternal(const RenderArguments &args, BitDepthEnum dstBitDepth);
150 
151     /* set up and run a processor */
152     void setupAndProcess(MixViewsBase &, const RenderArguments &args);
153 
154 private:
155     // do not need to delete these, the ImageEffect is managing them for us
156     Clip *_dstClip;
157     Clip *_srcClip;
158     DoubleParam *_mix;
159 };
160 
161 
162 ////////////////////////////////////////////////////////////////////////////////
163 /** @brief render for the filter */
164 
165 ////////////////////////////////////////////////////////////////////////////////
166 // basic plugin render function, just a skelington to instantiate templates from
167 
168 
169 /* set up and run a processor */
170 void
setupAndProcess(MixViewsBase & processor,const RenderArguments & args)171 MixViewsPlugin::setupAndProcess(MixViewsBase &processor,
172                                 const RenderArguments &args)
173 {
174     // get a dst image
175     auto_ptr<Image> dst( _dstClip->fetchImage(args.time) );
176 
177     if ( !dst.get() ) {
178         throwSuiteStatusException(kOfxStatFailed);
179     }
180     BitDepthEnum dstBitDepth    = dst->getPixelDepth();
181     PixelComponentEnum dstComponents  = dst->getPixelComponents();
182     if ( ( dstBitDepth != _dstClip->getPixelDepth() ) ||
183          ( dstComponents != _dstClip->getPixelComponents() ) ) {
184         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
185         throwSuiteStatusException(kOfxStatFailed);
186     }
187     if ( (dst->getRenderScale().x != args.renderScale.x) ||
188          ( dst->getRenderScale().y != args.renderScale.y) ||
189          ( ( dst->getField() != eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
190         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
191         throwSuiteStatusException(kOfxStatFailed);
192     }
193 
194     // fetch main input image
195     auto_ptr<const Image> srcLeft( ( _srcClip && _srcClip->isConnected() ) ?
196                                         _srcClip->fetchStereoscopicImage(args.time, 0) : 0 );
197     auto_ptr<const Image> srcRight( ( _srcClip && _srcClip->isConnected() ) ?
198                                          _srcClip->fetchStereoscopicImage(args.time, 1) : 0 );
199 
200     // make sure bit depths are sane
201     if ( srcLeft.get() ) {
202         if ( (srcLeft->getRenderScale().x != args.renderScale.x) ||
203              ( srcLeft->getRenderScale().y != args.renderScale.y) ||
204              ( ( srcLeft->getField() != eFieldNone) /* for DaVinci Resolve */ && ( srcLeft->getField() != args.fieldToRender) ) ) {
205             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
206             throwSuiteStatusException(kOfxStatFailed);
207         }
208         BitDepthEnum srcBitDepth      = srcLeft->getPixelDepth();
209         PixelComponentEnum srcComponents = srcLeft->getPixelComponents();
210 
211         // see if they have the same depths and bytes and all
212         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
213             throwSuiteStatusException(kOfxStatErrImageFormat);
214         }
215     }
216     if ( srcRight.get() ) {
217         if ( (srcRight->getRenderScale().x != args.renderScale.x) ||
218              ( srcRight->getRenderScale().y != args.renderScale.y) ||
219              ( ( srcRight->getField() != eFieldNone) /* for DaVinci Resolve */ && ( srcRight->getField() != args.fieldToRender) ) ) {
220             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
221             throwSuiteStatusException(kOfxStatFailed);
222         }
223         BitDepthEnum srcBitDepth      = srcRight->getPixelDepth();
224         PixelComponentEnum srcComponents = srcRight->getPixelComponents();
225 
226         // see if they have the same depths and bytes and all
227         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
228             throwSuiteStatusException(kOfxStatErrImageFormat);
229         }
230     }
231 
232     double mix = _mix->getValueAtTime(args.time);
233 
234     // set the images
235     processor.setDstImg( dst.get() );
236     processor.setSrcLeftImg( srcLeft.get() );
237     processor.setSrcRightImg( srcRight.get() );
238 
239     // set the render window
240     processor.setRenderWindow(args.renderWindow);
241 
242     // set the parameters
243     processor.setMix( (float)mix );
244 
245     // Call the base class process member, this will call the derived templated process code
246     processor.process();
247 } // MixViewsPlugin::setupAndProcess
248 
249 // the internal render function
250 template <int nComponents>
251 void
renderInternal(const RenderArguments & args,BitDepthEnum dstBitDepth)252 MixViewsPlugin::renderInternal(const RenderArguments &args,
253                                BitDepthEnum dstBitDepth)
254 {
255     switch (dstBitDepth) {
256     case eBitDepthUByte: {
257         ViewMixer<unsigned char, nComponents, 255> fred(*this);
258         setupAndProcess(fred, args);
259         break;
260     }
261     case eBitDepthUShort: {
262         ViewMixer<unsigned short, nComponents, 65535> fred(*this);
263         setupAndProcess(fred, args);
264         break;
265     }
266     case eBitDepthFloat: {
267         ViewMixer<float, nComponents, 1> fred(*this);
268         setupAndProcess(fred, args);
269         break;
270     }
271     default:
272         throwSuiteStatusException(kOfxStatErrUnsupported);
273     }
274 }
275 
276 // the overridden render function
277 void
render(const RenderArguments & args)278 MixViewsPlugin::render(const RenderArguments &args)
279 {
280     if ( !fetchSuite(kOfxVegasStereoscopicImageEffectSuite, 1, true) ) {
281         throwHostMissingSuiteException(kOfxVegasStereoscopicImageEffectSuite);
282     }
283 
284     assert( kSupportsMultipleClipPARs   || !_srcClip || _srcClip->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
285     assert( kSupportsMultipleClipDepths || !_srcClip || _srcClip->getPixelDepth()       == _dstClip->getPixelDepth() );
286     // instantiate the render code based on the pixel depth of the dst clip
287     BitDepthEnum dstBitDepth    = _dstClip->getPixelDepth();
288     PixelComponentEnum dstComponents  = _dstClip->getPixelComponents();
289 
290     // do the rendering
291     if (dstComponents == ePixelComponentRGBA) {
292         renderInternal<4>(args, dstBitDepth);
293     } else if (dstComponents == ePixelComponentRGB) {
294         renderInternal<3>(args, dstBitDepth);
295 #ifdef OFX_EXTENSIONS_NATRON
296     } else if (dstComponents == ePixelComponentXY) {
297         renderInternal<2>(args, dstBitDepth);
298 #endif
299     } else {
300         assert(dstComponents == ePixelComponentAlpha);
301         renderInternal<1>(args, dstBitDepth);
302     }
303 }
304 
305 mDeclarePluginFactory(MixViewsPluginFactory, {ofxsThreadSuiteCheck();}, {});
306 #if 0
307 void
308 MixViewsPluginFactory::load()
309 {
310     // we can't be used on hosts that don't support the stereoscopic suite
311     // returning an error here causes a blank menu entry in Nuke
312     //if (!fetchSuite(kOfxVegasStereoscopicImageEffectSuite, 1, true)) {
313     //    throwHostMissingSuiteException(kOfxVegasStereoscopicImageEffectSuite);
314     //}
315 }
316 #endif
317 
318 void
describe(ImageEffectDescriptor & desc)319 MixViewsPluginFactory::describe(ImageEffectDescriptor &desc)
320 {
321     // basic labels
322     desc.setLabel(kPluginName);
323     desc.setPluginGrouping(kPluginGrouping);
324     desc.setPluginDescription(kPluginDescription);
325 
326     // add the supported contexts, only filter at the moment
327     desc.addSupportedContext(eContextFilter);
328 
329     // add supported pixel depths
330     desc.addSupportedBitDepth(eBitDepthUByte);
331     desc.addSupportedBitDepth(eBitDepthUShort);
332     desc.addSupportedBitDepth(eBitDepthFloat);
333 
334     // set a few flags
335     desc.setSingleInstance(false);
336     desc.setHostFrameThreading(false);
337     desc.setSupportsMultiResolution(kSupportsMultiResolution);
338     desc.setSupportsTiles(kSupportsTiles);
339     desc.setTemporalClipAccess(false);
340     desc.setRenderTwiceAlways(false);
341     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
342     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
343     desc.setRenderThreadSafety(kRenderThreadSafety);
344     // returning an error here crashes Nuke
345     //if (!fetchSuite(kOfxVegasStereoscopicImageEffectSuite, 1, true)) {
346     //  throwHostMissingSuiteException(kOfxVegasStereoscopicImageEffectSuite);
347     //}
348 #ifdef OFX_EXTENSIONS_NATRON
349     desc.setChannelSelector(ePixelComponentNone);
350 #endif
351 }
352 
353 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum)354 MixViewsPluginFactory::describeInContext(ImageEffectDescriptor &desc,
355                                          ContextEnum /*context*/)
356 {
357     if ( !fetchSuite(kOfxVegasStereoscopicImageEffectSuite, 1, true) ) {
358         throwHostMissingSuiteException(kOfxVegasStereoscopicImageEffectSuite);
359     }
360 
361     // Source clip only in the filter context
362     // create the mandated source clip
363     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
364     srcClip->addSupportedComponent(ePixelComponentRGBA);
365     srcClip->addSupportedComponent(ePixelComponentRGB);
366 #ifdef OFX_EXTENSIONS_NATRON
367     srcClip->addSupportedComponent(ePixelComponentXY);
368 #endif
369     srcClip->addSupportedComponent(ePixelComponentAlpha);
370     srcClip->setTemporalClipAccess(false);
371     srcClip->setSupportsTiles(kSupportsTiles);
372     srcClip->setIsMask(false);
373 
374     // create the mandated output clip
375     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
376     dstClip->addSupportedComponent(ePixelComponentRGBA);
377     dstClip->addSupportedComponent(ePixelComponentRGB);
378 #ifdef OFX_EXTENSIONS_NATRON
379     dstClip->addSupportedComponent(ePixelComponentXY);
380 #endif
381     dstClip->addSupportedComponent(ePixelComponentAlpha);
382     dstClip->setSupportsTiles(kSupportsTiles);
383 
384     // make some pages and to things in
385     PageParamDescriptor *page = desc.definePageParam("Controls");
386 
387     // mix
388     {
389         DoubleParamDescriptor *param = desc.defineDoubleParam(kParamMix);
390         param->setLabel(kParamMixLabel);
391         param->setHint(kParamMixHint);
392         param->setDefault(0.);
393         param->setIncrement(0.01);
394         param->setRange(0., 1.);
395         param->setDisplayRange(0., 1.);
396         param->setDoubleType(eDoubleTypeScale);
397         param->setAnimates(true);
398         if (page) {
399             page->addChild(*param);
400         }
401     }
402 }
403 
404 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)405 MixViewsPluginFactory::createInstance(OfxImageEffectHandle handle,
406                                       ContextEnum /*context*/)
407 {
408     return new MixViewsPlugin(handle);
409 }
410 
411 static MixViewsPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
412 mRegisterPluginFactoryInstance(p)
413 
414 OFXS_NAMESPACE_ANONYMOUS_EXIT
415