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 &paramName) 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