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