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