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 CImgHistEQ 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 #include "ofxsLut.h"
36 
37 #include "CImgFilter.h"
38 
39 using namespace OFX;
40 
41 OFXS_NAMESPACE_ANONYMOUS_ENTER
42 
43 #define kPluginName          "HistEQCImg"
44 #define kPluginGrouping      "Color"
45 #define kPluginDescription \
46     "Equalize histogram of brightness values.\n" \
47     "Uses the 'equalize' function from the CImg library on the 'V' channel of the HSV decomposition of the image.\n" \
48     "CImg is a free, open-source library distributed under the CeCILL-C " \
49     "(close to the GNU LGPL) or CeCILL (compatible with the GNU GPL) licenses. " \
50     "It can be used in commercial applications (see http://cimg.eu)."
51 
52 #define kPluginIdentifier    "net.sf.cimg.CImgHistEQ"
53 // History:
54 // version 1.0: initial version
55 // version 2.0: use kNatronOfxParamProcess* parameters
56 #define kPluginVersionMajor 2 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
57 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
58 
59 #define kSupportsComponentRemapping 1
60 #define kSupportsTiles 0 // Histogram must be computed on the whole image
61 #define kSupportsMultiResolution 1
62 #define kSupportsRenderScale 1
63 #define kSupportsMultipleClipPARs false
64 #define kSupportsMultipleClipDepths false
65 #define kRenderThreadSafety eRenderFullySafe
66 #ifdef cimg_use_openmp
67 #define kHostFrameThreading false
68 #else
69 #define kHostFrameThreading true
70 #endif
71 #define kSupportsRGBA true
72 #define kSupportsRGB true
73 #define kSupportsXY true
74 #define kSupportsAlpha true
75 
76 #define kParamNbLevels "nb_levels"
77 #define kParamNbLevelsLabel "NbLevels"
78 #define kParamNbLevelsHint "Number of histogram levels used for the equalization."
79 #define kParamNbLevelsDefault 4096
80 
81 
82 /// HistEQ plugin
83 struct CImgHistEQParams
84 {
85     int nb_levels;
86 };
87 
88 class CImgHistEQPlugin
89     : public CImgFilterPluginHelper<CImgHistEQParams, false>
90 {
91 public:
92 
CImgHistEQPlugin(OfxImageEffectHandle handle)93     CImgHistEQPlugin(OfxImageEffectHandle handle)
94         : CImgFilterPluginHelper<CImgHistEQParams, false>(handle, /*usesMask=*/false, kSupportsComponentRemapping, kSupportsTiles, kSupportsMultiResolution, kSupportsRenderScale, /*defaultUnpremult=*/ true)
95     {
96         _nb_levels  = fetchIntParam(kParamNbLevels);
97         assert(_nb_levels);
98     }
99 
getValuesAtTime(double time,CImgHistEQParams & params)100     virtual void getValuesAtTime(double time,
101                                  CImgHistEQParams& params) OVERRIDE FINAL
102     {
103         _nb_levels->getValueAtTime(time, params.nb_levels);
104     }
105 
106     // compute the roi required to compute rect, given params. This roi is then intersected with the image rod.
107     // only called if mix != 0.
getRoI(const OfxRectI & rect,const OfxPointD &,const CImgHistEQParams &,OfxRectI * roi)108     virtual void getRoI(const OfxRectI& rect,
109                         const OfxPointD& /*renderScale*/,
110                         const CImgHistEQParams& /*params*/,
111                         OfxRectI* roi) OVERRIDE FINAL
112     {
113         int delta_pix = 0;
114 
115         roi->x1 = rect.x1 - delta_pix;
116         roi->x2 = rect.x2 + delta_pix;
117         roi->y1 = rect.y1 - delta_pix;
118         roi->y2 = rect.y2 + delta_pix;
119     }
120 
render(const RenderArguments &,const CImgHistEQParams & params,int,int,cimg_library::CImg<cimgpix_t> &,cimg_library::CImg<cimgpix_t> & cimg,int)121     virtual void render(const RenderArguments & /*args*/,
122                         const CImgHistEQParams& params,
123                         int /*x1*/,
124                         int /*y1*/,
125                         cimg_library::CImg<cimgpix_t>& /*mask*/,
126                         cimg_library::CImg<cimgpix_t>& cimg,
127                         int /*alphaChannel*/) OVERRIDE FINAL
128     {
129         // PROCESSING.
130         // This is the only place where the actual processing takes place
131         if (cimg.spectrum() < 3) {
132             assert(cimg.spectrum() == 1); // Alpha image
133             float vmin, vmax;
134             vmin = cimg.min_max(vmax);
135             cimg.equalize(params.nb_levels, vmin, vmax);
136         } else {
137             cimg_pragma_openmp(parallel for if (cimg.size()>=1048576))
138             cimg_forXY(cimg, x, y) {
139                 float h, s, v;
140                 Color::rgb_to_hsv(cimg(x, y, 0, 0), cimg(x, y, 0, 1), cimg(x, y, 0, 2), &h, &s, &v);
141 
142                 cimg(x, y, 0, 0) = h;
143                 cimg(x, y, 0, 1) = s;
144                 cimg(x, y, 0, 2) = v;
145             }
146             cimg_library::CImg<cimgpix_t> vchannel = cimg.get_shared_channel(2);
147             float vmin, vmax;
148             vmin = vchannel.min_max(vmax);
149             vchannel.equalize(params.nb_levels, vmin, vmax);
150             cimg_forXY(cimg, x, y) {
151                 float r, g, b;
152                 Color::hsv_to_rgb(cimg(x, y, 0, 0), cimg(x, y, 0, 1), cimg(x, y, 0, 2), &r, &g, &b);
153 
154                 cimg(x, y, 0, 0) = r;
155                 cimg(x, y, 0, 1) = g;
156                 cimg(x, y, 0, 2) = b;
157             }
158         }
159     }
160 
161     //virtual bool isIdentity(const IsIdentityArguments &args, const CImgHistEQParams& params) OVERRIDE FINAL
162     //{
163     //    return false;
164     //};
165 
166 private:
167 
168     // params
169     IntParam *_nb_levels;
170 };
171 
172 
173 mDeclarePluginFactory(CImgHistEQPluginFactory, {ofxsThreadSuiteCheck();}, {});
174 
175 void
describe(ImageEffectDescriptor & desc)176 CImgHistEQPluginFactory::describe(ImageEffectDescriptor& desc)
177 {
178     // basic labels
179     desc.setLabel(kPluginName);
180     desc.setPluginGrouping(kPluginGrouping);
181     desc.setPluginDescription(kPluginDescription);
182 
183     // add supported context
184     desc.addSupportedContext(eContextFilter);
185     desc.addSupportedContext(eContextGeneral);
186 
187     // add supported pixel depths
188     //desc.addSupportedBitDepth(eBitDepthUByte);
189     //desc.addSupportedBitDepth(eBitDepthUShort);
190     desc.addSupportedBitDepth(eBitDepthFloat);
191 
192     // set a few flags
193     desc.setSingleInstance(false);
194     desc.setHostFrameThreading(kHostFrameThreading);
195     desc.setSupportsMultiResolution(kSupportsMultiResolution);
196     desc.setSupportsTiles(kSupportsTiles);
197     desc.setTemporalClipAccess(false);
198     desc.setRenderTwiceAlways(true);
199     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
200     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
201     desc.setRenderThreadSafety(kRenderThreadSafety);
202 }
203 
204 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)205 CImgHistEQPluginFactory::describeInContext(ImageEffectDescriptor& desc,
206                                            ContextEnum context)
207 {
208     // create the clips and params
209     PageParamDescriptor *page = CImgHistEQPlugin::describeInContextBegin(desc, context,
210                                                                               kSupportsRGBA,
211                                                                               kSupportsRGB,
212                                                                               kSupportsXY,
213                                                                               kSupportsAlpha,
214                                                                               kSupportsTiles,
215                                                                               /*processRGB=*/ true,
216                                                                               /*processAlpha=*/ true,
217                                                                               /*processIsSecret=*/ true);
218 
219     {
220         IntParamDescriptor *param = desc.defineIntParam(kParamNbLevels);
221         param->setLabel(kParamNbLevelsLabel);
222         param->setHint(kParamNbLevelsHint);
223         param->setDefault(kParamNbLevelsDefault);
224         if (page) {
225             page->addChild(*param);
226         }
227     }
228     CImgHistEQPlugin::describeInContextEnd(desc, context, page);
229 }
230 
231 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)232 CImgHistEQPluginFactory::createInstance(OfxImageEffectHandle handle,
233                                         ContextEnum /*context*/)
234 {
235     return new CImgHistEQPlugin(handle);
236 }
237 
238 static CImgHistEQPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
239 mRegisterPluginFactoryInstance(p)
240 
241 OFXS_NAMESPACE_ANONYMOUS_EXIT
242