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 CImgEqualize plugin.
21  */
22 
23 #include <memory>
24 #include <cmath>
25 #include <cfloat> // DBL_MAX
26 #include <cstring>
27 #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
28 #include <windows.h>
29 #endif
30 
31 #include "ofxsProcessing.H"
32 #include "ofxsMaskMix.h"
33 #include "ofxsMacros.h"
34 #include "ofxsCoords.h"
35 #include "ofxsCopier.h"
36 
37 #include "CImgFilter.h"
38 
39 using namespace OFX;
40 
41 OFXS_NAMESPACE_ANONYMOUS_ENTER
42 
43 #define kPluginName          "EqualizeCImg"
44 #define kPluginGrouping      "Color"
45 #define kPluginDescription \
46     "Equalize histogram of pixel values.\n" \
47     "To equalize image brightness only, use the HistEQCImg plugin.\n" \
48     "Uses the 'equalize' function from the CImg library.\n" \
49     "CImg is a free, open-source library distributed under the CeCILL-C " \
50     "(close to the GNU LGPL) or CeCILL (compatible with the GNU GPL) licenses. " \
51     "It can be used in commercial applications (see http://cimg.eu)."
52 
53 #define kPluginIdentifier    "net.sf.cimg.CImgEqualize"
54 // History:
55 // version 1.0: initial version
56 // version 2.0: use kNatronOfxParamProcess* parameters
57 #define kPluginVersionMajor 2 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
58 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
59 
60 #define kSupportsComponentRemapping 1
61 #define kSupportsTiles 0 // Histogram must be computed on the whole image
62 #define kSupportsMultiResolution 1
63 #define kSupportsRenderScale 1
64 #define kSupportsMultipleClipPARs false
65 #define kSupportsMultipleClipDepths false
66 #define kRenderThreadSafety eRenderFullySafe
67 #ifdef cimg_use_openmp
68 #define kHostFrameThreading false
69 #else
70 #define kHostFrameThreading true
71 #endif
72 #define kSupportsRGBA true
73 #define kSupportsRGB true
74 #define kSupportsXY true
75 #define kSupportsAlpha true
76 
77 #define kParamNbLevels "nb_levels"
78 #define kParamNbLevelsLabel "NbLevels"
79 #define kParamNbLevelsHint "Number of histogram levels used for the equalization."
80 #define kParamNbLevelsDefault 4096
81 
82 #define kParamMin "min_value"
83 #define kParamMinLabel "Min Value"
84 #define kParamMinHint "Minimum pixel value considered for the histogram computation. All pixel values lower than min_value will not be counted."
85 #define kParamMinDefault 0.0
86 
87 #define kParamMax "max_value"
88 #define kParamMaxLabel "Max Value"
89 #define kParamMaxHint "Maximum pixel value considered for the histogram computation. All pixel values higher than max_value will not be counted."
90 #define kParamMaxDefault 1.0
91 
92 
93 /// Equalize plugin
94 struct CImgEqualizeParams
95 {
96     int nb_levels;
97     double min_value;
98     double max_value;
99 };
100 
101 class CImgEqualizePlugin
102     : public CImgFilterPluginHelper<CImgEqualizeParams, false>
103 {
104 public:
105 
CImgEqualizePlugin(OfxImageEffectHandle handle)106     CImgEqualizePlugin(OfxImageEffectHandle handle)
107         : CImgFilterPluginHelper<CImgEqualizeParams, false>(handle, /*usesMask=*/false, kSupportsComponentRemapping, kSupportsTiles, kSupportsMultiResolution, kSupportsRenderScale, /*defaultUnpremult=*/ true)
108     {
109         _nb_levels  = fetchIntParam(kParamNbLevels);
110         _min_value  = fetchDoubleParam(kParamMin);
111         _max_value  = fetchDoubleParam(kParamMax);
112         assert(_nb_levels && _min_value && _max_value);
113     }
114 
getValuesAtTime(double time,CImgEqualizeParams & params)115     virtual void getValuesAtTime(double time,
116                                  CImgEqualizeParams& params) OVERRIDE FINAL
117     {
118         _nb_levels->getValueAtTime(time, params.nb_levels);
119         _min_value->getValueAtTime(time, params.min_value);
120         _max_value->getValueAtTime(time, params.max_value);
121     }
122 
123     // compute the roi required to compute rect, given params. This roi is then intersected with the image rod.
124     // only called if mix != 0.
getRoI(const OfxRectI & rect,const OfxPointD &,const CImgEqualizeParams &,OfxRectI * roi)125     virtual void getRoI(const OfxRectI& rect,
126                         const OfxPointD& /*renderScale*/,
127                         const CImgEqualizeParams& /*params*/,
128                         OfxRectI* roi) OVERRIDE FINAL
129     {
130         int delta_pix = 0;
131 
132         roi->x1 = rect.x1 - delta_pix;
133         roi->x2 = rect.x2 + delta_pix;
134         roi->y1 = rect.y1 - delta_pix;
135         roi->y2 = rect.y2 + delta_pix;
136     }
137 
render(const RenderArguments &,const CImgEqualizeParams & params,int,int,cimg_library::CImg<cimgpix_t> &,cimg_library::CImg<cimgpix_t> & cimg,int)138     virtual void render(const RenderArguments & /*args*/,
139                         const CImgEqualizeParams& params,
140                         int /*x1*/,
141                         int /*y1*/,
142                         cimg_library::CImg<cimgpix_t>& /*mask*/,
143                         cimg_library::CImg<cimgpix_t>& cimg,
144                         int /*alphaChannel*/) OVERRIDE FINAL
145     {
146         // PROCESSING.
147         // This is the only place where the actual processing takes place
148         cimg.equalize(params.nb_levels, (float)params.min_value, (float)params.max_value);
149     }
150 
151     //virtual bool isIdentity(const IsIdentityArguments &/*args*/, const CImgEqualizeParams& /*params*/) OVERRIDE FINAL
152     //{
153     //    return false;
154     //};
155 
156 private:
157 
158     // params
159     IntParam *_nb_levels;
160     DoubleParam *_min_value;
161     DoubleParam *_max_value;
162 };
163 
164 
165 mDeclarePluginFactory(CImgEqualizePluginFactory, {ofxsThreadSuiteCheck();}, {});
166 
167 void
describe(ImageEffectDescriptor & desc)168 CImgEqualizePluginFactory::describe(ImageEffectDescriptor& desc)
169 {
170     // basic labels
171     desc.setLabel(kPluginName);
172     desc.setPluginGrouping(kPluginGrouping);
173     desc.setPluginDescription(kPluginDescription);
174 
175     // add supported context
176     desc.addSupportedContext(eContextFilter);
177     desc.addSupportedContext(eContextGeneral);
178 
179     // add supported pixel depths
180     //desc.addSupportedBitDepth(eBitDepthUByte);
181     //desc.addSupportedBitDepth(eBitDepthUShort);
182     desc.addSupportedBitDepth(eBitDepthFloat);
183 
184     // set a few flags
185     desc.setSingleInstance(false);
186     desc.setHostFrameThreading(kHostFrameThreading);
187     desc.setSupportsMultiResolution(kSupportsMultiResolution);
188     desc.setSupportsTiles(kSupportsTiles);
189     desc.setTemporalClipAccess(false);
190     desc.setRenderTwiceAlways(true);
191     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
192     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
193     desc.setRenderThreadSafety(kRenderThreadSafety);
194 }
195 
196 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)197 CImgEqualizePluginFactory::describeInContext(ImageEffectDescriptor& desc,
198                                              ContextEnum context)
199 {
200     // create the clips and params
201     PageParamDescriptor *page = CImgEqualizePlugin::describeInContextBegin(desc, context,
202                                                                            kSupportsRGBA,
203                                                                            kSupportsRGB,
204                                                                            kSupportsXY,
205                                                                            kSupportsAlpha,
206                                                                            kSupportsTiles,
207                                                                            /*processRGB=*/ true,
208                                                                            /*processAlpha*/ false,
209                                                                            /*processIsSecret=*/ false);
210 
211     {
212         IntParamDescriptor *param = desc.defineIntParam(kParamNbLevels);
213         param->setLabel(kParamNbLevelsLabel);
214         param->setHint(kParamNbLevelsHint);
215         param->setDefault(kParamNbLevelsDefault);
216         if (page) {
217             page->addChild(*param);
218         }
219     }
220     {
221         DoubleParamDescriptor *param = desc.defineDoubleParam(kParamMin);
222         param->setLabel(kParamMinLabel);
223         param->setHint(kParamMinHint);
224         param->setDefault(kParamMinDefault);
225         param->setRange(-DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
226         param->setDisplayRange(0., 1.);
227         param->setIncrement(0.001);
228         if (page) {
229             page->addChild(*param);
230         }
231     }
232     {
233         DoubleParamDescriptor *param = desc.defineDoubleParam(kParamMax);
234         param->setLabel(kParamMaxLabel);
235         param->setHint(kParamMaxHint);
236         param->setDefault(kParamMaxDefault);
237         param->setRange(-DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
238         param->setDisplayRange(0., 1.);
239         param->setIncrement(0.001);
240         if (page) {
241             page->addChild(*param);
242         }
243     }
244 
245     CImgEqualizePlugin::describeInContextEnd(desc, context, page);
246 }
247 
248 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)249 CImgEqualizePluginFactory::createInstance(OfxImageEffectHandle handle,
250                                           ContextEnum /*context*/)
251 {
252     return new CImgEqualizePlugin(handle);
253 }
254 
255 static CImgEqualizePluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
256 mRegisterPluginFactoryInstance(p)
257 
258 OFXS_NAMESPACE_ANONYMOUS_EXIT
259