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 <ImfInputFile.h>
12 #include <ImathRandom.h>
13 #include <ImfTiledInputFile.h>
14 #include <ImfArray.h>
15 #include <ImfChannelList.h>
16 #include <ImfFrameBuffer.h>
17 #include <ImfHeader.h>
18 #include <ImfThreading.h>
19 #include <IlmThread.h>
20 #include <half.h>
21
22 #include <vector>
23 #include <stdio.h>
24 #include <assert.h>
25
26
27 using namespace OPENEXR_IMF_NAMESPACE;
28 using namespace std;
29 using namespace IMATH_NAMESPACE;
30
31
32 namespace {
33
34 void
fillPixels(Array2D<half> & ph,int width,int height)35 fillPixels (Array2D<half> &ph, int width, int height)
36 {
37 for (int y = 0; y < height; ++y)
38 for (int x = 0; x < width; ++x)
39 ph[y][x] = sin (double (x)) + sin (y * 0.5);
40 }
41
42
43 void
writeCopyReadONE(const char fileName[],int width,int height,LineOrder lorder,LevelRoundingMode rmode,int xSize,int ySize,Compression comp,bool triggerBuffering,bool triggerSeeks)44 writeCopyReadONE (const char fileName[],
45 int width,
46 int height,
47 LineOrder lorder,
48 LevelRoundingMode rmode,
49 int xSize,
50 int ySize,
51 Compression comp,
52 bool triggerBuffering,
53 bool triggerSeeks)
54 {
55 cout << "LineOrder " << lorder << ", buffer " << triggerBuffering <<
56 ", seek " << triggerSeeks << ", levelMode 0, " <<
57 "roundingMode " << rmode << ", "
58 "compression " << comp << endl;
59
60 Header hdr ((Box2i (V2i (0, 0), // display window
61 V2i (width - 1, height -1))),
62 (Box2i (V2i (0, 0), // data window
63 V2i (width - 1, height - 1))));
64
65 hdr.compression() = comp;
66 hdr.lineOrder() = INCREASING_Y;
67 hdr.channels().insert ("H", Channel (HALF, 1, 1));
68
69 hdr.setTileDescription(TileDescription(xSize, ySize, ONE_LEVEL, rmode));
70
71 Array2D<half> ph1 (height, width);
72 fillPixels (ph1, width, height);
73
74 {
75 FrameBuffer fb;
76
77 fb.insert ("H",
78 Slice (HALF,
79 (char *) &ph1[0][0],
80 sizeof (ph1[0][0]),
81 sizeof (ph1[0][0]) * width));
82
83 cout << " writing" << flush;
84
85 remove (fileName);
86 TiledOutputFile out (fileName, hdr);
87 out.setFrameBuffer (fb);
88
89 int i;
90
91 Rand32 rand1 = Rand32();
92 std::vector<int> tileYs = std::vector<int>(out.numYTiles());
93 std::vector<int> tileXs = std::vector<int>(out.numXTiles());
94 for (i = 0; i < out.numYTiles(); i++)
95 {
96 if (lorder == DECREASING_Y)
97 tileYs[out.numYTiles()-1-i] = i;
98 else
99 tileYs[i] = i;
100 }
101
102 for (i = 0; i < out.numXTiles(); i++)
103 {
104 tileXs[i] = i;
105 }
106
107 if (triggerBuffering)
108 {
109 // shuffle the tile orders
110 for (i = 0; i < out.numYTiles(); i++)
111 std::swap(tileYs[i], tileYs[int(rand1.nextf(i,out.numYTiles()-1) + 0.5)]);
112
113 for (i = 0; i < out.numXTiles(); i++)
114 std::swap(tileXs[i], tileXs[int(rand1.nextf(i,out.numXTiles()-1) + 0.5)]);
115 }
116
117 for (int tileY = 0; tileY < out.numYTiles(); tileY++)
118 for (int tileX = 0; tileX < out.numXTiles(); tileX++)
119 out.writeTile (tileXs[tileX], tileYs[tileY]);
120 }
121
122 {
123 cout << " reading" << flush;
124
125 TiledInputFile in (fileName);
126
127 const Box2i &dw = in.header().dataWindow();
128 int w = dw.max.x - dw.min.x + 1;
129 int h = dw.max.y - dw.min.y + 1;
130 int dwx = dw.min.x;
131 int dwy = dw.min.y;
132
133 Array2D<half> ph2 (h, w);
134
135 FrameBuffer fb;
136
137 fb.insert ("H",
138 Slice (HALF,
139 (char *) &ph2[-dwy][-dwx],
140 sizeof (ph2[0][0]),
141 sizeof (ph2[0][0]) * w));
142
143 in.setFrameBuffer (fb);
144
145 int startTileY, endTileY;
146 int dy;
147
148 if ((lorder == DECREASING_Y && !triggerSeeks) ||
149 (lorder == INCREASING_Y && triggerSeeks) ||
150 (lorder == RANDOM_Y && triggerSeeks))
151 {
152 startTileY = in.numYTiles() - 1;
153 endTileY = -1;
154
155 dy = -1;
156 }
157 else
158 {
159 startTileY = 0;
160 endTileY = in.numYTiles();
161
162 dy = 1;
163 }
164
165 for (int tileY = startTileY; tileY != endTileY; tileY += dy)
166 for (int tileX = 0; tileX < in.numXTiles(); ++tileX)
167 in.readTile (tileX, tileY);
168
169 cout << " comparing" << flush;
170
171 assert (in.header().displayWindow() == hdr.displayWindow());
172 assert (in.header().dataWindow() == hdr.dataWindow());
173 assert (in.header().pixelAspectRatio() == hdr.pixelAspectRatio());
174 assert (in.header().screenWindowCenter() == hdr.screenWindowCenter());
175 assert (in.header().screenWindowWidth() == hdr.screenWindowWidth());
176 assert (in.header().lineOrder() == hdr.lineOrder());
177 assert (in.header().compression() == hdr.compression());
178 assert (in.header().channels() == hdr.channels());
179
180 for (int y = 0; y < h; ++y)
181 for (int x = 0; x < w; ++x)
182 assert (ph1[y][x] == ph2[y][x]);
183 }
184
185 remove (fileName);
186 cout << endl;
187 }
188
189
190
191 void
writeCopyReadMIP(const char fileName[],int width,int height,LineOrder lorder,LevelRoundingMode rmode,int xSize,int ySize,Compression comp,bool triggerBuffering,bool triggerSeeks)192 writeCopyReadMIP (const char fileName[],
193 int width,
194 int height,
195 LineOrder lorder,
196 LevelRoundingMode rmode,
197 int xSize,
198 int ySize,
199 Compression comp,
200 bool triggerBuffering,
201 bool triggerSeeks)
202 {
203 cout << "LineOrder " << lorder << ", buffer " << triggerBuffering <<
204 ", seek " << triggerSeeks << ", levelMode 1, " <<
205 "roundingMode " << rmode << ", "
206 "compression " << comp << endl;
207
208 Header hdr ((Box2i (V2i (0, 0), // display window
209 V2i (width - 1, height -1))),
210 (Box2i (V2i (0, 0), // data window
211 V2i (width - 1, height - 1))));
212
213 hdr.compression() = comp;
214 hdr.lineOrder() = INCREASING_Y;
215 hdr.channels().insert ("H", Channel (HALF, 1, 1));
216
217 hdr.setTileDescription(TileDescription(xSize, ySize, MIPMAP_LEVELS, rmode));
218
219 Array < Array2D<half> > levels;
220
221 {
222 cout << " writing" << flush;
223
224 remove (fileName);
225 TiledOutputFile out (fileName, hdr);
226
227 int numLevels = out.numLevels();
228 levels.resizeErase (numLevels);
229
230 int i;
231
232 Rand32 rand1 = Rand32();
233 std::vector<int> shuffled_levels = std::vector<int>(numLevels);
234
235 for (i = 0; i < numLevels; i++)
236 shuffled_levels[i] = i;
237
238 if (triggerBuffering)
239 // shuffle the level order
240 for (i = 0; i < numLevels; i++)
241 std::swap(shuffled_levels[i], shuffled_levels[int(rand1.nextf(i,numLevels-1) + 0.5)]);
242
243 for (int level = 0; level < numLevels; ++level)
244 {
245 const int slevel = shuffled_levels[level];
246
247 int levelWidth = out.levelWidth(slevel);
248 int levelHeight = out.levelHeight(slevel);
249 levels[slevel].resizeErase(levelHeight, levelWidth);
250 fillPixels (levels[slevel], levelWidth, levelHeight);
251
252 FrameBuffer fb;
253
254 fb.insert ("H",
255 Slice (HALF,
256 (char *) &levels[slevel][0][0],
257 sizeof (levels[slevel][0][0]),
258 sizeof (levels[slevel][0][0]) * levelWidth));
259
260 out.setFrameBuffer (fb);
261
262 std::vector<int> tileYs = std::vector<int>(out.numYTiles(slevel));
263 std::vector<int> tileXs = std::vector<int>(out.numXTiles(slevel));
264 for (i = 0; i < out.numYTiles(slevel); i++)
265 {
266 if (lorder == DECREASING_Y)
267 tileYs[out.numYTiles(slevel)-1-i] = i;
268 else
269 tileYs[i] = i;
270 }
271
272 for (i = 0; i < out.numXTiles(slevel); i++)
273 tileXs[i] = i;
274
275 if (triggerBuffering)
276 {
277 // shuffle the tile orders
278 for (i = 0; i < out.numYTiles(slevel); i++)
279 std::swap(tileYs[i], tileYs[int(rand1.nextf(i,out.numYTiles(slevel)-1) + 0.5)]);
280
281 for (i = 0; i < out.numXTiles(slevel); i++)
282 std::swap(tileXs[i], tileXs[int(rand1.nextf(i,out.numXTiles(slevel)-1) + 0.5)]);
283 }
284
285 for (int tileY = 0; tileY < out.numYTiles(slevel); ++tileY)
286 for (int tileX = 0; tileX < out.numXTiles(slevel); ++tileX)
287 out.writeTile (tileXs[tileX], tileYs[tileY], slevel);
288 }
289 }
290
291 {
292 cout << " reading" << flush;
293
294 TiledInputFile in (fileName);
295
296 const Box2i &dw = in.header().dataWindow();
297 int dwx = dw.min.x;
298 int dwy = dw.min.y;
299
300 int numLevels = in.numLevels();
301 Array < Array2D<half> > levels2 (numLevels);
302
303 int startTileY, endTileY;
304 int dy;
305
306 for (int level = 0; level < in.numLevels(); ++level)
307 {
308 int levelWidth = in.levelWidth(level);
309 int levelHeight = in.levelHeight(level);
310 levels2[level].resizeErase(levelHeight, levelWidth);
311
312 FrameBuffer fb;
313
314 fb.insert ("H",
315 Slice (HALF,
316 (char *) &levels2[level][-dwy][-dwx],
317 sizeof (levels2[level][0][0]),
318 sizeof (levels2[level][0][0]) * levelWidth));
319
320 in.setFrameBuffer (fb);
321
322 if ((lorder == DECREASING_Y && !triggerSeeks) ||
323 (lorder == INCREASING_Y && triggerSeeks) ||
324 (lorder == RANDOM_Y && triggerSeeks))
325 {
326 startTileY = in.numYTiles(level) - 1;
327 endTileY = -1;
328
329 dy = -1;
330 }
331 else
332 {
333 startTileY = 0;
334 endTileY = in.numYTiles(level);
335
336 dy = 1;
337 }
338
339 for (int tileY = startTileY; tileY != endTileY; tileY += dy)
340 for (int tileX = 0; tileX < in.numXTiles (level); ++tileX)
341 in.readTile (tileX, tileY, level);
342 }
343
344 cout << " comparing" << flush;
345
346 assert (in.header().displayWindow() == hdr.displayWindow());
347 assert (in.header().dataWindow() == hdr.dataWindow());
348 assert (in.header().pixelAspectRatio() == hdr.pixelAspectRatio());
349 assert (in.header().screenWindowCenter() == hdr.screenWindowCenter());
350 assert (in.header().screenWindowWidth() == hdr.screenWindowWidth());
351 assert (in.header().lineOrder() == hdr.lineOrder());
352 assert (in.header().compression() == hdr.compression());
353 assert (in.header().channels() == hdr.channels());
354
355 for (int l = 0; l < numLevels; ++l)
356 for (int y = 0; y < in.levelHeight(l); ++y)
357 for (int x = 0; x < in.levelWidth(l); ++x)
358 assert ((levels2[l])[y][x] == (levels[l])[y][x]);
359 }
360
361 remove (fileName);
362 cout << endl;
363 }
364
365
366 void
writeCopyReadRIP(const char fileName[],int width,int height,LineOrder lorder,LevelRoundingMode rmode,int xSize,int ySize,Compression comp,bool triggerBuffering,bool triggerSeeks)367 writeCopyReadRIP (const char fileName[],
368 int width,
369 int height,
370 LineOrder lorder,
371 LevelRoundingMode rmode,
372 int xSize,
373 int ySize,
374 Compression comp,
375 bool triggerBuffering,
376 bool triggerSeeks)
377 {
378 cout << "LineOrder " << lorder << ", buffer " << triggerBuffering <<
379 ", seek " << triggerSeeks << ", levelMode 2, " <<
380 "roundingMode " << rmode << ", "
381 "compression " << comp << endl;
382
383 Header hdr ((Box2i (V2i (0, 0), // display window
384 V2i (width - 1, height -1))),
385 (Box2i (V2i (0, 0), // data window
386 V2i (width - 1, height - 1))));
387
388 hdr.compression() = comp;
389 hdr.lineOrder() = INCREASING_Y;
390 hdr.channels().insert ("H", Channel (HALF, 1, 1));
391
392 hdr.setTileDescription(TileDescription(xSize, ySize, RIPMAP_LEVELS, rmode));
393
394 Array2D < Array2D<half> > levels;
395
396 {
397 cout << " writing" << flush;
398
399 remove (fileName);
400 TiledOutputFile out (fileName, hdr);
401
402 levels.resizeErase (out.numYLevels(), out.numXLevels());
403
404 int i;
405
406 Rand32 rand1 = Rand32();
407 std::vector<int> shuffled_xLevels = std::vector<int>(out.numXLevels());
408 std::vector<int> shuffled_yLevels = std::vector<int>(out.numYLevels());
409
410 for (i = 0; i < out.numXLevels(); i++)
411 shuffled_xLevels[i] = i;
412
413 for (i = 0; i < out.numYLevels(); i++)
414 shuffled_yLevels[i] = i;
415
416 if (triggerBuffering)
417 {
418 // shuffle the level orders
419 for (i = 0; i < out.numXLevels(); i++)
420 std::swap(shuffled_xLevels[i], shuffled_xLevels[int(rand1.nextf(i,out.numXLevels()-1) + 0.5)]);
421
422 for (i = 0; i < out.numYLevels(); i++)
423 std::swap(shuffled_yLevels[i], shuffled_yLevels[int(rand1.nextf(i,out.numYLevels()-1) + 0.5)]);
424 }
425
426 for (int ylevel = 0; ylevel < out.numYLevels(); ++ylevel)
427 {
428 const int sylevel = shuffled_yLevels[ylevel];
429
430 std::vector<int> tileYs = std::vector<int>(out.numYTiles(sylevel));
431 for (i = 0; i < out.numYTiles(sylevel); i++)
432 {
433 if (lorder == DECREASING_Y)
434 tileYs[out.numYTiles(sylevel)-1-i] = i;
435 else
436 tileYs[i] = i;
437 }
438
439 if (triggerBuffering)
440 // shuffle the tile orders
441 for (i = 0; i < out.numYTiles(sylevel); i++)
442 std::swap(tileYs[i], tileYs[int(rand1.nextf(i,out.numYTiles(sylevel)-1) + 0.5)]);
443
444 for (int xlevel = 0; xlevel < out.numXLevels(); ++xlevel)
445 {
446 const int sxlevel = shuffled_xLevels[xlevel];
447
448 int levelWidth = out.levelWidth(sxlevel);
449 int levelHeight = out.levelHeight(sylevel);
450 levels[sylevel][sxlevel].resizeErase(levelHeight, levelWidth);
451 fillPixels (levels[sylevel][sxlevel], levelWidth, levelHeight);
452
453 FrameBuffer fb;
454 fb.insert ("H",
455 Slice (HALF,
456 (char *) &levels[sylevel][sxlevel][0][0],
457 sizeof (levels[sylevel][sxlevel][0][0]),
458 sizeof (levels[sylevel][sxlevel][0][0]) * levelWidth));
459
460 out.setFrameBuffer (fb);
461
462 std::vector<int> tileXs = std::vector<int>(out.numXTiles(sxlevel));
463 for (i = 0; i < out.numXTiles(sxlevel); i++)
464 tileXs[i] = i;
465
466 if (triggerBuffering)
467 // shuffle the tile orders
468 for (i = 0; i < out.numXTiles(sxlevel); i++)
469 std::swap(tileXs[i], tileXs[int(rand1.nextf(i,out.numXTiles(sxlevel)-1) + 0.5)]);
470
471 for (int tileY = 0; tileY < out.numYTiles(sylevel); ++tileY)
472 for (int tileX = 0; tileX < out.numXTiles(sxlevel); ++tileX)
473 out.writeTile(tileXs[tileX], tileYs[tileY], sxlevel, sylevel);
474 }
475 }
476 }
477
478 {
479 cout << " reading" << flush;
480
481 TiledInputFile in (fileName);
482
483 const Box2i &dw = in.header().dataWindow();
484 int dwx = dw.min.x;
485 int dwy = dw.min.y;
486
487 int numXLevels = in.numXLevels();
488 int numYLevels = in.numYLevels();
489 Array2D < Array2D<half> > levels2 (numYLevels, numXLevels);
490
491 int startTileY, endTileY;
492 int dy;
493
494 for (int ylevel = 0; ylevel < in.numYLevels(); ++ylevel)
495 {
496 if ((lorder == DECREASING_Y && !triggerSeeks) ||
497 (lorder == INCREASING_Y && triggerSeeks) ||
498 (lorder == RANDOM_Y && triggerSeeks))
499 {
500 startTileY = in.numYTiles(ylevel) - 1;
501 endTileY = -1;
502
503 dy = -1;
504 }
505 else
506 {
507 startTileY = 0;
508 endTileY = in.numYTiles(ylevel);
509
510 dy = 1;
511 }
512
513 for (int xlevel = 0; xlevel < in.numXLevels(); ++xlevel)
514 {
515 int levelWidth = in.levelWidth(xlevel);
516 int levelHeight = in.levelHeight(ylevel);
517 levels2[ylevel][xlevel].resizeErase(levelHeight, levelWidth);
518
519 FrameBuffer fb;
520 fb.insert ("H",
521 Slice (HALF,
522 (char *) &levels2[ylevel][xlevel][-dwy][-dwx],
523 sizeof (levels2[ylevel][xlevel][0][0]),
524 sizeof (levels2[ylevel][xlevel][0][0]) * levelWidth));
525
526 in.setFrameBuffer (fb);
527
528 for (int tileY = startTileY; tileY != endTileY; tileY += dy)
529 for (int tileX = 0; tileX < in.numXTiles (xlevel); ++tileX)
530 in.readTile (tileX, tileY, xlevel, ylevel);
531 }
532 }
533
534 cout << " comparing" << flush;
535
536 assert (in.header().displayWindow() == hdr.displayWindow());
537 assert (in.header().dataWindow() == hdr.dataWindow());
538 assert (in.header().pixelAspectRatio() == hdr.pixelAspectRatio());
539 assert (in.header().screenWindowCenter() == hdr.screenWindowCenter());
540 assert (in.header().screenWindowWidth() == hdr.screenWindowWidth());
541 assert (in.header().lineOrder() == hdr.lineOrder());
542 assert (in.header().compression() == hdr.compression());
543 assert (in.header().channels() == hdr.channels());
544
545 for (int ly = 0; ly < numYLevels; ++ly)
546 for (int lx = 0; lx < numXLevels; ++lx)
547 for (int y = 0; y < in.levelHeight(ly); ++y)
548 for (int x = 0; x < in.levelWidth(lx); ++x)
549 assert ((levels2[ly][lx])[y][x] ==
550 (levels[ly][lx])[y][x]);
551 }
552
553 remove (fileName);
554 cout << endl;
555 }
556
557
558 void
writeCopyRead(const std::string & tempDir,int w,int h,int xs,int ys)559 writeCopyRead (const std::string &tempDir, int w, int h, int xs, int ys)
560 {
561 std::string filename = tempDir + "imf_test_copy.exr";
562
563 for (int comp = 0; comp < NUM_COMPRESSION_METHODS; ++comp)
564 {
565 if (comp == B44_COMPRESSION ||
566 comp == B44A_COMPRESSION)
567 {
568 continue;
569 }
570
571 for (int lorder = 0; lorder < RANDOM_Y; ++lorder)
572 {
573 for (int rmode = 0; rmode < NUM_ROUNDINGMODES; ++rmode)
574 {
575 for (int tb = 0; tb <= 1; ++tb)
576 {
577 for (int ts = 0; ts <= 1; ++ts)
578 {
579 writeCopyReadONE (filename.c_str(), w, h,
580 (LineOrder)lorder,
581 (LevelRoundingMode) rmode,
582 xs, ys,
583 Compression (comp),
584 (bool)tb, (bool)ts);
585
586 writeCopyReadMIP (filename.c_str(), w, h,
587 (LineOrder)lorder,
588 (LevelRoundingMode) rmode,
589 xs, ys,
590 Compression (comp),
591 (bool)tb, (bool)ts);
592
593 writeCopyReadRIP (filename.c_str(), w, h,
594 (LineOrder)lorder,
595 (LevelRoundingMode) rmode,
596 xs, ys,
597 Compression (comp),
598 (bool)tb, (bool)ts);
599 }
600 }
601 }
602 }
603 }
604 }
605
606 } // namespace
607
608
609 void
testTiledLineOrder(const std::string & tempDir)610 testTiledLineOrder (const std::string &tempDir)
611 {
612 try
613 {
614 cout << "Testing line orders for tiled files and "
615 "buffered/unbuffered tile writes" << endl;
616
617 const int W = 171;
618 const int H = 259;
619 const int XS = 55;
620 const int YS = 55;
621
622 int maxThreads = ILMTHREAD_NAMESPACE::supportsThreads()? 3: 0;
623
624 for (int n = 0; n <= maxThreads; ++n)
625 {
626 if (ILMTHREAD_NAMESPACE::supportsThreads())
627 {
628 setGlobalThreadCount (n);
629 cout << "\nnumber of threads: " << globalThreadCount() << endl;
630 }
631
632 writeCopyRead (tempDir, W, H, XS, YS);
633 }
634
635 cout << "ok\n" << endl;
636 }
637 catch (const std::exception &e)
638 {
639 cerr << "ERROR -- caught exception: " << e.what() << endl;
640 assert (false);
641 }
642 }
643