1 ///////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (c) 2011, Industrial Light & Magic, a division of Lucas
4 // Digital Ltd. LLC
5 //
6 // All rights reserved.
7 //
8 // Redistribution and use in source and binary forms, with or without
9 // modification, are permitted provided that the following conditions are
10 // met:
11 // * Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13 // * Redistributions in binary form must reproduce the above
14 // copyright notice, this list of conditions and the following disclaimer
15 // in the documentation and/or other materials provided with the
16 // distribution.
17 // * Neither the name of Industrial Light & Magic nor the names of
18 // its contributors may be used to endorse or promote products derived
19 // from this software without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 //
33 ///////////////////////////////////////////////////////////////////////////
34
35 #include "ImfMultiPartOutputFile.h"
36 #include "ImfBoxAttribute.h"
37 #include "ImfFloatAttribute.h"
38 #include "ImfTimeCodeAttribute.h"
39 #include "ImfChromaticitiesAttribute.h"
40 #include "ImfOutputPartData.h"
41 #include "ImfPartType.h"
42 #include "ImfOutputFile.h"
43 #include "ImfTiledOutputFile.h"
44 #include "ImfThreading.h"
45 #include "IlmThreadMutex.h"
46 #include "ImfMisc.h"
47 #include "ImfStdIO.h"
48 #include "ImfDeepScanLineOutputFile.h"
49 #include "ImfDeepTiledOutputFile.h"
50 #include "ImfOutputStreamMutex.h"
51
52 #include "ImfNamespace.h"
53 #include <Iex.h>
54
55
56 #include <set>
57
58
59 OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER
60
61 using IMATH_NAMESPACE::Box2i;
62 using ILMTHREAD_NAMESPACE::Lock;
63
64
65 using std::vector;
66 using std::map;
67 using std::set;
68
69
70 struct MultiPartOutputFile::Data: public OutputStreamMutex
71 {
72 vector<OutputPartData*> parts; // Contains data to initialize Output files.
73 bool deleteStream; // If we should delete the stream when destruction.
74 int numThreads; // The number of threads.
75 std::map<int, GenericOutputFile*> _outputFiles;
76 std::vector<Header> _headers;
77
78
79 void headerNameUniquenessCheck (const std::vector<Header> &headers);
80
81 void writeHeadersToFile (const std::vector<Header> &headers);
82
83 void writeChunkTableOffsets (std::vector<OutputPartData*> &parts);
84
85
86 //-------------------------------------
87 // ensure that _headers is valid: called by constructors
88 //-------------------------------------
89 void do_header_sanity_checks(bool overrideSharedAttributes);
90
91 // ------------------------------------------------
92 // Given a source header, we copy over all the 'shared attributes' to
93 // the destination header and remove any conflicting ones.
94 // ------------------------------------------------
95 void overrideSharedAttributesValues (const Header & src,
96 Header & dst);
97
98 // ------------------------------------------------
99 // Given a source header, we check the destination header for any
100 // attributes that are part of the shared attribute set. For attributes
101 // present in both we check the values. For attribute present in
102 // destination but absent in source we return false.
103 // For attributes present in src but missing from dst we return false
104 // and add the attribute to dst.
105 // We return false for all other cases.
106 // If we return true then we also populate the conflictingAttributes
107 // vector with the names of the attributes that failed the above.
108 // ------------------------------------------------
109 bool checkSharedAttributesValues (const Header & src,
110 const Header & dst,
111 std::vector<std::string> & conflictingAttributes) const;
DataMultiPartOutputFile::Data112 Data (bool deleteStream, int numThreads):
113 OutputStreamMutex(),
114 deleteStream (deleteStream),
115 numThreads (numThreads)
116 {
117 }
118
119
~DataMultiPartOutputFile::Data120 ~Data()
121 {
122 if (deleteStream) delete os;
123
124 for (size_t i = 0; i < parts.size(); i++)
125 delete parts[i];
126 }
127 };
128
129 void
do_header_sanity_checks(bool overrideSharedAttributes)130 MultiPartOutputFile::Data::do_header_sanity_checks(bool overrideSharedAttributes)
131 {
132 size_t parts = _headers.size();
133 if (parts == 0)
134 throw IEX_NAMESPACE::ArgExc ("Empty header list.");
135
136 bool isMultiPart = (parts > 1);
137
138 //
139 // Do part 0 checks first.
140 //
141
142 _headers[0].sanityCheck (_headers[0].hasTileDescription(), isMultiPart);
143
144
145 if (isMultiPart)
146 {
147 // multipart files must contain a chunkCount attribute
148 _headers[0].setChunkCount(getChunkOffsetTableSize(_headers[0],true));
149
150 for (size_t i = 1; i < parts; i++)
151 {
152 if (_headers[i].hasType() == false)
153 throw IEX_NAMESPACE::ArgExc ("Every header in a multipart file should have a type");
154
155
156 _headers[i].setChunkCount(getChunkOffsetTableSize(_headers[i],true));
157 _headers[i].sanityCheck (_headers[i].hasTileDescription(), isMultiPart);
158
159
160 if (overrideSharedAttributes)
161 overrideSharedAttributesValues(_headers[0],_headers[i]);
162 else
163 {
164 std::vector<std::string> conflictingAttributes;
165 bool valid =checkSharedAttributesValues (_headers[0],
166 _headers[i],
167 conflictingAttributes);
168 if (valid)
169 {
170 string excMsg("Conflicting attributes found for header :: ");
171 excMsg += _headers[i].name();
172 for (size_t i=0; i<conflictingAttributes.size(); i++)
173 excMsg += " '" + conflictingAttributes[i] + "' ";
174
175 THROW (IEX_NAMESPACE::ArgExc, excMsg);
176 }
177 }
178 }
179
180 headerNameUniquenessCheck(_headers);
181
182 }else{
183
184 // add chunk count offset to single part data (if not an image)
185
186 if (_headers[0].hasType() && isImage(_headers[0].type()) == false)
187 {
188 _headers[0].setChunkCount(getChunkOffsetTableSize(_headers[0],true));
189 }
190
191 }
192 }
193
194
MultiPartOutputFile(const char fileName[],const Header * headers,int parts,bool overrideSharedAttributes,int numThreads)195 MultiPartOutputFile::MultiPartOutputFile (const char fileName[],
196 const Header * headers,
197 int parts,
198 bool overrideSharedAttributes,
199 int numThreads)
200 :
201 _data (new Data (true, numThreads))
202 {
203 // grab headers
204 _data->_headers.resize(parts);
205
206 for(int i=0;i<parts;i++)
207 {
208 _data->_headers[i]=headers[i];
209 }
210 try
211 {
212
213 _data->do_header_sanity_checks(overrideSharedAttributes);
214
215 //
216 // Build parts and write headers and offset tables to file.
217 //
218
219 _data->os = new StdOFStream (fileName);
220 for (size_t i = 0; i < _data->_headers.size(); i++)
221 _data->parts.push_back( new OutputPartData(_data, _data->_headers[i], i, numThreads, parts>1 ) );
222
223 writeMagicNumberAndVersionField(*_data->os, &_data->_headers[0],_data->_headers.size());
224 _data->writeHeadersToFile(_data->_headers);
225 _data->writeChunkTableOffsets(_data->parts);
226 }
227 catch (IEX_NAMESPACE::BaseExc &e)
228 {
229 delete _data;
230
231 REPLACE_EXC (e, "Cannot open image file "
232 "\"" << fileName << "\". " << e);
233 throw;
234 }
235 catch (...)
236 {
237 delete _data;
238 throw;
239 }
240 }
241
MultiPartOutputFile(OStream & os,const Header * headers,int parts,bool overrideSharedAttributes,int numThreads)242 MultiPartOutputFile::MultiPartOutputFile(OStream& os,
243 const Header* headers,
244 int parts,
245 bool overrideSharedAttributes,
246 int numThreads):
247 _data(new Data(false,numThreads))
248 {
249 // grab headers
250 _data->_headers.resize(parts);
251 _data->os=&os;
252
253 for(int i=0;i<parts;i++)
254 {
255 _data->_headers[i]=headers[i];
256 }
257 try
258 {
259
260 _data->do_header_sanity_checks(overrideSharedAttributes);
261
262 //
263 // Build parts and write headers and offset tables to file.
264 //
265
266 for (size_t i = 0; i < _data->_headers.size(); i++)
267 _data->parts.push_back( new OutputPartData(_data, _data->_headers[i], i, numThreads, parts>1 ) );
268
269 writeMagicNumberAndVersionField(*_data->os, &_data->_headers[0],_data->_headers.size());
270 _data->writeHeadersToFile(_data->_headers);
271 _data->writeChunkTableOffsets(_data->parts);
272 }
273 catch (IEX_NAMESPACE::BaseExc &e)
274 {
275 delete _data;
276
277 REPLACE_EXC (e, "Cannot open image stream "
278 "\"" << os.fileName() << "\". " << e);
279 throw;
280 }
281 catch (...)
282 {
283 delete _data;
284 throw;
285 }
286 }
287
288
289 const Header &
header(int n) const290 MultiPartOutputFile::header(int n) const
291 {
292 if(n<0 || n>int(_data->_headers.size()))
293 {
294 throw IEX_NAMESPACE::ArgExc("MultiPartOutputFile::header called with invalid part number");
295 }
296 return _data->_headers[n];
297 }
298
299 int
parts() const300 MultiPartOutputFile::parts() const
301 {
302 return _data->_headers.size();
303 }
304
305
~MultiPartOutputFile()306 MultiPartOutputFile::~MultiPartOutputFile ()
307 {
308 for (map<int, GenericOutputFile*>::iterator it = _data->_outputFiles.begin();
309 it != _data->_outputFiles.end(); it++)
310 {
311 delete it->second;
312 }
313
314 delete _data;
315 }
316
317 template <class T>
318 T*
getOutputPart(int partNumber)319 MultiPartOutputFile::getOutputPart(int partNumber)
320 {
321 Lock lock(*_data);
322 if (_data->_outputFiles.find(partNumber) == _data->_outputFiles.end())
323 {
324 T* file = new T(_data->parts[partNumber]);
325 _data->_outputFiles.insert(std::make_pair(partNumber, (GenericOutputFile*) file));
326 return file;
327 }
328 else return (T*) _data->_outputFiles[partNumber];
329 }
330
331 // instance above function for all four types
332 template OutputFile* MultiPartOutputFile::getOutputPart<OutputFile>(int);
333 template TiledOutputFile * MultiPartOutputFile::getOutputPart<TiledOutputFile>(int);
334 template DeepScanLineOutputFile * MultiPartOutputFile::getOutputPart<DeepScanLineOutputFile> (int);
335 template DeepTiledOutputFile * MultiPartOutputFile::getOutputPart<DeepTiledOutputFile> (int);
336
337
338
339 void
overrideSharedAttributesValues(const Header & src,Header & dst)340 MultiPartOutputFile::Data::overrideSharedAttributesValues(const Header & src, Header & dst)
341 {
342 //
343 // Display Window
344 //
345 const Box2iAttribute * displayWindow =
346 src.findTypedAttribute<Box2iAttribute> ("displayWindow");
347
348 if (displayWindow)
349 dst.insert ("displayWindow", *displayWindow);
350 else
351 dst.erase ("displayWindow");
352
353
354 //
355 // Pixel Aspect Ratio
356 //
357 const FloatAttribute * pixelAspectRatio =
358 src.findTypedAttribute<FloatAttribute> ("pixelAspectRatio");
359
360 if (pixelAspectRatio)
361 dst.insert ("pixelAspectRatio", *pixelAspectRatio);
362 else
363 dst.erase ("pixelAspectRatio");
364
365
366 //
367 // Timecode
368 //
369 const TimeCodeAttribute * timeCode =
370 src.findTypedAttribute<TimeCodeAttribute> ("timecode");
371
372 if (timeCode)
373 dst.insert ("timecode", *timeCode);
374 else
375 dst.erase ("timecode");
376
377
378 //
379 // Chromaticities
380 //
381 const ChromaticitiesAttribute * chromaticities =
382 src.findTypedAttribute<ChromaticitiesAttribute> ("chromaticities");
383
384 if (chromaticities)
385 dst.insert ("chromaticities", *chromaticities);
386 else
387 dst.erase ("chromaticities");
388
389 }
390
391
392 bool
checkSharedAttributesValues(const Header & src,const Header & dst,vector<string> & conflictingAttributes) const393 MultiPartOutputFile::Data::checkSharedAttributesValues(const Header & src,
394 const Header & dst,
395 vector<string> & conflictingAttributes) const
396 {
397 bool conflict = false;
398
399 //
400 // Display Window
401 //
402 if (src.displayWindow() != dst.displayWindow())
403 {
404 conflict = true;
405 conflictingAttributes.push_back ("displayWindow");
406 }
407
408
409 //
410 // Pixel Aspect Ratio
411 //
412 if (src.pixelAspectRatio() != dst.pixelAspectRatio())
413 {
414 conflict = true;
415 conflictingAttributes.push_back ("pixelAspectRatio");
416 }
417
418
419 //
420 // Timecode
421 //
422 const TimeCodeAttribute * srcTimeCode = src.findTypedAttribute<
423 TimeCodeAttribute> (TimeCodeAttribute::staticTypeName());
424 const TimeCodeAttribute * dstTimeCode = dst.findTypedAttribute<
425 TimeCodeAttribute> (TimeCodeAttribute::staticTypeName());
426
427 if (dstTimeCode)
428 {
429 if ((srcTimeCode && (srcTimeCode->value() != dstTimeCode->value())) ||
430 (!srcTimeCode))
431 {
432 conflict = true;
433 conflictingAttributes.push_back (TimeCodeAttribute::staticTypeName());
434 }
435 }
436
437 //
438 // Chromaticities
439 //
440 const ChromaticitiesAttribute * srcChrom = src.findTypedAttribute<
441 ChromaticitiesAttribute> (ChromaticitiesAttribute::staticTypeName());
442 const ChromaticitiesAttribute * dstChrom = dst.findTypedAttribute<
443 ChromaticitiesAttribute> (ChromaticitiesAttribute::staticTypeName());
444
445 if (dstChrom)
446 {
447 if ( (srcChrom && (srcChrom->value() != dstChrom->value())) ||
448 (!srcChrom))
449 {
450 conflict = true;
451 conflictingAttributes.push_back (ChromaticitiesAttribute::staticTypeName());
452 }
453 }
454
455 return conflict;
456 }
457
458
459 void
headerNameUniquenessCheck(const vector<Header> & headers)460 MultiPartOutputFile::Data::headerNameUniquenessCheck (const vector<Header> &headers)
461 {
462 set<string> names;
463 for (size_t i = 0; i < headers.size(); i++)
464 {
465 if (names.find(headers[i].name()) != names.end())
466 throw IEX_NAMESPACE::ArgExc ("Each part should have a unique name.");
467 names.insert(headers[i].name());
468 }
469 }
470
471 void
writeHeadersToFile(const vector<Header> & headers)472 MultiPartOutputFile::Data::writeHeadersToFile (const vector<Header> &headers)
473 {
474 for (size_t i = 0; i < headers.size(); i++)
475 {
476
477 // (TODO) consider deep files' preview images here.
478 if (headers[i].type() == TILEDIMAGE)
479 parts[i]->previewPosition = headers[i].writeTo(*os, true);
480 else
481 parts[i]->previewPosition = headers[i].writeTo(*os, false);
482 }
483
484 //
485 // If a multipart file, write zero-length attribute name to mark the end of all headers.
486 //
487
488 if (headers.size() !=1)
489 OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::write <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (*os, "");
490 }
491
492 void
writeChunkTableOffsets(vector<OutputPartData * > & parts)493 MultiPartOutputFile::Data::writeChunkTableOffsets (vector<OutputPartData*> &parts)
494 {
495 for (size_t i = 0; i < parts.size(); i++)
496 {
497 int chunkTableSize = getChunkOffsetTableSize(parts[i]->header,false);
498
499 Int64 pos = os->tellp();
500
501 if (pos == -1)
502 IEX_NAMESPACE::throwErrnoExc ("Cannot determine current file position (%T).");
503
504 parts[i]->chunkOffsetTablePosition = os->tellp();
505
506 //
507 // Fill in empty data for now. We'll write actual offsets during destruction.
508 //
509
510 for (int j = 0; j < chunkTableSize; j++)
511 {
512 Int64 empty = 0;
513 OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::write <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (*os, empty);
514 }
515 }
516 }
517
518
519 OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT
520