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 CImgMatrix plugin.
21 */
22
23 #include <memory>
24 #include <cmath>
25 #include <cstring>
26 #include <cfloat> // DBL_MAX
27 #include <algorithm>
28 #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
29 #include <windows.h>
30 #endif
31
32 #include "ofxsProcessing.H"
33 #include "ofxsMaskMix.h"
34 #include "ofxsMacros.h"
35 #include "ofxsCoords.h"
36 #include "ofxsCopier.h"
37
38 #include "CImgFilter.h"
39
40 using namespace OFX;
41 using namespace cimg_library;
42
43 OFXS_NAMESPACE_ANONYMOUS_ENTER
44
45 #define kPluginName "Matrix0x0CImg"
46 #define kPluginGrouping "Filter/Matrix"
47 #define kPluginDescription \
48 "Compute the convolution of the input image with the specified matrix.\n" \
49 "This works by multiplying each surrounding pixel of the input image with the corresponding matrix coefficient (the current pixel is at the center of the matrix), and summing up the results.\n" \
50 "For example [-1 -1 -1] [-1 8 -1] [-1 -1 -1] produces an edge detection filter (which is an approximation of the Laplacian filter) by multiplying the center pixel by 8 and the surrounding pixels by -1, and then adding the nine values together to calculate the new value of the center pixel.\n" \
51 "Uses the CImg library.\n" \
52 "CImg is a free, open-source library distributed under the CeCILL-C " \
53 "(close to the GNU LGPL) or CeCILL (compatible with the GNU GPL) licenses. " \
54 "It can be used in commercial applications (see http://cimg.eu)."
55
56 #define kPluginIdentifier "eu.cimg.CImgMatrix"
57 // History:
58 // version 1.0: initial version
59 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
60 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
61
62 #define kSupportsComponentRemapping 1
63 #define kSupportsTiles 1
64 #define kSupportsMultiResolution 1
65 #define kSupportsRenderScale 1
66 #define kSupportsMultipleClipPARs false
67 #define kSupportsMultipleClipDepths false
68 #define kRenderThreadSafety eRenderFullySafe
69 #ifdef cimg_use_openmp
70 #define kHostFrameThreading false
71 #else
72 #define kHostFrameThreading true
73 #endif
74 #define kSupportsRGBA true
75 #define kSupportsRGB true
76 #define kSupportsXY true
77 #define kSupportsAlpha true
78
79 #define kParamMatrix "matrix"
80 #define kParamMatrixLabel "Matrix", "The coefficients of the matrix filter."
81 #define kParamMatrixCoeffLabel "", "Matrix coefficient."
82
83 #define kParamNormalize "normalize"
84 #define kParamNormalizeLabel "Normalize", "Normalize the matrix coefficients so that their sum is 1."
85
86
87 /// Matrix plugin
88 template<int dim>
89 struct CImgMatrixParams
90 {
91 double coeff[dim*dim];
92 bool normalize;
93 };
94
95 template<int dim>
96 class CImgMatrixPlugin
97 : public CImgFilterPluginHelper<CImgMatrixParams<dim>, false>
98 {
99 public:
100
CImgMatrixPlugin(OfxImageEffectHandle handle)101 CImgMatrixPlugin(OfxImageEffectHandle handle)
102 : CImgFilterPluginHelper<CImgMatrixParams<dim>, false>(handle, /*usesMask=*/false, kSupportsComponentRemapping, kSupportsTiles, kSupportsMultiResolution, kSupportsRenderScale, /*defaultUnpremult=*/ false)
103 {
104 for (int i = 0; i < dim; ++i) {
105 for (int j = 0; j < dim; ++j) {
106 _coeff[i*dim + j] = ImageEffect::fetchDoubleParam(std::string(kParamMatrix) + (char)('1' + i) + (char)('1' + j));
107 assert(_coeff[i*dim + j]);
108 }
109 }
110 _normalize = ImageEffect::fetchBooleanParam(kParamNormalize);
111 assert(_normalize);
112 }
113
getValuesAtTime(double time,CImgMatrixParams<dim> & params)114 virtual void getValuesAtTime(double time,
115 CImgMatrixParams<dim>& params) OVERRIDE FINAL
116 {
117 for (int i = 0; i < dim; ++i) {
118 for (int j = 0; j < dim; ++j) {
119 _coeff[i*dim + j]->getValueAtTime(time, params.coeff[i*dim + j]);
120 }
121 }
122 _normalize->getValueAtTime(time, params.normalize);
123 }
124
125 // compute the roi required to compute rect, given params. This roi is then intersected with the image rod.
126 // only called if mix != 0.
getRoI(const OfxRectI & rect,const OfxPointD & renderScale,const CImgMatrixParams<dim> &,OfxRectI * roi)127 virtual void getRoI(const OfxRectI& rect,
128 const OfxPointD& renderScale,
129 const CImgMatrixParams<dim>& /*params*/,
130 OfxRectI* roi) OVERRIDE FINAL
131 {
132 int delta_pix_x = (int)std::ceil((dim * renderScale.x - 1)/2.);
133 int delta_pix_y = (int)std::ceil((dim * renderScale.x - 1)/2.); // note: we only use renderScale.x
134
135 roi->x1 = rect.x1 - delta_pix_x;
136 roi->x2 = rect.x2 + delta_pix_x;
137 roi->y1 = rect.y1 - delta_pix_y;
138 roi->y2 = rect.y2 + delta_pix_y;
139 }
140
render(const RenderArguments & args,const CImgMatrixParams<dim> & params,int,int,CImg<cimgpix_t> &,CImg<cimgpix_t> & cimg,int)141 virtual void render(const RenderArguments &args,
142 const CImgMatrixParams<dim>& params,
143 int /*x1*/,
144 int /*y1*/,
145 CImg<cimgpix_t>& /*mask*/,
146 CImg<cimgpix_t>& cimg,
147 int /*alphaChannel*/) OVERRIDE FINAL
148 {
149 // PROCESSING.
150 // This is the only place where the actual processing takes place
151 // compute the filter size from renderscale.x:
152 // - 1x1: identity if normalize is checked, else mult by scalar
153 // - 3x3 if dim=3 and renderScale.x > 0.33 or dim=5 and renderScale.x > 0.4
154 // - 5x5 if dim=5 and renderScale.x > 0.6
155 const OfxPointD& renderScale = args.renderScale;
156 double s = 1;
157 if (params.normalize) {
158 s = 0.;
159 for (int i=0; i < dim * dim; ++i) {
160 s += params.coeff[i];
161 }
162 if (s == 0.) {
163 ImageEffect::setPersistentMessage(OFX::Message::eMessageError, "", "Matrix sums to zero, cannot normalize");
164 throwSuiteStatusException(kOfxStatFailed);
165 }
166 }
167 if (dim == 5 && renderScale.x > 0.6) {
168 CImg<cimgpix_t> res(cimg.width(), cimg.height(), cimg.depth(), cimg.spectrum());
169 double *M = (double*)params.coeff;
170 double Mbb = M[0]; double Mpb = M[1]; double Mcb = M[2]; double Mnb = M[3]; double Mab = M[4];
171 double Mbp = M[5]; double Mpp = M[6]; double Mcp = M[7]; double Mnp = M[8]; double Map = M[9];
172 double Mbc = M[10]; double Mpc = M[11]; double Mcc = M[12]; double Mnc = M[13]; double Mac = M[14];
173 double Mbn = M[15]; double Mpn = M[16]; double Mcn = M[17]; double Mnn = M[18]; double Man = M[19];
174 double Mba = M[20]; double Mpa = M[21]; double Mca = M[22]; double Mna = M[23]; double Maa = M[24];
175
176 cimg_pragma_openmp(parallel for cimg_openmp_if(cimg.width() * cimg.height() >= 262144 && cimg.spectrum() >= 2))
177 cimg_forC(cimg,c) {
178 cimgpix_t *ptrd = res.data() + cimg.width() * cimg.height() * c;
179 CImg_5x5(I,cimgpix_t);
180 cimg_for5x5(cimg,x,y,0,c,I,cimgpix_t) {
181 *(ptrd++) = (Mbb * Ibb + Mpb * Ipb + Mcb * Icb + Mnb * Inb + Mab * Iab +
182 Mbp * Ibp + Mpp * Ipp + Mcp * Icp + Mnp * Inp + Map * Iap +
183 Mbc * Ibc + Mpc * Ipc + Mcc * Icc + Mnc * Inc + Mac * Iac +
184 Mbn * Ibn + Mpn * Ipn + Mcn * Icn + Mnn * Inn + Man * Ian +
185 Mba * Iba + Mpa * Ipa + Mca * Ica + Mna * Ina + Maa * Iaa);
186 }
187 }
188 cimg = res;
189 if (params.normalize) {
190 cimg /= s;
191 }
192
193 } else if ( (dim == 3 && renderScale.x > 1./3.) ||
194 (dim == 5 && renderScale.x > 0.4) ) {
195 CImg<cimgpix_t> res(cimg.width(), cimg.height(), cimg.depth(), cimg.spectrum());
196 double *M = (double*)params.coeff;
197 double Mpp = M[0]; double Mcp = M[1]; double Mnp = M[2];
198 double Mpc = M[3]; double Mcc = M[4]; double Mnc = M[5];
199 double Mpn = M[6]; double Mcn = M[7]; double Mnn = M[8];
200
201 cimg_pragma_openmp(parallel for cimg_openmp_if(cimg.width() * cimg.height() >= 262144 && cimg.spectrum() >= 2))
202 cimg_forC(cimg,c) {
203 cimgpix_t *ptrd = res.data() + cimg.width() * cimg.height() * c;
204 CImg_3x3(I,cimgpix_t);
205 cimg_for3x3(cimg,x,y,0,c,I,cimgpix_t) {
206 *(ptrd++) = (Mpp * Ipp + Mcp * Icp + Mnp * Inp +
207 Mpc * Ipc + Mcc * Icc + Mnc * Inc +
208 Mpn * Ipn + Mcn * Icn + Mnn * Inn);
209 }
210 }
211 cimg = res;
212 if (params.normalize) {
213 cimg /= s;
214 }
215
216 } else {
217 if (params.normalize) {
218 return;
219 }
220 cimg *= s;
221 }
222 }
223
isIdentity(const IsIdentityArguments &,const CImgMatrixParams<dim> & params)224 virtual bool isIdentity(const IsIdentityArguments &/*args*/,
225 const CImgMatrixParams<dim>& params) OVERRIDE FINAL
226 {
227 for (int i = 0; i < dim; ++i) {
228 for (int j = 0; j < dim; ++j) {
229 bool isIdentityCoeff = false;
230 if (i == (dim-1)/2 && j == (dim-1)/2 ) {
231 if ( (params.coeff[i*dim + j] == 1.) ||
232 (params.coeff[i*dim + j] != 0. && params.normalize) ) {
233 isIdentityCoeff = true;
234 }
235 } else if (params.coeff[i*dim + j] == 0.) {
236 isIdentityCoeff = true;
237 }
238 if (!isIdentityCoeff) {
239 return false;
240 }
241 }
242 }
243 return false;
244 };
245
changedParam(const InstanceChangedArgs & args,const std::string & paramName)246 virtual void changedParam(const InstanceChangedArgs &args,
247 const std::string ¶mName) OVERRIDE FINAL
248 {
249 // must clear persistent message, or render() is not called by Nuke
250 ImageEffect::clearPersistentMessage();
251
252 CImgFilterPluginHelper<CImgMatrixParams<dim>, false>::changedParam(args, paramName);
253 }
254 private:
255
256 // params
257 DoubleParam *_coeff[dim*dim];
258 BooleanParam *_normalize;
259 };
260
261
262 //mDeclarePluginFactory(CImgMatrixPluginFactory, {ofxsThreadSuiteCheck();}, {});
263 template<int dim>
264 class CImgMatrixPluginFactory
265 : public PluginFactoryHelper<CImgMatrixPluginFactory<dim> >
266 {
267 public:
268 CImgMatrixPluginFactory<dim>(const std::string & id, unsigned int verMaj, unsigned int verMin)
269 : PluginFactoryHelper<CImgMatrixPluginFactory>(id, verMaj, verMin)
270 {
271 }
272
load()273 virtual void load() OVERRIDE FINAL {ofxsThreadSuiteCheck();}
274 virtual void describe(ImageEffectDescriptor &desc) OVERRIDE FINAL;
275 virtual void describeInContext(ImageEffectDescriptor &desc, ContextEnum context) OVERRIDE FINAL;
276 virtual ImageEffect* createInstance(OfxImageEffectHandle handle, ContextEnum context) OVERRIDE FINAL;
277 };
278
279 template<int dim>
280 void
describe(ImageEffectDescriptor & desc)281 CImgMatrixPluginFactory<dim>::describe(ImageEffectDescriptor& desc)
282 {
283 // basic labels
284 std::string pluginName(kPluginName);
285 std::replace( pluginName.begin(), pluginName.end(), '0', (char)('0' + dim));
286 desc.setLabel(pluginName);
287 desc.setPluginGrouping(kPluginGrouping);
288 desc.setPluginDescription(kPluginDescription);
289
290 // add supported context
291 desc.addSupportedContext(eContextFilter);
292 desc.addSupportedContext(eContextGeneral);
293
294 // add supported pixel depths
295 //desc.addSupportedBitDepth(eBitDepthUByte);
296 //desc.addSupportedBitDepth(eBitDepthUShort);
297 desc.addSupportedBitDepth(eBitDepthFloat);
298
299 // set a few flags
300 desc.setSingleInstance(false);
301 desc.setHostFrameThreading(kHostFrameThreading);
302 desc.setSupportsMultiResolution(kSupportsMultiResolution);
303 desc.setSupportsTiles(kSupportsTiles);
304 desc.setTemporalClipAccess(false);
305 desc.setRenderTwiceAlways(true);
306 desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
307 desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
308 desc.setRenderThreadSafety(kRenderThreadSafety);
309 }
310
311 template<int dim>
312 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)313 CImgMatrixPluginFactory<dim>::describeInContext(ImageEffectDescriptor& desc,
314 ContextEnum context)
315 {
316 // create the clips and params
317 PageParamDescriptor *page = CImgMatrixPlugin<dim>::describeInContextBegin(desc, context,
318 kSupportsRGBA,
319 kSupportsRGB,
320 kSupportsXY,
321 kSupportsAlpha,
322 kSupportsTiles,
323 /*processRGB=*/ true,
324 /*processAlpha*/ true,
325 /*processIsSecret=*/ false);
326
327
328 {
329 GroupParamDescriptor* group = desc.defineGroupParam(kParamMatrix);
330 if (group) {
331 group->setLabelAndHint(kParamMatrixLabel);
332 group->setOpen(true);
333 }
334 for (int i = dim-1; i >=0; --i) {
335 for (int j = 0; j < dim; ++j) {
336 DoubleParamDescriptor* param = desc.defineDoubleParam(std::string(kParamMatrix) + (char)('1' + i) + (char)('1' + j));
337 param->setLabelAndHint(kParamMatrixCoeffLabel);
338 param->setRange(-DBL_MAX, DBL_MAX);
339 param->setDisplayRange(-1., 1.);
340 param->setDefault(0.);
341 if (j < dim - 1) {
342 param->setLayoutHint(eLayoutHintNoNewLine, 1);
343 }
344 if (group) {
345 param->setParent(*group);
346 }
347 if (page) {
348 page->addChild(*param);
349 }
350 }
351 }
352 }
353 {
354 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamNormalize);
355 param->setLabelAndHint(kParamNormalizeLabel);
356 param->setDefault(false);
357 param->setAnimates(false);
358 //if (group) {
359 // param->setParent(*group);
360 //}
361 if (page) {
362 page->addChild(*param);
363 }
364 }
365
366 CImgMatrixPlugin<dim>::describeInContextEnd(desc, context, page);
367 }
368
369 template<int dim>
370 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)371 CImgMatrixPluginFactory<dim>::createInstance(OfxImageEffectHandle handle,
372 ContextEnum /*context*/)
373 {
374 return new CImgMatrixPlugin<dim>(handle);
375 }
376
377 static CImgMatrixPluginFactory<3> p3(kPluginIdentifier "3x3", kPluginVersionMajor, kPluginVersionMinor);
378 static CImgMatrixPluginFactory<5> p5(kPluginIdentifier "5x5", kPluginVersionMajor, kPluginVersionMinor);
379 mRegisterPluginFactoryInstance(p3)
380 mRegisterPluginFactoryInstance(p5)
381
382 OFXS_NAMESPACE_ANONYMOUS_EXIT
383
384