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 CImgSmooth plugin.
21 */
22
23 #include <memory>
24 #include <cmath>
25 #include <cstring>
26 #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
27 #include <windows.h>
28 #endif
29
30 #include "ofxsProcessing.H"
31 #include "ofxsMaskMix.h"
32 #include "ofxsMacros.h"
33 #include "ofxsCoords.h"
34 #include "ofxsCopier.h"
35
36 #include "CImgFilter.h"
37
38 using namespace OFX;
39
40 OFXS_NAMESPACE_ANONYMOUS_ENTER
41
42 #define kPluginName "SmoothAnisotropicCImg"
43 #define kPluginGrouping "Filter"
44 #define kPluginDescription \
45 "Smooth/Denoise input stream using anisotropic PDE-based smoothing.\n" \
46 "Uses the 'blur_anisotropic' function from the CImg library.\n" \
47 "CImg is a free, open-source library distributed under the CeCILL-C " \
48 "(close to the GNU LGPL) or CeCILL (compatible with the GNU GPL) licenses. " \
49 "It can be used in commercial applications (see http://cimg.eu)."
50
51 #define kPluginIdentifier "net.sf.cimg.CImgSmooth"
52 // History:
53 // version 1.0: initial version
54 // version 2.0: use kNatronOfxParamProcess* parameters
55 #define kPluginVersionMajor 2 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
56 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
57
58 #define kSupportsComponentRemapping 1
59 #define kSupportsTiles 1
60 #define kSupportsMultiResolution 1
61 #define kSupportsRenderScale 1
62 #define kSupportsMultipleClipPARs false
63 #define kSupportsMultipleClipDepths false
64 #define kRenderThreadSafety eRenderFullySafe
65 #ifdef cimg_use_openmp
66 #define kHostFrameThreading false
67 #else
68 #define kHostFrameThreading true
69 #endif
70 #define kSupportsRGBA true
71 #define kSupportsRGB true
72 #define kSupportsXY true
73 #define kSupportsAlpha true
74
75 #define kParamAmplitude "amplitude"
76 #define kParamAmplitudeLabel "Amplitude"
77 #define kParamAmplitudeHint "Amplitude of the smoothing, in pixel units (>=0). This is the maximum length of streamlines used to smooth the data."
78 #define kParamAmplitudeDefault 60.0
79
80 #define kParamSharpness "sharpness"
81 #define kParamSharpnessLabel "Sharpness"
82 #define kParamSharpnessHint "Contour preservation (>=0)"
83 #define kParamSharpnessDefault 0.7
84
85 #define kParamAnisotropy "anisotropy"
86 #define kParamAnisotropyLabel "Anisotropy"
87 #define kParamAnisotropyHint "Smoothing anisotropy (0<=a<=1)"
88 #define kParamAnisotropyDefault 0.3
89
90 #define kParamAlpha "alpha"
91 #define kParamAlphaLabel "Gradient Smoothness"
92 #define kParamAlphaHint "Noise scale, in pixels units (>=0)"
93 #define kParamAlphaDefault 0.6
94
95 #define kParamSigma "sigma"
96 #define kParamSigmaLabel "Tensor Smoothness"
97 #define kParamSigmaHint "Geometry regularity, in pixels units (>=0)"
98 #define kParamSigmaDefault 1.1
99
100 #define kParamDl "dl"
101 #define kParamDlLabel "Spatial Precision"
102 #define kParamDlHint "Spatial discretization, in pixel units (0<=dl<=1)"
103 #define kParamDlDefault 0.8
104
105 #define kParamDa "da"
106 #define kParamDaLabel "Angular Precision"
107 #define kParamDaHint "Angular integration step, in degrees (0<=da<=90). If da=0, Iterated oriented Laplacians is used instead of LIC-based smoothing."
108 #define kParamDaDefault 30.0
109
110 #define kParamGaussPrec "prec"
111 #define kParamGaussPrecLabel "Value Precision"
112 #define kParamGaussPrecHint "Precision of the diffusion process (>0)."
113 #define kParamGaussPrecDefault 2.0
114
115 #define kParamInterp "interpolation"
116 #define kParamInterpLabel "Interpolation"
117 #define kParamInterpHint "Interpolation type"
118 #define kParamInterpOptionNearest "Nearest-neighbor", "Nearest-neighbor.", "nearest"
119 #define kParamInterpOptionLinear "Linear", "Linear interpolation.", "linear"
120 #define kParamInterpOptionRungeKutta "Runge-Kutta", "Runge-Kutta interpolation.", "rungekutta"
121 #define kParamInterpDefault eInterpNearest
122 enum InterpEnum
123 {
124 eInterpNearest = 0,
125 eInterpLinear,
126 eInterpRungeKutta,
127 };
128
129 #define kParamFastApprox "is_fast_approximation"
130 #define kParamFastApproxLabel "Fast Approximation"
131 #define kParamFastApproxHint "Tells if a fast approximation of the gaussian function is used or not"
132 #define kParamFastApproxDefault true
133
134 #define kParamIterations "iterations"
135 #define kParamIterationsLabel "Iterations"
136 #define kParamIterationsHint "Number of iterations."
137 #define kParamIterationsDefault 1
138
139 #define kParamThinBrush "thinBrush"
140 #define kParamThinBrushLabel "Set Thin Brush Defaults"
141 #define kParamThinBrushHint "Set the defaults to the value of the Thin Brush filter by PhotoComiX, as featured in the G'MIC Gimp plugin."
142
143 /// Smooth plugin
144 struct CImgSmoothParams
145 {
146 double amplitude;
147 double sharpness;
148 double anisotropy;
149 double alpha;
150 double sigma;
151 double dl;
152 double da;
153 double gprec;
154 int interp_i;
155 //InterpEnum interp;
156 bool fast_approx;
157 int iterations;
158 };
159
160 class CImgSmoothPlugin
161 : public CImgFilterPluginHelper<CImgSmoothParams, false>
162 {
163 public:
164
CImgSmoothPlugin(OfxImageEffectHandle handle)165 CImgSmoothPlugin(OfxImageEffectHandle handle)
166 : CImgFilterPluginHelper<CImgSmoothParams, false>(handle, /*usesMask=*/false, kSupportsComponentRemapping, kSupportsTiles, kSupportsMultiResolution, kSupportsRenderScale, /*defaultUnpremult=*/ true)
167 {
168 _amplitude = fetchDoubleParam(kParamAmplitude);
169 _sharpness = fetchDoubleParam(kParamSharpness);
170 _anisotropy = fetchDoubleParam(kParamAnisotropy);
171 _alpha = fetchDoubleParam(kParamAlpha);
172 _sigma = fetchDoubleParam(kParamSigma);
173 _dl = fetchDoubleParam(kParamDl);
174 _da = fetchDoubleParam(kParamDa);
175 _gprec = fetchDoubleParam(kParamGaussPrec);
176 _interp = fetchChoiceParam(kParamInterp);
177 _fast_approx = fetchBooleanParam(kParamFastApprox);
178 _iterations = fetchIntParam(kParamIterations);
179 assert(_amplitude && _sharpness && _anisotropy && _alpha && _sigma && _dl && _da && _gprec && _interp && _fast_approx && _iterations);
180 }
181
getValuesAtTime(double time,CImgSmoothParams & params)182 virtual void getValuesAtTime(double time,
183 CImgSmoothParams& params) OVERRIDE FINAL
184 {
185 _amplitude->getValueAtTime(time, params.amplitude);
186 _sharpness->getValueAtTime(time, params.sharpness);
187 _anisotropy->getValueAtTime(time, params.anisotropy);
188 _alpha->getValueAtTime(time, params.alpha);
189 _sigma->getValueAtTime(time, params.sigma);
190 _dl->getValueAtTime(time, params.dl);
191 _da->getValueAtTime(time, params.da);
192 _gprec->getValueAtTime(time, params.gprec);
193 _interp->getValueAtTime(time, params.interp_i);
194 _fast_approx->getValueAtTime(time, params.fast_approx);
195 _iterations->getValueAtTime(time, params.iterations);
196 }
197
198 // compute the roi required to compute rect, given params. This roi is then intersected with the image rod.
199 // only called if mix != 0.
getRoI(const OfxRectI & rect,const OfxPointD & renderScale,const CImgSmoothParams & params,OfxRectI * roi)200 virtual void getRoI(const OfxRectI& rect,
201 const OfxPointD& renderScale,
202 const CImgSmoothParams& params,
203 OfxRectI* roi) OVERRIDE FINAL
204 {
205 int delta_pix = (int)std::ceil( (params.amplitude + params.alpha + params.sigma) * renderScale.x * params.iterations );
206
207 roi->x1 = rect.x1 - delta_pix;
208 roi->x2 = rect.x2 + delta_pix;
209 roi->y1 = rect.y1 - delta_pix;
210 roi->y2 = rect.y2 + delta_pix;
211 }
212
render(const RenderArguments & args,const CImgSmoothParams & params,int,int,cimg_library::CImg<cimgpix_t> &,cimg_library::CImg<cimgpix_t> & cimg,int)213 virtual void render(const RenderArguments &args,
214 const CImgSmoothParams& params,
215 int /*x1*/,
216 int /*y1*/,
217 cimg_library::CImg<cimgpix_t>& /*mask*/,
218 cimg_library::CImg<cimgpix_t>& cimg,
219 int /*alphaChannel*/) OVERRIDE FINAL
220 {
221 // PROCESSING.
222 // This is the only place where the actual processing takes place
223 if ( (params.iterations <= 0) || (params.amplitude <= 0.) || (params.dl < 0.) || cimg.is_empty() ) {
224 return;
225 }
226 for (int i = 0; i < params.iterations; ++i) {
227 if ( abort() ) {
228 return;
229 }
230 cimg.blur_anisotropic( (float)(params.amplitude * args.renderScale.x), // in pixels
231 (float)params.sharpness,
232 (float)params.anisotropy,
233 (float)(params.alpha * args.renderScale.x), // in pixels
234 (float)(params.sigma * args.renderScale.x), // in pixels
235 (float)params.dl, // in pixel, but we don't discretize more
236 (float)params.da,
237 (float)params.gprec,
238 params.interp_i,
239 params.fast_approx );
240 }
241 }
242
isIdentity(const IsIdentityArguments &,const CImgSmoothParams & params)243 virtual bool isIdentity(const IsIdentityArguments & /*args*/,
244 const CImgSmoothParams& params) OVERRIDE FINAL
245 {
246 return (params.iterations <= 0) || (params.amplitude <= 0.) || (params.dl < 0.);
247 };
changedParam(const InstanceChangedArgs & args,const std::string & paramName)248 virtual void changedParam(const InstanceChangedArgs &args,
249 const std::string ¶mName) OVERRIDE FINAL
250 {
251 if ( (paramName == kParamThinBrush) ) {
252 _amplitude->resetToDefault();
253 _sharpness->setValue(0.9);
254 _anisotropy->setValue(0.64);
255 _alpha->setValue(3.1);
256 _sigma->resetToDefault();
257 _dl->resetToDefault();
258 _da->resetToDefault();
259 _gprec->resetToDefault();
260 _interp->resetToDefault();
261 _fast_approx->resetToDefault();
262 _iterations->resetToDefault();
263 } else {
264 CImgFilterPluginHelper<CImgSmoothParams, false>::changedParam(args, paramName);
265 }
266 }
267
268 private:
269
270 // params
271 DoubleParam *_amplitude;
272 DoubleParam *_sharpness;
273 DoubleParam *_anisotropy;
274 DoubleParam *_alpha;
275 DoubleParam *_sigma;
276 DoubleParam *_dl;
277 DoubleParam *_da;
278 DoubleParam *_gprec;
279 ChoiceParam *_interp;
280 BooleanParam *_fast_approx;
281 IntParam *_iterations;
282 };
283
284
285 mDeclarePluginFactory(CImgSmoothPluginFactory, {ofxsThreadSuiteCheck();}, {});
286
287 void
describe(ImageEffectDescriptor & desc)288 CImgSmoothPluginFactory::describe(ImageEffectDescriptor& desc)
289 {
290 // basic labels
291 desc.setLabel(kPluginName);
292 desc.setPluginGrouping(kPluginGrouping);
293 desc.setPluginDescription(kPluginDescription);
294
295 // add supported context
296 desc.addSupportedContext(eContextFilter);
297 desc.addSupportedContext(eContextGeneral);
298
299 // add supported pixel depths
300 //desc.addSupportedBitDepth(eBitDepthUByte);
301 //desc.addSupportedBitDepth(eBitDepthUShort);
302 desc.addSupportedBitDepth(eBitDepthFloat);
303
304 // set a few flags
305 desc.setSingleInstance(false);
306 desc.setHostFrameThreading(kHostFrameThreading);
307 desc.setSupportsMultiResolution(kSupportsMultiResolution);
308 desc.setSupportsTiles(kSupportsTiles);
309 desc.setTemporalClipAccess(false);
310 desc.setRenderTwiceAlways(true);
311 desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
312 desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
313 desc.setRenderThreadSafety(kRenderThreadSafety);
314 }
315
316 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)317 CImgSmoothPluginFactory::describeInContext(ImageEffectDescriptor& desc,
318 ContextEnum context)
319 {
320 // create the clips and params
321 PageParamDescriptor *page = CImgSmoothPlugin::describeInContextBegin(desc, context,
322 kSupportsRGBA,
323 kSupportsRGB,
324 kSupportsXY,
325 kSupportsAlpha,
326 kSupportsTiles,
327 /*processRGB=*/ true,
328 /*processAlpha*/ false,
329 /*processIsSecret=*/ false);
330
331 {
332 DoubleParamDescriptor *param = desc.defineDoubleParam(kParamAmplitude);
333 param->setLabel(kParamAmplitudeLabel);
334 param->setHint(kParamAmplitudeHint);
335 param->setRange(0., 1000.);
336 param->setDisplayRange(0., 100.);
337 param->setDefault(kParamAmplitudeDefault);
338 param->setIncrement(1);
339 if (page) {
340 page->addChild(*param);
341 }
342 }
343 {
344 DoubleParamDescriptor *param = desc.defineDoubleParam(kParamSharpness);
345 param->setLabel(kParamSharpnessLabel);
346 param->setRange(0., 1.);
347 param->setDisplayRange(0., 1.);
348 param->setDefault(kParamSharpnessDefault);
349 param->setIncrement(0.05);
350 if (page) {
351 page->addChild(*param);
352 }
353 }
354 {
355 DoubleParamDescriptor *param = desc.defineDoubleParam(kParamAnisotropy);
356 param->setLabel(kParamAnisotropyLabel);
357 param->setHint(kParamAnisotropyHint);
358 param->setRange(0., 1.);
359 param->setDisplayRange(0., 1.);
360 param->setDefault(kParamAnisotropyDefault);
361 param->setIncrement(0.05);
362 if (page) {
363 page->addChild(*param);
364 }
365 }
366 {
367 DoubleParamDescriptor *param = desc.defineDoubleParam(kParamAlpha);
368 param->setLabel(kParamAlphaLabel);
369 param->setRange(0., 1.);
370 param->setDisplayRange(0., 1.);
371 param->setDefault(kParamAlphaDefault);
372 param->setIncrement(0.05);
373 if (page) {
374 page->addChild(*param);
375 }
376 }
377 {
378 DoubleParamDescriptor *param = desc.defineDoubleParam(kParamSigma);
379 param->setLabel(kParamSigmaLabel);
380 param->setHint(kParamSigmaHint);
381 param->setRange(0., 3.);
382 param->setDisplayRange(0., 3.);
383 param->setDefault(kParamSigmaDefault);
384 param->setIncrement(0.05);
385 if (page) {
386 page->addChild(*param);
387 }
388 }
389 {
390 DoubleParamDescriptor *param = desc.defineDoubleParam(kParamDl);
391 param->setLabel(kParamDlLabel);
392 param->setHint(kParamDlHint);
393 param->setRange(0., 1.);
394 param->setDisplayRange(0., 1.);
395 param->setDefault(kParamDlDefault);
396 param->setIncrement(0.05);
397 if (page) {
398 page->addChild(*param);
399 }
400 }
401 {
402 DoubleParamDescriptor *param = desc.defineDoubleParam(kParamDa);
403 param->setLabel(kParamDaLabel);
404 param->setHint(kParamDaHint);
405 param->setRange(0., 90.);
406 param->setDisplayRange(0., 90.);
407 param->setDefault(kParamDaDefault);
408 param->setIncrement(0.5);
409 if (page) {
410 page->addChild(*param);
411 }
412 }
413 {
414 DoubleParamDescriptor *param = desc.defineDoubleParam(kParamGaussPrec);
415 param->setLabel(kParamGaussPrecLabel);
416 param->setHint(kParamGaussPrecHint);
417 param->setRange(0., 5.);
418 param->setDisplayRange(0., 5.);
419 param->setDefault(kParamGaussPrecDefault);
420 param->setIncrement(0.05);
421 if (page) {
422 page->addChild(*param);
423 }
424 }
425 {
426 ChoiceParamDescriptor *param = desc.defineChoiceParam(kParamInterp);
427 param->setLabel(kParamInterpLabel);
428 param->setHint(kParamInterpHint);
429 assert(param->getNOptions() == eInterpNearest && param->getNOptions() == 0);
430 param->appendOption(kParamInterpOptionNearest);
431 assert(param->getNOptions() == eInterpLinear && param->getNOptions() == 1);
432 param->appendOption(kParamInterpOptionLinear);
433 assert(param->getNOptions() == eInterpRungeKutta && param->getNOptions() == 2);
434 param->appendOption(kParamInterpOptionRungeKutta);
435 param->setDefault( (int)kParamInterpDefault );
436 if (page) {
437 page->addChild(*param);
438 }
439 }
440 {
441 BooleanParamDescriptor *param = desc.defineBooleanParam(kParamFastApprox);
442 param->setLabel(kParamFastApproxLabel);
443 param->setHint(kParamFastApproxHint);
444 param->setDefault(kParamFastApproxDefault);
445 if (page) {
446 page->addChild(*param);
447 }
448 }
449 {
450 IntParamDescriptor *param = desc.defineIntParam(kParamIterations);
451 param->setLabel(kParamIterationsLabel);
452 param->setHint(kParamIterationsHint);
453 param->setRange(0, 10);
454 param->setDisplayRange(0, 10);
455 param->setDefault(kParamIterationsDefault);
456 if (page) {
457 page->addChild(*param);
458 }
459 }
460 {
461 PushButtonParamDescriptor *param = desc.definePushButtonParam(kParamThinBrush);
462 param->setLabel(kParamThinBrushLabel);
463 param->setHint(kParamThinBrushHint);
464 }
465
466 CImgSmoothPlugin::describeInContextEnd(desc, context, page);
467 } // CImgSmoothPluginFactory::describeInContext
468
469 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)470 CImgSmoothPluginFactory::createInstance(OfxImageEffectHandle handle,
471 ContextEnum /*context*/)
472 {
473 return new CImgSmoothPlugin(handle);
474 }
475
476 static CImgSmoothPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
477 mRegisterPluginFactoryInstance(p)
478
479 OFXS_NAMESPACE_ANONYMOUS_EXIT
480