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