1 //
2 // SPDX-License-Identifier: BSD-3-Clause
3 // Copyright (c) Contributors to the OpenEXR Project.
4 //
5
6 #ifdef NDEBUG
7 # undef NDEBUG
8 #endif
9
10 #include <ImfTiledOutputFile.h>
11 #include <ImfTiledInputFile.h>
12 #include <ImfInputFile.h>
13 #include <ImfTiledRgbaFile.h>
14 #include <ImfRgbaFile.h>
15 #include <ImfArray.h>
16 #include <ImfChannelList.h>
17 #include <ImfThreading.h>
18 #include "IlmThread.h"
19 #include "ImathRandom.h"
20 #include <string>
21 #include <stdio.h>
22 #include <assert.h>
23 #include <vector>
24 #include <math.h>
25 #include <ImfTileDescriptionAttribute.h>
26
27
28 namespace IMF = OPENEXR_IMF_NAMESPACE;
29 using namespace IMF;
30 using namespace std;
31 using namespace IMATH_NAMESPACE;
32
33 namespace {
34
35
36 void
fillPixels(Array2D<unsigned int> & pi,Array2D<half> & ph,Array2D<float> & pf,int width,int height)37 fillPixels (Array2D<unsigned int> &pi,
38 Array2D<half> &ph,
39 Array2D<float> &pf,
40 int width,
41 int height)
42 {
43 for (int y = 0; y < height; ++y)
44 for (int x = 0; x < width; ++x)
45 {
46 pi[y][x] = x % 100 + 100 * (y % 100);
47 ph[y][x] = sin (double (x)) + sin (y * 0.5);
48 pf[y][x] = sin (double (y)) + sin (x * 0.5);
49 }
50 }
51
52
53 void
writeRead(const Array2D<unsigned int> & pi1,const Array2D<half> & ph1,const Array2D<float> & pf1,const char fileName[],LineOrder lorder,int width,int height,int xSize,int ySize,int xOffset,int yOffset,Compression comp,LevelMode mode,LevelRoundingMode rmode,bool fillChannel)54 writeRead (const Array2D<unsigned int> &pi1,
55 const Array2D<half> &ph1,
56 const Array2D<float> &pf1,
57 const char fileName[],
58 LineOrder lorder,
59 int width,
60 int height,
61 int xSize,
62 int ySize,
63 int xOffset,
64 int yOffset,
65 Compression comp,
66 LevelMode mode,
67 LevelRoundingMode rmode,
68 bool fillChannel
69 )
70 {
71 //
72 // Write the pixel data in pi1, ph1 and ph2 to a tiled
73 // image file using the specified parameters.
74 // Read the pixel data back from the file using the scanline
75 // interface one scanline at a time, and verify that the data did
76 // not change.
77 // For MIPMAP and RIPMAP_LEVELS, the lower levels of the images
78 // are filled in cropped versions of the level(0,0) image,
79 // i.e. no filtering is done.
80 //
81
82 cout << "levelMode " << mode <<
83 ", roundingMode " << rmode <<
84 ", line order " << lorder <<
85 ",\ntileSize " << xSize << "x" << ySize <<
86 ", xOffset " << xOffset <<
87 ", yOffset "<< yOffset << endl;
88
89 Header hdr ((Box2i (V2i (0, 0), // display window
90 V2i (width - 1, height -1))),
91 (Box2i (V2i (xOffset, yOffset), // data window
92 V2i (xOffset + width - 1, yOffset + height - 1))));
93 hdr.lineOrder() = lorder;
94 hdr.compression() = comp;
95
96 hdr.channels().insert ("I", Channel (IMF::UINT));
97 hdr.channels().insert ("H", Channel (IMF::HALF));
98 hdr.channels().insert ("F", Channel (IMF::FLOAT));
99
100 hdr.setTileDescription(TileDescription(xSize, ySize, mode, rmode));
101 {
102 FrameBuffer fb;
103
104 fb.insert ("I", // name
105 Slice (IMF::UINT, // type
106 (char *) &pi1[-yOffset][-xOffset], // base
107 sizeof (pi1[0][0]), // xStride
108 sizeof (pi1[0][0]) * width) // yStride
109 );
110
111 fb.insert ("H", // name
112 Slice (IMF::HALF, // type
113 (char *) &ph1[-yOffset][-xOffset], // base
114 sizeof (ph1[0][0]), // xStride
115 sizeof (ph1[0][0]) * width) // yStride
116 );
117
118 fb.insert ("F", // name
119 Slice (IMF::FLOAT, // type
120 (char *) &pf1[-yOffset][-xOffset], // base
121 sizeof (pf1[0][0]), // xStride
122 sizeof (pf1[0][0]) * width) // yStride
123 );
124
125 cout << " writing" << flush;
126
127 remove (fileName);
128 TiledOutputFile out (fileName, hdr);
129 out.setFrameBuffer (fb);
130
131 int startTileY = -1;
132 int endTileY = -1;
133 int dy;
134
135 switch (mode)
136 {
137 case ONE_LEVEL:
138 {
139 if (lorder == DECREASING_Y)
140 {
141 startTileY = out.numYTiles() - 1;
142 endTileY = -1;
143
144 dy = -1;
145 }
146 else
147 {
148 startTileY = 0;
149 endTileY = out.numYTiles();
150
151 dy = 1;
152 }
153
154 for (int tileY = startTileY; tileY != endTileY; tileY += dy)
155 for (int tileX = 0; tileX < out.numXTiles(); ++tileX)
156 out.writeTile (tileX, tileY);
157 }
158 break;
159
160 case MIPMAP_LEVELS:
161 {
162 if (lorder == DECREASING_Y)
163 {
164 endTileY = -1;
165 dy = -1;
166 }
167 else
168 {
169 startTileY = 0;
170 dy = 1;
171 }
172
173 for (int level = 0; level < out.numLevels(); ++level)
174 {
175 if (lorder == DECREASING_Y)
176 startTileY = out.numYTiles(level) - 1;
177 else
178 endTileY = out.numYTiles(level);
179
180 for (int tileY = startTileY; tileY != endTileY; tileY += dy)
181 for (int tileX = 0; tileX < out.numXTiles(level); ++tileX)
182 out.writeTile (tileX, tileY, level);
183 }
184 }
185 break;
186
187 case RIPMAP_LEVELS:
188 {
189 for (int ylevel = 0; ylevel < out.numYLevels(); ++ylevel)
190 {
191 if (lorder == DECREASING_Y)
192 {
193 startTileY = out.numYTiles(ylevel) - 1;
194 endTileY = -1;
195
196 dy = -1;
197 }
198 else
199 {
200 startTileY = 0;
201 endTileY = out.numYTiles(ylevel);
202
203 dy = 1;
204 }
205
206 for (int xlevel = 0; xlevel < out.numXLevels(); ++xlevel)
207 {
208 for (int tileY = startTileY; tileY != endTileY;
209 tileY += dy)
210 for (int tileX = 0; tileX < out.numXTiles (xlevel);
211 ++tileX)
212 out.writeTile (tileX, tileY, xlevel, ylevel);
213 }
214 }
215 }
216 break;
217 case NUM_LEVELMODES:
218 default:
219 std::cerr << "Invalid tile mode " << int(mode) << std::endl;
220 break;
221 }
222 }
223
224 {
225 cout << " reading INCREASING_Y" << flush;
226
227 InputFile in (fileName);
228
229 const Box2i &dw = in.header().dataWindow();
230 int w = dw.max.x - dw.min.x + 1;
231 int h = dw.max.y - dw.min.y + 1;
232 int dwx = dw.min.x;
233 int dwy = dw.min.y;
234
235 Array2D<unsigned int> pi2 (h, w);
236 Array2D<half> ph2 (h, w);
237 Array2D<float> pf2 (h, w);
238
239 Array2D<unsigned int> fi2 (fillChannel ? h : 1 , fillChannel ? w : 1);
240 Array2D<half> fh2 (fillChannel ? h : 1 , fillChannel ? w : 1);
241 Array2D<float> ff2 (fillChannel ? h : 1 , fillChannel ? w : 1);
242
243
244 const unsigned int fillInt = 12;
245 const half fillHalf = 4.5;
246 const float fillFloat = M_PI;
247
248
249 FrameBuffer fb;
250
251 fb.insert ("I", // name
252 Slice (IMF::UINT, // type
253 (char *) &pi2[-dwy][-dwx],// base
254 sizeof (pi2[0][0]), // xStride
255 sizeof (pi2[0][0]) * w) // yStride
256 );
257
258 fb.insert ("H", // name
259 Slice (IMF::HALF, // type
260 (char *) &ph2[-dwy][-dwx],// base
261 sizeof (ph2[0][0]), // xStride
262 sizeof (ph2[0][0]) * w) // yStride
263 );
264
265 fb.insert ("F", // name
266 Slice (IMF::FLOAT, // type
267 (char *) &pf2[-dwy][-dwx],// base
268 sizeof (pf2[0][0]), // xStride
269 sizeof (pf2[0][0]) * w) // yStride
270 );
271
272 if(fillChannel)
273 {
274 fb.insert ("FI", // name
275 Slice (IMF::UINT, // type
276 (char *) &fi2[-dwy][-dwx],// base
277 sizeof (fi2[0][0]), // xStride
278 sizeof (fi2[0][0]) * w,1,1,fillInt) // yStride
279 );
280
281 fb.insert ("FH", // name
282 Slice (IMF::HALF, // type
283 (char *) &fh2[-dwy][-dwx],// base
284 sizeof (fh2[0][0]), // xStride
285 sizeof (fh2[0][0]) * w,1,1,fillHalf) // yStride
286 );
287
288 fb.insert ("FF", // name
289 Slice (IMF::FLOAT, // type
290 (char *) &ff2[-dwy][-dwx],// base
291 sizeof (ff2[0][0]), // xStride
292 sizeof (ff2[0][0]) * w,1,1,fillFloat) // yStride
293 );
294 }
295
296 in.setFrameBuffer (fb);
297 for (int y = dw.min.y; y <= dw.max.y; ++y)
298 in.readPixels (y);
299
300 cout << " comparing" << flush;
301
302 assert (in.header().displayWindow() == hdr.displayWindow());
303 assert (in.header().dataWindow() == hdr.dataWindow());
304 assert (in.header().pixelAspectRatio() == hdr.pixelAspectRatio());
305 assert (in.header().screenWindowCenter() == hdr.screenWindowCenter());
306 assert (in.header().screenWindowWidth() == hdr.screenWindowWidth());
307 assert (in.header().lineOrder() == hdr.lineOrder());
308 assert (in.header().compression() == hdr.compression());
309
310 ChannelList::ConstIterator hi = hdr.channels().begin();
311 ChannelList::ConstIterator ii = in.header().channels().begin();
312
313 while (hi != hdr.channels().end())
314 {
315 assert (!strcmp (hi.name(), ii.name()));
316 assert (hi.channel().type == ii.channel().type);
317 assert (hi.channel().xSampling == ii.channel().xSampling);
318 assert (hi.channel().ySampling == ii.channel().ySampling);
319
320 ++hi;
321 ++ii;
322 }
323
324 assert (ii == in.header().channels().end());
325
326 for (int y = 0; y < h; ++y)
327 {
328 for (int x = 0; x < w; ++x)
329 {
330 assert (pi1[y][x] == pi2[y][x]);
331 assert (ph1[y][x] == ph2[y][x]);
332 assert (pf1[y][x] == pf2[y][x]);
333
334 if (fillChannel)
335 {
336 assert(fi2[y][x] == fillInt);
337 assert(fh2[y][x] == fillHalf);
338 assert(ff2[y][x] == fillFloat);
339 }
340 }
341 }
342 }
343
344 {
345 cout << endl << " reading DECREASING_Y" << flush;
346
347 InputFile in (fileName);
348
349 const Box2i &dw = in.header().dataWindow();
350 int w = dw.max.x - dw.min.x + 1;
351 int h = dw.max.y - dw.min.y + 1;
352 int dwx = dw.min.x;
353 int dwy = dw.min.y;
354
355 Array2D<unsigned int> pi2 (h, w);
356 Array2D<half> ph2 (h, w);
357 Array2D<float> pf2 (h, w);
358
359 Array2D<unsigned int> fi2 (fillChannel ? h : 1 , fillChannel ? w : 1);
360 Array2D<half> fh2 (fillChannel ? h : 1 , fillChannel ? w : 1);
361 Array2D<float> ff2 (fillChannel ? h : 1 , fillChannel ? w : 1);
362
363 FrameBuffer fb;
364
365 fb.insert ("I", // name
366 Slice (IMF::UINT, // type
367 (char *) &pi2[-dwy][-dwx],// base
368 sizeof (pi2[0][0]), // xStride
369 sizeof (pi2[0][0]) * w) // yStride
370 );
371
372 fb.insert ("H", // name
373 Slice (IMF::HALF, // type
374 (char *) &ph2[-dwy][-dwx],// base
375 sizeof (ph2[0][0]), // xStride
376 sizeof (ph2[0][0]) * w) // yStride
377 );
378
379 fb.insert ("F", // name
380 Slice (IMF::FLOAT, // type
381 (char *) &pf2[-dwy][-dwx],// base
382 sizeof (pf2[0][0]), // xStride
383 sizeof (pf2[0][0]) * w) // yStride
384 );
385 const unsigned int fillInt = 21;
386 const half fillHalf = 42;
387 const float fillFloat = 2.8;
388
389 if (fillChannel)
390 {
391 fb.insert ("FI", // name
392 Slice (IMF::UINT, // type
393 (char *) &fi2[-dwy][-dwx],// base
394 sizeof (fi2[0][0]), // xStride
395 sizeof (fi2[0][0]) * w,1,1,fillInt) // yStride
396 );
397
398 fb.insert ("FH", // name
399 Slice (IMF::HALF, // type
400 (char *) &fh2[-dwy][-dwx],// base
401 sizeof (fh2[0][0]), // xStride
402 sizeof (fh2[0][0]) * w,1,1,fillHalf) // yStride
403 );
404
405 fb.insert ("FF", // name
406 Slice (IMF::FLOAT, // type
407 (char *) &ff2[-dwy][-dwx],// base
408 sizeof (ff2[0][0]), // xStride
409 sizeof (ff2[0][0]) * w,1,1,fillFloat) // yStride
410 );
411
412 }
413
414 in.setFrameBuffer (fb);
415 for (int y = dw.max.y; y >= dw.min.y; --y)
416 in.readPixels (y);
417
418 cout << " comparing" << flush;
419
420 assert (in.header().displayWindow() == hdr.displayWindow());
421 assert (in.header().dataWindow() == hdr.dataWindow());
422 assert (in.header().pixelAspectRatio() == hdr.pixelAspectRatio());
423 assert (in.header().screenWindowCenter() == hdr.screenWindowCenter());
424 assert (in.header().screenWindowWidth() == hdr.screenWindowWidth());
425 assert (in.header().lineOrder() == hdr.lineOrder());
426 assert (in.header().compression() == hdr.compression());
427
428 ChannelList::ConstIterator hi = hdr.channels().begin();
429 ChannelList::ConstIterator ii = in.header().channels().begin();
430
431 while (hi != hdr.channels().end())
432 {
433 assert (!strcmp (hi.name(), ii.name()));
434 assert (hi.channel().type == ii.channel().type);
435 assert (hi.channel().xSampling == ii.channel().xSampling);
436 assert (hi.channel().ySampling == ii.channel().ySampling);
437
438 ++hi;
439 ++ii;
440 }
441
442 assert (ii == in.header().channels().end());
443
444 for (int y = 0; y < h; ++y)
445 {
446 for (int x = 0; x < w; ++x)
447 {
448 assert (pi1[y][x] == pi2[y][x]);
449 assert (ph1[y][x] == ph2[y][x]);
450 assert (pf1[y][x] == pf2[y][x]);
451 if (fillChannel)
452 {
453 assert(fi2[y][x] == fillInt);
454 assert(fh2[y][x] == fillHalf);
455 assert(ff2[y][x] == fillFloat);
456 }
457 }
458 }
459 }
460
461 {
462 cout << endl << " reading INCREASING_Y "
463 "(new frame buffer on every line)" << flush;
464
465 InputFile in (fileName);
466
467 const Box2i &dw = in.header().dataWindow();
468 int w = dw.max.x - dw.min.x + 1;
469 int h = dw.max.y - dw.min.y + 1;
470 int dwx = dw.min.x;
471 int dwy = dw.min.y;
472
473 Array2D<unsigned int> pi2 (h, w);
474 Array2D<half> ph2 (h, w);
475 Array2D<float> pf2 (h, w);
476
477
478 Array2D<unsigned int> fi2 (fillChannel ? h : 1 , fillChannel ? w : 1);
479 Array2D<half> fh2 (fillChannel ? h : 1 , fillChannel ? w : 1);
480 Array2D<float> ff2 (fillChannel ? h : 1 , fillChannel ? w : 1);
481
482
483 const unsigned int fillInt = 81;
484 const half fillHalf = 0.5;
485 const float fillFloat = 7.8;
486
487
488 for (int y = dw.min.y; y <= dw.max.y; ++y)
489 {
490 FrameBuffer fb;
491
492 fb.insert ("I", // name
493 Slice (IMF::UINT, // type
494 (char *) &pi2[y - dwy][-dwx], // base
495 sizeof (pi2[0][0]), // xStride
496 0) // yStride
497 );
498
499 fb.insert ("H", // name
500 Slice (IMF::HALF, // type
501 (char *) &ph2[y - dwy][-dwx], // base
502 sizeof (ph2[0][0]), // xStride
503 0) // yStride
504 );
505
506 fb.insert ("F", // name
507 Slice (IMF::FLOAT, // type
508 (char *) &pf2[y - dwy][-dwx], // base
509 sizeof (pf2[0][0]), // xStride
510 0) // yStride
511 );
512
513 if (fillChannel)
514 {
515 fb.insert ("FI", // name
516 Slice (IMF::UINT, // type
517 (char *) &fi2[y - dwy][-dwx], // base
518 sizeof (fi2[0][0]), // xStride
519 0,1,1,fillInt) // yStride
520 );
521
522 fb.insert ("FH", // name
523 Slice (IMF::HALF, // type
524 (char *) &fh2[y - dwy][-dwx], // base
525 sizeof (fh2[0][0]), // xStride
526 0,1,1,fillHalf) // yStride
527 );
528
529 fb.insert ("FF", // name
530 Slice (IMF::FLOAT, // type
531 (char *) &ff2[y - dwy][-dwx], // base
532 sizeof (ff2[0][0]), // xStride
533 0,1,1,fillFloat) // yStride
534 );
535
536 }
537
538 in.setFrameBuffer (fb);
539 in.readPixels (y);
540 }
541
542 cout << " comparing" << flush;
543
544 assert (in.header().displayWindow() == hdr.displayWindow());
545 assert (in.header().dataWindow() == hdr.dataWindow());
546 assert (in.header().pixelAspectRatio() == hdr.pixelAspectRatio());
547 assert (in.header().screenWindowCenter() == hdr.screenWindowCenter());
548 assert (in.header().screenWindowWidth() == hdr.screenWindowWidth());
549 assert (in.header().lineOrder() == hdr.lineOrder());
550 assert (in.header().compression() == hdr.compression());
551
552 ChannelList::ConstIterator hi = hdr.channels().begin();
553 ChannelList::ConstIterator ii = in.header().channels().begin();
554
555 while (hi != hdr.channels().end())
556 {
557 assert (!strcmp (hi.name(), ii.name()));
558 assert (hi.channel().type == ii.channel().type);
559 assert (hi.channel().xSampling == ii.channel().xSampling);
560 assert (hi.channel().ySampling == ii.channel().ySampling);
561
562 ++hi;
563 ++ii;
564 }
565
566 assert (ii == in.header().channels().end());
567
568 for (int y = 0; y < h; ++y)
569 {
570 for (int x = 0; x < w; ++x)
571 {
572 assert (pi1[y][x] == pi2[y][x]);
573 assert (ph1[y][x] == ph2[y][x]);
574 assert (pf1[y][x] == pf2[y][x]);
575 if (fillChannel)
576 {
577 assert (fi2[y][x] == fillInt);
578 assert (fh2[y][x] == fillHalf);
579 assert (ff2[y][x] == fillFloat);
580 }
581 }
582
583 }
584 }
585
586 remove (fileName);
587 cout << endl;
588 }
589
590
591 void
writeRead(const std::string & tempDir,const Array2D<unsigned int> & pi,const Array2D<half> & ph,const Array2D<float> & pf,int W,int H,LineOrder lorder,Compression comp,LevelRoundingMode rmode,int dx,int dy,int xSize,int ySize)592 writeRead (const std::string &tempDir,
593 const Array2D<unsigned int> &pi,
594 const Array2D<half> &ph,
595 const Array2D<float> &pf,
596 int W,
597 int H,
598 LineOrder lorder,
599 Compression comp,
600 LevelRoundingMode rmode,
601 int dx, int dy,
602 int xSize, int ySize)
603 {
604 std::string filename = tempDir + "imf_test_scanline_api.exr";
605
606 writeRead (pi, ph, pf, filename.c_str(), lorder, W, H,
607 xSize, ySize, dx, dy, comp, ONE_LEVEL, rmode , false);
608 writeRead (pi, ph, pf, filename.c_str(), lorder, W, H,
609 xSize, ySize, dx, dy, comp, MIPMAP_LEVELS, rmode , false );
610 writeRead (pi, ph, pf, filename.c_str(), lorder, W, H,
611 xSize, ySize, dx, dy, comp, RIPMAP_LEVELS, rmode , false);
612 writeRead (pi, ph, pf, filename.c_str(), lorder, W, H,
613 xSize, ySize, dx, dy, comp, ONE_LEVEL, rmode , true);
614 }
615
616 } // namespace
617
618
619 void
testScanLineApi(const std::string & tempDir)620 testScanLineApi (const std::string &tempDir)
621 {
622 try
623 {
624 cout << "Testing the scanline API for tiled files" << endl;
625
626 const int W = 48;
627 const int H = 81;
628 const int DX = -17;
629 const int DY = -29;
630
631 Array2D<unsigned int> pi (H, W);
632 Array2D<half> ph (H, W);
633 Array2D<float> pf (H, W);
634 fillPixels (pi, ph, pf, W, H);
635
636 int maxThreads = ILMTHREAD_NAMESPACE::supportsThreads()? 3: 0;
637
638 for (int n = 0; n <= maxThreads; ++n)
639 {
640 if (ILMTHREAD_NAMESPACE::supportsThreads())
641 {
642 setGlobalThreadCount (n);
643 cout << "\nnumber of threads: " << globalThreadCount() << endl;
644 }
645
646 for (int lorder = 0; lorder < NUM_LINEORDERS; ++lorder)
647 {
648 for (int rmode = 0; rmode < NUM_ROUNDINGMODES; ++rmode)
649 {
650 writeRead (tempDir, pi, ph, pf, W, H,
651 LineOrder (lorder),
652 ZIP_COMPRESSION,
653 LevelRoundingMode (rmode),
654 0, 0, 1, 1);
655
656 writeRead (tempDir, pi, ph, pf, W, H,
657 LineOrder (lorder),
658 ZIP_COMPRESSION,
659 LevelRoundingMode (rmode),
660 DX, DY, 1, 1);
661
662 writeRead (tempDir, pi, ph, pf, W, H,
663 LineOrder (lorder),
664 ZIP_COMPRESSION,
665 LevelRoundingMode (rmode),
666 0, 0, 24, 26);
667
668 writeRead (tempDir, pi, ph, pf, W, H,
669 LineOrder (lorder),
670 ZIP_COMPRESSION,
671 LevelRoundingMode (rmode),
672 DX, DY, 24, 26);
673
674 writeRead (tempDir, pi, ph, pf, W, H,
675 LineOrder (lorder),
676 ZIP_COMPRESSION,
677 LevelRoundingMode (rmode),
678 0, 0, 48, 81);
679
680 writeRead (tempDir, pi, ph, pf, W, H,
681 LineOrder (lorder),
682 ZIP_COMPRESSION,
683 LevelRoundingMode (rmode),
684 DX, DY, 48, 81);
685
686 writeRead (tempDir, pi, ph, pf, W, H,
687 LineOrder (lorder),
688 ZIP_COMPRESSION,
689 LevelRoundingMode (rmode),
690 0, 0, 128, 96);
691
692 writeRead (tempDir, pi, ph, pf, W, H,
693 LineOrder (lorder),
694 ZIP_COMPRESSION,
695 LevelRoundingMode (rmode),
696 DX, DY, 128, 96);
697 }
698 }
699 }
700
701 cout << "ok\n" << endl;
702 }
703 catch (const std::exception &e)
704 {
705 cerr << "ERROR -- caught exception: " << e.what() << endl;
706 assert (false);
707 }
708 }
709