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 CImgPlasma plugin.
21  */
22 
23 #include <memory>
24 #include <cmath>
25 #include <cstring>
26 #include <cfloat> // DBL_MAX
27 #include <algorithm>
28 #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
29 #include <windows.h>
30 #endif
31 
32 #include "ofxsProcessing.H"
33 #include "ofxsMaskMix.h"
34 #include "ofxsMacros.h"
35 #include "ofxsCoords.h"
36 #include "ofxsCopier.h"
37 
38 #include "CImgFilter.h"
39 
40 using namespace OFX;
41 
42 OFXS_NAMESPACE_ANONYMOUS_ENTER
43 
44 #define kPluginName          "PlasmaCImg"
45 #define kPluginGrouping      "Draw"
46 #define kPluginDescription \
47     "Draw a random plasma texture (using the mid-point algorithm).\n" \
48     "\n" \
49     "Uses the 'draw_plasma' function from the CImg library, modified so that noise is reproductible at each render..\n" \
50     "CImg is a free, open-source library distributed under the CeCILL-C " \
51     "(close to the GNU LGPL) or CeCILL (compatible with the GNU GPL) licenses. " \
52     "It can be used in commercial applications (see http://cimg.eu)."
53 
54 #define kPluginIdentifier    "net.sf.cimg.CImgPlasma"
55 // History:
56 // version 1.0: initial version
57 // version 2.0: use kNatronOfxParamProcess* parameters
58 #define kPluginVersionMajor 2 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
59 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
60 
61 #define kSupportsComponentRemapping 1
62 #define kSupportsTiles 0 // Plasma effect can only be computed on the whole image
63 #define kSupportsMultiResolution 1
64 #define kSupportsRenderScale 1
65 #define kSupportsMultipleClipPARs false
66 #define kSupportsMultipleClipDepths false
67 #define kRenderThreadSafety eRenderFullySafe
68 #ifdef cimg_use_openmp
69 #define kHostFrameThreading false
70 #else
71 #define kHostFrameThreading true
72 #endif
73 #define kSupportsRGBA true
74 #define kSupportsRGB true
75 #define kSupportsXY true
76 #define kSupportsAlpha true
77 
78 #define kParamAlpha "alpha"
79 #define kParamAlphaLabel "Alpha"
80 #define kParamAlphaHint "Alpha-parameter, in intensity units (>=0)."
81 #define kParamAlphaDefault 0.002 // 0.5/255
82 #define kParamAlphaMin 0.
83 #define kParamAlphaMax 0.02 // 5./255
84 #define kParamAlphaIncrement 0.0005
85 
86 #define kParamBeta "beta"
87 #define kParamBetaLabel "Beta"
88 #define kParamBetaHint "Beta-parameter, in intensity units (>=0)."
89 #define kParamBetaDefault 0.
90 #define kParamBetaMin 0.
91 #define kParamBetaMax 0.5 // 100./255
92 #define kParamBetaIncrement 0.01
93 
94 #define kParamScale "scale"
95 #define kParamScaleLabel "Scale"
96 #define kParamScaleHint "Noise scale, as a power of two (>=0)."
97 #define kParamScaleDefault 8
98 #define kParamScaleMin 2
99 #define kParamScaleMax 10
100 
101 #define kParamOffset "offset"
102 #define kParamOffsetLabel "Offset"
103 #define kParamOffsetHint "Offset to add to the plasma noise."
104 #define kParamOffsetGeneratorDefault 0.5
105 
106 #define kParamSeed "seed"
107 #define kParamSeedLabel "Seed"
108 #define kParamSeedHint "Random seed: change this if you want different instances to have different noise."
109 
110 #define kParamStaticSeed "staticSeed"
111 #define kParamStaticSeedLabel "Static Seed"
112 #define kParamStaticSeedHint "When enabled, the dither pattern remains the same for every frame producing a constant noise effect."
113 
114 #define cimg_noise_internal
115 
116 #ifdef cimg_noise_internal
117 
118 // the following is cimg_library::CImg::draw_plasma(), but with a fixed seed and pseudo-random function
119 //! Add random noise to pixel values.
120 
121 #define T cimgpix_t
122 #define Tfloat cimgpixfloat_t
123 using namespace cimg_library;
124 
125 // DIfferences with the original cimg_library::CImg::noise:
126 // - static function
127 // - replaced *this with img
128 // - replaced cimg::rand with cimg_rand, etc.
129 
130 //! Draw a random plasma texture.
131 /**
132  \param alpha Alpha-parameter.
133  \param beta Beta-parameter.
134  \param scale Scale-parameter.
135  \note Use the mid-point algorithm to render.
136  **/
137 CImg<T>&
draw_plasma(CImg<T> & img,const float alpha,const float beta,const unsigned int scale,unsigned int seed,int x_1,int y_1)138 draw_plasma(CImg<T>&img, const float alpha/*=1*/, const float beta/*=0*/, const unsigned int scale/*=8*/, unsigned int seed, int x_1, int y_1)
139 {
140     if (img.is_empty()) {
141         return img;
142     }
143     const int w = img.width();
144     const int h = img.height();
145     const Tfloat m = (Tfloat)cimg::type<T>::min();
146     const Tfloat M = (Tfloat)cimg::type<T>::max();
147     cimg_forZC(img,z,c) {
148         CImg<T> ref = img.get_shared_slice(z,c);
149         for (int delta = 1<<std::min(scale,31U); delta>1; delta>>=1) {
150             const int delta2 = delta>>1;
151             const float r = alpha*delta + beta;
152 
153             // Square step.
154             for (int y0 = 0; y0<h; y0+=delta)
155                 for (int x0 = 0; x0<w; x0+=delta) {
156                     const int x1 = (x0 + delta)%w;
157                     const int y1 = (y0 + delta)%h;
158                     const int xc = (x0 + delta2)%w;
159                     const int yc = (y0 + delta2)%h;
160                     const Tfloat val = (Tfloat)(0.25f*(ref(x0,y0) + ref(x0,y1) + ref(x0,y1) + ref(x1,y1)) +
161                                                 r*cimg_rand(seed, xc + x_1, yc + y_1, c,-1,1));
162                     ref(xc,yc) = (T)(val<m?m:val>M?M:val);
163                 }
164 
165             // Diamond steps.
166             for (int y = -delta2; y<h; y+=delta)
167                 for (int x0=0; x0<w; x0+=delta) {
168                     const int y0 = cimg::mod(y,h);
169                     const int x1 = (x0 + delta)%w;
170                     const int y1 = (y + delta)%h;
171                     const int xc = (x0 + delta2)%w;
172                     const int yc = (y + delta2)%h;
173                     const Tfloat val = (Tfloat)(0.25f*(ref(xc,y0) + ref(x0,yc) + ref(xc,y1) + ref(x1,yc)) +
174                                                 r*cimg_rand(seed, xc + x_1, yc + y_1, c,-1,1));
175                     ref(xc,yc) = (T)(val<m?m:val>M?M:val);
176                 }
177             for (int y0 = 0; y0<h; y0+=delta)
178                 for (int x = -delta2; x<w; x+=delta) {
179                     const int x0 = cimg::mod(x,w);
180                     const int x1 = (x + delta)%w;
181                     const int y1 = (y0 + delta)%h;
182                     const int xc = (x + delta2)%w;
183                     const int yc = (y0 + delta2)%h;
184                     const Tfloat val = (Tfloat)(0.25f*(ref(xc,y0) + ref(x0,yc) + ref(xc,y1) + ref(x1,yc)) +
185                                                 r*cimg_rand(seed, xc + x_1, yc + y_1, c,-1,1));
186                     ref(xc,yc) = (T)(val<m?m:val>M?M:val);
187                 }
188             for (int y = -delta2; y<h; y+=delta)
189                 for (int x = -delta2; x<w; x+=delta) {
190                     const int x0 = cimg::mod(x,w);
191                     const int y0 = cimg::mod(y,h);
192                     const int x1 = (x + delta)%w;
193                     const int y1 = (y + delta)%h;
194                     const int xc = (x + delta2)%w;
195                     const int yc = (y + delta2)%h;
196                     const Tfloat val = (Tfloat)(0.25f*(ref(xc,y0) + ref(x0,yc) + ref(xc,y1) + ref(x1,yc)) +
197                                                 r*cimg_rand(seed, xc + x_1, yc + y_1, c,-1,1));
198                     ref(xc,yc) = (T)(val<m?m:val>M?M:val);
199                 }
200         }
201     }
202     return img;
203 }
204 #endif
205 
206 /// Plasma plugin
207 struct CImgPlasmaParams
208 {
209     double alpha;
210     double beta;
211     int scale;
212     double offset;
213     int seed;
214     bool staticSeed;
215 };
216 
217 class CImgPlasmaPlugin
218     : public CImgFilterPluginHelper<CImgPlasmaParams, true>
219 {
220 public:
221 
CImgPlasmaPlugin(OfxImageEffectHandle handle)222     CImgPlasmaPlugin(OfxImageEffectHandle handle)
223         : CImgFilterPluginHelper<CImgPlasmaParams, true>(handle, /*usesMask=*/false, kSupportsComponentRemapping, kSupportsTiles, kSupportsMultiResolution, kSupportsRenderScale, /*defaultUnpremult=*/ true)
224     {
225         _alpha  = fetchDoubleParam(kParamAlpha);
226         _beta  = fetchDoubleParam(kParamBeta);
227         _scale = fetchIntParam(kParamScale);
228         _offset = fetchDoubleParam(kParamOffset);
229         _seed   = fetchIntParam(kParamSeed);
230         _staticSeed = fetchBooleanParam(kParamStaticSeed);
231         assert(_seed && _staticSeed);
232         assert(_alpha && _beta && _scale);
233     }
234 
getValuesAtTime(double time,CImgPlasmaParams & params)235     virtual void getValuesAtTime(double time,
236                                  CImgPlasmaParams& params) OVERRIDE FINAL
237     {
238         _alpha->getValueAtTime(time, params.alpha);
239         _beta->getValueAtTime(time, params.beta);
240         _scale->getValueAtTime(time, params.scale);
241         _offset->getValueAtTime(time, params.offset);
242         _seed->getValueAtTime(time, params.seed);
243         _staticSeed->getValueAtTime(time, params.staticSeed);
244     }
245 
246     // compute the roi required to compute rect, given params. This roi is then intersected with the image rod.
247     // only called if mix != 0.
getRoI(const OfxRectI & rect,const OfxPointD & renderScale,const CImgPlasmaParams & params,OfxRectI * roi)248     virtual void getRoI(const OfxRectI& rect,
249                         const OfxPointD& renderScale,
250                         const CImgPlasmaParams& params,
251                         OfxRectI* roi) OVERRIDE FINAL
252     {
253         int delta_pix = 1 << std::max( 0, params.scale - (int)Coords::mipmapLevelFromScale(renderScale.x) );
254 
255         roi->x1 = rect.x1 - delta_pix;
256         roi->x2 = rect.x2 + delta_pix;
257         roi->y1 = rect.y1 - delta_pix;
258         roi->y2 = rect.y2 + delta_pix;
259     }
260 
render(const RenderArguments & args,const CImgPlasmaParams & params,int x1,int y1,cimg_library::CImg<cimgpix_t> &,cimg_library::CImg<cimgpix_t> & cimg,int)261     virtual void render(const RenderArguments &args,
262                         const CImgPlasmaParams& params,
263                         int x1,
264                         int y1,
265                         cimg_library::CImg<cimgpix_t>& /*mask*/,
266                         cimg_library::CImg<cimgpix_t>& cimg,
267                         int /*alphaChannel*/) OVERRIDE FINAL
268     {
269         // PROCESSING.
270         // This is the only place where the actual processing takes place
271 #ifdef cimg_noise_internal
272         unsigned int seed = cimg_hash(params.seed);
273         if (!params.staticSeed) {
274             float time_f = args.time;
275 
276             // set the seed based on the current time, and double it we get difference seeds on different fields
277             seed = cimg_hash( *( (unsigned int*)&time_f ) ^ seed );
278         }
279         draw_plasma(cimg, (float)params.alpha / args.renderScale.x, (float)params.beta / args.renderScale.x, std::max( 0, params.scale - (int)Coords::mipmapLevelFromScale(args.renderScale.x) ), seed, x1, y1);
280 #else
281         cimg_library::cimg::srand( (unsigned int)args.time + (unsigned int)params.seed );
282 
283         cimg.draw_plasma( (float)params.alpha / args.renderScale.x, (float)params.beta / args.renderScale.x, std::max( 0, params.scale - (int)Coords::mipmapLevelFromScale(args.renderScale.x) ) );
284 #endif
285         if (params.offset != 0.) {
286             cimg += params.offset;
287         }
288     }
289 
290     //virtual bool isIdentity(const IsIdentityArguments &args, const CImgPlasmaParams& params) OVERRIDE FINAL
291     //{
292     //    return (params.scale - (int)Coords::mipmapLevelFromScale(args.renderScale.x) <= 0);
293     //};
294 
295     /* Override the clip preferences, we need to say we are setting the frame varying flag */
getClipPreferences(ClipPreferencesSetter & clipPreferences)296     virtual void getClipPreferences(ClipPreferencesSetter &clipPreferences) OVERRIDE FINAL
297     {
298         bool staticSeed = _staticSeed->getValue();
299         if (!staticSeed) {
300             clipPreferences.setOutputFrameVarying(true);
301             clipPreferences.setOutputHasContinuousSamples(true);
302         }
303     }
304 
305 private:
306 
307     // params
308     DoubleParam *_alpha;
309     DoubleParam *_beta;
310     IntParam *_scale;
311     DoubleParam *_offset;
312     IntParam *_seed;
313     BooleanParam* _staticSeed;
314 };
315 
316 
317 mDeclarePluginFactory(CImgPlasmaPluginFactory, {ofxsThreadSuiteCheck();}, {});
318 
319 void
describe(ImageEffectDescriptor & desc)320 CImgPlasmaPluginFactory::describe(ImageEffectDescriptor& desc)
321 {
322     // basic labels
323     desc.setLabel(kPluginName);
324     desc.setPluginGrouping(kPluginGrouping);
325     desc.setPluginDescription(kPluginDescription);
326 
327     // add supported context
328     desc.addSupportedContext(eContextFilter);
329     desc.addSupportedContext(eContextGenerator);
330     desc.addSupportedContext(eContextGeneral);
331 
332     // add supported pixel depths
333     //desc.addSupportedBitDepth(eBitDepthUByte);
334     //desc.addSupportedBitDepth(eBitDepthUShort);
335     desc.addSupportedBitDepth(eBitDepthFloat);
336 
337     // set a few flags
338     desc.setSingleInstance(false);
339     desc.setHostFrameThreading(kHostFrameThreading);
340     desc.setSupportsMultiResolution(kSupportsMultiResolution);
341     desc.setSupportsTiles(kSupportsTiles);
342     desc.setTemporalClipAccess(false);
343     desc.setRenderTwiceAlways(true);
344     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
345     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
346     desc.setRenderThreadSafety(kRenderThreadSafety);
347 }
348 
349 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)350 CImgPlasmaPluginFactory::describeInContext(ImageEffectDescriptor& desc,
351                                            ContextEnum context)
352 {
353     // create the clips and params
354     PageParamDescriptor *page = CImgPlasmaPlugin::describeInContextBegin(desc, context,
355                                                                          kSupportsRGBA,
356                                                                          kSupportsRGB,
357                                                                          kSupportsXY,
358                                                                          kSupportsAlpha,
359                                                                          kSupportsTiles,
360                                                                          /*processRGB=*/ true,
361                                                                          /*processAlpha*/ false,
362                                                                          /*processIsSecret=*/ false);
363 
364     {
365         DoubleParamDescriptor *param = desc.defineDoubleParam(kParamAlpha);
366         param->setLabel(kParamAlphaLabel);
367         param->setHint(kParamAlphaHint);
368         param->setRange(kParamAlphaMin, kParamAlphaMax);
369         param->setDisplayRange(kParamAlphaMin, kParamAlphaMax);
370         param->setDefault(kParamAlphaDefault);
371         param->setIncrement(kParamAlphaIncrement);
372         param->setDigits(4);
373         if (page) {
374             page->addChild(*param);
375         }
376     }
377     {
378         DoubleParamDescriptor *param = desc.defineDoubleParam(kParamBeta);
379         param->setLabel(kParamBetaLabel);
380         param->setHint(kParamBetaHint);
381         param->setRange(kParamBetaMin, kParamBetaMax);
382         param->setDisplayRange(kParamBetaMin, kParamBetaMax);
383         param->setDefault(kParamBetaDefault);
384         param->setIncrement(kParamBetaIncrement);
385         param->setDigits(2);
386         if (page) {
387             page->addChild(*param);
388         }
389     }
390     {
391         IntParamDescriptor *param = desc.defineIntParam(kParamScale);
392         param->setLabel(kParamScaleLabel);
393         param->setHint(kParamScaleHint);
394         param->setRange(kParamScaleMin, kParamScaleMax);
395         param->setDisplayRange(kParamScaleMin, kParamScaleMax);
396         param->setDefault(kParamScaleDefault);
397         if (page) {
398             page->addChild(*param);
399         }
400     }
401     {
402         DoubleParamDescriptor *param = desc.defineDoubleParam(kParamOffset);
403         param->setLabel(kParamOffsetLabel);
404         param->setHint(kParamOffsetHint);
405         param->setRange(-DBL_MAX, DBL_MAX);
406         param->setDisplayRange(0., 1.);
407         if (context == eContextGenerator) {
408             param->setDefault(kParamOffsetGeneratorDefault);
409         }
410         if (page) {
411             page->addChild(*param);
412         }
413     }
414     // seed
415     {
416         IntParamDescriptor *param = desc.defineIntParam(kParamSeed);
417         param->setLabel(kParamSeedLabel);
418         param->setHint(kParamSeedHint);
419         param->setDefault(2000);
420         param->setAnimates(true); // can animate
421         param->setLayoutHint(eLayoutHintNoNewLine, 1);
422         if (page) {
423             page->addChild(*param);
424         }
425     }
426     {
427         BooleanParamDescriptor *param = desc.defineBooleanParam(kParamStaticSeed);
428         param->setLabel(kParamStaticSeedLabel);
429         param->setHint(kParamStaticSeedHint);
430         param->setDefault(true);
431         desc.addClipPreferencesSlaveParam(*param);
432         if (page) {
433             page->addChild(*param);
434         }
435     }
436 
437     CImgPlasmaPlugin::describeInContextEnd(desc, context, page);
438 } // CImgPlasmaPluginFactory::describeInContext
439 
440 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)441 CImgPlasmaPluginFactory::createInstance(OfxImageEffectHandle handle,
442                                         ContextEnum /*context*/)
443 {
444     return new CImgPlasmaPlugin(handle);
445 }
446 
447 static CImgPlasmaPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
448 mRegisterPluginFactoryInstance(p)
449 
450 OFXS_NAMESPACE_ANONYMOUS_EXIT
451