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 CImgSharpenInvDiff 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          "SharpenInvDiffCImg"
43 #define kPluginGrouping      "Filter"
44 #define kPluginDescription \
45     "Sharpen selected images by inverse diffusion.\n" \
46     "Uses 'sharpen' 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.CImgSharpenInvDiff"
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 0 // a maximum computation is done in sharpen, tiling is theoretically not possible (although gmicol uses a 24 pixel overlap)
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 "Standard deviation of the spatial kernel, in pixel units (>=0). Details smaller than this size are filtered out."
78 #define kParamAmplitudeDefault 0.2 // 50.0/255
79 
80 #define kParamIterations "iterations"
81 #define kParamIterationsLabel "Iterations"
82 #define kParamIterationsHint "Number of iterations. A reasonable value is 2."
83 #define kParamIterationsDefault 2
84 
85 using namespace cimg_library;
86 
87 /// SharpenInvDiff plugin
88 struct CImgSharpenInvDiffParams
89 {
90     double amplitude;
91     int iterations;
92 };
93 
94 class CImgSharpenInvDiffPlugin
95     : public CImgFilterPluginHelper<CImgSharpenInvDiffParams, false>
96 {
97 public:
98 
CImgSharpenInvDiffPlugin(OfxImageEffectHandle handle)99     CImgSharpenInvDiffPlugin(OfxImageEffectHandle handle)
100         : CImgFilterPluginHelper<CImgSharpenInvDiffParams, false>(handle, /*usesMask=*/false, kSupportsComponentRemapping, kSupportsTiles, kSupportsMultiResolution, kSupportsRenderScale, /*defaultUnpremult=*/ true)
101     {
102         _amplitude  = fetchDoubleParam(kParamAmplitude);
103         _iterations = fetchIntParam(kParamIterations);
104         assert(_amplitude && _iterations);
105     }
106 
getValuesAtTime(double time,CImgSharpenInvDiffParams & params)107     virtual void getValuesAtTime(double time,
108                                  CImgSharpenInvDiffParams& params) OVERRIDE FINAL
109     {
110         _amplitude->getValueAtTime(time, params.amplitude);
111         _iterations->getValueAtTime(time, params.iterations);
112     }
113 
114     // compute the roi required to compute rect, given params. This roi is then intersected with the image rod.
115     // only called if mix != 0.
getRoI(const OfxRectI & rect,const OfxPointD &,const CImgSharpenInvDiffParams & params,OfxRectI * roi)116     virtual void getRoI(const OfxRectI& rect,
117                         const OfxPointD& /*renderScale*/,
118                         const CImgSharpenInvDiffParams& params,
119                         OfxRectI* roi) OVERRIDE FINAL
120     {
121         int delta_pix = 24 * params.iterations; // overlap is 24 in gmicol
122 
123         roi->x1 = rect.x1 - delta_pix;
124         roi->x2 = rect.x2 + delta_pix;
125         roi->y1 = rect.y1 - delta_pix;
126         roi->y2 = rect.y2 + delta_pix;
127     }
128 
render(const RenderArguments &,const CImgSharpenInvDiffParams & params,int,int,cimg_library::CImg<cimgpix_t> &,cimg_library::CImg<cimgpix_t> & cimg,int)129     virtual void render(const RenderArguments & /*args*/,
130                         const CImgSharpenInvDiffParams& params,
131                         int /*x1*/,
132                         int /*y1*/,
133                         cimg_library::CImg<cimgpix_t>& /*mask*/,
134                         cimg_library::CImg<cimgpix_t>& cimg,
135                         int /*alphaChannel*/) OVERRIDE FINAL
136     {
137         // PROCESSING.
138         // This is the only place where the actual processing takes place
139         if ( (params.iterations <= 0) || (params.amplitude == 0.) || cimg.is_empty() ) {
140             return;
141         }
142         for (int i = 0; i < params.iterations; ++i) {
143             if ( abort() ) {
144                 return;
145             }
146 #ifdef CIMG_ABORTABLE
147             // args
148             const float amplitude = params.amplitude;
149 
150 #define Tfloat float
151 #define T float
152 
153             T val_min, val_max = cimg.max_min(val_min);
154             CImg<Tfloat> velocity(cimg._width, cimg._height, cimg._depth, cimg._spectrum), _veloc_max(cimg._spectrum);
155 
156             cimg_forC(cimg, c) {
157                 Tfloat *ptrd = velocity.data(0, 0, 0, c), veloc_max = 0;
158 
159                 CImg_3x3(I, Tfloat);
160                 cimg_for3(cimg._height, y) {
161                     if ( abort() ) {
162                         return;
163                     }
164                     for (int x = 0,
165                          _p1x = 0,
166                          _n1x = (int)(
167                              ( I[0] = I[1] = (T)cimg(_p1x, _p1y, 0, c) ),
168                              ( I[3] = I[4] = (T)cimg(0, y, 0, c) ),
169                              ( I[6] = I[7] = (T)cimg(0, _n1y, 0, c) ),
170                              1 >= cimg._width ? cimg.width() - 1 : 1);
171                          ( _n1x < cimg.width() && (
172                                ( I[2] = (T)cimg(_n1x, _p1y, 0, c) ),
173                                ( I[5] = (T)cimg(_n1x, y, 0, c) ),
174                                ( I[8] = (T)cimg(_n1x, _n1y, 0, c) ), 1) ) ||
175                          x == --_n1x;
176                          I[0] = I[1], I[1] = I[2],
177                          I[3] = I[4], I[4] = I[5],
178                          I[6] = I[7], I[7] = I[8],
179                          _p1x = x++, ++_n1x) {
180                         const Tfloat veloc = -Ipc - Inc - Icp - Icn + 4 * Icc;
181                         *(ptrd++) = veloc;
182                         if (veloc > veloc_max) {
183                             veloc_max = veloc;
184                         } else if (-veloc > veloc_max) {
185                             veloc_max = -veloc;
186                         }
187                     }
188                 }
189                 _veloc_max[c] = veloc_max;
190             }
191 
192             const Tfloat veloc_max = _veloc_max.max();
193             if (veloc_max > 0) {
194                 ( (velocity *= amplitude / veloc_max) += cimg ).cut(val_min, val_max).move_to(cimg);
195             }
196 
197 #undef Tfloat
198 #undef T
199 
200 #else // ifdef CIMG_ABORTABLE
201             cimg.sharpen( (float)params.amplitude );
202 #endif // ifdef CIMG_ABORTABLE
203         }
204     } // render
205 
isIdentity(const IsIdentityArguments &,const CImgSharpenInvDiffParams & params)206     virtual bool isIdentity(const IsIdentityArguments & /*args*/,
207                             const CImgSharpenInvDiffParams& params) OVERRIDE FINAL
208     {
209         return (params.iterations <= 0 || params.amplitude == 0.);
210     };
211 
212 private:
213 
214     // params
215     DoubleParam *_amplitude;
216     IntParam *_iterations;
217 };
218 
219 
220 mDeclarePluginFactory(CImgSharpenInvDiffPluginFactory, {ofxsThreadSuiteCheck();}, {});
221 
222 void
describe(ImageEffectDescriptor & desc)223 CImgSharpenInvDiffPluginFactory::describe(ImageEffectDescriptor& desc)
224 {
225     // basic labels
226     desc.setLabel(kPluginName);
227     desc.setPluginGrouping(kPluginGrouping);
228     desc.setPluginDescription(kPluginDescription);
229 
230     // add supported context
231     desc.addSupportedContext(eContextFilter);
232     desc.addSupportedContext(eContextGeneral);
233 
234     // add supported pixel depths
235     //desc.addSupportedBitDepth(eBitDepthUByte);
236     //desc.addSupportedBitDepth(eBitDepthUShort);
237     desc.addSupportedBitDepth(eBitDepthFloat);
238 
239     // set a few flags
240     desc.setSingleInstance(false);
241     desc.setHostFrameThreading(kHostFrameThreading);
242     desc.setSupportsMultiResolution(kSupportsMultiResolution);
243     desc.setSupportsTiles(kSupportsTiles);
244     desc.setTemporalClipAccess(false);
245     desc.setRenderTwiceAlways(true);
246     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
247     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
248     desc.setRenderThreadSafety(kRenderThreadSafety);
249 }
250 
251 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)252 CImgSharpenInvDiffPluginFactory::describeInContext(ImageEffectDescriptor& desc,
253                                                    ContextEnum context)
254 {
255     // create the clips and params
256     PageParamDescriptor *page = CImgSharpenInvDiffPlugin::describeInContextBegin(desc, context,
257                                                                                  kSupportsRGBA,
258                                                                                  kSupportsRGB,
259                                                                                  kSupportsXY,
260                                                                                  kSupportsAlpha,
261                                                                                  kSupportsTiles,
262                                                                                  /*processRGB=*/ true,
263                                                                                  /*processAlpha*/ false,
264                                                                                  /*processIsSecret=*/ false);
265 
266     {
267         DoubleParamDescriptor *param = desc.defineDoubleParam(kParamAmplitude);
268         param->setLabel(kParamAmplitudeLabel);
269         param->setHint(kParamAmplitudeHint);
270         param->setRange(0, 4. /*1000/256*/);
271         param->setDisplayRange(0, 1.2 /*300/255*/);
272         param->setDefault(kParamAmplitudeDefault);
273         param->setIncrement(0.01);
274         if (page) {
275             page->addChild(*param);
276         }
277     }
278     {
279         IntParamDescriptor *param = desc.defineIntParam(kParamIterations);
280         param->setLabel(kParamIterationsLabel);
281         param->setHint(kParamIterationsHint);
282         param->setRange(0, 10);
283         param->setDisplayRange(0, 10);
284         param->setDefault(kParamIterationsDefault);
285         if (page) {
286             page->addChild(*param);
287         }
288     }
289 
290     CImgSharpenInvDiffPlugin::describeInContextEnd(desc, context, page);
291 }
292 
293 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)294 CImgSharpenInvDiffPluginFactory::createInstance(OfxImageEffectHandle handle,
295                                                 ContextEnum /*context*/)
296 {
297     return new CImgSharpenInvDiffPlugin(handle);
298 }
299 
300 static CImgSharpenInvDiffPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
301 mRegisterPluginFactoryInstance(p)
302 
303 OFXS_NAMESPACE_ANONYMOUS_EXIT
304