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