1 /*
2 Copyright (c) 2003-2010 Sony Pictures Imageworks Inc., et al.
3 All Rights Reserved.
4 
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are
7 met:
8 * Redistributions of source code must retain the above copyright
9   notice, this list of conditions and the following disclaimer.
10 * Redistributions in binary form must reproduce the above copyright
11   notice, this list of conditions and the following disclaimer in the
12   documentation and/or other materials provided with the distribution.
13 * Neither the name of Sony Pictures Imageworks nor the names of its
14   contributors may be used to endorse or promote products derived from
15   this software without specific prior written permission.
16 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28 
29 #include <OpenColorIO/OpenColorIO.h>
30 
31 #include "FileTransform.h"
32 #include "Logging.h"
33 #include "Mutex.h"
34 #include "NoOps.h"
35 #include "PathUtils.h"
36 #include "pystring/pystring.h"
37 
38 #include <fstream>
39 #include <map>
40 #include <sstream>
41 
42 OCIO_NAMESPACE_ENTER
43 {
44     FileTransformRcPtr FileTransform::Create()
45     {
46         return FileTransformRcPtr(new FileTransform(), &deleter);
47     }
48 
49     void FileTransform::deleter(FileTransform* t)
50     {
51         delete t;
52     }
53 
54 
55     class FileTransform::Impl
56     {
57     public:
58         TransformDirection dir_;
59         std::string src_;
60         std::string cccid_;
61         Interpolation interp_;
62 
63         Impl() :
64             dir_(TRANSFORM_DIR_FORWARD),
65             interp_(INTERP_UNKNOWN)
66         { }
67 
68         ~Impl()
69         { }
70 
71         Impl& operator= (const Impl & rhs)
72         {
73             dir_ = rhs.dir_;
74             src_ = rhs.src_;
75             cccid_ = rhs.cccid_;
76             interp_ = rhs.interp_;
77             return *this;
78         }
79     };
80 
81     ///////////////////////////////////////////////////////////////////////////
82 
83 
84     FileTransform::FileTransform()
85         : m_impl(new FileTransform::Impl)
86     {
87     }
88 
89     TransformRcPtr FileTransform::createEditableCopy() const
90     {
91         FileTransformRcPtr transform = FileTransform::Create();
92         *(transform->m_impl) = *m_impl;
93         return transform;
94     }
95 
96     FileTransform::~FileTransform()
97     {
98         delete m_impl;
99         m_impl = NULL;
100     }
101 
102     FileTransform& FileTransform::operator= (const FileTransform & rhs)
103     {
104         *m_impl = *rhs.m_impl;
105         return *this;
106     }
107 
108     TransformDirection FileTransform::getDirection() const
109     {
110         return getImpl()->dir_;
111     }
112 
113     void FileTransform::setDirection(TransformDirection dir)
114     {
115         getImpl()->dir_ = dir;
116     }
117 
118     const char * FileTransform::getSrc() const
119     {
120         return getImpl()->src_.c_str();
121     }
122 
123     void FileTransform::setSrc(const char * src)
124     {
125         getImpl()->src_ = src;
126     }
127 
128     const char * FileTransform::getCCCId() const
129     {
130         return getImpl()->cccid_.c_str();
131     }
132 
133     void FileTransform::setCCCId(const char * cccid)
134     {
135         getImpl()->cccid_ = cccid;
136     }
137 
138     Interpolation FileTransform::getInterpolation() const
139     {
140         return getImpl()->interp_;
141     }
142 
143     void FileTransform::setInterpolation(Interpolation interp)
144     {
145         getImpl()->interp_ = interp;
146     }
147 
148     int FileTransform::getNumFormats()
149     {
150         return FormatRegistry::GetInstance().getNumFormats(FORMAT_CAPABILITY_READ);
151     }
152 
153     const char * FileTransform::getFormatNameByIndex(int index)
154     {
155         return FormatRegistry::GetInstance().getFormatNameByIndex(FORMAT_CAPABILITY_READ, index);
156     }
157 
158     const char * FileTransform::getFormatExtensionByIndex(int index)
159     {
160         return FormatRegistry::GetInstance().getFormatExtensionByIndex(FORMAT_CAPABILITY_READ, index);
161     }
162 
163     std::ostream& operator<< (std::ostream& os, const FileTransform& t)
164     {
165         os << "<FileTransform ";
166         os << "direction=" << TransformDirectionToString(t.getDirection()) << ", ";
167         os << "interpolation=" << InterpolationToString(t.getInterpolation()) << ", ";
168         os << "src=" << t.getSrc() << ", ";
169         os << "cccid=" << t.getCCCId();
170         os << ">";
171 
172         return os;
173     }
174 
175     ///////////////////////////////////////////////////////////////////////////
176 
177     // NOTE: You must be mindful when editing this function.
178     //       to be resiliant to the static initialization order 'fiasco'
179     //
180     //       See
181     //       http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14
182     //       http://stackoverflow.com/questions/335369/finding-c-static-initialization-order-problems
183     //       for more info.
184 
185     namespace
186     {
187         FormatRegistry* g_formatRegistry = NULL;
188         Mutex g_formatRegistryLock;
189     }
190 
191     FormatRegistry & FormatRegistry::GetInstance()
192     {
193         AutoMutex lock(g_formatRegistryLock);
194 
195         if(!g_formatRegistry)
196         {
197             g_formatRegistry = new FormatRegistry();
198         }
199 
200         return *g_formatRegistry;
201     }
202 
203     FormatRegistry::FormatRegistry()
204     {
205         registerFileFormat(CreateFileFormat3DL());
206         registerFileFormat(CreateFileFormatCCC());
207         registerFileFormat(CreateFileFormatCDL());
208         registerFileFormat(CreateFileFormatCC());
209         registerFileFormat(CreateFileFormatCSP());
210         registerFileFormat(CreateFileFormatHDL());
211         registerFileFormat(CreateFileFormatIridasItx());
212         registerFileFormat(CreateFileFormatIridasCube());
213         registerFileFormat(CreateFileFormatIridasLook());
214         registerFileFormat(CreateFileFormatPandora());
215         registerFileFormat(CreateFileFormatSpi1D());
216         registerFileFormat(CreateFileFormatSpi3D());
217         registerFileFormat(CreateFileFormatSpiMtx());
218         registerFileFormat(CreateFileFormatTruelight());
219         registerFileFormat(CreateFileFormatVF());
220     }
221 
222     FormatRegistry::~FormatRegistry()
223     {
224     }
225 
226     FileFormat* FormatRegistry::getFileFormatByName(const std::string & name) const
227     {
228         FileFormatMap::const_iterator iter = m_formatsByName.find(
229             pystring::lower(name));
230         if(iter != m_formatsByName.end())
231             return iter->second;
232         return NULL;
233     }
234 
235     FileFormat* FormatRegistry::getFileFormatForExtension(const std::string & extension) const
236     {
237         FileFormatMap::const_iterator iter = m_formatsByExtension.find(
238             pystring::lower(extension));
239         if(iter != m_formatsByExtension.end())
240             return iter->second;
241         return NULL;
242     }
243 
244     void FormatRegistry::registerFileFormat(FileFormat* format)
245     {
246         FormatInfoVec formatInfoVec;
247         format->GetFormatInfo(formatInfoVec);
248 
249         if(formatInfoVec.empty())
250         {
251             std::ostringstream os;
252             os << "FileFormat Registry error. ";
253             os << "A file format did not provide the required format info.";
254             throw Exception(os.str().c_str());
255         }
256 
257         for(unsigned int i=0; i<formatInfoVec.size(); ++i)
258         {
259             if(formatInfoVec[i].capabilities == FORMAT_CAPABILITY_NONE)
260             {
261                 std::ostringstream os;
262                 os << "FileFormat Registry error. ";
263                 os << "A file format does not define either reading or writing.";
264                 throw Exception(os.str().c_str());
265             }
266 
267             if(getFileFormatByName(formatInfoVec[i].name))
268             {
269                 std::ostringstream os;
270                 os << "Cannot register multiple file formats named, '";
271                 os << formatInfoVec[i].name << "'.";
272                 throw Exception(os.str().c_str());
273             }
274 
275             m_formatsByName[formatInfoVec[i].name] = format;
276 
277             // For now, dont worry if multiple formats register the same extension
278             // TODO: keep track of all of em! (make the value a vector)
279             m_formatsByExtension[formatInfoVec[i].extension] = format;
280 
281             if(formatInfoVec[i].capabilities & FORMAT_CAPABILITY_READ)
282             {
283                 m_readFormatNames.push_back(formatInfoVec[i].name);
284                 m_readFormatExtensions.push_back(formatInfoVec[i].extension);
285             }
286 
287             if(formatInfoVec[i].capabilities & FORMAT_CAPABILITY_WRITE)
288             {
289                 m_writeFormatNames.push_back(formatInfoVec[i].name);
290                 m_writeFormatExtensions.push_back(formatInfoVec[i].extension);
291             }
292         }
293 
294         m_rawFormats.push_back(format);
295     }
296 
297     int FormatRegistry::getNumRawFormats() const
298     {
299         return static_cast<int>(m_rawFormats.size());
300     }
301 
302     FileFormat* FormatRegistry::getRawFormatByIndex(int index) const
303     {
304         if(index<0 || index>=getNumRawFormats())
305         {
306             return NULL;
307         }
308 
309         return m_rawFormats[index];
310     }
311 
312     int FormatRegistry::getNumFormats(int capability) const
313     {
314         if(capability == FORMAT_CAPABILITY_READ)
315         {
316             return static_cast<int>(m_readFormatNames.size());
317         }
318         else if(capability == FORMAT_CAPABILITY_WRITE)
319         {
320             return static_cast<int>(m_writeFormatNames.size());
321         }
322         return 0;
323     }
324 
325     const char * FormatRegistry::getFormatNameByIndex(int capability, int index) const
326     {
327         if(capability == FORMAT_CAPABILITY_READ)
328         {
329             if(index<0 || index>=static_cast<int>(m_readFormatNames.size()))
330             {
331                 return "";
332             }
333             return m_readFormatNames[index].c_str();
334         }
335         else if(capability == FORMAT_CAPABILITY_WRITE)
336         {
337             if(index<0 || index>=static_cast<int>(m_readFormatNames.size()))
338             {
339                 return "";
340             }
341             return m_writeFormatNames[index].c_str();
342         }
343         return "";
344     }
345 
346     const char * FormatRegistry::getFormatExtensionByIndex(int capability, int index) const
347     {
348         if(capability == FORMAT_CAPABILITY_READ)
349         {
350             if(index<0 || index>=static_cast<int>(m_readFormatExtensions.size()))
351             {
352                 return "";
353             }
354             return m_readFormatExtensions[index].c_str();
355         }
356         else if(capability == FORMAT_CAPABILITY_WRITE)
357         {
358             if(index<0 || index>=static_cast<int>(m_writeFormatExtensions.size()))
359             {
360                 return "";
361             }
362             return m_writeFormatExtensions[index].c_str();
363         }
364         return "";
365     }
366 
367     ///////////////////////////////////////////////////////////////////////////
368 
369     FileFormat::~FileFormat()
370     {
371 
372     }
373 
374     std::string FileFormat::getName() const
375     {
376         FormatInfoVec infoVec;
377         GetFormatInfo(infoVec);
378         if(infoVec.size()>0)
379         {
380             return infoVec[0].name;
381         }
382         return "Unknown Format";
383     }
384 
385 
386 
387     void FileFormat::Write(const Baker & /*baker*/,
388                            const std::string & formatName,
389                            std::ostream & /*ostream*/) const
390     {
391         std::ostringstream os;
392         os << "Format " << formatName << " does not support writing.";
393         throw Exception(os.str().c_str());
394     }
395 
396     namespace
397     {
398 
399         void LoadFileUncached(FileFormat * & returnFormat,
400             CachedFileRcPtr & returnCachedFile,
401             const std::string & filepath)
402         {
403             returnFormat = NULL;
404 
405             {
406                 std::ostringstream os;
407                 os << "Opening " << filepath;
408                 LogDebug(os.str());
409             }
410 
411             // Open the filePath
412             std::ifstream filestream;
413             filestream.open(filepath.c_str(), std::ios_base::in);
414             if (!filestream.good())
415             {
416                 std::ostringstream os;
417                 os << "The specified FileTransform srcfile, '";
418                 os << filepath <<"', could not be opened. ";
419                 os << "Please confirm the file exists with appropriate read";
420                 os << " permissions.";
421                 throw Exception(os.str().c_str());
422             }
423 
424             // Try the initial format.
425             std::string primaryErrorText;
426             std::string root, extension;
427             pystring::os::path::splitext(root, extension, filepath);
428             extension = pystring::replace(extension,".","",1); // remove the leading '.'
429 
430             FormatRegistry & formatRegistry = FormatRegistry::GetInstance();
431 
432             FileFormat * primaryFormat =
433                 formatRegistry.getFileFormatForExtension(extension);
434             if(primaryFormat)
435             {
436                 try
437                 {
438                     CachedFileRcPtr cachedFile = primaryFormat->Read(filestream);
439 
440                     if(IsDebugLoggingEnabled())
441                     {
442                         std::ostringstream os;
443                         os << "    Loaded primary format ";
444                         os << primaryFormat->getName();
445                         LogDebug(os.str());
446                     }
447 
448                     returnFormat = primaryFormat;
449                     returnCachedFile = cachedFile;
450                     return;
451                 }
452                 catch(std::exception & e)
453                 {
454                     primaryErrorText = e.what();
455 
456                     if(IsDebugLoggingEnabled())
457                     {
458                         std::ostringstream os;
459                         os << "    Failed primary format ";
460                         os << primaryFormat->getName();
461                         os << ":  " << e.what();
462                         LogDebug(os.str());
463                     }
464                 }
465             }
466 
467             filestream.clear();
468             filestream.seekg( std::ifstream::beg );
469 
470             // If this fails, try all other formats
471             CachedFileRcPtr cachedFile;
472             FileFormat * altFormat = NULL;
473 
474             for(int findex = 0;
475                 findex<formatRegistry.getNumRawFormats();
476                 ++findex)
477             {
478                 altFormat = formatRegistry.getRawFormatByIndex(findex);
479 
480                 // Dont bother trying the primaryFormat twice.
481                 if(altFormat == primaryFormat) continue;
482 
483                 try
484                 {
485                     cachedFile = altFormat->Read(filestream);
486 
487                     if(IsDebugLoggingEnabled())
488                     {
489                         std::ostringstream os;
490                         os << "    Loaded alt format ";
491                         os << altFormat->getName();
492                         LogDebug(os.str());
493                     }
494 
495                     returnFormat = altFormat;
496                     returnCachedFile = cachedFile;
497                     return;
498                 }
499                 catch(std::exception & e)
500                 {
501                     if(IsDebugLoggingEnabled())
502                     {
503                         std::ostringstream os;
504                         os << "    Failed alt format ";
505                         os << altFormat->getName();
506                         os << ":  " << e.what();
507                         LogDebug(os.str());
508                     }
509                 }
510 
511                 filestream.clear();
512                 filestream.seekg( std::ifstream::beg );
513             }
514 
515             // No formats succeeded. Error out with a sensible message.
516             if(primaryFormat)
517             {
518                 std::ostringstream os;
519                 os << "The specified transform file '";
520                 os << filepath <<"' could not be loaded. ";
521                 os << primaryErrorText;
522 
523                 throw Exception(os.str().c_str());
524             }
525             else
526             {
527                 std::ostringstream os;
528                 os << "The specified transform file '";
529                 os << filepath <<"' does not appear to be a valid, known LUT file format.";
530                 throw Exception(os.str().c_str());
531             }
532         }
533 
534         // We mutex both the main map and each item individually, so that
535         // the potentially slow file access wont block other lookups to already
536         // existing items. (Loads of the *same* file will mutually block though)
537 
538         struct FileCacheResult
539         {
540             Mutex mutex;
541             FileFormat * format;
542             bool ready;
543             bool error;
544             CachedFileRcPtr cachedFile;
545             std::string exceptionText;
546 
547             FileCacheResult():
548                 format(NULL),
549                 ready(false),
550                 error(false)
551             {}
552         };
553 
554         typedef OCIO_SHARED_PTR<FileCacheResult> FileCacheResultPtr;
555         typedef std::map<std::string, FileCacheResultPtr> FileCacheMap;
556 
557         FileCacheMap g_fileCache;
558         Mutex g_fileCacheLock;
559 
560         void GetCachedFileAndFormat(
561             FileFormat * & format, CachedFileRcPtr & cachedFile,
562             const std::string & filepath)
563         {
564             // Load the file cache ptr from the global map
565             FileCacheResultPtr result;
566             {
567                 AutoMutex lock(g_fileCacheLock);
568                 FileCacheMap::iterator iter = g_fileCache.find(filepath);
569                 if(iter != g_fileCache.end())
570                 {
571                     result = iter->second;
572                 }
573                 else
574                 {
575                     result = FileCacheResultPtr(new FileCacheResult);
576                     g_fileCache[filepath] = result;
577                 }
578             }
579 
580             // If this file has already been loaded, return
581             // the result immediately
582 
583             AutoMutex lock(result->mutex);
584             if(!result->ready)
585             {
586                 result->ready = true;
587                 result->error = false;
588 
589                 try
590                 {
591                     LoadFileUncached(result->format,
592                                      result->cachedFile,
593                                      filepath);
594                 }
595                 catch(std::exception & e)
596                 {
597                     result->error = true;
598                     result->exceptionText = e.what();
599                 }
600                 catch(...)
601                 {
602                     result->error = true;
603                     std::ostringstream os;
604                     os << "An unknown error occurred in LoadFileUncached, ";
605                     os << filepath;
606                     result->exceptionText = os.str();
607                 }
608             }
609 
610             if(result->error)
611             {
612                 throw Exception(result->exceptionText.c_str());
613             }
614             else
615             {
616                 format = result->format;
617                 cachedFile = result->cachedFile;
618             }
619         }
620     } // namespace
621 
622     void ClearFileTransformCaches()
623     {
624         AutoMutex lock(g_fileCacheLock);
625         g_fileCache.clear();
626     }
627 
628     void BuildFileOps(OpRcPtrVec & ops,
629                       const Config& config,
630                       const ConstContextRcPtr & context,
631                       const FileTransform& fileTransform,
632                       TransformDirection dir)
633     {
634         std::string src = fileTransform.getSrc();
635         if(src.empty())
636         {
637             std::ostringstream os;
638             os << "The transform file has not been specified.";
639             throw Exception(os.str().c_str());
640         }
641 
642         std::string filepath = context->resolveFileLocation(src.c_str());
643         CreateFileNoOp(ops, filepath);
644 
645         FileFormat* format = NULL;
646         CachedFileRcPtr cachedFile;
647 
648         GetCachedFileAndFormat(format, cachedFile, filepath);
649         if(!format)
650         {
651             std::ostringstream os;
652             os << "The specified file load ";
653             os << filepath << " appeared to succeed, but no format ";
654             os << "was returned.";
655             throw Exception(os.str().c_str());
656         }
657 
658         if(!cachedFile.get())
659         {
660             std::ostringstream os;
661             os << "The specified file load ";
662             os << filepath << " appeared to succeed, but no cachedFile ";
663             os << "was returned.";
664             throw Exception(os.str().c_str());
665         }
666 
667         format->BuildFileOps(ops,
668                              config, context,
669                              cachedFile, fileTransform,
670                              dir);
671     }
672 }
673 OCIO_NAMESPACE_EXIT
674