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 "testMultiPartSharedAttributes.h"
11 #include "random.h"
12 
13 #include <ImfMultiPartInputFile.h>
14 #include <ImfMultiPartOutputFile.h>
15 #include <ImfOutputFile.h>
16 #include <ImfTiledOutputFile.h>
17 #include <ImfGenericOutputFile.h>
18 #include <ImfArray.h>
19 #include <ImfChannelList.h>
20 #include <ImfFrameBuffer.h>
21 #include <ImfHeader.h>
22 #include <ImfOutputPart.h>
23 #include <ImfInputPart.h>
24 #include <ImfTiledOutputPart.h>
25 #include <ImfTiledInputPart.h>
26 #include <ImfBoxAttribute.h>
27 #include <ImfChromaticitiesAttribute.h>
28 #include <ImfTimeCodeAttribute.h>
29 #include <ImfFloatAttribute.h>
30 #include <ImfIntAttribute.h>
31 #include <ImfPartType.h>
32 #include <IexBaseExc.h>
33 
34 #include <iostream>
35 #include <string>
36 #include <vector>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <assert.h>
40 
41 
42 namespace IMF = OPENEXR_IMF_NAMESPACE;
43 using namespace IMF;
44 using namespace std;
45 using namespace IMATH_NAMESPACE;
46 
47 #ifndef ILM_IMF_TEST_IMAGEDIR
48     #define ILM_IMF_TEST_IMAGEDIR
49 #endif
50 
51 namespace
52 {
53 
54 const int height = 263;
55 const int width = 197;
56 
57 
58 void
generateRandomHeaders(int partCount,vector<Header> & headers)59 generateRandomHeaders (int partCount, vector<Header> & headers)
60 {
61     headers.clear();
62 
63     for (int i = 0; i < partCount; i++)
64     {
65         Header header(width, height);
66         int pixelType = random_int(3);
67         int partType = random_int(2);
68 
69         stringstream ss;
70         ss << i;
71         header.setName(ss.str());
72 
73         switch (pixelType)
74         {
75             case 0:
76                 header.channels().insert("UINT", Channel(IMF::UINT));
77                 break;
78             case 1:
79                 header.channels().insert("FLOAT", Channel(IMF::FLOAT));
80                 break;
81             case 2:
82                 header.channels().insert("HALF", Channel(IMF::HALF));
83                 break;
84         }
85 
86         switch (partType)
87         {
88             case 0:
89                 header.setType(IMF::SCANLINEIMAGE);
90                 break;
91             case 1:
92                 header.setType(IMF::TILEDIMAGE);
93                 break;
94         }
95 
96         int tileX;
97         int tileY;
98         int levelMode;
99         if (partType == 1)
100         {
101             tileX = random_int(width) + 1;
102             tileY = random_int(height) + 1;
103             levelMode = random_int(3);
104             LevelMode lm = NUM_LEVELMODES;
105             switch (levelMode)
106             {
107                 case 0:
108                     lm = ONE_LEVEL;
109                     break;
110                 case 1:
111                     lm = MIPMAP_LEVELS;
112                     break;
113                 case 2:
114                     lm = RIPMAP_LEVELS;
115                     break;
116             }
117             header.setTileDescription(TileDescription(tileX, tileY, lm));
118         }
119 
120         headers.push_back(header);
121     }
122 }
123 
124 void
testMultiPartOutputFileForExpectedFailure(const vector<Header> & headers,const std::string & fn,const string & failMessage="")125 testMultiPartOutputFileForExpectedFailure (const vector<Header> & headers,
126                                            const std::string & fn,
127                                            const string & failMessage="")
128 {
129     bool caught = false;
130 
131     try
132     {
133         remove(fn.c_str());
134         MultiPartOutputFile file(fn.c_str(), headers.data() , headers.size() );
135         cerr << "ERROR -- " << failMessage << endl;
136         assert (false);
137     }
138     catch (const IEX_NAMESPACE::ArgExc & e)
139     {
140         // expected behaviour
141         caught = true;
142     }
143     assert (caught);
144 }
145 
146 
147 void
testDisplayWindow(const vector<Header> & hs,const std::string & fn)148 testDisplayWindow (const vector<Header> & hs, const std::string & fn)
149 {
150     vector<Header> headers(hs);
151     headers[0].channels().insert("Dummy",Channel());
152     IMATH_NAMESPACE::Box2i newDisplayWindow = headers[0].displayWindow();
153     Header newHeader (newDisplayWindow.size().x+10, newDisplayWindow.size().y+10);
154     newHeader.setType (headers[0].type());
155     newHeader.channels() = headers[0].channels();
156     newHeader.setName (headers[0].name() + string("_newHeader"));
157     headers.push_back (newHeader);
158     testMultiPartOutputFileForExpectedFailure (headers,
159                                                fn,
160                                                "Shared Attributes : displayWindow : should fail for !=values");
161 
162     return;
163 }
164 
165 void
testPixelAspectRatio(const vector<Header> & hs,const std::string & fn)166 testPixelAspectRatio (const vector<Header> & hs, const std::string & fn)
167 {
168     vector<Header> headers(hs);
169 
170     Header newHeader (headers[0].displayWindow().size().x+1,
171                       headers[0].displayWindow().size().y+1,
172                       headers[0].pixelAspectRatio() + 1.f);
173     newHeader.setType (headers[0].type());
174     newHeader.setName (headers[0].name() + string("_newHeader"));
175     newHeader.channels().insert("Dummy",Channel());
176     headers.push_back (newHeader);
177     testMultiPartOutputFileForExpectedFailure (headers,
178                                                fn,
179                                                "Shared Attributes : pixelAspecRatio : should fail for !=values");
180 
181     return;
182 }
183 
184 void
testTimeCode(const vector<Header> & hs,const std::string & fn)185 testTimeCode (const vector<Header> & hs, const std::string & fn)
186 {
187     vector<Header> headers(hs);
188 
189     Header newHeader (headers[0]);
190     newHeader.setName (headers[0].name() + string("_newHeader"));
191     newHeader.channels().insert("Dummy",Channel());
192 
193 
194     //
195     // Test against a vector of headers that has no attributes of this type
196     //
197     TimeCode t(1234567);
198     TimeCodeAttribute ta(t);
199     newHeader.insert(TimeCodeAttribute::staticTypeName(), ta);
200     headers.push_back (newHeader);
201     testMultiPartOutputFileForExpectedFailure (headers,
202                                                fn,
203                                                "Shared Attributes : timecode : should fail for !presence");
204 
205 
206     //
207     // Test against a vector of headers that has chromaticities attribute
208     // but of differing value
209     //
210     for (size_t i=0; i<headers.size(); i++)
211         headers[i].insert (TimeCodeAttribute::staticTypeName(), ta);
212 
213     t.setTimeAndFlags (t.timeAndFlags()+1);
214     TimeCodeAttribute tta(t);
215     newHeader.insert (TimeCodeAttribute::staticTypeName(), tta);
216     newHeader.setName (newHeader.name() + string("_+1"));
217     headers.push_back (newHeader);
218     testMultiPartOutputFileForExpectedFailure (headers,
219                                                fn,
220                                                "Shared Attributes : timecode : should fail for != values");
221 
222     return;
223 }
224 
225 void
testChromaticities(const vector<Header> & hs,const std::string & fn)226 testChromaticities (const vector<Header> & hs, const std::string & fn)
227 {
228     vector<Header> headers(hs);
229 
230     Header newHeader (headers[0]);
231     newHeader.setName (headers[0].name() + string("_newHeader"));
232     newHeader.channels().insert("Dummy",Channel());
233 
234     Chromaticities c;
235     ChromaticitiesAttribute ca(c);
236     newHeader.insert (ChromaticitiesAttribute::staticTypeName(), ca);
237 
238     //
239     // Test against a vector of headers that has no attributes of this type
240     //
241     headers.push_back (newHeader);
242     testMultiPartOutputFileForExpectedFailure (headers,
243                                                fn,
244                                                "Shared Attributes : chromaticities : should fail for !present");
245 
246 
247     //
248     // Test against a vector of headers that has chromaticities attribute
249     // but of differing value
250     //
251     for (size_t i=0; i<headers.size(); i++)
252         headers[i].insert (ChromaticitiesAttribute::staticTypeName(), ca);
253 
254     c.red += IMATH_NAMESPACE::V2f (1.0f, 1.0f);
255     ChromaticitiesAttribute cca(c);
256     newHeader.insert (ChromaticitiesAttribute::staticTypeName(), cca);
257     headers.push_back (newHeader);
258     testMultiPartOutputFileForExpectedFailure (headers,
259                                                fn,
260                                                "Shared Attributes : chromaticities : should fail for != values");
261 
262     return;
263 }
264 
265 
266 void
testSharedAttributes(const std::string & fn)267 testSharedAttributes (const std::string & fn)
268 {
269     //
270     // The Shared Attributes are currently:
271     // Display Window
272     // Pixel Aspect Ratio
273     // TimeCode
274     // Chromaticities
275     //
276 
277     int partCount = 3;
278     vector<Header> headers;
279 
280 
281     // This will generate headers that are valid for all parts
282     generateRandomHeaders (partCount, headers);
283 
284     // expect this to be successful
285     {
286         remove(fn.c_str());
287         MultiPartOutputFile file(fn.c_str(), &headers[0],headers.size());
288     }
289 
290     // Adding a header a that has non-complient standard attributes will throw
291     // an exception.
292 
293     // Run the tests
294     testDisplayWindow    (headers, fn);
295     testPixelAspectRatio (headers, fn);
296     testTimeCode         (headers, fn);
297     testChromaticities   (headers, fn);
298 }
299 
300 
301 template <class T>
302 void
testDiskAttrValue(const Header & diskHeader,const T & testAttribute)303 testDiskAttrValue (const Header & diskHeader, const T & testAttribute)
304 {
305     const string & attrName = testAttribute.typeName();
306     const T & diskAttr = dynamic_cast <const T &> (diskHeader[attrName]);
307     if (diskAttr.value() != testAttribute.value())
308     {
309         throw IEX_NAMESPACE::InputExc ("incorrect value from disk");
310     }
311 
312     return;
313 }
314 
315 
316 void
testHeaders(const std::string & fn)317 testHeaders (const std::string & fn)
318 {
319     //
320     // In a multipart context the headers must be subject to the following
321     // constraints
322     // * type must be set and valid
323     // * unique names
324     //
325 
326 
327     vector<Header> headers;
328 
329     //
330     // expect this to fail - empty header list
331     //
332     testMultiPartOutputFileForExpectedFailure (headers,
333                                                fn,
334                                                "Header : empty header list passed");
335 
336 
337     //
338     // expect this to fail - header has no image attribute type
339     //
340     Header h;
341     h.channels().insert("Dummy",Channel());
342     headers.push_back (h);
343     testMultiPartOutputFileForExpectedFailure (headers,
344                                                fn,
345                                                "Header : empty image type passed");
346 
347 
348     //
349     // expect this to fail - header name duplication
350     //
351     headers[0].setType (IMF::SCANLINEIMAGE);
352     Header hh(headers[0]);
353     headers.push_back(hh);
354     testMultiPartOutputFileForExpectedFailure (headers,
355                                                fn,
356                                                "Header: duplicate header names passed");
357 
358 
359     //
360     // expect this to fail - header has incorrect image attribute type
361     //
362     bool caught = false;
363     try
364     {
365         headers[0].setType ("invalid image type");
366         cerr << "Header : unsupported image type passed" << endl;
367         assert (false);
368     }
369     catch (const IEX_NAMESPACE::ArgExc & e)
370     {
371         // expected behaviour
372         caught = true;
373     }
374     assert (caught);
375 
376 
377     //
378     // Write and Read the data off disk and check values
379     //
380     TimeCode t(1234567);
381     TimeCodeAttribute ta(t);
382     Chromaticities c;
383     ChromaticitiesAttribute ca(c);
384     vector<IntAttribute> ia;
385     for (size_t i=0; i<headers.size(); i++)
386     {
387         stringstream ss;
388         ss << i;
389         headers[i].setName (ss.str());
390         headers[i].setType (IMF::SCANLINEIMAGE);
391         headers[i].insert(TimeCodeAttribute::staticTypeName(), ta);
392         headers[i].insert(ChromaticitiesAttribute::staticTypeName(), ca);
393 
394         IntAttribute ta(i);
395         ia.push_back(ta);
396         headers[i].insert(IntAttribute::staticTypeName(), ta);
397     }
398     vector<FloatAttribute> ifa;
399     ifa.push_back( FloatAttribute( 3.14f ) );
400     headers[0].insert(FloatAttribute::staticTypeName(), ifa.back());
401 
402     // write out the file
403     remove(fn.c_str());
404     {
405         MultiPartOutputFile file(fn.c_str(), &headers[0],headers.size());
406     }
407 
408 
409     // read in the file and look at the attribute data
410     MultiPartInputFile file (fn.c_str());
411 
412     assert (file.parts() == 2);
413 
414     for (int i=0; i<file.parts(); i++)
415     {
416         const Header & diskHeader = file.header(i);
417         //
418         // Test Display Window
419         //
420         const IMATH_NAMESPACE::Box2i & diskDispWin =     diskHeader.displayWindow();
421         const IMATH_NAMESPACE::Box2i & testDispWin =     headers[i].displayWindow();
422         assert (diskDispWin == testDispWin);
423 
424         //
425         // Test Pixel Aspect Ratio
426         //
427         float diskPAR = diskHeader.pixelAspectRatio();
428         float testPAR =     headers[i].pixelAspectRatio();
429         assert (diskPAR == testPAR);
430 
431         //
432         // Test TimeCode
433         //
434         try
435         {
436             testDiskAttrValue<TimeCodeAttribute> (diskHeader, ta);
437         }
438         catch (const IEX_NAMESPACE::InputExc &e)
439         {
440             cerr << "Shared Attributes : TimeCode : " << e.what() << endl;
441             assert (false);
442         }
443 
444         //
445         // Test Chromaticities
446         //
447         try
448         {
449             testDiskAttrValue<ChromaticitiesAttribute> (diskHeader, ca);
450         }
451         catch (const IEX_NAMESPACE::InputExc &e)
452         {
453             cerr << "Shared Attributes : Chromaticities : " << e.what() << endl;
454             assert (false);
455         }
456 
457         //
458         // Test for non-shared attribute that can have differing values across
459         // multiple parts
460         //
461         try
462         {
463             testDiskAttrValue<IntAttribute> (diskHeader, ia[i]);
464         }
465         catch (const IEX_NAMESPACE::InputExc &e)
466         {
467             cerr <<  "Shared Attributes : IntAttribute : " << e.what() << endl;
468             assert (false);
469         }
470     }
471 
472 
473     //
474     // Test the code against an incorrectly constructed file
475     //
476     try
477     {
478         caught = false;
479         MultiPartInputFile file (ILM_IMF_TEST_IMAGEDIR "invalid_shared_attrs_multipart.exr");
480         cerr << "Shared Attributes : InputFile : incorrect input file passed\n";
481         assert (false);
482     }
483     catch (const IEX_NAMESPACE::InputExc &e)
484     {
485         // expectected behaviour
486         caught = true;
487     }
488     assert (caught);
489 }
490 
491 
492 } // namespace
493 
494 void
testMultiPartSharedAttributes(const std::string & tempDir)495 testMultiPartSharedAttributes (const std::string & tempDir)
496 {
497     try
498     {
499         cout << "Testing multi part APIs : shared attributes, header... " << endl;
500 
501         random_reseed(1);
502 
503         std::string fn = tempDir +  "imf_test_multipart_shared_attrs.exr";
504 
505         testSharedAttributes (fn);
506         testHeaders (fn);
507 
508         cout << " ... ok\n" << endl;
509     }
510     catch (const IEX_NAMESPACE::BaseExc & e)
511     {
512         cerr << "ERROR -- caught IEX_NAMESPACE::BaseExc exception: " << e.what() << endl;
513         assert (false);
514     }
515     catch (const std::exception & e)
516     {
517         cerr << "ERROR -- caught std::exception exception: " << e.what() << endl;
518         assert (false);
519     }
520 }
521