diff options
Diffstat (limited to 'src/core/FileFormatCSP.cpp')
-rw-r--r-- | src/core/FileFormatCSP.cpp | 1112 |
1 files changed, 1112 insertions, 0 deletions
diff --git a/src/core/FileFormatCSP.cpp b/src/core/FileFormatCSP.cpp new file mode 100644 index 0000000..eae4d9d --- /dev/null +++ b/src/core/FileFormatCSP.cpp @@ -0,0 +1,1112 @@ +/* +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 <cassert> +#include <cmath> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <iostream> +#include <iterator> +#include <sstream> + +#include <OpenColorIO/OpenColorIO.h> + +#include "FileTransform.h" +#include "Lut1DOp.h" +#include "Lut3DOp.h" +#include "MathUtils.h" +#include "ParseUtils.h" +#include "pystring/pystring.h" + +OCIO_NAMESPACE_ENTER +{ + namespace + { + const int NUM_PRELUT_SAMPLES = 65536; // 2**16 samples + // Always use linear interpolation for preluts to get the + // best precision + const Interpolation PRELUT_INTERPOLATION = INTERP_LINEAR; + + typedef struct rsr_Interpolator1D_Raw_ + { + float * stims; + float * values; + unsigned int length; + } rsr_Interpolator1D_Raw; + + rsr_Interpolator1D_Raw * rsr_Interpolator1D_Raw_create( unsigned int prelutLength ); + void rsr_Interpolator1D_Raw_destroy( rsr_Interpolator1D_Raw * prelut ); + + + /* An opaque handle to the cineSpace 1D Interpolator object */ + typedef struct rsr_Interpolator1D_ rsr_Interpolator1D; + + rsr_Interpolator1D * rsr_Interpolator1D_createFromRaw( rsr_Interpolator1D_Raw * data ); + void rsr_Interpolator1D_destroy( rsr_Interpolator1D * rawdata ); + float rsr_Interpolator1D_interpolate( float x, rsr_Interpolator1D * data ); + + + /* + * =========== INTERNAL HELPER FUNCTIONS ============ + */ + struct rsr_Interpolator1D_ + { + int nSamplePoints; + float * stims; + + /* 5 * (nSamplePoints-1) long, holding a sequence of + * 1.0/delta, a, b, c, d + * such that the curve in interval i is given by + * z = (stims[i] - x)* (1.0/delta) + * y = a + b*z + c*z^2 + d*z^3 + */ + float * parameters; + + float minValue; /* = f( stims[0] ) */ + float maxValue; /* = f(stims[nSamplePoints-1] ) */ + }; + + static int rsr_internal_I1D_refineSegment( float x, float * data, int low, int high ) + { + int midPoint; + + // TODO: Change assert to an exception? + assert( x>= data[low] ); + assert( x<= data[high] ); + assert( (high-low) > 0); + if( high-low==1 ) return low; + + midPoint = (low+high)/2; + if( x<data[midPoint] ) return rsr_internal_I1D_refineSegment(x, data, low, midPoint ); + return rsr_internal_I1D_refineSegment(x, data, midPoint, high ); + } + + static int rsr_internal_I1D_findSegmentContaining( float x, float * data, int n ) + { + return rsr_internal_I1D_refineSegment(x, data, 0, n-1); + } + + /* + * =========== USER FUNCTIONS ============ + */ + + + void rsr_Interpolator1D_destroy( rsr_Interpolator1D * data ) + { + if(data==NULL) return; + free(data->stims); + free(data->parameters); + free(data); + } + + + + float rsr_Interpolator1D_interpolate( float x, rsr_Interpolator1D * data ) + { + int segId; + float * segdata; + float invDelta; + float a,b,c,d,z; + + assert(data!=NULL); + + /* Is x in range? */ + if( isnan(x) ) return x; + + if( x<data->stims[0] ) return data->minValue; + if (x>data->stims[ data->nSamplePoints -1] ) return data->maxValue; + + /* Ok so its between the begining and end .. lets find out where... */ + segId = rsr_internal_I1D_findSegmentContaining( x, data->stims, data->nSamplePoints ); + + assert(data->parameters !=NULL ); + + segdata = data->parameters + 5 * segId; + + invDelta = segdata[0]; + a = segdata[1]; + b = segdata[2]; + c = segdata[3]; + d = segdata[4]; + + z = ( x - data->stims[segId] ) * invDelta; + + return a + z * ( b + z * ( c + d * z ) ) ; + + } + + rsr_Interpolator1D * rsr_Interpolator1D_createFromRaw( rsr_Interpolator1D_Raw * data ) + { + rsr_Interpolator1D * retval = NULL; + /* Check the sanity of the data */ + assert(data!=NULL); + assert(data->length>=2); + assert(data->stims!=NULL); + assert(data->values!=NULL); + + /* Create the real data. */ + retval = (rsr_Interpolator1D*)malloc( sizeof(rsr_Interpolator1D) ); // OCIO change: explicit cast + if(retval==NULL) + { + return NULL; + } + + retval->stims = (float*)malloc( sizeof(float) * data->length ); // OCIO change: explicit cast + if(retval->stims==NULL) + { + free(retval); + return NULL; + } + memcpy( retval->stims, data->stims, sizeof(float) * data->length ); + + retval->parameters = (float*)malloc( 5*sizeof(float) * ( data->length - 1 ) ); // OCIO change: explicit cast + if(retval->parameters==NULL) + { + free(retval->stims); + free(retval); + return NULL; + } + retval->nSamplePoints = data->length; + retval->minValue = data->values[0]; + retval->maxValue = data->values[ data->length -1]; + + /* Now the fun part .. filling in the coeficients. */ + if(data->length==2) + { + retval->parameters[0] = 1.0f/(data->stims[1]-data->stims[0]); + retval->parameters[1] = data->values[0]; + retval->parameters[2] = ( data->values[1] - data->values[0] ); + retval->parameters[3] = 0; + retval->parameters[4] = 0; + } + else + { + unsigned int i; + float * params = retval->parameters; + for(i=0; i< data->length-1; ++i) + { + float f0 = data->values[i+0]; + float f1 = data->values[i+1]; + + params[0] = 1.0f/(retval->stims[i+1]-retval->stims[i+0]); + + if(i==0) + { + float delta = data->stims[i+1] - data->stims[i]; + float delta2 = (data->stims[i+2] - data->stims[i+1])/delta; + float f2 = data->values[i+2]; + + float dfdx1 = (f2-f0)/(1+delta2); + params[1] = 1.0f * f0 + 0.0f * f1 + 0.0f * dfdx1; + params[2] = -2.0f * f0 + 2.0f * f1 - 1.0f * dfdx1; + params[3] = 1.0f * f0 - 1.0f * f1 + 1.0f * dfdx1; + params[4] = 0.0; + } + else if (i==data->length-2) + { + float delta = data->stims[i+1] - data->stims[i]; + float delta1 = (data->stims[i]-data->stims[i-1])/delta; + float fn1 = data->values[i-1]; + float dfdx0 = (f1-fn1)/(1+delta1); + params[1] = 1.0f * f0 + 0.0f * f1 + 0.0f * dfdx0; + params[2] = 0.0f * f0 + 0.0f * f1 + 1.0f * dfdx0; + params[3] = -1.0f * f0 + 1.0f * f1 - 1.0f * dfdx0; + params[4] = 0.0; + } + else + { + float delta = data->stims[i+1] - data->stims[i]; + float fn1=data->values[i-1]; + float delta1 = (data->stims[i] - data->stims[i-1])/delta; + + float f2=data->values[i+2]; + float delta2 = (data->stims[i+2] - data->stims[i+1])/delta; + + float dfdx0 = (f1-fn1)/(1.0f+delta1); + float dfdx1 = (f2-f0)/(1.0f+delta2); + + params[1] = 1.0f * f0 + 0.0f * dfdx0 + 0.0f * f1 + 0.0f * dfdx1; + params[2] = 0.0f * f0 + 1.0f * dfdx0 + 0.0f * f1 + 0.0f * dfdx1; + params[3] =-3.0f * f0 - 2.0f * dfdx0 + 3.0f * f1 - 1.0f * dfdx1; + params[4] = 2.0f * f0 + 1.0f * dfdx0 - 2.0f * f1 + 1.0f * dfdx1; + } + + params+=5; + } + } + return retval; + } + + rsr_Interpolator1D_Raw * rsr_Interpolator1D_Raw_create( unsigned int prelutLength) + { + unsigned int i; + rsr_Interpolator1D_Raw * prelut = (rsr_Interpolator1D_Raw*)malloc( sizeof(rsr_Interpolator1D_Raw) ); // OCIO change: explicit cast + if(prelut==NULL) return NULL; + + prelut->stims = (float*)malloc( sizeof(float) * prelutLength ); // OCIO change: explicit cast + if(prelut->stims==NULL) + { + free(prelut); + return NULL; + } + + prelut->values = (float*)malloc( sizeof(float) * prelutLength ); // OCIO change: explicit cast + if(prelut->values == NULL) + { + free(prelut->stims); + free(prelut); + return NULL; + } + + prelut->length = prelutLength; + + for( i=0; i<prelutLength; ++i ) + { + prelut->stims[i] = 0.0; + prelut->values[i] = 0.0; + } + + return prelut; + } + + void rsr_Interpolator1D_Raw_destroy( rsr_Interpolator1D_Raw * prelut ) + { + if(prelut==NULL) return; + free( prelut->stims ); + free( prelut->values ); + free( prelut ); + } + + } // End unnamed namespace for Interpolators.c + + namespace + { + class CachedFileCSP : public CachedFile + { + public: + CachedFileCSP () : + hasprelut(false), + csptype("unknown"), + metadata("none") + { + prelut = Lut1D::Create(); + lut1D = Lut1D::Create(); + lut3D = Lut3D::Create(); + }; + ~CachedFileCSP() {}; + + bool hasprelut; + std::string csptype; + std::string metadata; + Lut1DRcPtr prelut; + Lut1DRcPtr lut1D; + Lut3DRcPtr lut3D; + }; + typedef OCIO_SHARED_PTR<CachedFileCSP> CachedFileCSPRcPtr; + + + + 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; + }; + + + // TODO: remove this when we don't need to debug + /* + template<class T> + std::ostream& operator<< (std::ostream& os, const std::vector<T>& v) + { + copy(v.begin(), v.end(), std::ostream_iterator<T>(std::cout, " ")); + return os; + } + */ + + void LocalFileFormat::GetFormatInfo(FormatInfoVec & formatInfoVec) const + { + FormatInfo info; + info.name = "cinespace"; + info.extension = "csp"; + info.capabilities = (FORMAT_CAPABILITY_READ | FORMAT_CAPABILITY_WRITE); + 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 csp lut"); + } + + Lut1DRcPtr prelut_ptr = Lut1D::Create(); + Lut1DRcPtr lut1d_ptr = Lut1D::Create(); + Lut3DRcPtr lut3d_ptr = Lut3D::Create(); + + // try and read the lut header + std::string line; + nextline (istream, line); + if (line != "CSPLUTV100") + { + std::ostringstream os; + os << "Lut doesn't seem to be a csp file, expected 'CSPLUTV100'."; + os << "First line: '" << line << "'."; + throw Exception(os.str().c_str()); + } + + // next line tells us if we are reading a 1D or 3D lut + nextline (istream, line); + if (line != "1D" && line != "3D") + { + std::ostringstream os; + os << "Unsupported CSP lut type. Require 1D or 3D. "; + os << "Found, '" << line << "'."; + throw Exception(os.str().c_str()); + } + std::string csptype = line; + + // read meta data block + std::string metadata; + std::streampos curr_pos = istream.tellg (); + nextline (istream, line); + if (line == "BEGIN METADATA") + { + while (line != "END METADATA" || !istream) + { + nextline (istream, line); + if (line != "END METADATA") + metadata += line + "\n"; + } + } + else + { + istream.seekg (curr_pos); + } + + // Make 3 vectors of prelut inputs + output values + std::vector<float> prelut_in[3]; + std::vector<float> prelut_out[3]; + bool useprelut[3] = { false, false, false }; + + // Parse the prelut block + for (int c = 0; c < 3; ++c) + { + // how many points do we have for this channel + nextline (istream, line); + int cpoints = 0; + + if(!StringToInt(&cpoints, line.c_str()) || cpoints<0) + { + std::ostringstream os; + os << "Prelut does not specify valid dimension size on channel '"; + os << c << ": " << line; + throw Exception(os.str().c_str()); + } + + if(cpoints>=2) + { + std::vector<std::string> inputparts, outputparts; + + nextline (istream, line); + pystring::split(pystring::strip(line), inputparts); + + nextline (istream, line); + pystring::split(pystring::strip(line), outputparts); + + if(static_cast<int>(inputparts.size()) != cpoints || + static_cast<int>(outputparts.size()) != cpoints) + { + std::ostringstream os; + os << "Prelut does not specify the expected number of data points. "; + os << "Expected: " << cpoints << "."; + os << "Found: " << inputparts.size() << ", " << outputparts.size() << "."; + throw Exception(os.str().c_str()); + } + + if(!StringVecToFloatVec(prelut_in[c], inputparts) || + !StringVecToFloatVec(prelut_out[c], outputparts)) + { + std::ostringstream os; + os << "Prelut data is malformed, cannot to float array."; + throw Exception(os.str().c_str()); + } + + + useprelut[c] = (!VecsEqualWithRelError(&(prelut_in[c][0]), static_cast<int>(prelut_in[c].size()), + &(prelut_out[c][0]), static_cast<int>(prelut_out[c].size()), + 1e-6f)); + } + else + { + // Even though it's probably not part of the spec, why not allow for a size 0 + // in a channel to be specified? It should be synonymous with identity, + // and allows the code lower down to assume all 3 channels exist + + prelut_in[c].push_back(0.0f); + prelut_in[c].push_back(1.0f); + prelut_out[c].push_back(0.0f); + prelut_out[c].push_back(1.0f); + useprelut[c] = false; + } + } + + if (csptype == "1D") + { + + // how many 1D lut points do we have + nextline (istream, line); + int points1D = atoi (line.c_str()); + + // + float from_min = 0.0; + float from_max = 1.0; + for(int i=0; i<3; ++i) + { + lut1d_ptr->from_min[i] = from_min; + lut1d_ptr->from_max[i] = from_max; + lut1d_ptr->luts[i].clear(); + lut1d_ptr->luts[i].reserve(points1D); + } + + for(int i = 0; i < points1D; ++i) + { + + // scan for the three floats + float lp[3]; + nextline (istream, line); + if (sscanf (line.c_str(), "%f %f %f", + &lp[0], &lp[1], &lp[2]) != 3) { + throw Exception ("malformed 1D csp lut"); + } + + // store each channel + lut1d_ptr->luts[0].push_back(lp[0]); + lut1d_ptr->luts[1].push_back(lp[1]); + lut1d_ptr->luts[2].push_back(lp[2]); + + } + + } + else if (csptype == "3D") + { + // read the cube size + nextline (istream, line); + if (sscanf (line.c_str(), "%d %d %d", + &lut3d_ptr->size[0], + &lut3d_ptr->size[1], + &lut3d_ptr->size[2]) != 3 ) { + throw Exception("malformed 3D csp lut, couldn't read cube size"); + } + + + // resize cube + int num3dentries = lut3d_ptr->size[0] * lut3d_ptr->size[1] * lut3d_ptr->size[2]; + lut3d_ptr->lut.resize(num3dentries * 3); + + for(int i=0; i<num3dentries; ++i) + { + // load the cube + nextline (istream, line); + + if(sscanf (line.c_str(), "%f %f %f", + &lut3d_ptr->lut[3*i+0], + &lut3d_ptr->lut[3*i+1], + &lut3d_ptr->lut[3*i+2]) != 3 ) + { + std::ostringstream os; + os << "Malformed 3D csp lut, couldn't read cube row ("; + os << i << "): " << line << " ."; + throw Exception(os.str().c_str()); + } + } + } + + CachedFileCSPRcPtr cachedFile = CachedFileCSPRcPtr (new CachedFileCSP ()); + cachedFile->csptype = csptype; + cachedFile->metadata = metadata; + + if(useprelut[0] || useprelut[1] || useprelut[2]) + { + cachedFile->hasprelut = true; + + for (int c = 0; c < 3; ++c) + { + size_t prelut_numpts = prelut_in[c].size(); + float from_min = prelut_in[c][0]; + float from_max = prelut_in[c][prelut_numpts-1]; + + // Allocate the interpolator + rsr_Interpolator1D_Raw * cprelut_raw = + rsr_Interpolator1D_Raw_create(static_cast<unsigned int>(prelut_numpts)); + + // Copy our prelut data into the interpolator + for(size_t i=0; i<prelut_numpts; ++i) + { + cprelut_raw->stims[i] = prelut_in[c][i]; + cprelut_raw->values[i] = prelut_out[c][i]; + } + + // Create interpolater, to resample to simple 1D lut + rsr_Interpolator1D * interpolater = + rsr_Interpolator1D_createFromRaw(cprelut_raw); + + // Resample into 1D lut + // TODO: Fancy spline analysis to determine required number of samples + prelut_ptr->from_min[c] = from_min; + prelut_ptr->from_max[c] = from_max; + prelut_ptr->luts[c].clear(); + prelut_ptr->luts[c].reserve(NUM_PRELUT_SAMPLES); + + for (int i = 0; i < NUM_PRELUT_SAMPLES; ++i) + { + float interpo = float(i) / float(NUM_PRELUT_SAMPLES-1); + float srcval = lerpf(from_min, from_max, interpo); + float newval = rsr_Interpolator1D_interpolate(srcval, interpolater); + prelut_ptr->luts[c].push_back(newval); + } + + rsr_Interpolator1D_Raw_destroy(cprelut_raw); + rsr_Interpolator1D_destroy(interpolater); + } + + prelut_ptr->maxerror = 1e-6f; + prelut_ptr->errortype = ERROR_RELATIVE; + + cachedFile->prelut = prelut_ptr; + } + + if(csptype == "1D") + { + lut1d_ptr->maxerror = 0.0f; + lut1d_ptr->errortype = ERROR_RELATIVE; + cachedFile->lut1D = lut1d_ptr; + } + else if (csptype == "3D") + { + cachedFile->lut3D = lut3d_ptr; + } + + return cachedFile; + } + + + void LocalFileFormat::Write(const Baker & baker, + const std::string & /*formatName*/, + std::ostream & ostream) const + { + const int DEFAULT_CUBE_SIZE = 32; + const int DEFAULT_SHAPER_SIZE = 1024; + + ConstConfigRcPtr config = baker.getConfig(); + + // TODO: Add 1d/3d lut writing switch, using hasChannelCrosstalk + int cubeSize = baker.getCubeSize(); + if(cubeSize==-1) cubeSize = DEFAULT_CUBE_SIZE; + cubeSize = std::max(2, cubeSize); // smallest cube is 2x2x2 + std::vector<float> cubeData; + cubeData.resize(cubeSize*cubeSize*cubeSize*3); + GenerateIdentityLut3D(&cubeData[0], cubeSize, 3, LUT3DORDER_FAST_RED); + PackedImageDesc cubeImg(&cubeData[0], cubeSize*cubeSize*cubeSize, 1, 3); + + std::string looks = baker.getLooks(); + + std::vector<float> shaperInData; + std::vector<float> shaperOutData; + + // Use an explicitly shaper space + // TODO: Use the optional allocation for the shaper space, + // instead of the implied 0-1 uniform allocation + std::string shaperSpace = baker.getShaperSpace(); + if(!shaperSpace.empty()) + { + int shaperSize = baker.getShaperSize(); + if(shaperSize<0) shaperSize = DEFAULT_SHAPER_SIZE; + if(shaperSize<2) + { + std::ostringstream os; + os << "When a shaper space has been specified, '"; + os << baker.getShaperSpace() << "', a shaper size less than 2 is not allowed."; + throw Exception(os.str().c_str()); + } + + shaperOutData.resize(shaperSize*3); + shaperInData.resize(shaperSize*3); + GenerateIdentityLut1D(&shaperOutData[0], shaperSize, 3); + GenerateIdentityLut1D(&shaperInData[0], shaperSize, 3); + + ConstProcessorRcPtr shaperToInput = config->getProcessor(baker.getShaperSpace(), baker.getInputSpace()); + if(shaperToInput->hasChannelCrosstalk()) + { + // TODO: Automatically turn shaper into non-crosstalked version? + std::ostringstream os; + os << "The specified shaperSpace, '"; + os << baker.getShaperSpace() << "' has channel crosstalk, which is not appropriate for shapers. "; + os << "Please select an alternate shaper space or omit this option."; + throw Exception(os.str().c_str()); + } + PackedImageDesc shaperInImg(&shaperInData[0], shaperSize, 1, 3); + shaperToInput->apply(shaperInImg); + + ConstProcessorRcPtr shaperToTarget; + if (!looks.empty()) + { + LookTransformRcPtr transform = LookTransform::Create(); + transform->setLooks(looks.c_str()); + transform->setSrc(baker.getShaperSpace()); + transform->setDst(baker.getTargetSpace()); + shaperToTarget = config->getProcessor(transform, + TRANSFORM_DIR_FORWARD); + } + else + { + shaperToTarget = config->getProcessor(baker.getShaperSpace(), + baker.getTargetSpace()); + } + shaperToTarget->apply(cubeImg); + } + else + { + // A shaper is not specified, let's fake one, using the input space allocation as + // our guide + + ConstColorSpaceRcPtr inputColorSpace = config->getColorSpace(baker.getInputSpace()); + + if(!inputColorSpace) + { + std::ostringstream os; + os << "Could not find colorspace '" << baker.getInputSpace() << "'"; + throw Exception(os.str().c_str()); + } + + // Let's make an allocation transform for this colorspace + AllocationTransformRcPtr allocationTransform = AllocationTransform::Create(); + allocationTransform->setAllocation(inputColorSpace->getAllocation()); + + // numVars may be '0' + int numVars = inputColorSpace->getAllocationNumVars(); + if(numVars>0) + { + std::vector<float> vars(numVars); + inputColorSpace->getAllocationVars(&vars[0]); + allocationTransform->setVars(numVars, &vars[0]); + } + else + { + allocationTransform->setVars(0, NULL); + } + + // What size shaper should we make? + int shaperSize = baker.getShaperSize(); + if(shaperSize<0) shaperSize = DEFAULT_SHAPER_SIZE; + shaperSize = std::max(2, shaperSize); + if(inputColorSpace->getAllocation() == ALLOCATION_UNIFORM) + { + // This is an awesome optimization. + // If we know it's a uniform scaling, only 2 points will suffice! + shaperSize = 2; + } + shaperOutData.resize(shaperSize*3); + shaperInData.resize(shaperSize*3); + GenerateIdentityLut1D(&shaperOutData[0], shaperSize, 3); + GenerateIdentityLut1D(&shaperInData[0], shaperSize, 3); + + // Apply the forward to the allocation to the output shaper y axis, and the cube + ConstProcessorRcPtr shaperToInput = config->getProcessor(allocationTransform, TRANSFORM_DIR_INVERSE); + PackedImageDesc shaperInImg(&shaperInData[0], shaperSize, 1, 3); + shaperToInput->apply(shaperInImg); + shaperToInput->apply(cubeImg); + + // Apply the 3d lut to the remainder (from the input to the output) + ConstProcessorRcPtr inputToTarget; + 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 + ostream << "CSPLUTV100\n"; + ostream << "3D\n"; + ostream << "BEGIN METADATA\n"; + std::string metadata = baker.getMetadata(); + if(!metadata.empty()) + { + ostream << metadata << "\n"; + } + ostream << "END METADATA\n"; + ostream << "\n"; + + // Write out the 1D Prelut + ostream.setf(std::ios::fixed, std::ios::floatfield); + ostream.precision(6); + + if(shaperInData.size()<2 || shaperOutData.size() != shaperInData.size()) + { + throw Exception("Internal shaper size exception."); + } + + if(!shaperInData.empty()) + { + for(int c=0; c<3; ++c) + { + ostream << shaperInData.size()/3 << "\n"; + for(unsigned int i = 0; i<shaperInData.size()/3; ++i) + { + if(i != 0) ostream << " "; + ostream << shaperInData[3*i+c]; + } + ostream << "\n"; + + for(unsigned int i = 0; i<shaperInData.size()/3; ++i) + { + if(i != 0) ostream << " "; + ostream << shaperOutData[3*i+c]; + } + ostream << "\n"; + } + } + ostream << "\n"; + + // Write out the 3D Cube + if(cubeSize < 2) + { + throw Exception("Internal cube size exception."); + } + ostream << cubeSize << " " << cubeSize << " " << cubeSize << "\n"; + for(int i=0; i<cubeSize*cubeSize*cubeSize; ++i) + { + ostream << cubeData[3*i+0] << " " << cubeData[3*i+1] << " " << cubeData[3*i+2] << "\n"; + } + ostream << "\n"; + } + + void + LocalFileFormat::BuildFileOps(OpRcPtrVec & ops, + const Config& /*config*/, + const ConstContextRcPtr & /*context*/, + CachedFileRcPtr untypedCachedFile, + const FileTransform& fileTransform, + TransformDirection dir) const + { + + CachedFileCSPRcPtr cachedFile = DynamicPtrCast<CachedFileCSP>(untypedCachedFile); + + // This should never happen. + if(!cachedFile) + { + std::ostringstream os; + os << "Cannot build CSP Op. Invalid cache type."; + throw Exception(os.str().c_str()); + } + + TransformDirection newDir = CombineTransformDirections(dir, + fileTransform.getDirection()); + + if(newDir == TRANSFORM_DIR_FORWARD) + { + if(cachedFile->hasprelut) + { + CreateLut1DOp(ops, cachedFile->prelut, + PRELUT_INTERPOLATION, newDir); + } + if(cachedFile->csptype == "1D") + CreateLut1DOp(ops, cachedFile->lut1D, + fileTransform.getInterpolation(), newDir); + else if(cachedFile->csptype == "3D") + CreateLut3DOp(ops, cachedFile->lut3D, + fileTransform.getInterpolation(), newDir); + } + else if(newDir == TRANSFORM_DIR_INVERSE) + { + if(cachedFile->csptype == "1D") + CreateLut1DOp(ops, cachedFile->lut1D, + fileTransform.getInterpolation(), newDir); + else if(cachedFile->csptype == "3D") + CreateLut3DOp(ops, cachedFile->lut3D, + fileTransform.getInterpolation(), newDir); + if(cachedFile->hasprelut) + { + CreateLut1DOp(ops, cachedFile->prelut, + PRELUT_INTERPOLATION, newDir); + } + } + + return; + + } + } + + FileFormat * CreateFileFormatCSP() + { + return new LocalFileFormat(); + } +} +OCIO_NAMESPACE_EXIT + + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef OCIO_UNIT_TEST + +namespace OCIO = OCIO_NAMESPACE; +#include "UnitTest.h" + +OIIO_ADD_TEST(FileFormatCSP, simple1D) +{ + std::ostringstream strebuf; + strebuf << "CSPLUTV100" << "\n"; + strebuf << "1D" << "\n"; + strebuf << "" << "\n"; + strebuf << "BEGIN METADATA" << "\n"; + strebuf << "foobar" << "\n"; + strebuf << "END METADATA" << "\n"; + strebuf << "" << "\n"; + strebuf << "2" << "\n"; + strebuf << "0.0 1.0" << "\n"; + strebuf << "0.0 2.0" << "\n"; + strebuf << "6" << "\n"; + strebuf << "0.0 0.2 0.4 0.6 0.8 1.0" << "\n"; + strebuf << "0.0 0.4 0.8 1.2 1.6 2.0" << "\n"; + strebuf << "3" << "\n"; + strebuf << "0.0 0.1 1.0" << "\n"; + strebuf << "0.0 0.2 2.0" << "\n"; + strebuf << "" << "\n"; + strebuf << "6" << "\n"; + strebuf << "0.0 0.0 0.0" << "\n"; + strebuf << "0.2 0.3 0.1" << "\n"; + strebuf << "0.4 0.5 0.2" << "\n"; + strebuf << "0.5 0.6 0.3" << "\n"; + strebuf << "0.6 0.8 0.4" << "\n"; + strebuf << "1.0 0.9 0.5" << "\n"; + + float red[6] = { 0.0f, 0.2f, 0.4f, 0.5f, 0.6f, 1.0f }; + float green[6] = { 0.0f, 0.3f, 0.5f, 0.6f, 0.8f, 0.9f }; + float blue[6] = { 0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f }; + + std::istringstream simple1D; + simple1D.str(strebuf.str()); + + // Read file + OCIO::LocalFileFormat tester; + OCIO::CachedFileRcPtr cachedFile = tester.Read(simple1D); + OCIO::CachedFileCSPRcPtr csplut = OCIO::DynamicPtrCast<OCIO::CachedFileCSP>(cachedFile); + + // check prelut data (note: the spline is resampled into a 1D LUT) + for(int c=0; c<3; ++c) + { + for (unsigned int i = 0; i < csplut->prelut->luts[c].size(); i += 128) + { + float input = float(i) / float(csplut->prelut->luts[c].size()-1); + float output = csplut->prelut->luts[c][i]; + OIIO_CHECK_CLOSE(input*2.0f, output, 1e-4); + } + } + // check 1D data + // red + unsigned int i; + for(i = 0; i < csplut->lut1D->luts[0].size(); ++i) + OIIO_CHECK_EQUAL(red[i], csplut->lut1D->luts[0][i]); + // green + for(i = 0; i < csplut->lut1D->luts[1].size(); ++i) + OIIO_CHECK_EQUAL(green[i], csplut->lut1D->luts[1][i]); + // blue + for(i = 0; i < csplut->lut1D->luts[2].size(); ++i) + OIIO_CHECK_EQUAL(blue[i], csplut->lut1D->luts[2][i]); + +} + +OIIO_ADD_TEST(FileFormatCSP, simple3D) +{ + std::ostringstream strebuf; + strebuf << "CSPLUTV100" << "\n"; + strebuf << "3D" << "\n"; + strebuf << "" << "\n"; + strebuf << "BEGIN METADATA" << "\n"; + strebuf << "foobar" << "\n"; + strebuf << "END METADATA" << "\n"; + strebuf << "" << "\n"; + strebuf << "11" << "\n"; + strebuf << "0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0" << "\n"; + strebuf << "0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0" << "\n"; + strebuf << "6" << "\n"; + strebuf << "0.0 0.2 0.4 0.6 0.8 1.0" << "\n"; + strebuf << "0.0 0.2000000 0.4 0.6 0.8 1.0" << "\n"; + strebuf << "5" << "\n"; + strebuf << "0.0 0.25 0.5 0.6 0.7" << "\n"; + strebuf << "0.0 0.25000001 0.5 0.6 0.7" << "\n"; + strebuf << "" << "\n"; + strebuf << "1 2 3" << "\n"; + strebuf << "0.0 0.0 0.0" << "\n"; + strebuf << "1.0 0.0 0.0" << "\n"; + strebuf << "0.0 0.5 0.0" << "\n"; + strebuf << "1.0 0.5 0.0" << "\n"; + strebuf << "0.0 1.0 0.0" << "\n"; + strebuf << "1.0 1.0 0.0" << "\n"; + + float cube[1 * 2 * 3 * 3] = { 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 0.0, 0.5, 0.0, + 1.0, 0.5, 0.0, + 0.0, 1.0, 0.0, + 1.0, 1.0, 0.0 }; + + std::istringstream simple3D; + simple3D.str(strebuf.str()); + + // Load file + OCIO::LocalFileFormat tester; + OCIO::CachedFileRcPtr cachedFile = tester.Read(simple3D); + OCIO::CachedFileCSPRcPtr csplut = OCIO::DynamicPtrCast<OCIO::CachedFileCSP>(cachedFile); + + // check prelut data + OIIO_CHECK_ASSERT(csplut->hasprelut == false); + + // check cube data + for(unsigned int i = 0; i < csplut->lut3D->lut.size(); ++i) { + OIIO_CHECK_EQUAL(cube[i], csplut->lut3D->lut[i]); + } + + // check baker output + OCIO::ConfigRcPtr config = OCIO::Config::Create(); + { + OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); + cs->setName("lnf"); + cs->setFamily("lnf"); + config->addColorSpace(cs); + config->setRole(OCIO::ROLE_REFERENCE, cs->getName()); + } + { + OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); + cs->setName("shaper"); + cs->setFamily("shaper"); + OCIO::ExponentTransformRcPtr transform1 = OCIO::ExponentTransform::Create(); + float test[4] = {2.6f, 2.6f, 2.6f, 1.0f}; + transform1->setValue(test); + cs->setTransform(transform1, OCIO::COLORSPACE_DIR_TO_REFERENCE); + config->addColorSpace(cs); + } + { + OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); + cs->setName("target"); + cs->setFamily("target"); + OCIO::CDLTransformRcPtr transform1 = OCIO::CDLTransform::Create(); + float rgb[3] = {0.1f, 0.1f, 0.1f}; + transform1->setOffset(rgb); + cs->setTransform(transform1, OCIO::COLORSPACE_DIR_FROM_REFERENCE); + config->addColorSpace(cs); + } + + /* + std::string bout = + "CSPLUTV100\n" + "3D\n" + "\n" + "BEGIN METADATA\n" + "date: 2011:02:21 15:22:55\n" + "END METADATA\n" + "\n" + "10\n" + "0.000000 0.111111 0.222222 0.333333 0.444444 0.555556 0.666667 0.777778 0.888889 1.000000\n" + "0.000000 0.429520 0.560744 0.655378 0.732058 0.797661 0.855604 0.907865 0.955710 1.000000\n" + "10\n" + "0.000000 0.111111 0.222222 0.333333 0.444444 0.555556 0.666667 0.777778 0.888889 1.000000\n" + "0.000000 0.429520 0.560744 0.655378 0.732058 0.797661 0.855604 0.907865 0.955710 1.000000\n" + "10\n" + "0.000000 0.111111 0.222222 0.333333 0.444444 0.555556 0.666667 0.777778 0.888889 1.000000\n" + "0.000000 0.429520 0.560744 0.655378 0.732058 0.797661 0.855604 0.907865 0.955710 1.000000\n" + "\n" + "2 2 2\n" + "0.100000 0.100000 0.100000\n" + "1.000000 0.100000 0.100000\n" + "0.100000 1.000000 0.100000\n" + "1.000000 1.000000 0.100000\n" + "0.100000 0.100000 1.000000\n" + "1.000000 0.100000 1.000000\n" + "0.100000 1.000000 1.000000\n" + "1.000000 1.000000 1.000000\n" + "\n"; + + OCIO::BakerRcPtr baker = OCIO::Baker::Create(); + baker->setConfig(config); + baker->setFormat("cinespace"); + baker->setInputSpace("lnf"); + baker->setShaperSpace("shaper"); + baker->setTargetSpace("target"); + baker->setShaperSize(10); + baker->setCubeSize(2); + std::ostringstream output; + baker->bake(output); + + // + std::vector<std::string> osvec; + OCIO::pystring::splitlines(output.str(), osvec); + std::vector<std::string> resvec; + OCIO::pystring::splitlines(bout, resvec); + OIIO_CHECK_EQUAL(osvec.size(), resvec.size()); + for(unsigned int i = 0; i < resvec.size(); ++i) + { + // skip timestamp line + if(i != 4) OIIO_CHECK_EQUAL(osvec[i], resvec[i]); + } + */ + +} + +// TODO: More strenuous tests of prelut resampling (non-noop preluts) + +#endif // OCIO_UNIT_TEST |