/src/core/Lut3DOp.cpp

http://github.com/imageworks/OpenColorIO · C++ · 982 lines · 708 code · 146 blank · 128 comment · 62 complexity · 07ed781d1b994b1f5987ab679f606137 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 <OpenColorIO/OpenColorIO.h>
  28. #include "HashUtils.h"
  29. #include "Lut3DOp.h"
  30. #include "MathUtils.h"
  31. #include <cmath>
  32. #include <limits>
  33. #include <sstream>
  34. OCIO_NAMESPACE_ENTER
  35. {
  36. Lut3D::Lut3D()
  37. {
  38. for(int i=0; i<3; ++i)
  39. {
  40. from_min[i] = 0.0f;
  41. from_max[i] = 1.0f;
  42. size[i] = 0;
  43. }
  44. }
  45. Lut3DRcPtr Lut3D::Create()
  46. {
  47. return Lut3DRcPtr(new Lut3D());
  48. }
  49. std::string Lut3D::getCacheID() const
  50. {
  51. AutoMutex lock(m_cacheidMutex);
  52. if(lut.empty())
  53. throw Exception("Cannot compute cacheID of invalid Lut3D");
  54. if(!m_cacheID.empty())
  55. return m_cacheID;
  56. md5_state_t state;
  57. md5_byte_t digest[16];
  58. md5_init(&state);
  59. md5_append(&state, (const md5_byte_t *)from_min, 3*sizeof(float));
  60. md5_append(&state, (const md5_byte_t *)from_max, 3*sizeof(float));
  61. md5_append(&state, (const md5_byte_t *)size, 3*sizeof(int));
  62. md5_append(&state, (const md5_byte_t *)&lut[0], (int) (lut.size()*sizeof(float)));
  63. md5_finish(&state, digest);
  64. m_cacheID = GetPrintableHash(digest);
  65. return m_cacheID;
  66. }
  67. namespace
  68. {
  69. // Linear
  70. inline float lerp(float a, float b, float z)
  71. { return (b - a) * z + a; }
  72. inline void lerp_rgb(float* out, float* a, float* b, float* z)
  73. {
  74. out[0] = (b[0] - a[0]) * z[0] + a[0];
  75. out[1] = (b[1] - a[1]) * z[1] + a[1];
  76. out[2] = (b[2] - a[2]) * z[2] + a[2];
  77. }
  78. // Bilinear
  79. inline float lerp(float a, float b, float c, float d, float y, float z)
  80. { return lerp(lerp(a, b, z), lerp(c, d, z), y); }
  81. inline void lerp_rgb(float* out, float* a, float* b, float* c,
  82. float* d, float* y, float* z)
  83. {
  84. float v1[3];
  85. float v2[3];
  86. lerp_rgb(v1, a, b, z);
  87. lerp_rgb(v2, c, d, z);
  88. lerp_rgb(out, v1, v2, y);
  89. }
  90. // Trilinear
  91. inline float lerp(float a, float b, float c, float d,
  92. float e, float f, float g, float h,
  93. float x, float y, float z)
  94. { return lerp(lerp(a,b,c,d,y,z), lerp(e,f,g,h,y,z), x); }
  95. inline void lerp_rgb(float* out, float* a, float* b, float* c, float* d,
  96. float* e, float* f, float* g, float* h,
  97. float* x, float* y, float* z)
  98. {
  99. float v1[3];
  100. float v2[3];
  101. lerp_rgb(v1, a,b,c,d,y,z);
  102. lerp_rgb(v2, e,f,g,h,y,z);
  103. lerp_rgb(out, v1, v2, x);
  104. }
  105. inline float lookupNearest_3D(int rIndex, int gIndex, int bIndex,
  106. int size_red, int size_green, int size_blue,
  107. const float* simple_rgb_lut, int channelIndex)
  108. {
  109. return simple_rgb_lut[ GetLut3DIndex_B(rIndex, gIndex, bIndex,
  110. size_red, size_green, size_blue) + channelIndex];
  111. }
  112. inline void lookupNearest_3D_rgb(float* rgb,
  113. int rIndex, int gIndex, int bIndex,
  114. int size_red, int size_green, int size_blue,
  115. const float* simple_rgb_lut)
  116. {
  117. int offset = GetLut3DIndex_B(rIndex, gIndex, bIndex, size_red, size_green, size_blue);
  118. rgb[0] = simple_rgb_lut[offset];
  119. rgb[1] = simple_rgb_lut[offset+1];
  120. rgb[2] = simple_rgb_lut[offset+2];
  121. }
  122. // Note: This function assumes that minVal is less than maxVal
  123. inline int clamp(float k, float minVal, float maxVal)
  124. {
  125. return static_cast<int>(roundf(std::max(std::min(k, maxVal), minVal)));
  126. }
  127. ///////////////////////////////////////////////////////////////////////
  128. // Nearest Forward
  129. void Lut3D_Nearest(float* rgbaBuffer, long numPixels, const Lut3D & lut)
  130. {
  131. float maxIndex[3];
  132. float mInv[3];
  133. float b[3];
  134. float mInv_x_maxIndex[3];
  135. int lutSize[3];
  136. const float* startPos = &(lut.lut[0]);
  137. for(int i=0; i<3; ++i)
  138. {
  139. maxIndex[i] = (float) (lut.size[i] - 1);
  140. mInv[i] = 1.0f / (lut.from_max[i] - lut.from_min[i]);
  141. b[i] = lut.from_min[i];
  142. mInv_x_maxIndex[i] = (float) (mInv[i] * maxIndex[i]);
  143. lutSize[i] = lut.size[i];
  144. }
  145. int localIndex[3];
  146. for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex)
  147. {
  148. if(isnan(rgbaBuffer[0]) || isnan(rgbaBuffer[1]) || isnan(rgbaBuffer[2]))
  149. {
  150. rgbaBuffer[0] = std::numeric_limits<float>::quiet_NaN();
  151. rgbaBuffer[1] = std::numeric_limits<float>::quiet_NaN();
  152. rgbaBuffer[2] = std::numeric_limits<float>::quiet_NaN();
  153. }
  154. else
  155. {
  156. localIndex[0] = clamp(mInv_x_maxIndex[0] * (rgbaBuffer[0] - b[0]), 0.0f, maxIndex[0]);
  157. localIndex[1] = clamp(mInv_x_maxIndex[1] * (rgbaBuffer[1] - b[1]), 0.0f, maxIndex[1]);
  158. localIndex[2] = clamp(mInv_x_maxIndex[2] * (rgbaBuffer[2] - b[2]), 0.0f, maxIndex[2]);
  159. lookupNearest_3D_rgb(rgbaBuffer, localIndex[0], localIndex[1], localIndex[2],
  160. lutSize[0], lutSize[1], lutSize[2], startPos);
  161. }
  162. rgbaBuffer += 4;
  163. }
  164. }
  165. ///////////////////////////////////////////////////////////////////////
  166. // Linear Forward
  167. void Lut3D_Linear(float* rgbaBuffer, long numPixels, const Lut3D & lut)
  168. {
  169. float maxIndex[3];
  170. float mInv[3];
  171. float b[3];
  172. float mInv_x_maxIndex[3];
  173. int lutSize[3];
  174. const float* startPos = &(lut.lut[0]);
  175. for(int i=0; i<3; ++i)
  176. {
  177. maxIndex[i] = (float) (lut.size[i] - 1);
  178. mInv[i] = 1.0f / (lut.from_max[i] - lut.from_min[i]);
  179. b[i] = lut.from_min[i];
  180. mInv_x_maxIndex[i] = (float) (mInv[i] * maxIndex[i]);
  181. lutSize[i] = lut.size[i];
  182. }
  183. for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex)
  184. {
  185. if(isnan(rgbaBuffer[0]) || isnan(rgbaBuffer[1]) || isnan(rgbaBuffer[2]))
  186. {
  187. rgbaBuffer[0] = std::numeric_limits<float>::quiet_NaN();
  188. rgbaBuffer[1] = std::numeric_limits<float>::quiet_NaN();
  189. rgbaBuffer[2] = std::numeric_limits<float>::quiet_NaN();
  190. }
  191. else
  192. {
  193. float localIndex[3];
  194. int indexLow[3];
  195. int indexHigh[3];
  196. float delta[3];
  197. float a[3];
  198. float b_[3];
  199. float c[3];
  200. float d[3];
  201. float e[3];
  202. float f[3];
  203. float g[3];
  204. float h[4];
  205. float x[4];
  206. float y[4];
  207. float z[4];
  208. localIndex[0] = std::max(std::min(mInv_x_maxIndex[0] * (rgbaBuffer[0] - b[0]), maxIndex[0]), 0.0f);
  209. localIndex[1] = std::max(std::min(mInv_x_maxIndex[1] * (rgbaBuffer[1] - b[1]), maxIndex[1]), 0.0f);
  210. localIndex[2] = std::max(std::min(mInv_x_maxIndex[2] * (rgbaBuffer[2] - b[2]), maxIndex[2]), 0.0f);
  211. indexLow[0] = static_cast<int>(std::floor(localIndex[0]));
  212. indexLow[1] = static_cast<int>(std::floor(localIndex[1]));
  213. indexLow[2] = static_cast<int>(std::floor(localIndex[2]));
  214. indexHigh[0] = static_cast<int>(std::ceil(localIndex[0]));
  215. indexHigh[1] = static_cast<int>(std::ceil(localIndex[1]));
  216. indexHigh[2] = static_cast<int>(std::ceil(localIndex[2]));
  217. delta[0] = localIndex[0] - static_cast<float>(indexLow[0]);
  218. delta[1] = localIndex[1] - static_cast<float>(indexLow[1]);
  219. delta[2] = localIndex[2] - static_cast<float>(indexLow[2]);
  220. // Lookup 8 corners of cube
  221. lookupNearest_3D_rgb(a, indexLow[0], indexLow[1], indexLow[2], lutSize[0], lutSize[1], lutSize[2], startPos);
  222. lookupNearest_3D_rgb(b_, indexLow[0], indexLow[1], indexHigh[2], lutSize[0], lutSize[1], lutSize[2], startPos);
  223. lookupNearest_3D_rgb(c, indexLow[0], indexHigh[1], indexLow[2], lutSize[0], lutSize[1], lutSize[2], startPos);
  224. lookupNearest_3D_rgb(d, indexLow[0], indexHigh[1], indexHigh[2], lutSize[0], lutSize[1], lutSize[2], startPos);
  225. lookupNearest_3D_rgb(e, indexHigh[0], indexLow[1], indexLow[2], lutSize[0], lutSize[1], lutSize[2], startPos);
  226. lookupNearest_3D_rgb(f, indexHigh[0], indexLow[1], indexHigh[2], lutSize[0], lutSize[1], lutSize[2], startPos);
  227. lookupNearest_3D_rgb(g, indexHigh[0], indexHigh[1], indexLow[2], lutSize[0], lutSize[1], lutSize[2], startPos);
  228. lookupNearest_3D_rgb(h, indexHigh[0], indexHigh[1], indexHigh[2], lutSize[0], lutSize[1], lutSize[2], startPos);
  229. // Also store the 3d interpolation coordinates
  230. x[0] = delta[0]; x[1] = delta[0]; x[2] = delta[0];
  231. y[0] = delta[1]; y[1] = delta[1]; y[2] = delta[1];
  232. z[0] = delta[2]; z[1] = delta[2]; z[2] = delta[2];
  233. // Do a trilinear interpolation of the 8 corners
  234. // 4726.8 scanlines/sec
  235. lerp_rgb(rgbaBuffer, a, b_, c, d, e, f, g, h,
  236. x, y, z);
  237. }
  238. rgbaBuffer += 4;
  239. }
  240. }
  241. }
  242. void Lut3D_Tetrahedral(float* rgbaBuffer, long numPixels, const Lut3D & lut)
  243. {
  244. // Tetrahedral interoplation, as described by:
  245. // http://www.filmlight.ltd.uk/pdf/whitepapers/FL-TL-TN-0057-SoftwareLib.pdf
  246. // http://blogs.mathworks.com/steve/2006/11/24/tetrahedral-interpolation-for-colorspace-conversion/
  247. // http://www.hpl.hp.com/techreports/98/HPL-98-95.html
  248. float maxIndex[3];
  249. float mInv[3];
  250. float b[3];
  251. float mInv_x_maxIndex[3];
  252. int lutSize[3];
  253. const float* startPos = &(lut.lut[0]);
  254. for(int i=0; i<3; ++i)
  255. {
  256. maxIndex[i] = (float) (lut.size[i] - 1);
  257. mInv[i] = 1.0f / (lut.from_max[i] - lut.from_min[i]);
  258. b[i] = lut.from_min[i];
  259. mInv_x_maxIndex[i] = (float) (mInv[i] * maxIndex[i]);
  260. lutSize[i] = lut.size[i];
  261. }
  262. for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex)
  263. {
  264. if(isnan(rgbaBuffer[0]) || isnan(rgbaBuffer[1]) || isnan(rgbaBuffer[2]))
  265. {
  266. rgbaBuffer[0] = std::numeric_limits<float>::quiet_NaN();
  267. rgbaBuffer[1] = std::numeric_limits<float>::quiet_NaN();
  268. rgbaBuffer[2] = std::numeric_limits<float>::quiet_NaN();
  269. }
  270. else
  271. {
  272. float localIndex[3];
  273. int indexLow[3];
  274. int indexHigh[3];
  275. float delta[3];
  276. // Same index/delta calculation as linear interpolation
  277. localIndex[0] = std::max(std::min(mInv_x_maxIndex[0] * (rgbaBuffer[0] - b[0]), maxIndex[0]), 0.0f);
  278. localIndex[1] = std::max(std::min(mInv_x_maxIndex[1] * (rgbaBuffer[1] - b[1]), maxIndex[1]), 0.0f);
  279. localIndex[2] = std::max(std::min(mInv_x_maxIndex[2] * (rgbaBuffer[2] - b[2]), maxIndex[2]), 0.0f);
  280. indexLow[0] = static_cast<int>(std::floor(localIndex[0]));
  281. indexLow[1] = static_cast<int>(std::floor(localIndex[1]));
  282. indexLow[2] = static_cast<int>(std::floor(localIndex[2]));
  283. indexHigh[0] = static_cast<int>(std::ceil(localIndex[0]));
  284. indexHigh[1] = static_cast<int>(std::ceil(localIndex[1]));
  285. indexHigh[2] = static_cast<int>(std::ceil(localIndex[2]));
  286. delta[0] = localIndex[0] - static_cast<float>(indexLow[0]);
  287. delta[1] = localIndex[1] - static_cast<float>(indexLow[1]);
  288. delta[2] = localIndex[2] - static_cast<float>(indexLow[2]);
  289. // Rebind for consistency with Truelight paper
  290. float fx = delta[0];
  291. float fy = delta[1];
  292. float fz = delta[2];
  293. // Compute index into LUT for surrounding corners
  294. const int n000 = GetLut3DIndex_B(indexLow[0], indexLow[1], indexLow[2],
  295. lutSize[0], lutSize[1], lutSize[2]);
  296. const int n100 = GetLut3DIndex_B(indexHigh[0], indexLow[1], indexLow[2],
  297. lutSize[0], lutSize[1], lutSize[2]);
  298. const int n010 = GetLut3DIndex_B(indexLow[0], indexHigh[1], indexLow[2],
  299. lutSize[0], lutSize[1], lutSize[2]);
  300. const int n001 = GetLut3DIndex_B(indexLow[0], indexLow[1], indexHigh[2],
  301. lutSize[0], lutSize[1], lutSize[2]);
  302. const int n110 = GetLut3DIndex_B(indexHigh[0], indexHigh[1], indexLow[2],
  303. lutSize[0], lutSize[1], lutSize[2]);
  304. const int n101 = GetLut3DIndex_B(indexHigh[0], indexLow[1], indexHigh[2],
  305. lutSize[0], lutSize[1], lutSize[2]);
  306. const int n011 = GetLut3DIndex_B(indexLow[0], indexHigh[1], indexHigh[2],
  307. lutSize[0], lutSize[1], lutSize[2]);
  308. const int n111 = GetLut3DIndex_B(indexHigh[0], indexHigh[1], indexHigh[2],
  309. lutSize[0], lutSize[1], lutSize[2]);
  310. if (fx > fy) {
  311. if (fy > fz) {
  312. rgbaBuffer[0] =
  313. (1-fx) * startPos[n000] +
  314. (fx-fy) * startPos[n100] +
  315. (fy-fz) * startPos[n110] +
  316. (fz) * startPos[n111];
  317. rgbaBuffer[1] =
  318. (1-fx) * startPos[n000+1] +
  319. (fx-fy) * startPos[n100+1] +
  320. (fy-fz) * startPos[n110+1] +
  321. (fz) * startPos[n111+1];
  322. rgbaBuffer[2] =
  323. (1-fx) * startPos[n000+2] +
  324. (fx-fy) * startPos[n100+2] +
  325. (fy-fz) * startPos[n110+2] +
  326. (fz) * startPos[n111+2];
  327. }
  328. else if (fx > fz)
  329. {
  330. rgbaBuffer[0] =
  331. (1-fx) * startPos[n000] +
  332. (fx-fz) * startPos[n100] +
  333. (fz-fy) * startPos[n101] +
  334. (fy) * startPos[n111];
  335. rgbaBuffer[1] =
  336. (1-fx) * startPos[n000+1] +
  337. (fx-fz) * startPos[n100+1] +
  338. (fz-fy) * startPos[n101+1] +
  339. (fy) * startPos[n111+1];
  340. rgbaBuffer[2] =
  341. (1-fx) * startPos[n000+2] +
  342. (fx-fz) * startPos[n100+2] +
  343. (fz-fy) * startPos[n101+2] +
  344. (fy) * startPos[n111+2];
  345. }
  346. else
  347. {
  348. rgbaBuffer[0] =
  349. (1-fz) * startPos[n000] +
  350. (fz-fx) * startPos[n001] +
  351. (fx-fy) * startPos[n101] +
  352. (fy) * startPos[n111];
  353. rgbaBuffer[1] =
  354. (1-fz) * startPos[n000+1] +
  355. (fz-fx) * startPos[n001+1] +
  356. (fx-fy) * startPos[n101+1] +
  357. (fy) * startPos[n111+1];
  358. rgbaBuffer[2] =
  359. (1-fz) * startPos[n000+2] +
  360. (fz-fx) * startPos[n001+2] +
  361. (fx-fy) * startPos[n101+2] +
  362. (fy) * startPos[n111+2];
  363. }
  364. }
  365. else
  366. {
  367. if (fz > fy)
  368. {
  369. rgbaBuffer[0] =
  370. (1-fz) * startPos[n000] +
  371. (fz-fy) * startPos[n001] +
  372. (fy-fx) * startPos[n011] +
  373. (fx) * startPos[n111];
  374. rgbaBuffer[1] =
  375. (1-fz) * startPos[n000+1] +
  376. (fz-fy) * startPos[n001+1] +
  377. (fy-fx) * startPos[n011+1] +
  378. (fx) * startPos[n111+1];
  379. rgbaBuffer[2] =
  380. (1-fz) * startPos[n000+2] +
  381. (fz-fy) * startPos[n001+2] +
  382. (fy-fx) * startPos[n011+2] +
  383. (fx) * startPos[n111+2];
  384. }
  385. else if (fz > fx)
  386. {
  387. rgbaBuffer[0] =
  388. (1-fy) * startPos[n000] +
  389. (fy-fz) * startPos[n010] +
  390. (fz-fx) * startPos[n011] +
  391. (fx) * startPos[n111];
  392. rgbaBuffer[1] =
  393. (1-fy) * startPos[n000+1] +
  394. (fy-fz) * startPos[n010+1] +
  395. (fz-fx) * startPos[n011+1] +
  396. (fx) * startPos[n111+1];
  397. rgbaBuffer[2] =
  398. (1-fy) * startPos[n000+2] +
  399. (fy-fz) * startPos[n010+2] +
  400. (fz-fx) * startPos[n011+2] +
  401. (fx) * startPos[n111+2];
  402. }
  403. else
  404. {
  405. rgbaBuffer[0] =
  406. (1-fy) * startPos[n000] +
  407. (fy-fx) * startPos[n010] +
  408. (fx-fz) * startPos[n110] +
  409. (fz) * startPos[n111];
  410. rgbaBuffer[1] =
  411. (1-fy) * startPos[n000+1] +
  412. (fy-fx) * startPos[n010+1] +
  413. (fx-fz) * startPos[n110+1] +
  414. (fz) * startPos[n111+1];
  415. rgbaBuffer[2] =
  416. (1-fy) * startPos[n000+2] +
  417. (fy-fx) * startPos[n010+2] +
  418. (fx-fz) * startPos[n110+2] +
  419. (fz) * startPos[n111+2];
  420. }
  421. }
  422. } // !isnan
  423. rgbaBuffer += 4;
  424. }
  425. }
  426. void GenerateIdentityLut3D(float* img, int edgeLen, int numChannels, Lut3DOrder lut3DOrder)
  427. {
  428. if(!img) return;
  429. if(numChannels < 3)
  430. {
  431. throw Exception("Cannot generate idenitity 3d lut with less than 3 channels.");
  432. }
  433. float c = 1.0f / ((float)edgeLen - 1.0f);
  434. if(lut3DOrder == LUT3DORDER_FAST_RED)
  435. {
  436. for(int i=0; i<edgeLen*edgeLen*edgeLen; i++)
  437. {
  438. img[numChannels*i+0] = (float)(i%edgeLen) * c;
  439. img[numChannels*i+1] = (float)((i/edgeLen)%edgeLen) * c;
  440. img[numChannels*i+2] = (float)((i/edgeLen/edgeLen)%edgeLen) * c;
  441. }
  442. }
  443. else if(lut3DOrder == LUT3DORDER_FAST_BLUE)
  444. {
  445. for(int i=0; i<edgeLen*edgeLen*edgeLen; i++)
  446. {
  447. img[numChannels*i+0] = (float)((i/edgeLen/edgeLen)%edgeLen) * c;
  448. img[numChannels*i+1] = (float)((i/edgeLen)%edgeLen) * c;
  449. img[numChannels*i+2] = (float)(i%edgeLen) * c;
  450. }
  451. }
  452. else
  453. {
  454. throw Exception("Unknown Lut3DOrder.");
  455. }
  456. }
  457. int Get3DLutEdgeLenFromNumPixels(int numPixels)
  458. {
  459. int dim = static_cast<int>(roundf(powf((float) numPixels, 1.0f/3.0f)));
  460. if(dim*dim*dim != numPixels)
  461. {
  462. std::ostringstream os;
  463. os << "Cannot infer 3D Lut size. ";
  464. os << numPixels << " element(s) does not correspond to a ";
  465. os << "unform cube edge length. (nearest edge length is ";
  466. os << dim << ").";
  467. throw Exception(os.str().c_str());
  468. }
  469. return dim;
  470. }
  471. namespace
  472. {
  473. class Lut3DOp : public Op
  474. {
  475. public:
  476. Lut3DOp(Lut3DRcPtr lut,
  477. Interpolation interpolation,
  478. TransformDirection direction);
  479. virtual ~Lut3DOp();
  480. virtual OpRcPtr clone() const;
  481. virtual std::string getInfo() const;
  482. virtual std::string getCacheID() const;
  483. virtual bool isNoOp() const;
  484. virtual bool isSameType(const OpRcPtr & op) const;
  485. virtual bool isInverse(const OpRcPtr & op) const;
  486. virtual bool hasChannelCrosstalk() const;
  487. virtual void finalize();
  488. virtual void apply(float* rgbaBuffer, long numPixels) const;
  489. virtual bool supportsGpuShader() const;
  490. virtual void writeGpuShader(std::ostream & shader,
  491. const std::string & pixelName,
  492. const GpuShaderDesc & shaderDesc) const;
  493. private:
  494. Lut3DRcPtr m_lut;
  495. Interpolation m_interpolation;
  496. TransformDirection m_direction;
  497. // Set in finalize
  498. std::string m_cacheID;
  499. };
  500. typedef OCIO_SHARED_PTR<Lut3DOp> Lut3DOpRcPtr;
  501. Lut3DOp::Lut3DOp(Lut3DRcPtr lut,
  502. Interpolation interpolation,
  503. TransformDirection direction):
  504. Op(),
  505. m_lut(lut),
  506. m_interpolation(interpolation),
  507. m_direction(direction)
  508. {
  509. }
  510. OpRcPtr Lut3DOp::clone() const
  511. {
  512. OpRcPtr op = OpRcPtr(new Lut3DOp(m_lut, m_interpolation, m_direction));
  513. return op;
  514. }
  515. Lut3DOp::~Lut3DOp()
  516. { }
  517. std::string Lut3DOp::getInfo() const
  518. {
  519. return "<Lut3DOp>";
  520. }
  521. std::string Lut3DOp::getCacheID() const
  522. {
  523. return m_cacheID;
  524. }
  525. // TODO: compute real value for isNoOp
  526. bool Lut3DOp::isNoOp() const
  527. {
  528. return false;
  529. }
  530. bool Lut3DOp::isSameType(const OpRcPtr & op) const
  531. {
  532. Lut3DOpRcPtr typedRcPtr = DynamicPtrCast<Lut3DOp>(op);
  533. if(!typedRcPtr) return false;
  534. return true;
  535. }
  536. bool Lut3DOp::isInverse(const OpRcPtr & op) const
  537. {
  538. Lut3DOpRcPtr typedRcPtr = DynamicPtrCast<Lut3DOp>(op);
  539. if(!typedRcPtr) return false;
  540. if(GetInverseTransformDirection(m_direction) != typedRcPtr->m_direction)
  541. return false;
  542. return (m_lut->getCacheID() == typedRcPtr->m_lut->getCacheID());
  543. }
  544. // TODO: compute real value for hasChannelCrosstalk
  545. bool Lut3DOp::hasChannelCrosstalk() const
  546. {
  547. return true;
  548. }
  549. void Lut3DOp::finalize()
  550. {
  551. if(m_direction != TRANSFORM_DIR_FORWARD)
  552. {
  553. std::ostringstream os;
  554. os << "3D Luts can only be applied in the forward direction. ";
  555. os << "(" << TransformDirectionToString(m_direction) << ")";
  556. os << " specified.";
  557. throw Exception(os.str().c_str());
  558. }
  559. // Validate the requested interpolation type
  560. switch(m_interpolation)
  561. {
  562. // These are the allowed values.
  563. case INTERP_NEAREST:
  564. case INTERP_LINEAR:
  565. case INTERP_TETRAHEDRAL:
  566. break;
  567. case INTERP_BEST:
  568. m_interpolation = INTERP_LINEAR;
  569. break;
  570. case INTERP_UNKNOWN:
  571. throw Exception("Cannot apply Lut3DOp, unspecified interpolation.");
  572. break;
  573. default:
  574. throw Exception("Cannot apply Lut3DOp, invalid interpolation specified.");
  575. }
  576. for(int i=0; i<3; ++i)
  577. {
  578. if(m_lut->size[i] == 0)
  579. {
  580. throw Exception("Cannot apply Lut3DOp, lut object is empty.");
  581. }
  582. // TODO if from_min[i] == from_max[i]
  583. }
  584. if(m_lut->size[0]*m_lut->size[1]*m_lut->size[2] * 3 != (int)m_lut->lut.size())
  585. {
  586. throw Exception("Cannot apply Lut3DOp, specified size does not match data.");
  587. }
  588. // Create the cacheID
  589. std::ostringstream cacheIDStream;
  590. cacheIDStream << "<Lut3DOp ";
  591. cacheIDStream << m_lut->getCacheID() << " ";
  592. cacheIDStream << InterpolationToString(m_interpolation) << " ";
  593. cacheIDStream << TransformDirectionToString(m_direction) << " ";
  594. cacheIDStream << ">";
  595. m_cacheID = cacheIDStream.str();
  596. }
  597. void Lut3DOp::apply(float* rgbaBuffer, long numPixels) const
  598. {
  599. if(m_interpolation == INTERP_NEAREST)
  600. {
  601. Lut3D_Nearest(rgbaBuffer, numPixels, *m_lut);
  602. }
  603. else if(m_interpolation == INTERP_LINEAR)
  604. {
  605. Lut3D_Linear(rgbaBuffer, numPixels, *m_lut);
  606. }
  607. else if(m_interpolation == INTERP_TETRAHEDRAL)
  608. {
  609. Lut3D_Tetrahedral(rgbaBuffer, numPixels, *m_lut);
  610. }
  611. }
  612. bool Lut3DOp::supportsGpuShader() const
  613. {
  614. return false;
  615. }
  616. void Lut3DOp::writeGpuShader(std::ostream & /*shader*/,
  617. const std::string & /*pixelName*/,
  618. const GpuShaderDesc & /*shaderDesc*/) const
  619. {
  620. throw Exception("Lut3DOp does not support analytical shader generation.");
  621. }
  622. }
  623. void CreateLut3DOp(OpRcPtrVec & ops,
  624. Lut3DRcPtr lut,
  625. Interpolation interpolation,
  626. TransformDirection direction)
  627. {
  628. ops.push_back( Lut3DOpRcPtr(new Lut3DOp(lut, interpolation, direction)) );
  629. }
  630. }
  631. OCIO_NAMESPACE_EXIT
  632. ///////////////////////////////////////////////////////////////////////////////
  633. #ifdef OCIO_UNIT_TEST
  634. #include <cstring>
  635. #include <cstdlib>
  636. #include <sys/time.h>
  637. namespace OCIO = OCIO_NAMESPACE;
  638. #include "UnitTest.h"
  639. OIIO_ADD_TEST(Lut3DOp, NanInfValueCheck)
  640. {
  641. OCIO::Lut3DRcPtr lut = OCIO::Lut3D::Create();
  642. lut->from_min[0] = 0.0f;
  643. lut->from_min[1] = 0.0f;
  644. lut->from_min[2] = 0.0f;
  645. lut->from_max[0] = 1.0f;
  646. lut->from_max[1] = 1.0f;
  647. lut->from_max[2] = 1.0f;
  648. lut->size[0] = 3;
  649. lut->size[1] = 3;
  650. lut->size[2] = 3;
  651. lut->lut.resize(lut->size[0]*lut->size[1]*lut->size[2]*3);
  652. GenerateIdentityLut3D(&lut->lut[0], lut->size[0], 3, OCIO::LUT3DORDER_FAST_RED);
  653. for(unsigned int i=0; i<lut->lut.size(); ++i)
  654. {
  655. lut->lut[i] = powf(lut->lut[i], 2.0f);
  656. }
  657. const float reference[4] = { std::numeric_limits<float>::signaling_NaN(),
  658. std::numeric_limits<float>::quiet_NaN(),
  659. std::numeric_limits<float>::infinity(),
  660. -std::numeric_limits<float>::infinity() };
  661. float color[4];
  662. memcpy(color, reference, 4*sizeof(float));
  663. OCIO::Lut3D_Nearest(color, 1, *lut);
  664. memcpy(color, reference, 4*sizeof(float));
  665. OCIO::Lut3D_Linear(color, 1, *lut);
  666. }
  667. OIIO_ADD_TEST(Lut3DOp, ValueCheck)
  668. {
  669. OCIO::Lut3DRcPtr lut = OCIO::Lut3D::Create();
  670. lut->from_min[0] = 0.0f;
  671. lut->from_min[1] = 0.0f;
  672. lut->from_min[2] = 0.0f;
  673. lut->from_max[0] = 1.0f;
  674. lut->from_max[1] = 1.0f;
  675. lut->from_max[2] = 1.0f;
  676. lut->size[0] = 32;
  677. lut->size[1] = 32;
  678. lut->size[2] = 32;
  679. lut->lut.resize(lut->size[0]*lut->size[1]*lut->size[2]*3);
  680. GenerateIdentityLut3D(&lut->lut[0], lut->size[0], 3, OCIO::LUT3DORDER_FAST_RED);
  681. for(unsigned int i=0; i<lut->lut.size(); ++i)
  682. {
  683. lut->lut[i] = powf(lut->lut[i], 2.0f);
  684. }
  685. const float reference[] = { 0.0f, 0.2f, 0.3f, 1.0f,
  686. 0.1234f, 0.4567f, 0.9876f, 1.0f,
  687. 11.0f, -0.5f, 0.5010f, 1.0f
  688. };
  689. const float nearest[] = { 0.0f, 0.03746097535f, 0.0842871964f, 1.0f,
  690. 0.01664932258f, 0.2039542049f, 1.0f, 1.0f,
  691. 1.0f, 0.0f, 0.2663891613f, 1.0f
  692. };
  693. const float linear[] = { 0.0f, 0.04016649351f, 0.09021852165f, 1.0f,
  694. 0.01537752338f, 0.2087130845f, 0.9756000042f, 1.0f,
  695. 1.0f, 0.0f, 0.2512601018f, 1.0f
  696. };
  697. float color[12];
  698. // Check nearest
  699. memcpy(color, reference, 12*sizeof(float));
  700. OCIO::Lut3D_Nearest(color, 3, *lut);
  701. for(unsigned int i=0; i<12; ++i)
  702. {
  703. OIIO_CHECK_CLOSE(color[i], nearest[i], 1e-8);
  704. }
  705. // Check linear
  706. memcpy(color, reference, 12*sizeof(float));
  707. OCIO::Lut3D_Linear(color, 3, *lut);
  708. for(unsigned int i=0; i<12; ++i)
  709. {
  710. OIIO_CHECK_CLOSE(color[i], linear[i], 1e-8);
  711. }
  712. // Check tetrahedral
  713. memcpy(color, reference, 12*sizeof(float));
  714. OCIO::Lut3D_Tetrahedral(color, 3, *lut);
  715. for(unsigned int i=0; i<12; ++i)
  716. {
  717. OIIO_CHECK_CLOSE(color[i], linear[i], 1e-7); // Note, max delta lowered from 1e-8
  718. }
  719. }
  720. OIIO_ADD_TEST(Lut3DOp, InverseComparisonCheck)
  721. {
  722. OCIO::Lut3DRcPtr lut_a = OCIO::Lut3D::Create();
  723. lut_a->from_min[0] = 0.0f;
  724. lut_a->from_min[1] = 0.0f;
  725. lut_a->from_min[2] = 0.0f;
  726. lut_a->from_max[0] = 1.0f;
  727. lut_a->from_max[1] = 1.0f;
  728. lut_a->from_max[2] = 1.0f;
  729. lut_a->size[0] = 32;
  730. lut_a->size[1] = 32;
  731. lut_a->size[2] = 32;
  732. lut_a->lut.resize(lut_a->size[0]*lut_a->size[1]*lut_a->size[2]*3);
  733. GenerateIdentityLut3D(&lut_a->lut[0], lut_a->size[0], 3, OCIO::LUT3DORDER_FAST_RED);
  734. OCIO::Lut3DRcPtr lut_b = OCIO::Lut3D::Create();
  735. lut_b->from_min[0] = 0.5f;
  736. lut_b->from_min[1] = 0.5f;
  737. lut_b->from_min[2] = 0.5f;
  738. lut_b->from_max[0] = 1.0f;
  739. lut_b->from_max[1] = 1.0f;
  740. lut_b->from_max[2] = 1.0f;
  741. lut_b->size[0] = 32;
  742. lut_b->size[1] = 32;
  743. lut_b->size[2] = 32;
  744. lut_b->lut.resize(lut_b->size[0]*lut_b->size[1]*lut_b->size[2]*3);
  745. GenerateIdentityLut3D(&lut_b->lut[0], lut_b->size[0], 3, OCIO::LUT3DORDER_FAST_RED);
  746. OCIO::OpRcPtrVec ops;
  747. CreateLut3DOp(ops, lut_a, OCIO::INTERP_NEAREST, OCIO::TRANSFORM_DIR_FORWARD);
  748. CreateLut3DOp(ops, lut_a, OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_INVERSE);
  749. CreateLut3DOp(ops, lut_b, OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_FORWARD);
  750. CreateLut3DOp(ops, lut_b, OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_INVERSE);
  751. OIIO_CHECK_EQUAL(ops.size(), 4);
  752. OIIO_CHECK_ASSERT(ops[0]->isSameType(ops[1]));
  753. OIIO_CHECK_ASSERT(ops[0]->isSameType(ops[2]));
  754. OIIO_CHECK_ASSERT(ops[0]->isSameType(ops[3]->clone()));
  755. OIIO_CHECK_EQUAL( ops[0]->isInverse(ops[1]), true);
  756. OIIO_CHECK_EQUAL( ops[0]->isInverse(ops[2]), false);
  757. OIIO_CHECK_EQUAL( ops[0]->isInverse(ops[2]), false);
  758. OIIO_CHECK_EQUAL( ops[0]->isInverse(ops[3]), false);
  759. OIIO_CHECK_EQUAL( ops[2]->isInverse(ops[3]), true);
  760. }
  761. OIIO_ADD_TEST(Lut3DOp, PerformanceCheck)
  762. {
  763. /*
  764. OCIO::Lut3D lut;
  765. lut.from_min[0] = 0.0f;
  766. lut.from_min[1] = 0.0f;
  767. lut.from_min[2] = 0.0f;
  768. lut.from_max[0] = 1.0f;
  769. lut.from_max[1] = 1.0f;
  770. lut.from_max[2] = 1.0f;
  771. lut.size[0] = 32;
  772. lut.size[1] = 32;
  773. lut.size[2] = 32;
  774. lut.lut.resize(lut.size[0]*lut.size[1]*lut.size[2]*3);
  775. GenerateIdentityLut3D(&lut.lut[0], lut.size[0], 3, OCIO::LUT3DORDER_FAST_RED);
  776. std::vector<float> img;
  777. int xres = 2048;
  778. int yres = 1;
  779. int channels = 4;
  780. img.resize(xres*yres*channels);
  781. srand48(0);
  782. // create random values from -0.05 to 1.05
  783. // (To simulate clipping performance)
  784. for(unsigned int i=0; i<img.size(); ++i)
  785. {
  786. float uniform = (float)drand48();
  787. img[i] = uniform*1.1f - 0.05f;
  788. }
  789. timeval t;
  790. gettimeofday(&t, 0);
  791. double starttime = (double) t.tv_sec + (double) t.tv_usec / 1000000.0;
  792. int numloops = 1024;
  793. for(int i=0; i<numloops; ++i)
  794. {
  795. //OCIO::Lut3D_Nearest(&img[0], xres*yres, lut);
  796. OCIO::Lut3D_Linear(&img[0], xres*yres, lut);
  797. }
  798. gettimeofday(&t, 0);
  799. double endtime = (double) t.tv_sec + (double) t.tv_usec / 1000000.0;
  800. double totaltime_a = (endtime-starttime)/numloops;
  801. printf("Linear: %0.1f ms - %0.1f fps\n", totaltime_a*1000.0, 1.0/totaltime_a);
  802. // Tetrahedral
  803. gettimeofday(&t, 0);
  804. starttime = (double) t.tv_sec + (double) t.tv_usec / 1000000.0;
  805. for(int i=0; i<numloops; ++i)
  806. {
  807. OCIO::Lut3D_Tetrahedral(&img[0], xres*yres, lut);
  808. }
  809. gettimeofday(&t, 0);
  810. endtime = (double) t.tv_sec + (double) t.tv_usec / 1000000.0;
  811. double totaltime_b = (endtime-starttime)/numloops;
  812. printf("Tetra: %0.1f ms - %0.1f fps\n", totaltime_b*1000.0, 1.0/totaltime_b);
  813. double speed_diff = totaltime_a/totaltime_b;
  814. printf("Tetra is %.04f speed of Linear\n", speed_diff);
  815. */
  816. }
  817. #endif // OCIO_UNIT_TEST