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