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