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