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