1 /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3  * This file is part of openfx-supportext <https://github.com/devernay/openfx-supportext>,
4  * Copyright (C) 2013-2018 INRIA
5  *
6  * openfx-supportext is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * openfx-supportext is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with openfx-supportext.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
18  * ***** END LICENSE BLOCK ***** */
19 
20 /*
21  * OFX Masking/Mixing help functions
22  */
23 
24 #ifndef Misc_ofxsMaskMix_h
25 #define Misc_ofxsMaskMix_h
26 
27 #include <cfloat> // FLT_EPSILON
28 
29 #include <ofxsImageEffect.h>
30 
31 #define kParamPremult "premult"
32 #define kParamPremultLabel "(Un)premult"
33 #define kParamPremultHint \
34     "Divide the image by the alpha channel before processing, and re-multiply it afterwards. " \
35     "Use if the input images are premultiplied."
36 
37 #define kParamPremultChannel "premultChannel"
38 #define kParamPremultChannelLabel "By"
39 #define kParamPremultChannelHint \
40     "The channel to use for (un)premult."
41 #define kParamPremultChannelR "R", "R channel from input", "r"
42 #define kParamPremultChannelG "G", "G channel from input", "g"
43 #define kParamPremultChannelB "B", "B channel from input", "b"
44 #define kParamPremultChannelA "A", "A channel from input", "a"
45 
46 #define kParamMix "mix"
47 #define kParamMixLabel "Mix"
48 #define kParamMixHint "Mix factor between the original and the transformed image."
49 #define kParamMaskApply "mask"
50 #define kParamMaskApplyLabel "Mask"
51 #define kParamMaskApplyHint "When checked, mask is applied."
52 #define kParamMaskInvert "maskInvert"
53 #define kParamMaskInvertLabel "Invert Mask"
54 #define kParamMaskInvertHint "When checked, the effect is fully applied where the mask is 0."
55 
56 namespace OFX {
57 inline
58 void
ofxsPremultDescribeParams(OFX::ImageEffectDescriptor & desc,OFX::PageParamDescriptor * page)59 ofxsPremultDescribeParams(OFX::ImageEffectDescriptor &desc,
60                           OFX::PageParamDescriptor *page)
61 {
62     {
63         OFX::BooleanParamDescriptor* param = desc.defineBooleanParam(kParamPremult);
64         param->setLabel(kParamPremultLabel);
65         param->setHint(kParamPremultHint);
66 #ifdef OFX_EXTENSIONS_NUKE
67         param->setLayoutHint(eLayoutHintNoNewLine, 1);
68 #endif
69         if (page) {
70             page->addChild(*param);
71         }
72     }
73     {
74         // not yet implemented, for future use (whenever deep compositing is supported)
75         OFX::ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamPremultChannel);
76         param->setLabel(kParamPremultChannelLabel);
77         param->setHint(kParamPremultChannelHint);
78         param->appendOption(kParamPremultChannelR);
79         param->appendOption(kParamPremultChannelG);
80         param->appendOption(kParamPremultChannelB);
81         param->appendOption(kParamPremultChannelA);
82         param->setDefault(3); // alpha
83         param->setIsSecret(true); // not yet implemented
84         if (page) {
85             page->addChild(*param);
86         }
87     }
88 }
89 
90 inline
91 bool
ofxsMaskIsAlwaysConnected(OFX::ImageEffectHostDescription * desc)92 ofxsMaskIsAlwaysConnected(OFX::ImageEffectHostDescription *desc)
93 {
94     return (desc->hostName.compare(0, 14, "DaVinciResolve") == 0);
95 }
96 
97 inline
98 void
ofxsMaskDescribeParams(OFX::ImageEffectDescriptor & desc,OFX::PageParamDescriptor * page)99 ofxsMaskDescribeParams(OFX::ImageEffectDescriptor &desc,
100                        OFX::PageParamDescriptor *page)
101 {
102     // If the host always sees mask clips are connected, this is a problem because
103     // mask will appear as black and transparent, although it is not connected
104     if ( ofxsMaskIsAlwaysConnected( OFX::getImageEffectHostDescription() ) ) {
105         OFX::BooleanParamDescriptor* param = desc.defineBooleanParam(kParamMaskApply);
106         param->setLabel(kParamMaskApplyLabel);
107         param->setHint(kParamMaskApplyHint);
108         if (page) {
109             page->addChild(*param);
110         }
111     }
112     {
113         OFX::BooleanParamDescriptor* param = desc.defineBooleanParam(kParamMaskInvert);
114         param->setLabel(kParamMaskInvertLabel);
115         param->setHint(kParamMaskInvertHint);
116         if (page) {
117             page->addChild(*param);
118         }
119     }
120 }
121 
122 inline
123 void
ofxsMixDescribeParams(OFX::ImageEffectDescriptor & desc,OFX::PageParamDescriptor * page)124 ofxsMixDescribeParams(OFX::ImageEffectDescriptor &desc,
125                           OFX::PageParamDescriptor *page)
126 {
127     // GENERIC (MASKED)
128     //
129     {
130         OFX::DoubleParamDescriptor* param = desc.defineDoubleParam(kParamMix);
131         param->setLabel(kParamMixLabel);
132         param->setHint(kParamMixHint);
133         param->setDefault(1.);
134         param->setIncrement(0.01);
135         param->setRange(0., 1.);
136         param->setDisplayRange(0., 1.);
137         if (page) {
138             page->addChild(*param);
139         }
140     }
141 }
142 
143 inline
144 void
ofxsMaskMixDescribeParams(OFX::ImageEffectDescriptor & desc,OFX::PageParamDescriptor * page)145 ofxsMaskMixDescribeParams(OFX::ImageEffectDescriptor &desc,
146                           OFX::PageParamDescriptor *page)
147 {
148     // GENERIC (MASKED)
149     //
150     ofxsMaskDescribeParams(desc, page);
151     ofxsMixDescribeParams(desc, page);
152 }
153 
154 
155 template <class T>
156 inline
157 T
ofxsClamp(T v,int min,int max)158 ofxsClamp(T v,
159           int min,
160           int max)
161 {
162     if ( v < T(min) ) {
163         return T(min);
164     }
165     if ( v > T(max) ) {
166         return T(max);
167     }
168 
169     return v;
170 }
171 
172 template <typename PIX, int maxValue>
173 inline
174 PIX
ofxsClampIfInt(float v,int min,int max)175 ofxsClampIfInt(float v,
176                int min,
177                int max)
178 {
179     if (maxValue == 1) {
180         return (PIX)(v);
181     }
182 
183     return (PIX)(ofxsClamp(v, min, max) * maxValue + 0.5);
184 }
185 
186 // normalize in [0,1]
187 template <class PIX, int nComponents, int maxValue>
188 void
ofxsToRGBA(const PIX * srcPix,float unpPix[4])189 ofxsToRGBA(const PIX *srcPix,
190            float unpPix[4])
191 {
192     if (!srcPix) {
193         // no src pixel here, be black and transparent
194         for (int c = 0; c < 4; ++c) {
195             unpPix[c] = 0.f;
196         }
197 
198         return;
199     }
200 
201     if (nComponents == 1) {
202         unpPix[0] = 0.f;
203         unpPix[1] = 0.f;
204         unpPix[2] = 0.f;
205         unpPix[3] = srcPix[0] / (float)maxValue;
206 
207         return;
208     }
209 
210     if (nComponents == 2) {
211         unpPix[0] = srcPix[0] / (float)maxValue;
212         unpPix[1] = srcPix[1] / (float)maxValue;
213         unpPix[2] = 0.f;
214         unpPix[3] = 1.0f;
215 
216         return;
217     }
218 
219     unpPix[0] = srcPix[0] / (float)maxValue;
220     unpPix[1] = srcPix[1] / (float)maxValue;
221     unpPix[2] = srcPix[2] / (float)maxValue;
222     unpPix[3] = (nComponents == 4) ? (srcPix[3] / (float)maxValue) : 1.0f;
223 }
224 
225 // normalize in [0,1] and unpremultiply srcPix
226 // if premult is false, just normalize
227 // unpremult by alpha <= 0 gives identity
228 // premult(unpremult(p)) or premult(unpremult(p)) is thus not identity for alpha <= 0
229 template <class PIX, int nComponents, int maxValue>
230 void
ofxsUnPremult(const PIX * srcPix,float unpPix[4],bool premult,int)231 ofxsUnPremult(const PIX *srcPix,
232               float unpPix[4],
233               bool premult,
234               int /*premultChannel*/)
235 {
236     if (!srcPix) {
237         // no src pixel here, be black and transparent
238         for (int c = 0; c < 4; ++c) {
239             unpPix[c] = 0.f;
240         }
241 
242         return;
243     }
244 
245     if (nComponents == 1) {
246         unpPix[0] = 0.f;
247         unpPix[1] = 0.f;
248         unpPix[2] = 0.f;
249         unpPix[3] = srcPix[0] / (float)maxValue;
250 
251         return;
252     }
253 
254     if (nComponents == 2) {
255         unpPix[0] = srcPix[0] / (float)maxValue;
256         unpPix[1] = srcPix[1] / (float)maxValue;
257         unpPix[2] = 0.f;
258         unpPix[3] = 1.0f;
259 
260         return;
261     }
262 
263     // unpremult by alpha <= 0 gives identity
264     if ( !premult || (nComponents == 3) || (srcPix[3] <= 0) ) {
265         unpPix[0] = srcPix[0] / (float)maxValue;
266         unpPix[1] = srcPix[1] / (float)maxValue;
267         unpPix[2] = srcPix[2] / (float)maxValue;
268         unpPix[3] = (nComponents == 4) ? (srcPix[3] / (float)maxValue) : 1.0f;
269 
270         return;
271     }
272 
273     assert(nComponents == 4);
274     PIX alpha = srcPix[3];
275     if ( alpha > (PIX)(FLT_EPSILON * maxValue) ) {
276         unpPix[0] = srcPix[0] / (float)alpha;
277         unpPix[1] = srcPix[1] / (float)alpha;
278         unpPix[2] = srcPix[2] / (float)alpha;
279     } else {
280         unpPix[0] = srcPix[0] / (float)maxValue;
281         unpPix[1] = srcPix[1] / (float)maxValue;
282         unpPix[2] = srcPix[2] / (float)maxValue;
283     }
284     unpPix[3] = srcPix[3] / (float)maxValue;
285 } // ofxsUnPremult
286 
287 // unpPix is in [0, 1]
288 // premultiply and denormalize in [0, maxValue]
289 // if premult is false, just denormalize
290 // premult by alpha <= 0 gives 0
291 // premult(unpremult(p)) or premult(unpremult(p)) is thus not identity for alpha <= 0
292 template <class PIX, int nComponents, int maxValue>
293 void
ofxsPremult(const float unpPix[4],float * tmpPix,bool premult,int)294 ofxsPremult(const float unpPix[4],
295             float *tmpPix,
296             bool premult,
297             int /*premultChannel*/)
298 {
299     if (nComponents == 1) {
300         tmpPix[0] = unpPix[3] * maxValue;
301 
302         return;
303     }
304 
305     if ( !premult ) {
306         tmpPix[0] = unpPix[0] * maxValue;
307         if (nComponents >= 2) {
308             tmpPix[1] = unpPix[1] * maxValue;
309         }
310         if (nComponents >= 3) {
311             tmpPix[2] = unpPix[2] * maxValue;
312         }
313         if (nComponents >= 4) {
314             tmpPix[3] = unpPix[3] * maxValue;
315         }
316 
317         return;
318     }
319 
320     // premult by alpha <= 0 gives 0
321     float alpha = (std::max)(0.f, unpPix[3]);
322 
323     tmpPix[0] = unpPix[0] * alpha * maxValue;
324     if (nComponents >= 2) {
325         tmpPix[1] = unpPix[1] * alpha * maxValue;
326     }
327     if (nComponents >= 3) {
328         tmpPix[2] = unpPix[2] * alpha * maxValue;
329     }
330     if (nComponents >= 4) {
331         tmpPix[3] = alpha * maxValue;
332     }
333 }
334 
335 // tmpPix is not normalized, it is within [0,maxValue]
336 template <class PIX, int nComponents, int maxValue>
337 void
ofxsPix(const float * tmpPix,PIX * dstPix)338 ofxsPix(const float *tmpPix, //!< interpolated pixel
339         PIX *dstPix) //!< destination pixel
340 {
341     // no mask, no mix
342     for (int c = 0; c < nComponents; ++c) {
343         dstPix[c] = ofxsClampIfInt<PIX, maxValue>(tmpPix[c], 0, maxValue);
344     }
345 } // ofxsMixPix
346 
347 // unpPix is normalized between [0,1]
348 template <class PIX, int nComponents, int maxValue>
349 void
ofxsPremultPix(const float unpPix[4],bool premult,int premultChannel,PIX * dstPix)350 ofxsPremultPix(const float unpPix[4], //!< interpolated unpremultiplied pixel
351                bool premult,
352                int premultChannel,
353                PIX *dstPix) //!< destination pixel
354 {
355     float tmpPix[nComponents];
356 
357     // unpPix is in [0..1]
358     ofxsPremult<PIX, nComponents, maxValue>(unpPix, tmpPix, premult, premultChannel);
359     // tmpPix is in [0..maxValue]
360     ofxsPix<PIX, nComponents, maxValue>(tmpPix, dstPix);
361 }
362 
363 
364 // tmpPix is not normalized, it is within [0,maxValue]
365 template <class PIX, int nComponents, int maxValue>
366 void
ofxsMixPix(const float * tmpPix,const PIX * srcPix,float mix,PIX * dstPix)367 ofxsMixPix(const float *tmpPix, //!< interpolated pixel
368            const PIX *srcPix, //!< the background image (the output is srcImg where maskImg=0, else it is tmpPix)
369            float mix, //!< mix factor between the output and bkImg
370            PIX *dstPix) //!< destination pixel
371 {
372     if (mix == 1.) {
373         ofxsPix<PIX, nComponents, maxValue>(tmpPix, dstPix);
374     } else {
375         // just mix
376         float alpha = mix;
377         if (alpha == 0.) {
378             if (srcPix) {
379                 for (int c = 0; c < nComponents; ++c) {
380                     dstPix[c] = ofxsClampIfInt<PIX, maxValue>(srcPix[c], 0, maxValue);
381                 }
382             } else {
383                 for (int c = 0; c < nComponents; ++c) {
384                     dstPix[c] = 0;
385                 }
386             }
387         } else if (alpha == 1) {
388             for (int c = 0; c < nComponents; ++c) {
389                 dstPix[c] = ofxsClampIfInt<PIX, maxValue>(tmpPix[c], 0, maxValue);
390             }
391         } else {
392             if (srcPix) {
393                 for (int c = 0; c < nComponents; ++c) {
394                     float v = tmpPix[c] * alpha + (1.f - alpha) * srcPix[c];
395                     dstPix[c] = ofxsClampIfInt<PIX, maxValue>(v, 0, maxValue);
396                 }
397             } else {
398                 for (int c = 0; c < nComponents; ++c) {
399                     float v = tmpPix[c] * alpha;
400                     dstPix[c] = ofxsClampIfInt<PIX, maxValue>(v, 0, maxValue);
401                 }
402             }
403         }
404     }
405 } // ofxsMixPix
406 
407 // tmpPix is not normalized, it is within [0,maxValue]
408 template <class PIX, int nComponents, int maxValue, bool masked>
409 void
ofxsMaskMixPix(const float * tmpPix,int x,int y,const PIX * srcPix,bool domask,const OFX::Image * maskImg,float mix,bool maskInvert,PIX * dstPix)410 ofxsMaskMixPix(const float *tmpPix, //!< interpolated pixel
411                int x, //!< coordinates for the pixel to be computed (PIXEL coordinates)
412                int y,
413                const PIX *srcPix, //!< the background image (the output is srcImg where maskImg=0, else it is tmpPix)
414                bool domask, //!< apply the mask?
415                const OFX::Image *maskImg, //!< the mask image (ignored if masked=false or domask=false), which must be Alpha
416                float mix, //!< mix factor between the output and bkImg
417                bool maskInvert, //<! invert mask behavior
418                PIX *dstPix) //!< destination pixel
419 {
420     // For a multi-planar effect, the mask image may have any components
421     assert(!domask || !maskImg || maskImg->getPixelComponentCount() > 0);
422     const PIX *maskPix = NULL;
423     float maskScale = 1.f;
424 
425     // are we doing masking
426     if (!masked) {
427         ofxsMixPix<PIX, nComponents, maxValue>(tmpPix, srcPix, mix, dstPix);
428     } else {
429         if (domask) {
430             // we do, get the pixel from the mask
431             maskPix = maskImg ? (const PIX *)maskImg->getPixelAddress(x, y) : 0;
432             // figure the scale factor from that pixel
433             if (maskPix == 0) {
434                 maskScale = maskInvert ? 1.f : 0.f;
435             } else {
436                 maskScale = *maskPix / float(maxValue);
437                 if (maskInvert) {
438                     maskScale = 1.f - maskScale;
439                 }
440             }
441         }
442         float alpha = maskScale * mix;
443         if (alpha == 0.) {
444             if (srcPix) {
445                 for (int c = 0; c < nComponents; ++c) {
446                     dstPix[c] = ofxsClampIfInt<PIX, maxValue>(srcPix[c], 0, maxValue);
447                 }
448             } else {
449                 for (int c = 0; c < nComponents; ++c) {
450                     dstPix[c] = 0;
451                 }
452             }
453         } else if (alpha == 1.) {
454             for (int c = 0; c < nComponents; ++c) {
455                 dstPix[c] = ofxsClampIfInt<PIX, maxValue>(tmpPix[c], 0, maxValue);
456             }
457         } else {
458             if (srcPix) {
459                 for (int c = 0; c < nComponents; ++c) {
460                     float v = tmpPix[c] * alpha + (1.f - alpha) * srcPix[c];
461                     dstPix[c] = ofxsClampIfInt<PIX, maxValue>(v, 0, maxValue);
462                 }
463             } else {
464                 for (int c = 0; c < nComponents; ++c) {
465                     float v = tmpPix[c] * alpha;
466                     dstPix[c] = ofxsClampIfInt<PIX, maxValue>(v, 0, maxValue);
467                 }
468             }
469         }
470     }
471 } // ofxsMaskMixPix
472 
473 // unpPix is normalized between [0,1]
474 template <class PIX, int nComponents, int maxValue, bool masked>
475 void
ofxsPremultMaskMixPix(const float unpPix[4],bool premult,int premultChannel,int x,int y,const PIX * srcPix,bool domask,const OFX::Image * maskImg,float mix,bool maskInvert,PIX * dstPix)476 ofxsPremultMaskMixPix(const float unpPix[4], //!< interpolated unpremultiplied pixel
477                       bool premult,
478                       int premultChannel,
479                       int x, //!< coordinates for the pixel to be computed (PIXEL coordinates)
480                       int y,
481                       const PIX *srcPix, //!< the background image (the output is srcImg where maskImg=0, else it is tmpPix)
482                       bool domask, //!< apply the mask?
483                       const OFX::Image *maskImg, //!< the mask image (ignored if masked=false or domask=false)
484                       float mix, //!< mix factor between the output and bkImg
485                       bool maskInvert, //<! invert mask behavior
486                       PIX *dstPix) //!< destination pixel
487 {
488     assert(!domask || !maskImg || maskImg->getPixelComponents() == ePixelComponentAlpha);
489     float tmpPix[nComponents];
490 
491     // unpPix is in [0..1]
492     ofxsPremult<PIX, nComponents, maxValue>(unpPix, tmpPix, premult, premultChannel);
493     // tmpPix is in [0..maxValue]
494     ofxsMaskMixPix<PIX, nComponents, maxValue, masked>(tmpPix, x, y, srcPix, domask, maskImg, mix, maskInvert, dstPix);
495 }
496 
497 // unpPix is normalized between [0,1]
498 template <class PIX, int nComponents, int maxValue>
499 void
ofxsPremultMixPix(const float unpPix[4],bool premult,int premultChannel,const PIX * srcPix,float mix,PIX * dstPix)500 ofxsPremultMixPix(const float unpPix[4], //!< interpolated unpremultiplied pixel
501                   bool premult,
502                   int premultChannel,
503                   const PIX *srcPix, //!< the background image (the output is srcImg where maskImg=0, else it is tmpPix)
504                   float mix, //!< mix factor between the output and bkImg
505                   PIX *dstPix) //!< destination pixel
506 {
507     float tmpPix[nComponents];
508 
509     // unpPix is in [0..1]
510     ofxsPremult<PIX, nComponents, maxValue>(unpPix, tmpPix, premult, premultChannel);
511     // tmpPix is in [0..maxValue]
512     ofxsMixPix<PIX, nComponents, maxValue>(tmpPix, srcPix, mix, dstPix);
513 }
514 
515 // tmpPix is not normalized, it is within [0,maxValue]
516 template <class PIX, int nComponents, int maxValue, bool masked>
517 void
ofxsMaskMix(const float * tmpPix,int x,int y,const OFX::Image * srcImg,bool domask,const OFX::Image * maskImg,float mix,bool maskInvert,PIX * dstPix)518 ofxsMaskMix(const float *tmpPix, //!< interpolated pixel
519             int x, //!< coordinates for the pixel to be computed (PIXEL coordinates)
520             int y,
521             const OFX::Image *srcImg, //!< the background image (the output is srcImg where maskImg=0, else it is tmpPix)
522             bool domask, //!< apply the mask?
523             const OFX::Image *maskImg, //!< the mask image (ignored if masked=false or domask=false)
524             float mix, //!< mix factor between the output and bkImg
525             bool maskInvert, //<! invert mask behavior
526             PIX *dstPix) //!< destination pixel
527 {
528     assert(!domask || !maskImg || maskImg->getPixelComponents() == ePixelComponentAlpha);
529     const PIX *srcPix = NULL;
530 
531     // are we doing masking/mixing? in this case, retrieve srcPix
532     if (masked && srcImg) {
533         if ( (domask /*&& maskImg*/) || (mix != 1.) ) {
534             srcPix = (const PIX *)srcImg->getPixelAddress(x, y);
535         }
536     }
537 
538     return ofxsMaskMixPix<PIX, nComponents, maxValue, masked>(tmpPix, x, y, srcPix, domask, maskImg, mix, maskInvert, dstPix);
539 }
540 } // OFX
541 
542 #endif // ifndef Misc_ofxsMaskMix_h
543