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 <cstdio>
30 #include <iostream>
31 #include <iomanip>
32 #include <iterator>
33 #include <algorithm>
34 
35 #include <OpenColorIO/OpenColorIO.h>
36 
37 #include "FileTransform.h"
38 #include "Lut1DOp.h"
39 #include "Lut3DOp.h"
40 #include "ParseUtils.h"
41 #include "pystring/pystring.h"
42 
43 // This implements the spec for:
44 // Per http://www.filmlight.ltd.uk/resources/documents/truelight/white-papers_tl.php
45 // FL-TL-TN-0388-TLCubeFormat2.0.pdf
46 //
47 // Known deficiency in implementation:
48 // 1D shaper luts (InputLUT) using integer encodings (vs float) are not supported.
49 // How to we determine if the input is integer? MaxVal?  Or do we look for a decimal-point?
50 // How about scientific notation? (which is explicitly allowed?)
51 
52 /*
53 The input LUT is used to interpolate a higher precision LUT matched to the particular image
54 format. For integer formats, the range 0-1 is mapped onto the integer range. Floating point
55 values outside the 0-1 range are allowed but may be truncated for integer formats.
56 */
57 
58 
59 OCIO_NAMESPACE_ENTER
60 {
61     namespace
62     {
63         class LocalCachedFile : public CachedFile
64         {
65         public:
66             LocalCachedFile () :
67                 has1D(false),
68                 has3D(false)
69             {
70                 lut1D = Lut1D::Create();
71                 lut3D = Lut3D::Create();
72             };
73             ~LocalCachedFile() {};
74 
75             bool has1D;
76             bool has3D;
77             Lut1DRcPtr lut1D;
78             Lut3DRcPtr lut3D;
79         };
80 
81         typedef OCIO_SHARED_PTR<LocalCachedFile> LocalCachedFileRcPtr;
82 
83 
84 
85         class LocalFileFormat : public FileFormat
86         {
87         public:
88 
89             ~LocalFileFormat() {};
90 
91             virtual void GetFormatInfo(FormatInfoVec & formatInfoVec) const;
92 
93             virtual CachedFileRcPtr Read(std::istream & istream) const;
94 
95             virtual void Write(const Baker & baker,
96                                const std::string & formatName,
97                                std::ostream & ostream) const;
98 
99             virtual void BuildFileOps(OpRcPtrVec & ops,
100                          const Config& config,
101                          const ConstContextRcPtr & context,
102                          CachedFileRcPtr untypedCachedFile,
103                          const FileTransform& fileTransform,
104                          TransformDirection dir) const;
105         };
106 
107         void LocalFileFormat::GetFormatInfo(FormatInfoVec & formatInfoVec) const
108         {
109             FormatInfo info;
110             info.name = "truelight";
111             info.extension = "cub";
112             info.capabilities = (FORMAT_CAPABILITY_READ | FORMAT_CAPABILITY_WRITE);
113             formatInfoVec.push_back(info);
114         }
115 
116         CachedFileRcPtr
117         LocalFileFormat::Read(std::istream & istream) const
118         {
119             // this shouldn't happen
120             if(!istream)
121             {
122                 throw Exception ("File stream empty when trying to read Truelight .cub lut");
123             }
124 
125             // Validate the file type
126             std::string line;
127             if(!nextline(istream, line) ||
128                !pystring::startswith(pystring::lower(line), "# truelight cube"))
129             {
130                 throw Exception("Lut doesn't seem to be a Truelight .cub lut.");
131             }
132 
133             // Parse the file
134             std::vector<float> raw1d;
135             std::vector<float> raw3d;
136             int size3d[] = { 0, 0, 0 };
137             int size1d = 0;
138             {
139                 std::vector<std::string> parts;
140                 std::vector<float> tmpfloats;
141 
142                 bool in1d = false;
143                 bool in3d = false;
144 
145                 while(nextline(istream, line))
146                 {
147                     // Strip, lowercase, and split the line
148                     pystring::split(pystring::lower(pystring::strip(line)), parts);
149 
150                     if(parts.empty()) continue;
151 
152                     // Parse header metadata (which starts with #)
153                     if(pystring::startswith(parts[0],"#"))
154                     {
155                         if(parts.size() < 2) continue;
156 
157                         if(parts[1] == "width")
158                         {
159                             if(parts.size() != 5 ||
160                                !StringToInt( &size3d[0], parts[2].c_str()) ||
161                                !StringToInt( &size3d[1], parts[3].c_str()) ||
162                                !StringToInt( &size3d[2], parts[4].c_str()))
163                             {
164                                 throw Exception("Malformed width tag in Truelight .cub lut.");
165                             }
166 
167                             raw3d.reserve(3*size3d[0]*size3d[1]*size3d[2]);
168                         }
169                         else if(parts[1] == "lutlength")
170                         {
171                             if(parts.size() != 3 ||
172                                !StringToInt( &size1d, parts[2].c_str()))
173                             {
174                                 throw Exception("Malformed lutlength tag in Truelight .cub lut.");
175                             }
176                             raw1d.reserve(3*size1d);
177                         }
178                         else if(parts[1] == "inputlut")
179                         {
180                             in1d = true;
181                             in3d = false;
182                         }
183                         else if(parts[1] == "cube")
184                         {
185                             in3d = true;
186                             in1d = false;
187                         }
188                         else if(parts[1] == "end")
189                         {
190                             in3d = false;
191                             in1d = false;
192 
193                             // If we hit the end tag, don't bother searching further in the file.
194                             break;
195                         }
196                     }
197 
198 
199                     if(in1d || in3d)
200                     {
201                         if(StringVecToFloatVec(tmpfloats, parts) && (tmpfloats.size() == 3))
202                         {
203                             if(in1d)
204                             {
205                                 raw1d.push_back(tmpfloats[0]);
206                                 raw1d.push_back(tmpfloats[1]);
207                                 raw1d.push_back(tmpfloats[2]);
208                             }
209                             else if(in3d)
210                             {
211                                 raw3d.push_back(tmpfloats[0]);
212                                 raw3d.push_back(tmpfloats[1]);
213                                 raw3d.push_back(tmpfloats[2]);
214                             }
215                         }
216                     }
217                 }
218             }
219 
220             // Interpret the parsed data, validate lut sizes
221 
222             if(size1d != static_cast<int>(raw1d.size()/3))
223             {
224                 std::ostringstream os;
225                 os << "Parse error in Truelight .cub lut. ";
226                 os << "Incorrect number of lut1d entries. ";
227                 os << "Found " << raw1d.size()/3 << ", expected " << size1d << ".";
228                 throw Exception(os.str().c_str());
229             }
230 
231             if(size3d[0]*size3d[1]*size3d[2] != static_cast<int>(raw3d.size()/3))
232             {
233                 std::ostringstream os;
234                 os << "Parse error in Truelight .cub lut. ";
235                 os << "Incorrect number of lut3d entries. ";
236                 os << "Found " << raw3d.size()/3 << ", expected " << size3d[0]*size3d[1]*size3d[2] << ".";
237                 throw Exception(os.str().c_str());
238             }
239 
240 
241             LocalCachedFileRcPtr cachedFile = LocalCachedFileRcPtr(new LocalCachedFile());
242 
243             cachedFile->has1D = (size1d>0);
244             cachedFile->has3D = (size3d[0]*size3d[1]*size3d[2]>0);
245 
246             // Reformat 1D data
247             if(cachedFile->has1D)
248             {
249                 for(int channel=0; channel<3; ++channel)
250                 {
251                     // Determine the scale factor for the 1d lut. Example:
252                     // The inputlut feeding a 6x6x6 3dlut should be scaled from 0.0-5.0.
253                     // Beware: Nuke Truelight Writer (at least 6.3 and before) is busted
254                     // and does this scaling incorrectly.
255 
256                     float descale = 1.0f;
257                     if(cachedFile->has3D)
258                     {
259                         descale = 1.0f / static_cast<float>(size3d[channel]-1);
260                     }
261 
262                     cachedFile->lut1D->luts[channel].resize(size1d);
263                     for(int i=0; i<size1d; ++i)
264                     {
265                         cachedFile->lut1D->luts[channel][i] = raw1d[3*i+channel] * descale;
266                     }
267                 }
268 
269                 // 1e-5 rel error is a good threshold when float numbers near 0
270                 // are written out with 6 decimal places of precision.  This is
271                 // a bit aggressive, I.e., changes in the 6th decimal place will
272                 // be considered roundoff error, but changes in the 5th decimal
273                 // will be considered lut 'intent'.
274                 // 1.0
275                 // 1.000005 equal to 1.0
276                 // 1.000007 equal to 1.0
277                 // 1.000010 not equal
278                 // 0.0
279                 // 0.000001 not equal
280 
281                 cachedFile->lut1D->maxerror = 1e-5f;
282                 cachedFile->lut1D->errortype = ERROR_RELATIVE;
283             }
284 
285             // Reformat 3D data
286             if(cachedFile->has3D)
287             {
288                 cachedFile->lut3D->size[0] = size3d[0];
289                 cachedFile->lut3D->size[1] = size3d[1];
290                 cachedFile->lut3D->size[2] = size3d[2];
291                 cachedFile->lut3D->lut = raw3d;
292             }
293 
294             return cachedFile;
295         }
296 
297 
298         void
299         LocalFileFormat::Write(const Baker & baker,
300                                const std::string & /*formatName*/,
301                                std::ostream & ostream) const
302         {
303             const int DEFAULT_CUBE_SIZE = 32;
304             const int DEFAULT_SHAPER_SIZE = 1024;
305 
306             ConstConfigRcPtr config = baker.getConfig();
307 
308             int cubeSize = baker.getCubeSize();
309             if (cubeSize==-1) cubeSize = DEFAULT_CUBE_SIZE;
310             cubeSize = std::max(2, cubeSize); // smallest cube is 2x2x2
311 
312             std::vector<float> cubeData;
313             cubeData.resize(cubeSize*cubeSize*cubeSize*3);
314             GenerateIdentityLut3D(&cubeData[0], cubeSize, 3, LUT3DORDER_FAST_RED);
315             PackedImageDesc cubeImg(&cubeData[0], cubeSize*cubeSize*cubeSize, 1, 3);
316 
317             // Apply processor to lut data
318             ConstProcessorRcPtr inputToTarget;
319             inputToTarget = config->getProcessor(baker.getInputSpace(), baker.getTargetSpace());
320             inputToTarget->apply(cubeImg);
321 
322             int shaperSize = baker.getShaperSize();
323             if (shaperSize==-1) shaperSize = DEFAULT_SHAPER_SIZE;
324             shaperSize = std::max(2, shaperSize); // smallest shaper is 2x2x2
325 
326 
327             // Write the header
328             ostream << "# Truelight Cube v2.0\n";
329             ostream << "# lutLength " << shaperSize << "\n";
330             ostream << "# iDims     3\n";
331             ostream << "# oDims     3\n";
332             ostream << "# width     " << cubeSize << " " << cubeSize << " " << cubeSize << "\n";
333             ostream << "\n";
334 
335 
336             // Write the shaper lut
337             // (We are just going to use a unity lut)
338             ostream << "# InputLUT\n";
339             ostream << std::setprecision(6) << std::fixed;
340             float v = 0.0f;
341             for (int i=0; i < shaperSize-1; i++)
342             {
343                 v = ((float)i / (float)(shaperSize-1)) * (float)(cubeSize-1);
344                 ostream << v << " " << v << " " << v << "\n";
345             }
346             v = (float) (cubeSize-1);
347             ostream << v << " " << v << " " << v << "\n"; // ensure that the last value is spot on
348             ostream << "\n";
349 
350             // Write the cube
351             ostream << "# Cube\n";
352             for (int i=0; i<cubeSize*cubeSize*cubeSize; ++i)
353             {
354                 ostream << cubeData[3*i+0] << " " << cubeData[3*i+1] << " " << cubeData[3*i+2] << "\n";
355             }
356 
357             ostream << "# end\n";
358         }
359 
360         void
361         LocalFileFormat::BuildFileOps(OpRcPtrVec & ops,
362                                       const Config& /*config*/,
363                                       const ConstContextRcPtr & /*context*/,
364                                       CachedFileRcPtr untypedCachedFile,
365                                       const FileTransform& fileTransform,
366                                       TransformDirection dir) const
367         {
368             LocalCachedFileRcPtr cachedFile = DynamicPtrCast<LocalCachedFile>(untypedCachedFile);
369 
370             // This should never happen.
371             if(!cachedFile)
372             {
373                 std::ostringstream os;
374                 os << "Cannot build Truelight .cub Op. Invalid cache type.";
375                 throw Exception(os.str().c_str());
376             }
377 
378             TransformDirection newDir = CombineTransformDirections(dir,
379                 fileTransform.getDirection());
380             if(newDir == TRANSFORM_DIR_UNKNOWN)
381             {
382                 std::ostringstream os;
383                 os << "Cannot build file format transform,";
384                 os << " unspecified transform direction.";
385                 throw Exception(os.str().c_str());
386             }
387 
388             // TODO: INTERP_LINEAR should not be hard-coded.
389             // Instead query 'highest' interpolation?
390             // (right now, it's linear). If cubic is added, consider
391             // using it
392 
393             if(newDir == TRANSFORM_DIR_FORWARD)
394             {
395                 if(cachedFile->has1D)
396                 {
397                     CreateLut1DOp(ops, cachedFile->lut1D,
398                                   INTERP_LINEAR, newDir);
399                 }
400 
401                 CreateLut3DOp(ops, cachedFile->lut3D,
402                               fileTransform.getInterpolation(), newDir);
403             }
404             else if(newDir == TRANSFORM_DIR_INVERSE)
405             {
406                 CreateLut3DOp(ops, cachedFile->lut3D,
407                               fileTransform.getInterpolation(), newDir);
408 
409                 if(cachedFile->has1D)
410                 {
411                     CreateLut1DOp(ops, cachedFile->lut1D,
412                                   INTERP_LINEAR, newDir);
413                 }
414             }
415         }
416     }
417 
418     FileFormat * CreateFileFormatTruelight()
419     {
420         return new LocalFileFormat();
421     }
422 }
423 OCIO_NAMESPACE_EXIT
424 
425 
426 ///////////////////////////////////////////////////////////////////////////////
427 
428 #ifdef OCIO_UNIT_TEST
429 
430 namespace OCIO = OCIO_NAMESPACE;
431 #include "UnitTest.h"
432 
OIIO_ADD_TEST(FileFormatTruelight,ShaperAndLut3D)433 OIIO_ADD_TEST(FileFormatTruelight, ShaperAndLut3D)
434 {
435     // This lowers the red channel by 0.5, other channels are unaffected.
436     const char * luttext = "# Truelight Cube v2.0\n"
437        "# iDims 3\n"
438        "# oDims 3\n"
439        "# width 3 3 3\n"
440        "# lutLength 5\n"
441        "# InputLUT\n"
442        " 0.000000 0.000000 0.000000\n"
443        " 0.500000 0.500000 0.500000\n"
444        " 1.000000 1.000000 1.000000\n"
445        " 1.500000 1.500000 1.500000\n"
446        " 2.000000 2.000000 2.000000\n"
447        "\n"
448        "# Cube\n"
449        " 0.000000 0.000000 0.000000\n"
450        " 0.250000 0.000000 0.000000\n"
451        " 0.500000 0.000000 0.000000\n"
452        " 0.000000 0.500000 0.000000\n"
453        " 0.250000 0.500000 0.000000\n"
454        " 0.500000 0.500000 0.000000\n"
455        " 0.000000 1.000000 0.000000\n"
456        " 0.250000 1.000000 0.000000\n"
457        " 0.500000 1.000000 0.000000\n"
458        " 0.000000 0.000000 0.500000\n"
459        " 0.250000 0.000000 0.500000\n"
460        " 0.500000 0.000000 0.500000\n"
461        " 0.000000 0.500000 0.500000\n"
462        " 0.250000 0.500000 0.500000\n"
463        " 0.500000 0.500000 0.500000\n"
464        " 0.000000 1.000000 0.500000\n"
465        " 0.250000 1.000000 0.500000\n"
466        " 0.500000 1.000000 0.500000\n"
467        " 0.000000 0.000000 1.000000\n"
468        " 0.250000 0.000000 1.000000\n"
469        " 0.500000 0.000000 1.000000\n"
470        " 0.000000 0.500000 1.000000\n"
471        " 0.250000 0.500000 1.000000\n"
472        " 0.500000 0.500000 1.000000\n"
473        " 0.000000 1.000000 1.000000\n"
474        " 0.250000 1.000000 1.000000\n"
475        " 0.500000 1.000000 1.000000\n"
476        "\n"
477        "# end\n"
478        "\n"
479        "# Truelight profile\n"
480        "title{madeup on some display}\n"
481        "print{someprint}\n"
482        "display{some}\n"
483        "cubeFile{madeup.cube}\n"
484        "\n"
485        " # This last line confirms 'end' tag is obeyed\n"
486        " 1.23456 1.23456 1.23456\n";
487 
488     std::istringstream lutIStream;
489     lutIStream.str(luttext);
490 
491     // Read file
492     OCIO::LocalFileFormat tester;
493     OCIO::CachedFileRcPtr cachedFile;
494     OIIO_CHECK_NO_THROW(cachedFile = tester.Read(lutIStream));
495     OCIO::LocalCachedFileRcPtr lut = OCIO::DynamicPtrCast<OCIO::LocalCachedFile>(cachedFile);
496 
497     OIIO_CHECK_ASSERT(lut->has1D);
498     OIIO_CHECK_ASSERT(lut->has3D);
499 
500     float data[4*3] = { 0.1f, 0.2f, 0.3f, 0.0f,
501                         1.0f, 0.5f, 0.123456f, 0.0f,
502                        -1.0f, 1.5f, 0.5f, 0.0f };
503 
504     float result[4*3] = { 0.05f, 0.2f, 0.3f, 0.0f,
505                           0.50f, 0.5f, 0.123456f, 0.0f,
506                           0.0f, 1.5f, 0.5f, 0.0f };
507 
508     OCIO::OpRcPtrVec ops;
509     if(lut->has1D)
510     {
511         CreateLut1DOp(ops, lut->lut1D,
512                       OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_FORWARD);
513     }
514     if(lut->has3D)
515     {
516         CreateLut3DOp(ops, lut->lut3D,
517                       OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_FORWARD);
518     }
519     FinalizeOpVec(ops);
520 
521 
522     // Apply the result
523     for(OCIO::OpRcPtrVec::size_type i = 0, size = ops.size(); i < size; ++i)
524     {
525         ops[i]->apply(data, 3);
526     }
527 
528     for(int i=0; i<3; ++i)
529     {
530         OIIO_CHECK_CLOSE( data[i], result[i], 1.0e-6 );
531     }
532 }
533 
OIIO_ADD_TEST(FileFormatTruelight,Shaper)534 OIIO_ADD_TEST(FileFormatTruelight, Shaper)
535 {
536     const char * luttext = "# Truelight Cube v2.0\n"
537        "# lutLength 11\n"
538        "# iDims 3\n"
539        "\n"
540        "\n"
541        "# InputLUT\n"
542        " 0.000 0.000 -0.000\n"
543        " 0.200 0.010 -0.100\n"
544        " 0.400 0.040 -0.200\n"
545        " 0.600 0.090 -0.300\n"
546        " 0.800 0.160 -0.400\n"
547        " 1.000 0.250 -0.500\n"
548        " 1.200 0.360 -0.600\n"
549        " 1.400 0.490 -0.700\n"
550        " 1.600 0.640 -0.800\n"
551        " 1.800 0.820 -0.900\n"
552        " 2.000 1.000 -1.000\n"
553        "\n\n\n"
554        "# end\n";
555 
556     std::istringstream lutIStream;
557     lutIStream.str(luttext);
558 
559     // Read file
560     OCIO::LocalFileFormat tester;
561     OCIO::CachedFileRcPtr cachedFile;
562     OIIO_CHECK_NO_THROW(cachedFile = tester.Read(lutIStream));
563 
564     OCIO::LocalCachedFileRcPtr lut = OCIO::DynamicPtrCast<OCIO::LocalCachedFile>(cachedFile);
565 
566     OIIO_CHECK_ASSERT(lut->has1D);
567     OIIO_CHECK_ASSERT(!lut->has3D);
568 
569     float data[4*3] = { 0.1f, 0.2f, 0.3f, 0.0f,
570                         1.0f, 0.5f, 0.123456f, 0.0f,
571                        -1.0f, 1.5f, 0.5f, 0.0f };
572 
573     float result[4*3] = { 0.2f, 0.04f, -0.3f, 0.0f,
574                           2.0f, 0.25f, -0.123456f, 0.0f,
575                           0.0f, 1.0f, -0.5f, 0.0f };
576 
577     OCIO::OpRcPtrVec ops;
578     if(lut->has1D)
579     {
580         CreateLut1DOp(ops, lut->lut1D,
581                       OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_FORWARD);
582     }
583     if(lut->has3D)
584     {
585         CreateLut3DOp(ops, lut->lut3D,
586                       OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_FORWARD);
587     }
588     FinalizeOpVec(ops);
589 
590 
591     // Apply the result
592     for(OCIO::OpRcPtrVec::size_type i = 0, size = ops.size(); i < size; ++i)
593     {
594         ops[i]->apply(data, 3);
595     }
596 
597     for(int i=0; i<3; ++i)
598     {
599         OIIO_CHECK_CLOSE( data[i], result[i], 1.0e-6 );
600     }
601 }
602 
603 
OIIO_ADD_TEST(FileFormatTruelight,Lut3D)604 OIIO_ADD_TEST(FileFormatTruelight, Lut3D)
605 {
606     // This lowers the red channel by 0.5, other channels are unaffected.
607     const char * luttext = "# Truelight Cube v2.0\n"
608        "# iDims 3\n"
609        "# oDims 3\n"
610        "# width 3 3 3\n"
611        "\n\n\n"
612        "# Cube\n"
613        " 0.000000 0.000000 0.000000\n"
614        " 0.250000 0.000000 0.000000\n"
615        " 0.500000 0.000000 0.000000\n"
616        " 0.000000 0.500000 0.000000\n"
617        " 0.250000 0.500000 0.000000\n"
618        " 0.500000 0.500000 0.000000\n"
619        " 0.000000 1.000000 0.000000\n"
620        " 0.250000 1.000000 0.000000\n"
621        " 0.500000 1.000000 0.000000\n"
622        " 0.000000 0.000000 0.500000\n"
623        " 0.250000 0.000000 0.500000\n"
624        " 0.500000 0.000000 0.500000\n"
625        " 0.000000 0.500000 0.500000\n"
626        " 0.250000 0.500000 0.500000\n"
627        " 0.500000 0.500000 0.500000\n"
628        " 0.000000 1.000000 0.500000\n"
629        " 0.250000 1.000000 0.500000\n"
630        " 0.500000 1.000000 0.500000\n"
631        " 0.000000 0.000000 1.000000\n"
632        " 0.250000 0.000000 1.000000\n"
633        " 0.500000 0.000000 1.000000\n"
634        " 0.000000 0.500000 1.000000\n"
635        " 0.250000 0.500000 1.000000\n"
636        " 0.500000 0.500000 1.000000\n"
637        " 0.000000 1.000000 1.000000\n"
638        " 0.250000 1.000000 1.000000\n"
639        " 0.500000 1.000000 1.000000\n"
640        "\n"
641        "# end\n";
642 
643     std::istringstream lutIStream;
644     lutIStream.str(luttext);
645 
646     // Read file
647     OCIO::LocalFileFormat tester;
648     OCIO::CachedFileRcPtr cachedFile;
649     OIIO_CHECK_NO_THROW(cachedFile = tester.Read(lutIStream));
650     OCIO::LocalCachedFileRcPtr lut = OCIO::DynamicPtrCast<OCIO::LocalCachedFile>(cachedFile);
651 
652     OIIO_CHECK_ASSERT(!lut->has1D);
653     OIIO_CHECK_ASSERT(lut->has3D);
654 
655     float data[4*3] = { 0.1f, 0.2f, 0.3f, 0.0f,
656                         1.0f, 0.5f, 0.123456f, 0.0f,
657                        -1.0f, 1.5f, 0.5f, 0.0f };
658 
659     float result[4*3] = { 0.05f, 0.2f, 0.3f, 0.0f,
660                           0.50f, 0.5f, 0.123456f, 0.0f,
661                           0.0f, 1.5f, 0.5f, 0.0f };
662 
663     OCIO::OpRcPtrVec ops;
664     if(lut->has1D)
665     {
666         CreateLut1DOp(ops, lut->lut1D,
667                       OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_FORWARD);
668     }
669     if(lut->has3D)
670     {
671         CreateLut3DOp(ops, lut->lut3D,
672                       OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_FORWARD);
673     }
674     FinalizeOpVec(ops);
675 
676 
677     // Apply the result
678     for(OCIO::OpRcPtrVec::size_type i = 0, size = ops.size(); i < size; ++i)
679     {
680         ops[i]->apply(data, 3);
681     }
682 
683     for(int i=0; i<3; ++i)
684     {
685         OIIO_CHECK_CLOSE( data[i], result[i], 1.0e-6 );
686     }
687 }
688 
689 #endif // OCIO_UNIT_TEST
690