PageRenderTime 36ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/src/core/FileFormatHDL.cpp

http://github.com/imageworks/OpenColorIO
C++ | 1481 lines | 1081 code | 204 blank | 196 comment | 148 complexity | 3a21d222f96fc2aa4a87335dbfd052f5 MD5 | raw file
Possible License(s): BSD-3-Clause
  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. /*
  28. Houdini LUTs
  29. http://www.sidefx.com/docs/hdk11.0/hdk_io_lut.html
  30. Types:
  31. - 1D Lut (partial support)
  32. - 3D Lut
  33. - 3D Lut with 1D Prelut
  34. TODO:
  35. - Add support for other 1D types (R, G, B, A, RGB, RGBA, All)
  36. we only support type 'C' atm.
  37. - Add support for 'Sampling' tag
  38. */
  39. #include <cstdio>
  40. #include <iostream>
  41. #include <iterator>
  42. #include <cmath>
  43. #include <vector>
  44. #include <string>
  45. #include <algorithm>
  46. #include <map>
  47. #include <OpenColorIO/OpenColorIO.h>
  48. #include "FileTransform.h"
  49. #include "Lut1DOp.h"
  50. #include "Lut3DOp.h"
  51. #include "ParseUtils.h"
  52. #include "MathUtils.h"
  53. #include "pystring/pystring.h"
  54. OCIO_NAMESPACE_ENTER
  55. {
  56. namespace
  57. {
  58. // HDL parser helpers
  59. // HDL headers/LUT's are shoved into these datatypes
  60. typedef std::map<std::string, std::vector<std::string> > StringToStringVecMap;
  61. typedef std::map<std::string, std::vector<float> > StringToFloatVecMap;
  62. void
  63. readHeaders(StringToStringVecMap& headers,
  64. std::istream& istream)
  65. {
  66. std::string line;
  67. while(nextline(istream, line))
  68. {
  69. std::vector<std::string> chunks;
  70. // Remove trailing/leading whitespace, lower-case and
  71. // split into words
  72. pystring::split(pystring::lower(pystring::strip(line)), chunks);
  73. // Skip empty lines
  74. if(chunks.empty()) continue;
  75. // Stop looking for headers at the "LUT:" line
  76. if(chunks[0] == "lut:") break;
  77. // Use first index as key, and remove it from the value
  78. std::string key = chunks[0];
  79. chunks.erase(chunks.begin());
  80. headers[key] = chunks;
  81. }
  82. }
  83. // Try to grab key (e.g "version") from headers. Throws
  84. // exception if not found, or if number of chunks in value is
  85. // not between min_vals and max_vals (e.g the "length" key
  86. // must exist, and must have either 1 or 2 values)
  87. std::vector<std::string>
  88. findHeaderItem(StringToStringVecMap& headers,
  89. const std::string key,
  90. const unsigned int min_vals,
  91. const unsigned int max_vals)
  92. {
  93. StringToStringVecMap::iterator iter;
  94. iter = headers.find(key);
  95. // Error if key is not found
  96. if(iter == headers.end())
  97. {
  98. std::ostringstream os;
  99. os << "'" << key << "' line not found";
  100. throw Exception(os.str().c_str());
  101. }
  102. // Error if incorrect number of values is found
  103. if(iter->second.size() < min_vals ||
  104. iter->second.size() > max_vals)
  105. {
  106. std::ostringstream os;
  107. os << "Incorrect number of chunks (" << iter->second.size() << ")";
  108. os << " after '" << key << "' line, expected ";
  109. if(min_vals == max_vals)
  110. {
  111. os << min_vals;
  112. }
  113. else
  114. {
  115. os << "between " << min_vals << " and " << max_vals;
  116. }
  117. throw Exception(os.str().c_str());
  118. }
  119. return iter->second;
  120. }
  121. // Simple wrapper to call findHeaderItem with a fixed number
  122. // of values (e.g "version" should have a single value)
  123. std::vector<std::string>
  124. findHeaderItem(StringToStringVecMap& chunks,
  125. const std::string key,
  126. const unsigned int numvals)
  127. {
  128. return findHeaderItem(chunks, key, numvals, numvals);
  129. }
  130. // Crudely parse LUT's - doesn't do any length checking etc,
  131. // just grabs a series of floats for Pre{...}, 3d{...} etc
  132. // Does some basic error checking, but there are situations
  133. // were it could incorrectly accept broken data (like
  134. // "Pre{0.0\n1.0}blah"), but hopefully none where it misses
  135. // data
  136. void
  137. readLuts(std::istream& istream,
  138. StringToFloatVecMap& lutValues)
  139. {
  140. // State variables
  141. bool inlut = false;
  142. std::string lutname;
  143. std::string word;
  144. while(istream >> word)
  145. {
  146. if(!inlut)
  147. {
  148. if(word == "{")
  149. {
  150. // Lone "{" is for a 3D
  151. inlut = true;
  152. lutname = "3d";
  153. }
  154. else
  155. {
  156. // Named lut, e.g "Pre {"
  157. inlut = true;
  158. lutname = pystring::lower(word);
  159. // Ensure next word is "{"
  160. std::string nextword;
  161. istream >> nextword;
  162. if(nextword != "{")
  163. {
  164. std::ostringstream os;
  165. os << "Malformed LUT - Unknown word '";
  166. os << word << "' after LUT name '";
  167. os << nextword << "'";
  168. throw Exception(os.str().c_str());
  169. }
  170. }
  171. }
  172. else if(word == "}")
  173. {
  174. // end of LUT
  175. inlut = false;
  176. lutname = "";
  177. }
  178. else if(inlut)
  179. {
  180. // StringToFloat was far slower, for 787456 values:
  181. // - StringToFloat took 3879 (avg nanoseconds per value)
  182. // - stdtod took 169 nanoseconds
  183. char* endptr = 0;
  184. float v = static_cast<float>(strtod(word.c_str(), &endptr));
  185. if(!*endptr)
  186. {
  187. // Since each word should contain a single
  188. // float value, the pointer should be null
  189. lutValues[lutname].push_back(v);
  190. }
  191. else
  192. {
  193. // stdtod endptr still contained stuff,
  194. // meaning an invalid float value
  195. std::ostringstream os;
  196. os << "Invalid float value in " << lutname;
  197. os << " LUT, '" << word << "'";
  198. throw Exception(os.str().c_str());
  199. }
  200. }
  201. else
  202. {
  203. std::ostringstream os;
  204. os << "Unexpected word, possibly a value outside";
  205. os <<" a LUT {} block. Word was '" << word << "'";
  206. throw Exception(os.str().c_str());
  207. }
  208. }
  209. }
  210. } // end anonymous "HDL parser helpers" namespace
  211. namespace
  212. {
  213. class CachedFileHDL : public CachedFile
  214. {
  215. public:
  216. CachedFileHDL ()
  217. {
  218. hdlversion = "unknown";
  219. hdlformat = "unknown";
  220. hdltype = "unknown";
  221. hdlblack = 0.0;
  222. hdlwhite = 1.0;
  223. lut1D = Lut1D::Create();
  224. lut3D = Lut3D::Create();
  225. };
  226. ~CachedFileHDL() {};
  227. std::string hdlversion;
  228. std::string hdlformat;
  229. std::string hdltype;
  230. float to_min; // TODO: maybe add this to Lut1DOp?
  231. float to_max; // TODO: maybe add this to Lut1DOp?
  232. float hdlblack;
  233. float hdlwhite;
  234. Lut1DRcPtr lut1D;
  235. Lut3DRcPtr lut3D;
  236. };
  237. typedef OCIO_SHARED_PTR<CachedFileHDL> CachedFileHDLRcPtr;
  238. class LocalFileFormat : public FileFormat
  239. {
  240. public:
  241. ~LocalFileFormat() {};
  242. virtual void GetFormatInfo(FormatInfoVec & formatInfoVec) const;
  243. virtual CachedFileRcPtr Read(std::istream & istream) const;
  244. virtual void Write(const Baker & baker,
  245. const std::string & formatName,
  246. std::ostream & ostream) const;
  247. virtual void BuildFileOps(OpRcPtrVec & ops,
  248. const Config& config,
  249. const ConstContextRcPtr & context,
  250. CachedFileRcPtr untypedCachedFile,
  251. const FileTransform& fileTransform,
  252. TransformDirection dir) const;
  253. };
  254. void LocalFileFormat::GetFormatInfo(FormatInfoVec & formatInfoVec) const
  255. {
  256. FormatInfo info;
  257. info.name = "houdini";
  258. info.extension = "lut";
  259. info.capabilities = FORMAT_CAPABILITY_READ | FORMAT_CAPABILITY_WRITE;
  260. formatInfoVec.push_back(info);
  261. }
  262. CachedFileRcPtr
  263. LocalFileFormat::Read(std::istream & istream) const
  264. {
  265. // this shouldn't happen
  266. if (!istream)
  267. throw Exception ("file stream empty when trying to read Houdini lut");
  268. //
  269. CachedFileHDLRcPtr cachedFile = CachedFileHDLRcPtr (new CachedFileHDL ());
  270. Lut1DRcPtr lut1d_ptr = Lut1D::Create();
  271. Lut3DRcPtr lut3d_ptr = Lut3D::Create();
  272. // Parse headers into key-value pairs
  273. StringToStringVecMap header_chunks;
  274. StringToStringVecMap::iterator iter;
  275. // Read headers, ending after the "LUT:" line
  276. readHeaders(header_chunks, istream);
  277. // Grab useful values from headers
  278. std::vector<std::string> value;
  279. // "Version 3" - format version (currently one version
  280. // number per LUT type)
  281. value = findHeaderItem(header_chunks, "version", 1);
  282. cachedFile->hdlversion = value[0];
  283. // "Format any" - bit depth of image the LUT should be
  284. // applied to (this is basically ignored)
  285. value = findHeaderItem(header_chunks, "format", 1);
  286. cachedFile->hdlformat = value[0];
  287. // "Type 3d" - type of LUT
  288. {
  289. value = findHeaderItem(header_chunks, "type", 1);
  290. cachedFile->hdltype = value[0];
  291. }
  292. // "From 0.0 1.0" - range of input values
  293. {
  294. float from_min, from_max;
  295. value = findHeaderItem(header_chunks, "from", 2);
  296. if(!StringToFloat(&from_min, value[0].c_str()) ||
  297. !StringToFloat(&from_max, value[1].c_str()))
  298. {
  299. std::ostringstream os;
  300. os << "Invalid float value(s) on 'From' line, '";
  301. os << value[0] << "' and '" << value[1] << "'";
  302. throw Exception(os.str().c_str());
  303. }
  304. for(int i = 0; i < 3; ++i)
  305. {
  306. lut1d_ptr->from_min[i] = from_min;
  307. lut1d_ptr->from_max[i] = from_max;
  308. }
  309. }
  310. // "To 0.0 1.0" - range of values in LUT (e.g "0 255"
  311. // to specify values as 8-bit numbers, usually "0 1")
  312. {
  313. float to_min, to_max;
  314. value = findHeaderItem(header_chunks, "to", 2);
  315. if(!StringToFloat(&to_min, value[0].c_str()) ||
  316. !StringToFloat(&to_max, value[1].c_str()))
  317. {
  318. std::ostringstream os;
  319. os << "Invalid float value(s) on 'To' line, '";
  320. os << value[0] << "' and '" << value[1] << "'";
  321. throw Exception(os.str().c_str());
  322. }
  323. cachedFile->to_min = to_min;
  324. cachedFile->to_max = to_max;
  325. }
  326. // "Black 0" and "White 1" - obsolete options, should be 0
  327. // and 1
  328. {
  329. value = findHeaderItem(header_chunks, "black", 1);
  330. float black;
  331. if(!StringToFloat(&black, value[0].c_str()))
  332. {
  333. std::ostringstream os;
  334. os << "Invalid float value on 'Black' line, '";
  335. os << value[0] << "'";
  336. throw Exception(os.str().c_str());
  337. }
  338. cachedFile->hdlblack = black;
  339. }
  340. {
  341. value = findHeaderItem(header_chunks, "white", 1);
  342. float white;
  343. if(!StringToFloat(&white, value[0].c_str()))
  344. {
  345. std::ostringstream os;
  346. os << "Invalid float value on 'White' line, '";
  347. os << value[0] << "'";
  348. throw Exception(os.str().c_str());
  349. }
  350. cachedFile->hdlwhite = white;
  351. }
  352. // Verify type is valid and supported - used to handle
  353. // length sensibly, and checking the LUT later
  354. {
  355. std::string ltype = cachedFile->hdltype;
  356. if(ltype != "3d" && ltype != "3d+1d" && ltype != "c")
  357. {
  358. std::ostringstream os;
  359. os << "Unsupported Houdini LUT type: '" << ltype << "'";
  360. throw Exception(os.str().c_str());
  361. }
  362. }
  363. // "Length 2" or "Length 2 5" - either "[cube size]", or "[cube
  364. // size] [prelut size]"
  365. int size_3d = -1;
  366. int size_prelut = -1;
  367. int size_1d = -1;
  368. {
  369. std::vector<int> lut_sizes;
  370. value = findHeaderItem(header_chunks, "length", 1, 2);
  371. for(unsigned int i = 0; i < value.size(); ++i)
  372. {
  373. int tmpsize = -1;
  374. if(!StringToInt(&tmpsize, value[i].c_str()))
  375. {
  376. std::ostringstream os;
  377. os << "Invalid integer on 'Length' line: ";
  378. os << "'" << value[0] << "'";
  379. throw Exception(os.str().c_str());
  380. }
  381. lut_sizes.push_back(tmpsize);
  382. }
  383. if(cachedFile->hdltype == "3d" || cachedFile->hdltype == "3d+1d")
  384. {
  385. // Set cube size
  386. size_3d = lut_sizes[0];
  387. lut3d_ptr->size[0] = lut_sizes[0];
  388. lut3d_ptr->size[1] = lut_sizes[0];
  389. lut3d_ptr->size[2] = lut_sizes[0];
  390. }
  391. if(cachedFile->hdltype == "c")
  392. {
  393. size_1d = lut_sizes[0];
  394. }
  395. if(cachedFile->hdltype == "3d+1d")
  396. {
  397. size_prelut = lut_sizes[1];
  398. }
  399. }
  400. // Read stuff after "LUT:"
  401. StringToFloatVecMap lut_data;
  402. readLuts(istream, lut_data);
  403. //
  404. StringToFloatVecMap::iterator lut_iter;
  405. if(cachedFile->hdltype == "3d+1d")
  406. {
  407. // Read prelut, and bind onto cachedFile
  408. lut_iter = lut_data.find("pre");
  409. if(lut_iter == lut_data.end())
  410. {
  411. std::ostringstream os;
  412. os << "3D+1D LUT should contain Pre{} LUT section";
  413. throw Exception(os.str().c_str());
  414. }
  415. if(size_prelut != static_cast<int>(lut_iter->second.size()))
  416. {
  417. std::ostringstream os;
  418. os << "Pre{} LUT was " << lut_iter->second.size();
  419. os << " values long, expected " << size_prelut << " values";
  420. throw Exception(os.str().c_str());
  421. }
  422. lut1d_ptr->luts[0] = lut_iter->second;
  423. lut1d_ptr->luts[1] = lut_iter->second;
  424. lut1d_ptr->luts[2] = lut_iter->second;
  425. lut1d_ptr->maxerror = 0.0f;
  426. lut1d_ptr->errortype = ERROR_RELATIVE;
  427. cachedFile->lut1D = lut1d_ptr;
  428. }
  429. if(cachedFile->hdltype == "3d" ||
  430. cachedFile->hdltype == "3d+1d")
  431. {
  432. // Bind 3D LUT to lut3d_ptr, along with some
  433. // slightly-elabourate error messages
  434. lut_iter = lut_data.find("3d");
  435. if(lut_iter == lut_data.end())
  436. {
  437. std::ostringstream os;
  438. os << "3D LUT section not found";
  439. throw Exception(os.str().c_str());
  440. }
  441. int size_3d_cubed = size_3d * size_3d * size_3d;
  442. if(size_3d_cubed * 3 != static_cast<int>(lut_iter->second.size()))
  443. {
  444. int foundsize = static_cast<int>(lut_iter->second.size());
  445. int foundlines = foundsize / 3;
  446. std::ostringstream os;
  447. os << "3D LUT contains incorrect number of values. ";
  448. os << "Contained " << foundsize << " values ";
  449. os << "(" << foundlines << " lines), ";
  450. os << "expected " << (size_3d_cubed*3) << " values ";
  451. os << "(" << size_3d_cubed << " lines)";
  452. throw Exception(os.str().c_str());
  453. }
  454. lut3d_ptr->lut = lut_iter->second;
  455. // Bind to cachedFile
  456. cachedFile->lut3D = lut3d_ptr;
  457. }
  458. if(cachedFile->hdltype == "c")
  459. {
  460. // Bind simple 1D RGB LUT
  461. lut_iter = lut_data.find("rgb");
  462. if(lut_iter == lut_data.end())
  463. {
  464. std::ostringstream os;
  465. os << "3D+1D LUT should contain Pre{} LUT section";
  466. throw Exception(os.str().c_str());
  467. }
  468. if(size_1d != static_cast<int>(lut_iter->second.size()))
  469. {
  470. std::ostringstream os;
  471. os << "RGB{} LUT was " << lut_iter->second.size();
  472. os << " values long, expected " << size_1d << " values";
  473. throw Exception(os.str().c_str());
  474. }
  475. lut1d_ptr->luts[0] = lut_iter->second;
  476. lut1d_ptr->luts[1] = lut_iter->second;
  477. lut1d_ptr->luts[2] = lut_iter->second;
  478. lut1d_ptr->maxerror = 0.0f;
  479. lut1d_ptr->errortype = ERROR_RELATIVE;
  480. cachedFile->lut1D = lut1d_ptr;
  481. }
  482. return cachedFile;
  483. }
  484. void LocalFileFormat::Write(const Baker & baker,
  485. const std::string & formatName,
  486. std::ostream & ostream) const
  487. {
  488. if(formatName != "houdini")
  489. {
  490. std::ostringstream os;
  491. os << "Unknown hdl format name, '";
  492. os << formatName << "'.";
  493. throw Exception(os.str().c_str());
  494. }
  495. // Get config
  496. ConstConfigRcPtr config = baker.getConfig();
  497. // setup the floating point precision
  498. ostream.setf(std::ios::fixed, std::ios::floatfield);
  499. ostream.precision(6);
  500. // Default sizes
  501. const int DEFAULT_SHAPER_SIZE = 1024;
  502. // MPlay produces bad results with 32^3 cube (in a way
  503. // that looks more quantised than even "nearest"
  504. // interpolation in OCIOFileTransform)
  505. const int DEFAULT_CUBE_SIZE = 64;
  506. const int DEFAULT_1D_SIZE = 1024;
  507. // Get configured sizes
  508. int cubeSize = baker.getCubeSize();
  509. int shaperSize = baker.getShaperSize();
  510. // FIXME: Misusing cube size to set 1D LUT size, as it seemed
  511. // slightly less confusing than using the shaper LUT size
  512. int onedSize = baker.getCubeSize();
  513. // Defaults and sanity check on cube size
  514. if(cubeSize == -1) cubeSize = DEFAULT_CUBE_SIZE;
  515. if(cubeSize < 0) cubeSize = DEFAULT_CUBE_SIZE;
  516. if(cubeSize<2)
  517. {
  518. std::ostringstream os;
  519. os << "Cube size must be 2 or larger (was " << cubeSize << ")";
  520. throw Exception(os.str().c_str());
  521. }
  522. // ..and same for shaper size
  523. if(shaperSize<0) shaperSize = DEFAULT_SHAPER_SIZE;
  524. if(shaperSize<2)
  525. {
  526. std::ostringstream os;
  527. os << "A shaper space ('" << baker.getShaperSpace() << "') has";
  528. os << " been specified, so the shaper size must be 2 or larger";
  529. throw Exception(os.str().c_str());
  530. }
  531. // ..and finally, for the 1D LUT size
  532. if(onedSize == -1) onedSize = DEFAULT_1D_SIZE;
  533. if(onedSize < 2)
  534. {
  535. std::ostringstream os;
  536. os << "1D LUT size must be higher than 2 (was " << onedSize << ")";
  537. throw Exception(os.str().c_str());
  538. }
  539. // Version numbers
  540. const int HDL_1D = 1; // 1D LUT version number
  541. const int HDL_3D = 2; // 3D LUT version number
  542. const int HDL_3D1D = 3; // 3D LUT with 1D prelut
  543. // Get spaces from baker
  544. const std::string shaperSpace = baker.getShaperSpace();
  545. const std::string inputSpace = baker.getInputSpace();
  546. const std::string targetSpace = baker.getTargetSpace();
  547. const std::string looks = baker.getLooks();
  548. // Determine required LUT type
  549. ConstProcessorRcPtr inputToTargetProc;
  550. if (!looks.empty())
  551. {
  552. LookTransformRcPtr transform = LookTransform::Create();
  553. transform->setLooks(looks.c_str());
  554. transform->setSrc(inputSpace.c_str());
  555. transform->setDst(targetSpace.c_str());
  556. inputToTargetProc = config->getProcessor(transform,
  557. TRANSFORM_DIR_FORWARD);
  558. }
  559. else
  560. {
  561. inputToTargetProc = config->getProcessor(
  562. inputSpace.c_str(),
  563. targetSpace.c_str());
  564. }
  565. int required_lut = -1;
  566. if(inputToTargetProc->hasChannelCrosstalk())
  567. {
  568. if(shaperSpace.empty())
  569. {
  570. // Has crosstalk, but no prelut, so need 3D LUT
  571. required_lut = HDL_3D;
  572. }
  573. else
  574. {
  575. // Crosstalk with shaper-space
  576. required_lut = HDL_3D1D;
  577. }
  578. }
  579. else
  580. {
  581. // No crosstalk
  582. required_lut = HDL_1D;
  583. }
  584. if(required_lut == -1)
  585. {
  586. // Unnecessary paranoia
  587. throw Exception(
  588. "Internal logic error, LUT type was not determined");
  589. }
  590. // Make prelut
  591. std::vector<float> prelutData;
  592. float fromInStart = 0; // for "From:" part of header
  593. float fromInEnd = 1;
  594. if(required_lut == HDL_3D1D)
  595. {
  596. // TODO: Later we only grab the green channel for the prelut,
  597. // should ensure the prelut is monochromatic somehow?
  598. ConstProcessorRcPtr inputToShaperProc = config->getProcessor(
  599. inputSpace.c_str(),
  600. shaperSpace.c_str());
  601. if(inputToShaperProc->hasChannelCrosstalk())
  602. {
  603. // TODO: Automatically turn shaper into
  604. // non-crosstalked version?
  605. std::ostringstream os;
  606. os << "The specified shaperSpace, '" << baker.getShaperSpace();
  607. os << "' has channel crosstalk, which is not appropriate for";
  608. os << " shapers. Please select an alternate shaper space or";
  609. os << " omit this option.";
  610. throw Exception(os.str().c_str());
  611. }
  612. // Calculate min/max value
  613. {
  614. // Get input value of 1.0 in shaper space, as this
  615. // is the higest value that is transformed by the
  616. // cube (e.g for a generic lin-to-log trasnform,
  617. // what the log value 1.0 is in linear).
  618. ConstProcessorRcPtr shaperToInputProc = config->getProcessor(
  619. shaperSpace.c_str(),
  620. inputSpace.c_str());
  621. float minval[3] = {0.0f, 0.0f, 0.0f};
  622. float maxval[3] = {1.0f, 1.0f, 1.0f};
  623. shaperToInputProc->applyRGB(minval);
  624. shaperToInputProc->applyRGB(maxval);
  625. // Grab green channel, as this is the one used later
  626. fromInStart = minval[1];
  627. fromInEnd = maxval[1];
  628. }
  629. // Generate the identity prelut values, then apply the transform.
  630. // Prelut is linearly sampled from fromInStart to fromInEnd
  631. prelutData.resize(shaperSize*3);
  632. for (int i = 0; i < shaperSize; ++i)
  633. {
  634. const float x = (float)(double(i) / double(shaperSize - 1));
  635. float cur_value = lerpf(fromInStart, fromInEnd, x);
  636. prelutData[3*i+0] = cur_value;
  637. prelutData[3*i+1] = cur_value;
  638. prelutData[3*i+2] = cur_value;
  639. }
  640. PackedImageDesc prelutImg(&prelutData[0], shaperSize, 1, 3);
  641. inputToShaperProc->apply(prelutImg);
  642. }
  643. // TODO: Do same "auto prelut" input-space allocation as FileFormatCSP?
  644. // Make 3D LUT
  645. std::vector<float> cubeData;
  646. if(required_lut == HDL_3D || required_lut == HDL_3D1D)
  647. {
  648. cubeData.resize(cubeSize*cubeSize*cubeSize*3);
  649. GenerateIdentityLut3D(&cubeData[0], cubeSize, 3, LUT3DORDER_FAST_RED);
  650. PackedImageDesc cubeImg(&cubeData[0], cubeSize*cubeSize*cubeSize, 1, 3);
  651. ConstProcessorRcPtr cubeProc;
  652. if(required_lut == HDL_3D1D)
  653. {
  654. // Prelut goes from input-to-shaper, so cube goes from shaper-to-target
  655. if (!looks.empty())
  656. {
  657. LookTransformRcPtr transform = LookTransform::Create();
  658. transform->setLooks(looks.c_str());
  659. transform->setSrc(inputSpace.c_str());
  660. transform->setDst(targetSpace.c_str());
  661. cubeProc = config->getProcessor(transform,
  662. TRANSFORM_DIR_FORWARD);
  663. }
  664. else
  665. {
  666. cubeProc = config->getProcessor(shaperSpace.c_str(),
  667. targetSpace.c_str());
  668. }
  669. }
  670. else
  671. {
  672. // No prelut, so cube goes from input-to-target
  673. cubeProc = inputToTargetProc;
  674. }
  675. cubeProc->apply(cubeImg);
  676. }
  677. // Make 1D LUT
  678. std::vector<float> onedData;
  679. if(required_lut == HDL_1D)
  680. {
  681. onedData.resize(onedSize * 3);
  682. GenerateIdentityLut1D(&onedData[0], onedSize, 3);
  683. PackedImageDesc onedImg(&onedData[0], onedSize, 1, 3);
  684. inputToTargetProc->apply(onedImg);
  685. }
  686. // Write the file contents
  687. ostream << "Version\t\t" << required_lut << "\n";
  688. ostream << "Format\t\t" << "any" << "\n";
  689. ostream << "Type\t\t";
  690. if(required_lut == HDL_1D)
  691. ostream << "RGB";
  692. if(required_lut == HDL_3D)
  693. ostream << "3D";
  694. if(required_lut == HDL_3D1D)
  695. ostream << "3D+1D";
  696. ostream << "\n";
  697. ostream << "From\t\t" << fromInStart << " " << fromInEnd << "\n";
  698. ostream << "To\t\t" << 0.0f << " " << 1.0f << "\n";
  699. ostream << "Black\t\t" << 0.0f << "\n";
  700. ostream << "White\t\t" << 1.0f << "\n";
  701. if(required_lut == HDL_3D1D)
  702. ostream << "Length\t\t" << cubeSize << " " << shaperSize << "\n";
  703. if(required_lut == HDL_3D)
  704. ostream << "Length\t\t" << cubeSize << "\n";
  705. if(required_lut == HDL_1D)
  706. ostream << "Length\t\t" << onedSize << "\n";
  707. ostream << "LUT:\n";
  708. // Write prelut
  709. if(required_lut == HDL_3D1D)
  710. {
  711. ostream << "Pre {\n";
  712. for(int i=0; i < shaperSize; ++i)
  713. {
  714. // Grab green channel from RGB prelut
  715. ostream << "\t" << prelutData[i*3+1] << "\n";
  716. }
  717. ostream << "}\n";
  718. }
  719. // Write "3D {" part of output of 3D+1D LUT
  720. if(required_lut == HDL_3D1D)
  721. {
  722. ostream << "3D {\n";
  723. }
  724. // Write the slightly-different "{" without line for the 3D-only LUT
  725. if(required_lut == HDL_3D)
  726. {
  727. ostream << " {\n";
  728. }
  729. // Write the cube data after the "{"
  730. if(required_lut == HDL_3D || required_lut == HDL_3D1D)
  731. {
  732. for(int i=0; i < cubeSize*cubeSize*cubeSize; ++i)
  733. {
  734. // TODO: Original baker code clamped values to
  735. // 1.0, was this necessary/desirable?
  736. ostream << "\t" << cubeData[3*i+0];
  737. ostream << " " << cubeData[3*i+1];
  738. ostream << " " << cubeData[3*i+2] << "\n";
  739. }
  740. // Write closing "}"
  741. ostream << " }\n";
  742. }
  743. // Write out channels for 1D LUT
  744. if(required_lut == HDL_1D)
  745. {
  746. ostream << "R {\n";
  747. for(int i=0; i < onedSize; ++i)
  748. ostream << "\t" << onedData[i*3+0] << "\n";
  749. ostream << "}\n";
  750. ostream << "G {\n";
  751. for(int i=0; i < onedSize; ++i)
  752. ostream << "\t" << onedData[i*3+1] << "\n";
  753. ostream << "}\n";
  754. ostream << "B {\n";
  755. for(int i=0; i < onedSize; ++i)
  756. ostream << "\t" << onedData[i*3+2] << "\n";
  757. ostream << "}\n";
  758. }
  759. }
  760. void
  761. LocalFileFormat::BuildFileOps(OpRcPtrVec & ops,
  762. const Config& /*config*/,
  763. const ConstContextRcPtr & /*context*/,
  764. CachedFileRcPtr untypedCachedFile,
  765. const FileTransform& fileTransform,
  766. TransformDirection dir) const
  767. {
  768. CachedFileHDLRcPtr cachedFile = DynamicPtrCast<CachedFileHDL>(untypedCachedFile);
  769. // This should never happen.
  770. if(!cachedFile)
  771. {
  772. std::ostringstream os;
  773. os << "Cannot build Houdini Op. Invalid cache type.";
  774. throw Exception(os.str().c_str());
  775. }
  776. TransformDirection newDir = CombineTransformDirections(dir,
  777. fileTransform.getDirection());
  778. if(newDir == TRANSFORM_DIR_FORWARD) {
  779. if(cachedFile->hdltype == "c")
  780. {
  781. CreateLut1DOp(ops, cachedFile->lut1D,
  782. fileTransform.getInterpolation(), newDir);
  783. }
  784. else if(cachedFile->hdltype == "3d")
  785. {
  786. CreateLut3DOp(ops, cachedFile->lut3D,
  787. fileTransform.getInterpolation(), newDir);
  788. }
  789. else if(cachedFile->hdltype == "3d+1d")
  790. {
  791. CreateLut1DOp(ops, cachedFile->lut1D,
  792. fileTransform.getInterpolation(), newDir);
  793. CreateLut3DOp(ops, cachedFile->lut3D,
  794. fileTransform.getInterpolation(), newDir);
  795. }
  796. else
  797. {
  798. throw Exception("Unhandled hdltype while creating forward ops");
  799. }
  800. } else if(newDir == TRANSFORM_DIR_INVERSE) {
  801. if(cachedFile->hdltype == "c")
  802. {
  803. CreateLut1DOp(ops, cachedFile->lut1D,
  804. fileTransform.getInterpolation(), newDir);
  805. }
  806. else if(cachedFile->hdltype == "3d")
  807. {
  808. CreateLut3DOp(ops, cachedFile->lut3D,
  809. fileTransform.getInterpolation(), newDir);
  810. }
  811. else if(cachedFile->hdltype == "3d+1d")
  812. {
  813. CreateLut3DOp(ops, cachedFile->lut3D,
  814. fileTransform.getInterpolation(), newDir);
  815. CreateLut1DOp(ops, cachedFile->lut1D,
  816. fileTransform.getInterpolation(), newDir);
  817. }
  818. else
  819. {
  820. throw Exception("Unhandled hdltype while creating reverse ops");
  821. }
  822. }
  823. return;
  824. }
  825. }
  826. FileFormat * CreateFileFormatHDL()
  827. {
  828. return new LocalFileFormat();
  829. }
  830. }
  831. OCIO_NAMESPACE_EXIT
  832. ///////////////////////////////////////////////////////////////////////////////
  833. #ifdef OCIO_UNIT_TEST
  834. namespace OCIO = OCIO_NAMESPACE;
  835. #include "UnitTest.h"
  836. OIIO_ADD_TEST(FileFormatHDL, Read1D)
  837. {
  838. std::ostringstream strebuf;
  839. strebuf << "Version\t\t1" << "\n";
  840. strebuf << "Format\t\tany" << "\n";
  841. strebuf << "Type\t\tC" << "\n";
  842. strebuf << "From\t\t0.1 3.2" << "\n";
  843. strebuf << "To\t\t0 1" << "\n";
  844. strebuf << "Black\t\t0" << "\n";
  845. strebuf << "White\t\t0.99" << "\n";
  846. strebuf << "Length\t\t9" << "\n";
  847. strebuf << "LUT:" << "\n";
  848. strebuf << "RGB {" << "\n";
  849. strebuf << "\t0" << "\n";
  850. strebuf << "\t0.000977517" << "\n";
  851. strebuf << "\t0.00195503" << "\n";
  852. strebuf << "\t0.00293255" << "\n";
  853. strebuf << "\t0.00391007" << "\n";
  854. strebuf << "\t0.00488759" << "\n";
  855. strebuf << "\t0.0058651" << "\n";
  856. strebuf << "\t0.999022" << "\n";
  857. strebuf << "\t1.67 }" << "\n";
  858. //
  859. float from_min = 0.1f;
  860. float from_max = 3.2f;
  861. float to_min = 0.0f;
  862. float to_max = 1.0f;
  863. float black = 0.0f;
  864. float white = 0.99f;
  865. float lut1d[9] = { 0.0f, 0.000977517f, 0.00195503f, 0.00293255f,
  866. 0.00391007f, 0.00488759f, 0.0058651f, 0.999022f, 1.67f };
  867. std::istringstream simple3D1D;
  868. simple3D1D.str(strebuf.str());
  869. // Load file
  870. OCIO::LocalFileFormat tester;
  871. OCIO::CachedFileRcPtr cachedFile = tester.Read(simple3D1D);
  872. OCIO::CachedFileHDLRcPtr lut = OCIO::DynamicPtrCast<OCIO::CachedFileHDL>(cachedFile);
  873. //
  874. OIIO_CHECK_EQUAL(to_min, lut->to_min);
  875. OIIO_CHECK_EQUAL(to_max, lut->to_max);
  876. OIIO_CHECK_EQUAL(black, lut->hdlblack);
  877. OIIO_CHECK_EQUAL(white, lut->hdlwhite);
  878. // check 1D data (each channel has the same data)
  879. for(int c = 0; c < 3; ++c) {
  880. OIIO_CHECK_EQUAL(from_min, lut->lut1D->from_min[c]);
  881. OIIO_CHECK_EQUAL(from_max, lut->lut1D->from_max[c]);
  882. OIIO_CHECK_EQUAL(9, lut->lut1D->luts[c].size());
  883. for(unsigned int i = 0; i < lut->lut1D->luts[c].size(); ++i) {
  884. OIIO_CHECK_EQUAL(lut1d[i], lut->lut1D->luts[c][i]);
  885. }
  886. }
  887. }
  888. OIIO_ADD_TEST(FileFormatHDL, Bake1D)
  889. {
  890. OCIO::ConfigRcPtr config = OCIO::Config::Create();
  891. // Add lnf space
  892. {
  893. OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create();
  894. cs->setName("lnf");
  895. cs->setFamily("lnf");
  896. config->addColorSpace(cs);
  897. config->setRole(OCIO::ROLE_REFERENCE, cs->getName());
  898. }
  899. // Add target space
  900. {
  901. OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create();
  902. cs->setName("target");
  903. cs->setFamily("target");
  904. OCIO::CDLTransformRcPtr transform1 = OCIO::CDLTransform::Create();
  905. float rgb[3] = {0.1f, 0.1f, 0.1f};
  906. transform1->setOffset(rgb);
  907. cs->setTransform(transform1, OCIO::COLORSPACE_DIR_FROM_REFERENCE);
  908. config->addColorSpace(cs);
  909. }
  910. std::string bout =
  911. "Version\t\t1\n"
  912. "Format\t\tany\n"
  913. "Type\t\tRGB\n"
  914. "From\t\t0.000000 1.000000\n"
  915. "To\t\t0.000000 1.000000\n"
  916. "Black\t\t0.000000\n"
  917. "White\t\t1.000000\n"
  918. "Length\t\t10\n"
  919. "LUT:\n"
  920. "R {\n"
  921. "\t0.100000\n"
  922. "\t0.211111\n"
  923. "\t0.322222\n"
  924. "\t0.433333\n"
  925. "\t0.544444\n"
  926. "\t0.655556\n"
  927. "\t0.766667\n"
  928. "\t0.877778\n"
  929. "\t0.988889\n"
  930. "\t1.100000\n"
  931. " }\n"
  932. "G {\n"
  933. "\t0.100000\n"
  934. "\t0.211111\n"
  935. "\t0.322222\n"
  936. "\t0.433333\n"
  937. "\t0.544444\n"
  938. "\t0.655556\n"
  939. "\t0.766667\n"
  940. "\t0.877778\n"
  941. "\t0.988889\n"
  942. "\t1.100000\n"
  943. " }\n"
  944. "B {\n"
  945. "\t0.100000\n"
  946. "\t0.211111\n"
  947. "\t0.322222\n"
  948. "\t0.433333\n"
  949. "\t0.544444\n"
  950. "\t0.655556\n"
  951. "\t0.766667\n"
  952. "\t0.877778\n"
  953. "\t0.988889\n"
  954. "\t1.100000\n"
  955. " }\n";
  956. //
  957. OCIO::BakerRcPtr baker = OCIO::Baker::Create();
  958. baker->setConfig(config);
  959. baker->setFormat("houdini");
  960. baker->setInputSpace("lnf");
  961. baker->setTargetSpace("target");
  962. baker->setCubeSize(10); // FIXME: Misusing the cube size to set the 1D LUT size
  963. std::ostringstream output;
  964. baker->bake(output);
  965. //std::cerr << "The LUT: " << std::endl << output.str() << std::endl;
  966. //std::cerr << "Expected:" << std::endl << bout << std::endl;
  967. //
  968. std::vector<std::string> osvec;
  969. OCIO::pystring::splitlines(output.str(), osvec);
  970. std::vector<std::string> resvec;
  971. OCIO::pystring::splitlines(bout, resvec);
  972. OIIO_CHECK_EQUAL(osvec.size(), resvec.size());
  973. for(unsigned int i = 0; i < std::min(osvec.size(), resvec.size()); ++i)
  974. OIIO_CHECK_EQUAL(OCIO::pystring::strip(osvec[i]), OCIO::pystring::strip(resvec[i]));
  975. }
  976. OIIO_ADD_TEST(FileFormatHDL, Read3D)
  977. {
  978. std::ostringstream strebuf;
  979. strebuf << "Version 2" << "\n";
  980. strebuf << "Format any" << "\n";
  981. strebuf << "Type 3D" << "\n";
  982. strebuf << "From 0.2 0.9" << "\n";
  983. strebuf << "To 0.001 0.999" << "\n";
  984. strebuf << "Black 0.002" << "\n";
  985. strebuf << "White 0.98" << "\n";
  986. strebuf << "Length 2" << "\n";
  987. strebuf << "LUT:" << "\n";
  988. strebuf << " {" << "\n";
  989. strebuf << " 0 0 0" << "\n";
  990. strebuf << " 0 0 0" << "\n";
  991. strebuf << " 0 0.390735 2.68116e-28" << "\n";
  992. strebuf << " 0 0.390735 0" << "\n";
  993. strebuf << " 0 0 0" << "\n";
  994. strebuf << " 0 0 0.599397" << "\n";
  995. strebuf << " 0 0.601016 0" << "\n";
  996. strebuf << " 0 0.601016 0.917034" << "\n";
  997. strebuf << " }" << "\n";
  998. std::istringstream simple3D1D;
  999. simple3D1D.str(strebuf.str());
  1000. // Load file
  1001. OCIO::LocalFileFormat tester;
  1002. OCIO::CachedFileRcPtr cachedFile = tester.Read(simple3D1D);
  1003. OCIO::CachedFileHDLRcPtr lut = OCIO::DynamicPtrCast<OCIO::CachedFileHDL>(cachedFile);
  1004. //
  1005. //float from_min = 0.2;
  1006. //float from_max = 0.9;
  1007. float to_min = 0.001f;
  1008. float to_max = 0.999f;
  1009. float black = 0.002f;
  1010. float white = 0.98f;
  1011. float cube[2 * 2 * 2 * 3 ] = {
  1012. 0.f, 0.f, 0.f,
  1013. 0.f, 0.f, 0.f,
  1014. 0.f, 0.390735f, 2.68116e-28f,
  1015. 0.f, 0.390735f, 0.f,
  1016. 0.f, 0.f, 0.f,
  1017. 0.f, 0.f, 0.599397f,
  1018. 0.f, 0.601016f, 0.f,
  1019. 0.f, 0.601016f, 0.917034f };
  1020. //
  1021. OIIO_CHECK_EQUAL(to_min, lut->to_min);
  1022. OIIO_CHECK_EQUAL(to_max, lut->to_max);
  1023. OIIO_CHECK_EQUAL(black, lut->hdlblack);
  1024. OIIO_CHECK_EQUAL(white, lut->hdlwhite);
  1025. // check cube data
  1026. OIIO_CHECK_EQUAL(2*2*2*3, lut->lut3D->lut.size());
  1027. for(unsigned int i = 0; i < lut->lut3D->lut.size(); ++i) {
  1028. OIIO_CHECK_EQUAL(cube[i], lut->lut3D->lut[i]);
  1029. }
  1030. }
  1031. OIIO_ADD_TEST(FileFormatHDL, Bake3D)
  1032. {
  1033. OCIO::ConfigRcPtr config = OCIO::Config::Create();
  1034. // Set luma coef's to simple values
  1035. {
  1036. float lumaCoef[3] = {0.333f, 0.333f, 0.333f};
  1037. config->setDefaultLumaCoefs(lumaCoef);
  1038. }
  1039. // Add lnf space
  1040. {
  1041. OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create();
  1042. cs->setName("lnf");
  1043. cs->setFamily("lnf");
  1044. config->addColorSpace(cs);
  1045. config->setRole(OCIO::ROLE_REFERENCE, cs->getName());
  1046. }
  1047. // Add target space
  1048. {
  1049. OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create();
  1050. cs->setName("target");
  1051. cs->setFamily("target");
  1052. OCIO::CDLTransformRcPtr transform1 = OCIO::CDLTransform::Create();
  1053. // Set saturation to cause channel crosstalk, making a 3D LUT
  1054. transform1->setSat(0.5f);
  1055. cs->setTransform(transform1, OCIO::COLORSPACE_DIR_FROM_REFERENCE);
  1056. config->addColorSpace(cs);
  1057. }
  1058. std::string bout =
  1059. "Version\t\t2\n"
  1060. "Format\t\tany\n"
  1061. "Type\t\t3D\n"
  1062. "From\t\t0.000000 1.000000\n"
  1063. "To\t\t0.000000 1.000000\n"
  1064. "Black\t\t0.000000\n"
  1065. "White\t\t1.000000\n"
  1066. "Length\t\t2\n"
  1067. "LUT:\n"
  1068. " {\n"
  1069. "\t0.000000 0.000000 0.000000\n"
  1070. "\t0.606300 0.106300 0.106300\n"
  1071. "\t0.357600 0.857600 0.357600\n"
  1072. "\t0.963900 0.963900 0.463900\n"
  1073. "\t0.036100 0.036100 0.536100\n"
  1074. "\t0.642400 0.142400 0.642400\n"
  1075. "\t0.393700 0.893700 0.893700\n"
  1076. "\t1.000000 1.000000 1.000000\n"
  1077. " }\n";
  1078. //
  1079. OCIO::BakerRcPtr baker = OCIO::Baker::Create();
  1080. baker->setConfig(config);
  1081. baker->setFormat("houdini");
  1082. baker->setInputSpace("lnf");
  1083. baker->setTargetSpace("target");
  1084. baker->setCubeSize(2);
  1085. std::ostringstream output;
  1086. baker->bake(output);
  1087. //std::cerr << "The LUT: " << std::endl << output.str() << std::endl;
  1088. //std::cerr << "Expected:" << std::endl << bout << std::endl;
  1089. //
  1090. std::vector<std::string> osvec;
  1091. OCIO::pystring::splitlines(output.str(), osvec);
  1092. std::vector<std::string> resvec;
  1093. OCIO::pystring::splitlines(bout, resvec);
  1094. OIIO_CHECK_EQUAL(osvec.size(), resvec.size());
  1095. for(unsigned int i = 0; i < std::min(osvec.size(), resvec.size()); ++i)
  1096. OIIO_CHECK_EQUAL(OCIO::pystring::strip(osvec[i]), OCIO::pystring::strip(resvec[i]));
  1097. }
  1098. OIIO_ADD_TEST(FileFormatHDL, Read3D1D)
  1099. {
  1100. std::ostringstream strebuf;
  1101. strebuf << "Version 3" << "\n";
  1102. strebuf << "Format any" << "\n";
  1103. strebuf << "Type 3D+1D" << "\n";
  1104. strebuf << "From 0.005478 14.080103" << "\n";
  1105. strebuf << "To 0 1" << "\n";
  1106. strebuf << "Black 0" << "\n";
  1107. strebuf << "White 1" << "\n";
  1108. strebuf << "Length 2 10" << "\n";
  1109. strebuf << "LUT:" << "\n";
  1110. strebuf << "Pre {" << "\n";
  1111. strebuf << " 0.994922" << "\n";
  1112. strebuf << " 0.995052" << "\n";
  1113. strebuf << " 0.995181" << "\n";
  1114. strebuf << " 0.995310" << "\n";
  1115. strebuf << " 0.995439" << "\n";
  1116. strebuf << " 0.995568" << "\n";
  1117. strebuf << " 0.995697" << "\n";
  1118. strebuf << " 0.995826" << "\n";
  1119. strebuf << " 0.995954" << "\n";
  1120. strebuf << " 0.996082" << "\n";
  1121. strebuf << "}" << "\n";
  1122. strebuf << "3D {" << "\n";
  1123. strebuf << " 0.093776 0.093776 0.093776" << "\n";
  1124. strebuf << " 0.105219 0.093776 0.093776" << "\n";
  1125. strebuf << " 0.118058 0.093776 0.093776" << "\n";
  1126. strebuf << " 0.132463 0.093776 0.093776" << "\n";
  1127. strebuf << " 0.148626 0.093776 0.093776" << "\n";
  1128. strebuf << " 0.166761 0.093776 0.093776" << "\n";
  1129. strebuf << " 0.187109 0.093776 0.093776" << "\n";
  1130. strebuf << " 0.209939 0.093776 0.093776" << "\n";
  1131. strebuf << "}" << "\n";
  1132. //
  1133. float from_min = 0.005478f;
  1134. float from_max = 14.080103f;
  1135. float to_min = 0.0f;
  1136. float to_max = 1.0f;
  1137. float black = 0.0f;
  1138. float white = 1.0f;
  1139. float prelut[10] = { 0.994922f, 0.995052f, 0.995181f, 0.995310f, 0.995439f,
  1140. 0.995568f, 0.995697f, 0.995826f, 0.995954f, 0.996082f };
  1141. float cube[2 * 2 * 2 * 3 ] = {
  1142. 0.093776f, 0.093776f, 0.093776f,
  1143. 0.105219f, 0.093776f, 0.093776f,
  1144. 0.118058f, 0.093776f, 0.093776f,
  1145. 0.132463f, 0.093776f, 0.093776f,
  1146. 0.148626f, 0.093776f, 0.093776f,
  1147. 0.166761f, 0.093776f, 0.093776f,
  1148. 0.187109f, 0.093776f, 0.093776f,
  1149. 0.209939f, 0.093776f, 0.093776f };
  1150. std::istringstream simple3D1D;
  1151. simple3D1D.str(strebuf.str());
  1152. // Load file
  1153. OCIO::LocalFileFormat tester;
  1154. OCIO::CachedFileRcPtr cachedFile = tester.Read(simple3D1D);
  1155. OCIO::CachedFileHDLRcPtr lut = OCIO::DynamicPtrCast<OCIO::CachedFileHDL>(cachedFile);
  1156. //
  1157. OIIO_CHECK_EQUAL(to_min, lut->to_min);
  1158. OIIO_CHECK_EQUAL(to_max, lut->to_max);
  1159. OIIO_CHECK_EQUAL(black, lut->hdlblack);
  1160. OIIO_CHECK_EQUAL(white, lut->hdlwhite);
  1161. // check prelut data (each channel has the same data)
  1162. for(int c = 0; c < 3; ++c) {
  1163. OIIO_CHECK_EQUAL(from_min, lut->lut1D->from_min[c]);
  1164. OIIO_CHECK_EQUAL(from_max, lut->lut1D->from_max[c]);
  1165. OIIO_CHECK_EQUAL(10, lut->lut1D->luts[c].size());
  1166. for(unsigned int i = 0; i < lut->lut1D->luts[c].size(); ++i) {
  1167. OIIO_CHECK_EQUAL(prelut[i], lut->lut1D->luts[c][i]);
  1168. }
  1169. }
  1170. OIIO_CHECK_EQUAL(2*2*2*3, lut->lut3D->lut.size());
  1171. // check cube data
  1172. for(unsigned int i = 0; i < lut->lut3D->lut.size(); ++i) {
  1173. OIIO_CHECK_EQUAL(cube[i], lut->lut3D->lut[i]);
  1174. }
  1175. }
  1176. OIIO_ADD_TEST(FileFormatHDL, Bake3D1D)
  1177. {
  1178. // check baker output
  1179. OCIO::ConfigRcPtr config = OCIO::Config::Create();
  1180. // Set luma coef's to simple values
  1181. {
  1182. float lumaCoef[3] = {0.333f, 0.333f, 0.333f};
  1183. config->setDefaultLumaCoefs(lumaCoef);
  1184. }
  1185. // Add lnf space
  1186. {
  1187. OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create();
  1188. cs->setName("lnf");
  1189. cs->setFamily("lnf");
  1190. config->addColorSpace(cs);
  1191. config->setRole(OCIO::ROLE_REFERENCE, cs->getName());
  1192. }
  1193. // Add shaper space
  1194. {
  1195. OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create();
  1196. cs->setName("shaper");
  1197. cs->setFamily("shaper");
  1198. OCIO::ExponentTransformRcPtr transform1 = OCIO::ExponentTransform::Create();
  1199. float test[4] = {2.6f, 2.6f, 2.6f, 1.0f};
  1200. transform1->setValue(test);
  1201. cs->setTransform(transform1, OCIO::COLORSPACE_DIR_TO_REFERENCE);
  1202. config->addColorSpace(cs);
  1203. }
  1204. // Add target space
  1205. {
  1206. OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create();
  1207. cs->setName("target");
  1208. cs->setFamily("target");
  1209. OCIO::CDLTransformRcPtr transform1 = OCIO::CDLTransform::Create();
  1210. // Set saturation to cause channel crosstalk, making a 3D LUT
  1211. transform1->setSat(0.5f);
  1212. cs->setTransform(transform1, OCIO::COLORSPACE_DIR_FROM_REFERENCE);
  1213. config->addColorSpace(cs);
  1214. }
  1215. std::string bout =
  1216. "Version\t\t3\n"
  1217. "Format\t\tany\n"
  1218. "Type\t\t3D+1D\n"
  1219. "From\t\t0.000000 1.000000\n"
  1220. "To\t\t0.000000 1.000000\n"
  1221. "Black\t\t0.000000\n"
  1222. "White\t\t1.000000\n"
  1223. "Length\t\t2 10\n"
  1224. "LUT:\n"
  1225. "Pre {\n"
  1226. "\t0.000000\n"
  1227. "\t0.429520\n"
  1228. "\t0.560744\n"
  1229. "\t0.655378\n"
  1230. "\t0.732057\n"
  1231. "\t0.797661\n"
  1232. "\t0.855604\n"
  1233. "\t0.907865\n"
  1234. "\t0.955710\n"
  1235. "\t1.000000\n"
  1236. "}\n"
  1237. "3D {\n"
  1238. "\t0.000000 0.000000 0.000000\n"
  1239. "\t0.606300 0.106300 0.106300\n"
  1240. "\t0.357600 0.857600 0.357600\n"
  1241. "\t0.963900 0.963900 0.463900\n"
  1242. "\t0.036100 0.036100 0.536100\n"
  1243. "\t0.642400 0.142400 0.642400\n"
  1244. "\t0.393700 0.893700 0.893700\n"
  1245. "\t1.000000 1.000000 1.000000\n"
  1246. "}\n";
  1247. //
  1248. OCIO::BakerRcPtr baker = OCIO::Baker::Create();
  1249. baker->setConfig(config);
  1250. baker->setFormat("houdini");
  1251. baker->setInputSpace("lnf");
  1252. baker->setShaperSpace("shaper");
  1253. baker->setTargetSpace("target");
  1254. baker->setShaperSize(10);
  1255. baker->setCubeSize(2);
  1256. std::ostringstream output;
  1257. baker->bake(output);
  1258. //std::cerr << "The LUT: " << std::endl << output.str() << std::endl;
  1259. //std::cerr << "Expected:" << std::endl << bout << std::endl;
  1260. //
  1261. std::vector<std::string> osvec;
  1262. OCIO::pystring::splitlines(output.str(), osvec);
  1263. std::vector<std::string> resvec;
  1264. OCIO::pystring::splitlines(bout, resvec);
  1265. OIIO_CHECK_EQUAL(osvec.size(), resvec.size());
  1266. // TODO: Get this working on osx
  1267. /*
  1268. for(unsigned int i = 0; i < std::min(osvec.size(), resvec.size()); ++i)
  1269. OIIO_CHECK_EQUAL(OCIO::pystring::strip(osvec[i]), OCIO::pystring::strip(resvec[i]));
  1270. */
  1271. }
  1272. #endif // OCIO_BUILD_TESTS