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 
6 #include <cstdio>
7 #include <iomanip>
8 #include <iostream>
9 #include <string>
10 
11 #include <OpenImageIO/platform.h>
12 
13 #if USE_OPENCV
14 // Suppress gcc 11 / C++20 errors about opencv 4 headers
15 #    if OIIO_GNUC_VERSION >= 110000 && OIIO_CPLUSPLUS_VERSION >= 20
16 #        pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion"
17 #    endif
18 #    include <opencv2/opencv.hpp>
19 #endif
20 
21 #include <OpenImageIO/argparse.h>
22 #include <OpenImageIO/benchmark.h>
23 #include <OpenImageIO/imagebuf.h>
24 #include <OpenImageIO/imagebufalgo.h>
25 #include <OpenImageIO/imagebufalgo_util.h>
26 #include <OpenImageIO/imageio.h>
27 #include <OpenImageIO/timer.h>
28 #include <OpenImageIO/unittest.h>
29 
30 using namespace OIIO;
31 
32 
33 static int iterations     = 1;
34 static int numthreads     = 16;
35 static int ntrials        = 1;
36 static bool verbose       = false;
37 static bool wedge         = false;
38 static int threadcounts[] = { 1,  2,  4,  8,  12,  16,   20,
39                               24, 28, 32, 64, 128, 1024, 1 << 30 };
40 
41 
42 static void
getargs(int argc,char * argv[])43 getargs(int argc, char* argv[])
44 {
45     ArgParse ap;
46     // clang-format off
47     ap.intro("imagebufalgo_test\n" OIIO_INTRO_STRING)
48       .usage("imagebufalgo_test [options]");
49 
50     ap.arg("-v", &verbose)
51       .help("Verbose mode");
52     ap.arg("--threads %d", &numthreads)
53       .help(Strutil::sprintf("Number of threads (default: %d)", numthreads));
54     ap.arg("--iters %d", &iterations)
55       .help(Strutil::sprintf("Number of iterations (default: %d)", iterations));
56     ap.arg("--trials %d", &ntrials)
57       .help("Number of trials");
58     ap.arg("--wedge", &wedge)
59       .help("Do a wedge test");
60     // clang-format on
61 
62     ap.parse(argc, (const char**)argv);
63 }
64 
65 
66 
67 void
test_type_merge()68 test_type_merge()
69 {
70     std::cout << "test type_merge\n";
71     OIIO_CHECK_EQUAL(TypeDesc::basetype_merge(TypeDesc::UINT8, TypeDesc::UINT8),
72                      TypeDesc::UINT8);
73     OIIO_CHECK_EQUAL(TypeDesc::basetype_merge(TypeDesc::UINT8, TypeDesc::FLOAT),
74                      TypeDesc::FLOAT);
75     OIIO_CHECK_EQUAL(TypeDesc::basetype_merge(TypeDesc::FLOAT, TypeDesc::UINT8),
76                      TypeDesc::FLOAT);
77     OIIO_CHECK_EQUAL(TypeDesc::basetype_merge(TypeDesc::UINT8, TypeDesc::UINT16),
78                      TypeDesc::UINT16);
79     OIIO_CHECK_EQUAL(TypeDesc::basetype_merge(TypeDesc::UINT16, TypeDesc::FLOAT),
80                      TypeDesc::FLOAT);
81     OIIO_CHECK_EQUAL(TypeDesc::basetype_merge(TypeDesc::HALF, TypeDesc::FLOAT),
82                      TypeDesc::FLOAT);
83     OIIO_CHECK_EQUAL(TypeDesc::basetype_merge(TypeDesc::HALF, TypeDesc::UINT8),
84                      TypeDesc::HALF);
85     OIIO_CHECK_EQUAL(TypeDesc::basetype_merge(TypeDesc::HALF, TypeDesc::UNKNOWN),
86                      TypeDesc::HALF);
87     OIIO_CHECK_EQUAL(TypeDesc::basetype_merge(TypeDesc::FLOAT,
88                                               TypeDesc::UNKNOWN),
89                      TypeDesc::FLOAT);
90     OIIO_CHECK_EQUAL(TypeDesc::basetype_merge(TypeDesc::UINT8,
91                                               TypeDesc::UNKNOWN),
92                      TypeDesc::UINT8);
93 }
94 
95 
96 
97 // Test ImageBuf::zero and ImageBuf::fill
98 void
test_zero_fill()99 test_zero_fill()
100 {
101     std::cout << "test zero_fill\n";
102     const int WIDTH    = 8;
103     const int HEIGHT   = 6;
104     const int CHANNELS = 4;
105     ImageSpec spec(WIDTH, HEIGHT, CHANNELS, TypeDesc::FLOAT);
106     spec.alpha_channel = 3;
107 
108     // Create a buffer -- pixels should be undefined
109     ImageBuf A(spec);
110 
111     // Set a pixel to an odd value, make sure it takes
112     const float arbitrary1[CHANNELS] = { 0.2f, 0.3f, 0.4f, 0.5f };
113     A.setpixel(1, 1, arbitrary1);
114     float pixel[CHANNELS];  // test pixel
115     A.getpixel(1, 1, pixel);
116     for (int c = 0; c < CHANNELS; ++c)
117         OIIO_CHECK_EQUAL(pixel[c], arbitrary1[c]);
118 
119     // Zero out and test that it worked
120     ImageBufAlgo::zero(A);
121     for (int j = 0; j < HEIGHT; ++j) {
122         for (int i = 0; i < WIDTH; ++i) {
123             float pixel[CHANNELS];
124             A.getpixel(i, j, pixel);
125             for (int c = 0; c < CHANNELS; ++c)
126                 OIIO_CHECK_EQUAL(pixel[c], 0.0f);
127         }
128     }
129 
130     // Test fill of whole image
131     const float arbitrary2[CHANNELS] = { 0.6f, 0.7f, 0.3f, 0.9f };
132     ImageBufAlgo::fill(A, arbitrary2);
133     for (int j = 0; j < HEIGHT; ++j) {
134         for (int i = 0; i < WIDTH; ++i) {
135             float pixel[CHANNELS];
136             A.getpixel(i, j, pixel);
137             for (int c = 0; c < CHANNELS; ++c)
138                 OIIO_CHECK_EQUAL(pixel[c], arbitrary2[c]);
139         }
140     }
141 
142     // Test fill of partial image
143     const float arbitrary3[CHANNELS] = { 0.42f, 0.43f, 0.44f, 0.45f };
144     {
145         const int xbegin = 3, xend = 5, ybegin = 0, yend = 4;
146         ImageBufAlgo::fill(A, arbitrary3, ROI(xbegin, xend, ybegin, yend));
147         for (int j = 0; j < HEIGHT; ++j) {
148             for (int i = 0; i < WIDTH; ++i) {
149                 float pixel[CHANNELS];
150                 A.getpixel(i, j, pixel);
151                 if (j >= ybegin && j < yend && i >= xbegin && i < xend) {
152                     for (int c = 0; c < CHANNELS; ++c)
153                         OIIO_CHECK_EQUAL(pixel[c], arbitrary3[c]);
154                 } else {
155                     for (int c = 0; c < CHANNELS; ++c)
156                         OIIO_CHECK_EQUAL(pixel[c], arbitrary2[c]);
157                 }
158             }
159         }
160     }
161 
162     // Timing
163     Benchmarker bench;
164     ImageBuf buf_rgba_float(ImageSpec(1000, 1000, 4, TypeFloat));
165     ImageBuf buf_rgba_uint8(ImageSpec(1000, 1000, 4, TypeUInt8));
166     ImageBuf buf_rgba_half(ImageSpec(1000, 1000, 4, TypeHalf));
167     ImageBuf buf_rgba_uint16(ImageSpec(1000, 1000, 4, TypeDesc::UINT16));
168     float vals[] = { 0, 0, 0, 0 };
169     bench("  IBA::fill float[4] ",
170           [&]() { ImageBufAlgo::fill(buf_rgba_float, vals); });
171     bench("  IBA::fill uint8[4] ",
172           [&]() { ImageBufAlgo::fill(buf_rgba_uint8, vals); });
173     bench("  IBA::fill uint16[4] ",
174           [&]() { ImageBufAlgo::fill(buf_rgba_uint16, vals); });
175     bench("  IBA::fill half[4] ",
176           [&]() { ImageBufAlgo::fill(buf_rgba_half, vals); });
177 }
178 
179 
180 
181 // Test ImageBuf::copy
182 void
test_copy()183 test_copy()
184 {
185     std::cout << "test copy\n";
186 
187     // Make image A red, image B green, copy part of B to A and check result
188     const int WIDTH = 4, HEIGHT = 4, CHANNELS = 4;
189     ImageSpec spec(WIDTH, HEIGHT, CHANNELS, TypeDesc::FLOAT);
190     // copy region we'll work with
191     ROI roi(2, 4, 1, 3);
192     ImageBuf A(spec), B(spec);
193     float red[4]   = { 1, 0, 0, 1 };
194     float green[4] = { 0, 0, 0.5, 0.5 };
195     ImageBufAlgo::fill(A, red);
196     ImageBufAlgo::fill(B, green);
197     ImageBufAlgo::copy(A, B, TypeUnknown, roi);
198     for (ImageBuf::ConstIterator<float> r(A); !r.done(); ++r) {
199         if (roi.contains(r.x(), r.y())) {
200             for (int c = 0; c < CHANNELS; ++c)
201                 OIIO_CHECK_EQUAL(r[c], green[c]);
202         } else {
203             for (int c = 0; c < CHANNELS; ++c)
204                 OIIO_CHECK_EQUAL(r[c], red[c]);
205         }
206     }
207 
208     // Test copying into a blank image
209     A.clear();
210     ImageBufAlgo::copy(A, B, TypeUnknown, roi);
211     for (ImageBuf::ConstIterator<float> r(A); !r.done(); ++r) {
212         if (roi.contains(r.x(), r.y())) {
213             for (int c = 0; c < CHANNELS; ++c)
214                 OIIO_CHECK_EQUAL(r[c], green[c]);
215         } else {
216             for (int c = 0; c < CHANNELS; ++c)
217                 OIIO_CHECK_EQUAL(r[c], 0.0f);
218         }
219     }
220 
221     // Timing
222     Benchmarker bench;
223     ImageSpec spec_rgba_float(1000, 1000, 4, TypeFloat);
224     ImageSpec spec_rgba_uint8(1000, 1000, 4, TypeUInt8);
225     ImageSpec spec_rgba_half(1000, 1000, 4, TypeHalf);
226     ImageSpec spec_rgba_int16(1000, 1000, 4, TypeDesc::INT16);
227     ImageBuf buf_rgba_uint8(spec_rgba_uint8);
228     ImageBuf buf_rgba_float(spec_rgba_float);
229     ImageBuf buf_rgba_float2(spec_rgba_float);
230     ImageBuf buf_rgba_half(spec_rgba_half);
231     ImageBuf buf_rgba_half2(spec_rgba_half);
232     ImageBuf empty;
233     bench("  IBA::copy float[4] -> float[4] ",
234           [&]() { ImageBufAlgo::copy(buf_rgba_float, buf_rgba_float2); });
235     bench("  IBA::copy float[4] -> empty ", [&]() {
236         empty.clear();
237         ImageBufAlgo::copy(empty, buf_rgba_float2);
238     });
239     bench("  IBA::copy float[4] -> uint8[4] ",
240           [&]() { ImageBufAlgo::copy(buf_rgba_uint8, buf_rgba_float2); });
241     bench("  IBA::copy half[4] -> half[4] ",
242           [&]() { ImageBufAlgo::copy(buf_rgba_half, buf_rgba_half2); });
243     bench("  IBA::copy half[4] -> empty ", [&]() {
244         empty.clear();
245         ImageBufAlgo::copy(empty, buf_rgba_half2);
246     });
247 }
248 
249 
250 
251 // Test ImageBuf::crop
252 void
test_crop()253 test_crop()
254 {
255     std::cout << "test crop\n";
256     int WIDTH = 8, HEIGHT = 6, CHANNELS = 4;
257     // Crop region we'll work with
258     int xbegin = 3, xend = 5, ybegin = 0, yend = 4;
259     ImageSpec spec(WIDTH, HEIGHT, CHANNELS, TypeDesc::FLOAT);
260     spec.alpha_channel = 3;
261     ImageBuf A, B;
262     A.reset(spec);
263     B.reset(spec);
264     float arbitrary1[4];
265     arbitrary1[0] = 0.2f;
266     arbitrary1[1] = 0.3f;
267     arbitrary1[2] = 0.4f;
268     arbitrary1[3] = 0.5f;
269     ImageBufAlgo::fill(A, arbitrary1);
270 
271     // Test CUT crop
272     ImageBufAlgo::crop(B, A, ROI(xbegin, xend, ybegin, yend));
273 
274     // Should have changed the data window (origin and width/height)
275     OIIO_CHECK_EQUAL(B.spec().x, xbegin);
276     OIIO_CHECK_EQUAL(B.spec().width, xend - xbegin);
277     OIIO_CHECK_EQUAL(B.spec().y, ybegin);
278     OIIO_CHECK_EQUAL(B.spec().height, yend - ybegin);
279     float* pixel = OIIO_ALLOCA(float, CHANNELS);
280     for (int j = 0; j < B.spec().height; ++j) {
281         for (int i = 0; i < B.spec().width; ++i) {
282             B.getpixel(i + B.xbegin(), j + B.ybegin(), pixel);
283             // Inside the crop region should match what it always was
284             for (int c = 0; c < CHANNELS; ++c)
285                 OIIO_CHECK_EQUAL(pixel[c], arbitrary1[c]);
286         }
287     }
288 }
289 
290 
291 
292 void
test_paste()293 test_paste()
294 {
295     std::cout << "test paste\n";
296     // Create the source image, make it a color gradient
297     ImageSpec Aspec(4, 4, 3, TypeDesc::FLOAT);
298     ImageBuf A(Aspec);
299     for (ImageBuf::Iterator<float> it(A); !it.done(); ++it) {
300         it[0] = float(it.x()) / float(Aspec.width - 1);
301         it[1] = float(it.y()) / float(Aspec.height - 1);
302         it[2] = 0.1f;
303     }
304 
305     // Create destination image -- fill with grey
306     ImageSpec Bspec(8, 8, 3, TypeDesc::FLOAT);
307     ImageBuf B(Bspec);
308     float gray[3] = { 0.1f, 0.1f, 0.1f };
309     ImageBufAlgo::fill(B, gray);
310 
311     // Paste a few pixels from A into B -- include offsets
312     ImageBufAlgo::paste(B, 2, 2, 0, 1 /* chan offset */,
313                         ImageBufAlgo::cut(A, ROI(1, 4, 1, 4)));
314 
315     // Spot check
316     float a[3], b[3];
317     B.getpixel(1, 1, 0, b);
318     OIIO_CHECK_EQUAL(b[0], gray[0]);
319     OIIO_CHECK_EQUAL(b[1], gray[1]);
320     OIIO_CHECK_EQUAL(b[2], gray[2]);
321 
322     B.getpixel(2, 2, 0, b);
323     A.getpixel(1, 1, 0, a);
324     OIIO_CHECK_EQUAL(b[0], gray[0]);
325     OIIO_CHECK_EQUAL(b[1], a[0]);
326     OIIO_CHECK_EQUAL(b[2], a[1]);
327 
328     B.getpixel(3, 4, 0, b);
329     A.getpixel(2, 3, 0, a);
330     OIIO_CHECK_EQUAL(b[0], gray[0]);
331     OIIO_CHECK_EQUAL(b[1], a[0]);
332     OIIO_CHECK_EQUAL(b[2], a[1]);
333 }
334 
335 
336 
337 void
test_channel_append()338 test_channel_append()
339 {
340     std::cout << "test channel_append\n";
341     ImageSpec spec(2, 2, 1, TypeDesc::FLOAT);
342     ImageBuf A(spec);
343     ImageBuf B(spec);
344     float Acolor = 0.1, Bcolor = 0.2;
345     ImageBufAlgo::fill(A, &Acolor);
346     ImageBufAlgo::fill(B, &Bcolor);
347 
348     ImageBuf R = ImageBufAlgo::channel_append(A, B);
349     OIIO_CHECK_EQUAL(R.spec().width, spec.width);
350     OIIO_CHECK_EQUAL(R.spec().height, spec.height);
351     OIIO_CHECK_EQUAL(R.nchannels(), 2);
352     for (ImageBuf::ConstIterator<float> r(R); !r.done(); ++r) {
353         OIIO_CHECK_EQUAL(r[0], Acolor);
354         OIIO_CHECK_EQUAL(r[1], Bcolor);
355     }
356 }
357 
358 
359 
360 // Tests ImageBufAlgo::add
361 void
test_add()362 test_add()
363 {
364     std::cout << "test add\n";
365     const int WIDTH = 4, HEIGHT = 4, CHANNELS = 4;
366     ImageSpec spec(WIDTH, HEIGHT, CHANNELS, TypeDesc::FLOAT);
367 
368     // Create buffers
369     ImageBuf A(spec);
370     const float Aval[CHANNELS] = { 0.1f, 0.2f, 0.3f, 0.4f };
371     ImageBufAlgo::fill(A, Aval);
372     ImageBuf B(spec);
373     const float Bval[CHANNELS] = { 0.01f, 0.02f, 0.03f, 0.04f };
374     ImageBufAlgo::fill(B, Bval);
375 
376     // Test addition of images
377     ImageBuf R(spec);
378     ImageBufAlgo::add(R, A, B);
379     for (int j = 0; j < spec.height; ++j)
380         for (int i = 0; i < spec.width; ++i)
381             for (int c = 0; c < spec.nchannels; ++c)
382                 OIIO_CHECK_EQUAL(R.getchannel(i, j, 0, c), Aval[c] + Bval[c]);
383 
384     // Test addition of image and constant color
385     ImageBuf D(spec);
386     ImageBufAlgo::add(D, A, Bval);
387     ImageBufAlgo::CompareResults comp;
388     ImageBufAlgo::compare(R, D, 1e-6f, 1e-6f, comp);
389     OIIO_CHECK_EQUAL(comp.maxerror, 0.0f);
390 }
391 
392 
393 
394 // Tests ImageBufAlgo::sub
395 void
test_sub()396 test_sub()
397 {
398     std::cout << "test sub\n";
399     const int WIDTH = 4, HEIGHT = 4, CHANNELS = 4;
400     ImageSpec spec(WIDTH, HEIGHT, CHANNELS, TypeDesc::FLOAT);
401 
402     // Create buffers
403     ImageBuf A(spec);
404     const float Aval[CHANNELS] = { 0.1f, 0.2f, 0.3f, 0.4f };
405     ImageBufAlgo::fill(A, Aval);
406     ImageBuf B(spec);
407     const float Bval[CHANNELS] = { 0.01f, 0.02f, 0.03f, 0.04f };
408     ImageBufAlgo::fill(B, Bval);
409 
410     // Test subtraction of images
411     ImageBuf R(spec);
412     ImageBufAlgo::sub(R, A, B);
413     for (int j = 0; j < spec.height; ++j)
414         for (int i = 0; i < spec.width; ++i)
415             for (int c = 0; c < spec.nchannels; ++c)
416                 OIIO_CHECK_EQUAL(R.getchannel(i, j, 0, c), Aval[c] - Bval[c]);
417 
418     // Test subtraction of image and constant color
419     ImageBuf D(spec);
420     ImageBufAlgo::sub(D, A, Bval);
421     ImageBufAlgo::CompareResults comp;
422     ImageBufAlgo::compare(R, D, 1e-6f, 1e-6f, comp);
423     OIIO_CHECK_EQUAL(comp.maxerror, 0.0f);
424 }
425 
426 
427 
428 // Tests ImageBufAlgo::mul
429 void
test_mul()430 test_mul()
431 {
432     std::cout << "test mul\n";
433     const int WIDTH = 4, HEIGHT = 4, CHANNELS = 4;
434     ImageSpec spec(WIDTH, HEIGHT, CHANNELS, TypeDesc::FLOAT);
435 
436     // Create buffers
437     ImageBuf A(spec);
438     const float Aval[CHANNELS] = { 0.1f, 0.2f, 0.3f, 0.4f };
439     ImageBufAlgo::fill(A, Aval);
440     ImageBuf B(spec);
441     const float Bval[CHANNELS] = { 0.01f, 0.02f, 0.03f, 0.04f };
442     ImageBufAlgo::fill(B, Bval);
443 
444     // Test multiplication of images
445     ImageBuf R(spec);
446     ImageBufAlgo::mul(R, A, B);
447     for (int j = 0; j < spec.height; ++j)
448         for (int i = 0; i < spec.width; ++i)
449             for (int c = 0; c < spec.nchannels; ++c)
450                 OIIO_CHECK_EQUAL(R.getchannel(i, j, 0, c), Aval[c] * Bval[c]);
451 
452     // Test multiplication of image and constant color
453     ImageBuf D(spec);
454     ImageBufAlgo::mul(D, A, Bval);
455     ImageBufAlgo::CompareResults comp;
456     ImageBufAlgo::compare(R, D, 1e-6f, 1e-6f, comp);
457     OIIO_CHECK_EQUAL(comp.maxerror, 0.0f);
458 }
459 
460 
461 
462 // Tests ImageBufAlgo::mad
463 void
test_mad()464 test_mad()
465 {
466     std::cout << "test mad\n";
467     const int WIDTH = 4, HEIGHT = 4, CHANNELS = 4;
468     ImageSpec spec(WIDTH, HEIGHT, CHANNELS, TypeDesc::FLOAT);
469 
470     // Create buffers
471     ImageBuf A(spec);
472     const float Aval[CHANNELS] = { 0.1f, 0.2f, 0.3f, 0.4f };
473     ImageBufAlgo::fill(A, Aval);
474     ImageBuf B(spec);
475     const float Bval[CHANNELS] = { 1, 2, 3, 4 };
476     ImageBufAlgo::fill(B, Bval);
477     ImageBuf C(spec);
478     const float Cval[CHANNELS] = { 0.01f, 0.02f, 0.03f, 0.04f };
479     ImageBufAlgo::fill(C, Cval);
480 
481     // Test multiplication of images
482     ImageBuf R(spec);
483     ImageBufAlgo::mad(R, A, B, C);
484     for (int j = 0; j < spec.height; ++j)
485         for (int i = 0; i < spec.width; ++i)
486             for (int c = 0; c < spec.nchannels; ++c)
487                 OIIO_CHECK_EQUAL(R.getchannel(i, j, 0, c),
488                                  Aval[c] * Bval[c] + Cval[c]);
489 
490     // Test multiplication of image and constant color
491     ImageBuf D(spec);
492     ImageBufAlgo::mad(D, A, Bval, Cval);
493     ImageBufAlgo::CompareResults comp;
494     ImageBufAlgo::compare(R, D, 1e-6f, 1e-6f, comp);
495     OIIO_CHECK_EQUAL(comp.maxerror, 0.0f);
496 }
497 
498 
499 
500 // Test ImageBuf::over
501 void
test_over()502 test_over()
503 {
504     std::cout << "test over\n";
505 
506     const int WIDTH = 4, HEIGHT = 4, CHANNELS = 4;
507     ImageSpec spec(WIDTH, HEIGHT, CHANNELS, TypeDesc::FLOAT);
508     ROI roi(2, 4, 1, 3);  // region with fg
509 
510     // Create buffers
511     ImageBuf BG(spec);
512     const float BGval[CHANNELS] = { 0.5f, 0.0f, 0.0f, 0.5f };
513     ImageBufAlgo::fill(BG, BGval);
514 
515     ImageBuf FG(spec);
516     ImageBufAlgo::zero(FG);
517     const float FGval[CHANNELS] = { 0.0f, 0.5f, 0.0f, 0.5f };
518     ImageBufAlgo::fill(FG, FGval, roi);
519 
520     // value it should be where composited
521     const float comp_val[CHANNELS] = { 0.25f, 0.5f, 0.0f, 0.75f };
522 
523     // Test over
524     ImageBuf R(spec);
525     ImageBufAlgo::over(R, FG, BG);
526     for (ImageBuf::ConstIterator<float> r(R); !r.done(); ++r) {
527         if (roi.contains(r.x(), r.y()))
528             for (int c = 0; c < CHANNELS; ++c)
529                 OIIO_CHECK_EQUAL(r[c], comp_val[c]);
530         else
531             for (int c = 0; c < CHANNELS; ++c)
532                 OIIO_CHECK_EQUAL(r[c], BGval[c]);
533     }
534 
535     // Timing
536     Benchmarker bench;
537     ImageSpec onekfloat(1000, 1000, 4, TypeFloat);
538     BG.reset(onekfloat);
539     ImageBufAlgo::fill(BG, BGval);
540     FG.reset(onekfloat);
541     ImageBufAlgo::zero(FG);
542     ImageBufAlgo::fill(FG, FGval, ROI(250, 750, 100, 900));
543     R.reset(onekfloat);
544     bench("  IBA::over ", [&]() { ImageBufAlgo::over(R, FG, BG); });
545 }
546 
547 
548 
549 // Tests ImageBufAlgo::compare
550 void
test_compare()551 test_compare()
552 {
553     std::cout << "test compare\n";
554     // Construct two identical 50% grey images
555     const int WIDTH = 10, HEIGHT = 10, CHANNELS = 3;
556     ImageSpec spec(WIDTH, HEIGHT, CHANNELS, TypeDesc::FLOAT);
557     ImageBuf A(spec);
558     ImageBuf B(spec);
559     const float grey[CHANNELS] = { 0.5f, 0.5f, 0.5f };
560     ImageBufAlgo::fill(A, grey);
561     ImageBufAlgo::fill(B, grey);
562 
563     // Introduce some minor differences
564     const int NDIFFS = 10;
565     ImageBuf::Iterator<float> a(A);
566     for (int i = 0; i < NDIFFS && a.valid(); ++i, ++a) {
567         for (int c = 0; c < CHANNELS; ++c)
568             a[c] = a[c] + 0.01f * i;
569     }
570     // We expect the differences to be { 0, 0.01, 0.02, 0.03, 0.04, 0.05,
571     // 0.06, 0.07, 0.08, 0.09, 0, 0, ...}.
572     const float failthresh = 0.05;
573     const float warnthresh = 0.025;
574     ImageBufAlgo::CompareResults comp;
575     ImageBufAlgo::compare(A, B, failthresh, warnthresh, comp);
576     // We expect 5 pixels to exceed the fail threshold, 7 pixels to
577     // exceed the warn threshold, the maximum difference to be 0.09,
578     // and the maximally different pixel to be (9,0).
579     // The total error should be 3 chans * sum{0.01,...,0.09} / (pixels*chans)
580     //   = 3 * 0.45 / (100*3) = 0.0045
581     std::cout << "Testing comparison: " << comp.nfail << " failed, "
582               << comp.nwarn << " warned, max diff = " << comp.maxerror << " @ ("
583               << comp.maxx << ',' << comp.maxy << ")\n";
584     std::cout << "   mean err " << comp.meanerror << ", RMS err "
585               << comp.rms_error << ", PSNR = " << comp.PSNR << "\n";
586     OIIO_CHECK_EQUAL(comp.nfail, 5);
587     OIIO_CHECK_EQUAL(comp.nwarn, 7);
588     OIIO_CHECK_EQUAL_THRESH(comp.maxerror, 0.09f, 1e-6f);
589     OIIO_CHECK_EQUAL(comp.maxx, 9);
590     OIIO_CHECK_EQUAL(comp.maxy, 0);
591     OIIO_CHECK_EQUAL_THRESH(comp.meanerror, 0.0045f, 1.0e-8f);
592 }
593 
594 
595 
596 // Tests ImageBufAlgo::isConstantColor
597 void
test_isConstantColor()598 test_isConstantColor()
599 {
600     std::cout << "test isConstantColor\n";
601     const int WIDTH = 10, HEIGHT = 10, CHANNELS = 3;
602     ImageSpec spec(WIDTH, HEIGHT, CHANNELS, TypeDesc::FLOAT);
603     ImageBuf A(spec);
604     const float col[CHANNELS] = { 0.25, 0.5, 0.75 };
605     ImageBufAlgo::fill(A, col);
606 
607     float thecolor[CHANNELS] = { 0, 0, 0 };
608     OIIO_CHECK_EQUAL(ImageBufAlgo::isConstantColor(A), true);
609     OIIO_CHECK_EQUAL(ImageBufAlgo::isConstantColor(A, thecolor), true);
610     OIIO_CHECK_EQUAL(col[0], thecolor[0]);
611     OIIO_CHECK_EQUAL(col[1], thecolor[1]);
612     OIIO_CHECK_EQUAL(col[2], thecolor[2]);
613 
614     // Now introduce a difference
615     const float another[CHANNELS] = { 0.25f, 0.51f, 0.75f };
616     A.setpixel(2, 2, 0, another, 3);
617     OIIO_CHECK_EQUAL(ImageBufAlgo::isConstantColor(A), false);
618     OIIO_CHECK_EQUAL(ImageBufAlgo::isConstantColor(A, thecolor), false);
619     // But not with lower threshold
620     OIIO_CHECK_EQUAL(ImageBufAlgo::isConstantColor(A, 0.015f), true);
621 
622     // Make sure ROI works
623     ROI roi(0, WIDTH, 0, 2, 0, 1, 0, CHANNELS);  // should match for this ROI
624     OIIO_CHECK_EQUAL(ImageBufAlgo::isConstantColor(A, 0.0f, {}, roi), true);
625 }
626 
627 
628 
629 // Tests ImageBufAlgo::isConstantChannel
630 void
test_isConstantChannel()631 test_isConstantChannel()
632 {
633     std::cout << "test isConstantChannel\n";
634     const int WIDTH = 10, HEIGHT = 10, CHANNELS = 3;
635     ImageSpec spec(WIDTH, HEIGHT, CHANNELS, TypeDesc::FLOAT);
636     ImageBuf A(spec);
637     const float col[CHANNELS] = { 0.25f, 0.5f, 0.75f };
638     ImageBufAlgo::fill(A, col);
639 
640     OIIO_CHECK_EQUAL(ImageBufAlgo::isConstantChannel(A, 1, 0.5f), true);
641 
642     // Now introduce a difference
643     const float another[CHANNELS] = { 0.25f, 0.51f, 0.75f };
644     A.setpixel(2, 2, 0, another, 3);
645     // It should still pass if within the threshold
646     OIIO_CHECK_EQUAL(ImageBufAlgo::isConstantChannel(A, 1, 0.5f, 0.015f), true);
647     // But not with lower threshold
648     OIIO_CHECK_EQUAL(ImageBufAlgo::isConstantChannel(A, 1, 0.5f, 0.005), false);
649     // And certainly not with zero threshold
650     OIIO_CHECK_EQUAL(ImageBufAlgo::isConstantChannel(A, 1, 0.5f), false);
651 
652     // Make sure ROI works
653     ROI roi(0, WIDTH, 0, 2, 0, 1, 0, CHANNELS);  // should match for this ROI
654     OIIO_CHECK_EQUAL(ImageBufAlgo::isConstantChannel(A, 1, 0.5f, roi = roi),
655                      true);
656 }
657 
658 
659 
660 // Tests ImageBufAlgo::isMonochrome
661 void
test_isMonochrome()662 test_isMonochrome()
663 {
664     std::cout << "test isMonochrome\n";
665     const int WIDTH = 10, HEIGHT = 10, CHANNELS = 3;
666     ImageSpec spec(WIDTH, HEIGHT, CHANNELS, TypeDesc::FLOAT);
667     ImageBuf A(spec);
668     const float col[CHANNELS] = { 0.25f, 0.25f, 0.25f };
669     ImageBufAlgo::fill(A, col);
670 
671     OIIO_CHECK_EQUAL(ImageBufAlgo::isMonochrome(A), true);
672 
673     // Now introduce a tiny difference
674     const float another[CHANNELS] = { 0.25f, 0.25f, 0.26f };
675     A.setpixel(2, 2, 0, another, 3);
676     // It should still pass if within the threshold
677     OIIO_CHECK_EQUAL(ImageBufAlgo::isMonochrome(A, 0.015f), true);
678     // But not with lower threshold
679     OIIO_CHECK_EQUAL(ImageBufAlgo::isMonochrome(A, 0.005f), false);
680     // And certainly not with zero threshold
681     OIIO_CHECK_EQUAL(ImageBufAlgo::isMonochrome(A), false);
682 
683 
684     // Make sure ROI works
685     ROI roi(0, WIDTH, 0, 2, 0, 1, 0, CHANNELS);  // should match for this ROI
686     OIIO_CHECK_EQUAL(ImageBufAlgo::isMonochrome(A, roi), true);
687 }
688 
689 
690 
691 // Tests ImageBufAlgo::computePixelStats()
692 void
test_computePixelStats()693 test_computePixelStats()
694 {
695     std::cout << "test computePixelStats\n";
696     ImageBuf img(ImageSpec(2, 2, 3, TypeDesc::FLOAT));
697     float black[3] = { 0, 0, 0 }, white[3] = { 1, 1, 1 };
698     img.setpixel(0, 0, black);
699     img.setpixel(1, 0, white);
700     img.setpixel(0, 1, black);
701     img.setpixel(1, 1, white);
702     ImageBufAlgo::PixelStats stats;
703     ImageBufAlgo::computePixelStats(stats, img);
704     for (int c = 0; c < 3; ++c) {
705         OIIO_CHECK_EQUAL(stats.min[c], 0.0f);
706         OIIO_CHECK_EQUAL(stats.max[c], 1.0f);
707         OIIO_CHECK_EQUAL(stats.avg[c], 0.5f);
708         OIIO_CHECK_EQUAL(stats.stddev[c], 0.5f);
709         OIIO_CHECK_EQUAL(stats.nancount[c], 0);
710         OIIO_CHECK_EQUAL(stats.infcount[c], 0);
711         OIIO_CHECK_EQUAL(stats.finitecount[c], 4);
712     }
713 }
714 
715 
716 
717 // Tests histogram computation.
718 void
histogram_computation_test()719 histogram_computation_test()
720 {
721     const int INPUT_WIDTH   = 64;
722     const int INPUT_HEIGHT  = 64;
723     const int INPUT_CHANNEL = 0;
724 
725     const int HISTOGRAM_BINS = 256;
726 
727     const int SPIKE1 = 51;   // 0.2f in range 0->1 maps to 51 in range 0->255
728     const int SPIKE2 = 128;  // 0.5f in range 0->1 maps to 128 in range 0->255
729     const int SPIKE3 = 204;  // 0.8f in range 0->1 maps to 204 in range 0->255
730 
731     const int SPIKE1_COUNT = INPUT_WIDTH * 8;
732     const int SPIKE2_COUNT = INPUT_WIDTH * 16;
733     const int SPIKE3_COUNT = INPUT_WIDTH * 40;
734 
735     // Create input image with three regions with different pixel values.
736     ImageSpec spec(INPUT_WIDTH, INPUT_HEIGHT, 1, TypeDesc::FLOAT);
737     ImageBuf A(spec);
738 
739     float value[] = { 0.2f };
740     ImageBufAlgo::fill(A, value, ROI(0, INPUT_WIDTH, 0, 8));
741 
742     value[0] = 0.5f;
743     ImageBufAlgo::fill(A, value, ROI(0, INPUT_WIDTH, 8, 24));
744 
745     value[0] = 0.8f;
746     ImageBufAlgo::fill(A, value, ROI(0, INPUT_WIDTH, 24, 64));
747 
748     // Compute A's histogram.
749     std::vector<imagesize_t> hist = ImageBufAlgo::histogram(A, INPUT_CHANNEL,
750                                                             HISTOGRAM_BINS);
751 
752     // Does the histogram size equal the number of bins?
753     OIIO_CHECK_EQUAL(hist.size(), (imagesize_t)HISTOGRAM_BINS);
754 
755     // Are the histogram values as expected?
756     OIIO_CHECK_EQUAL(hist[SPIKE1], (imagesize_t)SPIKE1_COUNT);
757     OIIO_CHECK_EQUAL(hist[SPIKE2], (imagesize_t)SPIKE2_COUNT);
758     OIIO_CHECK_EQUAL(hist[SPIKE3], (imagesize_t)SPIKE3_COUNT);
759     for (int i = 0; i < HISTOGRAM_BINS; i++)
760         if (i != SPIKE1 && i != SPIKE2 && i != SPIKE3)
761             OIIO_CHECK_EQUAL(hist[i], 0);
762 }
763 
764 
765 
766 // Test ability to do a maketx directly from an ImageBuf
767 void
test_maketx_from_imagebuf()768 test_maketx_from_imagebuf()
769 {
770     std::cout << "test make_texture from ImageBuf\n";
771     // Make a checkerboard
772     const int WIDTH = 16, HEIGHT = 16, CHANNELS = 3;
773     ImageSpec spec(WIDTH, HEIGHT, CHANNELS, TypeDesc::FLOAT);
774     ImageBuf A(spec);
775     float pink[] = { 0.5f, 0.3f, 0.3f }, green[] = { 0.1f, 0.5f, 0.1f };
776     ImageBufAlgo::checker(A, 4, 4, 4, pink, green);
777 
778     // Write it
779     const char* pgname = "oiio-pgcheck.tx";
780     remove(pgname);  // Remove it first
781     ImageSpec configspec;
782     ImageBufAlgo::make_texture(ImageBufAlgo::MakeTxTexture, A, pgname,
783                                configspec);
784 
785     // Read it back and compare it
786     ImageBuf B(pgname);
787     B.read();
788     ImageBufAlgo::CompareResults comparison;
789     ImageBufAlgo::compare(A, B, 0, 0, comparison);
790     OIIO_CHECK_EQUAL(comparison.nwarn, 0);
791     OIIO_CHECK_EQUAL(comparison.nfail, 0);
792     remove(pgname);  // clean up
793 }
794 
795 
796 
797 // Test various IBAprep features
798 void
test_IBAprep()799 test_IBAprep()
800 {
801     std::cout << "test IBAprep\n";
802     using namespace ImageBufAlgo;
803     ImageBuf rgb(ImageSpec(256, 256, 3));   // Basic RGB uint8 image
804     ImageBuf rgba(ImageSpec(256, 256, 4));  // Basic RGBA uint8 image
805 
806 #define CHECK(...)                               \
807     {                                            \
808         ImageBuf dst;                            \
809         ROI roi;                                 \
810         OIIO_CHECK_ASSERT(IBAprep(__VA_ARGS__)); \
811     }
812 #define CHECK0(...)                               \
813     {                                             \
814         ImageBuf dst;                             \
815         ROI roi;                                  \
816         OIIO_CHECK_ASSERT(!IBAprep(__VA_ARGS__)); \
817     }
818 
819     // Test REQUIRE_ALPHA
820     CHECK(roi, &dst, &rgba, IBAprep_REQUIRE_ALPHA);
821     CHECK0(roi, &dst, &rgb, IBAprep_REQUIRE_ALPHA);
822 
823     // Test REQUIRE_Z
824     ImageSpec rgbaz_spec(256, 256, 5);
825     rgbaz_spec.channelnames[4] = "Z";
826     rgbaz_spec.z_channel       = 4;
827     ImageBuf rgbaz(rgbaz_spec);
828     CHECK(roi, &dst, &rgbaz, IBAprep_REQUIRE_Z);
829     CHECK0(roi, &dst, &rgb, IBAprep_REQUIRE_Z);
830 
831     // Test REQUIRE_SAME_NCHANNELS
832     CHECK(roi, &dst, &rgb, &rgb, NULL, NULL, IBAprep_REQUIRE_SAME_NCHANNELS);
833     CHECK0(roi, &dst, &rgb, &rgba, NULL, NULL, IBAprep_REQUIRE_SAME_NCHANNELS);
834 
835     // Test NO_SUPPOERT_VOLUME
836     ImageSpec volspec(256, 256, 3);
837     volspec.depth = 256;
838     ImageBuf vol(volspec);
839     CHECK(roi, &dst, &rgb, IBAprep_NO_SUPPORT_VOLUME);
840     CHECK0(roi, &dst, &vol, IBAprep_NO_SUPPORT_VOLUME);
841 
842     // Test SUPPORT_DEEP
843     ImageSpec deepspec(256, 256, 3);
844     deepspec.deep = true;
845     ImageBuf deep(deepspec);
846     CHECK(roi, &dst, &deep, IBAprep_SUPPORT_DEEP);
847     CHECK0(roi, &dst, &deep);  // deep should be rejected
848 
849     // Test DEEP_MIXED
850     CHECK(roi, &dst, &deep, &deep, NULL,
851           IBAprep_SUPPORT_DEEP | IBAprep_DEEP_MIXED);
852     CHECK(roi, &dst, &deep, &rgb, NULL,
853           IBAprep_SUPPORT_DEEP | IBAprep_DEEP_MIXED);
854     CHECK(roi, &dst, &deep, &deep, NULL, IBAprep_SUPPORT_DEEP);
855     CHECK0(roi, &dst, &deep, &rgb, NULL, IBAprep_SUPPORT_DEEP);
856 
857     // Test DST_FLOAT_PIXELS
858     {
859         ROI roi1, roi2;
860         ImageBuf dst1, dst2;
861         OIIO_CHECK_ASSERT(IBAprep(roi1, &dst1, &rgb));
862         OIIO_CHECK_EQUAL(dst1.spec().format, TypeDesc::UINT8);
863         OIIO_CHECK_ASSERT(IBAprep(roi2, &dst2, &rgb, IBAprep_DST_FLOAT_PIXELS));
864         OIIO_CHECK_EQUAL(dst2.spec().format, TypeDesc::FLOAT);
865     }
866 
867     // Test MINIMIZE_NCHANNELS
868     {
869         ROI roi1, roi2;
870         ImageBuf dst1, dst2;
871         OIIO_CHECK_ASSERT(IBAprep(roi1, &dst1, &rgb, &rgba));
872         OIIO_CHECK_EQUAL(dst1.nchannels(), 4);
873         OIIO_CHECK_ASSERT(IBAprep(roi2, &dst2, &rgb, &rgba, NULL, NULL,
874                                   IBAprep_MINIMIZE_NCHANNELS));
875         OIIO_CHECK_EQUAL(dst2.nchannels(), 3);
876     }
877 #undef CHECK
878 }
879 
880 
881 
882 void
benchmark_parallel_image(int res,int iters)883 benchmark_parallel_image(int res, int iters)
884 {
885     using namespace ImageBufAlgo;
886     std::cout << "\nTime old parallel_image for " << res << "x" << res
887               << std::endl;
888 
889     std::cout << "  threads time    rate   (best of " << ntrials << ")\n";
890     std::cout << "  ------- ------- -------\n";
891     ImageSpec spec(res, res, 3, TypeDesc::FLOAT);
892     ImageBuf X(spec), Y(spec);
893     float one[] = { 1, 1, 1 };
894     ImageBufAlgo::zero(Y);
895     ImageBufAlgo::fill(X, one);
896     float a = 0.5f;
897 
898     // Lambda that does some exercise (a basic SAXPY)
899     auto exercise = [&](ROI roi) {
900         ImageBuf::Iterator<float> y(Y, roi);
901         ImageBuf::ConstIterator<float> x(X, roi);
902         for (; !y.done(); ++y, ++x)
903             for (int c = roi.chbegin; c < roi.chend; ++c)
904                 y[c] = a * x[c] + y[c];
905     };
906 
907     for (int i = 0; threadcounts[i] <= numthreads; ++i) {
908         int nt = wedge ? threadcounts[i] : numthreads;
909         ImageBufAlgo::zero(Y);
910         auto func = [&]() {
911             ImageBufAlgo::parallel_image(Y.roi(), nt, exercise);
912         };
913         double range;
914         double t = time_trial(func, ntrials, iters, &range) / iters;
915         std::cout << Strutil::sprintf("  %4d   %7.3f ms  %5.1f Mpels/s\n", nt,
916                                       t * 1000, double(res * res) / t / 1.0e6);
917         if (!wedge)
918             break;  // don't loop if we're not wedging
919     }
920 
921     std::cout << "\nTime new parallel_image for " << res << "x" << res << "\n";
922 
923     std::cout << "  threads time    rate   (best of " << ntrials << ")\n";
924     std::cout << "  ------- ------- -------\n";
925     for (int i = 0; threadcounts[i] <= numthreads; ++i) {
926         int nt = wedge ? threadcounts[i] : numthreads;
927         // default_thread_pool()->resize (nt);
928         zero(Y);
929         auto func = [&]() { parallel_image(Y.roi(), nt, exercise); };
930         double range;
931         double t = time_trial(func, ntrials, iters, &range) / iters;
932         std::cout << Strutil::sprintf("  %4d   %6.2f ms  %5.1f Mpels/s\n", nt,
933                                       t * 1000, double(res * res) / t / 1.0e6);
934         if (!wedge)
935             break;  // don't loop if we're not wedging
936     }
937 }
938 
939 
940 
941 void
test_opencv()942 test_opencv()
943 {
944 #if USE_OPENCV
945     std::cout << "Testing OpenCV round trip\n";
946     // Make a gradient RGB image, convert to OpenCV cv::Mat, then convert
947     // that back to ImageBuf, make sure the round trip has the same pixels
948     // as the original image.
949     ImageBuf src
950         = ImageBufAlgo::fill({ 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f },
951                              { 0.0f, 0.0f, 1.0f }, { 1.0f, 1.0f, 1.0f },
952                              ROI(0, 64, 0, 64, 0, 1, 0, 3));
953     cv::Mat mat;
954     ImageBufAlgo::to_OpenCV(mat, src);
955     OIIO_CHECK_ASSERT(!mat.empty());
956     ImageBuf dst = ImageBufAlgo::from_OpenCV(mat);
957     OIIO_CHECK_ASSERT(!dst.has_error());
958     auto comp = ImageBufAlgo::compare(src, dst, 0.0f, 0.0f);
959     OIIO_CHECK_EQUAL(comp.error, false);
960     OIIO_CHECK_EQUAL(comp.maxerror, 0.0f);
961 #endif
962 }
963 
964 
965 
966 int
main(int argc,char ** argv)967 main(int argc, char** argv)
968 {
969 #if !defined(NDEBUG) || defined(OIIO_CI) || defined(OIIO_CODE_COVERAGE)
970     // For the sake of test time, reduce the default iterations for DEBUG,
971     // CI, and code coverage builds. Explicit use of --iters or --trials
972     // will override this, since it comes before the getargs() call.
973     iterations /= 10;
974     ntrials = 1;
975 #endif
976 
977     getargs(argc, argv);
978 
979     test_type_merge();
980     test_zero_fill();
981     test_copy();
982     test_crop();
983     test_paste();
984     test_channel_append();
985     test_add();
986     test_sub();
987     test_mul();
988     test_mad();
989     test_over();
990     test_compare();
991     test_isConstantColor();
992     test_isConstantChannel();
993     test_isMonochrome();
994     test_computePixelStats();
995     histogram_computation_test();
996     test_maketx_from_imagebuf();
997     test_IBAprep();
998     test_opencv();
999 
1000     benchmark_parallel_image(64, iterations * 64);
1001     benchmark_parallel_image(512, iterations * 16);
1002     benchmark_parallel_image(1024, iterations * 4);
1003     benchmark_parallel_image(2048, iterations);
1004 
1005     return unit_test_failures;
1006 }
1007