/src/core/FileFormatIridasCube.cpp

http://github.com/imageworks/OpenColorIO · C++ · 398 lines · 252 code · 48 blank · 98 comment · 59 complexity · 4c2ee10abe1f2dbc11e0c03a66331f17 MD5 · raw file

  1. /*
  2. Copyright (c) 2003-2010 Sony Pictures Imageworks Inc., et al.
  3. All Rights Reserved.
  4. Redistribution and use in source and binary forms, with or without
  5. modification, are permitted provided that the following conditions are
  6. met:
  7. * Redistributions of source code must retain the above copyright
  8. notice, this list of conditions and the following disclaimer.
  9. * Redistributions in binary form must reproduce the above copyright
  10. notice, this list of conditions and the following disclaimer in the
  11. documentation and/or other materials provided with the distribution.
  12. * Neither the name of Sony Pictures Imageworks nor the names of its
  13. contributors may be used to endorse or promote products derived from
  14. this software without specific prior written permission.
  15. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  16. "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  17. LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  18. A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  19. OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  20. SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  21. LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  22. DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  23. THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  25. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. #include <cstdio>
  28. #include <cstring>
  29. #include <iterator>
  30. #include <OpenColorIO/OpenColorIO.h>
  31. #include "FileTransform.h"
  32. #include "Lut1DOp.h"
  33. #include "Lut3DOp.h"
  34. #include "ParseUtils.h"
  35. #include "pystring/pystring.h"
  36. /*
  37. http://doc.iridas.com/index.php/LUT_Formats
  38. #comments start with '#'
  39. #title is currently ignored, but it's not an error to enter one
  40. TITLE "title"
  41. #LUT_1D_SIZE M or
  42. #LUT_3D_SIZE M
  43. #where M is the size of the texture
  44. #a 3D texture has the size M x M x M
  45. #e.g. LUT_3D_SIZE 16 creates a 16 x 16 x 16 3D texture
  46. LUT_3D_SIZE 2
  47. #Default input value range (domain) is 0.0 (black) to 1.0 (white)
  48. #Specify other min/max values to map the cube to any custom input
  49. #range you wish to use, for example if you're working with HDR data
  50. DOMAIN_MIN 0.0 0.0 0.0
  51. DOMAIN_MAX 1.0 1.0 1.0
  52. #for 1D textures, the data is simply a list of floating point values,
  53. #three per line, in RGB order
  54. #for 3D textures, the data is also RGB, and ordered in such a way
  55. #that the red coordinate changes fastest, then the green coordinate,
  56. #and finally, the blue coordinate changes slowest:
  57. 0.0 0.0 0.0
  58. 1.0 0.0 0.0
  59. 0.0 1.0 0.0
  60. 1.0 1.0 0.0
  61. 0.0 0.0 1.0
  62. 1.0 0.0 1.0
  63. 0.0 1.0 1.0
  64. 1.0 1.0 1.0
  65. #Note that the LUT data is not limited to any particular range
  66. #and can contain values under 0.0 and over 1.0
  67. #The processing application might however still clip the
  68. #output values to the 0.0 - 1.0 range, depending on the internal
  69. #precision of that application's pipeline
  70. #IRIDAS applications generally use a floating point pipeline
  71. #with little or no clipping
  72. */
  73. OCIO_NAMESPACE_ENTER
  74. {
  75. namespace
  76. {
  77. class LocalCachedFile : public CachedFile
  78. {
  79. public:
  80. LocalCachedFile () :
  81. has1D(false),
  82. has3D(false)
  83. {
  84. lut1D = Lut1D::Create();
  85. lut3D = Lut3D::Create();
  86. };
  87. ~LocalCachedFile() {};
  88. bool has1D;
  89. bool has3D;
  90. Lut1DRcPtr lut1D;
  91. Lut3DRcPtr lut3D;
  92. };
  93. typedef OCIO_SHARED_PTR<LocalCachedFile> LocalCachedFileRcPtr;
  94. class LocalFileFormat : public FileFormat
  95. {
  96. public:
  97. ~LocalFileFormat() {};
  98. virtual void GetFormatInfo(FormatInfoVec & formatInfoVec) const;
  99. virtual CachedFileRcPtr Read(std::istream & istream) const;
  100. virtual void BuildFileOps(OpRcPtrVec & ops,
  101. const Config& config,
  102. const ConstContextRcPtr & context,
  103. CachedFileRcPtr untypedCachedFile,
  104. const FileTransform& fileTransform,
  105. TransformDirection dir) const;
  106. };
  107. void LocalFileFormat::GetFormatInfo(FormatInfoVec & formatInfoVec) const
  108. {
  109. FormatInfo info;
  110. info.name = "iridas_cube";
  111. info.extension = "cube";
  112. info.capabilities = FORMAT_CAPABILITY_READ;
  113. formatInfoVec.push_back(info);
  114. }
  115. CachedFileRcPtr
  116. LocalFileFormat::Read(std::istream & istream) const
  117. {
  118. // this shouldn't happen
  119. if(!istream)
  120. {
  121. throw Exception ("File stream empty when trying to read Iridas .cube lut");
  122. }
  123. // Parse the file
  124. std::vector<float> raw;
  125. int size3d[] = { 0, 0, 0 };
  126. int size1d = 0;
  127. bool in1d = false;
  128. bool in3d = false;
  129. float domain_min[] = { 0.0f, 0.0f, 0.0f };
  130. float domain_max[] = { 1.0f, 1.0f, 1.0f };
  131. {
  132. std::string line;
  133. std::vector<std::string> parts;
  134. std::vector<float> tmpfloats;
  135. while(nextline(istream, line))
  136. {
  137. // All lines starting with '#' are comments
  138. if(pystring::startswith(line,"#")) continue;
  139. // Strip, lowercase, and split the line
  140. pystring::split(pystring::lower(pystring::strip(line)), parts);
  141. if(parts.empty()) continue;
  142. if(pystring::lower(parts[0]) == "title")
  143. {
  144. // Optional, and currently unhandled
  145. }
  146. else if(pystring::lower(parts[0]) == "lut_1d_size")
  147. {
  148. if(parts.size() != 2 || !StringToInt( &size1d, parts[1].c_str()))
  149. {
  150. throw Exception("Malformed LUT_1D_SIZE tag in Iridas .cube lut.");
  151. }
  152. raw.reserve(3*size1d);
  153. in1d = true;
  154. }
  155. else if(pystring::lower(parts[0]) == "lut_2d_size")
  156. {
  157. throw Exception("Unsupported Iridas .cube lut tag: 'LUT_2D_SIZE'.");
  158. }
  159. else if(pystring::lower(parts[0]) == "lut_3d_size")
  160. {
  161. int size = 0;
  162. if(parts.size() != 2 || !StringToInt( &size, parts[1].c_str()))
  163. {
  164. throw Exception("Malformed LUT_3D_SIZE tag in Iridas .cube lut.");
  165. }
  166. size3d[0] = size;
  167. size3d[1] = size;
  168. size3d[2] = size;
  169. raw.reserve(3*size3d[0]*size3d[1]*size3d[2]);
  170. in3d = true;
  171. }
  172. else if(pystring::lower(parts[0]) == "domain_min")
  173. {
  174. if(parts.size() != 4 ||
  175. !StringToFloat( &domain_min[0], parts[1].c_str()) ||
  176. !StringToFloat( &domain_min[1], parts[2].c_str()) ||
  177. !StringToFloat( &domain_min[2], parts[3].c_str()))
  178. {
  179. throw Exception("Malformed DOMAIN_MIN tag in Iridas .cube lut.");
  180. }
  181. }
  182. else if(pystring::lower(parts[0]) == "domain_max")
  183. {
  184. if(parts.size() != 4 ||
  185. !StringToFloat( &domain_max[0], parts[1].c_str()) ||
  186. !StringToFloat( &domain_max[1], parts[2].c_str()) ||
  187. !StringToFloat( &domain_max[2], parts[3].c_str()))
  188. {
  189. throw Exception("Malformed DOMAIN_MAX tag in Iridas .cube lut.");
  190. }
  191. }
  192. else
  193. {
  194. // It must be a float triple!
  195. if(!StringVecToFloatVec(tmpfloats, parts) || tmpfloats.size() != 3)
  196. {
  197. std::ostringstream os;
  198. os << "Malformed color triples specified in Iridas .cube lut:";
  199. os << "'" << line << "'.";
  200. throw Exception(os.str().c_str());
  201. }
  202. for(int i=0; i<3; ++i)
  203. {
  204. raw.push_back(tmpfloats[i]);
  205. }
  206. }
  207. }
  208. }
  209. // Interpret the parsed data, validate lut sizes
  210. LocalCachedFileRcPtr cachedFile = LocalCachedFileRcPtr(new LocalCachedFile());
  211. if(in1d)
  212. {
  213. if(size1d != static_cast<int>(raw.size()/3))
  214. {
  215. std::ostringstream os;
  216. os << "Parse error in Iridas .cube lut. ";
  217. os << "Incorrect number of lut1d entries. ";
  218. os << "Found " << raw.size()/3 << ", expected " << size1d << ".";
  219. throw Exception(os.str().c_str());
  220. }
  221. // Reformat 1D data
  222. if(size1d>0)
  223. {
  224. cachedFile->has1D = true;
  225. memcpy(cachedFile->lut1D->from_min, domain_min, 3*sizeof(float));
  226. memcpy(cachedFile->lut1D->from_max, domain_max, 3*sizeof(float));
  227. for(int channel=0; channel<3; ++channel)
  228. {
  229. cachedFile->lut1D->luts[channel].resize(size1d);
  230. for(int i=0; i<size1d; ++i)
  231. {
  232. cachedFile->lut1D->luts[channel][i] = raw[3*i+channel];
  233. }
  234. }
  235. // 1e-5 rel error is a good threshold when float numbers near 0
  236. // are written out with 6 decimal places of precision. This is
  237. // a bit aggressive, I.e., changes in the 6th decimal place will
  238. // be considered roundoff error, but changes in the 5th decimal
  239. // will be considered lut 'intent'.
  240. // 1.0
  241. // 1.000005 equal to 1.0
  242. // 1.000007 equal to 1.0
  243. // 1.000010 not equal
  244. // 0.0
  245. // 0.000001 not equal
  246. cachedFile->lut1D->maxerror = 1e-5f;
  247. cachedFile->lut1D->errortype = ERROR_RELATIVE;
  248. }
  249. }
  250. else if(in3d)
  251. {
  252. cachedFile->has3D = true;
  253. if(size3d[0]*size3d[1]*size3d[2] != static_cast<int>(raw.size()/3))
  254. {
  255. std::ostringstream os;
  256. os << "Parse error in Iridas .cube lut. ";
  257. os << "Incorrect number of lut3d entries. ";
  258. os << "Found " << raw.size()/3 << ", expected " << size3d[0]*size3d[1]*size3d[2] << ".";
  259. throw Exception(os.str().c_str());
  260. }
  261. // Reformat 3D data
  262. memcpy(cachedFile->lut3D->from_min, domain_min, 3*sizeof(float));
  263. memcpy(cachedFile->lut3D->from_max, domain_max, 3*sizeof(float));
  264. cachedFile->lut3D->size[0] = size3d[0];
  265. cachedFile->lut3D->size[1] = size3d[1];
  266. cachedFile->lut3D->size[2] = size3d[2];
  267. cachedFile->lut3D->lut = raw;
  268. }
  269. else
  270. {
  271. std::ostringstream os;
  272. os << "Parse error in Iridas .cube lut. ";
  273. os << "Lut type (1D/3D) unspecified.";
  274. throw Exception(os.str().c_str());
  275. }
  276. return cachedFile;
  277. }
  278. void
  279. LocalFileFormat::BuildFileOps(OpRcPtrVec & ops,
  280. const Config& /*config*/,
  281. const ConstContextRcPtr & /*context*/,
  282. CachedFileRcPtr untypedCachedFile,
  283. const FileTransform& fileTransform,
  284. TransformDirection dir) const
  285. {
  286. LocalCachedFileRcPtr cachedFile = DynamicPtrCast<LocalCachedFile>(untypedCachedFile);
  287. // This should never happen.
  288. if(!cachedFile)
  289. {
  290. std::ostringstream os;
  291. os << "Cannot build Iridas .cube Op. Invalid cache type.";
  292. throw Exception(os.str().c_str());
  293. }
  294. TransformDirection newDir = CombineTransformDirections(dir,
  295. fileTransform.getDirection());
  296. if(newDir == TRANSFORM_DIR_UNKNOWN)
  297. {
  298. std::ostringstream os;
  299. os << "Cannot build file format transform,";
  300. os << " unspecified transform direction.";
  301. throw Exception(os.str().c_str());
  302. }
  303. // TODO: INTERP_LINEAR should not be hard-coded.
  304. // Instead query 'highest' interpolation?
  305. // (right now, it's linear). If cubic is added, consider
  306. // using it
  307. if(newDir == TRANSFORM_DIR_FORWARD)
  308. {
  309. if(cachedFile->has1D)
  310. {
  311. CreateLut1DOp(ops, cachedFile->lut1D,
  312. INTERP_LINEAR, newDir);
  313. }
  314. if(cachedFile->has3D)
  315. {
  316. CreateLut3DOp(ops, cachedFile->lut3D,
  317. fileTransform.getInterpolation(), newDir);
  318. }
  319. }
  320. else if(newDir == TRANSFORM_DIR_INVERSE)
  321. {
  322. if(cachedFile->has3D)
  323. {
  324. CreateLut3DOp(ops, cachedFile->lut3D,
  325. fileTransform.getInterpolation(), newDir);
  326. }
  327. if(cachedFile->has1D)
  328. {
  329. CreateLut1DOp(ops, cachedFile->lut1D,
  330. INTERP_LINEAR, newDir);
  331. }
  332. }
  333. }
  334. }
  335. FileFormat * CreateFileFormatIridasCube()
  336. {
  337. return new LocalFileFormat();
  338. }
  339. }
  340. OCIO_NAMESPACE_EXIT
  341. ///////////////////////////////////////////////////////////////////////////////