/src/openexr/src/lib/OpenEXR/ImfHeader.cpp
Line | Count | Source |
1 | | // |
2 | | // SPDX-License-Identifier: BSD-3-Clause |
3 | | // Copyright (c) Contributors to the OpenEXR Project. |
4 | | // |
5 | | |
6 | | //----------------------------------------------------------------------------- |
7 | | // |
8 | | // class Header |
9 | | // |
10 | | //----------------------------------------------------------------------------- |
11 | | |
12 | | #include "Iex.h" |
13 | | #include <IlmThreadConfig.h> |
14 | | #include <ImfBoxAttribute.h> |
15 | | #include <ImfChannelListAttribute.h> |
16 | | #include <ImfChromaticitiesAttribute.h> |
17 | | #include <ImfCompressionAttribute.h> |
18 | | #include <ImfCompressor.h> |
19 | | #include <ImfDeepImageStateAttribute.h> |
20 | | #include <ImfDoubleAttribute.h> |
21 | | #include <ImfDwaCompressor.h> |
22 | | #include <ImfEnvmapAttribute.h> |
23 | | #include <ImfFloatAttribute.h> |
24 | | #include <ImfFloatVectorAttribute.h> |
25 | | #include <ImfHeader.h> |
26 | | #include <ImfIDManifestAttribute.h> |
27 | | #include <ImfIntAttribute.h> |
28 | | #include <ImfKeyCodeAttribute.h> |
29 | | #include <ImfLineOrderAttribute.h> |
30 | | #include <ImfMatrixAttribute.h> |
31 | | #include <ImfMisc.h> |
32 | | #include <ImfOpaqueAttribute.h> |
33 | | #include <ImfPartType.h> |
34 | | #include <ImfPreviewImageAttribute.h> |
35 | | #include <ImfRationalAttribute.h> |
36 | | #include <ImfStdIO.h> |
37 | | #include <ImfStringAttribute.h> |
38 | | #include <ImfStringVectorAttribute.h> |
39 | | #include <ImfTileDescriptionAttribute.h> |
40 | | #include <ImfTimeCodeAttribute.h> |
41 | | #include <ImfVecAttribute.h> |
42 | | #include <ImfVersion.h> |
43 | | #include <atomic> |
44 | | #include <cmath> |
45 | | #include <sstream> |
46 | | #include <stdlib.h> |
47 | | #include <time.h> |
48 | | #include <openexr_base.h> |
49 | | |
50 | | #include "ImfNamespace.h" |
51 | | #include "ImfTiledMisc.h" |
52 | | |
53 | | #if ILMTHREAD_THREADING_ENABLED |
54 | | # include <mutex> |
55 | | #endif |
56 | | |
57 | | OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER |
58 | | |
59 | | using namespace std; |
60 | | using IMATH_NAMESPACE::Box2i; |
61 | | using IMATH_NAMESPACE::V2f; |
62 | | using IMATH_NAMESPACE::V2i; |
63 | | |
64 | | namespace |
65 | | { |
66 | | |
67 | | struct CompressionRecord |
68 | | { |
69 | | CompressionRecord () |
70 | 6.48k | { |
71 | 6.48k | exr_get_default_zip_compression_level (&zip_level); |
72 | 6.48k | exr_get_default_dwa_compression_quality (&dwa_level); |
73 | 6.48k | } |
74 | | int zip_level; |
75 | | float dwa_level; |
76 | | }; |
77 | | // NB: This is extra complicated than one would normally write to |
78 | | // handle scenario that seems to happen on MacOS/Windows (probably |
79 | | // linux too, but unobserved) with a static library, where a |
80 | | // (static/global) Header is being destroyed after the shutdown of |
81 | | // this translation unit happens, causing use after destroy. |
82 | | // |
83 | | // but if we just use the once_flag / call_once mechanism, windows |
84 | | // then starts crashing on exit in a different way. |
85 | | |
86 | | struct CompressionStash; |
87 | | // assignments to here happen in static singleton ctor which is |
88 | | // guaranteed construction safe. |
89 | | // |
90 | | // we could potentially solve with using an atomic shared ptr instead |
91 | | // of needing the ctor/dtor and getstash thing, but gcc 4.8 does not |
92 | | // include proper support for those. |
93 | | static std::atomic<CompressionStash*> s_stash{nullptr}; |
94 | | |
95 | | struct CompressionStash |
96 | | { |
97 | 1 | CompressionStash () { s_stash.store (this); } |
98 | | ~CompressionStash () |
99 | 1 | { |
100 | | // technically not safe in that if there are multiple threads |
101 | | // running at object destruction time, another thread may have |
102 | | // retrieved the pointer, but not yet entered the mutex, but |
103 | | // then we run, destroying the mutex, and then they crash |
104 | | // against a destroyed mutex. But this code only happens at |
105 | | // static object destruction time, and only has |
106 | | // non-deterministic behavior when compiled statically. |
107 | | // so. this is about all we can do to add this feature without |
108 | | // changing the abi other than say don't have static/global |
109 | | // Header objects? |
110 | 1 | s_stash.store (nullptr); |
111 | 1 | #if ILMTHREAD_THREADING_ENABLED |
112 | | // let's explicitly grab the lock and clear the map in case |
113 | | // there is someone waiting on a lock concurrently at static |
114 | | // destruction time, just to be pedantic |
115 | 1 | _mutex.lock (); |
116 | 1 | _store.clear (); |
117 | 1 | _mutex.unlock (); |
118 | 1 | #endif |
119 | 1 | } |
120 | | #if ILMTHREAD_THREADING_ENABLED |
121 | | std::mutex _mutex; |
122 | | #endif |
123 | | std::map<const void*, CompressionRecord> _store; |
124 | | }; |
125 | | |
126 | | static CompressionStash* |
127 | | getStash () |
128 | 66.3k | { |
129 | 66.3k | static CompressionStash stash_impl; |
130 | 66.3k | return s_stash.load (); |
131 | 66.3k | } |
132 | | |
133 | | static void |
134 | | clearCompressionRecord (Header* hdr) |
135 | 42.5k | { |
136 | 42.5k | CompressionStash* s = getStash (); |
137 | 42.5k | if (s) |
138 | 42.5k | { |
139 | 42.5k | #if ILMTHREAD_THREADING_ENABLED |
140 | 42.5k | std::lock_guard<std::mutex> lk (s->_mutex); |
141 | 42.5k | #endif |
142 | 42.5k | auto i = s->_store.find (hdr); |
143 | 42.5k | if (i != s->_store.end ()) s->_store.erase (i); |
144 | 42.5k | } |
145 | 42.5k | } |
146 | | |
147 | | static CompressionRecord |
148 | | retrieveCompressionRecord (const Header* hdr) |
149 | 0 | { |
150 | 0 | CompressionRecord retval; |
151 | |
|
152 | 0 | CompressionStash* s = getStash (); |
153 | 0 | if (s) |
154 | 0 | { |
155 | 0 | #if ILMTHREAD_THREADING_ENABLED |
156 | 0 | std::lock_guard<std::mutex> lk (s->_mutex); |
157 | 0 | #endif |
158 | 0 | auto i = s->_store.find (hdr); |
159 | 0 | if (i != s->_store.end ()) retval = i->second; |
160 | 0 | } |
161 | 0 | return retval; |
162 | 0 | } |
163 | | |
164 | | static CompressionRecord& |
165 | | retrieveCompressionRecord (Header* hdr) |
166 | 3.24k | { |
167 | 3.24k | CompressionStash* s = getStash (); |
168 | 3.24k | if (s) |
169 | 3.24k | { |
170 | 3.24k | #if ILMTHREAD_THREADING_ENABLED |
171 | 3.24k | std::lock_guard<std::mutex> lk (s->_mutex); |
172 | 3.24k | #endif |
173 | 3.24k | return s->_store[hdr]; |
174 | 3.24k | } |
175 | | // this will only happen at app shutdown, so it'd be an invalid |
176 | | // store anyway, but just return something to avoid a crash |
177 | 0 | static CompressionRecord defrec; |
178 | 0 | return defrec; |
179 | 3.24k | } |
180 | | |
181 | | static void |
182 | | copyCompressionRecord (Header* dst, const Header* src) |
183 | 20.5k | { |
184 | 20.5k | CompressionStash* s = getStash (); |
185 | 20.5k | if (s) |
186 | 20.5k | { |
187 | 20.5k | #if ILMTHREAD_THREADING_ENABLED |
188 | 20.5k | std::lock_guard<std::mutex> lk (s->_mutex); |
189 | 20.5k | #endif |
190 | 20.5k | auto i = s->_store.find (src); |
191 | 20.5k | if (i != s->_store.end ()) { s->_store[dst] = i->second; } |
192 | 17.3k | else |
193 | 17.3k | { |
194 | 17.3k | i = s->_store.find (dst); |
195 | 17.3k | if (i != s->_store.end ()) s->_store.erase (i); |
196 | 17.3k | } |
197 | 20.5k | } |
198 | 20.5k | }; |
199 | | |
200 | | void |
201 | | initialize ( |
202 | | Header& header, |
203 | | const Box2i& displayWindow, |
204 | | const Box2i& dataWindow, |
205 | | float pixelAspectRatio, |
206 | | const V2f& screenWindowCenter, |
207 | | float screenWindowWidth, |
208 | | LineOrder lineOrder, |
209 | | Compression compression) |
210 | 42.5k | { |
211 | 42.5k | header.insert ("displayWindow", Box2iAttribute (displayWindow)); |
212 | 42.5k | header.insert ("dataWindow", Box2iAttribute (dataWindow)); |
213 | 42.5k | if (!std::isnormal (pixelAspectRatio) || pixelAspectRatio < 0.f) |
214 | 0 | THROW (IEX_NAMESPACE::ArgExc, "Invalid pixel aspect ratio"); |
215 | 42.5k | header.insert ("pixelAspectRatio", FloatAttribute (pixelAspectRatio)); |
216 | 42.5k | header.insert ("screenWindowCenter", V2fAttribute (screenWindowCenter)); |
217 | 42.5k | header.insert ("screenWindowWidth", FloatAttribute (screenWindowWidth)); |
218 | 42.5k | header.insert ("lineOrder", LineOrderAttribute (lineOrder)); |
219 | 42.5k | header.insert ("compression", CompressionAttribute (compression)); |
220 | 42.5k | header.insert ("channels", ChannelListAttribute ()); |
221 | 42.5k | } |
222 | | |
223 | | template <size_t N> |
224 | | void |
225 | | checkIsNullTerminated (const char (&str)[N], const char* what) |
226 | 0 | { |
227 | 0 | for (size_t i = 0; i < N; ++i) |
228 | 0 | { |
229 | 0 | if (str[i] == '\0') return; |
230 | 0 | } |
231 | 0 | std::stringstream s; |
232 | 0 | s << "Invalid " << what << ": it is more than " << (N - 1) |
233 | 0 | << " characters long."; |
234 | 0 | throw IEX_NAMESPACE::InputExc (s); |
235 | 0 | } |
236 | | |
237 | | void |
238 | | sanityCheckDisplayWindow (int width, int height) |
239 | 42.5k | { |
240 | | // |
241 | | // Ensure a valid displayWindow. All values for which width-1 < 0 |
242 | | // are invalid, but in particular, note that if width=-INT_MAX, |
243 | | // width-1 will overflow. |
244 | | // |
245 | | |
246 | 42.5k | if (width < 1 || height < 1) |
247 | 0 | throw IEX_NAMESPACE::ArgExc ("Invalid display window in image header."); |
248 | 42.5k | } |
249 | | |
250 | | } // namespace |
251 | | |
252 | | void |
253 | | setDefaultZipCompressionLevel (int level) |
254 | 0 | { |
255 | 0 | exr_set_default_zip_compression_level (level); |
256 | 0 | } |
257 | | |
258 | | void |
259 | | setDefaultDwaCompressionLevel (float level) |
260 | 0 | { |
261 | 0 | exr_set_default_dwa_compression_quality (level); |
262 | 0 | } |
263 | | |
264 | | Header::Header ( |
265 | | int width, |
266 | | int height, |
267 | | float pixelAspectRatio, |
268 | | const V2f& screenWindowCenter, |
269 | | float screenWindowWidth, |
270 | | LineOrder lineOrder, |
271 | | Compression compression) |
272 | 42.5k | : _map (), _readsNothing (false) |
273 | 42.5k | { |
274 | 42.5k | sanityCheckDisplayWindow (width, height); |
275 | | |
276 | 42.5k | staticInitialize (); |
277 | | |
278 | 42.5k | Box2i displayWindow (V2i (0, 0), V2i (width - 1, height - 1)); |
279 | | |
280 | 42.5k | initialize ( |
281 | 42.5k | *this, |
282 | 42.5k | displayWindow, |
283 | 42.5k | displayWindow, |
284 | 42.5k | pixelAspectRatio, |
285 | 42.5k | screenWindowCenter, |
286 | 42.5k | screenWindowWidth, |
287 | 42.5k | lineOrder, |
288 | 42.5k | compression); |
289 | 42.5k | } |
290 | | |
291 | | Header::Header ( |
292 | | int width, |
293 | | int height, |
294 | | const Box2i& dataWindow, |
295 | | float pixelAspectRatio, |
296 | | const V2f& screenWindowCenter, |
297 | | float screenWindowWidth, |
298 | | LineOrder lineOrder, |
299 | | Compression compression) |
300 | 0 | : _map (), _readsNothing (false) |
301 | 0 | { |
302 | 0 | sanityCheckDisplayWindow (width, height); |
303 | |
|
304 | 0 | staticInitialize (); |
305 | |
|
306 | 0 | Box2i displayWindow (V2i (0, 0), V2i (width - 1, height - 1)); |
307 | |
|
308 | 0 | initialize ( |
309 | 0 | *this, |
310 | 0 | displayWindow, |
311 | 0 | dataWindow, |
312 | 0 | pixelAspectRatio, |
313 | 0 | screenWindowCenter, |
314 | 0 | screenWindowWidth, |
315 | 0 | lineOrder, |
316 | 0 | compression); |
317 | 0 | } |
318 | | |
319 | | Header::Header ( |
320 | | const Box2i& displayWindow, |
321 | | const Box2i& dataWindow, |
322 | | float pixelAspectRatio, |
323 | | const V2f& screenWindowCenter, |
324 | | float screenWindowWidth, |
325 | | LineOrder lineOrder, |
326 | | Compression compression) |
327 | 0 | : _map (), _readsNothing (false) |
328 | 0 | { |
329 | 0 | staticInitialize (); |
330 | |
|
331 | 0 | initialize ( |
332 | 0 | *this, |
333 | 0 | displayWindow, |
334 | 0 | dataWindow, |
335 | 0 | pixelAspectRatio, |
336 | 0 | screenWindowCenter, |
337 | 0 | screenWindowWidth, |
338 | 0 | lineOrder, |
339 | 0 | compression); |
340 | 0 | } |
341 | | |
342 | | Header::Header (const Header& other) |
343 | 0 | : _map (), _readsNothing (other._readsNothing) |
344 | 0 | { |
345 | 0 | for (AttributeMap::const_iterator i = other._map.begin (); |
346 | 0 | i != other._map.end (); |
347 | 0 | ++i) |
348 | 0 | { |
349 | 0 | insert (*i->first, *i->second); |
350 | 0 | } |
351 | 0 | copyCompressionRecord (this, &other); |
352 | 0 | } |
353 | | |
354 | | Header::Header (Header&& other) |
355 | 0 | : _map (std::move (other._map)), _readsNothing (other._readsNothing) |
356 | 0 | { |
357 | 0 | copyCompressionRecord (this, &other); |
358 | 0 | } |
359 | | |
360 | | Header::~Header () |
361 | 42.5k | { |
362 | 553k | for (AttributeMap::iterator i = _map.begin (); i != _map.end (); ++i) |
363 | 510k | { |
364 | 510k | delete i->second; |
365 | 510k | } |
366 | 42.5k | clearCompressionRecord (this); |
367 | 42.5k | } |
368 | | |
369 | | Header& |
370 | | Header::operator= (const Header& other) |
371 | 0 | { |
372 | 0 | if (this != &other) |
373 | 0 | { |
374 | 0 | for (AttributeMap::iterator i = _map.begin (); i != _map.end (); ++i) |
375 | 0 | { |
376 | 0 | delete i->second; |
377 | 0 | } |
378 | |
|
379 | 0 | _map.clear (); |
380 | |
|
381 | 0 | for (AttributeMap::const_iterator i = other._map.begin (); |
382 | 0 | i != other._map.end (); |
383 | 0 | ++i) |
384 | 0 | { |
385 | 0 | insert (*i->first, *i->second); |
386 | 0 | } |
387 | 0 | copyCompressionRecord (this, &other); |
388 | 0 | _readsNothing = other._readsNothing; |
389 | 0 | } |
390 | |
|
391 | 0 | return *this; |
392 | 0 | } |
393 | | |
394 | | Header& |
395 | | Header::operator= (Header&& other) |
396 | 20.5k | { |
397 | 20.5k | if (this != &other) |
398 | 20.5k | { |
399 | 20.5k | std::swap (_map, other._map); |
400 | | // don't have to move or anything as it's pod types |
401 | 20.5k | copyCompressionRecord (this, &other); |
402 | 20.5k | _readsNothing = other._readsNothing; |
403 | 20.5k | } |
404 | | |
405 | 20.5k | return *this; |
406 | 20.5k | } |
407 | | |
408 | | void |
409 | | Header::erase (const char name[]) |
410 | 0 | { |
411 | 0 | if (name[0] == 0) |
412 | 0 | THROW ( |
413 | 0 | IEX_NAMESPACE::ArgExc, |
414 | 0 | "Image attribute name cannot be an empty string."); |
415 | | |
416 | 0 | AttributeMap::iterator i = _map.find (name); |
417 | 0 | if (i != _map.end ()) |
418 | 0 | { |
419 | 0 | delete i->second; |
420 | 0 | _map.erase (i); |
421 | 0 | } |
422 | 0 | } |
423 | | |
424 | | void |
425 | | Header::erase (const string& name) |
426 | 0 | { |
427 | 0 | erase (name.c_str ()); |
428 | 0 | } |
429 | | |
430 | | void |
431 | | Header::insert (const char name[], const Attribute& attribute) |
432 | 654k | { |
433 | 654k | if (name[0] == 0) |
434 | 0 | THROW ( |
435 | 654k | IEX_NAMESPACE::ArgExc, |
436 | 654k | "Image attribute name cannot be an empty string."); |
437 | | |
438 | 654k | AttributeMap::iterator i = _map.find (name); |
439 | 654k | if (!strcmp (name, "dwaCompressionLevel") && |
440 | 3.77k | !strcmp (attribute.typeName (), "float")) |
441 | 3.24k | { |
442 | 3.24k | const TypedAttribute<float>& dwaattr = |
443 | 3.24k | dynamic_cast<const TypedAttribute<float>&> (attribute); |
444 | 3.24k | dwaCompressionLevel () = dwaattr.value (); |
445 | 3.24k | } |
446 | | |
447 | 654k | if (i == _map.end ()) |
448 | 510k | { |
449 | 510k | Attribute* tmp = attribute.copy (); |
450 | | |
451 | 510k | try |
452 | 510k | { |
453 | 510k | _map[name] = tmp; |
454 | 510k | } |
455 | 510k | catch (...) |
456 | 510k | { |
457 | 0 | delete tmp; |
458 | 0 | throw; |
459 | 0 | } |
460 | 510k | } |
461 | 143k | else |
462 | 143k | { |
463 | 143k | if (strcmp (i->second->typeName (), attribute.typeName ())) |
464 | 0 | THROW ( |
465 | 143k | IEX_NAMESPACE::TypeExc, |
466 | 143k | "Cannot assign a value of " |
467 | 143k | "type \"" |
468 | 143k | << attribute.typeName () |
469 | 143k | << "\" " |
470 | 143k | "to image attribute \"" |
471 | 143k | << name |
472 | 143k | << "\" of " |
473 | 143k | "type \"" |
474 | 143k | << i->second->typeName () << "\"."); |
475 | | |
476 | 143k | Attribute* tmp = attribute.copy (); |
477 | 143k | delete i->second; |
478 | 143k | i->second = tmp; |
479 | 143k | } |
480 | 654k | } |
481 | | |
482 | | void |
483 | | Header::insert (const string& name, const Attribute& attribute) |
484 | 0 | { |
485 | 0 | insert (name.c_str (), attribute); |
486 | 0 | } |
487 | | |
488 | | Attribute& |
489 | | Header::operator[] (const char name[]) |
490 | 25.7k | { |
491 | 25.7k | AttributeMap::iterator i = _map.find (name); |
492 | | |
493 | 25.7k | if (i == _map.end ()) |
494 | 0 | THROW ( |
495 | 25.7k | IEX_NAMESPACE::ArgExc, |
496 | 25.7k | "Cannot find image attribute \"" << name << "\"."); |
497 | | |
498 | 25.7k | return *i->second; |
499 | 25.7k | } |
500 | | |
501 | | const Attribute& |
502 | | Header::operator[] (const char name[]) const |
503 | 16.5k | { |
504 | 16.5k | AttributeMap::const_iterator i = _map.find (name); |
505 | | |
506 | 16.5k | if (i == _map.end ()) |
507 | 0 | THROW ( |
508 | 16.5k | IEX_NAMESPACE::ArgExc, |
509 | 16.5k | "Cannot find image attribute \"" << name << "\"."); |
510 | | |
511 | 16.5k | return *i->second; |
512 | 16.5k | } |
513 | | |
514 | | Attribute& |
515 | | Header::operator[] (const string& name) |
516 | 0 | { |
517 | 0 | return this->operator[] (name.c_str ()); |
518 | 0 | } |
519 | | |
520 | | const Attribute& |
521 | | Header::operator[] (const string& name) const |
522 | 0 | { |
523 | 0 | return this->operator[] (name.c_str ()); |
524 | 0 | } |
525 | | |
526 | | Header::Iterator |
527 | | Header::begin () |
528 | 0 | { |
529 | 0 | return _map.begin (); |
530 | 0 | } |
531 | | |
532 | | Header::ConstIterator |
533 | | Header::begin () const |
534 | 0 | { |
535 | 0 | return _map.begin (); |
536 | 0 | } |
537 | | |
538 | | Header::Iterator |
539 | | Header::end () |
540 | 0 | { |
541 | 0 | return _map.end (); |
542 | 0 | } |
543 | | |
544 | | Header::ConstIterator |
545 | | Header::end () const |
546 | 0 | { |
547 | 0 | return _map.end (); |
548 | 0 | } |
549 | | |
550 | | Header::Iterator |
551 | | Header::find (const char name[]) |
552 | 0 | { |
553 | 0 | return _map.find (name); |
554 | 0 | } |
555 | | |
556 | | Header::ConstIterator |
557 | | Header::find (const char name[]) const |
558 | 0 | { |
559 | 0 | return _map.find (name); |
560 | 0 | } |
561 | | |
562 | | Header::Iterator |
563 | | Header::find (const string& name) |
564 | 0 | { |
565 | 0 | return find (name.c_str ()); |
566 | 0 | } |
567 | | |
568 | | Header::ConstIterator |
569 | | Header::find (const string& name) const |
570 | 0 | { |
571 | 0 | return find (name.c_str ()); |
572 | 0 | } |
573 | | |
574 | | IMATH_NAMESPACE::Box2i& |
575 | | Header::displayWindow () |
576 | 0 | { |
577 | 0 | return static_cast<Box2iAttribute&> ((*this)["displayWindow"]).value (); |
578 | 0 | } |
579 | | |
580 | | const IMATH_NAMESPACE::Box2i& |
581 | | Header::displayWindow () const |
582 | 0 | { |
583 | 0 | return static_cast<const Box2iAttribute&> ((*this)["displayWindow"]) |
584 | 0 | .value (); |
585 | 0 | } |
586 | | |
587 | | IMATH_NAMESPACE::Box2i& |
588 | | Header::dataWindow () |
589 | 0 | { |
590 | 0 | return static_cast<Box2iAttribute&> ((*this)["dataWindow"]).value (); |
591 | 0 | } |
592 | | |
593 | | const IMATH_NAMESPACE::Box2i& |
594 | | Header::dataWindow () const |
595 | 2.99k | { |
596 | 2.99k | return static_cast<const Box2iAttribute&> ((*this)["dataWindow"]).value (); |
597 | 2.99k | } |
598 | | |
599 | | float& |
600 | | Header::pixelAspectRatio () |
601 | 0 | { |
602 | 0 | return static_cast<FloatAttribute&> ((*this)["pixelAspectRatio"]).value (); |
603 | 0 | } |
604 | | |
605 | | const float& |
606 | | Header::pixelAspectRatio () const |
607 | 0 | { |
608 | 0 | return static_cast<const FloatAttribute&> ((*this)["pixelAspectRatio"]) |
609 | 0 | .value (); |
610 | 0 | } |
611 | | |
612 | | IMATH_NAMESPACE::V2f& |
613 | | Header::screenWindowCenter () |
614 | 0 | { |
615 | 0 | return static_cast<V2fAttribute&> ((*this)["screenWindowCenter"]).value (); |
616 | 0 | } |
617 | | |
618 | | const IMATH_NAMESPACE::V2f& |
619 | | Header::screenWindowCenter () const |
620 | 0 | { |
621 | 0 | return static_cast<const V2fAttribute&> ((*this)["screenWindowCenter"]) |
622 | 0 | .value (); |
623 | 0 | } |
624 | | |
625 | | float& |
626 | | Header::screenWindowWidth () |
627 | 0 | { |
628 | 0 | return static_cast<FloatAttribute&> ((*this)["screenWindowWidth"]).value (); |
629 | 0 | } |
630 | | |
631 | | const float& |
632 | | Header::screenWindowWidth () const |
633 | 0 | { |
634 | 0 | return static_cast<const FloatAttribute&> ((*this)["screenWindowWidth"]) |
635 | 0 | .value (); |
636 | 0 | } |
637 | | |
638 | | ChannelList& |
639 | | Header::channels () |
640 | 25.7k | { |
641 | 25.7k | return static_cast<ChannelListAttribute&> ((*this)["channels"]).value (); |
642 | 25.7k | } |
643 | | |
644 | | const ChannelList& |
645 | | Header::channels () const |
646 | 13.1k | { |
647 | 13.1k | return static_cast<const ChannelListAttribute&> ((*this)["channels"]) |
648 | 13.1k | .value (); |
649 | 13.1k | } |
650 | | |
651 | | LineOrder& |
652 | | Header::lineOrder () |
653 | 0 | { |
654 | 0 | return static_cast<LineOrderAttribute&> ((*this)["lineOrder"]).value (); |
655 | 0 | } |
656 | | |
657 | | const LineOrder& |
658 | | Header::lineOrder () const |
659 | 250 | { |
660 | 250 | return static_cast<const LineOrderAttribute&> ((*this)["lineOrder"]) |
661 | 250 | .value (); |
662 | 250 | } |
663 | | |
664 | | Compression& |
665 | | Header::compression () |
666 | 0 | { |
667 | 0 | return static_cast<CompressionAttribute&> ((*this)["compression"]).value (); |
668 | 0 | } |
669 | | |
670 | | const Compression& |
671 | | Header::compression () const |
672 | 0 | { |
673 | 0 | return static_cast<const CompressionAttribute&> ((*this)["compression"]) |
674 | 0 | .value (); |
675 | 0 | } |
676 | | |
677 | | void |
678 | | Header::resetDefaultCompressionLevels () |
679 | 0 | { |
680 | 0 | clearCompressionRecord (this); |
681 | 0 | } |
682 | | |
683 | | int& |
684 | | Header::zipCompressionLevel () |
685 | 0 | { |
686 | 0 | return retrieveCompressionRecord (this).zip_level; |
687 | 0 | } |
688 | | |
689 | | int |
690 | | Header::zipCompressionLevel () const |
691 | 0 | { |
692 | 0 | return retrieveCompressionRecord (this).zip_level; |
693 | 0 | } |
694 | | |
695 | | float& |
696 | | Header::dwaCompressionLevel () |
697 | 3.24k | { |
698 | 3.24k | return retrieveCompressionRecord (this).dwa_level; |
699 | 3.24k | } |
700 | | |
701 | | float |
702 | | Header::dwaCompressionLevel () const |
703 | 0 | { |
704 | 0 | return retrieveCompressionRecord (this).dwa_level; |
705 | 0 | } |
706 | | |
707 | | void |
708 | | Header::setName (const string& name) |
709 | 0 | { |
710 | 0 | insert ("name", StringAttribute (name)); |
711 | 0 | } |
712 | | |
713 | | bool |
714 | | Header::hasName () const |
715 | 0 | { |
716 | 0 | return findTypedAttribute<StringAttribute> ("name") != 0; |
717 | 0 | } |
718 | | |
719 | | string& |
720 | | Header::name () |
721 | 0 | { |
722 | 0 | return typedAttribute<StringAttribute> ("name").value (); |
723 | 0 | } |
724 | | |
725 | | const string& |
726 | | Header::name () const |
727 | 0 | { |
728 | 0 | return typedAttribute<StringAttribute> ("name").value (); |
729 | 0 | } |
730 | | |
731 | | void |
732 | | Header::setType (const string& type) |
733 | 651 | { |
734 | 651 | if (isSupportedType (type) == false) |
735 | 0 | { |
736 | 0 | throw IEX_NAMESPACE::ArgExc ( |
737 | 0 | type + "is not a supported image type." + |
738 | 0 | "The following are supported: " + SCANLINEIMAGE + ", " + |
739 | 0 | TILEDIMAGE + ", " + DEEPSCANLINE + " or " + DEEPTILE + "."); |
740 | 0 | } |
741 | | |
742 | 651 | insert ("type", StringAttribute (type)); |
743 | | |
744 | | // (TODO) Should we do it here? |
745 | 651 | if (isDeepData (type) && hasVersion () == false) { setVersion (1); } |
746 | 651 | } |
747 | | |
748 | | bool |
749 | | Header::hasType () const |
750 | 20.2k | { |
751 | 20.2k | return findTypedAttribute<StringAttribute> ("type") != 0; |
752 | 20.2k | } |
753 | | |
754 | | string& |
755 | | Header::type () |
756 | 0 | { |
757 | 0 | return typedAttribute<StringAttribute> ("type").value (); |
758 | 0 | } |
759 | | |
760 | | const string& |
761 | | Header::type () const |
762 | 0 | { |
763 | 0 | return typedAttribute<StringAttribute> ("type").value (); |
764 | 0 | } |
765 | | |
766 | | void |
767 | | Header::setView (const string& view) |
768 | 0 | { |
769 | 0 | insert ("view", StringAttribute (view)); |
770 | 0 | } |
771 | | |
772 | | bool |
773 | | Header::hasView () const |
774 | 0 | { |
775 | 0 | return findTypedAttribute<StringAttribute> ("view") != 0; |
776 | 0 | } |
777 | | |
778 | | string& |
779 | | Header::view () |
780 | 0 | { |
781 | 0 | return typedAttribute<StringAttribute> ("view").value (); |
782 | 0 | } |
783 | | |
784 | | const string& |
785 | | Header::view () const |
786 | 0 | { |
787 | 0 | return typedAttribute<StringAttribute> ("view").value (); |
788 | 0 | } |
789 | | |
790 | | void |
791 | | Header::setVersion (const int version) |
792 | 0 | { |
793 | 0 | if (version != 1) |
794 | 0 | { |
795 | 0 | throw IEX_NAMESPACE::ArgExc ("We can only process version 1"); |
796 | 0 | } |
797 | | |
798 | 0 | insert ("version", IntAttribute (version)); |
799 | 0 | } |
800 | | |
801 | | bool |
802 | | Header::hasVersion () const |
803 | 0 | { |
804 | 0 | return findTypedAttribute<IntAttribute> ("version") != 0; |
805 | 0 | } |
806 | | |
807 | | int& |
808 | | Header::version () |
809 | 0 | { |
810 | 0 | return typedAttribute<IntAttribute> ("version").value (); |
811 | 0 | } |
812 | | |
813 | | const int& |
814 | | Header::version () const |
815 | 0 | { |
816 | 0 | return typedAttribute<IntAttribute> ("version").value (); |
817 | 0 | } |
818 | | |
819 | | void |
820 | | Header::setChunkCount (int chunks) |
821 | 0 | { |
822 | 0 | insert ("chunkCount", IntAttribute (chunks)); |
823 | 0 | } |
824 | | |
825 | | bool |
826 | | Header::hasChunkCount () const |
827 | 0 | { |
828 | 0 | return findTypedAttribute<IntAttribute> ("chunkCount") != 0; |
829 | 0 | } |
830 | | |
831 | | int& |
832 | | Header::chunkCount () |
833 | 0 | { |
834 | 0 | return typedAttribute<IntAttribute> ("chunkCount").value (); |
835 | 0 | } |
836 | | |
837 | | const int& |
838 | | Header::chunkCount () const |
839 | 0 | { |
840 | 0 | return typedAttribute<IntAttribute> ("chunkCount").value (); |
841 | 0 | } |
842 | | |
843 | | void |
844 | | Header::setTileDescription (const TileDescription& td) |
845 | 0 | { |
846 | 0 | insert ("tiles", TileDescriptionAttribute (td)); |
847 | 0 | } |
848 | | |
849 | | bool |
850 | | Header::hasTileDescription () const |
851 | 0 | { |
852 | 0 | return findTypedAttribute<TileDescriptionAttribute> ("tiles") != 0; |
853 | 0 | } |
854 | | |
855 | | TileDescription& |
856 | | Header::tileDescription () |
857 | 0 | { |
858 | 0 | return typedAttribute<TileDescriptionAttribute> ("tiles").value (); |
859 | 0 | } |
860 | | |
861 | | const TileDescription& |
862 | | Header::tileDescription () const |
863 | 0 | { |
864 | 0 | return typedAttribute<TileDescriptionAttribute> ("tiles").value (); |
865 | 0 | } |
866 | | |
867 | | void |
868 | | Header::setPreviewImage (const PreviewImage& pi) |
869 | 0 | { |
870 | 0 | insert ("preview", PreviewImageAttribute (pi)); |
871 | 0 | } |
872 | | |
873 | | PreviewImage& |
874 | | Header::previewImage () |
875 | 0 | { |
876 | 0 | return typedAttribute<PreviewImageAttribute> ("preview").value (); |
877 | 0 | } |
878 | | |
879 | | const PreviewImage& |
880 | | Header::previewImage () const |
881 | 0 | { |
882 | 0 | return typedAttribute<PreviewImageAttribute> ("preview").value (); |
883 | 0 | } |
884 | | |
885 | | bool |
886 | | Header::hasPreviewImage () const |
887 | 0 | { |
888 | 0 | return findTypedAttribute<PreviewImageAttribute> ("preview") != 0; |
889 | 0 | } |
890 | | |
891 | | void |
892 | | Header::sanityCheck (bool isTiled, bool isMultipartFile) const |
893 | 0 | { |
894 | | // |
895 | | // The display window and the data window must each |
896 | | // contain at least one pixel. In addition, the |
897 | | // coordinates of the window corners must be small |
898 | | // enough to keep expressions like max-min+1 or |
899 | | // max+min from overflowing. |
900 | | // |
901 | |
|
902 | 0 | const Box2i& displayWindow = this->displayWindow (); |
903 | |
|
904 | 0 | if (displayWindow.min.x > displayWindow.max.x || |
905 | 0 | displayWindow.min.y > displayWindow.max.y || |
906 | 0 | displayWindow.min.x <= -(INT_MAX / 2) || |
907 | 0 | displayWindow.min.y <= -(INT_MAX / 2) || |
908 | 0 | displayWindow.max.x >= (INT_MAX / 2) || |
909 | 0 | displayWindow.max.y >= (INT_MAX / 2)) |
910 | 0 | { |
911 | 0 | throw IEX_NAMESPACE::ArgExc ("Invalid display window in image header."); |
912 | 0 | } |
913 | | |
914 | 0 | const Box2i& dataWindow = this->dataWindow (); |
915 | |
|
916 | 0 | if (dataWindow.min.x > dataWindow.max.x || |
917 | 0 | dataWindow.min.y > dataWindow.max.y || |
918 | 0 | dataWindow.min.x <= -(INT_MAX / 2) || |
919 | 0 | dataWindow.min.y <= -(INT_MAX / 2) || |
920 | 0 | dataWindow.max.x >= (INT_MAX / 2) || dataWindow.max.y >= (INT_MAX / 2)) |
921 | 0 | { |
922 | 0 | throw IEX_NAMESPACE::ArgExc ("Invalid data window in image header."); |
923 | 0 | } |
924 | | |
925 | 0 | int w = (dataWindow.max.x - dataWindow.min.x + 1); |
926 | |
|
927 | 0 | int maxImageWidth = 0, maxImageHeight = 0; |
928 | | // TODO: this really should be accessed via the context but |
929 | | // we don't have that fully wired through just yet, so continue |
930 | | // to use the default as the older C++ code has done |
931 | 0 | exr_get_default_maximum_image_size (&maxImageWidth, &maxImageHeight); |
932 | |
|
933 | 0 | if (maxImageWidth > 0 && maxImageWidth < w) |
934 | 0 | { |
935 | 0 | THROW ( |
936 | 0 | IEX_NAMESPACE::ArgExc, |
937 | 0 | "The width of the data window exceeds the " |
938 | 0 | "maximum width of " |
939 | 0 | << maxImageWidth << "pixels."); |
940 | 0 | } |
941 | | |
942 | 0 | int h = (dataWindow.max.y - dataWindow.min.y + 1); |
943 | 0 | if (maxImageHeight > 0 && maxImageHeight < h) |
944 | 0 | { |
945 | 0 | THROW ( |
946 | 0 | IEX_NAMESPACE::ArgExc, |
947 | 0 | "The height of the data window exceeds the " |
948 | 0 | "maximum height of " |
949 | 0 | << maxImageHeight << "pixels."); |
950 | 0 | } |
951 | | |
952 | | // chunk table must be smaller than the maximum image area |
953 | | // (only reachable for unknown types or damaged files: will have thrown earlier |
954 | | // for regular image types) |
955 | 0 | if (maxImageHeight > 0 && maxImageWidth > 0 && hasChunkCount () && |
956 | 0 | static_cast<uint64_t> (chunkCount ()) > |
957 | 0 | uint64_t (maxImageWidth) * uint64_t (maxImageHeight)) |
958 | 0 | { |
959 | 0 | THROW ( |
960 | 0 | IEX_NAMESPACE::ArgExc, |
961 | 0 | "chunkCount exceeds maximum area of " |
962 | 0 | << uint64_t (maxImageWidth) * uint64_t (maxImageHeight) |
963 | 0 | << " pixels."); |
964 | 0 | } |
965 | | |
966 | | // |
967 | | // The pixel aspect ratio must be greater than 0. |
968 | | // In applications, numbers like the display or the |
969 | | // data window dimensions are likely to be multiplied |
970 | | // or divided by the pixel aspect ratio; to avoid |
971 | | // arithmetic exceptions, we limit the pixel aspect |
972 | | // ratio to a range that is smaller than theoretically |
973 | | // possible (real aspect ratios are likely to be close |
974 | | // to 1.0 anyway). |
975 | | // |
976 | | |
977 | 0 | float pixelAspectRatio = this->pixelAspectRatio (); |
978 | |
|
979 | 0 | const float MIN_PIXEL_ASPECT_RATIO = 1e-6f; |
980 | 0 | const float MAX_PIXEL_ASPECT_RATIO = 1e+6f; |
981 | |
|
982 | 0 | if (!std::isnormal (pixelAspectRatio) || |
983 | 0 | pixelAspectRatio < MIN_PIXEL_ASPECT_RATIO || |
984 | 0 | pixelAspectRatio > MAX_PIXEL_ASPECT_RATIO) |
985 | 0 | { |
986 | 0 | throw IEX_NAMESPACE::ArgExc ( |
987 | 0 | "Invalid pixel aspect ratio in image header."); |
988 | 0 | } |
989 | | |
990 | | // |
991 | | // The screen window width must not be less than 0. |
992 | | // The size of the screen window can vary over a wide |
993 | | // range (fish-eye lens to astronomical telescope), |
994 | | // so we can't limit the screen window width to a |
995 | | // small range. |
996 | | // |
997 | | |
998 | 0 | float screenWindowWidth = this->screenWindowWidth (); |
999 | |
|
1000 | 0 | if (screenWindowWidth < 0) |
1001 | 0 | throw IEX_NAMESPACE::ArgExc ( |
1002 | 0 | "Invalid screen window width in image header."); |
1003 | | |
1004 | | // |
1005 | | // If the file has multiple parts, verify that each header has attribute |
1006 | | // name and type. |
1007 | | // (TODO) We may want to check more stuff here. |
1008 | | // |
1009 | | |
1010 | 0 | if (isMultipartFile) |
1011 | 0 | { |
1012 | 0 | if (!hasName ()) |
1013 | 0 | { |
1014 | 0 | throw IEX_NAMESPACE::ArgExc ("Headers in a multipart file should" |
1015 | 0 | " have name attribute."); |
1016 | 0 | } |
1017 | | |
1018 | 0 | if (!hasType ()) |
1019 | 0 | { |
1020 | 0 | throw IEX_NAMESPACE::ArgExc ("Headers in a multipart file should" |
1021 | 0 | " have type attribute."); |
1022 | 0 | } |
1023 | 0 | } |
1024 | | |
1025 | 0 | const std::string& part_type = hasType () ? type () : ""; |
1026 | |
|
1027 | 0 | if (part_type != "" && !isSupportedType (part_type)) |
1028 | 0 | { |
1029 | | // |
1030 | | // skip remaining sanity checks with unsupported types - they may not hold |
1031 | | // |
1032 | 0 | return; |
1033 | 0 | } |
1034 | | |
1035 | 0 | bool isDeep = isDeepData (part_type); |
1036 | | |
1037 | | // |
1038 | | // If the file is tiled, verify that the tile description has reasonable |
1039 | | // values and check to see if the lineOrder is one of the predefined 3. |
1040 | | // If the file is not tiled, then the lineOrder can only be INCREASING_Y |
1041 | | // or DECREASING_Y. |
1042 | | // |
1043 | |
|
1044 | 0 | LineOrder lineOrder = this->lineOrder (); |
1045 | |
|
1046 | 0 | if (isTiled) |
1047 | 0 | { |
1048 | 0 | if (!hasTileDescription ()) |
1049 | 0 | { |
1050 | 0 | throw IEX_NAMESPACE::ArgExc ("Tiled image has no tile " |
1051 | 0 | "description attribute."); |
1052 | 0 | } |
1053 | | |
1054 | 0 | const TileDescription& tileDesc = tileDescription (); |
1055 | |
|
1056 | 0 | if (tileDesc.xSize <= 0 || tileDesc.ySize <= 0 || |
1057 | 0 | tileDesc.xSize > INT_MAX || tileDesc.ySize > INT_MAX) |
1058 | 0 | throw IEX_NAMESPACE::ArgExc ("Invalid tile size in image header."); |
1059 | | |
1060 | 0 | int maxTileWidth = 0, maxTileHeight = 0; |
1061 | | // TODO: this really should be accessed via the context but |
1062 | | // we don't have that fully wired through just yet, so continue |
1063 | | // to use the default as the older C++ code has done |
1064 | 0 | exr_get_default_maximum_tile_size (&maxTileWidth, &maxTileHeight); |
1065 | |
|
1066 | 0 | if (maxTileWidth > 0 && maxTileWidth < int (tileDesc.xSize)) |
1067 | 0 | { |
1068 | 0 | THROW ( |
1069 | 0 | IEX_NAMESPACE::ArgExc, |
1070 | 0 | "The width of the tiles exceeds the maximum " |
1071 | 0 | "width of " |
1072 | 0 | << maxTileWidth << "pixels."); |
1073 | 0 | } |
1074 | | |
1075 | 0 | if (maxTileHeight > 0 && maxTileHeight < int (tileDesc.ySize)) |
1076 | 0 | { |
1077 | 0 | THROW ( |
1078 | 0 | IEX_NAMESPACE::ArgExc, |
1079 | 0 | "The width of the tiles exceeds the maximum " |
1080 | 0 | "width of " |
1081 | 0 | << maxTileHeight << "pixels."); |
1082 | 0 | } |
1083 | | |
1084 | 0 | if (tileDesc.mode != ONE_LEVEL && tileDesc.mode != MIPMAP_LEVELS && |
1085 | 0 | tileDesc.mode != RIPMAP_LEVELS) |
1086 | 0 | throw IEX_NAMESPACE::ArgExc ("Invalid level mode in image header."); |
1087 | | |
1088 | 0 | if (tileDesc.roundingMode != ROUND_UP && |
1089 | 0 | tileDesc.roundingMode != ROUND_DOWN) |
1090 | 0 | throw IEX_NAMESPACE::ArgExc ( |
1091 | 0 | "Invalid level rounding mode in image header."); |
1092 | | |
1093 | 0 | if (lineOrder != INCREASING_Y && lineOrder != DECREASING_Y && |
1094 | 0 | lineOrder != RANDOM_Y) |
1095 | 0 | throw IEX_NAMESPACE::ArgExc ("Invalid line order in image header."); |
1096 | | |
1097 | | // computes size of chunk offset table. Throws an exception if this exceeds |
1098 | | // the maximum allowable size |
1099 | 0 | getTiledChunkOffsetTableSize (*this); |
1100 | 0 | } |
1101 | 0 | else |
1102 | 0 | { |
1103 | 0 | if (lineOrder != INCREASING_Y && lineOrder != DECREASING_Y) |
1104 | 0 | throw IEX_NAMESPACE::ArgExc ("Invalid line order in image header."); |
1105 | 0 | } |
1106 | | |
1107 | | // |
1108 | | // The compression method must be one of the predefined values. |
1109 | | // |
1110 | | |
1111 | 0 | if (!isValidCompression (this->compression ())) |
1112 | 0 | throw IEX_NAMESPACE::ArgExc ( |
1113 | 0 | "Unknown compression type in image header."); |
1114 | | |
1115 | 0 | if (isDeep) |
1116 | 0 | { |
1117 | 0 | if (!isValidDeepCompression (this->compression ())) |
1118 | 0 | throw IEX_NAMESPACE::ArgExc ( |
1119 | 0 | "Compression type in header not valid for deep data"); |
1120 | 0 | } |
1121 | | |
1122 | | // |
1123 | | // Check the channel list: |
1124 | | // |
1125 | | // If the file is tiled then for each channel, the type must be one of the |
1126 | | // predefined values, and the x and y sampling must both be 1. |
1127 | | // |
1128 | | // x and y sampling must currently also be 1 for deep scanline images |
1129 | | // |
1130 | | // If the file is not tiled then for each channel, the type must be one |
1131 | | // of the predefined values, the x and y coordinates of the data window's |
1132 | | // upper left corner must be divisible by the x and y subsampling factors, |
1133 | | // and the width and height of the data window must be divisible by the |
1134 | | // x and y subsampling factors. |
1135 | | // |
1136 | | |
1137 | 0 | const ChannelList& channels = this->channels (); |
1138 | |
|
1139 | 0 | if (channels.begin () == channels.end ()) |
1140 | 0 | { |
1141 | 0 | THROW ( |
1142 | 0 | IEX_NAMESPACE::ArgExc, "Missing or empty channel list in header"); |
1143 | 0 | } |
1144 | | |
1145 | 0 | if (isTiled || isDeep) |
1146 | 0 | { |
1147 | 0 | for (ChannelList::ConstIterator i = channels.begin (); |
1148 | 0 | i != channels.end (); |
1149 | 0 | ++i) |
1150 | 0 | { |
1151 | 0 | if (i.channel ().type != OPENEXR_IMF_INTERNAL_NAMESPACE::UINT && |
1152 | 0 | i.channel ().type != OPENEXR_IMF_INTERNAL_NAMESPACE::HALF && |
1153 | 0 | i.channel ().type != OPENEXR_IMF_INTERNAL_NAMESPACE::FLOAT) |
1154 | 0 | { |
1155 | 0 | THROW ( |
1156 | 0 | IEX_NAMESPACE::ArgExc, |
1157 | 0 | "Pixel type of \"" << i.name () |
1158 | 0 | << "\" " |
1159 | 0 | "image channel is invalid."); |
1160 | 0 | } |
1161 | | |
1162 | 0 | if (i.channel ().xSampling != 1) |
1163 | 0 | { |
1164 | 0 | THROW ( |
1165 | 0 | IEX_NAMESPACE::ArgExc, |
1166 | 0 | "The x subsampling factor for the " |
1167 | 0 | "\"" << i.name () |
1168 | 0 | << "\" channel " |
1169 | 0 | "is not 1."); |
1170 | 0 | } |
1171 | | |
1172 | 0 | if (i.channel ().ySampling != 1) |
1173 | 0 | { |
1174 | 0 | THROW ( |
1175 | 0 | IEX_NAMESPACE::ArgExc, |
1176 | 0 | "The y subsampling factor for the " |
1177 | 0 | "\"" << i.name () |
1178 | 0 | << "\" channel " |
1179 | 0 | "is not 1."); |
1180 | 0 | } |
1181 | 0 | } |
1182 | 0 | } |
1183 | 0 | else |
1184 | 0 | { |
1185 | 0 | for (ChannelList::ConstIterator i = channels.begin (); |
1186 | 0 | i != channels.end (); |
1187 | 0 | ++i) |
1188 | 0 | { |
1189 | 0 | if (i.channel ().type != OPENEXR_IMF_INTERNAL_NAMESPACE::UINT && |
1190 | 0 | i.channel ().type != OPENEXR_IMF_INTERNAL_NAMESPACE::HALF && |
1191 | 0 | i.channel ().type != OPENEXR_IMF_INTERNAL_NAMESPACE::FLOAT) |
1192 | 0 | { |
1193 | 0 | THROW ( |
1194 | 0 | IEX_NAMESPACE::ArgExc, |
1195 | 0 | "Pixel type of \"" << i.name () |
1196 | 0 | << "\" " |
1197 | 0 | "image channel is invalid."); |
1198 | 0 | } |
1199 | | |
1200 | 0 | if (i.channel ().xSampling < 1) |
1201 | 0 | { |
1202 | 0 | THROW ( |
1203 | 0 | IEX_NAMESPACE::ArgExc, |
1204 | 0 | "The x subsampling factor for the " |
1205 | 0 | "\"" << i.name () |
1206 | 0 | << "\" channel " |
1207 | 0 | "is invalid."); |
1208 | 0 | } |
1209 | | |
1210 | 0 | if (i.channel ().ySampling < 1) |
1211 | 0 | { |
1212 | 0 | THROW ( |
1213 | 0 | IEX_NAMESPACE::ArgExc, |
1214 | 0 | "The y subsampling factor for the " |
1215 | 0 | "\"" << i.name () |
1216 | 0 | << "\" channel " |
1217 | 0 | "is invalid."); |
1218 | 0 | } |
1219 | | |
1220 | 0 | if (dataWindow.min.x % i.channel ().xSampling) |
1221 | 0 | { |
1222 | 0 | THROW ( |
1223 | 0 | IEX_NAMESPACE::ArgExc, |
1224 | 0 | "The minimum x coordinate of the " |
1225 | 0 | "image's data window is not a multiple " |
1226 | 0 | "of the x subsampling factor of " |
1227 | 0 | "the \"" |
1228 | 0 | << i.name () << "\" channel."); |
1229 | 0 | } |
1230 | | |
1231 | 0 | if (dataWindow.min.y % i.channel ().ySampling) |
1232 | 0 | { |
1233 | 0 | THROW ( |
1234 | 0 | IEX_NAMESPACE::ArgExc, |
1235 | 0 | "The minimum y coordinate of the " |
1236 | 0 | "image's data window is not a multiple " |
1237 | 0 | "of the y subsampling factor of " |
1238 | 0 | "the \"" |
1239 | 0 | << i.name () << "\" channel."); |
1240 | 0 | } |
1241 | | |
1242 | 0 | if ((dataWindow.max.x - dataWindow.min.x + 1) % |
1243 | 0 | i.channel ().xSampling) |
1244 | 0 | { |
1245 | 0 | THROW ( |
1246 | 0 | IEX_NAMESPACE::ArgExc, |
1247 | 0 | "Number of pixels per row in the " |
1248 | 0 | "image's data window is not a multiple " |
1249 | 0 | "of the x subsampling factor of " |
1250 | 0 | "the \"" |
1251 | 0 | << i.name () << "\" channel."); |
1252 | 0 | } |
1253 | | |
1254 | 0 | if ((dataWindow.max.y - dataWindow.min.y + 1) % |
1255 | 0 | i.channel ().ySampling) |
1256 | 0 | { |
1257 | 0 | THROW ( |
1258 | 0 | IEX_NAMESPACE::ArgExc, |
1259 | 0 | "Number of pixels per column in the " |
1260 | 0 | "image's data window is not a multiple " |
1261 | 0 | "of the y subsampling factor of " |
1262 | 0 | "the \"" |
1263 | 0 | << i.name () << "\" channel."); |
1264 | 0 | } |
1265 | 0 | } |
1266 | 0 | } |
1267 | 0 | } |
1268 | | |
1269 | | void |
1270 | | Header::setMaxImageSize (int maxWidth, int maxHeight) |
1271 | 2.02k | { |
1272 | 2.02k | exr_set_default_maximum_image_size (maxWidth, maxHeight); |
1273 | 2.02k | } |
1274 | | |
1275 | | void |
1276 | | Header::setMaxTileSize (int maxWidth, int maxHeight) |
1277 | 0 | { |
1278 | 0 | exr_set_default_maximum_tile_size (maxWidth, maxHeight); |
1279 | 0 | } |
1280 | | |
1281 | | void |
1282 | | Header::getMaxImageSize (int& maxWidth, int& maxHeight) |
1283 | 0 | { |
1284 | 0 | exr_get_default_maximum_image_size (&maxWidth, &maxHeight); |
1285 | 0 | } |
1286 | | |
1287 | | void |
1288 | | Header::getMaxTileSize (int& maxWidth, int& maxHeight) |
1289 | 0 | { |
1290 | 0 | exr_get_default_maximum_tile_size (&maxWidth, &maxHeight); |
1291 | 0 | } |
1292 | | |
1293 | | bool |
1294 | | Header::readsNothing () |
1295 | 0 | { |
1296 | 0 | return _readsNothing; |
1297 | 0 | } |
1298 | | |
1299 | | uint64_t |
1300 | | Header::writeTo ( |
1301 | | OPENEXR_IMF_INTERNAL_NAMESPACE::OStream& os, bool isTiled) const |
1302 | 0 | { |
1303 | | // |
1304 | | // Write a "magic number" to identify the file as an image file. |
1305 | | // Write the current file format version number. |
1306 | | // |
1307 | |
|
1308 | 0 | int version = EXR_VERSION; |
1309 | | |
1310 | | // |
1311 | | // Write all attributes. If we have a preview image attribute, |
1312 | | // keep track of its position in the file. |
1313 | | // |
1314 | |
|
1315 | 0 | uint64_t previewPosition = 0; |
1316 | |
|
1317 | 0 | const Attribute* preview = |
1318 | 0 | findTypedAttribute<PreviewImageAttribute> ("preview"); |
1319 | |
|
1320 | 0 | for (ConstIterator i = begin (); i != end (); ++i) |
1321 | 0 | { |
1322 | | // |
1323 | | // Write the attribute's name and type. |
1324 | | // |
1325 | |
|
1326 | 0 | OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::write< |
1327 | 0 | OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (os, i.name ()); |
1328 | 0 | OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::write< |
1329 | 0 | OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> ( |
1330 | 0 | os, i.attribute ().typeName ()); |
1331 | | |
1332 | | // |
1333 | | // Write the size of the attribute value, |
1334 | | // and the value itself. |
1335 | | // |
1336 | |
|
1337 | 0 | StdOSStream oss; |
1338 | 0 | i.attribute ().writeValueTo (oss, version); |
1339 | |
|
1340 | 0 | std::string s = oss.str (); |
1341 | 0 | OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::write< |
1342 | 0 | OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (os, (int) s.length ()); |
1343 | |
|
1344 | 0 | if (&i.attribute () == preview) previewPosition = os.tellp (); |
1345 | |
|
1346 | 0 | os.write (s.data (), int (s.length ())); |
1347 | 0 | } |
1348 | | |
1349 | | // |
1350 | | // Write zero-length attribute name to mark the end of the header. |
1351 | | // |
1352 | |
|
1353 | 0 | OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::write< |
1354 | 0 | OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (os, ""); |
1355 | |
|
1356 | 0 | return previewPosition; |
1357 | 0 | } |
1358 | | |
1359 | | void |
1360 | | Header::readFrom (OPENEXR_IMF_INTERNAL_NAMESPACE::IStream& is, int& version) |
1361 | 0 | { |
1362 | | // |
1363 | | // Read all attributes. |
1364 | | // |
1365 | |
|
1366 | 0 | int attrCount = 0; |
1367 | |
|
1368 | 0 | while (true) |
1369 | 0 | { |
1370 | | // |
1371 | | // Read the name of the attribute. |
1372 | | // A zero-length attribute name indicates the end of the header. |
1373 | | // |
1374 | |
|
1375 | 0 | char name[Name::SIZE]; |
1376 | 0 | OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read< |
1377 | 0 | OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> ( |
1378 | 0 | is, Name::MAX_LENGTH, name); |
1379 | |
|
1380 | 0 | if (name[0] == 0) |
1381 | 0 | { |
1382 | 0 | if (attrCount == 0) |
1383 | 0 | _readsNothing = true; |
1384 | 0 | else |
1385 | 0 | _readsNothing = false; |
1386 | 0 | break; |
1387 | 0 | } |
1388 | | |
1389 | 0 | attrCount++; |
1390 | |
|
1391 | 0 | checkIsNullTerminated (name, "attribute name"); |
1392 | | |
1393 | | // |
1394 | | // Read the attribute type and the size of the attribute value. |
1395 | | // |
1396 | |
|
1397 | 0 | char typeName[Name::SIZE]; |
1398 | 0 | int size; |
1399 | |
|
1400 | 0 | OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read< |
1401 | 0 | OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> ( |
1402 | 0 | is, Name::MAX_LENGTH, typeName); |
1403 | 0 | checkIsNullTerminated (typeName, "attribute type name"); |
1404 | 0 | OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read< |
1405 | 0 | OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, size); |
1406 | |
|
1407 | 0 | if (size < 0) |
1408 | 0 | { |
1409 | 0 | throw IEX_NAMESPACE::InputExc ( |
1410 | 0 | "Invalid size field in header attribute"); |
1411 | 0 | } |
1412 | | |
1413 | 0 | AttributeMap::iterator i = _map.find (name); |
1414 | |
|
1415 | 0 | if (i != _map.end ()) |
1416 | 0 | { |
1417 | | // |
1418 | | // The attribute already exists (for example, |
1419 | | // because it is a predefined attribute). |
1420 | | // Read the attribute's new value from the file. |
1421 | | // |
1422 | |
|
1423 | 0 | if (strncmp (i->second->typeName (), typeName, sizeof (typeName))) |
1424 | 0 | THROW ( |
1425 | 0 | IEX_NAMESPACE::InputExc, |
1426 | 0 | "Unexpected type for image attribute " |
1427 | 0 | "\"" << name |
1428 | 0 | << "\"."); |
1429 | | |
1430 | 0 | i->second->readValueFrom (is, size, version); |
1431 | 0 | } |
1432 | 0 | else |
1433 | 0 | { |
1434 | | // |
1435 | | // The new attribute does not exist yet. |
1436 | | // If the attribute type is of a known type, |
1437 | | // read the attribute value. If the attribute |
1438 | | // is of an unknown type, read its value and |
1439 | | // store it as an OpaqueAttribute. |
1440 | | // |
1441 | |
|
1442 | 0 | Attribute* attr; |
1443 | |
|
1444 | 0 | if (Attribute::knownType (typeName)) |
1445 | 0 | attr = Attribute::newAttribute (typeName); |
1446 | 0 | else |
1447 | 0 | attr = new OpaqueAttribute (typeName); |
1448 | |
|
1449 | 0 | try |
1450 | 0 | { |
1451 | 0 | attr->readValueFrom (is, size, version); |
1452 | 0 | _map[name] = attr; |
1453 | 0 | } |
1454 | 0 | catch (...) |
1455 | 0 | { |
1456 | 0 | delete attr; |
1457 | 0 | throw; |
1458 | 0 | } |
1459 | 0 | } |
1460 | 0 | } |
1461 | 0 | } |
1462 | | |
1463 | | void |
1464 | | staticInitialize () |
1465 | 42.5k | { |
1466 | 42.5k | #if ILMTHREAD_THREADING_ENABLED |
1467 | 42.5k | static std::mutex criticalSection; |
1468 | 42.5k | std::lock_guard<std::mutex> lock (criticalSection); |
1469 | 42.5k | #endif |
1470 | 42.5k | static bool initialized = false; |
1471 | | |
1472 | 42.5k | if (!initialized) |
1473 | 1 | { |
1474 | | // |
1475 | | // One-time initialization -- register |
1476 | | // some predefined attribute types. |
1477 | | // |
1478 | | |
1479 | 1 | Box2fAttribute::registerAttributeType (); |
1480 | 1 | Box2iAttribute::registerAttributeType (); |
1481 | 1 | ChannelListAttribute::registerAttributeType (); |
1482 | 1 | CompressionAttribute::registerAttributeType (); |
1483 | 1 | ChromaticitiesAttribute::registerAttributeType (); |
1484 | 1 | DeepImageStateAttribute::registerAttributeType (); |
1485 | 1 | DoubleAttribute::registerAttributeType (); |
1486 | 1 | EnvmapAttribute::registerAttributeType (); |
1487 | 1 | FloatAttribute::registerAttributeType (); |
1488 | 1 | FloatVectorAttribute::registerAttributeType (); |
1489 | 1 | IntAttribute::registerAttributeType (); |
1490 | 1 | KeyCodeAttribute::registerAttributeType (); |
1491 | 1 | LineOrderAttribute::registerAttributeType (); |
1492 | 1 | M33dAttribute::registerAttributeType (); |
1493 | 1 | M33fAttribute::registerAttributeType (); |
1494 | 1 | M44dAttribute::registerAttributeType (); |
1495 | 1 | M44fAttribute::registerAttributeType (); |
1496 | 1 | PreviewImageAttribute::registerAttributeType (); |
1497 | 1 | RationalAttribute::registerAttributeType (); |
1498 | 1 | StringAttribute::registerAttributeType (); |
1499 | 1 | StringVectorAttribute::registerAttributeType (); |
1500 | 1 | TileDescriptionAttribute::registerAttributeType (); |
1501 | 1 | TimeCodeAttribute::registerAttributeType (); |
1502 | 1 | V2dAttribute::registerAttributeType (); |
1503 | 1 | V2fAttribute::registerAttributeType (); |
1504 | 1 | V2iAttribute::registerAttributeType (); |
1505 | 1 | V3dAttribute::registerAttributeType (); |
1506 | 1 | V3fAttribute::registerAttributeType (); |
1507 | 1 | V3iAttribute::registerAttributeType (); |
1508 | 1 | IDManifestAttribute::registerAttributeType (); |
1509 | | |
1510 | | // |
1511 | | // Register functions, for example specialized functions |
1512 | | // for different CPU architectures. |
1513 | | // |
1514 | | |
1515 | 1 | initialized = true; |
1516 | 1 | } |
1517 | 42.5k | } |
1518 | | |
1519 | | OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT |