diff options
Diffstat (limited to 'src/core/FileFormatTruelight.cpp')
-rw-r--r-- | src/core/FileFormatTruelight.cpp | 620 |
1 files changed, 620 insertions, 0 deletions
diff --git a/src/core/FileFormatTruelight.cpp b/src/core/FileFormatTruelight.cpp new file mode 100644 index 0000000..a7ccc48 --- /dev/null +++ b/src/core/FileFormatTruelight.cpp @@ -0,0 +1,620 @@ +/* +Copyright (c) 2003-2010 Sony Pictures Imageworks Inc., et al. +All Rights Reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of Sony Pictures Imageworks nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include <cstdio> +#include <iostream> +#include <iterator> + +#include <OpenColorIO/OpenColorIO.h> + +#include "FileTransform.h" +#include "Lut1DOp.h" +#include "Lut3DOp.h" +#include "ParseUtils.h" +#include "pystring/pystring.h" + +// This implements the spec for: +// Per http://www.filmlight.ltd.uk/resources/documents/truelight/white-papers_tl.php +// FL-TL-TN-0388-TLCubeFormat2.0.pdf +// +// Known deficiency in implementation: +// 1D shaper luts (InputLUT) using integer encodings (vs float) are not supported. +// How to we determine if the input is integer? MaxVal? Or do we look for a decimal-point? +// How about scientific notation? (which is explicitly allowed?) + +/* +The input LUT is used to interpolate a higher precision LUT matched to the particular image +format. For integer formats, the range 0-1 is mapped onto the integer range. Floating point +values outside the 0-1 range are allowed but may be truncated for integer formats. +*/ + + +OCIO_NAMESPACE_ENTER +{ + namespace + { + class LocalCachedFile : public CachedFile + { + public: + LocalCachedFile () : + has1D(false), + has3D(false) + { + lut1D = Lut1D::Create(); + lut3D = Lut3D::Create(); + }; + ~LocalCachedFile() {}; + + bool has1D; + bool has3D; + Lut1DRcPtr lut1D; + Lut3DRcPtr lut3D; + }; + + typedef OCIO_SHARED_PTR<LocalCachedFile> LocalCachedFileRcPtr; + + + + class LocalFileFormat : public FileFormat + { + public: + + ~LocalFileFormat() {}; + + virtual void GetFormatInfo(FormatInfoVec & formatInfoVec) const; + + virtual CachedFileRcPtr Read(std::istream & istream) const; + + virtual void BuildFileOps(OpRcPtrVec & ops, + const Config& config, + const ConstContextRcPtr & context, + CachedFileRcPtr untypedCachedFile, + const FileTransform& fileTransform, + TransformDirection dir) const; + }; + + void LocalFileFormat::GetFormatInfo(FormatInfoVec & formatInfoVec) const + { + FormatInfo info; + info.name = "truelight"; + info.extension = "cub"; + info.capabilities = FORMAT_CAPABILITY_READ; + formatInfoVec.push_back(info); + } + + CachedFileRcPtr + LocalFileFormat::Read(std::istream & istream) const + { + // this shouldn't happen + if(!istream) + { + throw Exception ("File stream empty when trying to read Truelight .cub lut"); + } + + // Validate the file type + std::string line; + if(!nextline(istream, line) || + !pystring::startswith(pystring::lower(line), "# truelight cube")) + { + throw Exception("Lut doesn't seem to be a Truelight .cub lut."); + } + + // Parse the file + std::vector<float> raw1d; + std::vector<float> raw3d; + int size3d[] = { 0, 0, 0 }; + int size1d = 0; + { + std::vector<std::string> parts; + std::vector<float> tmpfloats; + + bool in1d = false; + bool in3d = false; + + while(nextline(istream, line)) + { + // Strip, lowercase, and split the line + pystring::split(pystring::lower(pystring::strip(line)), parts); + + if(parts.empty()) continue; + + // Parse header metadata (which starts with #) + if(pystring::startswith(parts[0],"#")) + { + if(parts.size() < 2) continue; + + if(parts[1] == "width") + { + if(parts.size() != 5 || + !StringToInt( &size3d[0], parts[2].c_str()) || + !StringToInt( &size3d[1], parts[3].c_str()) || + !StringToInt( &size3d[2], parts[4].c_str())) + { + throw Exception("Malformed width tag in Truelight .cub lut."); + } + + raw3d.reserve(3*size3d[0]*size3d[1]*size3d[2]); + } + else if(parts[1] == "lutlength") + { + if(parts.size() != 3 || + !StringToInt( &size1d, parts[2].c_str())) + { + throw Exception("Malformed lutlength tag in Truelight .cub lut."); + } + raw1d.reserve(3*size1d); + } + else if(parts[1] == "inputlut") + { + in1d = true; + in3d = false; + } + else if(parts[1] == "cube") + { + in3d = true; + in1d = false; + } + else if(parts[1] == "end") + { + in3d = false; + in1d = false; + + // If we hit the end tag, don't bother searching further in the file. + break; + } + } + + + if(in1d || in3d) + { + if(StringVecToFloatVec(tmpfloats, parts) && (tmpfloats.size() == 3)) + { + if(in1d) + { + raw1d.push_back(tmpfloats[0]); + raw1d.push_back(tmpfloats[1]); + raw1d.push_back(tmpfloats[2]); + } + else if(in3d) + { + raw3d.push_back(tmpfloats[0]); + raw3d.push_back(tmpfloats[1]); + raw3d.push_back(tmpfloats[2]); + } + } + } + } + } + + // Interpret the parsed data, validate lut sizes + + if(size1d != static_cast<int>(raw1d.size()/3)) + { + std::ostringstream os; + os << "Parse error in Truelight .cub lut. "; + os << "Incorrect number of lut1d entries. "; + os << "Found " << raw1d.size()/3 << ", expected " << size1d << "."; + throw Exception(os.str().c_str()); + } + + if(size3d[0]*size3d[1]*size3d[2] != static_cast<int>(raw3d.size()/3)) + { + std::ostringstream os; + os << "Parse error in Truelight .cub lut. "; + os << "Incorrect number of lut3d entries. "; + os << "Found " << raw3d.size()/3 << ", expected " << size3d[0]*size3d[1]*size3d[2] << "."; + throw Exception(os.str().c_str()); + } + + + LocalCachedFileRcPtr cachedFile = LocalCachedFileRcPtr(new LocalCachedFile()); + + cachedFile->has1D = (size1d>0); + cachedFile->has3D = (size3d[0]*size3d[1]*size3d[2]>0); + + // Reformat 1D data + if(cachedFile->has1D) + { + for(int channel=0; channel<3; ++channel) + { + // Determine the scale factor for the 1d lut. Example: + // The inputlut feeding a 6x6x6 3dlut should be scaled from 0.0-5.0. + // Beware: Nuke Truelight Writer (at least 6.3 and before) is busted + // and does this scaling incorrectly. + + float descale = 1.0f; + if(cachedFile->has3D) + { + descale = 1.0f / static_cast<float>(size3d[channel]-1); + } + + cachedFile->lut1D->luts[channel].resize(size1d); + for(int i=0; i<size1d; ++i) + { + cachedFile->lut1D->luts[channel][i] = raw1d[3*i+channel] * descale; + } + } + + // 1e-5 rel error is a good threshold when float numbers near 0 + // are written out with 6 decimal places of precision. This is + // a bit aggressive, I.e., changes in the 6th decimal place will + // be considered roundoff error, but changes in the 5th decimal + // will be considered lut 'intent'. + // 1.0 + // 1.000005 equal to 1.0 + // 1.000007 equal to 1.0 + // 1.000010 not equal + // 0.0 + // 0.000001 not equal + + cachedFile->lut1D->maxerror = 1e-5f; + cachedFile->lut1D->errortype = ERROR_RELATIVE; + } + + // Reformat 3D data + if(cachedFile->has3D) + { + cachedFile->lut3D->size[0] = size3d[0]; + cachedFile->lut3D->size[1] = size3d[1]; + cachedFile->lut3D->size[2] = size3d[2]; + cachedFile->lut3D->lut = raw3d; + } + + return cachedFile; + } + + void + LocalFileFormat::BuildFileOps(OpRcPtrVec & ops, + const Config& /*config*/, + const ConstContextRcPtr & /*context*/, + CachedFileRcPtr untypedCachedFile, + const FileTransform& fileTransform, + TransformDirection dir) const + { + LocalCachedFileRcPtr cachedFile = DynamicPtrCast<LocalCachedFile>(untypedCachedFile); + + // This should never happen. + if(!cachedFile) + { + std::ostringstream os; + os << "Cannot build Truelight .cub Op. Invalid cache type."; + throw Exception(os.str().c_str()); + } + + TransformDirection newDir = CombineTransformDirections(dir, + fileTransform.getDirection()); + if(newDir == TRANSFORM_DIR_UNKNOWN) + { + std::ostringstream os; + os << "Cannot build file format transform,"; + os << " unspecified transform direction."; + throw Exception(os.str().c_str()); + } + + // TODO: INTERP_LINEAR should not be hard-coded. + // Instead query 'highest' interpolation? + // (right now, it's linear). If cubic is added, consider + // using it + + if(newDir == TRANSFORM_DIR_FORWARD) + { + if(cachedFile->has1D) + { + CreateLut1DOp(ops, cachedFile->lut1D, + INTERP_LINEAR, newDir); + } + + CreateLut3DOp(ops, cachedFile->lut3D, + fileTransform.getInterpolation(), newDir); + } + else if(newDir == TRANSFORM_DIR_INVERSE) + { + CreateLut3DOp(ops, cachedFile->lut3D, + fileTransform.getInterpolation(), newDir); + + if(cachedFile->has1D) + { + CreateLut1DOp(ops, cachedFile->lut1D, + INTERP_LINEAR, newDir); + } + } + } + } + + FileFormat * CreateFileFormatTruelight() + { + return new LocalFileFormat(); + } +} +OCIO_NAMESPACE_EXIT + + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef OCIO_UNIT_TEST + +namespace OCIO = OCIO_NAMESPACE; +#include "UnitTest.h" + +OIIO_ADD_TEST(FileFormatTruelight, ShaperAndLut3D) +{ + // This lowers the red channel by 0.5, other channels are unaffected. + const char * luttext = "# Truelight Cube v2.0\n" + "# iDims 3\n" + "# oDims 3\n" + "# width 3 3 3\n" + "# lutLength 5\n" + "# InputLUT\n" + " 0.000000 0.000000 0.000000\n" + " 0.500000 0.500000 0.500000\n" + " 1.000000 1.000000 1.000000\n" + " 1.500000 1.500000 1.500000\n" + " 2.000000 2.000000 2.000000\n" + "\n" + "# Cube\n" + " 0.000000 0.000000 0.000000\n" + " 0.250000 0.000000 0.000000\n" + " 0.500000 0.000000 0.000000\n" + " 0.000000 0.500000 0.000000\n" + " 0.250000 0.500000 0.000000\n" + " 0.500000 0.500000 0.000000\n" + " 0.000000 1.000000 0.000000\n" + " 0.250000 1.000000 0.000000\n" + " 0.500000 1.000000 0.000000\n" + " 0.000000 0.000000 0.500000\n" + " 0.250000 0.000000 0.500000\n" + " 0.500000 0.000000 0.500000\n" + " 0.000000 0.500000 0.500000\n" + " 0.250000 0.500000 0.500000\n" + " 0.500000 0.500000 0.500000\n" + " 0.000000 1.000000 0.500000\n" + " 0.250000 1.000000 0.500000\n" + " 0.500000 1.000000 0.500000\n" + " 0.000000 0.000000 1.000000\n" + " 0.250000 0.000000 1.000000\n" + " 0.500000 0.000000 1.000000\n" + " 0.000000 0.500000 1.000000\n" + " 0.250000 0.500000 1.000000\n" + " 0.500000 0.500000 1.000000\n" + " 0.000000 1.000000 1.000000\n" + " 0.250000 1.000000 1.000000\n" + " 0.500000 1.000000 1.000000\n" + "\n" + "# end\n" + "\n" + "# Truelight profile\n" + "title{madeup on some display}\n" + "print{someprint}\n" + "display{some}\n" + "cubeFile{madeup.cube}\n" + "\n" + " # This last line confirms 'end' tag is obeyed\n" + " 1.23456 1.23456 1.23456\n"; + + std::istringstream lutIStream; + lutIStream.str(luttext); + + // Read file + OCIO::LocalFileFormat tester; + OCIO::CachedFileRcPtr cachedFile; + OIIO_CHECK_NO_THOW(cachedFile = tester.Read(lutIStream)); + OCIO::LocalCachedFileRcPtr lut = OCIO::DynamicPtrCast<OCIO::LocalCachedFile>(cachedFile); + + OIIO_CHECK_ASSERT(lut->has1D); + OIIO_CHECK_ASSERT(lut->has3D); + + float data[4*3] = { 0.1f, 0.2f, 0.3f, 0.0f, + 1.0f, 0.5f, 0.123456f, 0.0f, + -1.0f, 1.5f, 0.5f, 0.0f }; + + float result[4*3] = { 0.05f, 0.2f, 0.3f, 0.0f, + 0.50f, 0.5f, 0.123456f, 0.0f, + 0.0f, 1.5f, 0.5f, 0.0f }; + + OCIO::OpRcPtrVec ops; + if(lut->has1D) + { + CreateLut1DOp(ops, lut->lut1D, + OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_FORWARD); + } + if(lut->has3D) + { + CreateLut3DOp(ops, lut->lut3D, + OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_FORWARD); + } + FinalizeOpVec(ops); + + + // Apply the result + for(OCIO::OpRcPtrVec::size_type i = 0, size = ops.size(); i < size; ++i) + { + ops[i]->apply(data, 3); + } + + for(int i=0; i<3; ++i) + { + OIIO_CHECK_CLOSE( data[i], result[i], 1.0e-6 ); + } +} + +OIIO_ADD_TEST(FileFormatTruelight, Shaper) +{ + const char * luttext = "# Truelight Cube v2.0\n" + "# lutLength 11\n" + "# iDims 3\n" + "\n" + "\n" + "# InputLUT\n" + " 0.000 0.000 -0.000\n" + " 0.200 0.010 -0.100\n" + " 0.400 0.040 -0.200\n" + " 0.600 0.090 -0.300\n" + " 0.800 0.160 -0.400\n" + " 1.000 0.250 -0.500\n" + " 1.200 0.360 -0.600\n" + " 1.400 0.490 -0.700\n" + " 1.600 0.640 -0.800\n" + " 1.800 0.820 -0.900\n" + " 2.000 1.000 -1.000\n" + "\n\n\n" + "# end\n"; + + std::istringstream lutIStream; + lutIStream.str(luttext); + + // Read file + OCIO::LocalFileFormat tester; + OCIO::CachedFileRcPtr cachedFile; + OIIO_CHECK_NO_THOW(cachedFile = tester.Read(lutIStream)); + + OCIO::LocalCachedFileRcPtr lut = OCIO::DynamicPtrCast<OCIO::LocalCachedFile>(cachedFile); + + OIIO_CHECK_ASSERT(lut->has1D); + OIIO_CHECK_ASSERT(!lut->has3D); + + float data[4*3] = { 0.1f, 0.2f, 0.3f, 0.0f, + 1.0f, 0.5f, 0.123456f, 0.0f, + -1.0f, 1.5f, 0.5f, 0.0f }; + + float result[4*3] = { 0.2f, 0.04f, -0.3f, 0.0f, + 2.0f, 0.25f, -0.123456f, 0.0f, + 0.0f, 1.0f, -0.5f, 0.0f }; + + OCIO::OpRcPtrVec ops; + if(lut->has1D) + { + CreateLut1DOp(ops, lut->lut1D, + OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_FORWARD); + } + if(lut->has3D) + { + CreateLut3DOp(ops, lut->lut3D, + OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_FORWARD); + } + FinalizeOpVec(ops); + + + // Apply the result + for(OCIO::OpRcPtrVec::size_type i = 0, size = ops.size(); i < size; ++i) + { + ops[i]->apply(data, 3); + } + + for(int i=0; i<3; ++i) + { + OIIO_CHECK_CLOSE( data[i], result[i], 1.0e-6 ); + } +} + + +OIIO_ADD_TEST(FileFormatTruelight, Lut3D) +{ + // This lowers the red channel by 0.5, other channels are unaffected. + const char * luttext = "# Truelight Cube v2.0\n" + "# iDims 3\n" + "# oDims 3\n" + "# width 3 3 3\n" + "\n\n\n" + "# Cube\n" + " 0.000000 0.000000 0.000000\n" + " 0.250000 0.000000 0.000000\n" + " 0.500000 0.000000 0.000000\n" + " 0.000000 0.500000 0.000000\n" + " 0.250000 0.500000 0.000000\n" + " 0.500000 0.500000 0.000000\n" + " 0.000000 1.000000 0.000000\n" + " 0.250000 1.000000 0.000000\n" + " 0.500000 1.000000 0.000000\n" + " 0.000000 0.000000 0.500000\n" + " 0.250000 0.000000 0.500000\n" + " 0.500000 0.000000 0.500000\n" + " 0.000000 0.500000 0.500000\n" + " 0.250000 0.500000 0.500000\n" + " 0.500000 0.500000 0.500000\n" + " 0.000000 1.000000 0.500000\n" + " 0.250000 1.000000 0.500000\n" + " 0.500000 1.000000 0.500000\n" + " 0.000000 0.000000 1.000000\n" + " 0.250000 0.000000 1.000000\n" + " 0.500000 0.000000 1.000000\n" + " 0.000000 0.500000 1.000000\n" + " 0.250000 0.500000 1.000000\n" + " 0.500000 0.500000 1.000000\n" + " 0.000000 1.000000 1.000000\n" + " 0.250000 1.000000 1.000000\n" + " 0.500000 1.000000 1.000000\n" + "\n" + "# end\n"; + + std::istringstream lutIStream; + lutIStream.str(luttext); + + // Read file + OCIO::LocalFileFormat tester; + OCIO::CachedFileRcPtr cachedFile; + OIIO_CHECK_NO_THOW(cachedFile = tester.Read(lutIStream)); + OCIO::LocalCachedFileRcPtr lut = OCIO::DynamicPtrCast<OCIO::LocalCachedFile>(cachedFile); + + OIIO_CHECK_ASSERT(!lut->has1D); + OIIO_CHECK_ASSERT(lut->has3D); + + float data[4*3] = { 0.1f, 0.2f, 0.3f, 0.0f, + 1.0f, 0.5f, 0.123456f, 0.0f, + -1.0f, 1.5f, 0.5f, 0.0f }; + + float result[4*3] = { 0.05f, 0.2f, 0.3f, 0.0f, + 0.50f, 0.5f, 0.123456f, 0.0f, + 0.0f, 1.5f, 0.5f, 0.0f }; + + OCIO::OpRcPtrVec ops; + if(lut->has1D) + { + CreateLut1DOp(ops, lut->lut1D, + OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_FORWARD); + } + if(lut->has3D) + { + CreateLut3DOp(ops, lut->lut3D, + OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_FORWARD); + } + FinalizeOpVec(ops); + + + // Apply the result + for(OCIO::OpRcPtrVec::size_type i = 0, size = ops.size(); i < size; ++i) + { + ops[i]->apply(data, 3); + } + + for(int i=0; i<3; ++i) + { + OIIO_CHECK_CLOSE( data[i], result[i], 1.0e-6 ); + } +} + +#endif // OCIO_UNIT_TEST |