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