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 &paramName) 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