1 // Copyright 2008-present Contributors to the OpenImageIO project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
4 
5 /// \file
6 /// Implementation of ImageBufAlgo algorithms that do math on
7 /// single pixels at a time.
8 
9 #include <cmath>
10 #include <iostream>
11 #include <limits>
12 
13 #include <OpenImageIO/dassert.h>
14 #include <OpenImageIO/deepdata.h>
15 #include <OpenImageIO/imagebuf.h>
16 #include <OpenImageIO/imagebufalgo.h>
17 #include <OpenImageIO/imagebufalgo_util.h>
18 #include <OpenImageIO/simd.h>
19 
20 #include "imageio_pvt.h"
21 
22 
23 OIIO_NAMESPACE_BEGIN
24 
25 
26 template<class Rtype, class Atype, class Btype>
27 static bool
min_impl(ImageBuf & R,const ImageBuf & A,const ImageBuf & B,ROI roi,int nthreads)28 min_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi,
29          int nthreads)
30 {
31     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
32         ImageBuf::Iterator<Rtype> r(R, roi);
33         ImageBuf::ConstIterator<Atype> a(A, roi);
34         ImageBuf::ConstIterator<Btype> b(B, roi);
35         for (; !r.done(); ++r, ++a, ++b)
36             for (int c = roi.chbegin; c < roi.chend; ++c)
37                 r[c] = std::min(a[c], b[c]);
38     });
39     return true;
40 }
41 
42 
43 
44 template<class Rtype, class Atype>
45 static bool
min_impl(ImageBuf & R,const ImageBuf & A,cspan<float> b,ROI roi,int nthreads)46 min_impl(ImageBuf& R, const ImageBuf& A, cspan<float> b, ROI roi, int nthreads)
47 {
48     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
49         ImageBuf::Iterator<Rtype> r(R, roi);
50         ImageBuf::ConstIterator<Atype> a(A, roi);
51         for (; !r.done(); ++r, ++a)
52             for (int c = roi.chbegin; c < roi.chend; ++c)
53                 r[c] = std::min(a[c], b[c]);
54     });
55     return true;
56 }
57 
58 
59 
60 bool
min(ImageBuf & dst,Image_or_Const A_,Image_or_Const B_,ROI roi,int nthreads)61 ImageBufAlgo::min(ImageBuf& dst, Image_or_Const A_, Image_or_Const B_, ROI roi,
62                   int nthreads)
63 {
64     pvt::LoggedTimer logtime("IBA::min");
65     if (A_.is_img() && B_.is_img()) {
66         const ImageBuf &A(A_.img()), &B(B_.img());
67         if (!IBAprep(roi, &dst, &A, &B))
68             return false;
69         ROI origroi = roi;
70         roi.chend = std::min(roi.chend, std::min(A.nchannels(), B.nchannels()));
71         bool ok;
72         OIIO_DISPATCH_COMMON_TYPES3(ok, "min", min_impl, dst.spec().format,
73                                     A.spec().format, B.spec().format, dst, A, B,
74                                     roi, nthreads);
75         if (roi.chend < origroi.chend && A.nchannels() != B.nchannels()) {
76             // Edge case: A and B differed in nchannels, we allocated dst to be
77             // the bigger of them, but adjusted roi to be the lesser. Now handle
78             // the channels that got left out because they were not common to
79             // all the inputs.
80             OIIO_ASSERT(roi.chend <= dst.nchannels());
81             roi.chbegin = roi.chend;
82             roi.chend   = origroi.chend;
83             if (A.nchannels() > B.nchannels()) {  // A exists
84                 copy(dst, A, dst.spec().format, roi, nthreads);
85             } else {  // B exists
86                 copy(dst, B, dst.spec().format, roi, nthreads);
87             }
88         }
89         return ok;
90     }
91     if (A_.is_val() && B_.is_img())  // canonicalize to A_img, B_val
92         A_.swap(B_);
93     if (A_.is_img() && B_.is_val()) {
94         const ImageBuf& A(A_.img());
95         cspan<float> b = B_.val();
96         if (!IBAprep(roi, &dst, &A, IBAprep_CLAMP_MUTUAL_NCHANNELS))
97             return false;
98         IBA_FIX_PERCHAN_LEN_DEF(b, A.nchannels());
99         bool ok;
100         OIIO_DISPATCH_COMMON_TYPES2(ok, "min", min_impl, dst.spec().format,
101                                     A.spec().format, dst, A, b, roi, nthreads);
102         return ok;
103     }
104     // Remaining cases: error
105     dst.errorf("ImageBufAlgo::min(): at least one argument must be an image");
106     return false;
107 }
108 
109 
110 
111 ImageBuf
min(Image_or_Const A,Image_or_Const B,ROI roi,int nthreads)112 ImageBufAlgo::min(Image_or_Const A, Image_or_Const B, ROI roi, int nthreads)
113 {
114     ImageBuf result;
115     bool ok = min(result, A, B, roi, nthreads);
116     if (!ok && !result.has_error())
117         result.errorf("ImageBufAlgo::min() error");
118     return result;
119 }
120 
121 
122 
123 template<class Rtype, class Atype, class Btype>
124 static bool
max_impl(ImageBuf & R,const ImageBuf & A,const ImageBuf & B,ROI roi,int nthreads)125 max_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi,
126          int nthreads)
127 {
128     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
129         ImageBuf::Iterator<Rtype> r(R, roi);
130         ImageBuf::ConstIterator<Atype> a(A, roi);
131         ImageBuf::ConstIterator<Btype> b(B, roi);
132         for (; !r.done(); ++r, ++a, ++b)
133             for (int c = roi.chbegin; c < roi.chend; ++c)
134                 r[c] = std::max(a[c], b[c]);
135     });
136     return true;
137 }
138 
139 
140 
141 template<class Rtype, class Atype>
142 static bool
max_impl(ImageBuf & R,const ImageBuf & A,cspan<float> b,ROI roi,int nthreads)143 max_impl(ImageBuf& R, const ImageBuf& A, cspan<float> b, ROI roi, int nthreads)
144 {
145     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
146         ImageBuf::Iterator<Rtype> r(R, roi);
147         ImageBuf::ConstIterator<Atype> a(A, roi);
148         for (; !r.done(); ++r, ++a)
149             for (int c = roi.chbegin; c < roi.chend; ++c)
150                 r[c] = std::max(a[c], b[c]);
151     });
152     return true;
153 }
154 
155 
156 
157 bool
max(ImageBuf & dst,Image_or_Const A_,Image_or_Const B_,ROI roi,int nthreads)158 ImageBufAlgo::max(ImageBuf& dst, Image_or_Const A_, Image_or_Const B_, ROI roi,
159                   int nthreads)
160 {
161     pvt::LoggedTimer logtime("IBA::max");
162     if (A_.is_img() && B_.is_img()) {
163         const ImageBuf &A(A_.img()), &B(B_.img());
164         if (!IBAprep(roi, &dst, &A, &B))
165             return false;
166         ROI origroi = roi;
167         roi.chend = std::max(roi.chend, std::max(A.nchannels(), B.nchannels()));
168         bool ok;
169         OIIO_DISPATCH_COMMON_TYPES3(ok, "max", max_impl, dst.spec().format,
170                                     A.spec().format, B.spec().format, dst, A, B,
171                                     roi, nthreads);
172         if (roi.chend < origroi.chend && A.nchannels() != B.nchannels()) {
173             // Edge case: A and B differed in nchannels, we allocated dst to be
174             // the bigger of them, but adjusted roi to be the lesser. Now handle
175             // the channels that got left out because they were not common to
176             // all the inputs.
177             OIIO_ASSERT(roi.chend <= dst.nchannels());
178             roi.chbegin = roi.chend;
179             roi.chend   = origroi.chend;
180             if (A.nchannels() > B.nchannels()) {  // A exists
181                 copy(dst, A, dst.spec().format, roi, nthreads);
182             } else {  // B exists
183                 copy(dst, B, dst.spec().format, roi, nthreads);
184             }
185         }
186         return ok;
187     }
188     if (A_.is_val() && B_.is_img())  // canonicalize to A_img, B_val
189         A_.swap(B_);
190     if (A_.is_img() && B_.is_val()) {
191         const ImageBuf& A(A_.img());
192         cspan<float> b = B_.val();
193         if (!IBAprep(roi, &dst, &A, IBAprep_CLAMP_MUTUAL_NCHANNELS))
194             return false;
195         IBA_FIX_PERCHAN_LEN_DEF(b, A.nchannels());
196         bool ok;
197         OIIO_DISPATCH_COMMON_TYPES2(ok, "max", max_impl, dst.spec().format,
198                                     A.spec().format, dst, A, b, roi, nthreads);
199         return ok;
200     }
201     // Remaining cases: error
202     dst.errorf("ImageBufAlgo::max(): at least one argument must be an image");
203     return false;
204 }
205 
206 
207 
208 ImageBuf
max(Image_or_Const A,Image_or_Const B,ROI roi,int nthreads)209 ImageBufAlgo::max(Image_or_Const A, Image_or_Const B, ROI roi, int nthreads)
210 {
211     ImageBuf result;
212     bool ok = max(result, A, B, roi, nthreads);
213     if (!ok && !result.has_error())
214         result.errorf("ImageBufAlgo::max() error");
215     return result;
216 }
217 
218 
219 
220 template<class D, class S>
221 static bool
clamp_(ImageBuf & dst,const ImageBuf & src,const float * min,const float * max,bool clampalpha01,ROI roi,int nthreads)222 clamp_(ImageBuf& dst, const ImageBuf& src, const float* min, const float* max,
223        bool clampalpha01, ROI roi, int nthreads)
224 {
225     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
226         ImageBuf::ConstIterator<S> s(src, roi);
227         for (ImageBuf::Iterator<D> d(dst, roi); !d.done(); ++d, ++s) {
228             for (int c = roi.chbegin; c < roi.chend; ++c)
229                 d[c] = OIIO::clamp<float>(s[c], min[c], max[c]);
230         }
231         int a = src.spec().alpha_channel;
232         if (clampalpha01 && a >= roi.chbegin && a < roi.chend) {
233             for (ImageBuf::Iterator<D> d(dst, roi); !d.done(); ++d)
234                 d[a] = OIIO::clamp<float>(d[a], 0.0f, 1.0f);
235         }
236     });
237     return true;
238 }
239 
240 
241 
242 bool
clamp(ImageBuf & dst,const ImageBuf & src,cspan<float> min,cspan<float> max,bool clampalpha01,ROI roi,int nthreads)243 ImageBufAlgo::clamp(ImageBuf& dst, const ImageBuf& src, cspan<float> min,
244                     cspan<float> max, bool clampalpha01, ROI roi, int nthreads)
245 {
246     pvt::LoggedTimer logtime("IBA::clamp");
247     if (!IBAprep(roi, &dst, &src))
248         return false;
249     const float big = std::numeric_limits<float>::max();
250     IBA_FIX_PERCHAN_LEN(min, dst.nchannels(), min.size() ? min.back() : -big,
251                         -big);
252     IBA_FIX_PERCHAN_LEN(max, dst.nchannels(), max.size() ? max.back() : big,
253                         big);
254     bool ok;
255     OIIO_DISPATCH_COMMON_TYPES2(ok, "clamp", clamp_, dst.spec().format,
256                                 src.spec().format, dst, src, min.data(),
257                                 max.data(), clampalpha01, roi, nthreads);
258     return ok;
259 }
260 
261 
262 
263 ImageBuf
clamp(const ImageBuf & src,cspan<float> min,cspan<float> max,bool clampalpha01,ROI roi,int nthreads)264 ImageBufAlgo::clamp(const ImageBuf& src, cspan<float> min, cspan<float> max,
265                     bool clampalpha01, ROI roi, int nthreads)
266 {
267     ImageBuf result;
268     bool ok = clamp(result, src, min, max, clampalpha01, roi, nthreads);
269     if (!ok && !result.has_error())
270         result.errorf("ImageBufAlgo::clamp error");
271     return result;
272 }
273 
274 
275 
276 template<class Rtype, class Atype, class Btype>
277 static bool
absdiff_impl(ImageBuf & R,const ImageBuf & A,const ImageBuf & B,ROI roi,int nthreads)278 absdiff_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi,
279              int nthreads)
280 {
281     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
282         ImageBuf::Iterator<Rtype> r(R, roi);
283         ImageBuf::ConstIterator<Atype> a(A, roi);
284         ImageBuf::ConstIterator<Btype> b(B, roi);
285         for (; !r.done(); ++r, ++a, ++b)
286             for (int c = roi.chbegin; c < roi.chend; ++c)
287                 r[c] = std::abs(a[c] - b[c]);
288     });
289     return true;
290 }
291 
292 
293 template<class Rtype, class Atype>
294 static bool
absdiff_impl(ImageBuf & R,const ImageBuf & A,cspan<float> b,ROI roi,int nthreads)295 absdiff_impl(ImageBuf& R, const ImageBuf& A, cspan<float> b, ROI roi,
296              int nthreads)
297 {
298     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
299         ImageBuf::Iterator<Rtype> r(R, roi);
300         ImageBuf::ConstIterator<Atype> a(A, roi);
301         for (; !r.done(); ++r, ++a)
302             for (int c = roi.chbegin; c < roi.chend; ++c)
303                 r[c] = std::abs(a[c] - b[c]);
304     });
305     return true;
306 }
307 
308 
309 
310 bool
absdiff(ImageBuf & dst,Image_or_Const A_,Image_or_Const B_,ROI roi,int nthreads)311 ImageBufAlgo::absdiff(ImageBuf& dst, Image_or_Const A_, Image_or_Const B_,
312                       ROI roi, int nthreads)
313 {
314     pvt::LoggedTimer logtime("IBA::absdiff");
315     if (!IBAprep(roi, &dst, A_.imgptr(), B_.imgptr(), nullptr,
316                  IBAprep_CLAMP_MUTUAL_NCHANNELS))
317         return false;
318     if (A_.is_img() && B_.is_img()) {
319         const ImageBuf &A(A_.img()), &B(B_.img());
320         ROI origroi = roi;
321         roi.chend = std::min(roi.chend, std::min(A.nchannels(), B.nchannels()));
322         bool ok;
323         OIIO_DISPATCH_COMMON_TYPES3(ok, "absdiff", absdiff_impl,
324                                     dst.spec().format, A.spec().format,
325                                     B.spec().format, dst, A, B, roi, nthreads);
326         if (roi.chend < origroi.chend && A.nchannels() != B.nchannels()) {
327             // Edge case: A and B differed in nchannels, we allocated dst to be
328             // the bigger of them, but adjusted roi to be the lesser. Now handle
329             // the channels that got left out because they were not common to
330             // all the inputs.
331             OIIO_DASSERT(roi.chend <= dst.nchannels());
332             roi.chbegin = roi.chend;
333             roi.chend   = origroi.chend;
334             if (A.nchannels() > B.nchannels()) {  // A exists
335                 copy(dst, A, dst.spec().format, roi, nthreads);
336             } else {  // B exists
337                 copy(dst, B, dst.spec().format, roi, nthreads);
338             }
339         }
340         return ok;
341     }
342     if (A_.is_val() && B_.is_img())  // canonicalize to A_img, B_val
343         A_.swap(B_);
344     if (A_.is_img() && B_.is_val()) {
345         const ImageBuf& A(A_.img());
346         cspan<float> b = B_.val();
347         IBA_FIX_PERCHAN_LEN_DEF(b, A.nchannels());
348         bool ok;
349         OIIO_DISPATCH_COMMON_TYPES2(ok, "absdiff", absdiff_impl,
350                                     dst.spec().format, A.spec().format, dst, A,
351                                     b, roi, nthreads);
352         return ok;
353     }
354     // Remaining cases: error
355     dst.errorf(
356         "ImageBufAlgo::absdiff(): at least one argument must be an image");
357     return false;
358 }
359 
360 
361 
362 ImageBuf
absdiff(Image_or_Const A,Image_or_Const B,ROI roi,int nthreads)363 ImageBufAlgo::absdiff(Image_or_Const A, Image_or_Const B, ROI roi, int nthreads)
364 {
365     ImageBuf result;
366     bool ok = absdiff(result, A, B, roi, nthreads);
367     if (!ok && !result.has_error())
368         result.errorf("ImageBufAlgo::absdiff() error");
369     return result;
370 }
371 
372 
373 
374 bool
abs(ImageBuf & dst,const ImageBuf & A,ROI roi,int nthreads)375 ImageBufAlgo::abs(ImageBuf& dst, const ImageBuf& A, ROI roi, int nthreads)
376 {
377     // Define abs in terms of absdiff(A,0.0)
378     return absdiff(dst, A, 0.0f, roi, nthreads);
379 }
380 
381 
382 
383 ImageBuf
abs(const ImageBuf & A,ROI roi,int nthreads)384 ImageBufAlgo::abs(const ImageBuf& A, ROI roi, int nthreads)
385 {
386     ImageBuf result;
387     bool ok = abs(result, A, roi, nthreads);
388     if (!ok && !result.has_error())
389         result.errorf("abs error");
390     return result;
391 }
392 
393 
394 
395 template<class Rtype, class Atype>
396 static bool
pow_impl(ImageBuf & R,const ImageBuf & A,cspan<float> b,ROI roi,int nthreads)397 pow_impl(ImageBuf& R, const ImageBuf& A, cspan<float> b, ROI roi, int nthreads)
398 {
399     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
400         ImageBuf::ConstIterator<Atype> a(A, roi);
401         for (ImageBuf::Iterator<Rtype> r(R, roi); !r.done(); ++r, ++a)
402             for (int c = roi.chbegin; c < roi.chend; ++c)
403                 r[c] = pow(a[c], b[c]);
404     });
405     return true;
406 }
407 
408 
409 bool
pow(ImageBuf & dst,const ImageBuf & A,cspan<float> b,ROI roi,int nthreads)410 ImageBufAlgo::pow(ImageBuf& dst, const ImageBuf& A, cspan<float> b, ROI roi,
411                   int nthreads)
412 {
413     pvt::LoggedTimer logtime("IBA::pow");
414     if (!IBAprep(roi, &dst, &A, IBAprep_CLAMP_MUTUAL_NCHANNELS))
415         return false;
416     IBA_FIX_PERCHAN_LEN_DEF(b, dst.nchannels());
417     bool ok;
418     OIIO_DISPATCH_COMMON_TYPES2(ok, "pow", pow_impl, dst.spec().format,
419                                 A.spec().format, dst, A, b, roi, nthreads);
420     return ok;
421 }
422 
423 
424 ImageBuf
pow(const ImageBuf & A,cspan<float> b,ROI roi,int nthreads)425 ImageBufAlgo::pow(const ImageBuf& A, cspan<float> b, ROI roi, int nthreads)
426 {
427     ImageBuf result;
428     bool ok = pow(result, A, b, roi, nthreads);
429     if (!ok && !result.has_error())
430         result.errorf("pow error");
431     return result;
432 }
433 
434 
435 
436 template<class D, class S>
437 static bool
channel_sum_(ImageBuf & dst,const ImageBuf & src,cspan<float> weights,ROI roi,int nthreads)438 channel_sum_(ImageBuf& dst, const ImageBuf& src, cspan<float> weights, ROI roi,
439              int nthreads)
440 {
441     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
442         ImageBuf::Iterator<D> d(dst, roi);
443         ImageBuf::ConstIterator<S> s(src, roi);
444         for (; !d.done(); ++d, ++s) {
445             float sum = 0.0f;
446             for (int c = roi.chbegin; c < roi.chend; ++c)
447                 sum += s[c] * weights[c];
448             d[0] = sum;
449         }
450     });
451     return true;
452 }
453 
454 
455 
456 bool
channel_sum(ImageBuf & dst,const ImageBuf & src,cspan<float> weights,ROI roi,int nthreads)457 ImageBufAlgo::channel_sum(ImageBuf& dst, const ImageBuf& src,
458                           cspan<float> weights, ROI roi, int nthreads)
459 {
460     pvt::LoggedTimer logtime("IBA::channel_sum");
461     if (!roi.defined())
462         roi = get_roi(src.spec());
463     roi.chend      = std::min(roi.chend, src.nchannels());
464     ROI dstroi     = roi;
465     dstroi.chbegin = 0;
466     dstroi.chend   = 1;
467     if (!IBAprep(dstroi, &dst))
468         return false;
469 
470     IBA_FIX_PERCHAN_LEN(weights, dst.nchannels(), 0.0f, 1.0f);
471 
472     bool ok;
473     OIIO_DISPATCH_COMMON_TYPES2(ok, "channel_sum", channel_sum_,
474                                 dst.spec().format, src.spec().format, dst, src,
475                                 weights, roi, nthreads);
476     return ok;
477 }
478 
479 
480 ImageBuf
channel_sum(const ImageBuf & src,cspan<float> weights,ROI roi,int nthreads)481 ImageBufAlgo::channel_sum(const ImageBuf& src, cspan<float> weights, ROI roi,
482                           int nthreads)
483 {
484     ImageBuf result;
485     bool ok = channel_sum(result, src, weights, roi, nthreads);
486     if (!ok && !result.has_error())
487         result.errorf("channel_sum error");
488     return result;
489 }
490 
491 
492 
493 inline float
rangecompress(float x)494 rangecompress(float x)
495 {
496     // Formula courtesy of Sony Pictures Imageworks
497 #if 0 /* original coeffs -- identity transform for vals < 1 */
498     const float x1 = 1.0, a = 1.2607481479644775391;
499     const float b = 0.28785100579261779785, c = -1.4042005538940429688;
500 #else /* but received wisdom is that these work better */
501     const float x1 = 0.18, a = -0.54576885700225830078;
502     const float b = 0.18351669609546661377, c = 284.3577880859375;
503 #endif
504 
505     float absx = fabsf(x);
506     if (absx <= x1)
507         return x;
508     return copysignf(a + b * logf(fabsf(c * absx + 1.0f)), x);
509 }
510 
511 
512 
513 inline float
rangeexpand(float y)514 rangeexpand(float y)
515 {
516     // Formula courtesy of Sony Pictures Imageworks
517 #if 0 /* original coeffs -- identity transform for vals < 1 */
518     const float x1 = 1.0, a = 1.2607481479644775391;
519     const float b = 0.28785100579261779785, c = -1.4042005538940429688;
520 #else /* but received wisdom is that these work better */
521     const float x1 = 0.18, a = -0.54576885700225830078;
522     const float b = 0.18351669609546661377, c = 284.3577880859375;
523 #endif
524 
525     float absy = fabsf(y);
526     if (absy <= x1)
527         return y;
528     float xIntermediate = expf((absy - a) / b);
529     // Since the compression step includes an absolute value, there are
530     // two possible results here. If x < x1 it is the incorrect result,
531     // so pick the other value.
532     float x = (xIntermediate - 1.0f) / c;
533     if (x < x1)
534         x = (-xIntermediate - 1.0f) / c;
535     return copysign(x, y);
536 }
537 
538 
539 
540 template<class Rtype, class Atype>
541 static bool
rangecompress_(ImageBuf & R,const ImageBuf & A,bool useluma,ROI roi,int nthreads)542 rangecompress_(ImageBuf& R, const ImageBuf& A, bool useluma, ROI roi,
543                int nthreads)
544 {
545     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
546         const ImageSpec& Aspec(A.spec());
547         int alpha_channel = Aspec.alpha_channel;
548         int z_channel     = Aspec.z_channel;
549         if (roi.nchannels() < 3
550             || (alpha_channel >= roi.chbegin && alpha_channel < roi.chbegin + 3)
551             || (z_channel >= roi.chbegin && z_channel < roi.chbegin + 3)) {
552             useluma = false;  // No way to use luma
553         }
554 
555         if (&R == &A) {
556             // Special case: operate in-place
557             for (ImageBuf::Iterator<Rtype> r(R, roi); !r.done(); ++r) {
558                 if (useluma) {
559                     float luma = 0.21264f * r[roi.chbegin]
560                                  + 0.71517f * r[roi.chbegin + 1]
561                                  + 0.07219f * r[roi.chbegin + 2];
562                     float scale = luma > 0.0f ? rangecompress(luma) / luma
563                                               : 0.0f;
564                     for (int c = roi.chbegin; c < roi.chend; ++c) {
565                         if (c == alpha_channel || c == z_channel)
566                             continue;
567                         r[c] = r[c] * scale;
568                     }
569                 } else {
570                     for (int c = roi.chbegin; c < roi.chend; ++c) {
571                         if (c == alpha_channel || c == z_channel)
572                             continue;
573                         r[c] = rangecompress(r[c]);
574                     }
575                 }
576             }
577         } else {
578             ImageBuf::ConstIterator<Atype> a(A, roi);
579             for (ImageBuf::Iterator<Rtype> r(R, roi); !r.done(); ++r, ++a) {
580                 if (useluma) {
581                     float luma = 0.21264f * a[roi.chbegin]
582                                  + 0.71517f * a[roi.chbegin + 1]
583                                  + 0.07219f * a[roi.chbegin + 2];
584                     float scale = luma > 0.0f ? rangecompress(luma) / luma
585                                               : 0.0f;
586                     for (int c = roi.chbegin; c < roi.chend; ++c) {
587                         if (c == alpha_channel || c == z_channel)
588                             r[c] = a[c];
589                         else
590                             r[c] = a[c] * scale;
591                     }
592                 } else {
593                     for (int c = roi.chbegin; c < roi.chend; ++c) {
594                         if (c == alpha_channel || c == z_channel)
595                             r[c] = a[c];
596                         else
597                             r[c] = rangecompress(a[c]);
598                     }
599                 }
600             }
601         }
602     });
603     return true;
604 }
605 
606 
607 
608 template<class Rtype, class Atype>
609 static bool
rangeexpand_(ImageBuf & R,const ImageBuf & A,bool useluma,ROI roi,int nthreads)610 rangeexpand_(ImageBuf& R, const ImageBuf& A, bool useluma, ROI roi,
611              int nthreads)
612 {
613     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
614         const ImageSpec& Aspec(A.spec());
615         int alpha_channel = Aspec.alpha_channel;
616         int z_channel     = Aspec.z_channel;
617         if (roi.nchannels() < 3
618             || (alpha_channel >= roi.chbegin && alpha_channel < roi.chbegin + 3)
619             || (z_channel >= roi.chbegin && z_channel < roi.chbegin + 3)) {
620             useluma = false;  // No way to use luma
621         }
622 
623         if (&R == &A) {
624             // Special case: operate in-place
625             for (ImageBuf::Iterator<Rtype> r(R, roi); !r.done(); ++r) {
626                 if (useluma) {
627                     float luma = 0.21264f * r[roi.chbegin]
628                                  + 0.71517f * r[roi.chbegin + 1]
629                                  + 0.07219f * r[roi.chbegin + 2];
630                     float scale = luma > 0.0f ? rangeexpand(luma) / luma : 0.0f;
631                     for (int c = roi.chbegin; c < roi.chend; ++c) {
632                         if (c == alpha_channel || c == z_channel)
633                             continue;
634                         r[c] = r[c] * scale;
635                     }
636                 } else {
637                     for (int c = roi.chbegin; c < roi.chend; ++c) {
638                         if (c == alpha_channel || c == z_channel)
639                             continue;
640                         r[c] = rangeexpand(r[c]);
641                     }
642                 }
643             }
644         } else {
645             ImageBuf::ConstIterator<Atype> a(A, roi);
646             for (ImageBuf::Iterator<Rtype> r(R, roi); !r.done(); ++r, ++a) {
647                 if (useluma) {
648                     float luma = 0.21264f * a[roi.chbegin]
649                                  + 0.71517f * a[roi.chbegin + 1]
650                                  + 0.07219f * a[roi.chbegin + 2];
651                     float scale = luma > 0.0f ? rangeexpand(luma) / luma : 0.0f;
652                     for (int c = roi.chbegin; c < roi.chend; ++c) {
653                         if (c == alpha_channel || c == z_channel)
654                             r[c] = a[c];
655                         else
656                             r[c] = a[c] * scale;
657                     }
658                 } else {
659                     for (int c = roi.chbegin; c < roi.chend; ++c) {
660                         if (c == alpha_channel || c == z_channel)
661                             r[c] = a[c];
662                         else
663                             r[c] = rangeexpand(a[c]);
664                     }
665                 }
666             }
667         }
668     });
669     return true;
670 }
671 
672 
673 
674 bool
rangecompress(ImageBuf & dst,const ImageBuf & src,bool useluma,ROI roi,int nthreads)675 ImageBufAlgo::rangecompress(ImageBuf& dst, const ImageBuf& src, bool useluma,
676                             ROI roi, int nthreads)
677 {
678     pvt::LoggedTimer logtime("IBA::rangecompress");
679     if (!IBAprep(roi, &dst, &src, IBAprep_CLAMP_MUTUAL_NCHANNELS))
680         return false;
681     bool ok;
682     OIIO_DISPATCH_COMMON_TYPES2(ok, "rangecompress", rangecompress_,
683                                 dst.spec().format, src.spec().format, dst, src,
684                                 useluma, roi, nthreads);
685     return ok;
686 }
687 
688 
689 
690 bool
rangeexpand(ImageBuf & dst,const ImageBuf & src,bool useluma,ROI roi,int nthreads)691 ImageBufAlgo::rangeexpand(ImageBuf& dst, const ImageBuf& src, bool useluma,
692                           ROI roi, int nthreads)
693 {
694     pvt::LoggedTimer logtime("IBA::rangeexpand");
695     if (!IBAprep(roi, &dst, &src, IBAprep_CLAMP_MUTUAL_NCHANNELS))
696         return false;
697     bool ok;
698     OIIO_DISPATCH_COMMON_TYPES2(ok, "rangeexpand", rangeexpand_,
699                                 dst.spec().format, src.spec().format, dst, src,
700                                 useluma, roi, nthreads);
701     return ok;
702 }
703 
704 
705 
706 ImageBuf
rangecompress(const ImageBuf & src,bool useluma,ROI roi,int nthreads)707 ImageBufAlgo::rangecompress(const ImageBuf& src, bool useluma, ROI roi,
708                             int nthreads)
709 {
710     ImageBuf result;
711     bool ok = rangecompress(result, src, useluma, roi, nthreads);
712     if (!ok && !result.has_error())
713         result.errorf("ImageBufAlgo::rangecompress() error");
714     return result;
715 }
716 
717 
718 
719 ImageBuf
rangeexpand(const ImageBuf & src,bool useluma,ROI roi,int nthreads)720 ImageBufAlgo::rangeexpand(const ImageBuf& src, bool useluma, ROI roi,
721                           int nthreads)
722 {
723     ImageBuf result;
724     bool ok = rangeexpand(result, src, useluma, roi, nthreads);
725     if (!ok && !result.has_error())
726         result.errorf("ImageBufAlgo::rangeexpand() error");
727     return result;
728 }
729 
730 
731 
732 template<class Rtype, class Atype>
733 static bool
unpremult_(ImageBuf & R,const ImageBuf & A,ROI roi,int nthreads)734 unpremult_(ImageBuf& R, const ImageBuf& A, ROI roi, int nthreads)
735 {
736     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
737         int alpha_channel = A.spec().alpha_channel;
738         int z_channel     = A.spec().z_channel;
739         if (&R == &A) {
740             for (ImageBuf::Iterator<Rtype> r(R, roi); !r.done(); ++r) {
741                 float alpha = r[alpha_channel];
742                 if (alpha == 0.0f || alpha == 1.0f)
743                     continue;
744                 for (int c = roi.chbegin; c < roi.chend; ++c)
745                     if (c != alpha_channel && c != z_channel)
746                         r[c] = r[c] / alpha;
747             }
748         } else {
749             ImageBuf::ConstIterator<Atype> a(A, roi);
750             for (ImageBuf::Iterator<Rtype> r(R, roi); !r.done(); ++r, ++a) {
751                 float alpha = a[alpha_channel];
752                 if (alpha == 0.0f || alpha == 1.0f) {
753                     for (int c = roi.chbegin; c < roi.chend; ++c)
754                         r[c] = a[c];
755                     continue;
756                 }
757                 for (int c = roi.chbegin; c < roi.chend; ++c)
758                     if (c != alpha_channel && c != z_channel)
759                         r[c] = a[c] / alpha;
760                     else
761                         r[c] = a[c];
762             }
763         }
764     });
765     return true;
766 }
767 
768 
769 
770 bool
unpremult(ImageBuf & dst,const ImageBuf & src,ROI roi,int nthreads)771 ImageBufAlgo::unpremult(ImageBuf& dst, const ImageBuf& src, ROI roi,
772                         int nthreads)
773 {
774     pvt::LoggedTimer logtime("IBA::unpremult");
775     if (!IBAprep(roi, &dst, &src, IBAprep_CLAMP_MUTUAL_NCHANNELS))
776         return false;
777     if (src.spec().alpha_channel < 0
778         // Wise?  || src.spec().get_int_attribute("oiio:UnassociatedAlpha") != 0
779     ) {
780         // If there is no alpha channel, just *copy* instead of dividing
781         // by alpha.
782         if (&dst != &src)
783             return paste(dst, src.spec().x, src.spec().y, src.spec().z,
784                          roi.chbegin, src, roi, nthreads);
785         return true;
786     }
787     bool ok;
788     OIIO_DISPATCH_COMMON_TYPES2(ok, "unpremult", unpremult_, dst.spec().format,
789                                 src.spec().format, dst, src, roi, nthreads);
790     // Mark the output as having unassociated alpha
791     dst.specmod().attribute("oiio:UnassociatedAlpha", 1);
792     return ok;
793 }
794 
795 
796 
797 ImageBuf
unpremult(const ImageBuf & src,ROI roi,int nthreads)798 ImageBufAlgo::unpremult(const ImageBuf& src, ROI roi, int nthreads)
799 {
800     ImageBuf result;
801     bool ok = unpremult(result, src, roi, nthreads);
802     if (!ok && !result.has_error())
803         result.errorf("ImageBufAlgo::unpremult() error");
804     return result;
805 }
806 
807 
808 
809 template<class Rtype, class Atype>
810 static bool
premult_(ImageBuf & R,const ImageBuf & A,bool preserve_alpha0,ROI roi,int nthreads)811 premult_(ImageBuf& R, const ImageBuf& A, bool preserve_alpha0, ROI roi,
812          int nthreads)
813 {
814     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
815         int alpha_channel = A.spec().alpha_channel;
816         int z_channel     = A.spec().z_channel;
817         if (&R == &A) {
818             for (ImageBuf::Iterator<Rtype> r(R, roi); !r.done(); ++r) {
819                 float alpha = r[alpha_channel];
820                 if (alpha == 1.0f || (preserve_alpha0 && alpha == 0.0f))
821                     continue;
822                 for (int c = roi.chbegin; c < roi.chend; ++c)
823                     if (c != alpha_channel && c != z_channel)
824                         r[c] = r[c] * alpha;
825             }
826         } else {
827             ImageBuf::ConstIterator<Atype> a(A, roi);
828             for (ImageBuf::Iterator<Rtype> r(R, roi); !r.done(); ++r, ++a) {
829                 float alpha   = a[alpha_channel];
830                 bool justcopy = alpha == 1.0f
831                                 || (preserve_alpha0 && alpha == 0.0f);
832                 if (justcopy) {
833                     for (int c = roi.chbegin; c < roi.chend; ++c)
834                         r[c] = a[c];
835                     continue;
836                 }
837                 for (int c = roi.chbegin; c < roi.chend; ++c)
838                     if (c != alpha_channel && c != z_channel)
839                         r[c] = a[c] * alpha;
840                     else
841                         r[c] = a[c];
842             }
843         }
844     });
845     return true;
846 }
847 
848 
849 
850 bool
premult(ImageBuf & dst,const ImageBuf & src,ROI roi,int nthreads)851 ImageBufAlgo::premult(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads)
852 {
853     pvt::LoggedTimer logtime("IBA::premult");
854     if (!IBAprep(roi, &dst, &src, IBAprep_CLAMP_MUTUAL_NCHANNELS))
855         return false;
856     if (src.spec().alpha_channel < 0) {
857         if (&dst != &src)
858             return paste(dst, src.spec().x, src.spec().y, src.spec().z,
859                          roi.chbegin, src, roi, nthreads);
860         return true;
861     }
862     bool ok;
863     OIIO_DISPATCH_COMMON_TYPES2(ok, "premult", premult_, dst.spec().format,
864                                 src.spec().format, dst, src, false, roi,
865                                 nthreads);
866     // Clear the output of any prior marking of associated alpha
867     dst.specmod().erase_attribute("oiio:UnassociatedAlpha");
868     return ok;
869 }
870 
871 
872 
873 ImageBuf
premult(const ImageBuf & src,ROI roi,int nthreads)874 ImageBufAlgo::premult(const ImageBuf& src, ROI roi, int nthreads)
875 {
876     ImageBuf result;
877     bool ok = premult(result, src, roi, nthreads);
878     if (!ok && !result.has_error())
879         result.errorf("ImageBufAlgo::premult() error");
880     return result;
881 }
882 
883 
884 
885 bool
repremult(ImageBuf & dst,const ImageBuf & src,ROI roi,int nthreads)886 ImageBufAlgo::repremult(ImageBuf& dst, const ImageBuf& src, ROI roi,
887                         int nthreads)
888 {
889     pvt::LoggedTimer logtime("IBA::repremult");
890     if (!IBAprep(roi, &dst, &src, IBAprep_CLAMP_MUTUAL_NCHANNELS))
891         return false;
892     if (src.spec().alpha_channel < 0) {
893         if (&dst != &src)
894             return paste(dst, src.spec().x, src.spec().y, src.spec().z,
895                          roi.chbegin, src, roi, nthreads);
896         return true;
897     }
898     bool ok;
899     OIIO_DISPATCH_COMMON_TYPES2(ok, "repremult", premult_, dst.spec().format,
900                                 src.spec().format, dst, src, true, roi,
901                                 nthreads);
902     // Clear the output of any prior marking of associated alpha
903     dst.specmod().erase_attribute("oiio:UnassociatedAlpha");
904     return ok;
905 }
906 
907 
908 
909 ImageBuf
repremult(const ImageBuf & src,ROI roi,int nthreads)910 ImageBufAlgo::repremult(const ImageBuf& src, ROI roi, int nthreads)
911 {
912     ImageBuf result;
913     bool ok = repremult(result, src, roi, nthreads);
914     if (!ok && !result.has_error())
915         result.errorf("ImageBufAlgo::repremult() error");
916     return result;
917 }
918 
919 
920 
921 // Helper: Are all elements of span s holding value v?
922 template<typename T>
923 inline bool
allspan(cspan<T> s,const T & v)924 allspan(cspan<T> s, const T& v)
925 {
926     return s.size() && std::all_of(s.cbegin(), s.cend(), [&](const T& e) {
927                return e == v;
928            });
929 }
930 
931 
932 
933 template<class D, class S>
934 static bool
contrast_remap_(ImageBuf & dst,const ImageBuf & src,cspan<float> black,cspan<float> white,cspan<float> min,cspan<float> max,cspan<float> scontrast,cspan<float> sthresh,ROI roi,int nthreads)935 contrast_remap_(ImageBuf& dst, const ImageBuf& src, cspan<float> black,
936                 cspan<float> white, cspan<float> min, cspan<float> max,
937                 cspan<float> scontrast, cspan<float> sthresh, ROI roi,
938                 int nthreads)
939 {
940     bool same_black_white = (black == white);
941     float* bwdiffinv      = OIIO_ALLOCA(float, roi.chend);
942     for (int c = roi.chbegin; c < roi.chend; ++c)
943         bwdiffinv[c] = 1.0f / (white[c] - black[c]);
944     bool use_sigmoid = !allspan(scontrast, 1.0f);
945     bool do_minmax   = !(allspan(min, 0.0f) && allspan(max, 1.0f));
946 
947     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
948         if (same_black_white) {
949             // Special case -- black & white are the same value, which is
950             // just a binary threshold.
951             ImageBuf::ConstIterator<S> s(src, roi);
952             for (ImageBuf::Iterator<D> d(dst, roi); !d.done(); ++d, ++s) {
953                 for (int c = roi.chbegin; c < roi.chend; ++c)
954                     d[c] = (s[c] < black[c] ? min[c] : max[c]);
955             }
956             return;
957         }
958 
959         // First do the linear stretch
960         float* r = OIIO_ALLOCA(float, roi.chend);  // temp result
961         ImageBuf::ConstIterator<S> s(src, roi);
962         float* y     = OIIO_ALLOCA(float, roi.chend);
963         float* denom = OIIO_ALLOCA(float, roi.chend);
964         for (ImageBuf::Iterator<D> d(dst, roi); !d.done(); ++d, ++s) {
965             for (int c = roi.chbegin; c < roi.chend; ++c)
966                 r[c] = (s[c] - black[c]) * bwdiffinv[c];
967 
968             // Apply the sigmoid if needed
969             // See http://www.imagemagick.org/Usage/color_mods/#sigmoidal
970             // for a description of the shaping function.
971             if (use_sigmoid) {
972                 // Sorry about the lack of clarity, we're working hard to
973                 // minimize computation.
974                 for (int c = roi.chbegin; c < roi.chend; ++c) {
975                     y[c]     = 1.0f / (1.0f + expf(scontrast[c] * sthresh[c]));
976                     denom[c] = 1.0f
977                                    / (1.0f
978                                       + expf(scontrast[c] * (sthresh[c] - 1.0f)))
979                                - y[c];
980                 }
981                 for (int c = roi.chbegin; c < roi.chend; ++c) {
982                     float x = 1.0f
983                               / (1.0f
984                                  + expf(scontrast[c] * (sthresh[c] - r[c])));
985                     r[c] = (x - y[c]) / denom[c];
986                 }
987             }
988 
989             // remap output range if needed
990             if (do_minmax) {
991                 for (int c = roi.chbegin; c < roi.chend; ++c)
992                     r[c] = lerp(min[c], max[c], r[c]);
993             }
994             for (int c = roi.chbegin; c < roi.chend; ++c)
995                 d[c] = r[c];
996         }
997     });
998     return true;
999 }
1000 
1001 
1002 
1003 bool
contrast_remap(ImageBuf & dst,const ImageBuf & src,cspan<float> black,cspan<float> white,cspan<float> min,cspan<float> max,cspan<float> scontrast,cspan<float> sthresh,ROI roi,int nthreads)1004 ImageBufAlgo::contrast_remap(ImageBuf& dst, const ImageBuf& src,
1005                              cspan<float> black, cspan<float> white,
1006                              cspan<float> min, cspan<float> max,
1007                              cspan<float> scontrast, cspan<float> sthresh,
1008                              ROI roi, int nthreads)
1009 {
1010     pvt::LoggedTimer logtime("IBA::contrast_remap");
1011     if (!IBAprep(roi, &dst, &src))
1012         return false;
1013     // Force all the input spans to have values for all channels.
1014     int n = dst.nchannels();
1015     IBA_FIX_PERCHAN_LEN(black, n, black.size() ? black.back() : 0.0f, 0.0f);
1016     IBA_FIX_PERCHAN_LEN(white, n, white.size() ? white.back() : 1.0f, 1.0f);
1017     IBA_FIX_PERCHAN_LEN(min, n, min.size() ? min.back() : 0.0f, 0.0f);
1018     IBA_FIX_PERCHAN_LEN(max, n, max.size() ? max.back() : 1.0f, 1.0f);
1019     IBA_FIX_PERCHAN_LEN(scontrast, n,
1020                         scontrast.size() ? scontrast.back() : 1.0f, 1.0f);
1021     IBA_FIX_PERCHAN_LEN(sthresh, n, sthresh.size() ? sthresh.back() : 0.5f,
1022                         0.5f);
1023     bool ok;
1024     OIIO_DISPATCH_COMMON_TYPES2(ok, "contrast_remap", contrast_remap_,
1025                                 dst.spec().format, src.spec().format, dst, src,
1026                                 black, white, min, max, scontrast, sthresh, roi,
1027                                 nthreads);
1028     return ok;
1029 }
1030 
1031 
1032 
1033 ImageBuf
contrast_remap(const ImageBuf & src,cspan<float> black,cspan<float> white,cspan<float> min,cspan<float> max,cspan<float> scontrast,cspan<float> sthresh,ROI roi,int nthreads)1034 ImageBufAlgo::contrast_remap(const ImageBuf& src, cspan<float> black,
1035                              cspan<float> white, cspan<float> min,
1036                              cspan<float> max, cspan<float> scontrast,
1037                              cspan<float> sthresh, ROI roi, int nthreads)
1038 {
1039     ImageBuf result;
1040     bool ok = contrast_remap(result, src, black, white, min, max, scontrast,
1041                              sthresh, roi, nthreads);
1042     if (!ok && !result.has_error())
1043         result.errorf("ImageBufAlgo::contrast_remap error");
1044     return result;
1045 }
1046 
1047 
1048 
1049 template<class D, class S>
1050 static bool
color_map_(ImageBuf & dst,const ImageBuf & src,int srcchannel,int nknots,int channels,cspan<float> knots,ROI roi,int nthreads)1051 color_map_(ImageBuf& dst, const ImageBuf& src, int srcchannel, int nknots,
1052            int channels, cspan<float> knots, ROI roi, int nthreads)
1053 {
1054     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
1055         if (srcchannel < 0 && src.nchannels() < 3)
1056             srcchannel = 0;
1057         roi.chend = std::min(roi.chend, channels);
1058         ImageBuf::Iterator<D> d(dst, roi);
1059         ImageBuf::ConstIterator<S> s(src, roi);
1060         for (; !d.done(); ++d, ++s) {
1061             float x = srcchannel < 0
1062                           ? 0.2126f * s[0] + 0.7152f * s[1] + 0.0722f * s[2]
1063                           : s[srcchannel];
1064             for (int c = roi.chbegin; c < roi.chend; ++c) {
1065                 span_strided<const float> k(knots.data() + c, nknots, channels);
1066                 d[c] = interpolate_linear(x, k);
1067             }
1068         }
1069     });
1070     return true;
1071 }
1072 
1073 
1074 
1075 bool
color_map(ImageBuf & dst,const ImageBuf & src,int srcchannel,int nknots,int channels,cspan<float> knots,ROI roi,int nthreads)1076 ImageBufAlgo::color_map(ImageBuf& dst, const ImageBuf& src, int srcchannel,
1077                         int nknots, int channels, cspan<float> knots, ROI roi,
1078                         int nthreads)
1079 {
1080     pvt::LoggedTimer logtime("IBA::color_map");
1081     if (srcchannel >= src.nchannels()) {
1082         dst.errorf("invalid source channel selected");
1083         return false;
1084     }
1085     if (nknots < 2 || knots.size() < (nknots * channels)) {
1086         dst.errorf("not enough knot values supplied");
1087         return false;
1088     }
1089     if (!roi.defined())
1090         roi = get_roi(src.spec());
1091     roi.chend      = std::min(roi.chend, src.nchannels());
1092     ROI dstroi     = roi;
1093     dstroi.chbegin = 0;
1094     dstroi.chend   = channels;
1095     if (!IBAprep(dstroi, &dst))
1096         return false;
1097     dstroi.chend = std::min(channels, dst.nchannels());
1098 
1099     bool ok;
1100     OIIO_DISPATCH_COMMON_TYPES2(ok, "color_map", color_map_, dst.spec().format,
1101                                 src.spec().format, dst, src, srcchannel, nknots,
1102                                 channels, knots, dstroi, nthreads);
1103     return ok;
1104 }
1105 
1106 
1107 
1108 ImageBuf
color_map(const ImageBuf & src,int srcchannel,int nknots,int channels,cspan<float> knots,ROI roi,int nthreads)1109 ImageBufAlgo::color_map(const ImageBuf& src, int srcchannel, int nknots,
1110                         int channels, cspan<float> knots, ROI roi, int nthreads)
1111 {
1112     ImageBuf result;
1113     bool ok = color_map(result, src, srcchannel, nknots, channels, knots, roi,
1114                         nthreads);
1115     if (!ok && !result.has_error())
1116         result.errorf("ImageBufAlgo::color_map() error");
1117     return result;
1118 }
1119 
1120 
1121 
1122 // The color maps for magma, inferno, plasma, and viridis are from
1123 // Matplotlib, written by Nathaniel Smith & Stefan van der Walt, and are
1124 // public domain (http://creativecommons.org/publicdomain/zero/1.0/) The
1125 // originals can be found here: https://github.com/bids/colormap
1126 // These color maps were specially designed to be (a) perceptually uniform,
1127 // (b) strictly increasing in luminance, (c) looking good when converted
1128 // to grayscale for printing, (d) useful even for people with various forms
1129 // of color blindness. They are therefore superior to most of the ad-hoc
1130 // visualization color maps used elsewhere, including the original ones used
1131 // in OIIO.
1132 //
1133 // LG has altered the original maps by converting from sRGB to a linear
1134 // response (since that's how OIIO wants to operate), and also decimated the
1135 // arrays from 256 entries to 17 entries (fine, since we interpolate).
1136 // clang-format off
1137 static const float magma_data[] = {
1138     0.000113f, 0.000036f, 0.001073f, 0.003066f, 0.002406f, 0.016033f, 0.012176f,
1139     0.005476f, 0.062265f, 0.036874f, 0.005102f, 0.146314f, 0.081757f, 0.006177f,
1140     0.200758f, 0.143411f, 0.011719f, 0.218382f, 0.226110f, 0.019191f, 0.221188f,
1141     0.334672f, 0.027718f, 0.212689f, 0.471680f, 0.037966f, 0.191879f, 0.632894f,
1142     0.053268f, 0.159870f, 0.795910f, 0.083327f, 0.124705f, 0.913454f, 0.146074f,
1143     0.106311f, 0.970011f, 0.248466f, 0.120740f, 0.991142f, 0.384808f, 0.167590f,
1144     0.992958f, 0.553563f, 0.247770f, 0.982888f, 0.756759f, 0.367372f, 0.970800f,
1145     0.980633f, 0.521749f
1146 };
1147 static const float inferno_data[] = {
1148     0.000113f, 0.000036f, 0.001073f, 0.003275f, 0.002178f, 0.017634f, 0.015183f,
1149     0.003697f, 0.068760f, 0.046307f, 0.002834f, 0.130327f, 0.095494f, 0.005137f,
1150     0.154432f, 0.163601f, 0.009920f, 0.156097f, 0.253890f, 0.016282f, 0.143715f,
1151     0.367418f, 0.024893f, 0.119982f, 0.500495f, 0.038279f, 0.089117f, 0.642469f,
1152     0.061553f, 0.057555f, 0.776190f, 0.102517f, 0.031141f, 0.883568f, 0.169990f,
1153     0.012559f, 0.951614f, 0.271639f, 0.002704f, 0.972636f, 0.413571f, 0.005451f,
1154     0.943272f, 0.599923f, 0.035112f, 0.884900f, 0.822282f, 0.140466f, 0.973729f,
1155     0.996282f, 0.373522f
1156 };
1157 static const float plasma_data[] = {
1158     0.003970f, 0.002307f, 0.240854f, 0.031078f, 0.001421f, 0.307376f, 0.073167f,
1159     0.000740f, 0.356714f, 0.132456f, 0.000066f, 0.388040f, 0.209330f, 0.000928f,
1160     0.390312f, 0.300631f, 0.005819f, 0.358197f, 0.399925f, 0.017084f, 0.301977f,
1161     0.501006f, 0.036122f, 0.240788f, 0.600808f, 0.063814f, 0.186921f, 0.698178f,
1162     0.101409f, 0.142698f, 0.790993f, 0.151134f, 0.106347f, 0.874354f, 0.216492f,
1163     0.076152f, 0.940588f, 0.302179f, 0.051495f, 0.980469f, 0.413691f, 0.032625f,
1164     0.984224f, 0.556999f, 0.020728f, 0.942844f, 0.738124f, 0.018271f, 0.868931f,
1165     0.944416f, 0.015590f
1166 };
1167 static const float viridis_data[] = {
1168     0.057951f, 0.000377f, 0.088657f, 0.064791f, 0.009258f, 0.145340f, 0.063189f,
1169     0.025975f, 0.198994f, 0.054539f, 0.051494f, 0.237655f, 0.043139f, 0.084803f,
1170     0.258811f, 0.032927f, 0.124348f, 0.268148f, 0.025232f, 0.169666f, 0.271584f,
1171     0.019387f, 0.221569f, 0.270909f, 0.014846f, 0.281323f, 0.263855f, 0.013529f,
1172     0.349530f, 0.246357f, 0.021457f, 0.425216f, 0.215605f, 0.049317f, 0.505412f,
1173     0.172291f, 0.112305f, 0.585164f, 0.121207f, 0.229143f, 0.657992f, 0.070438f,
1174     0.417964f, 0.717561f, 0.029928f, 0.683952f, 0.762557f, 0.009977f, 0.984709f,
1175     0.799651f, 0.018243f
1176 };
1177 
1178 
1179 // "Turbo" color map Copyright 2019 Google LLC.
1180 // SPDX-License-Identifier: Apache-2.0
1181 // Author: Anton Mikhailov
1182 // https://gist.github.com/mikhailov-work/6a308c20e494d9e0ccc29036b28faa7a
1183 // Altered by LG to convert from sRGB to linear and decimate the table to
1184 // 17 entries.
1185 //
1186 // Turbo is also pretty nice, in similar ways to the matplotlib palettes,
1187 // except for not being monotonically increasing in luminance.
1188 static const float turbo_data[] = {
1189     0.03006f, 0.00619f, 0.04403f,  0.05131f, 0.05183f, 0.35936f,
1190     0.06204f, 0.14820f, 0.77017f,  0.05440f, 0.29523f, 0.99718f,
1191     0.02160f, 0.50023f, 0.83380f,  0.00892f, 0.72094f, 0.54189f,
1192     0.03205f, 0.88790f, 0.31235f,  0.15318f, 0.98683f, 0.12310f,
1193     0.37185f, 0.97738f, 0.04454f,  0.61188f, 0.83681f, 0.03455f,
1194     0.85432f, 0.62499f, 0.04203f,  0.98447f, 0.41196f, 0.03420f,
1195     0.96310f, 0.20754f, 0.01503f,  0.82971f, 0.08083f, 0.00438f,
1196     0.63144f, 0.02851f, 0.00140f,  0.39907f, 0.00776f, 0.00033f,
1197     0.19564f, 0.00123f, 0.00082f
1198 };
1199 
1200 // clang-format on
1201 
1202 
1203 
1204 bool
color_map(ImageBuf & dst,const ImageBuf & src,int srcchannel,string_view mapname,ROI roi,int nthreads)1205 ImageBufAlgo::color_map(ImageBuf& dst, const ImageBuf& src, int srcchannel,
1206                         string_view mapname, ROI roi, int nthreads)
1207 {
1208     pvt::LoggedTimer logtime("IBA::color_map");
1209     if (srcchannel >= src.nchannels()) {
1210         dst.errorf("invalid source channel selected");
1211         return false;
1212     }
1213     cspan<float> knots;
1214     if (mapname == "magma") {
1215         knots = cspan<float>(magma_data);
1216     } else if (mapname == "inferno") {
1217         knots = cspan<float>(inferno_data);
1218     } else if (mapname == "plasma") {
1219         knots = cspan<float>(plasma_data);
1220     } else if (mapname == "viridis") {
1221         knots = cspan<float>(viridis_data);
1222     } else if (mapname == "turbo") {
1223         knots = cspan<float>(turbo_data);
1224     } else if (mapname == "blue-red" || mapname == "red-blue"
1225                || mapname == "bluered" || mapname == "redblue") {
1226         static const float k[] = { 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f };
1227         knots                  = cspan<float>(k);
1228     } else if (mapname == "spectrum") {
1229         static const float k[] = { 0.0f,  0.0f, 0.05f, 0.0f, 0.0f,
1230                                    0.75f, 0.0f, 0.5f,  0.0f, 0.5f,
1231                                    0.5f,  0.0f, 1.0f,  0.0f, 0.0f };
1232         knots                  = cspan<float>(k);
1233     } else if (mapname == "heat") {
1234         static const float k[] = { 0.0f,  0.0f,  0.0f, 0.05f, 0.0f,
1235                                    0.0f,  0.25f, 0.0f, 0.0f,  0.75f,
1236                                    0.75f, 0.0f,  1.0f, 1.0f,  1.0f };
1237         knots                  = cspan<float>(k);
1238     } else {
1239         dst.errorf("Unknown map name \"%s\"", mapname);
1240         return false;
1241     }
1242     return color_map(dst, src, srcchannel, int(knots.size() / 3), 3, knots, roi,
1243                      nthreads);
1244 }
1245 
1246 
1247 ImageBuf
color_map(const ImageBuf & src,int srcchannel,string_view mapname,ROI roi,int nthreads)1248 ImageBufAlgo::color_map(const ImageBuf& src, int srcchannel,
1249                         string_view mapname, ROI roi, int nthreads)
1250 {
1251     ImageBuf result;
1252     bool ok = color_map(result, src, srcchannel, mapname, roi, nthreads);
1253     if (!ok && !result.has_error())
1254         result.errorf("ImageBufAlgo::color_map() error");
1255     return result;
1256 }
1257 
1258 
1259 
1260 namespace {
1261 
1262 using std::isfinite;
1263 
1264 // Make sure isfinite is defined for 'half'
1265 inline bool
isfinite(half h)1266 isfinite(half h)
1267 {
1268     return h.isFinite();
1269 }
1270 
1271 
1272 template<typename T>
1273 bool
fixNonFinite_(ImageBuf & dst,ImageBufAlgo::NonFiniteFixMode mode,int * pixelsFixed,ROI roi,int nthreads)1274 fixNonFinite_(ImageBuf& dst, ImageBufAlgo::NonFiniteFixMode mode,
1275               int* pixelsFixed, ROI roi, int nthreads)
1276 {
1277     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
1278         ROI dstroi = get_roi(dst.spec());
1279         int count  = 0;  // Number of pixels with nonfinite values
1280 
1281         if (mode == ImageBufAlgo::NONFINITE_NONE
1282             || mode == ImageBufAlgo::NONFINITE_ERROR) {
1283             // Just count the number of pixels with non-finite values
1284             for (ImageBuf::Iterator<T, T> pixel(dst, roi); !pixel.done();
1285                  ++pixel) {
1286                 for (int c = roi.chbegin; c < roi.chend; ++c) {
1287                     T value = pixel[c];
1288                     if (!isfinite(value)) {
1289                         ++count;
1290                         break;  // only count one per pixel
1291                     }
1292                 }
1293             }
1294         } else if (mode == ImageBufAlgo::NONFINITE_BLACK) {
1295             // Replace non-finite pixels with black
1296             for (ImageBuf::Iterator<T, T> pixel(dst, roi); !pixel.done();
1297                  ++pixel) {
1298                 bool fixed = false;
1299                 for (int c = roi.chbegin; c < roi.chend; ++c) {
1300                     T value = pixel[c];
1301                     if (!isfinite(value)) {
1302                         pixel[c] = T(0.0);
1303                         fixed    = true;
1304                     }
1305                 }
1306                 if (fixed)
1307                     ++count;
1308             }
1309         } else if (mode == ImageBufAlgo::NONFINITE_BOX3) {
1310             // Replace non-finite pixels with a simple 3x3 window average
1311             // (the average excluding non-finite pixels, of course)
1312             for (ImageBuf::Iterator<T, T> pixel(dst, roi); !pixel.done();
1313                  ++pixel) {
1314                 bool fixed = false;
1315                 for (int c = roi.chbegin; c < roi.chend; ++c) {
1316                     T value = pixel[c];
1317                     if (!isfinite(value)) {
1318                         int numvals = 0;
1319                         T sum(0.0);
1320                         ROI roi2(pixel.x() - 1, pixel.x() + 2, pixel.y() - 1,
1321                                  pixel.y() + 2, pixel.z() - 1, pixel.z() + 2);
1322                         roi2 = roi_intersection(roi2, dstroi);
1323                         for (ImageBuf::Iterator<T, T> i(dst, roi2); !i.done();
1324                              ++i) {
1325                             T v = i[c];
1326                             if (isfinite(v)) {
1327                                 sum += v;
1328                                 ++numvals;
1329                             }
1330                         }
1331                         pixel[c] = numvals ? T(sum / numvals) : T(0.0);
1332                         fixed    = true;
1333                     }
1334                 }
1335                 if (fixed)
1336                     ++count;
1337             }
1338         }
1339 
1340         if (pixelsFixed) {
1341             // Update pixelsFixed atomically -- that's what makes this whole
1342             // function thread-safe.
1343             *(atomic_int*)pixelsFixed += count;
1344         }
1345     });
1346     return true;
1347 }
1348 
1349 
1350 bool
fixNonFinite_deep_(ImageBuf & dst,ImageBufAlgo::NonFiniteFixMode mode,int * pixelsFixed,ROI roi,int nthreads)1351 fixNonFinite_deep_(ImageBuf& dst, ImageBufAlgo::NonFiniteFixMode mode,
1352                    int* pixelsFixed, ROI roi, int nthreads)
1353 {
1354     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
1355         int count = 0;  // Number of pixels with nonfinite values
1356         if (mode == ImageBufAlgo::NONFINITE_NONE
1357             || mode == ImageBufAlgo::NONFINITE_ERROR) {
1358             // Just count the number of pixels with non-finite values
1359             for (ImageBuf::Iterator<float> pixel(dst, roi); !pixel.done();
1360                  ++pixel) {
1361                 int samples = pixel.deep_samples();
1362                 if (samples == 0)
1363                     continue;
1364                 bool bad = false;
1365                 for (int samp = 0; samp < samples && !bad; ++samp)
1366                     for (int c = roi.chbegin; c < roi.chend; ++c) {
1367                         float value = pixel.deep_value(c, samp);
1368                         if (!isfinite(value)) {
1369                             ++count;
1370                             bad = true;
1371                             break;
1372                         }
1373                     }
1374             }
1375         } else {
1376             // We don't know what to do with BOX3, so just always set to black.
1377             // Replace non-finite pixels with black
1378             for (ImageBuf::Iterator<float> pixel(dst, roi); !pixel.done();
1379                  ++pixel) {
1380                 int samples = pixel.deep_samples();
1381                 if (samples == 0)
1382                     continue;
1383                 bool fixed = false;
1384                 for (int samp = 0; samp < samples; ++samp)
1385                     for (int c = roi.chbegin; c < roi.chend; ++c) {
1386                         float value = pixel.deep_value(c, samp);
1387                         if (!isfinite(value)) {
1388                             pixel.set_deep_value(c, samp, 0.0f);
1389                             fixed = true;
1390                         }
1391                     }
1392                 if (fixed)
1393                     ++count;
1394             }
1395         }
1396         if (pixelsFixed) {
1397             // Update pixelsFixed atomically -- that's what makes this whole
1398             // function thread-safe.
1399             *(atomic_int*)pixelsFixed += count;
1400         }
1401     });
1402     return true;
1403 }
1404 
1405 }  // namespace
1406 
1407 
1408 
1409 /// Fix all non-finite pixels (nan/inf) using the specified approach
1410 bool
fixNonFinite(ImageBuf & dst,const ImageBuf & src,NonFiniteFixMode mode,int * pixelsFixed,ROI roi,int nthreads)1411 ImageBufAlgo::fixNonFinite(ImageBuf& dst, const ImageBuf& src,
1412                            NonFiniteFixMode mode, int* pixelsFixed, ROI roi,
1413                            int nthreads)
1414 {
1415     pvt::LoggedTimer logtime("IBA::fixNonFinite");
1416     if (mode != ImageBufAlgo::NONFINITE_NONE
1417         && mode != ImageBufAlgo::NONFINITE_BLACK
1418         && mode != ImageBufAlgo::NONFINITE_BOX3
1419         && mode != ImageBufAlgo::NONFINITE_ERROR) {
1420         // Something went wrong
1421         dst.errorf("fixNonFinite: unknown repair mode");
1422         return false;
1423     }
1424 
1425     if (!IBAprep(roi, &dst, &src, IBAprep_SUPPORT_DEEP))
1426         return false;
1427 
1428     // Initialize
1429     bool ok = true;
1430     int pixelsFixed_local;
1431     if (!pixelsFixed)
1432         pixelsFixed = &pixelsFixed_local;
1433     *pixelsFixed = 0;
1434 
1435     // Start by copying dst to src, if they aren't the same image
1436     if (&dst != &src)
1437         ok &= ImageBufAlgo::copy(dst, src, TypeDesc::UNKNOWN, roi, nthreads);
1438 
1439     if (dst.deep())
1440         ok &= fixNonFinite_deep_(dst, mode, pixelsFixed, roi, nthreads);
1441     else if (src.spec().format.basetype == TypeDesc::FLOAT)
1442         ok &= fixNonFinite_<float>(dst, mode, pixelsFixed, roi, nthreads);
1443     else if (src.spec().format.basetype == TypeDesc::HALF)
1444         ok &= fixNonFinite_<half>(dst, mode, pixelsFixed, roi, nthreads);
1445     else if (src.spec().format.basetype == TypeDesc::DOUBLE)
1446         ok &= fixNonFinite_<double>(dst, mode, pixelsFixed, roi, nthreads);
1447     // All other format types aren't capable of having nonfinite
1448     // pixel values, so the copy was enough.
1449 
1450     if (mode == ImageBufAlgo::NONFINITE_ERROR && *pixelsFixed) {
1451         dst.errorf("Nonfinite pixel values found");
1452         ok = false;
1453     }
1454     return ok;
1455 }
1456 
1457 
1458 
1459 ImageBuf
fixNonFinite(const ImageBuf & src,NonFiniteFixMode mode,int * pixelsFixed,ROI roi,int nthreads)1460 ImageBufAlgo::fixNonFinite(const ImageBuf& src, NonFiniteFixMode mode,
1461                            int* pixelsFixed, ROI roi, int nthreads)
1462 {
1463     ImageBuf result;
1464     bool ok = fixNonFinite(result, src, mode, pixelsFixed, roi, nthreads);
1465     if (!ok && !result.has_error())
1466         result.errorf("ImageBufAlgo::fixNonFinite() error");
1467     return result;
1468 }
1469 
1470 
1471 
1472 static bool
decode_over_channels(const ImageBuf & R,int & nchannels,int & alpha,int & z,int & colors)1473 decode_over_channels(const ImageBuf& R, int& nchannels, int& alpha, int& z,
1474                      int& colors)
1475 {
1476     if (!R.initialized()) {
1477         alpha  = -1;
1478         z      = -1;
1479         colors = 0;
1480         return false;
1481     }
1482     const ImageSpec& spec(R.spec());
1483     alpha          = spec.alpha_channel;
1484     bool has_alpha = (alpha >= 0);
1485     z              = spec.z_channel;
1486     bool has_z     = (z >= 0);
1487     nchannels      = spec.nchannels;
1488     colors         = nchannels - has_alpha - has_z;
1489     if (!has_alpha && colors == 4) {
1490         // No marked alpha channel, but suspiciously 4 channel -- assume
1491         // it's RGBA.
1492         colors -= 1;
1493         // Assume alpha is the highest channel that's not z
1494         alpha = nchannels - 1;
1495         if (alpha == z)
1496             --alpha;
1497     }
1498     return true;
1499 }
1500 
1501 
1502 
1503 // Fully type-specialized version of over.
1504 template<class Rtype, class Atype, class Btype>
1505 static bool
over_impl(ImageBuf & R,const ImageBuf & A,const ImageBuf & B,bool zcomp,bool z_zeroisinf,ROI roi,int nthreads)1506 over_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, bool zcomp,
1507           bool z_zeroisinf, ROI roi, int nthreads)
1508 {
1509     // It's already guaranteed that R, A, and B have matching channel
1510     // ordering, and have an alpha channel.  So just decode one.
1511     int nchannels = 0, alpha_channel = 0, z_channel = 0, ncolor_channels = 0;
1512     decode_over_channels(R, nchannels, alpha_channel, z_channel,
1513                          ncolor_channels);
1514     bool has_z = (z_channel >= 0);
1515 
1516     ImageBufAlgo::parallel_image(roi, nthreads, [=, &R, &A, &B](ROI roi) {
1517         ImageBuf::ConstIterator<Atype> a(A, roi);
1518         ImageBuf::ConstIterator<Btype> b(B, roi);
1519         ImageBuf::Iterator<Rtype> r(R, roi);
1520         for (; !r.done(); ++r, ++a, ++b) {
1521             float az = 0.0f, bz = 0.0f;
1522             bool a_is_closer = true;  // will remain true if !zcomp
1523             if (zcomp && has_z) {
1524                 az = a[z_channel];
1525                 bz = b[z_channel];
1526                 if (z_zeroisinf) {
1527                     if (az == 0.0f)
1528                         az = std::numeric_limits<float>::max();
1529                     if (bz == 0.0f)
1530                         bz = std::numeric_limits<float>::max();
1531                 }
1532                 a_is_closer = (az <= bz);
1533             }
1534             if (a_is_closer) {
1535                 // A over B
1536                 float alpha           = clamp(a[alpha_channel], 0.0f, 1.0f);
1537                 float one_minus_alpha = 1.0f - alpha;
1538                 for (int c = roi.chbegin; c < roi.chend; c++)
1539                     r[c] = a[c] + one_minus_alpha * b[c];
1540                 if (has_z)
1541                     r[z_channel] = (alpha != 0.0) ? a[z_channel] : b[z_channel];
1542             } else {
1543                 // B over A -- because we're doing a Z composite
1544                 float alpha           = clamp(b[alpha_channel], 0.0f, 1.0f);
1545                 float one_minus_alpha = 1.0f - alpha;
1546                 for (int c = roi.chbegin; c < roi.chend; c++)
1547                     r[c] = b[c] + one_minus_alpha * a[c];
1548                 r[z_channel] = (alpha != 0.0) ? b[z_channel] : a[z_channel];
1549             }
1550         }
1551     });
1552 
1553     return true;
1554 }
1555 
1556 
1557 
1558 // Special case -- 4 channel RGBA float, in-memory buffer, no wrapping.
1559 // Use loops and SIMD.
1560 static bool
over_impl_rgbafloat(ImageBuf & R,const ImageBuf & A,const ImageBuf & B,ROI roi,int nthreads)1561 over_impl_rgbafloat(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi,
1562                     int nthreads)
1563 {
1564     using namespace simd;
1565     OIIO_DASSERT(A.localpixels() && B.localpixels()
1566                  && A.spec().format == TypeFloat && A.nchannels() == 4
1567                  && B.spec().format == TypeFloat && B.nchannels() == 4
1568                  && A.spec().alpha_channel == 3 && A.spec().z_channel < 0
1569                  && B.spec().alpha_channel == 3 && B.spec().z_channel < 0);
1570     // const int nchannels = 4, alpha_channel = 3;
1571     ImageBufAlgo::parallel_image(roi, nthreads, [=, &R, &A, &B](ROI roi) {
1572         vfloat4 zero = vfloat4::Zero();
1573         vfloat4 one  = vfloat4::One();
1574         int w        = roi.width();
1575         for (int z = roi.zbegin; z < roi.zend; ++z) {
1576             for (int y = roi.ybegin; y < roi.yend; ++y) {
1577                 float* r       = (float*)R.pixeladdr(roi.xbegin, y, z);
1578                 const float* a = (const float*)A.pixeladdr(roi.xbegin, y, z);
1579                 const float* b = (const float*)B.pixeladdr(roi.xbegin, y, z);
1580                 for (int x = 0; x < w; ++x, r += 4, a += 4, b += 4) {
1581                     vfloat4 a_simd(a);
1582                     vfloat4 b_simd(b);
1583                     vfloat4 alpha           = shuffle<3>(a_simd);
1584                     vfloat4 one_minus_alpha = one - clamp(alpha, zero, one);
1585                     vfloat4 result          = a_simd + one_minus_alpha * b_simd;
1586                     result.store(r);
1587                 }
1588             }
1589         }
1590     });
1591     return true;
1592 }
1593 
1594 
1595 
1596 bool
over(ImageBuf & dst,const ImageBuf & A,const ImageBuf & B,ROI roi,int nthreads)1597 ImageBufAlgo::over(ImageBuf& dst, const ImageBuf& A, const ImageBuf& B, ROI roi,
1598                    int nthreads)
1599 {
1600     pvt::LoggedTimer logtime("IBA::over");
1601     if (!IBAprep(roi, &dst, &A, &B, NULL,
1602                  IBAprep_REQUIRE_ALPHA | IBAprep_REQUIRE_SAME_NCHANNELS))
1603         return false;
1604 
1605     if (A.localpixels() && B.localpixels() && A.spec().format == TypeFloat
1606         && A.nchannels() == 4 && B.spec().format == TypeFloat
1607         && B.nchannels() == 4 && A.spec().alpha_channel == 3
1608         && A.spec().z_channel < 0 && B.spec().alpha_channel == 3
1609         && B.spec().z_channel < 0 && A.roi().contains(roi)
1610         && B.roi().contains(roi) && roi.chbegin == 0 && roi.chend == 4) {
1611         // Easy case -- both buffers are float, 4 channels, alpha is
1612         // channel[3], no special z channel, and pixel data windows
1613         // completely cover the roi. This reduces to a simpler case we can
1614         // handle without iterators and taking advantage of SIMD.
1615         return over_impl_rgbafloat(dst, A, B, roi, nthreads);
1616     }
1617 
1618     bool ok;
1619     OIIO_DISPATCH_COMMON_TYPES3(ok, "over", over_impl, dst.spec().format,
1620                                 A.spec().format, B.spec().format, dst, A, B,
1621                                 false, false, roi, nthreads);
1622     return ok && !dst.has_error();
1623 }
1624 
1625 
1626 
1627 ImageBuf
over(const ImageBuf & A,const ImageBuf & B,ROI roi,int nthreads)1628 ImageBufAlgo::over(const ImageBuf& A, const ImageBuf& B, ROI roi, int nthreads)
1629 {
1630     ImageBuf result;
1631     bool ok = over(result, A, B, roi, nthreads);
1632     if (!ok && !result.has_error())
1633         result.errorf("ImageBufAlgo::over() error");
1634     return result;
1635 }
1636 
1637 
1638 
1639 bool
zover(ImageBuf & dst,const ImageBuf & A,const ImageBuf & B,bool z_zeroisinf,ROI roi,int nthreads)1640 ImageBufAlgo::zover(ImageBuf& dst, const ImageBuf& A, const ImageBuf& B,
1641                     bool z_zeroisinf, ROI roi, int nthreads)
1642 {
1643     pvt::LoggedTimer logtime("IBA::zover");
1644     if (!IBAprep(roi, &dst, &A, &B, NULL,
1645                  IBAprep_REQUIRE_ALPHA | IBAprep_REQUIRE_Z
1646                      | IBAprep_REQUIRE_SAME_NCHANNELS))
1647         return false;
1648     bool ok;
1649     OIIO_DISPATCH_COMMON_TYPES3(ok, "zover", over_impl, dst.spec().format,
1650                                 A.spec().format, B.spec().format, dst, A, B,
1651                                 true, z_zeroisinf, roi, nthreads);
1652     return ok && !dst.has_error();
1653 }
1654 
1655 
1656 
1657 ImageBuf
zover(const ImageBuf & A,const ImageBuf & B,bool z_zeroisinf,ROI roi,int nthreads)1658 ImageBufAlgo::zover(const ImageBuf& A, const ImageBuf& B, bool z_zeroisinf,
1659                     ROI roi, int nthreads)
1660 {
1661     ImageBuf result;
1662     bool ok = zover(result, A, B, z_zeroisinf, roi, nthreads);
1663     if (!ok && !result.has_error())
1664         result.errorf("ImageBufAlgo::zover() error");
1665     return result;
1666 }
1667 
1668 
1669 OIIO_NAMESPACE_END
1670