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