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