diff options
Diffstat (limited to 'src/core/FileFormat3DL.cpp')
-rw-r--r-- | src/core/FileFormat3DL.cpp | 638 |
1 files changed, 638 insertions, 0 deletions
diff --git a/src/core/FileFormat3DL.cpp b/src/core/FileFormat3DL.cpp new file mode 100644 index 0000000..9d7e131 --- /dev/null +++ b/src/core/FileFormat3DL.cpp @@ -0,0 +1,638 @@ +/* +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 <OpenColorIO/OpenColorIO.h> + +#include "FileTransform.h" +#include "Lut1DOp.h" +#include "Lut3DOp.h" +#include "MathUtils.h" +#include "ParseUtils.h" +#include "pystring/pystring.h" + +#include <algorithm> +#include <cmath> +#include <cstdio> +#include <sstream> + +/* +// Discreet's Flame Lut Format +// Use a loose interpretation of the format to allow other 3d luts that look +// similar, but dont strictly adhere to the real definition. + +// If line starts with text or # skip it +// If line is a bunch of ints (more than 3) , it's the 1d shaper lut + +// All remaining lines of size 3 int are data +// cube size is determined from num entries +// The bit depth of the shaper lut and the 3d lut need not be the same. + +Example 1, FLAME +# Comment here +0 64 128 192 256 320 384 448 512 576 640 704 768 832 896 960 1023 + +0 0 0 +0 0 100 +0 0 200 + + +Example 2, LUSTRE +#Tokens required by applications - do not edit +3DMESH +Mesh 4 12 +0 64 128 192 256 320 384 448 512 576 640 704 768 832 896 960 1023 + + + +0 17 17 +0 0 88 +0 0 157 +9 101 197 +0 118 308 +... + +4092 4094 4094 + +#Tokens required by applications - do not edit + +LUT8 +gamma 1.0 + +In this example, the 3D LUT has an input bit depth of 4 bits and an output +bit depth of 12 bits. You use the input value to calculate the RGB triplet +to be 17*17*17 (where 17=(2 to the power of 4)+1, and 4 is the input bit +depth). The first triplet is the output value at (0,0,0);(0,0,1);...; +(0,0,16) r,g,b coordinates; the second triplet is the output value at +(0,1,0);(0,1,1);...;(0,1,16) r,g,b coordinates; and so on. You use the output +bit depth to set the output bit depth range (12 bits or 0-4095). +NoteLustre supports an input and output depth of 16 bits for 3D LUTs; however, +in the processing pipeline, the BLACK_LEVEL to WHITE_LEVEL range is only 14 +bits. This means that even if the 3D LUT is 16 bits, it is normalized to +fit the BLACK_LEVEL to WHITE_LEVEL range of Lustre. +In Lustre, 3D LUT files can contain grids of 17 cubed, 33 cubed, and 65 cubed; +however, Lustre converts 17 cubed and 65 cubed grids to 33 cubed for internal +processing on the output (for rendering and calibration), but not on the input +3D LUT. +*/ + + +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 Write(const Baker & baker, + const std::string & formatName, + std::ostream & ostream) const; + + virtual void BuildFileOps(OpRcPtrVec & ops, + const Config& config, + const ConstContextRcPtr & context, + CachedFileRcPtr untypedCachedFile, + const FileTransform& fileTransform, + TransformDirection dir) const; + }; + + + + + + + // We use the maximum value found in the lut to infer + // the bit depth. While this is fugly. We dont believe + // there is a better way, looking at the file, to + // determine this. + // + // Note: We allow for 2x overshoot in the luts. + // As we dont allow for odd bit depths, this isnt a big deal. + // So sizes from 1/2 max - 2x max are valid + // + // FILE EXPECTED MAX CORRECTLY DECODED IF MAX IN THIS RANGE + // 8-bit 255 [0, 511] + // 10-bit 1023 [512, 2047] + // 12-bit 4095 [2048, 8191] + // 14-bit 16383 [8192, 32767] + // 16-bit 65535 [32768, 131071+] + + int GetLikelyLutBitDepth(int testval) + { + const int MIN_BIT_DEPTH = 8; + const int MAX_BIT_DEPTH = 16; + + if(testval < 0) return -1; + + // Only test even bit depths + for(int bitDepth = MIN_BIT_DEPTH; + bitDepth <= MAX_BIT_DEPTH; bitDepth+=2) + { + int maxcode = static_cast<int>(pow(2.0,bitDepth)); + int adjustedMax = maxcode * 2 - 1; + if(testval<=adjustedMax) return bitDepth; + } + + return MAX_BIT_DEPTH; + } + + int GetMaxValueFromIntegerBitDepth(int bitDepth) + { + return static_cast<int>( pow(2.0, bitDepth) ) - 1; + } + + int GetClampedIntFromNormFloat(float val, float scale) + { + val = std::min(std::max(0.0f, val), 1.0f) * scale; + return static_cast<int>(roundf(val)); + } + + void LocalFileFormat::GetFormatInfo(FormatInfoVec & formatInfoVec) const + { + FormatInfo info; + info.name = "flame"; + info.extension = "3dl"; + info.capabilities = (FORMAT_CAPABILITY_READ | FORMAT_CAPABILITY_WRITE); + formatInfoVec.push_back(info); + + FormatInfo info2 = info; + info2.name = "lustre"; + formatInfoVec.push_back(info2); + } + + // Try and load the format + // Raise an exception if it can't be loaded. + + CachedFileRcPtr LocalFileFormat::Read(std::istream & istream) const + { + std::vector<int> rawshaper; + std::vector<int> raw3d; + + // Parse the file 3d lut data to an int array + { + const int MAX_LINE_SIZE = 4096; + char lineBuffer[MAX_LINE_SIZE]; + + std::vector<std::string> lineParts; + std::vector<int> tmpData; + + while(istream.good()) + { + istream.getline(lineBuffer, MAX_LINE_SIZE); + + // Strip and split the line + pystring::split(pystring::strip(lineBuffer), lineParts); + + if(lineParts.empty()) continue; + if((lineParts.size() > 0) && pystring::startswith(lineParts[0],"#")) continue; + + // If we havent found a list of ints, continue + if(!StringVecToIntVec(tmpData, lineParts)) continue; + + // If we've found more than 3 ints, and dont have + // a shaper lut yet, we've got it! + if(tmpData.size()>3 && rawshaper.empty()) + { + for(unsigned int i=0; i<tmpData.size(); ++i) + { + rawshaper.push_back(tmpData[i]); + } + } + + // If we've found 3 ints, add it to our 3dlut. + if(tmpData.size() == 3) + { + raw3d.push_back(tmpData[0]); + raw3d.push_back(tmpData[1]); + raw3d.push_back(tmpData[2]); + } + } + } + + if(raw3d.empty() && rawshaper.empty()) + { + std::ostringstream os; + os << "Error parsing .3dl file."; + os << "Does not appear to contain a valid shaper lut or a 3D lut."; + throw Exception(os.str().c_str()); + } + + LocalCachedFileRcPtr cachedFile = LocalCachedFileRcPtr(new LocalCachedFile()); + + // If all we're doing to parse the format is to read in sets of 3 numbers, + // it's possible that other formats will accidentally be able to be read + // mistakenly as .3dl files. We can exclude a huge segement of these mis-reads + // by screening for files that use float represenations. I.e., if the MAX + // value of the lut is a small number (such as <128.0) it's likely not an integer + // format, and thus not a likely 3DL file. + + const int FORMAT3DL_CODEVALUE_LOWEST_PLAUSIBLE_MAXINT = 128; + + // Interpret the shaper lut + if(!rawshaper.empty()) + { + cachedFile->has1D = true; + + // Find the maximum shaper lut value to infer bit-depth + int shapermax = 0; + for(unsigned int i=0; i<rawshaper.size(); ++i) + { + shapermax = std::max(shapermax, rawshaper[i]); + } + + if(shapermax<FORMAT3DL_CODEVALUE_LOWEST_PLAUSIBLE_MAXINT) + { + std::ostringstream os; + os << "Error parsing .3dl file."; + os << "The maximum shaper lut value, " << shapermax; + os << ", is unreasonably low. This lut is probably not a .3dl "; + os << "file, but instead a related format that shares a similar "; + os << "structure."; + + throw Exception(os.str().c_str()); + } + + int shaperbitdepth = GetLikelyLutBitDepth(shapermax); + if(shaperbitdepth<0) + { + std::ostringstream os; + os << "Error parsing .3dl file."; + os << "The maximum shaper lut value, " << shapermax; + os << ", does not correspond to any likely bit depth. "; + os << "Please confirm source file is valid."; + throw Exception(os.str().c_str()); + } + + int bitdepthmax = GetMaxValueFromIntegerBitDepth(shaperbitdepth); + float scale = 1.0f / static_cast<float>(bitdepthmax); + + for(int channel=0; channel<3; ++channel) + { + cachedFile->lut1D->luts[channel].resize(rawshaper.size()); + + for(unsigned int i=0; i<rawshaper.size(); ++i) + { + cachedFile->lut1D->luts[channel][i] = static_cast<float>(rawshaper[i])*scale; + } + } + + // The error threshold will be 2 code values. This will allow + // shaper luts which use different int conversions (round vs. floor) + // to both be optimized. + // Required: Abs Tolerance + + const int FORMAT3DL_SHAPER_CODEVALUE_TOLERANCE = 2; + cachedFile->lut1D->maxerror = FORMAT3DL_SHAPER_CODEVALUE_TOLERANCE*scale; + cachedFile->lut1D->errortype = ERROR_ABSOLUTE; + } + + + + // Interpret the parsed data. + if(!raw3d.empty()) + { + cachedFile->has3D = true; + + // Find the maximum shaper lut value to infer bit-depth + int lut3dmax = 0; + for(unsigned int i=0; i<raw3d.size(); ++i) + { + lut3dmax = std::max(lut3dmax, raw3d[i]); + } + + if(lut3dmax<FORMAT3DL_CODEVALUE_LOWEST_PLAUSIBLE_MAXINT) + { + std::ostringstream os; + os << "Error parsing .3dl file."; + os << "The maximum 3d lut value, " << lut3dmax; + os << ", is unreasonably low. This lut is probably not a .3dl "; + os << "file, but instead a related format that shares a similar "; + os << "structure."; + + throw Exception(os.str().c_str()); + } + + int lut3dbitdepth = GetLikelyLutBitDepth(lut3dmax); + if(lut3dbitdepth<0) + { + std::ostringstream os; + os << "Error parsing .3dl file."; + os << "The maximum 3d lut value, " << lut3dmax; + os << ", does not correspond to any likely bit depth. "; + os << "Please confirm source file is valid."; + throw Exception(os.str().c_str()); + } + + int bitdepthmax = GetMaxValueFromIntegerBitDepth(lut3dbitdepth); + float scale = 1.0f / static_cast<float>(bitdepthmax); + + // Interpret the int array as a 3dlut + int lutEdgeLen = Get3DLutEdgeLenFromNumPixels((int)raw3d.size()/3); + + // Reformat 3D data + cachedFile->lut3D->size[0] = lutEdgeLen; + cachedFile->lut3D->size[1] = lutEdgeLen; + cachedFile->lut3D->size[2] = lutEdgeLen; + cachedFile->lut3D->lut.reserve(lutEdgeLen * lutEdgeLen * lutEdgeLen * 3); + + for(int rIndex=0; rIndex<lutEdgeLen; ++rIndex) + { + for(int gIndex=0; gIndex<lutEdgeLen; ++gIndex) + { + for(int bIndex=0; bIndex<lutEdgeLen; ++bIndex) + { + int i = GetLut3DIndex_B(rIndex, gIndex, bIndex, + lutEdgeLen, lutEdgeLen, lutEdgeLen); + + cachedFile->lut3D->lut.push_back(static_cast<float>(raw3d[i+0]) * scale); + cachedFile->lut3D->lut.push_back(static_cast<float>(raw3d[i+1]) * scale); + cachedFile->lut3D->lut.push_back(static_cast<float>(raw3d[i+2]) * scale); + } + } + } + } + + return cachedFile; + } + + // 65 -> 6 + // 33 -> 5 + // 17 -> 4 + + int CubeDimensionLenToLustreBitDepth(int size) + { + float logval = logf(static_cast<float>(size-1)) / logf(2.0); + return static_cast<int>(logval); + } + + void LocalFileFormat::Write(const Baker & baker, + const std::string & formatName, + std::ostream & ostream) const + { + int DEFAULT_CUBE_SIZE = 0; + int SHAPER_BIT_DEPTH = 10; + int CUBE_BIT_DEPTH = 12; + + if(formatName == "lustre") + { + DEFAULT_CUBE_SIZE = 33; + } + else if(formatName == "flame") + { + DEFAULT_CUBE_SIZE = 17; + } + else + { + std::ostringstream os; + os << "Unknown 3dl format name, '"; + os << formatName << "'."; + throw Exception(os.str().c_str()); + } + + ConstConfigRcPtr config = baker.getConfig(); + + int cubeSize = baker.getCubeSize(); + if(cubeSize==-1) cubeSize = DEFAULT_CUBE_SIZE; + cubeSize = std::max(2, cubeSize); // smallest cube is 2x2x2 + + int shaperSize = baker.getShaperSize(); + if(shaperSize==-1) shaperSize = cubeSize; + + std::vector<float> cubeData; + cubeData.resize(cubeSize*cubeSize*cubeSize*3); + GenerateIdentityLut3D(&cubeData[0], cubeSize, 3, LUT3DORDER_FAST_BLUE); + PackedImageDesc cubeImg(&cubeData[0], cubeSize*cubeSize*cubeSize, 1, 3); + + // Apply our conversion from the input space to the output space. + ConstProcessorRcPtr inputToTarget; + std::string looks = baker.getLooks(); + if (!looks.empty()) + { + LookTransformRcPtr transform = LookTransform::Create(); + transform->setLooks(looks.c_str()); + transform->setSrc(baker.getInputSpace()); + transform->setDst(baker.getTargetSpace()); + inputToTarget = config->getProcessor(transform, + TRANSFORM_DIR_FORWARD); + } + else + { + inputToTarget = config->getProcessor(baker.getInputSpace(), + baker.getTargetSpace()); + } + inputToTarget->apply(cubeImg); + + // Write out the file. + // For for maximum compatibility with other apps, we will + // not utilize the shaper or output any metadata + + if(formatName == "lustre") + { + int meshInputBitDepth = CubeDimensionLenToLustreBitDepth(cubeSize); + ostream << "3DMESH\n"; + ostream << "Mesh " << meshInputBitDepth << " " << CUBE_BIT_DEPTH << "\n"; + } + + std::vector<float> shaperData(shaperSize); + GenerateIdentityLut1D(&shaperData[0], shaperSize, 1); + + float shaperScale = static_cast<float>( + GetMaxValueFromIntegerBitDepth(SHAPER_BIT_DEPTH)); + + for(unsigned int i=0; i<shaperData.size(); ++i) + { + if(i != 0) ostream << " "; + int val = GetClampedIntFromNormFloat(shaperData[i], shaperScale); + ostream << val; + } + ostream << "\n"; + + // Write out the 3D Cube + float cubeScale = static_cast<float>( + GetMaxValueFromIntegerBitDepth(CUBE_BIT_DEPTH)); + if(cubeSize < 2) + { + throw Exception("Internal cube size exception."); + } + for(int i=0; i<cubeSize*cubeSize*cubeSize; ++i) + { + int r = GetClampedIntFromNormFloat(cubeData[3*i+0], cubeScale); + int g = GetClampedIntFromNormFloat(cubeData[3*i+1], cubeScale); + int b = GetClampedIntFromNormFloat(cubeData[3*i+2], cubeScale); + ostream << r << " " << g << " " << b << "\n"; + } + ostream << "\n"; + + if(formatName == "lustre") + { + ostream << "LUT8\n"; + ostream << "gamma 1.0\n"; + } + } + + 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 .3dl 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); + } + if(cachedFile->has3D) + { + CreateLut3DOp(ops, cachedFile->lut3D, + fileTransform.getInterpolation(), newDir); + } + } + else if(newDir == TRANSFORM_DIR_INVERSE) + { + if(cachedFile->has3D) + { + CreateLut3DOp(ops, cachedFile->lut3D, + fileTransform.getInterpolation(), newDir); + } + if(cachedFile->has1D) + { + CreateLut1DOp(ops, cachedFile->lut1D, + INTERP_LINEAR, newDir); + } + } + } + } + + FileFormat * CreateFileFormat3DL() + { + return new LocalFileFormat(); + } +} +OCIO_NAMESPACE_EXIT + +#ifdef OCIO_UNIT_TEST + +namespace OCIO = OCIO_NAMESPACE; +#include "UnitTest.h" + +// FILE EXPECTED MAX CORRECTLY DECODED IF MAX IN THIS RANGE +// 8-bit 255 [0, 511] +// 10-bit 1023 [512, 2047] +// 12-bit 4095 [2048, 8191] +// 14-bit 16383 [8192, 32767] +// 16-bit 65535 [32768, 131071] + +OIIO_ADD_TEST(FileFormat3DL, GetLikelyLutBitDepth) +{ + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(-1), -1); + + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(0), 8); + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(1), 8); + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(255), 8); + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(256), 8); + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(511), 8); + + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(512), 10); + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(1023), 10); + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(1024), 10); + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(2047), 10); + + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(2048), 12); + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(4095), 12); + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(4096), 12); + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(8191), 12); + + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(16383), 14); + + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(65535), 16); + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(65536), 16); + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(131071), 16); + + OIIO_CHECK_EQUAL(OCIO::GetLikelyLutBitDepth(131072), 16); +} + +#endif // OCIO_UNIT_TEST |