diff options
Diffstat (limited to 'src/core')
90 files changed, 30443 insertions, 0 deletions
diff --git a/src/core/AllocationOp.cpp b/src/core/AllocationOp.cpp new file mode 100644 index 0000000..ed4c9db --- /dev/null +++ b/src/core/AllocationOp.cpp @@ -0,0 +1,128 @@ +/* +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 "AllocationOp.h" +#include "LogOps.h" +#include "MatrixOps.h" +#include "Op.h" + +#include <OpenColorIO/OpenColorIO.h> + +OCIO_NAMESPACE_ENTER +{ + + void CreateAllocationOps(OpRcPtrVec & ops, + const AllocationData & data, + TransformDirection dir) + { + if(data.allocation == ALLOCATION_UNIFORM) + { + float oldmin[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float oldmax[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + float newmin[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float newmax[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + + if(data.vars.size() >= 2) + { + for(int i=0; i<3; ++i) + { + oldmin[i] = data.vars[0]; + oldmax[i] = data.vars[1]; + } + } + + CreateFitOp(ops, + oldmin, oldmax, + newmin, newmax, + dir); + } + else if(data.allocation == ALLOCATION_LG2) + { + float oldmin[4] = { -10.0f, -10.0f, -10.0f, 0.0f }; + float oldmax[4] = { 6.0f, 6.0f, 6.0f, 1.0f }; + float newmin[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float newmax[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + + if(data.vars.size() >= 2) + { + for(int i=0; i<3; ++i) + { + oldmin[i] = data.vars[0]; + oldmax[i] = data.vars[1]; + } + } + + + // Log Settings + // output = k * log(mx+b, base) + kb + + float k[3] = { 1.0f, 1.0f, 1.0f }; + float m[3] = { 1.0f, 1.0f, 1.0f }; + float b[3] = { 0.0f, 0.0f, 0.0f }; + float base[3] = { 2.0f, 2.0f, 2.0f }; + float kb[3] = { 0.0f, 0.0f, 0.0f }; + + if(data.vars.size() >= 3) + { + for(int i=0; i<3; ++i) + { + b[i] = data.vars[2]; + } + } + + if(dir == TRANSFORM_DIR_FORWARD) + { + CreateLogOp(ops, k, m, b, base, kb, dir); + + CreateFitOp(ops, + oldmin, oldmax, + newmin, newmax, + dir); + } + else if(dir == TRANSFORM_DIR_INVERSE) + { + CreateFitOp(ops, + oldmin, oldmax, + newmin, newmax, + dir); + + CreateLogOp(ops, k, m, b, base, kb, dir); + } + else + { + throw Exception("Cannot BuildAllocationOps, unspecified transform direction."); + } + } + else + { + throw Exception("Unsupported Allocation Type."); + } + } + +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/AllocationOp.h b/src/core/AllocationOp.h new file mode 100644 index 0000000..8fd491f --- /dev/null +++ b/src/core/AllocationOp.h @@ -0,0 +1,45 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_ALLOCATIONOP_H +#define INCLUDED_OCIO_ALLOCATIONOP_H + +#include <OpenColorIO/OpenColorIO.h> + +#include "Op.h" + +OCIO_NAMESPACE_ENTER +{ + void CreateAllocationOps(OpRcPtrVec & ops, + const AllocationData & data, + TransformDirection dir); +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/AllocationTransform.cpp b/src/core/AllocationTransform.cpp new file mode 100644 index 0000000..592b36e --- /dev/null +++ b/src/core/AllocationTransform.cpp @@ -0,0 +1,182 @@ +/* +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 <cstring> +#include <sstream> +#include <vector> + +#include <OpenColorIO/OpenColorIO.h> + +#include "AllocationOp.h" +#include "OpBuilders.h" + +OCIO_NAMESPACE_ENTER +{ + AllocationTransformRcPtr AllocationTransform::Create() + { + return AllocationTransformRcPtr(new AllocationTransform(), &deleter); + } + + void AllocationTransform::deleter(AllocationTransform* t) + { + delete t; + } + + + class AllocationTransform::Impl + { + public: + TransformDirection dir_; + Allocation allocation_; + std::vector<float> vars_; + + Impl() : + dir_(TRANSFORM_DIR_FORWARD), + allocation_(ALLOCATION_UNIFORM) + { } + + ~Impl() + { } + + Impl& operator= (const Impl & rhs) + { + dir_ = rhs.dir_; + allocation_ = rhs.allocation_; + vars_ = rhs.vars_; + return *this; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + + AllocationTransform::AllocationTransform() + : m_impl(new AllocationTransform::Impl) + { + } + + TransformRcPtr AllocationTransform::createEditableCopy() const + { + AllocationTransformRcPtr transform = AllocationTransform::Create(); + *(transform->m_impl) = *m_impl; + return transform; + } + + AllocationTransform::~AllocationTransform() + { + delete m_impl; + m_impl = NULL; + } + + AllocationTransform& AllocationTransform::operator= (const AllocationTransform & rhs) + { + *m_impl = *rhs.m_impl; + return *this; + } + + TransformDirection AllocationTransform::getDirection() const + { + return getImpl()->dir_; + } + + void AllocationTransform::setDirection(TransformDirection dir) + { + getImpl()->dir_ = dir; + } + + + Allocation AllocationTransform::getAllocation() const + { + return getImpl()->allocation_; + } + + void AllocationTransform::setAllocation(Allocation allocation) + { + getImpl()->allocation_ = allocation; + } + + int AllocationTransform::getNumVars() const + { + return static_cast<int>(getImpl()->vars_.size()); + } + + void AllocationTransform::getVars(float * vars) const + { + if(!getImpl()->vars_.empty()) + { + memcpy(vars, + &getImpl()->vars_[0], + getImpl()->vars_.size()*sizeof(float)); + } + } + + void AllocationTransform::setVars(int numvars, const float * vars) + { + getImpl()->vars_.resize(numvars); + + if(!getImpl()->vars_.empty()) + { + memcpy(&getImpl()->vars_[0], + vars, + numvars*sizeof(float)); + } + } + + std::ostream& operator<< (std::ostream& os, const AllocationTransform& t) + { + os << "<AllocationTransform "; + os << "direction=" << TransformDirectionToString(t.getDirection()) << ", "; + os << ">\n"; + + return os; + } + + + /////////////////////////////////////////////////////////////////////////// + + + void BuildAllocationOps(OpRcPtrVec & ops, + const Config& /*config*/, + const AllocationTransform& allocationTransform, + TransformDirection dir) + { + TransformDirection combinedDir = CombineTransformDirections(dir, + allocationTransform.getDirection()); + + AllocationData data; + data.allocation = allocationTransform.getAllocation(); + data.vars.resize(allocationTransform.getNumVars()); + if(!data.vars.empty()) + { + allocationTransform.getVars(&data.vars[0]); + } + + CreateAllocationOps(ops, data, combinedDir); + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/Baker.cpp b/src/core/Baker.cpp new file mode 100644 index 0000000..581df5f --- /dev/null +++ b/src/core/Baker.cpp @@ -0,0 +1,297 @@ +/* +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 <vector> +#include <iostream> + +#include <OpenColorIO/OpenColorIO.h> + +#include "FileTransform.h" +#include "MathUtils.h" +#include "pystring/pystring.h" + +OCIO_NAMESPACE_ENTER +{ + + BakerRcPtr Baker::Create() + { + return BakerRcPtr(new Baker(), &deleter); + } + + void Baker::deleter(Baker* c) + { + delete c; + } + + class Baker::Impl + { + public: + + ConfigRcPtr config_; + std::string formatName_; + std::string type_; + std::string metadata_; + std::string inputSpace_; + std::string shaperSpace_; + std::string looks_; + std::string targetSpace_; + int shapersize_; + int cubesize_; + + Impl() : + shapersize_(-1), + cubesize_(-1) + { + } + + ~Impl() + { + } + + Impl& operator= (const Impl & rhs) + { + config_ = rhs.config_; + formatName_ = rhs.formatName_; + inputSpace_ = rhs.inputSpace_; + shaperSpace_ = rhs.shaperSpace_; + looks_ = rhs.looks_; + targetSpace_ = rhs.targetSpace_; + shapersize_ = rhs.shapersize_; + cubesize_ = rhs.cubesize_; + return *this; + } + }; + + Baker::Baker() + : m_impl(new Baker::Impl) + { + } + + Baker::~Baker() + { + delete m_impl; + m_impl = NULL; + } + + BakerRcPtr Baker::createEditableCopy() const + { + BakerRcPtr oven = Baker::Create(); + *oven->m_impl = *m_impl; + return oven; + } + + void Baker::setConfig(const ConstConfigRcPtr & config) + { + getImpl()->config_ = config->createEditableCopy(); + } + + ConstConfigRcPtr Baker::getConfig() const + { + return getImpl()->config_; + } + + int Baker::getNumFormats() + { + return FormatRegistry::GetInstance().getNumFormats(FORMAT_CAPABILITY_WRITE); + } + + const char * Baker::getFormatNameByIndex(int index) + { + return FormatRegistry::GetInstance().getFormatNameByIndex(FORMAT_CAPABILITY_WRITE, index); + } + + const char * Baker::getFormatExtensionByIndex(int index) + { + return FormatRegistry::GetInstance().getFormatExtensionByIndex(FORMAT_CAPABILITY_WRITE, index); + } + + void Baker::setFormat(const char * formatName) + { + getImpl()->formatName_ = formatName; + } + + const char * Baker::getFormat() const + { + return getImpl()->formatName_.c_str(); + } + + void Baker::setType(const char * type) + { + getImpl()->type_ = type; + } + + const char * Baker::getType() const + { + return getImpl()->type_.c_str(); + } + + void Baker::setMetadata(const char * metadata) + { + getImpl()->metadata_ = metadata; + } + + const char * Baker::getMetadata() const + { + return getImpl()->metadata_.c_str(); + } + + void Baker::setInputSpace(const char * inputSpace) + { + getImpl()->inputSpace_ = inputSpace; + } + + const char * Baker::getInputSpace() const + { + return getImpl()->inputSpace_.c_str(); + } + + void Baker::setShaperSpace(const char * shaperSpace) + { + getImpl()->shaperSpace_ = shaperSpace; + } + + const char * Baker::getShaperSpace() const + { + return getImpl()->shaperSpace_.c_str(); + } + + void Baker::setLooks(const char * looks) + { + getImpl()->looks_ = looks; + } + + const char * Baker::getLooks() const + { + return getImpl()->looks_.c_str(); + } + + void Baker::setTargetSpace(const char * targetSpace) + { + getImpl()->targetSpace_ = targetSpace; + } + + const char * Baker::getTargetSpace() const + { + return getImpl()->targetSpace_.c_str(); + } + + void Baker::setShaperSize(int shapersize) + { + getImpl()->shapersize_ = shapersize; + } + + int Baker::getShaperSize() const + { + return getImpl()->shapersize_; + } + + void Baker::setCubeSize(int cubesize) + { + getImpl()->cubesize_ = cubesize; + } + + int Baker::getCubeSize() const + { + return getImpl()->cubesize_; + } + + void Baker::bake(std::ostream & os) const + { + FileFormat* fmt = FormatRegistry::GetInstance().getFileFormatByName(getImpl()->formatName_); + + if(!fmt) + { + std::ostringstream err; + err << "The format named '" << getImpl()->formatName_; + err << "' could not be found. "; + throw Exception(err.str().c_str()); + } + + try + { + fmt->Write(*this, getImpl()->formatName_, os); + } + catch(std::exception & e) + { + std::ostringstream err; + err << "Error baking " << getImpl()->formatName_ << ":"; + err << e.what(); + throw Exception(err.str().c_str()); + } + + // + // TODO: + // + // - throw exception when we don't have inputSpace and targetSpace + // at least set + // - check limits of shaper and target, throw exception if we can't + // write that much data in x format + // - check that the shaper is 1D transform only, throw excpetion + // - check the file format supports shapers, 1D and 3D + // - add some checks to make sure we are monotonic + // - deal with the case of writing out non cube formats (1D only) + // - do a compare between ocio transform and output lut transform + // throw error if we going beyond tolerance + // + } + +} +OCIO_NAMESPACE_EXIT + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef OCIO_UNIT_TEST + +namespace OCIO = OCIO_NAMESPACE; +#include "UnitTest.h" + +/* +OIIO_ADD_TEST(Baker_Unit_Tests, test_listlutwriters) +{ + + std::vector<std::string> current_writers; + current_writers.push_back("cinespace"); + current_writers.push_back("houdini"); + + OCIO::BakerRcPtr baker = OCIO::Baker::Create(); + + OIIO_CHECK_EQUAL(baker->getNumFormats(), (int)current_writers.size()); + + std::vector<std::string> test; + for(int i = 0; i < baker->getNumFormats(); ++i) + test.push_back(baker->getFormatNameByIndex(i)); + + for(unsigned int i = 0; i < current_writers.size(); ++i) + OIIO_CHECK_EQUAL(current_writers[i], test[i]); + +} +*/ + +#endif // OCIO_BUILD_TESTS + + diff --git a/src/core/CDLTransform.cpp b/src/core/CDLTransform.cpp new file mode 100644 index 0000000..0e321ff --- /dev/null +++ b/src/core/CDLTransform.cpp @@ -0,0 +1,776 @@ +/* +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 <fstream> +#include <tinyxml.h> + +#include <OpenColorIO/OpenColorIO.h> + +#include "CDLTransform.h" +#include "ExponentOps.h" +#include "MatrixOps.h" +#include "MathUtils.h" +#include "Mutex.h" +#include "OpBuilders.h" +#include "ParseUtils.h" +#include "pystring/pystring.h" + +OCIO_NAMESPACE_ENTER +{ + namespace + { + /* + "<ColorCorrection id=''>" + " <SOPNode>" + " <Description/> " + " <Slope>1 1 1</Slope> " + " <Offset>0 0 0</Offset> " + " <Power>1 1 1</Power> " + " </SOPNode> " + " <SatNode>" + " <Saturation> 1 </Saturation> " + " </SatNode> " + " </ColorCorrection>"; + + */ + + // http://ticpp.googlecode.com/svn/docs/ticpp_8h-source.html#l01670 + + void SetTiXmlText( TiXmlElement* element, const char * value) + { + if ( element->NoChildren() ) + { + element->LinkEndChild( new TiXmlText( value ) ); + } + else + { + if ( 0 == element->GetText() ) + { + element->InsertBeforeChild( element->FirstChild(), TiXmlText( value ) ); + } + else + { + // There already is text, so change it + element->FirstChild()->SetValue( value ); + } + } + } + + std::string BuildXML(const CDLTransform & cdl) + { + TiXmlDocument doc; + + // TiXmlDeclaration * decl = new TiXmlDeclaration( "1.0", "", "" ); + TiXmlElement * root = new TiXmlElement( "ColorCorrection" ); + doc.LinkEndChild( root ); + root->SetAttribute("id", cdl.getID()); + + TiXmlElement * sop = new TiXmlElement( "SOPNode" ); + root->LinkEndChild( sop ); + + TiXmlElement * desc = new TiXmlElement( "Description" ); + sop->LinkEndChild( desc ); + SetTiXmlText(desc, cdl.getDescription()); + + TiXmlElement * slope = new TiXmlElement( "Slope" ); + sop->LinkEndChild( slope ); + float slopeval[3]; + cdl.getSlope(slopeval); + SetTiXmlText(slope, FloatVecToString(slopeval, 3).c_str()); + + TiXmlElement * offset = new TiXmlElement( "Offset" ); + sop->LinkEndChild( offset ); + float offsetval[3]; + cdl.getOffset(offsetval); + SetTiXmlText(offset, FloatVecToString(offsetval, 3).c_str()); + + TiXmlElement * power = new TiXmlElement( "Power" ); + sop->LinkEndChild( power ); + float powerval[3]; + cdl.getPower(powerval); + SetTiXmlText(power, FloatVecToString(powerval, 3).c_str()); + + TiXmlElement * sat = new TiXmlElement( "SatNode" ); + root->LinkEndChild( sat ); + + TiXmlElement * saturation = new TiXmlElement( "Saturation" ); + sat->LinkEndChild( saturation ); + SetTiXmlText(saturation, FloatToString(cdl.getSat()).c_str()); + + TiXmlPrinter printer; + printer.SetStreamPrinting(); + doc.Accept( &printer ); + return printer.Str(); + } + } + + void LoadCDL(CDLTransform * cdl, TiXmlElement * root) + { + if(!cdl) return; + + if(!root) + { + std::ostringstream os; + os << "Error loading CDL xml. "; + os << "Null root element."; + throw Exception(os.str().c_str()); + } + + if(std::string(root->Value()) != "ColorCorrection") + { + std::ostringstream os; + os << "Error loading CDL xml. "; + os << "Root element is type '" << root->Value() << "', "; + os << "ColorCorrection expected."; + throw Exception(os.str().c_str()); + } + + TiXmlHandle handle( root ); + + const char * id = root->Attribute("id"); + if(!id) id = ""; + + cdl->setID(id); + + TiXmlElement* desc = handle.FirstChild( "SOPNode" ).FirstChild("Description").ToElement(); + if(desc) + { + const char * text = desc->GetText(); + if(text) cdl->setDescription(text); + } + + std::vector<std::string> lineParts; + std::vector<float> floatArray; + + TiXmlElement* slope = handle.FirstChild( "SOPNode" ).FirstChild("Slope").ToElement(); + if(slope) + { + const char * text = slope->GetText(); + if(text) + { + pystring::split(pystring::strip(text), lineParts); + if((lineParts.size() != 3) || (!StringVecToFloatVec(floatArray, lineParts))) + { + std::ostringstream os; + os << "Error loading CDL xml. "; + os << id << ".SOPNode.Slope text '"; + os << text << "' is not convertible to 3 floats."; + throw Exception(os.str().c_str()); + } + cdl->setSlope(&floatArray[0]); + } + } + + TiXmlElement* offset = handle.FirstChild( "SOPNode" ).FirstChild("Offset").ToElement(); + if(offset) + { + const char * text = offset->GetText(); + if(text) + { + pystring::split(pystring::strip(text), lineParts); + if((lineParts.size() != 3) || (!StringVecToFloatVec(floatArray, lineParts))) + { + std::ostringstream os; + os << "Error loading CDL xml. "; + os << id << ".SOPNode.Offset text '"; + os << text << "' is not convertible to 3 floats."; + throw Exception(os.str().c_str()); + } + cdl->setOffset(&floatArray[0]); + } + } + + TiXmlElement* power = handle.FirstChild( "SOPNode" ).FirstChild("Power").ToElement(); + if(power) + { + const char * text = power->GetText(); + if(text) + { + pystring::split(pystring::strip(text), lineParts); + if((lineParts.size() != 3) || (!StringVecToFloatVec(floatArray, lineParts))) + { + std::ostringstream os; + os << "Error loading CDL xml. "; + os << id << ".SOPNode.Power text '"; + os << text << "' is not convertible to 3 floats."; + throw Exception(os.str().c_str()); + } + cdl->setPower(&floatArray[0]); + } + } + + TiXmlElement* sat = handle.FirstChild( "SatNode" ).FirstChild("Saturation").ToElement(); + if(sat) + { + const char * text = sat->GetText(); + if(text) + { + float satval = 1.0f; + if(!StringToFloat(&satval, text)) + { + std::ostringstream os; + os << "Error loading CDL xml. "; + os << id << ".SatNode.Saturation text '"; + os << text << "' is not convertible to float."; + throw Exception(os.str().c_str()); + } + cdl->setSat(satval); + } + } + } + + + + void GetCDLTransforms(CDLTransformMap & transforms, + TiXmlElement * cccRootElement) + { + if(std::string(cccRootElement->Value()) != "ColorCorrectionCollection") + { + std::ostringstream os; + os << "GetCDLTransforms Error. "; + os << "Root element is type '" << cccRootElement->Value() << "', "; + os << "ColorCorrectionCollection expected."; + throw Exception(os.str().c_str()); + } + + TiXmlNode * child = cccRootElement->FirstChild("ColorCorrection"); + while(child) + { + CDLTransformRcPtr transform = CDLTransform::Create(); + LoadCDL(transform.get(), child->ToElement()); + + std::string id = transform->getID(); + if(id.empty()) + { + std::ostringstream os; + os << "Error loading ccc xml, "; + os << "All ASC ColorCorrections must specify an 'id' value."; + throw Exception(os.str().c_str()); + } + + CDLTransformMap::iterator iter = transforms.find(id); + if(iter != transforms.end()) + { + std::ostringstream os; + os << "Error loading ccc xml. "; + os << "All ASC ColorCorrections must specify a unique 'id' value. "; + os << "Duplicate elements with '" << id << "' found."; + throw Exception(os.str().c_str()); + } + + transforms[id] = transform; + + child = child->NextSibling("ColorCorrection"); + } + } + + void LoadCDL(CDLTransform * cdl, const char * xml) + { + if(!xml || (strlen(xml) == 0)) + { + std::ostringstream os; + os << "Error loading CDL xml. "; + os << "Null string provided."; + throw Exception(os.str().c_str()); + } + + TiXmlDocument doc; + doc.Parse(xml); + + if(doc.Error()) + { + std::ostringstream os; + os << "Error loading CDL xml. "; + os << doc.ErrorDesc() << " (line "; + os << doc.ErrorRow() << ", character "; + os << doc.ErrorCol() << ")"; + throw Exception(os.str().c_str()); + } + + if(!doc.RootElement()) + { + std::ostringstream os; + os << "Error loading CDL xml, "; + os << "please confirm the xml is valid."; + throw Exception(os.str().c_str()); + } + + LoadCDL(cdl, doc.RootElement()->ToElement()); + } + + CDLTransformRcPtr CDLTransform::Create() + { + return CDLTransformRcPtr(new CDLTransform(), &deleter); + } + + namespace + { + std::string GetCDLLocalCacheKey(const std::string & src, + const std::string & cccid) + { + return src + " : " + cccid; + } + + CDLTransformMap g_cache; + Mutex g_cacheMutex; + } + + void ClearCDLTransformFileCache() + { + AutoMutex lock(g_cacheMutex); + g_cache.clear(); + } + + // TODO: Expose functions for introspecting in ccc file + // TODO: Share caching with normal cdl pathway + + CDLTransformRcPtr CDLTransform::CreateFromFile(const char * src, const char * cccid) + { + if(!src || (strlen(src) == 0) || !cccid) + { + std::ostringstream os; + os << "Error loading CDL xml. "; + os << "Source file not specified."; + throw Exception(os.str().c_str()); + } + + // Check cache + AutoMutex lock(g_cacheMutex); + { + CDLTransformMap::iterator iter = + g_cache.find(GetCDLLocalCacheKey(src,cccid)); + if(iter != g_cache.end()) + { + return iter->second; + } + } + + std::ifstream istream(src); + if(istream.fail()) { + std::ostringstream os; + os << "Error could not read CDL source file '" << src; + os << "'. Please verify the file exists and appropriate "; + os << "permissions are set."; + throw Exception (os.str().c_str()); + } + + // Read the file into a string. + std::ostringstream rawdata; + rawdata << istream.rdbuf(); + std::string xml = rawdata.str(); + + if(xml.empty()) + { + std::ostringstream os; + os << "Error loading CDL xml. "; + os << "The specified source file, '"; + os << src << "' appears to be empty."; + throw Exception(os.str().c_str()); + } + + TiXmlDocument doc; + doc.Parse(xml.c_str()); + + if(doc.Error()) + { + std::ostringstream os; + os << "Error loading CDL xml from file '"; + os << src << "'. "; + os << doc.ErrorDesc() << " (line "; + os << doc.ErrorRow() << ", character "; + os << doc.ErrorCol() << ")"; + throw Exception(os.str().c_str()); + } + + if(!doc.RootElement()) + { + std::ostringstream os; + os << "Error loading CDL xml from file '"; + os << src << "'. "; + os << "Please confirm the xml is valid."; + throw Exception(os.str().c_str()); + } + + std::string rootValue = doc.RootElement()->Value(); + if(rootValue == "ColorCorrection") + { + // Load a single ColorCorrection into the cache + CDLTransformRcPtr cdl = CDLTransform::Create(); + LoadCDL(cdl.get(), doc.RootElement()->ToElement()); + g_cache[GetCDLLocalCacheKey(src,cccid)] = cdl; + return cdl; + } + else if(rootValue == "ColorCorrectionCollection") + { + // Load all CCs from the ColorCorrectionCollection + // into the cache + + CDLTransformMap transforms; + GetCDLTransforms(transforms, doc.RootElement()); + + if(transforms.empty()) + { + std::ostringstream os; + os << "Error loading ccc xml. "; + os << "No ColorCorrection elements found in file '"; + os << src << "'."; + throw Exception(os.str().c_str()); + } + + for(CDLTransformMap::iterator iter = transforms.begin(); + iter != transforms.end(); + ++iter) + { + g_cache[GetCDLLocalCacheKey(src,iter->first)] = iter->second; + } + + CDLTransformMap::iterator cciter = g_cache.find(GetCDLLocalCacheKey(src,cccid)); + if(cciter == g_cache.end()) + { + std::ostringstream os; + os << "Error loading ccc xml. "; + os << "The specified cccid " << cccid << " "; + os << "could not be found in file '"; + os << src << "'."; + throw Exception(os.str().c_str()); + } + + return cciter->second; + } + + std::ostringstream os; + os << "Error loading CDL xml from file '"; + os << src << "'. "; + os << "Root xml element is type '" << rootValue << "', "; + os << "ColorCorrection or ColorCorrectionCollection expected."; + throw Exception(os.str().c_str()); + } + + void CDLTransform::deleter(CDLTransform* t) + { + delete t; + } + + class CDLTransform::Impl + { + public: + TransformDirection dir_; + + float sop_[9]; + float sat_; + std::string id_; + std::string description_; + + mutable std::string xml_; + + Impl() : + dir_(TRANSFORM_DIR_FORWARD), + sat_(1.0f) + { + sop_[0] = 1.0f; + sop_[1] = 1.0f; + sop_[2] = 1.0f; + sop_[3] = 0.0f; + sop_[4] = 0.0f; + sop_[5] = 0.0f; + sop_[6] = 1.0f; + sop_[7] = 1.0f; + sop_[8] = 1.0f; + } + + ~Impl() + { + + } + + Impl& operator= (const Impl & rhs) + { + dir_ = rhs.dir_; + + memcpy(sop_, rhs.sop_, sizeof(float)*9); + sat_ = rhs.sat_; + id_ = rhs.id_; + description_ = rhs.description_; + + return *this; + } + + }; + + + /////////////////////////////////////////////////////////////////////////// + + + + CDLTransform::CDLTransform() + : m_impl(new CDLTransform::Impl) + { + } + + TransformRcPtr CDLTransform::createEditableCopy() const + { + CDLTransformRcPtr transform = CDLTransform::Create(); + *(transform->m_impl) = *m_impl; + return transform; + } + + CDLTransform::~CDLTransform() + { + delete m_impl; + m_impl = NULL; + } + + CDLTransform& CDLTransform::operator= (const CDLTransform & rhs) + { + *m_impl = *rhs.m_impl; + return *this; + } + + TransformDirection CDLTransform::getDirection() const + { + return getImpl()->dir_; + } + + void CDLTransform::setDirection(TransformDirection dir) + { + getImpl()->dir_ = dir; + } + + const char * CDLTransform::getXML() const + { + getImpl()->xml_ = BuildXML(*this); + return getImpl()->xml_.c_str(); + } + + void CDLTransform::setXML(const char * xml) + { + LoadCDL(this, xml); + } + + // We use this approach, rather than comparing XML to get around the + // case where setXML with extra data was provided. + + bool CDLTransform::equals(const ConstCDLTransformRcPtr & other) const + { + if(!other) return false; + + if(getImpl()->dir_ != other->getImpl()->dir_) return false; + + const float abserror = 1e-9f; + + for(int i=0; i<9; ++i) + { + if(!equalWithAbsError(getImpl()->sop_[i], other->getImpl()->sop_[i], abserror)) + { + return false; + } + } + + if(!equalWithAbsError(getImpl()->sat_, other->getImpl()->sat_, abserror)) + { + return false; + } + + if(getImpl()->id_ != other->getImpl()->id_) + { + return false; + } + + if(getImpl()->description_ != other->getImpl()->description_) + { + return false; + } + + return true; + } + + void CDLTransform::setSlope(const float * rgb) + { + memcpy(&getImpl()->sop_[0], rgb, sizeof(float)*3); + } + + void CDLTransform::getSlope(float * rgb) const + { + memcpy(rgb, &getImpl()->sop_[0], sizeof(float)*3); + } + + void CDLTransform::setOffset(const float * rgb) + { + memcpy(&getImpl()->sop_[3], rgb, sizeof(float)*3); + } + + void CDLTransform::getOffset(float * rgb) const + { + memcpy(rgb, &getImpl()->sop_[3], sizeof(float)*3); + } + + void CDLTransform::setPower(const float * rgb) + { + memcpy(&getImpl()->sop_[6], rgb, sizeof(float)*3); + } + + void CDLTransform::getPower(float * rgb) const + { + memcpy(rgb, &getImpl()->sop_[6], sizeof(float)*3); + } + + void CDLTransform::setSOP(const float * vec9) + { + memcpy(&getImpl()->sop_, vec9, sizeof(float)*9); + } + + void CDLTransform::getSOP(float * vec9) const + { + memcpy(vec9, &getImpl()->sop_, sizeof(float)*9); + } + + void CDLTransform::setSat(float sat) + { + getImpl()->sat_ = sat; + } + + float CDLTransform::getSat() const + { + return getImpl()->sat_; + } + + void CDLTransform::getSatLumaCoefs(float * rgb) const + { + if(!rgb) return; + rgb[0] = 0.2126f; + rgb[1] = 0.7152f; + rgb[2] = 0.0722f; + } + + void CDLTransform::setID(const char * id) + { + if(id) + { + getImpl()->id_ = id; + } + else + { + getImpl()->id_ = ""; + } + } + + const char * CDLTransform::getID() const + { + return getImpl()->id_.c_str(); + } + + void CDLTransform::setDescription(const char * desc) + { + if(desc) + { + getImpl()->description_ = desc; + } + else + { + getImpl()->description_ = ""; + } + } + + const char * CDLTransform::getDescription() const + { + return getImpl()->description_.c_str(); + } + + std::ostream& operator<< (std::ostream& os, const CDLTransform& t) + { + float sop[9]; + t.getSOP(sop); + + os << "<CDLTransform "; + os << "direction=" << TransformDirectionToString(t.getDirection()) << ", "; + os << "sop="; + for (unsigned int i=0; i<9; ++i) + { + if(i!=0) os << " "; + os << sop[i]; + } + os << ", "; + os << "sat=" << t.getSat() << ","; + os << TransformDirectionToString(t.getDirection()) << ", "; + os << ">\n"; + return os; + } + + + /////////////////////////////////////////////////////////////////////////// + + void BuildCDLOps(OpRcPtrVec & ops, + const Config & /*config*/, + const CDLTransform & cdlTransform, + TransformDirection dir) + { + float scale4[] = { 1.0f, 1.0f, 1.0f, 1.0f }; + cdlTransform.getSlope(scale4); + + float offset4[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + cdlTransform.getOffset(offset4); + + float power4[] = { 1.0f, 1.0f, 1.0f, 1.0f }; + cdlTransform.getPower(power4); + + float lumaCoef3[] = { 1.0f, 1.0f, 1.0f }; + cdlTransform.getSatLumaCoefs(lumaCoef3); + + float sat = cdlTransform.getSat(); + + TransformDirection combinedDir = CombineTransformDirections(dir, + cdlTransform.getDirection()); + + // TODO: Confirm ASC Sat math is correct. + // TODO: Handle Clamping conditions more explicitly + + if(combinedDir == TRANSFORM_DIR_FORWARD) + { + // 1) Scale + Offset + CreateScaleOffsetOp(ops, scale4, offset4, TRANSFORM_DIR_FORWARD); + + // 2) Power + Clamp + CreateExponentOp(ops, power4, TRANSFORM_DIR_FORWARD); + + // 3) Saturation + Clamp + CreateSaturationOp(ops, sat, lumaCoef3, TRANSFORM_DIR_FORWARD); + } + else if(combinedDir == TRANSFORM_DIR_INVERSE) + { + // 3) Saturation + Clamp + CreateSaturationOp(ops, sat, lumaCoef3, TRANSFORM_DIR_INVERSE); + + // 2) Power + Clamp + CreateExponentOp(ops, power4, TRANSFORM_DIR_INVERSE); + + // 1) Scale + Offset + CreateScaleOffsetOp(ops, scale4, offset4, TRANSFORM_DIR_INVERSE); + } + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/CDLTransform.h b/src/core/CDLTransform.h new file mode 100644 index 0000000..8b945ad --- /dev/null +++ b/src/core/CDLTransform.h @@ -0,0 +1,53 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_CDLTRANSFORM_H +#define INCLUDED_OCIO_CDLTRANSFORM_H + +#include <map> +#include <tinyxml.h> + +#include <OpenColorIO/OpenColorIO.h> + +OCIO_NAMESPACE_ENTER +{ + typedef std::map<std::string,CDLTransformRcPtr> CDLTransformMap; + + void ClearCDLTransformFileCache(); + + void LoadCDL(CDLTransform * cdl, const char * xml); + void LoadCDL(CDLTransform * cdl, TiXmlElement * root); + + // Get a map of transform cccid : cdl transform + void GetCDLTransforms(CDLTransformMap & transforms, + TiXmlElement * cccroot); +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt new file mode 100644 index 0000000..50194f3 --- /dev/null +++ b/src/core/CMakeLists.txt @@ -0,0 +1,93 @@ +############################################################################### +### OCIO CORE ### + +include_directories( + ${CMAKE_SOURCE_DIR}/export/ + ${CMAKE_BINARY_DIR}/export/ + ${EXTERNAL_INCLUDE_DIRS} + ${CMAKE_SOURCE_DIR}/ext/oiio/src/include +) + +file(GLOB_RECURSE core_src_files "${CMAKE_SOURCE_DIR}/src/core/*.cpp") +file(GLOB_RECURSE core_export_headers "${CMAKE_SOURCE_DIR}/export/OpenColorIO/*.h") + +message(STATUS "Create OpenColorABI.h from OpenColorABI.h.in") +configure_file(${CMAKE_SOURCE_DIR}/export/OpenColorIO/OpenColorABI.h.in + ${CMAKE_BINARY_DIR}/export/OpenColorABI.h @ONLY) +list(APPEND core_export_headers ${CMAKE_BINARY_DIR}/export/OpenColorABI.h) + +# SHARED + +if(OCIO_BUILD_SHARED) + add_library(OpenColorIO SHARED ${core_src_files}) + + if(USE_EXTERNAL_TINYXML) + target_link_libraries(OpenColorIO ${TINYXML_LIBRARIES}) + else(USE_EXTERNAL_TINYXML) + add_dependencies(OpenColorIO tinyxml) + endif(USE_EXTERNAL_TINYXML) + + if(USE_EXTERNAL_YAML) + target_link_libraries(OpenColorIO ${YAML_CPP_LIBRARIES}) + else(USE_EXTERNAL_YAML) + add_dependencies(OpenColorIO YAML_CPP_LOCAL) + endif() + + if(WIN32) + target_link_libraries(OpenColorIO + debug ${EXTERNAL_DEBUG_LIBRARIES} + optimized ${EXTERNAL_OPTIMIZED_LIBRARIES} + general ${EXTERNAL_GENERAL_LIBRARIES}) + else() + target_link_libraries(OpenColorIO ${EXTERNAL_GENERAL_LIBRARIES}) + endif() + set_target_properties(OpenColorIO PROPERTIES + OUTPUT_NAME OpenColorIO + COMPILE_FLAGS "${EXTERNAL_COMPILE_FLAGS}" + LINK_FLAGS "${EXTERNAL_LINK_FLAGS}") + + message(STATUS "Setting OCIO SOVERSION to: ${SOVERSION}") + set_target_properties(OpenColorIO PROPERTIES + VERSION ${OCIO_VERSION} + SOVERSION ${SOVERSION}) + + install(TARGETS OpenColorIO DESTINATION ${CMAKE_INSTALL_EXEC_PREFIX}/lib${LIB_SUFFIX}) +endif() + +# STATIC + +if(OCIO_BUILD_STATIC) + list(REMOVE_ITEM core_src_files ${CMAKE_SOURCE_DIR}/src/core/UnitTest.cpp) + add_library(OpenColorIO_STATIC STATIC ${core_src_files}) + add_dependencies(OpenColorIO_STATIC tinyxml YAML_CPP_LOCAL) + if(WIN32) + target_link_libraries(OpenColorIO_STATIC + debug ${EXTERNAL_DEBUG_LIBRARIES} + optimized ${EXTERNAL_OPTIMIZED_LIBRARIES} + general ${EXTERNAL_GENERAL_LIBRARIES}) + else() + target_link_libraries(OpenColorIO_STATIC ${EXTERNAL_GENERAL_LIBRARIES}) + endif() + set_target_properties(OpenColorIO_STATIC PROPERTIES + OUTPUT_NAME OpenColorIO + COMPILE_FLAGS "${EXTERNAL_COMPILE_FLAGS}" + LINK_FLAGS "${EXTERNAL_LINK_FLAGS}") + + message(STATUS "Setting OCIO SOVERSION to: ${SOVERSION}") + set_target_properties(OpenColorIO_STATIC PROPERTIES + VERSION ${OCIO_VERSION} + SOVERSION ${SOVERSION}) + + install(TARGETS OpenColorIO_STATIC DESTINATION ${CMAKE_INSTALL_EXEC_PREFIX}/lib) +endif() + +# public interface +install(FILES ${core_export_headers} + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/OpenColorIO/) + +# pkg-config +message(STATUS "Create OpenColorIO.pc from OpenColorIO.pc.in") +configure_file(${CMAKE_SOURCE_DIR}/export/pkgconfig/OpenColorIO.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/OpenColorIO.pc @ONLY) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpenColorIO.pc + DESTINATION ${CMAKE_INSTALL_EXEC_PREFIX}/lib${LIB_SUFFIX}/pkgconfig/) diff --git a/src/core/Caching.cpp b/src/core/Caching.cpp new file mode 100644 index 0000000..afb0e7c --- /dev/null +++ b/src/core/Caching.cpp @@ -0,0 +1,47 @@ +/* +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 "CDLTransform.h" +#include "PathUtils.h" +#include "FileTransform.h" + +OCIO_NAMESPACE_ENTER +{ + // TODO: Processors which the user hangs onto have local caches. + // Should these be cleared? + + void ClearAllCaches() + { + ClearPathCaches(); + ClearFileTransformCaches(); + ClearCDLTransformFileCache(); + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/ColorSpace.cpp b/src/core/ColorSpace.cpp new file mode 100644 index 0000000..ed2a473 --- /dev/null +++ b/src/core/ColorSpace.cpp @@ -0,0 +1,270 @@ +/* +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 <cstring> +#include <sstream> +#include <vector> + +#include <OpenColorIO/OpenColorIO.h> + +OCIO_NAMESPACE_ENTER +{ + ColorSpaceRcPtr ColorSpace::Create() + { + return ColorSpaceRcPtr(new ColorSpace(), &deleter); + } + + void ColorSpace::deleter(ColorSpace* c) + { + delete c; + } + + + class ColorSpace::Impl + { + public: + std::string name_; + std::string family_; + std::string equalityGroup_; + std::string description_; + + BitDepth bitDepth_; + bool isData_; + + Allocation allocation_; + std::vector<float> allocationVars_; + + TransformRcPtr toRefTransform_; + TransformRcPtr fromRefTransform_; + + bool toRefSpecified_; + bool fromRefSpecified_; + + Impl() : + bitDepth_(BIT_DEPTH_UNKNOWN), + isData_(false), + allocation_(ALLOCATION_UNIFORM), + toRefSpecified_(false), + fromRefSpecified_(false) + { } + + ~Impl() + { } + + Impl& operator= (const Impl & rhs) + { + name_ = rhs.name_; + family_ = rhs.family_; + equalityGroup_ = rhs.equalityGroup_; + description_ = rhs.description_; + bitDepth_ = rhs.bitDepth_; + isData_ = rhs.isData_; + allocation_ = rhs.allocation_; + allocationVars_ = rhs.allocationVars_; + + toRefTransform_ = rhs.toRefTransform_; + if(toRefTransform_) toRefTransform_ = toRefTransform_->createEditableCopy(); + + fromRefTransform_ = rhs.fromRefTransform_; + if(fromRefTransform_) fromRefTransform_ = fromRefTransform_->createEditableCopy(); + + toRefSpecified_ = rhs.toRefSpecified_; + fromRefSpecified_ = rhs.fromRefSpecified_; + return *this; + } + }; + + + /////////////////////////////////////////////////////////////////////////// + + + + ColorSpace::ColorSpace() + : m_impl(new ColorSpace::Impl) + { + } + + ColorSpace::~ColorSpace() + { + delete m_impl; + m_impl = NULL; + } + + ColorSpaceRcPtr ColorSpace::createEditableCopy() const + { + ColorSpaceRcPtr cs = ColorSpace::Create(); + *cs->m_impl = *m_impl; + return cs; + } + + const char * ColorSpace::getName() const + { + return getImpl()->name_.c_str(); + } + + void ColorSpace::setName(const char * name) + { + getImpl()->name_ = name; + } + const char * ColorSpace::getFamily() const + { + return getImpl()->family_.c_str(); + } + + void ColorSpace::setFamily(const char * family) + { + getImpl()->family_ = family; + } + + const char * ColorSpace::getEqualityGroup() const + { + return getImpl()->equalityGroup_.c_str(); + } + + void ColorSpace::setEqualityGroup(const char * equalityGroup) + { + getImpl()->equalityGroup_ = equalityGroup; + } + + const char * ColorSpace::getDescription() const + { + return getImpl()->description_.c_str(); + } + + void ColorSpace::setDescription(const char * description) + { + getImpl()->description_ = description; + } + + BitDepth ColorSpace::getBitDepth() const + { + return getImpl()->bitDepth_; + } + + void ColorSpace::setBitDepth(BitDepth bitDepth) + { + getImpl()->bitDepth_ = bitDepth; + } + + bool ColorSpace::isData() const + { + return getImpl()->isData_; + } + + void ColorSpace::setIsData(bool val) + { + getImpl()->isData_ = val; + } + + Allocation ColorSpace::getAllocation() const + { + return getImpl()->allocation_; + } + + void ColorSpace::setAllocation(Allocation allocation) + { + getImpl()->allocation_ = allocation; + } + + int ColorSpace::getAllocationNumVars() const + { + return static_cast<int>(getImpl()->allocationVars_.size()); + } + + void ColorSpace::getAllocationVars(float * vars) const + { + if(!getImpl()->allocationVars_.empty()) + { + memcpy(vars, + &getImpl()->allocationVars_[0], + getImpl()->allocationVars_.size()*sizeof(float)); + } + } + + void ColorSpace::setAllocationVars(int numvars, const float * vars) + { + getImpl()->allocationVars_.resize(numvars); + + if(!getImpl()->allocationVars_.empty()) + { + memcpy(&getImpl()->allocationVars_[0], + vars, + numvars*sizeof(float)); + } + } + + ConstTransformRcPtr ColorSpace::getTransform(ColorSpaceDirection dir) const + { + if(dir == COLORSPACE_DIR_TO_REFERENCE) + return getImpl()->toRefTransform_; + else if(dir == COLORSPACE_DIR_FROM_REFERENCE) + return getImpl()->fromRefTransform_; + + throw Exception("Unspecified ColorSpaceDirection"); + } + + void ColorSpace::setTransform(const ConstTransformRcPtr & transform, + ColorSpaceDirection dir) + { + TransformRcPtr transformCopy; + if(transform) transformCopy = transform->createEditableCopy(); + + if(dir == COLORSPACE_DIR_TO_REFERENCE) + getImpl()->toRefTransform_ = transformCopy; + else if(dir == COLORSPACE_DIR_FROM_REFERENCE) + getImpl()->fromRefTransform_ = transformCopy; + else + throw Exception("Unspecified ColorSpaceDirection"); + } + + std::ostream& operator<< (std::ostream& os, const ColorSpace& cs) + { + os << "<ColorSpace "; + os << "name=" << cs.getName() << ", "; + os << "family=" << cs.getFamily() << ", "; + os << "equalityGroup=" << cs.getEqualityGroup() << ", "; + os << "bitDepth=" << BitDepthToString(cs.getBitDepth()) << ", "; + os << "isData=" << BoolToString(cs.isData()) << ", "; + os << "allocation=" << AllocationToString(cs.getAllocation()) << ", "; + os << ">\n"; + + if(cs.getTransform(COLORSPACE_DIR_TO_REFERENCE)) + { + os << "\t" << cs.getName() << " --> Reference\n"; + os << cs.getTransform(COLORSPACE_DIR_TO_REFERENCE); + } + + if(cs.getTransform(COLORSPACE_DIR_FROM_REFERENCE)) + { + os << "\tReference --> " << cs.getName() << "\n"; + os << cs.getTransform(COLORSPACE_DIR_FROM_REFERENCE); + } + return os; + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/ColorSpaceTransform.cpp b/src/core/ColorSpaceTransform.cpp new file mode 100644 index 0000000..92f3878 --- /dev/null +++ b/src/core/ColorSpaceTransform.cpp @@ -0,0 +1,246 @@ +/* +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 "NoOps.h" +#include "OpBuilders.h" + + +OCIO_NAMESPACE_ENTER +{ + ColorSpaceTransformRcPtr ColorSpaceTransform::Create() + { + return ColorSpaceTransformRcPtr(new ColorSpaceTransform(), &deleter); + } + + void ColorSpaceTransform::deleter(ColorSpaceTransform* t) + { + delete t; + } + + + class ColorSpaceTransform::Impl + { + public: + TransformDirection dir_; + std::string src_; + std::string dst_; + + Impl() : + dir_(TRANSFORM_DIR_FORWARD) + { } + + ~Impl() + { } + + Impl& operator= (const Impl & rhs) + { + dir_ = rhs.dir_; + src_ = rhs.src_; + dst_ = rhs.dst_; + return *this; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + + + ColorSpaceTransform::ColorSpaceTransform() + : m_impl(new ColorSpaceTransform::Impl) + { + } + + TransformRcPtr ColorSpaceTransform::createEditableCopy() const + { + ColorSpaceTransformRcPtr transform = ColorSpaceTransform::Create(); + *(transform->m_impl) = *m_impl; + return transform; + } + + ColorSpaceTransform::~ColorSpaceTransform() + { + delete m_impl; + m_impl = NULL; + } + + ColorSpaceTransform& ColorSpaceTransform::operator= (const ColorSpaceTransform & rhs) + { + *m_impl = *rhs.m_impl; + return *this; + } + + TransformDirection ColorSpaceTransform::getDirection() const + { + return getImpl()->dir_; + } + + void ColorSpaceTransform::setDirection(TransformDirection dir) + { + getImpl()->dir_ = dir; + } + + const char * ColorSpaceTransform::getSrc() const + { + return getImpl()->src_.c_str(); + } + + void ColorSpaceTransform::setSrc(const char * src) + { + getImpl()->src_ = src; + } + + const char * ColorSpaceTransform::getDst() const + { + return getImpl()->dst_.c_str(); + } + + void ColorSpaceTransform::setDst(const char * dst) + { + getImpl()->dst_ = dst; + } + + std::ostream& operator<< (std::ostream& os, const ColorSpaceTransform& t) + { + os << "<ColorSpaceTransform "; + os << "direction=" << TransformDirectionToString(t.getDirection()) << ", "; + os << ">\n"; + return os; + } + + + /////////////////////////////////////////////////////////////////////////////////////////////////////// + + void BuildColorSpaceOps(OpRcPtrVec & ops, + const Config& config, + const ConstContextRcPtr & context, + const ColorSpaceTransform & colorSpaceTransform, + TransformDirection dir) + { + TransformDirection combinedDir = CombineTransformDirections(dir, + colorSpaceTransform.getDirection()); + + ConstColorSpaceRcPtr src, dst; + + if(combinedDir == TRANSFORM_DIR_FORWARD) + { + src = config.getColorSpace( colorSpaceTransform.getSrc() ); + dst = config.getColorSpace( colorSpaceTransform.getDst() ); + } + else if(combinedDir == TRANSFORM_DIR_INVERSE) + { + dst = config.getColorSpace( colorSpaceTransform.getSrc() ); + src = config.getColorSpace( colorSpaceTransform.getDst() ); + } + + BuildColorSpaceOps(ops, config, context, src, dst); + } + + namespace + { + bool AreColorSpacesInSameEqualityGroup(const ConstColorSpaceRcPtr & csa, + const ConstColorSpaceRcPtr & csb) + { + std::string a = csa->getEqualityGroup(); + std::string b = csb->getEqualityGroup(); + + if(!a.empty()) return (a==b); + return false; + } + } + + void BuildColorSpaceOps(OpRcPtrVec & ops, + const Config & config, + const ConstContextRcPtr & context, + const ConstColorSpaceRcPtr & srcColorSpace, + const ConstColorSpaceRcPtr & dstColorSpace) + { + if(!srcColorSpace) + throw Exception("BuildColorSpaceOps failed, null srcColorSpace."); + if(!dstColorSpace) + throw Exception("BuildColorSpaceOps failed, null dstColorSpace."); + + if(AreColorSpacesInSameEqualityGroup(srcColorSpace, dstColorSpace)) + return; + if(dstColorSpace->isData() || srcColorSpace->isData()) + return; + + // Consider dt8 -> vd8? + // One would have to explode the srcColorSpace->getTransform(COLORSPACE_DIR_TO_REFERENCE); + // result, and walk through it step by step. If the dstColorspace family were + // ever encountered in transit, we'd want to short circuit the result. + + AllocationData srcAllocation; + srcAllocation.allocation = srcColorSpace->getAllocation(); + srcAllocation.vars.resize( srcColorSpace->getAllocationNumVars()); + if(srcAllocation.vars.size() > 0) + { + srcColorSpace->getAllocationVars(&srcAllocation.vars[0]); + } + + CreateGpuAllocationNoOp(ops, srcAllocation); + + // Go to the reference space, either by using + // * cs->ref in the forward direction + // * ref->cs in the inverse direction + if(srcColorSpace->getTransform(COLORSPACE_DIR_TO_REFERENCE)) + { + BuildOps(ops, config, context, srcColorSpace->getTransform(COLORSPACE_DIR_TO_REFERENCE), TRANSFORM_DIR_FORWARD); + } + else if(srcColorSpace->getTransform(COLORSPACE_DIR_FROM_REFERENCE)) + { + BuildOps(ops, config, context, srcColorSpace->getTransform(COLORSPACE_DIR_FROM_REFERENCE), TRANSFORM_DIR_INVERSE); + } + // Otherwise, both are not defined so its a no-op. This is not an error condition. + + // Go from the reference space, either by using + // * ref->cs in the forward direction + // * cs->ref in the inverse direction + if(dstColorSpace->getTransform(COLORSPACE_DIR_FROM_REFERENCE)) + { + BuildOps(ops, config, context, dstColorSpace->getTransform(COLORSPACE_DIR_FROM_REFERENCE), TRANSFORM_DIR_FORWARD); + } + else if(dstColorSpace->getTransform(COLORSPACE_DIR_TO_REFERENCE)) + { + BuildOps(ops, config, context, dstColorSpace->getTransform(COLORSPACE_DIR_TO_REFERENCE), TRANSFORM_DIR_INVERSE); + } + // Otherwise, both are not defined so its a no-op. This is not an error condition. + + AllocationData dstAllocation; + dstAllocation.allocation = dstColorSpace->getAllocation(); + dstAllocation.vars.resize( dstColorSpace->getAllocationNumVars()); + if(dstAllocation.vars.size() > 0) + { + dstColorSpace->getAllocationVars(&dstAllocation.vars[0]); + } + + CreateGpuAllocationNoOp(ops, dstAllocation); + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/Config.cpp b/src/core/Config.cpp new file mode 100644 index 0000000..985990f --- /dev/null +++ b/src/core/Config.cpp @@ -0,0 +1,2223 @@ +/* +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 <cstdlib> +#include <cstring> +#include <set> +#include <sstream> +#include <fstream> +#include <utility> +#include <vector> + +#include <OpenColorIO/OpenColorIO.h> + +#include "HashUtils.h" +#include "Logging.h" +#include "LookParse.h" +#include "MathUtils.h" +#include "Mutex.h" +#include "OpBuilders.h" +#include "PathUtils.h" +#include "ParseUtils.h" +#include "Processor.h" +#include "PrivateTypes.h" +#include "pystring/pystring.h" +#include "OCIOYaml.h" + +OCIO_NAMESPACE_ENTER +{ + namespace + { + const char * OCIO_CONFIG_ENVVAR = "OCIO"; + const char * OCIO_ACTIVE_DISPLAYS_ENVVAR = "OCIO_ACTIVE_DISPLAYS"; + const char * OCIO_ACTIVE_VIEWS_ENVVAR = "OCIO_ACTIVE_VIEWS"; + + enum Sanity + { + SANITY_UNKNOWN = 0, + SANITY_SANE, + SANITY_INSANE + }; + + // These are the 709 primaries specified by the ASC. + const float DEFAULT_LUMA_COEFF_R = 0.2126f; + const float DEFAULT_LUMA_COEFF_G = 0.7152f; + const float DEFAULT_LUMA_COEFF_B = 0.0722f; + + const char * INTERNAL_RAW_PROFILE = + "ocio_profile_version: 1\n" + "strictparsing: false\n" + "roles:\n" + " default: raw\n" + "displays:\n" + " sRGB:\n" + " - !<View> {name: Raw, colorspace: raw}\n" + "colorspaces:\n" + " - !<ColorSpace>\n" + " name: raw\n" + " family: raw\n" + " equalitygroup:\n" + " bitdepth: 32f\n" + " isdata: true\n" + " allocation: uniform\n" + " description: 'A raw color space. Conversions to and from this space are no-ops.'\n"; + } + + + /////////////////////////////////////////////////////////////////////////// + + const char * GetVersion() + { + return OCIO_VERSION; + } + + int GetVersionHex() + { + return OCIO_VERSION_HEX; + } + + namespace + { + ConstConfigRcPtr g_currentConfig; + Mutex g_currentConfigLock; + } + + ConstConfigRcPtr GetCurrentConfig() + { + AutoMutex lock(g_currentConfigLock); + + if(!g_currentConfig) + { + g_currentConfig = Config::CreateFromEnv(); + } + + return g_currentConfig; + } + + void SetCurrentConfig(const ConstConfigRcPtr & config) + { + AutoMutex lock(g_currentConfigLock); + + g_currentConfig = config->createEditableCopy(); + } + + namespace + { + + // Roles + // (lower case role name: colorspace name) + std::string LookupRole(const StringMap & roles, const std::string & rolename) + { + StringMap::const_iterator iter = roles.find(pystring::lower(rolename)); + if(iter == roles.end()) return ""; + return iter->second; + } + + + void GetFileReferences(std::set<std::string> & files, + const ConstTransformRcPtr & transform) + { + if(!transform) return; + + if(ConstGroupTransformRcPtr groupTransform = \ + DynamicPtrCast<const GroupTransform>(transform)) + { + for(int i=0; i<groupTransform->size(); ++i) + { + GetFileReferences(files, groupTransform->getTransform(i)); + } + } + else if(ConstFileTransformRcPtr fileTransform = \ + DynamicPtrCast<const FileTransform>(transform)) + { + files.insert(fileTransform->getSrc()); + } + } + + void GetColorSpaceReferences(std::set<std::string> & colorSpaceNames, + const ConstTransformRcPtr & transform) + { + if(!transform) return; + + if(ConstGroupTransformRcPtr groupTransform = \ + DynamicPtrCast<const GroupTransform>(transform)) + { + for(int i=0; i<groupTransform->size(); ++i) + { + GetColorSpaceReferences(colorSpaceNames, groupTransform->getTransform(i)); + } + } + else if(ConstColorSpaceTransformRcPtr colorSpaceTransform = \ + DynamicPtrCast<const ColorSpaceTransform>(transform)) + { + colorSpaceNames.insert(colorSpaceTransform->getSrc()); + colorSpaceNames.insert(colorSpaceTransform->getDst()); + } + else if(ConstDisplayTransformRcPtr displayTransform = \ + DynamicPtrCast<const DisplayTransform>(transform)) + { + colorSpaceNames.insert(displayTransform->getInputColorSpaceName()); + } + else if(ConstLookTransformRcPtr lookTransform = \ + DynamicPtrCast<const LookTransform>(transform)) + { + colorSpaceNames.insert(colorSpaceTransform->getSrc()); + colorSpaceNames.insert(colorSpaceTransform->getDst()); + } + } + + + bool FindColorSpaceIndex(int * index, + const ColorSpaceVec & colorspaces, + const std::string & csname) + { + if(csname.empty()) return false; + + std::string csnamelower = pystring::lower(csname); + + for(unsigned int i = 0; i < colorspaces.size(); ++i) + { + if(csnamelower == pystring::lower(colorspaces[i]->getName())) + { + if(index) *index = i; + return true; + } + } + + return false; + } + + + // Displays + struct View + { + std::string name; + std::string colorspace; + std::string looks; + + View() { } + + View(const std::string & name_, + const std::string & colorspace_, + const std::string & looksList_) : + name(name_), + colorspace(colorspace_), + looks(looksList_) + { } + }; + + typedef std::vector<View> ViewVec; + typedef std::map<std::string, ViewVec> DisplayMap; // (display name : ViewVec) + + void operator >> (const YAML::Node& node, View& v) + { + if(node.Tag() != "View") + return; + + std::string key, stringval; + + for (YAML::Iterator iter = node.begin(); + iter != node.end(); + ++iter) + { + iter.first() >> key; + + if(key == "name") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + v.name = stringval; + } + else if(key == "colorspace") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + v.colorspace = stringval; + } + else if(key == "looks" || key == "look") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + v.looks = stringval; + } + else + { + LogUnknownKeyWarning(node.Tag(), iter.first()); + } + } + + if(v.name.empty()) + { + throw Exception("View does not specify 'name'."); + } + if(v.colorspace.empty()) + { + std::ostringstream os; + os << "View '" << v.name << "' "; + os << "does not specify colorspace."; + throw Exception(os.str().c_str()); + } + } + + YAML::Emitter& operator << (YAML::Emitter& out, View view) + { + out << YAML::VerbatimTag("View"); + out << YAML::Flow; + out << YAML::BeginMap; + out << YAML::Key << "name" << YAML::Value << view.name; + out << YAML::Key << "colorspace" << YAML::Value << view.colorspace; + if(!view.looks.empty()) out << YAML::Key << "looks" << YAML::Value << view.looks; + out << YAML::EndMap; + return out; + } + + DisplayMap::iterator find_display(DisplayMap & displays, const std::string & display) + { + for(DisplayMap::iterator iter = displays.begin(); + iter != displays.end(); + ++iter) + { + if(StrEqualsCaseIgnore(display, iter->first)) return iter; + } + return displays.end(); + } + + DisplayMap::const_iterator find_display_const(const DisplayMap & displays, const std::string & display) + { + for(DisplayMap::const_iterator iter = displays.begin(); + iter != displays.end(); + ++iter) + { + if(StrEqualsCaseIgnore(display, iter->first)) return iter; + } + return displays.end(); + } + + int find_view(const ViewVec & vec, const std::string & name) + { + for(unsigned int i=0; i<vec.size(); ++i) + { + if(StrEqualsCaseIgnore(name, vec[i].name)) return i; + } + return -1; + } + + void AddDisplay(DisplayMap & displays, + const std::string & display, + const std::string & view, + const std::string & colorspace, + const std::string & looks) + { + DisplayMap::iterator iter = find_display(displays, display); + if(iter == displays.end()) + { + ViewVec views; + views.push_back( View(view, colorspace, looks) ); + displays[display] = views; + } + else + { + ViewVec & views = iter->second; + int index = find_view(views, view); + if(index<0) + { + views.push_back( View(view, colorspace, looks) ); + } + else + { + views[index].colorspace = colorspace; + views[index].looks = looks; + } + } + } + + void ComputeDisplays(StringVec & displayCache, + const DisplayMap & displays, + const StringVec & activeDisplays, + const StringVec & activeDisplaysEnvOverride) + { + displayCache.clear(); + + StringVec displayMasterList; + for(DisplayMap::const_iterator iter = displays.begin(); + iter != displays.end(); + ++iter) + { + displayMasterList.push_back(iter->first); + } + + // Apply the env override if it's not empty. + if(!activeDisplaysEnvOverride.empty()) + { + displayCache = IntersectStringVecsCaseIgnore(displayMasterList, activeDisplaysEnvOverride); + if(!displayCache.empty()) return; + } + // Otherwise, aApply the active displays if it's not empty. + else if(!activeDisplays.empty()) + { + displayCache = IntersectStringVecsCaseIgnore(displayMasterList, activeDisplays); + if(!displayCache.empty()) return; + } + + displayCache = displayMasterList; + } + + + + } // namespace + + class Config::Impl + { + public: + ContextRcPtr context_; + std::string description_; + ColorSpaceVec colorspaces_; + StringMap roles_; + LookVec looksList_; + + DisplayMap displays_; + StringVec activeDisplays_; + StringVec activeDisplaysEnvOverride_; + StringVec activeViews_; + StringVec activeViewsEnvOverride_; + + mutable std::string activeDisplaysStr_; + mutable std::string activeViewsStr_; + mutable StringVec displayCache_; + + // Misc + std::vector<float> defaultLumaCoefs_; + bool strictParsing_; + + mutable Sanity sanity_; + mutable std::string sanitytext_; + + mutable Mutex cacheidMutex_; + mutable StringMap cacheids_; + mutable std::string cacheidnocontext_; + + Impl() : + context_(Context::Create()), + strictParsing_(true), + sanity_(SANITY_UNKNOWN) + { + context_->loadEnvironment(); + + char* activeDisplays = std::getenv(OCIO_ACTIVE_DISPLAYS_ENVVAR); + SplitStringEnvStyle(activeDisplaysEnvOverride_, activeDisplays); + + char * activeViews = std::getenv(OCIO_ACTIVE_VIEWS_ENVVAR); + SplitStringEnvStyle(activeViewsEnvOverride_, activeViews); + + defaultLumaCoefs_.resize(3); + defaultLumaCoefs_[0] = DEFAULT_LUMA_COEFF_R; + defaultLumaCoefs_[1] = DEFAULT_LUMA_COEFF_G; + defaultLumaCoefs_[2] = DEFAULT_LUMA_COEFF_B; + } + + ~Impl() + { + + } + + Impl& operator= (const Impl & rhs) + { + context_ = rhs.context_->createEditableCopy(); + description_ = rhs.description_; + + // Deep copy the colorspaces + colorspaces_.clear(); + colorspaces_.reserve(rhs.colorspaces_.size()); + for(unsigned int i=0; i<rhs.colorspaces_.size(); ++i) + { + colorspaces_.push_back(rhs.colorspaces_[i]->createEditableCopy()); + } + + // Deep copy the looks + looksList_.clear(); + looksList_.reserve(rhs.looksList_.size()); + for(unsigned int i=0; i<rhs.looksList_.size(); ++i) + { + looksList_.push_back(rhs.looksList_[i]->createEditableCopy()); + } + + // Assignment operator will suffice for these + roles_ = rhs.roles_; + + displays_ = rhs.displays_; + activeDisplays_ = rhs.activeDisplays_; + activeViews_ = rhs.activeViews_; + activeViewsEnvOverride_ = rhs.activeViewsEnvOverride_; + activeDisplaysEnvOverride_ = rhs.activeDisplaysEnvOverride_; + activeDisplaysStr_ = rhs.activeDisplaysStr_; + displayCache_ = rhs.displayCache_; + + defaultLumaCoefs_ = rhs.defaultLumaCoefs_; + strictParsing_ = rhs.strictParsing_; + + sanity_ = rhs.sanity_; + sanitytext_ = rhs.sanitytext_; + + cacheids_ = rhs.cacheids_; + cacheidnocontext_ = cacheidnocontext_; + return *this; + } + + void load(std::istream & istream, const char * name); + + // Any time you modify the state of the config, you must call this + // to reset internal cache states. You also should do this in a + // thread safe manner by acquiring the cacheidMutex_; + void resetCacheIDs(); + + // Get all internal transforms (to generate cacheIDs, validation, etc). + // This currently crawls colorspaces + looks + void getAllIntenalTransforms(ConstTransformVec & transformVec) const; + }; + + + /////////////////////////////////////////////////////////////////////////// + + ConfigRcPtr Config::Create() + { + return ConfigRcPtr(new Config(), &deleter); + } + + void Config::deleter(Config* c) + { + delete c; + } + + ConstConfigRcPtr Config::CreateFromEnv() + { + char* file = std::getenv(OCIO_CONFIG_ENVVAR); + if(file) return CreateFromFile(file); + + std::ostringstream os; + os << "Color management disabled. "; + os << "(Specify the $OCIO environment variable to enable.)"; + LogInfo(os.str()); + + std::istringstream istream; + istream.str(INTERNAL_RAW_PROFILE); + + ConfigRcPtr config = Config::Create(); + config->getImpl()->load(istream, ""); + return config; + } + + ConstConfigRcPtr Config::CreateFromFile(const char * filename) + { + std::ifstream istream(filename); + if(istream.fail()) { + std::ostringstream os; + os << "Error could not read '" << filename; + os << "' OCIO profile."; + throw Exception (os.str().c_str()); + } + + ConfigRcPtr config = Config::Create(); + config->getImpl()->load(istream, filename); + return config; + } + + ConstConfigRcPtr Config::CreateFromStream(std::istream & istream) + { + ConfigRcPtr config = Config::Create(); + config->getImpl()->load(istream, ""); + return config; + } + + /////////////////////////////////////////////////////////////////////////// + + + + Config::Config() + : m_impl(new Config::Impl) + { + } + + Config::~Config() + { + delete m_impl; + m_impl = NULL; + } + + ConfigRcPtr Config::createEditableCopy() const + { + ConfigRcPtr config = Config::Create(); + *config->m_impl = *m_impl; + return config; + } + + void Config::sanityCheck() const + { + if(getImpl()->sanity_ == SANITY_SANE) return; + if(getImpl()->sanity_ == SANITY_INSANE) + { + throw Exception(getImpl()->sanitytext_.c_str()); + } + + getImpl()->sanity_ = SANITY_INSANE; + getImpl()->sanitytext_ = ""; + + + ///// COLORSPACES + StringSet existingColorSpaces; + + // Confirm all ColorSpaces are valid + for(unsigned int i=0; i<getImpl()->colorspaces_.size(); ++i) + { + if(!getImpl()->colorspaces_[i]) + { + std::ostringstream os; + os << "Config failed sanitycheck. "; + os << "The colorspace at index " << i << " is null."; + getImpl()->sanitytext_ = os.str(); + throw Exception(getImpl()->sanitytext_.c_str()); + } + + const char * name = getImpl()->colorspaces_[i]->getName(); + if(!name || strlen(name) == 0) + { + std::ostringstream os; + os << "Config failed sanitycheck. "; + os << "The colorspace at index " << i << " is not named."; + getImpl()->sanitytext_ = os.str(); + throw Exception(getImpl()->sanitytext_.c_str()); + } + + std::string namelower = pystring::lower(name); + StringSet::const_iterator it = existingColorSpaces.find(namelower); + if(it != existingColorSpaces.end()) + { + std::ostringstream os; + os << "Config failed sanitycheck. "; + os << "Two colorspaces are defined with the same name, '"; + os << namelower << "'."; + getImpl()->sanitytext_ = os.str(); + throw Exception(getImpl()->sanitytext_.c_str()); + } + + existingColorSpaces.insert(namelower); + } + + // Confirm all roles are valid + { + for(StringMap::const_iterator iter = getImpl()->roles_.begin(), + end = getImpl()->roles_.end(); iter!=end; ++iter) + { + int csindex = -1; + if(!FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, iter->second)) + { + std::ostringstream os; + os << "Config failed sanitycheck. "; + os << "The role '" << iter->first << "' "; + os << "refers to a colorspace, '" << iter->second << "', "; + os << "which is not defined."; + getImpl()->sanitytext_ = os.str(); + throw Exception(getImpl()->sanitytext_.c_str()); + } + + // Confirm no name conflicts between colorspaces and roles + if(FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, iter->first)) + { + std::ostringstream os; + os << "Config failed sanitycheck. "; + os << "The role '" << iter->first << "' "; + os << " is in conflict with a colorspace of the same name."; + getImpl()->sanitytext_ = os.str(); + throw Exception(getImpl()->sanitytext_.c_str()); + } + } + } + + ///// DISPLAYS + + int numviews = 0; + + // Confirm all Displays transforms refer to colorspaces that exit + for(DisplayMap::const_iterator iter = getImpl()->displays_.begin(); + iter != getImpl()->displays_.end(); + ++iter) + { + std::string display = iter->first; + const ViewVec & views = iter->second; + if(views.empty()) + { + std::ostringstream os; + os << "Config failed sanitycheck. "; + os << "The display '" << display << "' "; + os << "does not define any views."; + getImpl()->sanitytext_ = os.str(); + throw Exception(getImpl()->sanitytext_.c_str()); + } + + for(unsigned int i=0; i<views.size(); ++i) + { + if(views[i].name.empty() || views[i].colorspace.empty()) + { + std::ostringstream os; + os << "Config failed sanitycheck. "; + os << "The display '" << display << "' "; + os << "defines a view with an empty name and/or colorspace."; + getImpl()->sanitytext_ = os.str(); + throw Exception(getImpl()->sanitytext_.c_str()); + } + + int csindex = -1; + if(!FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, views[i].colorspace)) + { + std::ostringstream os; + os << "Config failed sanitycheck. "; + os << "The display '" << display << "' "; + os << "refers to a colorspace, '" << views[i].colorspace << "', "; + os << "which is not defined."; + getImpl()->sanitytext_ = os.str(); + throw Exception(getImpl()->sanitytext_.c_str()); + } + + // Confirm looks references exist + LookParseResult looks; + const LookParseResult::Options & options = looks.parse(views[i].looks); + + for(unsigned int optionindex=0; + optionindex<options.size(); + ++optionindex) + { + for(unsigned int tokenindex=0; + tokenindex<options[optionindex].size(); + ++tokenindex) + { + std::string look = options[optionindex][tokenindex].name; + + if(!look.empty() && !getLook(look.c_str())) + { + std::ostringstream os; + os << "Config failed sanitycheck. "; + os << "The display '" << display << "' "; + os << "refers to a look, '" << look << "', "; + os << "which is not defined."; + getImpl()->sanitytext_ = os.str(); + throw Exception(getImpl()->sanitytext_.c_str()); + } + } + } + + ++numviews; + } + } + + // Confirm at least one display entry exists. + if(numviews == 0) + { + std::ostringstream os; + os << "Config failed sanitycheck. "; + os << "No displays are specified."; + getImpl()->sanitytext_ = os.str(); + throw Exception(getImpl()->sanitytext_.c_str()); + } + + // Confirm for all Transforms that reference internal colorspaces, + // the named space exists + { + ConstTransformVec allTransforms; + getImpl()->getAllIntenalTransforms(allTransforms); + + std::set<std::string> colorSpaceNames; + for(unsigned int i=0; i<colorSpaceNames.size(); ++i) + { + GetColorSpaceReferences(colorSpaceNames, allTransforms[i]); + } + + for(std::set<std::string>::iterator iter = colorSpaceNames.begin(); + iter != colorSpaceNames.end(); ++iter) + { + int csindex = -1; + if(!FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, *iter)) + { + std::ostringstream os; + os << "Config failed sanitycheck. "; + os << "This config references a ColorSpace, '" << *iter << "', "; + os << "which is not defined."; + getImpl()->sanitytext_ = os.str(); + throw Exception(getImpl()->sanitytext_.c_str()); + } + } + } + + ///// LOOKS + + // For all looks, confirm the process space exists and the look is named + for(unsigned int i=0; i<getImpl()->looksList_.size(); ++i) + { + std::string name = getImpl()->looksList_[i]->getName(); + if(name.empty()) + { + std::ostringstream os; + os << "Config failed sanitycheck. "; + os << "The look at index '" << i << "' "; + os << "does not specify a name."; + getImpl()->sanitytext_ = os.str(); + throw Exception(getImpl()->sanitytext_.c_str()); + } + + std::string processSpace = getImpl()->looksList_[i]->getProcessSpace(); + if(processSpace.empty()) + { + std::ostringstream os; + os << "Config failed sanitycheck. "; + os << "The look '" << name << "' "; + os << "does not specify a process space."; + getImpl()->sanitytext_ = os.str(); + throw Exception(getImpl()->sanitytext_.c_str()); + } + + int csindex=0; + if(!FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, processSpace)) + { + std::ostringstream os; + os << "Config failed sanitycheck. "; + os << "The look '" << name << "' "; + os << "specifies a process color space, '"; + os << processSpace << "', which is not defined."; + getImpl()->sanitytext_ = os.str(); + throw Exception(getImpl()->sanitytext_.c_str()); + } + } + + + + // Everything is groovy. + getImpl()->sanity_ = SANITY_SANE; + } + + /////////////////////////////////////////////////////////////////////////// + + const char * Config::getDescription() const + { + return getImpl()->description_.c_str(); + } + + void Config::setDescription(const char * description) + { + getImpl()->description_ = description; + + AutoMutex lock(getImpl()->cacheidMutex_); + getImpl()->resetCacheIDs(); + } + + + // RESOURCES ////////////////////////////////////////////////////////////// + + ConstContextRcPtr Config::getCurrentContext() const + { + return getImpl()->context_; + } + + const char * Config::getSearchPath() const + { + return getImpl()->context_->getSearchPath(); + } + + void Config::setSearchPath(const char * path) + { + getImpl()->context_->setSearchPath(path); + + AutoMutex lock(getImpl()->cacheidMutex_); + getImpl()->resetCacheIDs(); + } + + const char * Config::getWorkingDir() const + { + return getImpl()->context_->getWorkingDir(); + } + + void Config::setWorkingDir(const char * dirname) + { + getImpl()->context_->setWorkingDir(dirname); + + AutoMutex lock(getImpl()->cacheidMutex_); + getImpl()->resetCacheIDs(); + } + + + /////////////////////////////////////////////////////////////////////////// + + int Config::getNumColorSpaces() const + { + return static_cast<int>(getImpl()->colorspaces_.size()); + } + + const char * Config::getColorSpaceNameByIndex(int index) const + { + if(index<0 || index >= (int)getImpl()->colorspaces_.size()) + { + return ""; + } + + return getImpl()->colorspaces_[index]->getName(); + } + + ConstColorSpaceRcPtr Config::getColorSpace(const char * name) const + { + int index = getIndexForColorSpace(name); + if(index<0 || index >= (int)getImpl()->colorspaces_.size()) + { + return ColorSpaceRcPtr(); + } + + return getImpl()->colorspaces_[index]; + } + + int Config::getIndexForColorSpace(const char * name) const + { + int csindex = -1; + + // Check to see if the name is a color space + if( FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, name) ) + { + return csindex; + } + + // Check to see if the name is a role + std::string csname = LookupRole(getImpl()->roles_, name); + if( FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, csname) ) + { + return csindex; + } + + // Is a default role defined? + // (And, are we allowed to use it) + if(!getImpl()->strictParsing_) + { + csname = LookupRole(getImpl()->roles_, ROLE_DEFAULT); + if( FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, csname) ) + { + return csindex; + } + } + + return -1; + } + + void Config::addColorSpace(const ConstColorSpaceRcPtr & original) + { + ColorSpaceRcPtr cs = original->createEditableCopy(); + + std::string name = cs->getName(); + if(name.empty()) + throw Exception("Cannot addColorSpace with an empty name."); + + // Check to see if the colorspace already exists + int csindex = -1; + if( FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, name) ) + { + getImpl()->colorspaces_[csindex] = cs; + } + else + { + // Otherwise, add it + getImpl()->colorspaces_.push_back( cs ); + } + + AutoMutex lock(getImpl()->cacheidMutex_); + getImpl()->resetCacheIDs(); + } + + void Config::clearColorSpaces() + { + getImpl()->colorspaces_.clear(); + } + + + + + + + const char * Config::parseColorSpaceFromString(const char * str) const + { + if(!str) return ""; + + // Search the entire filePath, including directory name (if provided) + // convert the filename to lowercase. + std::string fullstr = pystring::lower(std::string(str)); + + // See if it matches a lut name. + // This is the position of the RIGHT end of the colorspace substring, not the left + int rightMostColorPos=-1; + std::string rightMostColorspace = ""; + int rightMostColorSpaceIndex = -1; + + // Find the right-most occcurance within the string for each colorspace. + for (unsigned int i=0; i<getImpl()->colorspaces_.size(); ++i) + { + std::string csname = pystring::lower(getImpl()->colorspaces_[i]->getName()); + + // find right-most extension matched in filename + int colorspacePos = pystring::rfind(fullstr, csname); + if(colorspacePos < 0) + continue; + + // If we have found a match, move the pointer over to the right end of the substring + // This will allow us to find the longest name that matches the rightmost colorspace + colorspacePos += (int)csname.size(); + + if ( (colorspacePos > rightMostColorPos) || + ((colorspacePos == rightMostColorPos) && (csname.size() > rightMostColorspace.size())) + ) + { + rightMostColorPos = colorspacePos; + rightMostColorspace = csname; + rightMostColorSpaceIndex = i; + } + } + + if(rightMostColorSpaceIndex>=0) + { + return getImpl()->colorspaces_[rightMostColorSpaceIndex]->getName(); + } + + if(!getImpl()->strictParsing_) + { + // Is a default role defined? + std::string csname = LookupRole(getImpl()->roles_, ROLE_DEFAULT); + if(!csname.empty()) + { + int csindex = -1; + if( FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, csname) ) + { + // This is necessary to not return a reference to + // a local variable. + return getImpl()->colorspaces_[csindex]->getName(); + } + } + } + + return ""; + } + + bool Config::isStrictParsingEnabled() const + { + return getImpl()->strictParsing_; + } + + void Config::setStrictParsingEnabled(bool enabled) + { + getImpl()->strictParsing_ = enabled; + + AutoMutex lock(getImpl()->cacheidMutex_); + getImpl()->resetCacheIDs(); + } + + // Roles + void Config::setRole(const char * role, const char * colorSpaceName) + { + // Set the role + if(colorSpaceName) + { + getImpl()->roles_[pystring::lower(role)] = std::string(colorSpaceName); + } + // Unset the role + else + { + StringMap::iterator iter = getImpl()->roles_.find(pystring::lower(role)); + if(iter != getImpl()->roles_.end()) + { + getImpl()->roles_.erase(iter); + } + } + + AutoMutex lock(getImpl()->cacheidMutex_); + getImpl()->resetCacheIDs(); + } + + int Config::getNumRoles() const + { + return static_cast<int>(getImpl()->roles_.size()); + } + + bool Config::hasRole(const char * role) const + { + return LookupRole(getImpl()->roles_, role) == "" ? false : true; + } + + const char * Config::getRoleName(int index) const + { + if(index < 0 || index >= (int)getImpl()->roles_.size()) return ""; + StringMap::const_iterator iter = getImpl()->roles_.begin(); + for(int i = 0; i < index; ++i) ++iter; + return iter->first.c_str(); + } + + /////////////////////////////////////////////////////////////////////////// + // + // Display/View Registration + + + const char * Config::getDefaultDisplay() const + { + if(getImpl()->displayCache_.empty()) + { + ComputeDisplays(getImpl()->displayCache_, + getImpl()->displays_, + getImpl()->activeDisplays_, + getImpl()->activeDisplaysEnvOverride_); + } + + int index = -1; + + if(!getImpl()->activeDisplaysEnvOverride_.empty()) + { + StringVec orderedDisplays = IntersectStringVecsCaseIgnore(getImpl()->activeDisplaysEnvOverride_, + getImpl()->displayCache_); + if(!orderedDisplays.empty()) + { + index = FindInStringVecCaseIgnore(getImpl()->displayCache_, orderedDisplays[0]); + } + } + else if(!getImpl()->activeDisplays_.empty()) + { + StringVec orderedDisplays = IntersectStringVecsCaseIgnore(getImpl()->activeDisplays_, + getImpl()->displayCache_); + if(!orderedDisplays.empty()) + { + index = FindInStringVecCaseIgnore(getImpl()->displayCache_, orderedDisplays[0]); + } + } + + if(index >= 0) + { + return getImpl()->displayCache_[index].c_str(); + } + + if(!getImpl()->displayCache_.empty()) + { + return getImpl()->displayCache_[0].c_str(); + } + + return ""; + } + + + int Config::getNumDisplays() const + { + if(getImpl()->displayCache_.empty()) + { + ComputeDisplays(getImpl()->displayCache_, + getImpl()->displays_, + getImpl()->activeDisplays_, + getImpl()->activeDisplaysEnvOverride_); + } + + return static_cast<int>(getImpl()->displayCache_.size()); + } + + const char * Config::getDisplay(int index) const + { + if(getImpl()->displayCache_.empty()) + { + ComputeDisplays(getImpl()->displayCache_, + getImpl()->displays_, + getImpl()->activeDisplays_, + getImpl()->activeDisplaysEnvOverride_); + } + + if(index>=0 || index < static_cast<int>(getImpl()->displayCache_.size())) + { + return getImpl()->displayCache_[index].c_str(); + } + + return ""; + } + + const char * Config::getDefaultView(const char * display) const + { + if(getImpl()->displayCache_.empty()) + { + ComputeDisplays(getImpl()->displayCache_, + getImpl()->displays_, + getImpl()->activeDisplays_, + getImpl()->activeDisplaysEnvOverride_); + } + + if(!display) return ""; + + DisplayMap::const_iterator iter = find_display_const(getImpl()->displays_, display); + if(iter == getImpl()->displays_.end()) return ""; + + const ViewVec & views = iter->second; + + StringVec masterViews; + for(unsigned int i=0; i<views.size(); ++i) + { + masterViews.push_back(views[i].name); + } + + int index = -1; + + if(!getImpl()->activeViewsEnvOverride_.empty()) + { + StringVec orderedViews = IntersectStringVecsCaseIgnore(getImpl()->activeViewsEnvOverride_, + masterViews); + if(!orderedViews.empty()) + { + index = FindInStringVecCaseIgnore(masterViews, orderedViews[0]); + } + } + else if(!getImpl()->activeViews_.empty()) + { + StringVec orderedViews = IntersectStringVecsCaseIgnore(getImpl()->activeViews_, + masterViews); + if(!orderedViews.empty()) + { + index = FindInStringVecCaseIgnore(masterViews, orderedViews[0]); + } + } + + if(index >= 0) + { + return views[index].name.c_str(); + } + + if(!views.empty()) + { + return views[0].name.c_str(); + } + + return ""; + } + + int Config::getNumViews(const char * display) const + { + if(getImpl()->displayCache_.empty()) + { + ComputeDisplays(getImpl()->displayCache_, + getImpl()->displays_, + getImpl()->activeDisplays_, + getImpl()->activeDisplaysEnvOverride_); + } + + if(!display) return 0; + + DisplayMap::const_iterator iter = find_display_const(getImpl()->displays_, display); + if(iter == getImpl()->displays_.end()) return 0; + + const ViewVec & views = iter->second; + return static_cast<int>(views.size()); + } + + const char * Config::getView(const char * display, int index) const + { + if(getImpl()->displayCache_.empty()) + { + ComputeDisplays(getImpl()->displayCache_, + getImpl()->displays_, + getImpl()->activeDisplays_, + getImpl()->activeDisplaysEnvOverride_); + } + + if(!display) return ""; + + DisplayMap::const_iterator iter = find_display_const(getImpl()->displays_, display); + if(iter == getImpl()->displays_.end()) return ""; + + const ViewVec & views = iter->second; + return views[index].name.c_str(); + } + + const char * Config::getDisplayColorSpaceName(const char * display, const char * view) const + { + if(!display || !view) return ""; + + DisplayMap::const_iterator iter = find_display_const(getImpl()->displays_, display); + if(iter == getImpl()->displays_.end()) return ""; + + const ViewVec & views = iter->second; + int index = find_view(views, view); + if(index<0) return ""; + + return views[index].colorspace.c_str(); + } + + const char * Config::getDisplayLooks(const char * display, const char * view) const + { + if(!display || !view) return ""; + + DisplayMap::const_iterator iter = find_display_const(getImpl()->displays_, display); + if(iter == getImpl()->displays_.end()) return ""; + + const ViewVec & views = iter->second; + int index = find_view(views, view); + if(index<0) return ""; + + return views[index].looks.c_str(); + } + + void Config::addDisplay(const char * display, const char * view, + const char * colorSpaceName, const char * lookName) + { + + if(!display || !view || !colorSpaceName || !lookName) return; + + AddDisplay(getImpl()->displays_, + display, view, colorSpaceName, lookName); + getImpl()->displayCache_.clear(); + + AutoMutex lock(getImpl()->cacheidMutex_); + getImpl()->resetCacheIDs(); + } + + void Config::clearDisplays() + { + getImpl()->displays_.clear(); + getImpl()->displayCache_.clear(); + + AutoMutex lock(getImpl()->cacheidMutex_); + getImpl()->resetCacheIDs(); + } + + void Config::setActiveDisplays(const char * displays) + { + getImpl()->activeDisplays_.clear(); + SplitStringEnvStyle(getImpl()->activeDisplays_, displays); + + getImpl()->displayCache_.clear(); + + AutoMutex lock(getImpl()->cacheidMutex_); + getImpl()->resetCacheIDs(); + } + + const char * Config::getActiveDisplays() const + { + getImpl()->activeDisplaysStr_ = JoinStringEnvStyle(getImpl()->activeDisplays_); + return getImpl()->activeDisplaysStr_.c_str(); + } + + void Config::setActiveViews(const char * views) + { + getImpl()->activeViews_.clear(); + SplitStringEnvStyle(getImpl()->activeViews_, views); + + getImpl()->displayCache_.clear(); + + AutoMutex lock(getImpl()->cacheidMutex_); + getImpl()->resetCacheIDs(); + } + + const char * Config::getActiveViews() const + { + getImpl()->activeViewsStr_ = JoinStringEnvStyle(getImpl()->activeViews_); + return getImpl()->activeViewsStr_.c_str(); + } + + /////////////////////////////////////////////////////////////////////////// + + + void Config::getDefaultLumaCoefs(float * c3) const + { + memcpy(c3, &getImpl()->defaultLumaCoefs_[0], 3*sizeof(float)); + } + + void Config::setDefaultLumaCoefs(const float * c3) + { + memcpy(&getImpl()->defaultLumaCoefs_[0], c3, 3*sizeof(float)); + + AutoMutex lock(getImpl()->cacheidMutex_); + getImpl()->resetCacheIDs(); + } + + + + + /////////////////////////////////////////////////////////////////////////// + + + + + ConstLookRcPtr Config::getLook(const char * name) const + { + std::string namelower = pystring::lower(name); + + for(unsigned int i=0; i<getImpl()->looksList_.size(); ++i) + { + if(pystring::lower(getImpl()->looksList_[i]->getName()) == namelower) + { + return getImpl()->looksList_[i]; + } + } + + return ConstLookRcPtr(); + } + + int Config::getNumLooks() const + { + return static_cast<int>(getImpl()->looksList_.size()); + } + + const char * Config::getLookNameByIndex(int index) const + { + if(index<0 || index>=static_cast<int>(getImpl()->looksList_.size())) + { + return ""; + } + + return getImpl()->looksList_[index]->getName(); + } + + void Config::addLook(const ConstLookRcPtr & look) + { + std::string name = look->getName(); + if(name.empty()) + throw Exception("Cannot addLook with an empty name."); + + std::string namelower = pystring::lower(name); + + // If the look exists, replace it + for(unsigned int i=0; i<getImpl()->looksList_.size(); ++i) + { + if(pystring::lower(getImpl()->looksList_[i]->getName()) == namelower) + { + getImpl()->looksList_[i] = look->createEditableCopy(); + return; + } + } + + // Otherwise, add it + getImpl()->looksList_.push_back(look->createEditableCopy()); + + AutoMutex lock(getImpl()->cacheidMutex_); + getImpl()->resetCacheIDs(); + } + + void Config::clearLooks() + { + getImpl()->looksList_.clear(); + + AutoMutex lock(getImpl()->cacheidMutex_); + getImpl()->resetCacheIDs(); + } + + /////////////////////////////////////////////////////////////////////////// + + + + ConstProcessorRcPtr Config::getProcessor(const ConstColorSpaceRcPtr & src, + const ConstColorSpaceRcPtr & dst) const + { + ConstContextRcPtr context = getCurrentContext(); + return getProcessor(context, src, dst); + } + + ConstProcessorRcPtr Config::getProcessor(const ConstContextRcPtr & context, + const ConstColorSpaceRcPtr & src, + const ConstColorSpaceRcPtr & dst) const + { + if(!src) + { + throw Exception("Config::GetProcessor failed. Source colorspace is null."); + } + if(!dst) + { + throw Exception("Config::GetProcessor failed. Destination colorspace is null."); + } + + ProcessorRcPtr processor = Processor::Create(); + processor->getImpl()->addColorSpaceConversion(*this, context, src, dst); + processor->getImpl()->finalize(); + return processor; + } + + ConstProcessorRcPtr Config::getProcessor(const char * srcName, + const char * dstName) const + { + ConstContextRcPtr context = getCurrentContext(); + return getProcessor(context, srcName, dstName); + } + + //! Names can be colorspace name or role name + ConstProcessorRcPtr Config::getProcessor(const ConstContextRcPtr & context, + const char * srcName, + const char * dstName) const + { + ConstColorSpaceRcPtr src = getColorSpace(srcName); + if(!src) + { + std::ostringstream os; + os << "Could not find colorspace '" << srcName << "'."; + throw Exception(os.str().c_str()); + } + + ConstColorSpaceRcPtr dst = getColorSpace(dstName); + if(!dst) + { + std::ostringstream os; + os << "Could not find colorspace '" << dstName << "'."; + throw Exception(os.str().c_str()); + } + + return getProcessor(context, src, dst); + } + + + ConstProcessorRcPtr Config::getProcessor(const ConstTransformRcPtr& transform) const + { + return getProcessor(transform, TRANSFORM_DIR_FORWARD); + } + + + ConstProcessorRcPtr Config::getProcessor(const ConstTransformRcPtr& transform, + TransformDirection direction) const + { + ConstContextRcPtr context = getCurrentContext(); + return getProcessor(context, transform, direction); + } + + ConstProcessorRcPtr Config::getProcessor(const ConstContextRcPtr & context, + const ConstTransformRcPtr& transform, + TransformDirection direction) const + { + ProcessorRcPtr processor = Processor::Create(); + processor->getImpl()->addTransform(*this, context, transform, direction); + processor->getImpl()->finalize(); + return processor; + } + + std::ostream& operator<< (std::ostream& os, const Config& config) + { + config.serialize(os); + return os; + } + + /////////////////////////////////////////////////////////////////////////// + // CacheID + + const char * Config::getCacheID() const + { + return getCacheID(getCurrentContext()); + } + + const char * Config::getCacheID(const ConstContextRcPtr & context) const + { + AutoMutex lock(getImpl()->cacheidMutex_); + + // A null context will use the empty cacheid + std::string contextcacheid = ""; + if(context) contextcacheid = context->getCacheID(); + + StringMap::const_iterator cacheiditer = getImpl()->cacheids_.find(contextcacheid); + if(cacheiditer != getImpl()->cacheids_.end()) + { + return cacheiditer->second.c_str(); + } + + // Include the hash of the yaml config serialization + if(getImpl()->cacheidnocontext_.empty()) + { + std::stringstream cacheid; + serialize(cacheid); + std::string fullstr = cacheid.str(); + getImpl()->cacheidnocontext_ = CacheIDHash(fullstr.c_str(), (int)fullstr.size()); + } + + // Also include all file references, using the context (if specified) + std::string fileReferencesFashHash = ""; + if(context) + { + std::ostringstream filehash; + + ConstTransformVec allTransforms; + getImpl()->getAllIntenalTransforms(allTransforms); + + std::set<std::string> files; + for(unsigned int i=0; i<allTransforms.size(); ++i) + { + GetFileReferences(files, allTransforms[i]); + } + + for(std::set<std::string>::iterator iter = files.begin(); + iter != files.end(); ++iter) + { + if(iter->empty()) continue; + filehash << *iter << "="; + + try + { + std::string resolvedLocation = context->resolveFileLocation(iter->c_str()); + filehash << GetFastFileHash(resolvedLocation) << " "; + } + catch(...) + { + filehash << "? "; + continue; + } + } + + std::string fullstr = filehash.str(); + fileReferencesFashHash = CacheIDHash(fullstr.c_str(), (int)fullstr.size()); + } + + getImpl()->cacheids_[contextcacheid] = getImpl()->cacheidnocontext_ + ":" + fileReferencesFashHash; + return getImpl()->cacheids_[contextcacheid].c_str(); + } + + + /////////////////////////////////////////////////////////////////////////// + // Serialization + + void Config::serialize(std::ostream& os) const + { + try + { + YAML::Emitter out; + out << YAML::Block; + out << YAML::BeginMap; + out << YAML::Key << "ocio_profile_version" << YAML::Value << 1; + out << YAML::Newline; + + out << YAML::Key << "search_path" << YAML::Value << getImpl()->context_->getSearchPath(); + out << YAML::Key << "strictparsing" << YAML::Value << getImpl()->strictParsing_; + out << YAML::Key << "luma" << YAML::Value << YAML::Flow << getImpl()->defaultLumaCoefs_; + + if(getImpl()->description_ != "") + { + out << YAML::Newline; + out << YAML::Key << "description"; + out << YAML::Value << getImpl()->description_; + } + + // Roles + out << YAML::Newline; + out << YAML::Key << "roles"; + out << YAML::Value << getImpl()->roles_; + + // Displays + out << YAML::Newline; + out << YAML::Key << "displays"; + out << YAML::Value << getImpl()->displays_; + out << YAML::Newline; + out << YAML::Key << "active_displays"; + out << YAML::Value << YAML::Flow << getImpl()->activeDisplays_; + out << YAML::Key << "active_views"; + out << YAML::Value << YAML::Flow << getImpl()->activeViews_; + + // Looks + if(!getImpl()->looksList_.empty()) + { + out << YAML::Newline; + out << YAML::Key << "looks"; + out << YAML::Value << getImpl()->looksList_; + } + + // ColorSpaces + { + out << YAML::Newline; + out << YAML::Key << "colorspaces"; + out << YAML::Value << getImpl()->colorspaces_; + } + + out << YAML::EndMap; + + os << out.c_str(); + } + catch( const std::exception & e) + { + std::ostringstream error; + error << "Error building YAML: " << e.what(); + throw Exception(error.str().c_str()); + } + } + + void Config::Impl::load(std::istream & istream, const char * filename) + { + try + { + YAML::Parser parser(istream); + YAML::Node node; + parser.GetNextDocument(node); + + // check profile version + int profile_version = 0; + if(node.FindValue("ocio_profile_version") == NULL) + { + std::ostringstream os; + os << "The specified file "; + os << "does not appear to be an OCIO configuration."; + throw Exception (os.str().c_str()); + } + + node["ocio_profile_version"] >> profile_version; + if(profile_version > 1) + { + std::ostringstream os; + os << "This .ocio config "; + if(filename && *filename) + { + os << " '" << filename << "' "; + } + os << "is version " << profile_version << ". "; + os << "This version of the OpenColorIO library (" << OCIO_VERSION ") "; + os << "is not known to be able to load this profile. "; + os << "An attempt will be made, but there are no guarantees that the "; + os << "results will be accurate. Continue at your own risk."; + LogWarning(os.str()); + } + + + std::string key, stringval; + bool boolval = false; + + for (YAML::Iterator iter = node.begin(); + iter != node.end(); + ++iter) + { + iter.first() >> key; + + if(key == "ocio_profile_version") { } // Already handled above. + else if(key == "search_path" || key == "resource_path") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + context_->setSearchPath(stringval.c_str()); + } + else if(key == "strictparsing") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<bool>(boolval)) + strictParsing_ = boolval; + } + else if(key == "description") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + description_ = stringval; + } + else if(key == "luma") + { + std::vector<float> val; + if (iter.second().Type() != YAML::NodeType::Null) + { + iter.second() >> val; + if(val.size() != 3) + { + std::ostringstream os; + os << "'luma' field must be 3 "; + os << "floats. Found '" << val.size() << "'."; + throw Exception(os.str().c_str()); + } + defaultLumaCoefs_ = val; + } + } + else if(key == "roles") + { + const YAML::Node& roles = iter.second(); + if(roles.Type() != YAML::NodeType::Map) + { + std::ostringstream os; + os << "'roles' field needs to be a (name: key) map."; + throw Exception(os.str().c_str()); + } + for (YAML::Iterator it = roles.begin(); + it != roles.end(); ++it) + { + std::string k, v; + it.first() >> k; + it.second() >> v; + roles_[pystring::lower(k)] = v; + } + } + else if(key == "displays") + { + if (iter.second().Type() != YAML::NodeType::Null) + { + iter.second() >> displays_; + } + } + else if(key == "active_displays") + { + if (iter.second().Type() != YAML::NodeType::Null) + { + iter.second() >> activeDisplays_; + } + } + else if(key == "active_views") + { + if (iter.second().Type() != YAML::NodeType::Null) + { + iter.second() >> activeViews_; + } + } + else if(key == "colorspaces") + { + const YAML::Node& colorspaces = iter.second(); + + if(colorspaces.Type() != YAML::NodeType::Sequence) + { + std::ostringstream os; + os << "'colorspaces' field needs to be a (- !<ColorSpace>) list."; + throw Exception(os.str().c_str()); + } + + for(unsigned i = 0; i < colorspaces.size(); ++i) + { + if(colorspaces[i].Tag() == "ColorSpace") + { + ColorSpaceRcPtr cs = ColorSpace::Create(); + colorspaces[i] >> cs; + colorspaces_.push_back( cs ); + } + else + { + std::ostringstream os; + os << "Unknown element found in colorspaces:"; + os << colorspaces[i].Tag() << ". Only ColorSpace(s)"; + os << " currently handled."; + LogWarning(os.str()); + } + } + } + else if(key == "looks") + { + const YAML::Node& looks = iter.second(); + + if(looks.Type() != YAML::NodeType::Sequence) + { + std::ostringstream os; + os << "'looks' field needs to be a (- !<Look>) list."; + throw Exception(os.str().c_str()); + } + + for(unsigned i = 0; i < looks.size(); ++i) + { + if(looks[i].Tag() == "Look") + { + LookRcPtr look = Look::Create(); + looks[i] >> look; + looksList_.push_back( look ); + } + else + { + std::ostringstream os; + os << "Unknown element found in looks:"; + os << looks[i].Tag() << ". Only Look(s)"; + os << " currently handled."; + LogWarning(os.str()); + } + } + } + else + { + LogUnknownKeyWarning("profile", iter.first()); + } + } + + if(filename) + { + std::string realfilename = pystring::os::path::abspath(filename); + std::string configrootdir = pystring::os::path::dirname(realfilename); + context_->setWorkingDir(configrootdir.c_str()); + } + } + catch( const std::exception & e) + { + std::ostringstream os; + os << "Error: Loading the OCIO profile "; + if(filename) os << "'" << filename << "' "; + os << "failed. " << e.what(); + throw Exception(os.str().c_str()); + } + } + + void Config::Impl::resetCacheIDs() + { + cacheids_.clear(); + cacheidnocontext_ = ""; + sanity_ = SANITY_UNKNOWN; + sanitytext_ = ""; + } + + void Config::Impl::getAllIntenalTransforms(ConstTransformVec & transformVec) const + { + // Grab all transforms from the ColorSpaces + for(unsigned int i=0; i<colorspaces_.size(); ++i) + { + if(colorspaces_[i]->getTransform(COLORSPACE_DIR_TO_REFERENCE)) + transformVec.push_back(colorspaces_[i]->getTransform(COLORSPACE_DIR_TO_REFERENCE)); + if(colorspaces_[i]->getTransform(COLORSPACE_DIR_FROM_REFERENCE)) + transformVec.push_back(colorspaces_[i]->getTransform(COLORSPACE_DIR_FROM_REFERENCE)); + } + + // Grab all transforms from the Looks + for(unsigned int i=0; i<looksList_.size(); ++i) + { + if(looksList_[i]->getTransform()) + transformVec.push_back(looksList_[i]->getTransform()); + if(looksList_[i]->getInverseTransform()) + transformVec.push_back(looksList_[i]->getInverseTransform()); + } + + } +} +OCIO_NAMESPACE_EXIT + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef OCIO_UNIT_TEST + +namespace OCIO = OCIO_NAMESPACE; +#include "UnitTest.h" + +#include <sys/stat.h> +#include "pystring/pystring.h" + +#if 0 +OIIO_ADD_TEST(Config, test_searchpath_filesystem) +{ + + OCIO::EnvMap env = OCIO::GetEnvMap(); + std::string OCIO_TEST_AREA("$OCIO_TEST_AREA"); + EnvExpand(&OCIO_TEST_AREA, &env); + + OCIO::ConfigRcPtr config = OCIO::Config::Create(); + + // basic get/set/expand + config->setSearchPath("." + ":$OCIO_TEST1" + ":/$OCIO_JOB/${OCIO_SEQ}/$OCIO_SHOT/ocio"); + + OIIO_CHECK_ASSERT(strcmp(config->getSearchPath(), + ".:$OCIO_TEST1:/$OCIO_JOB/${OCIO_SEQ}/$OCIO_SHOT/ocio") == 0); + OIIO_CHECK_ASSERT(strcmp(config->getSearchPath(true), + ".:foobar:/meatballs/cheesecake/mb-cc-001/ocio") == 0); + + // find some files + config->setSearchPath(".." + ":$OCIO_TEST1" + ":${OCIO_TEST_AREA}/test_search/one" + ":$OCIO_TEST_AREA/test_search/two"); + + // setup for search test + std::string base_dir("$OCIO_TEST_AREA/test_search/"); + EnvExpand(&base_dir, &env); + mkdir(base_dir.c_str(), 0777); + + std::string one_dir("$OCIO_TEST_AREA/test_search/one/"); + EnvExpand(&one_dir, &env); + mkdir(one_dir.c_str(), 0777); + + std::string two_dir("$OCIO_TEST_AREA/test_search/two/"); + EnvExpand(&two_dir, &env); + mkdir(two_dir.c_str(), 0777); + + std::string lut1(one_dir+"somelut1.lut"); + std::ofstream somelut1(lut1.c_str()); + somelut1.close(); + + std::string lut2(two_dir+"somelut2.lut"); + std::ofstream somelut2(lut2.c_str()); + somelut2.close(); + + std::string lut3(two_dir+"somelut3.lut"); + std::ofstream somelut3(lut3.c_str()); + somelut3.close(); + + std::string lutdotdot(OCIO_TEST_AREA+"/lutdotdot.lut"); + std::ofstream somelutdotdot(lutdotdot.c_str()); + somelutdotdot.close(); + + // basic search test + OIIO_CHECK_ASSERT(strcmp(config->findFile("somelut1.lut"), + lut1.c_str()) == 0); + OIIO_CHECK_ASSERT(strcmp(config->findFile("somelut2.lut"), + lut2.c_str()) == 0); + OIIO_CHECK_ASSERT(strcmp(config->findFile("somelut3.lut"), + lut3.c_str()) == 0); + OIIO_CHECK_ASSERT(strcmp(config->findFile("lutdotdot.lut"), + lutdotdot.c_str()) == 0); + +} +#endif + +OIIO_ADD_TEST(Config, InternalRawProfile) +{ + std::istringstream is; + is.str(OCIO::INTERNAL_RAW_PROFILE); + OIIO_CHECK_NO_THOW(OCIO::ConstConfigRcPtr config = OCIO::Config::CreateFromStream(is)); +} + +OIIO_ADD_TEST(Config, SimpleConfig) +{ + + std::string SIMPLE_PROFILE = + "ocio_profile_version: 1\n" + "resource_path: luts\n" + "strictparsing: false\n" + "luma: [0.2126, 0.7152, 0.0722]\n" + "roles:\n" + " compositing_log: lgh\n" + " default: raw\n" + " scene_linear: lnh\n" + "displays:\n" + " sRGB:\n" + " - !<View> {name: Film1D, colorspace: vd8}\n" + " - !<View> {name: Log, colorspace: lg10}\n" + " - !<View> {name: Raw, colorspace: raw}\n" + "colorspaces:\n" + " - !<ColorSpace>\n" + " name: raw\n" + " family: raw\n" + " equalitygroup: \n" + " bitdepth: 32f\n" + " description: |\n" + " A raw color space. Conversions to and from this space are no-ops.\n" + " isdata: true\n" + " allocation: uniform\n" + " - !<ColorSpace>\n" + " name: lnh\n" + " family: ln\n" + " equalitygroup: \n" + " bitdepth: 16f\n" + " description: |\n" + " The show reference space. This is a sensor referred linear\n" + " representation of the scene with primaries that correspond to\n" + " scanned film. 0.18 in this space corresponds to a properly\n" + " exposed 18% grey card.\n" + " isdata: false\n" + " allocation: lg2\n" + " - !<ColorSpace>\n" + " name: loads_of_transforms\n" + " family: vd8\n" + " equalitygroup: \n" + " bitdepth: 8ui\n" + " description: 'how many transforms can we use?'\n" + " isdata: false\n" + " allocation: uniform\n" + " to_reference: !<GroupTransform>\n" + " direction: forward\n" + " children:\n" + " - !<FileTransform>\n" + " src: diffusemult.spimtx\n" + " interpolation: unknown\n" + " - !<ColorSpaceTransform>\n" + " src: vd8\n" + " dst: lnh\n" + " - !<ExponentTransform>\n" + " value: [2.2, 2.2, 2.2, 1]\n" + " - !<MatrixTransform>\n" + " matrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]\n" + " offset: [0, 0, 0, 0]\n" + " - !<CDLTransform>\n" + " slope: [1, 1, 1]\n" + " offset: [0, 0, 0]\n" + " power: [1, 1, 1]\n" + " saturation: 1\n" + "\n"; + + std::istringstream is; + is.str(SIMPLE_PROFILE); + OCIO::ConstConfigRcPtr config; + OIIO_CHECK_NO_THOW(config = OCIO::Config::CreateFromStream(is)); +} + +OIIO_ADD_TEST(Config, Roles) +{ + + std::string SIMPLE_PROFILE = + "ocio_profile_version: 1\n" + "strictparsing: false\n" + "roles:\n" + " compositing_log: lgh\n" + " default: raw\n" + " scene_linear: lnh\n" + "colorspaces:\n" + " - !<ColorSpace>\n" + " name: raw\n" + " - !<ColorSpace>\n" + " name: lnh\n" + " - !<ColorSpace>\n" + " name: lgh\n" + "\n"; + + std::istringstream is; + is.str(SIMPLE_PROFILE); + OCIO::ConstConfigRcPtr config; + OIIO_CHECK_NO_THOW(config = OCIO::Config::CreateFromStream(is)); + + OIIO_CHECK_EQUAL(config->getNumRoles(), 3); + + OIIO_CHECK_ASSERT(config->hasRole("compositing_log") == true); + OIIO_CHECK_ASSERT(config->hasRole("cheese") == false); + OIIO_CHECK_ASSERT(config->hasRole("") == false); + + OIIO_CHECK_ASSERT(strcmp(config->getRoleName(2), "scene_linear") == 0); + OIIO_CHECK_ASSERT(strcmp(config->getRoleName(0), "compositing_log") == 0); + OIIO_CHECK_ASSERT(strcmp(config->getRoleName(1), "default") == 0); + OIIO_CHECK_ASSERT(strcmp(config->getRoleName(10), "") == 0); + OIIO_CHECK_ASSERT(strcmp(config->getRoleName(-4), "") == 0); + +} + +OIIO_ADD_TEST(Config, Serialize) +{ + + OCIO::ConfigRcPtr config = OCIO::Config::Create(); + { + OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); + cs->setName("testing"); + cs->setFamily("test"); + OCIO::FileTransformRcPtr transform1 = \ + OCIO::FileTransform::Create(); + OCIO::GroupTransformRcPtr groupTransform = OCIO::GroupTransform::Create(); + groupTransform->push_back(transform1); + cs->setTransform(groupTransform, OCIO::COLORSPACE_DIR_TO_REFERENCE); + config->addColorSpace(cs); + config->setRole( OCIO::ROLE_COMPOSITING_LOG, cs->getName() ); + } + { + OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); + cs->setName("testing2"); + cs->setFamily("test"); + OCIO::ExponentTransformRcPtr transform1 = \ + OCIO::ExponentTransform::Create(); + OCIO::GroupTransformRcPtr groupTransform = OCIO::GroupTransform::Create(); + groupTransform->push_back(transform1); + cs->setTransform(groupTransform, OCIO::COLORSPACE_DIR_TO_REFERENCE); + config->addColorSpace(cs); + config->setRole( OCIO::ROLE_COMPOSITING_LOG, cs->getName() ); + } + + // for testing + //std::ofstream outfile("/tmp/test.ocio"); + //config->serialize(outfile); + //outfile.close(); + + std::ostringstream os; + config->serialize(os); + + std::string PROFILE_OUT = + "ocio_profile_version: 1\n" + "\n" + "search_path: \"\"\n" + "strictparsing: true\n" + "luma: [0.2126, 0.7152, 0.0722]\n" + "\n" + "roles:\n" + " compositing_log: testing2\n" + "\n" + "displays:\n" + " {}\n" + "\n" + "active_displays: []\n" + "active_views: []\n" + "\n" + "colorspaces:\n" + " - !<ColorSpace>\n" + " name: testing\n" + " family: test\n" + " equalitygroup: \"\"\n" + " bitdepth: unknown\n" + " isdata: false\n" + " allocation: uniform\n" + " to_reference: !<GroupTransform>\n" + " children:\n" + " - !<FileTransform> {src: \"\", interpolation: unknown}\n" + "\n" + " - !<ColorSpace>\n" + " name: testing2\n" + " family: test\n" + " equalitygroup: \"\"\n" + " bitdepth: unknown\n" + " isdata: false\n" + " allocation: uniform\n" + " to_reference: !<GroupTransform>\n" + " children:\n" + " - !<ExponentTransform> {value: [1, 1, 1, 1]}\n"; + + std::vector<std::string> osvec; + OCIO::pystring::splitlines(os.str(), osvec); + std::vector<std::string> PROFILE_OUTvec; + OCIO::pystring::splitlines(PROFILE_OUT, PROFILE_OUTvec); + + OIIO_CHECK_EQUAL(osvec.size(), PROFILE_OUTvec.size()); + for(unsigned int i = 0; i < PROFILE_OUTvec.size(); ++i) + OIIO_CHECK_EQUAL(osvec[i], PROFILE_OUTvec[i]); +} + + +OIIO_ADD_TEST(Config, SanityCheck) +{ + { + std::string SIMPLE_PROFILE = + "ocio_profile_version: 1\n" + "colorspaces:\n" + " - !<ColorSpace>\n" + " name: raw\n" + " - !<ColorSpace>\n" + " name: raw\n" + "strictparsing: false\n" + "roles:\n" + " default: raw\n" + "displays:\n" + " sRGB:\n" + " - !<View> {name: Raw, colorspace: raw}\n" + "\n"; + + std::istringstream is; + is.str(SIMPLE_PROFILE); + OCIO::ConstConfigRcPtr config; + OIIO_CHECK_NO_THOW(config = OCIO::Config::CreateFromStream(is)); + + OIIO_CHECK_THOW(config->sanityCheck(), OCIO::Exception); + } + + { + std::string SIMPLE_PROFILE = + "ocio_profile_version: 1\n" + "colorspaces:\n" + " - !<ColorSpace>\n" + " name: raw\n" + "strictparsing: false\n" + "roles:\n" + " default: raw\n" + "displays:\n" + " sRGB:\n" + " - !<View> {name: Raw, colorspace: raw}\n" + "\n"; + + std::istringstream is; + is.str(SIMPLE_PROFILE); + OCIO::ConstConfigRcPtr config; + OIIO_CHECK_NO_THOW(config = OCIO::Config::CreateFromStream(is)); + + OIIO_CHECK_NO_THOW(config->sanityCheck()); + } +} + + +#endif // OCIO_UNIT_TEST diff --git a/src/core/Context.cpp b/src/core/Context.cpp new file mode 100644 index 0000000..25d4ea7 --- /dev/null +++ b/src/core/Context.cpp @@ -0,0 +1,364 @@ +/* +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 <map> +#include <string> +#include <iostream> +#include <sstream> + +#include <OpenColorIO/OpenColorIO.h> + +#include "HashUtils.h" +#include "Mutex.h" +#include "PathUtils.h" +#include "pystring/pystring.h" + +OCIO_NAMESPACE_ENTER +{ + +namespace +{ + typedef std::map< std::string, std::string> StringMap; + + void GetAbsoluteSearchPaths(std::vector<std::string> & searchpaths, + const std::string & pathString, + const std::string & configRootDir); +} + + class Context::Impl + { + public: + std::string searchPath_; + std::string workingDir_; + EnvMap envMap_; + + mutable std::string cacheID_; + mutable StringMap resultsCache_; + mutable Mutex resultsCacheMutex_; + + Impl() + { + } + + ~Impl() + { + + } + + Impl& operator= (const Impl & rhs) + { + AutoMutex lock1(resultsCacheMutex_); + AutoMutex lock2(rhs.resultsCacheMutex_); + + searchPath_ = rhs.searchPath_; + workingDir_ = rhs.workingDir_; + envMap_ = rhs.envMap_; + + resultsCache_ = rhs.resultsCache_; + cacheID_ = rhs.cacheID_; + + return *this; + } + }; + + + /////////////////////////////////////////////////////////////////////////// + + ContextRcPtr Context::Create() + { + return ContextRcPtr(new Context(), &deleter); + } + + void Context::deleter(Context* c) + { + delete c; + } + + /////////////////////////////////////////////////////////////////////////// + + + + Context::Context() + : m_impl(new Context::Impl) + { + } + + Context::~Context() + { + delete m_impl; + m_impl = NULL; + } + + ContextRcPtr Context::createEditableCopy() const + { + ContextRcPtr context = Context::Create(); + *context->m_impl = *getImpl(); + return context; + } + + const char * Context::getCacheID() const + { + AutoMutex lock(getImpl()->resultsCacheMutex_); + + if(getImpl()->cacheID_.empty()) + { + std::ostringstream cacheid; + cacheid << "Search Path " << getImpl()->searchPath_ << " "; + cacheid << "Working Dir " << getImpl()->workingDir_ << " "; + + for (EnvMap::const_iterator iter = getImpl()->envMap_.begin(), + end = getImpl()->envMap_.end(); + iter != end; ++iter) + { + cacheid << iter->first << "=" << iter->second << " "; + } + + std::string fullstr = cacheid.str(); + getImpl()->cacheID_ = CacheIDHash(fullstr.c_str(), (int)fullstr.size()); + } + + return getImpl()->cacheID_.c_str(); + } + + void Context::setSearchPath(const char * path) + { + AutoMutex lock(getImpl()->resultsCacheMutex_); + + getImpl()->searchPath_ = path; + getImpl()->resultsCache_.clear(); + getImpl()->cacheID_ = ""; + } + + const char * Context::getSearchPath() const + { + return getImpl()->searchPath_.c_str(); + } + + void Context::setWorkingDir(const char * dirname) + { + AutoMutex lock(getImpl()->resultsCacheMutex_); + + getImpl()->workingDir_ = dirname; + getImpl()->resultsCache_.clear(); + getImpl()->cacheID_ = ""; + } + + const char * Context::getWorkingDir() const + { + return getImpl()->workingDir_.c_str(); + } + + void Context::loadEnvironment() + { + LoadEnvironment(getImpl()->envMap_); + } + + void Context::setStringVar(const char * name, const char * value) + { + if(!name) return; + + AutoMutex lock(getImpl()->resultsCacheMutex_); + getImpl()->resultsCache_.clear(); + getImpl()->cacheID_ = ""; + + // Set the value if specified + if(value) + { + getImpl()->envMap_[name] = value; + } + // If a null value is specified, erase it + else + { + EnvMap::iterator iter = getImpl()->envMap_.find(name); + if(iter != getImpl()->envMap_.end()) + { + getImpl()->envMap_.erase(iter); + } + } + } + + const char * Context::getStringVar(const char * name) const + { + if(!name) return ""; + + EnvMap::const_iterator iter = getImpl()->envMap_.find(name); + if(iter != getImpl()->envMap_.end()) + { + return iter->second.c_str(); + } + + return ""; + } + + int Context::getNumStringVars() const + { + return static_cast<int>(getImpl()->envMap_.size()); + } + + const char * Context::getStringVarNameByIndex(int index) const + { + if(index < 0 || index >= static_cast<int>(getImpl()->envMap_.size())) + return ""; + + EnvMap::const_iterator iter = getImpl()->envMap_.begin(); + for(int count = 0; count<index; ++count) ++iter; + + return iter->first.c_str(); + } + + const char * Context::resolveStringVar(const char * val) const + { + AutoMutex lock(getImpl()->resultsCacheMutex_); + + if(!val || !*val) + { + return ""; + } + + StringMap::const_iterator iter = getImpl()->resultsCache_.find(val); + if(iter != getImpl()->resultsCache_.end()) + { + return iter->second.c_str(); + } + + + std::string resolvedval = EnvExpand(val, getImpl()->envMap_); + + getImpl()->resultsCache_[val] = resolvedval; + return getImpl()->resultsCache_[val].c_str(); + } + + + + const char * Context::resolveFileLocation(const char * filename) const + { + AutoMutex lock(getImpl()->resultsCacheMutex_); + + if(!filename || !*filename) + { + return ""; + } + + StringMap::const_iterator iter = getImpl()->resultsCache_.find(filename); + if(iter != getImpl()->resultsCache_.end()) + { + return iter->second.c_str(); + } + + // Load an absolute file reference + if(pystring::os::path::isabs(filename)) + { + std::string expandedfullpath = EnvExpand(filename, getImpl()->envMap_); + if(FileExists(expandedfullpath)) + { + getImpl()->resultsCache_[filename] = expandedfullpath; + return getImpl()->resultsCache_[filename].c_str(); + } + + std::ostringstream errortext; + errortext << "The specified absolute file reference "; + errortext << "'" << expandedfullpath << "' could not be located. "; + throw Exception(errortext.str().c_str()); + } + + // Load a relative file reference + // Prep the search path vector + // TODO: Cache this prepped vector? + std::vector<std::string> searchpaths; + GetAbsoluteSearchPaths(searchpaths, + getImpl()->searchPath_, + getImpl()->workingDir_); + + // Loop over each path, and try to find the file + std::ostringstream errortext; + errortext << "The specified file reference "; + errortext << " '" << filename << "' could not be located. "; + errortext << "The following attempts were made: "; + + for (unsigned int i = 0; i < searchpaths.size(); ++i) + { + // Make an attempt to find the lut in one of the search paths + std::string fullpath = pystring::os::path::join(searchpaths[i], filename); + std::string expandedfullpath = EnvExpand(fullpath, getImpl()->envMap_); + if(FileExists(expandedfullpath)) + { + getImpl()->resultsCache_[filename] = expandedfullpath; + return getImpl()->resultsCache_[filename].c_str(); + } + if(i!=0) errortext << " : "; + errortext << expandedfullpath; + } + + throw ExceptionMissingFile(errortext.str().c_str()); + } + + std::ostream& operator<< (std::ostream& os, const Context& context) + { + os << "Context:\n"; + for(int i=0; i<context.getNumStringVars(); ++i) + { + const char * key = context.getStringVarNameByIndex(i); + os << key << "=" << context.getStringVar(key) << "\n"; + } + return os; + } + + +namespace +{ + void GetAbsoluteSearchPaths(std::vector<std::string> & searchpaths, + const std::string & pathString, + const std::string & workingDir) + { + if(pathString.empty()) + { + searchpaths.push_back(workingDir); + return; + } + + std::vector<std::string> parts; + pystring::split(pathString, parts, ":"); + + for (unsigned int i = 0; i < parts.size(); ++i) + { + // Remove trailing "/", and spaces + std::string dirname = pystring::rstrip(pystring::strip(parts[i]), "/"); + + if(!pystring::os::path::isabs(dirname)) + { + dirname = pystring::os::path::join(workingDir, dirname); + } + + searchpaths.push_back(pystring::os::path::normpath(dirname)); + } + } +} + + +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/DisplayTransform.cpp b/src/core/DisplayTransform.cpp new file mode 100644 index 0000000..35216db --- /dev/null +++ b/src/core/DisplayTransform.cpp @@ -0,0 +1,410 @@ +/* +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 "OpBuilders.h" + +#include <cmath> +#include <cstring> +#include <iterator> + +OCIO_NAMESPACE_ENTER +{ + DisplayTransformRcPtr DisplayTransform::Create() + { + return DisplayTransformRcPtr(new DisplayTransform(), &deleter); + } + + void DisplayTransform::deleter(DisplayTransform* t) + { + delete t; + } + + class DisplayTransform::Impl + { + public: + TransformDirection dir_; + std::string inputColorSpaceName_; + TransformRcPtr linearCC_; + TransformRcPtr colorTimingCC_; + TransformRcPtr channelView_; + std::string display_; + std::string view_; + TransformRcPtr displayCC_; + + std::string looksOverride_; + bool looksOverrideEnabled_; + + Impl() : + dir_(TRANSFORM_DIR_FORWARD), + looksOverrideEnabled_(false) + { } + + ~Impl() + { } + + Impl& operator= (const Impl & rhs) + { + dir_ = rhs.dir_; + inputColorSpaceName_ = rhs.inputColorSpaceName_; + + linearCC_ = rhs.linearCC_; + if(linearCC_) linearCC_ = linearCC_->createEditableCopy(); + + colorTimingCC_ = rhs.colorTimingCC_; + if(colorTimingCC_) colorTimingCC_ = colorTimingCC_->createEditableCopy(); + + channelView_ = rhs.channelView_; + if(channelView_) channelView_ = channelView_->createEditableCopy(); + + display_ = rhs.display_; + view_ = rhs.view_; + + displayCC_ = rhs.displayCC_; + if(displayCC_) displayCC_ = displayCC_->createEditableCopy(); + + looksOverride_ = rhs.looksOverride_; + looksOverrideEnabled_ = rhs.looksOverrideEnabled_; + + return *this; + } + }; + + + /////////////////////////////////////////////////////////////////////////// + + + + DisplayTransform::DisplayTransform() + : m_impl(new DisplayTransform::Impl) + { + } + + TransformRcPtr DisplayTransform::createEditableCopy() const + { + DisplayTransformRcPtr transform = DisplayTransform::Create(); + *(transform->m_impl) = *m_impl; + return transform; + } + + DisplayTransform::~DisplayTransform() + { + delete m_impl; + m_impl = NULL; + } + + DisplayTransform& DisplayTransform::operator= (const DisplayTransform & rhs) + { + *m_impl = *rhs.m_impl; + return *this; + } + + TransformDirection DisplayTransform::getDirection() const + { + return getImpl()->dir_; + } + + void DisplayTransform::setDirection(TransformDirection dir) + { + getImpl()->dir_ = dir; + } + + void DisplayTransform::setInputColorSpaceName(const char * name) + { + getImpl()->inputColorSpaceName_ = name; + } + + const char * DisplayTransform::getInputColorSpaceName() const + { + return getImpl()->inputColorSpaceName_.c_str(); + } + + void DisplayTransform::setLinearCC(const ConstTransformRcPtr & cc) + { + getImpl()->linearCC_ = cc->createEditableCopy(); + } + + ConstTransformRcPtr DisplayTransform::getLinearCC() const + { + return getImpl()->linearCC_; + } + + void DisplayTransform::setColorTimingCC(const ConstTransformRcPtr & cc) + { + getImpl()->colorTimingCC_ = cc->createEditableCopy(); + } + + ConstTransformRcPtr DisplayTransform::getColorTimingCC() const + { + return getImpl()->colorTimingCC_; + } + + void DisplayTransform::setChannelView(const ConstTransformRcPtr & transform) + { + getImpl()->channelView_ = transform->createEditableCopy(); + } + + ConstTransformRcPtr DisplayTransform::getChannelView() const + { + return getImpl()->channelView_; + } + + void DisplayTransform::setDisplay(const char * display) + { + getImpl()->display_ = display; + } + + const char * DisplayTransform::getDisplay() const + { + return getImpl()->display_.c_str(); + } + + void DisplayTransform::setView(const char * view) + { + getImpl()->view_ = view; + } + + const char * DisplayTransform::getView() const + { + return getImpl()->view_.c_str(); + } + + void DisplayTransform::setDisplayCC(const ConstTransformRcPtr & cc) + { + getImpl()->displayCC_ = cc->createEditableCopy(); + } + + ConstTransformRcPtr DisplayTransform::getDisplayCC() const + { + return getImpl()->displayCC_; + } + + void DisplayTransform::setLooksOverride(const char * looks) + { + getImpl()->looksOverride_ = looks; + } + + const char * DisplayTransform::getLooksOverride() const + { + return getImpl()->looksOverride_.c_str(); + } + + void DisplayTransform::setLooksOverrideEnabled(bool enabled) + { + getImpl()->looksOverrideEnabled_ = enabled; + } + + bool DisplayTransform::getLooksOverrideEnabled() const + { + return getImpl()->looksOverrideEnabled_; + } + + std::ostream& operator<< (std::ostream& os, const DisplayTransform& t) + { + os << "<DisplayTransform "; + os << "direction=" << TransformDirectionToString(t.getDirection()) << ", "; + os << "inputColorSpace=" << t.getInputColorSpaceName() << ", "; + os << "display=" << t.getDisplay() << ", "; + os << "view=" << t.getView() << ", "; + os << ">\n"; + return os; + } + + + /////////////////////////////////////////////////////////////////////////// + + + + void BuildDisplayOps(OpRcPtrVec & ops, + const Config & config, + const ConstContextRcPtr & context, + const DisplayTransform & displayTransform, + TransformDirection dir) + { + TransformDirection combinedDir = CombineTransformDirections(dir, + displayTransform.getDirection()); + if(combinedDir != TRANSFORM_DIR_FORWARD) + { + std::ostringstream os; + os << "DisplayTransform can only be applied in the forward direction."; + throw Exception(os.str().c_str()); + } + + std::string inputColorSpaceName = displayTransform.getInputColorSpaceName(); + ConstColorSpaceRcPtr inputColorSpace = config.getColorSpace(inputColorSpaceName.c_str()); + if(!inputColorSpace) + { + std::ostringstream os; + os << "DisplayTransform error."; + if(inputColorSpaceName.empty()) os << " InputColorSpaceName is unspecified."; + else os << " Cannot find inputColorSpace, named '" << inputColorSpaceName << "'."; + throw Exception(os.str().c_str()); + } + + std::string display = displayTransform.getDisplay(); + std::string view = displayTransform.getView(); + + std::string displayColorSpaceName = config.getDisplayColorSpaceName(display.c_str(), view.c_str()); + ConstColorSpaceRcPtr displayColorspace = config.getColorSpace(displayColorSpaceName.c_str()); + if(!displayColorspace) + { + std::ostringstream os; + os << "DisplayTransform error."; + os << " Cannot find display colorspace, '" << displayColorSpaceName << "'."; + throw Exception(os.str().c_str()); + } + + bool skipColorSpaceConversions = (inputColorSpace->isData() || displayColorspace->isData()); + + // If we're viewing alpha, also skip all color space conversions. + // If the user does uses a different transform for the channel view, + // in place of a simple matrix, they run the risk that when viewing alpha + // the colorspace transforms will not be skipped. (I.e., filmlook will be applied + // to alpha.) If this ever becomes an issue, additional engineering will be + // added at that time. + + ConstMatrixTransformRcPtr typedChannelView = DynamicPtrCast<const MatrixTransform>( + displayTransform.getChannelView()); + if(typedChannelView) + { + float matrix44[16]; + typedChannelView->getValue(matrix44, 0x0); + + if((matrix44[3]>0.0f) || (matrix44[7]>0.0f) || (matrix44[11]>0.0f)) + { + skipColorSpaceConversions = true; + } + } + + + + ConstColorSpaceRcPtr currentColorSpace = inputColorSpace; + + + + // Apply a transform in ROLE_SCENE_LINEAR + ConstTransformRcPtr linearCC = displayTransform.getLinearCC(); + if(linearCC) + { + // Put the new ops into a temp array, to see if it's a no-op + // If it is a no-op, dont bother doing the colorspace conversion. + OpRcPtrVec tmpOps; + BuildOps(tmpOps, config, context, linearCC, TRANSFORM_DIR_FORWARD); + + if(!IsOpVecNoOp(tmpOps)) + { + ConstColorSpaceRcPtr targetColorSpace = config.getColorSpace(ROLE_SCENE_LINEAR); + + if(!skipColorSpaceConversions) + { + BuildColorSpaceOps(ops, config, context, + currentColorSpace, + targetColorSpace); + currentColorSpace = targetColorSpace; + } + + std::copy(tmpOps.begin(), tmpOps.end(), std::back_inserter(ops)); + } + } + + + // Apply a color correction, in ROLE_COLOR_TIMING + ConstTransformRcPtr colorTimingCC = displayTransform.getColorTimingCC(); + if(colorTimingCC) + { + // Put the new ops into a temp array, to see if it's a no-op + // If it is a no-op, dont bother doing the colorspace conversion. + OpRcPtrVec tmpOps; + BuildOps(tmpOps, config, context, colorTimingCC, TRANSFORM_DIR_FORWARD); + + if(!IsOpVecNoOp(tmpOps)) + { + ConstColorSpaceRcPtr targetColorSpace = config.getColorSpace(ROLE_COLOR_TIMING); + + if(!skipColorSpaceConversions) + { + BuildColorSpaceOps(ops, config, context, + currentColorSpace, + targetColorSpace); + currentColorSpace = targetColorSpace; + } + + std::copy(tmpOps.begin(), tmpOps.end(), std::back_inserter(ops)); + } + } + + // Apply a look, if specified + LookParseResult looks; + if(displayTransform.getLooksOverrideEnabled()) + { + looks.parse(displayTransform.getLooksOverride()); + } + else if(!skipColorSpaceConversions) + { + looks.parse(config.getDisplayLooks(display.c_str(), view.c_str())); + } + + if(!looks.empty()) + { + BuildLookOps(ops, + currentColorSpace, + skipColorSpaceConversions, + config, + context, + looks); + } + + // Apply a channel view + ConstTransformRcPtr channelView = displayTransform.getChannelView(); + if(channelView) + { + BuildOps(ops, config, context, channelView, TRANSFORM_DIR_FORWARD); + } + + + // Apply the conversion to the display color space + if(!skipColorSpaceConversions) + { + BuildColorSpaceOps(ops, config, context, + currentColorSpace, + displayColorspace); + currentColorSpace = displayColorspace; + } + + + // Apply a display cc + ConstTransformRcPtr displayCC = displayTransform.getDisplayCC(); + if(displayCC) + { + BuildOps(ops, config, context, displayCC, TRANSFORM_DIR_FORWARD); + } + + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/Exception.cpp b/src/core/Exception.cpp new file mode 100644 index 0000000..b3d514c --- /dev/null +++ b/src/core/Exception.cpp @@ -0,0 +1,74 @@ +/* +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> + +OCIO_NAMESPACE_ENTER +{ + + Exception::Exception(const char * msg) throw() + : std::exception(), + msg_(msg) + {} + + Exception::Exception(const Exception& e) throw() + : std::exception(), + msg_(e.msg_) + {} + + //*** operator= + Exception& Exception::operator=(const Exception& e) throw() + { + msg_ = e.msg_; + return *this; + } + + //*** ~Exception + Exception::~Exception() throw() + { + } + + //*** what + const char* Exception::what() const throw() + { + return msg_.c_str(); + } + + + + + ExceptionMissingFile::ExceptionMissingFile(const char * msg) throw() + : Exception(msg) + {} + + ExceptionMissingFile::ExceptionMissingFile(const ExceptionMissingFile& e) throw() + : Exception(e) + {} + +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/ExponentOps.cpp b/src/core/ExponentOps.cpp new file mode 100644 index 0000000..56e34d4 --- /dev/null +++ b/src/core/ExponentOps.cpp @@ -0,0 +1,372 @@ +/* +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 <cmath> +#include <cstring> +#include <sstream> + +#include <OpenColorIO/OpenColorIO.h> + +#include "ExponentOps.h" +#include "GpuShaderUtils.h" +#include "MathUtils.h" + +OCIO_NAMESPACE_ENTER +{ + namespace + { + void ApplyClampExponent(float* rgbaBuffer, long numPixels, + const float* exp4) + { + for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex) + { + rgbaBuffer[0] = powf( std::max(0.0f, rgbaBuffer[0]), exp4[0]); + rgbaBuffer[1] = powf( std::max(0.0f, rgbaBuffer[1]), exp4[1]); + rgbaBuffer[2] = powf( std::max(0.0f, rgbaBuffer[2]), exp4[2]); + rgbaBuffer[3] = powf( std::max(0.0f, rgbaBuffer[3]), exp4[3]); + + rgbaBuffer += 4; + } + } + + const int FLOAT_DECIMALS = 7; + } + + + namespace + { + class ExponentOp : public Op + { + public: + ExponentOp(const float * exp4, + TransformDirection direction); + virtual ~ExponentOp(); + + virtual OpRcPtr clone() const; + + virtual std::string getInfo() const; + virtual std::string getCacheID() const; + + virtual bool isNoOp() const; + virtual bool isSameType(const OpRcPtr & op) const; + virtual bool isInverse(const OpRcPtr & op) const; + + virtual bool canCombineWith(const OpRcPtr & op) const; + virtual void combineWith(OpRcPtrVec & ops, const OpRcPtr & secondOp) const; + + virtual bool hasChannelCrosstalk() const; + virtual void finalize(); + virtual void apply(float* rgbaBuffer, long numPixels) const; + + virtual bool supportsGpuShader() const; + virtual void writeGpuShader(std::ostream & shader, + const std::string & pixelName, + const GpuShaderDesc & shaderDesc) const; + private: + float m_exp4[4]; + + // Set in finalize + std::string m_cacheID; + }; + + typedef OCIO_SHARED_PTR<ExponentOp> ExponentOpRcPtr; + + + ExponentOp::ExponentOp(const float * exp4, + TransformDirection direction): + Op() + { + if(direction == TRANSFORM_DIR_UNKNOWN) + { + throw Exception("Cannot create ExponentOp with unspecified transform direction."); + } + + if(direction == TRANSFORM_DIR_INVERSE) + { + for(int i=0; i<4; ++i) + { + if(!IsScalarEqualToZero(exp4[i])) + { + m_exp4[i] = 1.0f / exp4[i]; + } + else + { + throw Exception("Cannot apply ExponentOp op, Cannot apply 0.0 exponent in the inverse."); + } + } + } + else + { + memcpy(m_exp4, exp4, 4*sizeof(float)); + } + } + + OpRcPtr ExponentOp::clone() const + { + OpRcPtr op = OpRcPtr(new ExponentOp(m_exp4, TRANSFORM_DIR_FORWARD)); + return op; + } + + ExponentOp::~ExponentOp() + { } + + std::string ExponentOp::getInfo() const + { + return "<ExponentOp>"; + } + + std::string ExponentOp::getCacheID() const + { + return m_cacheID; + } + + bool ExponentOp::isNoOp() const + { + return IsVecEqualToOne(m_exp4, 4); + } + + bool ExponentOp::isSameType(const OpRcPtr & op) const + { + ExponentOpRcPtr typedRcPtr = DynamicPtrCast<ExponentOp>(op); + if(!typedRcPtr) return false; + return true; + } + + bool ExponentOp::isInverse(const OpRcPtr & op) const + { + ExponentOpRcPtr typedRcPtr = DynamicPtrCast<ExponentOp>(op); + if(!typedRcPtr) return false; + + float combined[4] = { m_exp4[0]*typedRcPtr->m_exp4[0], + m_exp4[1]*typedRcPtr->m_exp4[1], + m_exp4[2]*typedRcPtr->m_exp4[2], + m_exp4[3]*typedRcPtr->m_exp4[3] }; + + return IsVecEqualToOne(combined, 4); + } + + bool ExponentOp::canCombineWith(const OpRcPtr & op) const + { + return isSameType(op); + } + + + void ExponentOp::combineWith(OpRcPtrVec & ops, const OpRcPtr & secondOp) const + { + ExponentOpRcPtr typedRcPtr = DynamicPtrCast<ExponentOp>(secondOp); + if(!typedRcPtr) + { + std::ostringstream os; + os << "ExponentOp can only be combined with other "; + os << "ExponentOps. secondOp:" << secondOp->getInfo(); + throw Exception(os.str().c_str()); + } + + float combined[4] = { m_exp4[0]*typedRcPtr->m_exp4[0], + m_exp4[1]*typedRcPtr->m_exp4[1], + m_exp4[2]*typedRcPtr->m_exp4[2], + m_exp4[3]*typedRcPtr->m_exp4[3] }; + + CreateExponentOp(ops, combined, TRANSFORM_DIR_FORWARD); + } + + bool ExponentOp::hasChannelCrosstalk() const + { + return false; + } + + void ExponentOp::finalize() + { + // Create the cacheID + std::ostringstream cacheIDStream; + cacheIDStream << "<ExponentOp "; + cacheIDStream.precision(FLOAT_DECIMALS); + for(int i=0; i<4; ++i) + { + cacheIDStream << m_exp4[i] << " "; + } + cacheIDStream << ">"; + m_cacheID = cacheIDStream.str(); + } + + void ExponentOp::apply(float* rgbaBuffer, long numPixels) const + { + if(!rgbaBuffer) return; + + ApplyClampExponent(rgbaBuffer, numPixels, m_exp4); + } + + bool ExponentOp::supportsGpuShader() const + { + return true; + } + + void ExponentOp::writeGpuShader(std::ostream & shader, + const std::string & pixelName, + const GpuShaderDesc & shaderDesc) const + { + GpuLanguage lang = shaderDesc.getLanguage(); + float zerovec[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + shader << pixelName << " = pow("; + shader << "max(" << pixelName << ", " << GpuTextHalf4(zerovec, lang) << ")"; + shader << ", " << GpuTextHalf4(m_exp4, lang) << ");\n"; + } + + } // Anon namespace + + + + void CreateExponentOp(OpRcPtrVec & ops, + const float * exp4, + TransformDirection direction) + { + bool expIsIdentity = IsVecEqualToOne(exp4, 4); + if(expIsIdentity) return; + + ops.push_back( ExponentOpRcPtr(new ExponentOp(exp4, direction)) ); + } +} +OCIO_NAMESPACE_EXIT + + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef OCIO_UNIT_TEST + +#include "UnitTest.h" + +OCIO_NAMESPACE_USING + +OIIO_ADD_TEST(ExponentOps, Value) +{ + float exp1[4] = { 1.2f, 1.3f, 1.4f, 1.5f }; + + OpRcPtrVec ops; + CreateExponentOp(ops, exp1, TRANSFORM_DIR_FORWARD); + CreateExponentOp(ops, exp1, TRANSFORM_DIR_INVERSE); + OIIO_CHECK_EQUAL(ops.size(), 2); + + for(unsigned int i=0; i<ops.size(); ++i) + { + ops[i]->finalize(); + } + + float error = 1e-6f; + + const float source[] = { 0.5f, 0.5f, 0.5f, 0.5f, }; + + const float result1[] = { 0.43527528164806206f, 0.40612619817811774f, + 0.37892914162759955f, 0.35355339059327379f }; + + float tmp[4]; + memcpy(tmp, source, 4*sizeof(float)); + ops[0]->apply(tmp, 1); + + for(unsigned int i=0; i<4; ++i) + { + OIIO_CHECK_CLOSE(tmp[i], result1[i], error); + } + + ops[1]->apply(tmp, 1); + for(unsigned int i=0; i<4; ++i) + { + OIIO_CHECK_CLOSE(tmp[i], source[i], error); + } +} + +OIIO_ADD_TEST(ExponentOps, Inverse) +{ + float exp1[4] = { 2.0f, 1.02345f, 5.651321f, 0.12345678910f }; + float exp2[4] = { 2.0f, 2.0f, 2.0f, 2.0f }; + + OpRcPtrVec ops; + + CreateExponentOp(ops, exp1, TRANSFORM_DIR_FORWARD); + CreateExponentOp(ops, exp1, TRANSFORM_DIR_INVERSE); + CreateExponentOp(ops, exp2, TRANSFORM_DIR_FORWARD); + CreateExponentOp(ops, exp2, TRANSFORM_DIR_INVERSE); + + OIIO_CHECK_EQUAL(ops.size(), 4); + + OIIO_CHECK_ASSERT(ops[0]->isSameType(ops[1])); + OIIO_CHECK_ASSERT(ops[0]->isSameType(ops[2])); + OIIO_CHECK_ASSERT(ops[0]->isSameType(ops[3]->clone())); + + OIIO_CHECK_EQUAL(ops[0]->isInverse(ops[0]), false); + OIIO_CHECK_EQUAL(ops[0]->isInverse(ops[1]), true); + OIIO_CHECK_EQUAL(ops[1]->isInverse(ops[0]), true); + OIIO_CHECK_EQUAL(ops[0]->isInverse(ops[2]), false); + OIIO_CHECK_EQUAL(ops[0]->isInverse(ops[3]), false); + OIIO_CHECK_EQUAL(ops[3]->isInverse(ops[0]), false); + OIIO_CHECK_EQUAL(ops[2]->isInverse(ops[3]), true); + OIIO_CHECK_EQUAL(ops[3]->isInverse(ops[2]), true); + OIIO_CHECK_EQUAL(ops[3]->isInverse(ops[3]), false); +} + +OIIO_ADD_TEST(ExponentOps, Combining) +{ + float exp1[4] = { 2.0f, 2.0f, 2.0f, 1.0f }; + float exp2[4] = { 1.2f, 1.2f, 1.2f, 1.0f }; + + OpRcPtrVec ops; + CreateExponentOp(ops, exp1, TRANSFORM_DIR_FORWARD); + CreateExponentOp(ops, exp2, TRANSFORM_DIR_FORWARD); + OIIO_CHECK_EQUAL(ops.size(), 2); + + float error = 1e-6f; + const float source[] = { 0.5f, 0.5f, 0.5f, 0.5f, }; + const float result[] = { 0.18946457081379978f, 0.18946457081379978f, + 0.18946457081379978f, 0.5f }; + + float tmp[4]; + memcpy(tmp, source, 4*sizeof(float)); + ops[0]->apply(tmp, 1); + ops[1]->apply(tmp, 1); + + for(unsigned int i=0; i<4; ++i) + { + OIIO_CHECK_CLOSE(tmp[i], result[i], error); + } + + + OpRcPtrVec combined; + ops[0]->combineWith(combined, ops[1]); + OIIO_CHECK_EQUAL(combined.size(), 1); + + float tmp2[4]; + memcpy(tmp2, source, 4*sizeof(float)); + combined[0]->apply(tmp2, 1); + + for(unsigned int i=0; i<4; ++i) + { + OIIO_CHECK_CLOSE(tmp2[i], result[i], error); + } +} + +#endif // OCIO_UNIT_TEST diff --git a/src/core/ExponentOps.h b/src/core/ExponentOps.h new file mode 100644 index 0000000..95dd1f6 --- /dev/null +++ b/src/core/ExponentOps.h @@ -0,0 +1,50 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_EXPONENTOP_H +#define INCLUDED_OCIO_EXPONENTOP_H + +#include <OpenColorIO/OpenColorIO.h> + +#include "Op.h" + +#include <vector> + +OCIO_NAMESPACE_ENTER +{ + // If the exponent is 1.0, this will return without clamping + // Otherwise, will be clamped between [0.0, inf] + + void CreateExponentOp(OpRcPtrVec & ops, + const float * exponent4, + TransformDirection direction); +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/ExponentTransform.cpp b/src/core/ExponentTransform.cpp new file mode 100644 index 0000000..7bc180f --- /dev/null +++ b/src/core/ExponentTransform.cpp @@ -0,0 +1,151 @@ +/* +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 <cstring> + +#include <OpenColorIO/OpenColorIO.h> + +#include "ExponentOps.h" +#include "OpBuilders.h" + + +OCIO_NAMESPACE_ENTER +{ + ExponentTransformRcPtr ExponentTransform::Create() + { + return ExponentTransformRcPtr(new ExponentTransform(), &deleter); + } + + void ExponentTransform::deleter(ExponentTransform* t) + { + delete t; + } + + + class ExponentTransform::Impl + { + public: + TransformDirection dir_; + float value_[4]; + + Impl() : + dir_(TRANSFORM_DIR_FORWARD) + { + for(int i=0; i<4; ++i) + { + value_[i] = 1.0f; + } + } + + ~Impl() + { } + + Impl& operator= (const Impl & rhs) + { + dir_ = rhs.dir_; + memcpy(value_, rhs.value_, 4*sizeof(float)); + return *this; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + + + ExponentTransform::ExponentTransform() + : m_impl(new ExponentTransform::Impl) + { + } + + TransformRcPtr ExponentTransform::createEditableCopy() const + { + ExponentTransformRcPtr transform = ExponentTransform::Create(); + *(transform->m_impl) = *m_impl; + return transform; + } + + ExponentTransform::~ExponentTransform() + { + delete m_impl; + m_impl = NULL; + } + + ExponentTransform& ExponentTransform::operator= (const ExponentTransform & rhs) + { + *m_impl = *rhs.m_impl; + return *this; + } + + TransformDirection ExponentTransform::getDirection() const + { + return getImpl()->dir_; + } + + void ExponentTransform::setDirection(TransformDirection dir) + { + getImpl()->dir_ = dir; + } + + void ExponentTransform::setValue(const float * vec4) + { + if(vec4) memcpy(getImpl()->value_, vec4, 4*sizeof(float)); + } + + void ExponentTransform::getValue(float * vec4) const + { + if(vec4) memcpy(vec4, getImpl()->value_, 4*sizeof(float)); + } + + std::ostream& operator<< (std::ostream& os, const ExponentTransform& t) + { + os << "<ExponentTransform "; + os << "direction=" << TransformDirectionToString(t.getDirection()) << ", "; + os << ">\n"; + return os; + } + + + /////////////////////////////////////////////////////////////////////////////////////////////////////// + + void BuildExponentOps(OpRcPtrVec & ops, + const Config& /*config*/, + const ExponentTransform & transform, + TransformDirection dir) + { + TransformDirection combinedDir = CombineTransformDirections(dir, + transform.getDirection()); + + float vec4[4]; + transform.getValue(vec4); + + CreateExponentOp(ops, + vec4, + combinedDir); + } +} +OCIO_NAMESPACE_EXIT 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 diff --git a/src/core/FileFormatCC.cpp b/src/core/FileFormatCC.cpp new file mode 100644 index 0000000..ade09a6 --- /dev/null +++ b/src/core/FileFormatCC.cpp @@ -0,0 +1,151 @@ +/* +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 "OpBuilders.h" + +OCIO_NAMESPACE_ENTER +{ + //////////////////////////////////////////////////////////////// + + namespace + { + class LocalCachedFile : public CachedFile + { + public: + LocalCachedFile () + { + transform = CDLTransform::Create(); + }; + + ~LocalCachedFile() {}; + + CDLTransformRcPtr transform; + }; + + 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 = "ColorCorrection"; + info.extension = "cc"; + info.capabilities = FORMAT_CAPABILITY_READ; + formatInfoVec.push_back(info); + } + + // Try and load the format + // Raise an exception if it can't be loaded. + + CachedFileRcPtr LocalFileFormat::Read(std::istream & istream) const + { + std::ostringstream rawdata; + rawdata << istream.rdbuf(); + + LocalCachedFileRcPtr cachedFile = LocalCachedFileRcPtr(new LocalCachedFile()); + + try + { + cachedFile->transform->setXML(rawdata.str().c_str()); + } + catch(Exception & e) + { + std::ostringstream os; + os << "Error parsing .cc file. "; + os << "Does not appear to contain a valid ASC CDL XML:"; + os << e.what(); + throw Exception(os.str().c_str()); + } + + 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 .cc 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()); + } + + BuildCDLOps(ops, + config, + *cachedFile->transform, + newDir); + } + } + + FileFormat * CreateFileFormatCC() + { + return new LocalFileFormat(); + } +} +OCIO_NAMESPACE_EXIT + diff --git a/src/core/FileFormatCCC.cpp b/src/core/FileFormatCCC.cpp new file mode 100644 index 0000000..ec43803 --- /dev/null +++ b/src/core/FileFormatCCC.cpp @@ -0,0 +1,195 @@ +/* +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 <map> +#include <tinyxml.h> + +#include <OpenColorIO/OpenColorIO.h> + +#include "CDLTransform.h" +#include "FileTransform.h" +#include "OpBuilders.h" +#include "pystring/pystring.h" + +OCIO_NAMESPACE_ENTER +{ + //////////////////////////////////////////////////////////////// + + namespace + { + typedef std::map<std::string,CDLTransformRcPtr> CDLMap; + + class LocalCachedFile : public CachedFile + { + public: + LocalCachedFile () {}; + + ~LocalCachedFile() {}; + + CDLMap transforms; + }; + + typedef OCIO_SHARED_PTR<LocalCachedFile> LocalCachedFileRcPtr; + typedef OCIO_SHARED_PTR<TiXmlDocument> TiXmlDocumentRcPtr; + + + + 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 = "ColorCorrectionCollection"; + info.extension = "ccc"; + info.capabilities = FORMAT_CAPABILITY_READ; + formatInfoVec.push_back(info); + } + + // Try and load the format + // Raise an exception if it can't be loaded. + + CachedFileRcPtr LocalFileFormat::Read(std::istream & istream) const + { + std::ostringstream rawdata; + rawdata << istream.rdbuf(); + + LocalCachedFileRcPtr cachedFile = LocalCachedFileRcPtr(new LocalCachedFile()); + + TiXmlDocumentRcPtr doc = TiXmlDocumentRcPtr(new TiXmlDocument()); + doc->Parse(rawdata.str().c_str()); + + if(doc->Error()) + { + std::ostringstream os; + os << "XML Parse Error. "; + os << doc->ErrorDesc() << " (line "; + os << doc->ErrorRow() << ", character "; + os << doc->ErrorCol() << ")"; + throw Exception(os.str().c_str()); + } + + TiXmlElement* rootElement = doc->RootElement(); + if(!rootElement) + { + std::ostringstream os; + os << "Error loading xml. Null root element."; + throw Exception(os.str().c_str()); + } + + if(std::string(rootElement->Value()) != "ColorCorrectionCollection") + { + std::ostringstream os; + os << "Error loading ccc xml. "; + os << "Root element is type '" << rootElement->Value() << "', "; + os << "ColorCorrectionCollection expected."; + throw Exception(os.str().c_str()); + } + + GetCDLTransforms(cachedFile->transforms, rootElement); + + if(cachedFile->transforms.empty()) + { + std::ostringstream os; + os << "Error loading ccc xml. "; + os << "No ColorCorrection elements found."; + throw Exception(os.str().c_str()); + } + + 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 .ccc 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 ASC FileTransform,"; + os << " unspecified transform direction."; + throw Exception(os.str().c_str()); + } + + std::string cccid = fileTransform.getCCCId(); + cccid = context->resolveStringVar(cccid.c_str()); + + CDLMap::const_iterator iter = cachedFile->transforms.find(cccid); + if(iter == cachedFile->transforms.end()) + { + std::ostringstream os; + os << "Cannot build ASC FileTransform, specified cccid '"; + os << cccid << "' not found in " << fileTransform.getSrc(); + throw Exception(os.str().c_str()); + } + + BuildCDLOps(ops, + config, + *(iter->second), + newDir); + } + } + + FileFormat * CreateFileFormatCCC() + { + return new LocalFileFormat(); + } +} +OCIO_NAMESPACE_EXIT + 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 diff --git a/src/core/FileFormatHDL.cpp b/src/core/FileFormatHDL.cpp new file mode 100644 index 0000000..995ef9f --- /dev/null +++ b/src/core/FileFormatHDL.cpp @@ -0,0 +1,1481 @@ +/* +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. +*/ + +/* + + Houdini LUTs + http://www.sidefx.com/docs/hdk11.0/hdk_io_lut.html + + Types: + - 1D Lut (partial support) + - 3D Lut + - 3D Lut with 1D Prelut + + TODO: + - Add support for other 1D types (R, G, B, A, RGB, RGBA, All) + we only support type 'C' atm. + - Add support for 'Sampling' tag + +*/ + +#include <cstdio> +#include <iostream> +#include <iterator> +#include <cmath> +#include <vector> +#include <string> +#include <algorithm> +#include <map> + +#include <OpenColorIO/OpenColorIO.h> + +#include "FileTransform.h" +#include "Lut1DOp.h" +#include "Lut3DOp.h" +#include "ParseUtils.h" +#include "MathUtils.h" +#include "pystring/pystring.h" + +OCIO_NAMESPACE_ENTER +{ + namespace + { + // HDL parser helpers + + // HDL headers/LUT's are shoved into these datatypes + typedef std::map<std::string, std::vector<std::string> > StringToStringVecMap; + typedef std::map<std::string, std::vector<float> > StringToFloatVecMap; + + void + readHeaders(StringToStringVecMap& headers, + std::istream& istream) + { + std::string line; + while(nextline(istream, line)) + { + std::vector<std::string> chunks; + + // Remove trailing/leading whitespace, lower-case and + // split into words + pystring::split(pystring::lower(pystring::strip(line)), chunks); + + // Skip empty lines + if(chunks.empty()) continue; + + // Stop looking for headers at the "LUT:" line + if(chunks[0] == "lut:") break; + + // Use first index as key, and remove it from the value + std::string key = chunks[0]; + chunks.erase(chunks.begin()); + + headers[key] = chunks; + } + } + + // Try to grab key (e.g "version") from headers. Throws + // exception if not found, or if number of chunks in value is + // not between min_vals and max_vals (e.g the "length" key + // must exist, and must have either 1 or 2 values) + std::vector<std::string> + findHeaderItem(StringToStringVecMap& headers, + const std::string key, + const unsigned int min_vals, + const unsigned int max_vals) + { + StringToStringVecMap::iterator iter; + iter = headers.find(key); + + // Error if key is not found + if(iter == headers.end()) + { + std::ostringstream os; + os << "'" << key << "' line not found"; + throw Exception(os.str().c_str()); + } + + // Error if incorrect number of values is found + if(iter->second.size() < min_vals || + iter->second.size() > max_vals) + { + std::ostringstream os; + os << "Incorrect number of chunks (" << iter->second.size() << ")"; + os << " after '" << key << "' line, expected "; + + if(min_vals == max_vals) + { + os << min_vals; + } + else + { + os << "between " << min_vals << " and " << max_vals; + } + + throw Exception(os.str().c_str()); + } + + return iter->second; + } + + // Simple wrapper to call findHeaderItem with a fixed number + // of values (e.g "version" should have a single value) + std::vector<std::string> + findHeaderItem(StringToStringVecMap& chunks, + const std::string key, + const unsigned int numvals) + { + return findHeaderItem(chunks, key, numvals, numvals); + } + + // Crudely parse LUT's - doesn't do any length checking etc, + // just grabs a series of floats for Pre{...}, 3d{...} etc + // Does some basic error checking, but there are situations + // were it could incorrectly accept broken data (like + // "Pre{0.0\n1.0}blah"), but hopefully none where it misses + // data + void + readLuts(std::istream& istream, + StringToFloatVecMap& lutValues) + { + // State variables + bool inlut = false; + std::string lutname; + + std::string word; + + while(istream >> word) + { + if(!inlut) + { + if(word == "{") + { + // Lone "{" is for a 3D + inlut = true; + lutname = "3d"; + } + else + { + // Named lut, e.g "Pre {" + inlut = true; + lutname = pystring::lower(word); + + // Ensure next word is "{" + std::string nextword; + istream >> nextword; + if(nextword != "{") + { + std::ostringstream os; + os << "Malformed LUT - Unknown word '"; + os << word << "' after LUT name '"; + os << nextword << "'"; + throw Exception(os.str().c_str()); + } + } + } + else if(word == "}") + { + // end of LUT + inlut = false; + lutname = ""; + } + else if(inlut) + { + // StringToFloat was far slower, for 787456 values: + // - StringToFloat took 3879 (avg nanoseconds per value) + // - stdtod took 169 nanoseconds + char* endptr = 0; + float v = static_cast<float>(strtod(word.c_str(), &endptr)); + + if(!*endptr) + { + // Since each word should contain a single + // float value, the pointer should be null + lutValues[lutname].push_back(v); + } + else + { + // stdtod endptr still contained stuff, + // meaning an invalid float value + std::ostringstream os; + os << "Invalid float value in " << lutname; + os << " LUT, '" << word << "'"; + throw Exception(os.str().c_str()); + } + } + else + { + std::ostringstream os; + os << "Unexpected word, possibly a value outside"; + os <<" a LUT {} block. Word was '" << word << "'"; + throw Exception(os.str().c_str()); + + } + } + } + + } // end anonymous "HDL parser helpers" namespace + + namespace + { + class CachedFileHDL : public CachedFile + { + public: + CachedFileHDL () + { + hdlversion = "unknown"; + hdlformat = "unknown"; + hdltype = "unknown"; + hdlblack = 0.0; + hdlwhite = 1.0; + lut1D = Lut1D::Create(); + lut3D = Lut3D::Create(); + }; + ~CachedFileHDL() {}; + std::string hdlversion; + std::string hdlformat; + std::string hdltype; + float to_min; // TODO: maybe add this to Lut1DOp? + float to_max; // TODO: maybe add this to Lut1DOp? + float hdlblack; + float hdlwhite; + Lut1DRcPtr lut1D; + Lut3DRcPtr lut3D; + }; + typedef OCIO_SHARED_PTR<CachedFileHDL> CachedFileHDLRcPtr; + + 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; + }; + + void LocalFileFormat::GetFormatInfo(FormatInfoVec & formatInfoVec) const + { + FormatInfo info; + info.name = "houdini"; + info.extension = "lut"; + 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 Houdini lut"); + + // + CachedFileHDLRcPtr cachedFile = CachedFileHDLRcPtr (new CachedFileHDL ()); + Lut1DRcPtr lut1d_ptr = Lut1D::Create(); + Lut3DRcPtr lut3d_ptr = Lut3D::Create(); + + // Parse headers into key-value pairs + StringToStringVecMap header_chunks; + StringToStringVecMap::iterator iter; + + // Read headers, ending after the "LUT:" line + readHeaders(header_chunks, istream); + + // Grab useful values from headers + std::vector<std::string> value; + + // "Version 3" - format version (currently one version + // number per LUT type) + value = findHeaderItem(header_chunks, "version", 1); + cachedFile->hdlversion = value[0]; + + // "Format any" - bit depth of image the LUT should be + // applied to (this is basically ignored) + value = findHeaderItem(header_chunks, "format", 1); + cachedFile->hdlformat = value[0]; + + // "Type 3d" - type of LUT + { + value = findHeaderItem(header_chunks, "type", 1); + + cachedFile->hdltype = value[0]; + } + + // "From 0.0 1.0" - range of input values + { + float from_min, from_max; + + value = findHeaderItem(header_chunks, "from", 2); + + if(!StringToFloat(&from_min, value[0].c_str()) || + !StringToFloat(&from_max, value[1].c_str())) + { + std::ostringstream os; + os << "Invalid float value(s) on 'From' line, '"; + os << value[0] << "' and '" << value[1] << "'"; + throw Exception(os.str().c_str()); + } + + for(int i = 0; i < 3; ++i) + { + lut1d_ptr->from_min[i] = from_min; + lut1d_ptr->from_max[i] = from_max; + } + } + + + // "To 0.0 1.0" - range of values in LUT (e.g "0 255" + // to specify values as 8-bit numbers, usually "0 1") + { + float to_min, to_max; + + value = findHeaderItem(header_chunks, "to", 2); + + if(!StringToFloat(&to_min, value[0].c_str()) || + !StringToFloat(&to_max, value[1].c_str())) + { + std::ostringstream os; + os << "Invalid float value(s) on 'To' line, '"; + os << value[0] << "' and '" << value[1] << "'"; + throw Exception(os.str().c_str()); + } + cachedFile->to_min = to_min; + cachedFile->to_max = to_max; + } + + // "Black 0" and "White 1" - obsolete options, should be 0 + // and 1 + + { + value = findHeaderItem(header_chunks, "black", 1); + + float black; + + if(!StringToFloat(&black, value[0].c_str())) + { + std::ostringstream os; + os << "Invalid float value on 'Black' line, '"; + os << value[0] << "'"; + throw Exception(os.str().c_str()); + } + cachedFile->hdlblack = black; + } + + { + value = findHeaderItem(header_chunks, "white", 1); + + float white; + + if(!StringToFloat(&white, value[0].c_str())) + { + std::ostringstream os; + os << "Invalid float value on 'White' line, '"; + os << value[0] << "'"; + throw Exception(os.str().c_str()); + } + cachedFile->hdlwhite = white; + } + + + // Verify type is valid and supported - used to handle + // length sensibly, and checking the LUT later + { + std::string ltype = cachedFile->hdltype; + if(ltype != "3d" && ltype != "3d+1d" && ltype != "c") + { + std::ostringstream os; + os << "Unsupported Houdini LUT type: '" << ltype << "'"; + throw Exception(os.str().c_str()); + } + } + + + // "Length 2" or "Length 2 5" - either "[cube size]", or "[cube + // size] [prelut size]" + int size_3d = -1; + int size_prelut = -1; + int size_1d = -1; + + { + std::vector<int> lut_sizes; + + value = findHeaderItem(header_chunks, "length", 1, 2); + for(unsigned int i = 0; i < value.size(); ++i) + { + int tmpsize = -1; + if(!StringToInt(&tmpsize, value[i].c_str())) + { + std::ostringstream os; + os << "Invalid integer on 'Length' line: "; + os << "'" << value[0] << "'"; + throw Exception(os.str().c_str()); + } + lut_sizes.push_back(tmpsize); + } + + if(cachedFile->hdltype == "3d" || cachedFile->hdltype == "3d+1d") + { + // Set cube size + size_3d = lut_sizes[0]; + + lut3d_ptr->size[0] = lut_sizes[0]; + lut3d_ptr->size[1] = lut_sizes[0]; + lut3d_ptr->size[2] = lut_sizes[0]; + } + + if(cachedFile->hdltype == "c") + { + size_1d = lut_sizes[0]; + } + + if(cachedFile->hdltype == "3d+1d") + { + size_prelut = lut_sizes[1]; + } + } + + // Read stuff after "LUT:" + StringToFloatVecMap lut_data; + readLuts(istream, lut_data); + + // + StringToFloatVecMap::iterator lut_iter; + + if(cachedFile->hdltype == "3d+1d") + { + // Read prelut, and bind onto cachedFile + lut_iter = lut_data.find("pre"); + if(lut_iter == lut_data.end()) + { + std::ostringstream os; + os << "3D+1D LUT should contain Pre{} LUT section"; + throw Exception(os.str().c_str()); + } + + if(size_prelut != static_cast<int>(lut_iter->second.size())) + { + std::ostringstream os; + os << "Pre{} LUT was " << lut_iter->second.size(); + os << " values long, expected " << size_prelut << " values"; + throw Exception(os.str().c_str()); + } + + lut1d_ptr->luts[0] = lut_iter->second; + lut1d_ptr->luts[1] = lut_iter->second; + lut1d_ptr->luts[2] = lut_iter->second; + lut1d_ptr->maxerror = 0.0f; + lut1d_ptr->errortype = ERROR_RELATIVE; + cachedFile->lut1D = lut1d_ptr; + } + + if(cachedFile->hdltype == "3d" || + cachedFile->hdltype == "3d+1d") + { + // Bind 3D LUT to lut3d_ptr, along with some + // slightly-elabourate error messages + + lut_iter = lut_data.find("3d"); + if(lut_iter == lut_data.end()) + { + std::ostringstream os; + os << "3D LUT section not found"; + throw Exception(os.str().c_str()); + } + + int size_3d_cubed = size_3d * size_3d * size_3d; + + if(size_3d_cubed * 3 != static_cast<int>(lut_iter->second.size())) + { + int foundsize = static_cast<int>(lut_iter->second.size()); + int foundlines = foundsize / 3; + + std::ostringstream os; + os << "3D LUT contains incorrect number of values. "; + os << "Contained " << foundsize << " values "; + os << "(" << foundlines << " lines), "; + os << "expected " << (size_3d_cubed*3) << " values "; + os << "(" << size_3d_cubed << " lines)"; + throw Exception(os.str().c_str()); + } + + lut3d_ptr->lut = lut_iter->second; + + // Bind to cachedFile + cachedFile->lut3D = lut3d_ptr; + } + + if(cachedFile->hdltype == "c") + { + // Bind simple 1D RGB LUT + lut_iter = lut_data.find("rgb"); + if(lut_iter == lut_data.end()) + { + std::ostringstream os; + os << "3D+1D LUT should contain Pre{} LUT section"; + throw Exception(os.str().c_str()); + } + + if(size_1d != static_cast<int>(lut_iter->second.size())) + { + std::ostringstream os; + os << "RGB{} LUT was " << lut_iter->second.size(); + os << " values long, expected " << size_1d << " values"; + throw Exception(os.str().c_str()); + } + + lut1d_ptr->luts[0] = lut_iter->second; + lut1d_ptr->luts[1] = lut_iter->second; + lut1d_ptr->luts[2] = lut_iter->second; + lut1d_ptr->maxerror = 0.0f; + lut1d_ptr->errortype = ERROR_RELATIVE; + cachedFile->lut1D = lut1d_ptr; + } + + return cachedFile; + } + + void LocalFileFormat::Write(const Baker & baker, + const std::string & formatName, + std::ostream & ostream) const + { + + if(formatName != "houdini") + { + std::ostringstream os; + os << "Unknown hdl format name, '"; + os << formatName << "'."; + throw Exception(os.str().c_str()); + } + + // Get config + ConstConfigRcPtr config = baker.getConfig(); + + // setup the floating point precision + ostream.setf(std::ios::fixed, std::ios::floatfield); + ostream.precision(6); + + // Default sizes + const int DEFAULT_SHAPER_SIZE = 1024; + // MPlay produces bad results with 32^3 cube (in a way + // that looks more quantised than even "nearest" + // interpolation in OCIOFileTransform) + const int DEFAULT_CUBE_SIZE = 64; + const int DEFAULT_1D_SIZE = 1024; + + // Get configured sizes + int cubeSize = baker.getCubeSize(); + int shaperSize = baker.getShaperSize(); + // FIXME: Misusing cube size to set 1D LUT size, as it seemed + // slightly less confusing than using the shaper LUT size + int onedSize = baker.getCubeSize(); + + // Defaults and sanity check on cube size + if(cubeSize == -1) cubeSize = DEFAULT_CUBE_SIZE; + if(cubeSize < 0) cubeSize = DEFAULT_CUBE_SIZE; + if(cubeSize<2) + { + std::ostringstream os; + os << "Cube size must be 2 or larger (was " << cubeSize << ")"; + throw Exception(os.str().c_str()); + } + + // ..and same for shaper size + if(shaperSize<0) shaperSize = DEFAULT_SHAPER_SIZE; + if(shaperSize<2) + { + std::ostringstream os; + os << "A shaper space ('" << baker.getShaperSpace() << "') has"; + os << " been specified, so the shaper size must be 2 or larger"; + throw Exception(os.str().c_str()); + } + + // ..and finally, for the 1D LUT size + if(onedSize == -1) onedSize = DEFAULT_1D_SIZE; + if(onedSize < 2) + { + std::ostringstream os; + os << "1D LUT size must be higher than 2 (was " << onedSize << ")"; + throw Exception(os.str().c_str()); + } + + // Version numbers + const int HDL_1D = 1; // 1D LUT version number + const int HDL_3D = 2; // 3D LUT version number + const int HDL_3D1D = 3; // 3D LUT with 1D prelut + + // Get spaces from baker + const std::string shaperSpace = baker.getShaperSpace(); + const std::string inputSpace = baker.getInputSpace(); + const std::string targetSpace = baker.getTargetSpace(); + const std::string looks = baker.getLooks(); + + // Determine required LUT type + ConstProcessorRcPtr inputToTargetProc; + if (!looks.empty()) + { + LookTransformRcPtr transform = LookTransform::Create(); + transform->setLooks(looks.c_str()); + transform->setSrc(inputSpace.c_str()); + transform->setDst(targetSpace.c_str()); + inputToTargetProc = config->getProcessor(transform, + TRANSFORM_DIR_FORWARD); + } + else + { + inputToTargetProc = config->getProcessor( + inputSpace.c_str(), + targetSpace.c_str()); + } + + int required_lut = -1; + + if(inputToTargetProc->hasChannelCrosstalk()) + { + if(shaperSpace.empty()) + { + // Has crosstalk, but no prelut, so need 3D LUT + required_lut = HDL_3D; + } + else + { + // Crosstalk with shaper-space + required_lut = HDL_3D1D; + } + } + else + { + // No crosstalk + required_lut = HDL_1D; + } + + if(required_lut == -1) + { + // Unnecessary paranoia + throw Exception( + "Internal logic error, LUT type was not determined"); + } + + // Make prelut + std::vector<float> prelutData; + + float fromInStart = 0; // for "From:" part of header + float fromInEnd = 1; + + if(required_lut == HDL_3D1D) + { + // TODO: Later we only grab the green channel for the prelut, + // should ensure the prelut is monochromatic somehow? + + ConstProcessorRcPtr inputToShaperProc = config->getProcessor( + inputSpace.c_str(), + shaperSpace.c_str()); + + if(inputToShaperProc->hasChannelCrosstalk()) + { + // TODO: Automatically turn shaper into + // non-crosstalked version? + std::ostringstream os; + os << "The specified shaperSpace, '" << baker.getShaperSpace(); + os << "' has channel crosstalk, which is not appropriate for"; + os << " shapers. Please select an alternate shaper space or"; + os << " omit this option."; + throw Exception(os.str().c_str()); + } + + // Calculate min/max value + { + // Get input value of 1.0 in shaper space, as this + // is the higest value that is transformed by the + // cube (e.g for a generic lin-to-log trasnform, + // what the log value 1.0 is in linear). + ConstProcessorRcPtr shaperToInputProc = config->getProcessor( + shaperSpace.c_str(), + inputSpace.c_str()); + + float minval[3] = {0.0f, 0.0f, 0.0f}; + float maxval[3] = {1.0f, 1.0f, 1.0f}; + + shaperToInputProc->applyRGB(minval); + shaperToInputProc->applyRGB(maxval); + + // Grab green channel, as this is the one used later + fromInStart = minval[1]; + fromInEnd = maxval[1]; + } + + // Generate the identity prelut values, then apply the transform. + // Prelut is linearly sampled from fromInStart to fromInEnd + prelutData.resize(shaperSize*3); + + for (int i = 0; i < shaperSize; ++i) + { + const float x = (float)(double(i) / double(shaperSize - 1)); + float cur_value = lerpf(fromInStart, fromInEnd, x); + + prelutData[3*i+0] = cur_value; + prelutData[3*i+1] = cur_value; + prelutData[3*i+2] = cur_value; + } + + PackedImageDesc prelutImg(&prelutData[0], shaperSize, 1, 3); + inputToShaperProc->apply(prelutImg); + } + + // TODO: Do same "auto prelut" input-space allocation as FileFormatCSP? + + // Make 3D LUT + std::vector<float> cubeData; + if(required_lut == HDL_3D || required_lut == HDL_3D1D) + { + cubeData.resize(cubeSize*cubeSize*cubeSize*3); + + GenerateIdentityLut3D(&cubeData[0], cubeSize, 3, LUT3DORDER_FAST_RED); + PackedImageDesc cubeImg(&cubeData[0], cubeSize*cubeSize*cubeSize, 1, 3); + + ConstProcessorRcPtr cubeProc; + if(required_lut == HDL_3D1D) + { + // Prelut goes from input-to-shaper, so cube goes from shaper-to-target + if (!looks.empty()) + { + LookTransformRcPtr transform = LookTransform::Create(); + transform->setLooks(looks.c_str()); + transform->setSrc(inputSpace.c_str()); + transform->setDst(targetSpace.c_str()); + cubeProc = config->getProcessor(transform, + TRANSFORM_DIR_FORWARD); + } + else + { + cubeProc = config->getProcessor(shaperSpace.c_str(), + targetSpace.c_str()); + } + } + else + { + // No prelut, so cube goes from input-to-target + cubeProc = inputToTargetProc; + } + + + cubeProc->apply(cubeImg); + } + + + // Make 1D LUT + std::vector<float> onedData; + if(required_lut == HDL_1D) + { + onedData.resize(onedSize * 3); + + GenerateIdentityLut1D(&onedData[0], onedSize, 3); + PackedImageDesc onedImg(&onedData[0], onedSize, 1, 3); + + inputToTargetProc->apply(onedImg); + } + + + // Write the file contents + ostream << "Version\t\t" << required_lut << "\n"; + ostream << "Format\t\t" << "any" << "\n"; + + ostream << "Type\t\t"; + if(required_lut == HDL_1D) + ostream << "RGB"; + if(required_lut == HDL_3D) + ostream << "3D"; + if(required_lut == HDL_3D1D) + ostream << "3D+1D"; + ostream << "\n"; + + ostream << "From\t\t" << fromInStart << " " << fromInEnd << "\n"; + ostream << "To\t\t" << 0.0f << " " << 1.0f << "\n"; + ostream << "Black\t\t" << 0.0f << "\n"; + ostream << "White\t\t" << 1.0f << "\n"; + + if(required_lut == HDL_3D1D) + ostream << "Length\t\t" << cubeSize << " " << shaperSize << "\n"; + if(required_lut == HDL_3D) + ostream << "Length\t\t" << cubeSize << "\n"; + if(required_lut == HDL_1D) + ostream << "Length\t\t" << onedSize << "\n"; + + ostream << "LUT:\n"; + + // Write prelut + if(required_lut == HDL_3D1D) + { + ostream << "Pre {\n"; + for(int i=0; i < shaperSize; ++i) + { + // Grab green channel from RGB prelut + ostream << "\t" << prelutData[i*3+1] << "\n"; + } + ostream << "}\n"; + } + + // Write "3D {" part of output of 3D+1D LUT + if(required_lut == HDL_3D1D) + { + ostream << "3D {\n"; + } + + // Write the slightly-different "{" without line for the 3D-only LUT + if(required_lut == HDL_3D) + { + ostream << " {\n"; + } + + // Write the cube data after the "{" + if(required_lut == HDL_3D || required_lut == HDL_3D1D) + { + for(int i=0; i < cubeSize*cubeSize*cubeSize; ++i) + { + // TODO: Original baker code clamped values to + // 1.0, was this necessary/desirable? + + ostream << "\t" << cubeData[3*i+0]; + ostream << " " << cubeData[3*i+1]; + ostream << " " << cubeData[3*i+2] << "\n"; + } + + // Write closing "}" + ostream << " }\n"; + } + + // Write out channels for 1D LUT + if(required_lut == HDL_1D) + { + ostream << "R {\n"; + for(int i=0; i < onedSize; ++i) + ostream << "\t" << onedData[i*3+0] << "\n"; + ostream << "}\n"; + + ostream << "G {\n"; + for(int i=0; i < onedSize; ++i) + ostream << "\t" << onedData[i*3+1] << "\n"; + ostream << "}\n"; + + ostream << "B {\n"; + for(int i=0; i < onedSize; ++i) + ostream << "\t" << onedData[i*3+2] << "\n"; + ostream << "}\n"; + } + } + + void + LocalFileFormat::BuildFileOps(OpRcPtrVec & ops, + const Config& /*config*/, + const ConstContextRcPtr & /*context*/, + CachedFileRcPtr untypedCachedFile, + const FileTransform& fileTransform, + TransformDirection dir) const + { + + CachedFileHDLRcPtr cachedFile = DynamicPtrCast<CachedFileHDL>(untypedCachedFile); + + // This should never happen. + if(!cachedFile) + { + std::ostringstream os; + os << "Cannot build Houdini Op. Invalid cache type."; + throw Exception(os.str().c_str()); + } + + TransformDirection newDir = CombineTransformDirections(dir, + fileTransform.getDirection()); + + if(newDir == TRANSFORM_DIR_FORWARD) { + if(cachedFile->hdltype == "c") + { + CreateLut1DOp(ops, cachedFile->lut1D, + fileTransform.getInterpolation(), newDir); + } + else if(cachedFile->hdltype == "3d") + { + CreateLut3DOp(ops, cachedFile->lut3D, + fileTransform.getInterpolation(), newDir); + } + else if(cachedFile->hdltype == "3d+1d") + { + CreateLut1DOp(ops, cachedFile->lut1D, + fileTransform.getInterpolation(), newDir); + CreateLut3DOp(ops, cachedFile->lut3D, + fileTransform.getInterpolation(), newDir); + } + else + { + throw Exception("Unhandled hdltype while creating forward ops"); + } + } else if(newDir == TRANSFORM_DIR_INVERSE) { + if(cachedFile->hdltype == "c") + { + CreateLut1DOp(ops, cachedFile->lut1D, + fileTransform.getInterpolation(), newDir); + } + else if(cachedFile->hdltype == "3d") + { + CreateLut3DOp(ops, cachedFile->lut3D, + fileTransform.getInterpolation(), newDir); + } + else if(cachedFile->hdltype == "3d+1d") + { + CreateLut3DOp(ops, cachedFile->lut3D, + fileTransform.getInterpolation(), newDir); + CreateLut1DOp(ops, cachedFile->lut1D, + fileTransform.getInterpolation(), newDir); + } + else + { + throw Exception("Unhandled hdltype while creating reverse ops"); + } + } + return; + } + } + + FileFormat * CreateFileFormatHDL() + { + return new LocalFileFormat(); + } +} +OCIO_NAMESPACE_EXIT + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef OCIO_UNIT_TEST + +namespace OCIO = OCIO_NAMESPACE; +#include "UnitTest.h" + +OIIO_ADD_TEST(FileFormatHDL, Read1D) +{ + std::ostringstream strebuf; + strebuf << "Version\t\t1" << "\n"; + strebuf << "Format\t\tany" << "\n"; + strebuf << "Type\t\tC" << "\n"; + strebuf << "From\t\t0.1 3.2" << "\n"; + strebuf << "To\t\t0 1" << "\n"; + strebuf << "Black\t\t0" << "\n"; + strebuf << "White\t\t0.99" << "\n"; + strebuf << "Length\t\t9" << "\n"; + strebuf << "LUT:" << "\n"; + strebuf << "RGB {" << "\n"; + strebuf << "\t0" << "\n"; + strebuf << "\t0.000977517" << "\n"; + strebuf << "\t0.00195503" << "\n"; + strebuf << "\t0.00293255" << "\n"; + strebuf << "\t0.00391007" << "\n"; + strebuf << "\t0.00488759" << "\n"; + strebuf << "\t0.0058651" << "\n"; + strebuf << "\t0.999022" << "\n"; + strebuf << "\t1.67 }" << "\n"; + + // + float from_min = 0.1f; + float from_max = 3.2f; + float to_min = 0.0f; + float to_max = 1.0f; + float black = 0.0f; + float white = 0.99f; + float lut1d[9] = { 0.0f, 0.000977517f, 0.00195503f, 0.00293255f, + 0.00391007f, 0.00488759f, 0.0058651f, 0.999022f, 1.67f }; + + std::istringstream simple3D1D; + simple3D1D.str(strebuf.str()); + + // Load file + OCIO::LocalFileFormat tester; + OCIO::CachedFileRcPtr cachedFile = tester.Read(simple3D1D); + OCIO::CachedFileHDLRcPtr lut = OCIO::DynamicPtrCast<OCIO::CachedFileHDL>(cachedFile); + + // + OIIO_CHECK_EQUAL(to_min, lut->to_min); + OIIO_CHECK_EQUAL(to_max, lut->to_max); + OIIO_CHECK_EQUAL(black, lut->hdlblack); + OIIO_CHECK_EQUAL(white, lut->hdlwhite); + + // check 1D data (each channel has the same data) + for(int c = 0; c < 3; ++c) { + OIIO_CHECK_EQUAL(from_min, lut->lut1D->from_min[c]); + OIIO_CHECK_EQUAL(from_max, lut->lut1D->from_max[c]); + + OIIO_CHECK_EQUAL(9, lut->lut1D->luts[c].size()); + + for(unsigned int i = 0; i < lut->lut1D->luts[c].size(); ++i) { + OIIO_CHECK_EQUAL(lut1d[i], lut->lut1D->luts[c][i]); + } + } +} + +OIIO_ADD_TEST(FileFormatHDL, Bake1D) +{ + + OCIO::ConfigRcPtr config = OCIO::Config::Create(); + + // Add lnf space + { + OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); + cs->setName("lnf"); + cs->setFamily("lnf"); + config->addColorSpace(cs); + config->setRole(OCIO::ROLE_REFERENCE, cs->getName()); + } + + // Add target space + { + 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 = + "Version\t\t1\n" + "Format\t\tany\n" + "Type\t\tRGB\n" + "From\t\t0.000000 1.000000\n" + "To\t\t0.000000 1.000000\n" + "Black\t\t0.000000\n" + "White\t\t1.000000\n" + "Length\t\t10\n" + "LUT:\n" + "R {\n" + "\t0.100000\n" + "\t0.211111\n" + "\t0.322222\n" + "\t0.433333\n" + "\t0.544444\n" + "\t0.655556\n" + "\t0.766667\n" + "\t0.877778\n" + "\t0.988889\n" + "\t1.100000\n" + " }\n" + "G {\n" + "\t0.100000\n" + "\t0.211111\n" + "\t0.322222\n" + "\t0.433333\n" + "\t0.544444\n" + "\t0.655556\n" + "\t0.766667\n" + "\t0.877778\n" + "\t0.988889\n" + "\t1.100000\n" + " }\n" + "B {\n" + "\t0.100000\n" + "\t0.211111\n" + "\t0.322222\n" + "\t0.433333\n" + "\t0.544444\n" + "\t0.655556\n" + "\t0.766667\n" + "\t0.877778\n" + "\t0.988889\n" + "\t1.100000\n" + " }\n"; + + // + OCIO::BakerRcPtr baker = OCIO::Baker::Create(); + baker->setConfig(config); + baker->setFormat("houdini"); + baker->setInputSpace("lnf"); + baker->setTargetSpace("target"); + baker->setCubeSize(10); // FIXME: Misusing the cube size to set the 1D LUT size + std::ostringstream output; + baker->bake(output); + + //std::cerr << "The LUT: " << std::endl << output.str() << std::endl; + //std::cerr << "Expected:" << std::endl << bout << std::endl; + + // + 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 < std::min(osvec.size(), resvec.size()); ++i) + OIIO_CHECK_EQUAL(OCIO::pystring::strip(osvec[i]), OCIO::pystring::strip(resvec[i])); + +} + +OIIO_ADD_TEST(FileFormatHDL, Read3D) +{ + std::ostringstream strebuf; + strebuf << "Version 2" << "\n"; + strebuf << "Format any" << "\n"; + strebuf << "Type 3D" << "\n"; + strebuf << "From 0.2 0.9" << "\n"; + strebuf << "To 0.001 0.999" << "\n"; + strebuf << "Black 0.002" << "\n"; + strebuf << "White 0.98" << "\n"; + strebuf << "Length 2" << "\n"; + strebuf << "LUT:" << "\n"; + strebuf << " {" << "\n"; + strebuf << " 0 0 0" << "\n"; + strebuf << " 0 0 0" << "\n"; + strebuf << " 0 0.390735 2.68116e-28" << "\n"; + strebuf << " 0 0.390735 0" << "\n"; + strebuf << " 0 0 0" << "\n"; + strebuf << " 0 0 0.599397" << "\n"; + strebuf << " 0 0.601016 0" << "\n"; + strebuf << " 0 0.601016 0.917034" << "\n"; + strebuf << " }" << "\n"; + + std::istringstream simple3D1D; + simple3D1D.str(strebuf.str()); + // Load file + OCIO::LocalFileFormat tester; + OCIO::CachedFileRcPtr cachedFile = tester.Read(simple3D1D); + OCIO::CachedFileHDLRcPtr lut = OCIO::DynamicPtrCast<OCIO::CachedFileHDL>(cachedFile); + + // + //float from_min = 0.2; + //float from_max = 0.9; + float to_min = 0.001f; + float to_max = 0.999f; + float black = 0.002f; + float white = 0.98f; + float cube[2 * 2 * 2 * 3 ] = { + 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, + 0.f, 0.390735f, 2.68116e-28f, + 0.f, 0.390735f, 0.f, + 0.f, 0.f, 0.f, + 0.f, 0.f, 0.599397f, + 0.f, 0.601016f, 0.f, + 0.f, 0.601016f, 0.917034f }; + + // + OIIO_CHECK_EQUAL(to_min, lut->to_min); + OIIO_CHECK_EQUAL(to_max, lut->to_max); + OIIO_CHECK_EQUAL(black, lut->hdlblack); + OIIO_CHECK_EQUAL(white, lut->hdlwhite); + + // check cube data + OIIO_CHECK_EQUAL(2*2*2*3, lut->lut3D->lut.size()); + + for(unsigned int i = 0; i < lut->lut3D->lut.size(); ++i) { + OIIO_CHECK_EQUAL(cube[i], lut->lut3D->lut[i]); + } +} + +OIIO_ADD_TEST(FileFormatHDL, Bake3D) +{ + OCIO::ConfigRcPtr config = OCIO::Config::Create(); + + // Set luma coef's to simple values + { + float lumaCoef[3] = {0.333f, 0.333f, 0.333f}; + config->setDefaultLumaCoefs(lumaCoef); + } + + // Add lnf space + { + OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); + cs->setName("lnf"); + cs->setFamily("lnf"); + config->addColorSpace(cs); + config->setRole(OCIO::ROLE_REFERENCE, cs->getName()); + } + + // Add target space + { + OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); + cs->setName("target"); + cs->setFamily("target"); + OCIO::CDLTransformRcPtr transform1 = OCIO::CDLTransform::Create(); + + // Set saturation to cause channel crosstalk, making a 3D LUT + transform1->setSat(0.5f); + + cs->setTransform(transform1, OCIO::COLORSPACE_DIR_FROM_REFERENCE); + config->addColorSpace(cs); + } + + std::string bout = + "Version\t\t2\n" + "Format\t\tany\n" + "Type\t\t3D\n" + "From\t\t0.000000 1.000000\n" + "To\t\t0.000000 1.000000\n" + "Black\t\t0.000000\n" + "White\t\t1.000000\n" + "Length\t\t2\n" + "LUT:\n" + " {\n" + "\t0.000000 0.000000 0.000000\n" + "\t0.606300 0.106300 0.106300\n" + "\t0.357600 0.857600 0.357600\n" + "\t0.963900 0.963900 0.463900\n" + "\t0.036100 0.036100 0.536100\n" + "\t0.642400 0.142400 0.642400\n" + "\t0.393700 0.893700 0.893700\n" + "\t1.000000 1.000000 1.000000\n" + " }\n"; + + // + OCIO::BakerRcPtr baker = OCIO::Baker::Create(); + baker->setConfig(config); + baker->setFormat("houdini"); + baker->setInputSpace("lnf"); + baker->setTargetSpace("target"); + baker->setCubeSize(2); + std::ostringstream output; + baker->bake(output); + + //std::cerr << "The LUT: " << std::endl << output.str() << std::endl; + //std::cerr << "Expected:" << std::endl << bout << std::endl; + + // + 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 < std::min(osvec.size(), resvec.size()); ++i) + OIIO_CHECK_EQUAL(OCIO::pystring::strip(osvec[i]), OCIO::pystring::strip(resvec[i])); +} + +OIIO_ADD_TEST(FileFormatHDL, Read3D1D) +{ + std::ostringstream strebuf; + strebuf << "Version 3" << "\n"; + strebuf << "Format any" << "\n"; + strebuf << "Type 3D+1D" << "\n"; + strebuf << "From 0.005478 14.080103" << "\n"; + strebuf << "To 0 1" << "\n"; + strebuf << "Black 0" << "\n"; + strebuf << "White 1" << "\n"; + strebuf << "Length 2 10" << "\n"; + strebuf << "LUT:" << "\n"; + strebuf << "Pre {" << "\n"; + strebuf << " 0.994922" << "\n"; + strebuf << " 0.995052" << "\n"; + strebuf << " 0.995181" << "\n"; + strebuf << " 0.995310" << "\n"; + strebuf << " 0.995439" << "\n"; + strebuf << " 0.995568" << "\n"; + strebuf << " 0.995697" << "\n"; + strebuf << " 0.995826" << "\n"; + strebuf << " 0.995954" << "\n"; + strebuf << " 0.996082" << "\n"; + strebuf << "}" << "\n"; + strebuf << "3D {" << "\n"; + strebuf << " 0.093776 0.093776 0.093776" << "\n"; + strebuf << " 0.105219 0.093776 0.093776" << "\n"; + strebuf << " 0.118058 0.093776 0.093776" << "\n"; + strebuf << " 0.132463 0.093776 0.093776" << "\n"; + strebuf << " 0.148626 0.093776 0.093776" << "\n"; + strebuf << " 0.166761 0.093776 0.093776" << "\n"; + strebuf << " 0.187109 0.093776 0.093776" << "\n"; + strebuf << " 0.209939 0.093776 0.093776" << "\n"; + strebuf << "}" << "\n"; + + // + float from_min = 0.005478f; + float from_max = 14.080103f; + float to_min = 0.0f; + float to_max = 1.0f; + float black = 0.0f; + float white = 1.0f; + float prelut[10] = { 0.994922f, 0.995052f, 0.995181f, 0.995310f, 0.995439f, + 0.995568f, 0.995697f, 0.995826f, 0.995954f, 0.996082f }; + float cube[2 * 2 * 2 * 3 ] = { + 0.093776f, 0.093776f, 0.093776f, + 0.105219f, 0.093776f, 0.093776f, + 0.118058f, 0.093776f, 0.093776f, + 0.132463f, 0.093776f, 0.093776f, + 0.148626f, 0.093776f, 0.093776f, + 0.166761f, 0.093776f, 0.093776f, + 0.187109f, 0.093776f, 0.093776f, + 0.209939f, 0.093776f, 0.093776f }; + + std::istringstream simple3D1D; + simple3D1D.str(strebuf.str()); + + // Load file + OCIO::LocalFileFormat tester; + OCIO::CachedFileRcPtr cachedFile = tester.Read(simple3D1D); + OCIO::CachedFileHDLRcPtr lut = OCIO::DynamicPtrCast<OCIO::CachedFileHDL>(cachedFile); + + // + OIIO_CHECK_EQUAL(to_min, lut->to_min); + OIIO_CHECK_EQUAL(to_max, lut->to_max); + OIIO_CHECK_EQUAL(black, lut->hdlblack); + OIIO_CHECK_EQUAL(white, lut->hdlwhite); + + // check prelut data (each channel has the same data) + for(int c = 0; c < 3; ++c) { + OIIO_CHECK_EQUAL(from_min, lut->lut1D->from_min[c]); + OIIO_CHECK_EQUAL(from_max, lut->lut1D->from_max[c]); + OIIO_CHECK_EQUAL(10, lut->lut1D->luts[c].size()); + + for(unsigned int i = 0; i < lut->lut1D->luts[c].size(); ++i) { + OIIO_CHECK_EQUAL(prelut[i], lut->lut1D->luts[c][i]); + } + } + + OIIO_CHECK_EQUAL(2*2*2*3, lut->lut3D->lut.size()); + + // check cube data + for(unsigned int i = 0; i < lut->lut3D->lut.size(); ++i) { + OIIO_CHECK_EQUAL(cube[i], lut->lut3D->lut[i]); + } +} + +OIIO_ADD_TEST(FileFormatHDL, Bake3D1D) +{ + // check baker output + OCIO::ConfigRcPtr config = OCIO::Config::Create(); + + // Set luma coef's to simple values + { + float lumaCoef[3] = {0.333f, 0.333f, 0.333f}; + config->setDefaultLumaCoefs(lumaCoef); + } + + // Add lnf space + { + OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); + cs->setName("lnf"); + cs->setFamily("lnf"); + config->addColorSpace(cs); + config->setRole(OCIO::ROLE_REFERENCE, cs->getName()); + } + + // Add shaper space + { + 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); + } + + // Add target space + { + OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); + cs->setName("target"); + cs->setFamily("target"); + OCIO::CDLTransformRcPtr transform1 = OCIO::CDLTransform::Create(); + + // Set saturation to cause channel crosstalk, making a 3D LUT + transform1->setSat(0.5f); + + cs->setTransform(transform1, OCIO::COLORSPACE_DIR_FROM_REFERENCE); + config->addColorSpace(cs); + } + + std::string bout = + "Version\t\t3\n" + "Format\t\tany\n" + "Type\t\t3D+1D\n" + "From\t\t0.000000 1.000000\n" + "To\t\t0.000000 1.000000\n" + "Black\t\t0.000000\n" + "White\t\t1.000000\n" + "Length\t\t2 10\n" + "LUT:\n" + "Pre {\n" + "\t0.000000\n" + "\t0.429520\n" + "\t0.560744\n" + "\t0.655378\n" + "\t0.732057\n" + "\t0.797661\n" + "\t0.855604\n" + "\t0.907865\n" + "\t0.955710\n" + "\t1.000000\n" + "}\n" + "3D {\n" + "\t0.000000 0.000000 0.000000\n" + "\t0.606300 0.106300 0.106300\n" + "\t0.357600 0.857600 0.357600\n" + "\t0.963900 0.963900 0.463900\n" + "\t0.036100 0.036100 0.536100\n" + "\t0.642400 0.142400 0.642400\n" + "\t0.393700 0.893700 0.893700\n" + "\t1.000000 1.000000 1.000000\n" + "}\n"; + + // + OCIO::BakerRcPtr baker = OCIO::Baker::Create(); + baker->setConfig(config); + baker->setFormat("houdini"); + baker->setInputSpace("lnf"); + baker->setShaperSpace("shaper"); + baker->setTargetSpace("target"); + baker->setShaperSize(10); + baker->setCubeSize(2); + std::ostringstream output; + baker->bake(output); + + //std::cerr << "The LUT: " << std::endl << output.str() << std::endl; + //std::cerr << "Expected:" << std::endl << bout << std::endl; + + // + 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()); + + // TODO: Get this working on osx + /* + for(unsigned int i = 0; i < std::min(osvec.size(), resvec.size()); ++i) + OIIO_CHECK_EQUAL(OCIO::pystring::strip(osvec[i]), OCIO::pystring::strip(resvec[i])); + */ +} + +#endif // OCIO_BUILD_TESTS diff --git a/src/core/FileFormatIridasCube.cpp b/src/core/FileFormatIridasCube.cpp new file mode 100644 index 0000000..6b2a496 --- /dev/null +++ b/src/core/FileFormatIridasCube.cpp @@ -0,0 +1,398 @@ +/* +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 <cstring> +#include <iterator> + +#include <OpenColorIO/OpenColorIO.h> + +#include "FileTransform.h" +#include "Lut1DOp.h" +#include "Lut3DOp.h" +#include "ParseUtils.h" +#include "pystring/pystring.h" + +/* + +http://doc.iridas.com/index.php/LUT_Formats + +#comments start with '#' +#title is currently ignored, but it's not an error to enter one +TITLE "title" + +#LUT_1D_SIZE M or +#LUT_3D_SIZE M +#where M is the size of the texture +#a 3D texture has the size M x M x M +#e.g. LUT_3D_SIZE 16 creates a 16 x 16 x 16 3D texture +LUT_3D_SIZE 2 + +#Default input value range (domain) is 0.0 (black) to 1.0 (white) +#Specify other min/max values to map the cube to any custom input +#range you wish to use, for example if you're working with HDR data +DOMAIN_MIN 0.0 0.0 0.0 +DOMAIN_MAX 1.0 1.0 1.0 + +#for 1D textures, the data is simply a list of floating point values, +#three per line, in RGB order +#for 3D textures, the data is also RGB, and ordered in such a way +#that the red coordinate changes fastest, then the green coordinate, +#and finally, the blue coordinate changes slowest: +0.0 0.0 0.0 +1.0 0.0 0.0 +0.0 1.0 0.0 +1.0 1.0 0.0 +0.0 0.0 1.0 +1.0 0.0 1.0 +0.0 1.0 1.0 +1.0 1.0 1.0 + +#Note that the LUT data is not limited to any particular range +#and can contain values under 0.0 and over 1.0 +#The processing application might however still clip the +#output values to the 0.0 - 1.0 range, depending on the internal +#precision of that application's pipeline +#IRIDAS applications generally use a floating point pipeline +#with little or no clipping + + +*/ + + +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 = "iridas_cube"; + info.extension = "cube"; + 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 Iridas .cube lut"); + } + + // Parse the file + std::vector<float> raw; + + int size3d[] = { 0, 0, 0 }; + int size1d = 0; + + bool in1d = false; + bool in3d = false; + + float domain_min[] = { 0.0f, 0.0f, 0.0f }; + float domain_max[] = { 1.0f, 1.0f, 1.0f }; + + { + std::string line; + std::vector<std::string> parts; + std::vector<float> tmpfloats; + + while(nextline(istream, line)) + { + // All lines starting with '#' are comments + if(pystring::startswith(line,"#")) continue; + + // Strip, lowercase, and split the line + pystring::split(pystring::lower(pystring::strip(line)), parts); + if(parts.empty()) continue; + + if(pystring::lower(parts[0]) == "title") + { + // Optional, and currently unhandled + } + else if(pystring::lower(parts[0]) == "lut_1d_size") + { + if(parts.size() != 2 || !StringToInt( &size1d, parts[1].c_str())) + { + throw Exception("Malformed LUT_1D_SIZE tag in Iridas .cube lut."); + } + + raw.reserve(3*size1d); + in1d = true; + } + else if(pystring::lower(parts[0]) == "lut_2d_size") + { + throw Exception("Unsupported Iridas .cube lut tag: 'LUT_2D_SIZE'."); + } + else if(pystring::lower(parts[0]) == "lut_3d_size") + { + int size = 0; + + if(parts.size() != 2 || !StringToInt( &size, parts[1].c_str())) + { + throw Exception("Malformed LUT_3D_SIZE tag in Iridas .cube lut."); + } + size3d[0] = size; + size3d[1] = size; + size3d[2] = size; + + raw.reserve(3*size3d[0]*size3d[1]*size3d[2]); + in3d = true; + } + else if(pystring::lower(parts[0]) == "domain_min") + { + if(parts.size() != 4 || + !StringToFloat( &domain_min[0], parts[1].c_str()) || + !StringToFloat( &domain_min[1], parts[2].c_str()) || + !StringToFloat( &domain_min[2], parts[3].c_str())) + { + throw Exception("Malformed DOMAIN_MIN tag in Iridas .cube lut."); + } + } + else if(pystring::lower(parts[0]) == "domain_max") + { + if(parts.size() != 4 || + !StringToFloat( &domain_max[0], parts[1].c_str()) || + !StringToFloat( &domain_max[1], parts[2].c_str()) || + !StringToFloat( &domain_max[2], parts[3].c_str())) + { + throw Exception("Malformed DOMAIN_MAX tag in Iridas .cube lut."); + } + } + else + { + // It must be a float triple! + + if(!StringVecToFloatVec(tmpfloats, parts) || tmpfloats.size() != 3) + { + std::ostringstream os; + os << "Malformed color triples specified in Iridas .cube lut:"; + os << "'" << line << "'."; + throw Exception(os.str().c_str()); + } + + for(int i=0; i<3; ++i) + { + raw.push_back(tmpfloats[i]); + } + } + } + } + + // Interpret the parsed data, validate lut sizes + + LocalCachedFileRcPtr cachedFile = LocalCachedFileRcPtr(new LocalCachedFile()); + + if(in1d) + { + if(size1d != static_cast<int>(raw.size()/3)) + { + std::ostringstream os; + os << "Parse error in Iridas .cube lut. "; + os << "Incorrect number of lut1d entries. "; + os << "Found " << raw.size()/3 << ", expected " << size1d << "."; + throw Exception(os.str().c_str()); + } + + // Reformat 1D data + if(size1d>0) + { + cachedFile->has1D = true; + memcpy(cachedFile->lut1D->from_min, domain_min, 3*sizeof(float)); + memcpy(cachedFile->lut1D->from_max, domain_max, 3*sizeof(float)); + + for(int channel=0; channel<3; ++channel) + { + cachedFile->lut1D->luts[channel].resize(size1d); + for(int i=0; i<size1d; ++i) + { + cachedFile->lut1D->luts[channel][i] = raw[3*i+channel]; + } + } + + // 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; + } + } + else if(in3d) + { + cachedFile->has3D = true; + + if(size3d[0]*size3d[1]*size3d[2] != static_cast<int>(raw.size()/3)) + { + std::ostringstream os; + os << "Parse error in Iridas .cube lut. "; + os << "Incorrect number of lut3d entries. "; + os << "Found " << raw.size()/3 << ", expected " << size3d[0]*size3d[1]*size3d[2] << "."; + throw Exception(os.str().c_str()); + } + + // Reformat 3D data + memcpy(cachedFile->lut3D->from_min, domain_min, 3*sizeof(float)); + memcpy(cachedFile->lut3D->from_max, domain_max, 3*sizeof(float)); + cachedFile->lut3D->size[0] = size3d[0]; + cachedFile->lut3D->size[1] = size3d[1]; + cachedFile->lut3D->size[2] = size3d[2]; + cachedFile->lut3D->lut = raw; + } + else + { + std::ostringstream os; + os << "Parse error in Iridas .cube lut. "; + os << "Lut type (1D/3D) unspecified."; + throw Exception(os.str().c_str()); + } + + 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 Iridas .cube 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 * CreateFileFormatIridasCube() + { + return new LocalFileFormat(); + } +} +OCIO_NAMESPACE_EXIT + + +/////////////////////////////////////////////////////////////////////////////// diff --git a/src/core/FileFormatIridasItx.cpp b/src/core/FileFormatIridasItx.cpp new file mode 100644 index 0000000..43af60e --- /dev/null +++ b/src/core/FileFormatIridasItx.cpp @@ -0,0 +1,336 @@ +/* +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 <cstring> +#include <iterator> + +#include <OpenColorIO/OpenColorIO.h> + +#include "FileTransform.h" +#include "Lut1DOp.h" +#include "Lut3DOp.h" +#include "ParseUtils.h" +#include "pystring/pystring.h" + +/* + +Iridas itx format +LUT_3D_SIZE M + +#LUT_3D_SIZE M +#where M is the size of the texture +#a 3D texture has the size M x M x M +#e.g. LUT_3D_SIZE 16 creates a 16 x 16 x 16 3D texture + +#for 1D textures, the data is simply a list of floating point values, +#three per line, in RGB order +#for 3D textures, the data is also RGB, and ordered in such a way +#that the red coordinate changes fastest, then the green coordinate, +#and finally, the blue coordinate changes slowest: +0.0 0.0 0.0 +1.0 0.0 0.0 +0.0 1.0 0.0 +1.0 1.0 0.0 +0.0 0.0 1.0 +1.0 0.0 1.0 +0.0 1.0 1.0 +1.0 1.0 1.0 +*/ + + +OCIO_NAMESPACE_ENTER +{ + namespace + { + class LocalCachedFile : public CachedFile + { + public: + LocalCachedFile () + { + lut3D = Lut3D::Create(); + }; + ~LocalCachedFile() {}; + + 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; + }; + + void LocalFileFormat::GetFormatInfo(FormatInfoVec & formatInfoVec) const + { + FormatInfo info; + info.name = "iridas_itx"; + info.extension = "itx"; + 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 Iridas .itx lut"); + } + + // Parse the file + std::vector<float> raw; + + int size3d[] = { 0, 0, 0 }; + bool in3d = false; + + { + std::string line; + std::vector<std::string> parts; + std::vector<float> tmpfloats; + + while(nextline(istream, line)) + { + // All lines starting with '#' are comments + if(pystring::startswith(line,"#")) continue; + + // Strip, lowercase, and split the line + pystring::split(pystring::lower(pystring::strip(line)), parts); + if(parts.empty()) continue; + + if(pystring::lower(parts[0]) == "lut_3d_size") + { + int size = 0; + + if(parts.size() != 2 || !StringToInt( &size, parts[1].c_str())) + { + throw Exception("Malformed LUT_3D_SIZE tag in Iridas .itx lut."); + } + size3d[0] = size; + size3d[1] = size; + size3d[2] = size; + + raw.reserve(3*size3d[0]*size3d[1]*size3d[2]); + in3d = true; + } + else if(in3d) + { + // It must be a float triple! + + if(!StringVecToFloatVec(tmpfloats, parts) || tmpfloats.size() != 3) + { + std::ostringstream os; + os << "Malformed color triples specified in Iridas .itx lut:"; + os << "'" << line << "'."; + throw Exception(os.str().c_str()); + } + + for(int i=0; i<3; ++i) + { + raw.push_back(tmpfloats[i]); + } + } + } + } + + // Interpret the parsed data, validate lut sizes + + LocalCachedFileRcPtr cachedFile = LocalCachedFileRcPtr(new LocalCachedFile()); + + if(in3d) + { + if(size3d[0]*size3d[1]*size3d[2] != static_cast<int>(raw.size()/3)) + { + std::ostringstream os; + os << "Parse error in Iridas .itx lut. "; + os << "Incorrect number of lut3d entries. "; + os << "Found " << raw.size()/3 << ", expected " << size3d[0]*size3d[1]*size3d[2] << "."; + throw Exception(os.str().c_str()); + } + + // Reformat 3D data + cachedFile->lut3D->size[0] = size3d[0]; + cachedFile->lut3D->size[1] = size3d[1]; + cachedFile->lut3D->size[2] = size3d[2]; + cachedFile->lut3D->lut = raw; + } + else + { + std::ostringstream os; + os << "Parse error in Iridas .itx lut. "; + os << "Lut type (1D/3D) unspecified."; + throw Exception(os.str().c_str()); + } + + return cachedFile; + } + + void LocalFileFormat::Write(const Baker & baker, + const std::string & formatName, + std::ostream & ostream) const + { + int DEFAULT_CUBE_SIZE = 64; + + if(formatName != "iridas_itx") + { + 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 + + 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); + + // 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 + + ostream << "LUT_3D_SIZE " << cubeSize << "\n"; + if(cubeSize < 2) + { + throw Exception("Internal cube size exception."); + } + + // Set to a fixed 6 decimal precision + ostream.setf(std::ios::fixed, std::ios::floatfield); + ostream.precision(6); + for(int i=0; i<cubeSize*cubeSize*cubeSize; ++i) + { + float r = cubeData[3*i+0]; + float g = cubeData[3*i+1]; + float b = cubeData[3*i+2]; + ostream << r << " " << g << " " << b << "\n"; + } + ostream << "\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 Iridas .itx 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) + { + CreateLut3DOp(ops, cachedFile->lut3D, + fileTransform.getInterpolation(), newDir); + } + else if(newDir == TRANSFORM_DIR_INVERSE) + { + CreateLut3DOp(ops, cachedFile->lut3D, + fileTransform.getInterpolation(), newDir); + } + } + } + + FileFormat * CreateFileFormatIridasItx() + { + return new LocalFileFormat(); + } +} +OCIO_NAMESPACE_EXIT + + +/////////////////////////////////////////////////////////////////////////////// diff --git a/src/core/FileFormatIridasLook.cpp b/src/core/FileFormatIridasLook.cpp new file mode 100644 index 0000000..d53c641 --- /dev/null +++ b/src/core/FileFormatIridasLook.cpp @@ -0,0 +1,1356 @@ +/* +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 <cstring> +#include <iterator> + +#include <tinyxml.h> + +#include <OpenColorIO/OpenColorIO.h> + +#include "FileTransform.h" +#include "Lut1DOp.h" +#include "Lut3DOp.h" +#include "ParseUtils.h" +#include "pystring/pystring.h" + +/* + +Iridas .look format + +An XML format containing <shaders>, a series of layers describing the +operations and their parameters (irrelevant to us in this context). + +This series of shaders is baked into the <LUT> section. + +<?xml version="1.0" ?> +<look> + <shaders> + # anything in here is useless to us + </shaders> + <LUT> + <size>"8"</size> # Size of 3D LUT + <data>" + 0000008000000080000000802CF52E3D2DF52E3D2DF52E3D2CF5AE3D2DF5AE3D + # ...cut... + 5A216A3F5A216A3FAD10753FAD10753FAD10753F0000803F0000803F0000803F" + </data> + </LUT> +</look> + +The LUT data contains a 3D LUT, as a hex-encoded series of 32-bit +floats, with little-endian bit-ordering. LUT value ordering is +LUT3DORDER_FAST_RED (red index incrementing fastest, then green, then +blue) + +The LUT data is parsed by removing all whitespace and quotes. Taking 8 +characters at a time and intepreting as little-endian float, as follows: + +Given the string "0000003F0000803FAD10753F": + + >>> import binascii, struct + >>> struct.unpack("<f", binascii.unhexlify("0000003F"))[0] + 0.5 + >>> struct.unpack("<f", binascii.unhexlify("0000803F"))[0] + 1.0 + >>> struct.unpack("<f", binascii.unhexlify("AD10753F"))[0] + 0.9572857022285461 + + */ + + +OCIO_NAMESPACE_ENTER +{ + namespace + { + // convert hex ascii to int + // return true on success, false on failure + bool hexasciitoint(char& ival, char character) + { + if(character>=48 && character<=57) // [0-9] + { + ival = static_cast<char>(character-48); + return true; + } + else if(character>=65 && character<=70) // [A-F] + { + ival = static_cast<char>(10+character-65); + return true; + } + else if(character>=97 && character<=102) // [a-f] + { + ival = static_cast<char>(10+character-97); + return true; + } + + ival = 0; + return false; + } + + // convert array of 8 hex ascii to f32 + // The input hexascii is required to be a little-endian representation + // as used in the iridas file format + // "AD10753F" -> 0.9572857022285461f on ALL architectures + + bool hexasciitofloat(float& fval, const char * ascii) + { + // Convert all ASCII numbers to their numerical representations + char asciinums[8]; + for(unsigned int i=0; i<8; ++i) + { + if(!hexasciitoint(asciinums[i], ascii[i])) + { + return false; + } + } + + unsigned char * fvalbytes = reinterpret_cast<unsigned char *>(&fval); + +#if OCIO_LITTLE_ENDIAN + // Since incoming values are little endian, and we're on little endian + // preserve the byte order + fvalbytes[0] = (unsigned char) (asciinums[1] | (asciinums[0] << 4)); + fvalbytes[1] = (unsigned char) (asciinums[3] | (asciinums[2] << 4)); + fvalbytes[2] = (unsigned char) (asciinums[5] | (asciinums[4] << 4)); + fvalbytes[3] = (unsigned char) (asciinums[7] | (asciinums[6] << 4)); +#else + // Since incoming values are little endian, and we're on big endian + // flip the byte order + fvalbytes[3] = (unsigned char) (asciinums[1] | (asciinums[0] << 4)); + fvalbytes[2] = (unsigned char) (asciinums[3] | (asciinums[2] << 4)); + fvalbytes[1] = (unsigned char) (asciinums[5] | (asciinums[4] << 4)); + fvalbytes[0] = (unsigned char) (asciinums[7] | (asciinums[6] << 4)); +#endif + return true; + } + } + + namespace + { + class LocalCachedFile : public CachedFile + { + public: + LocalCachedFile () + { + lut3D = Lut3D::Create(); + }; + ~LocalCachedFile() {}; + + Lut3DRcPtr lut3D; + }; + + typedef OCIO_SHARED_PTR<LocalCachedFile> LocalCachedFileRcPtr; + typedef OCIO_SHARED_PTR<TiXmlDocument> TiXmlDocumentRcPtr; + + + 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 = "iridas_look"; + info.extension = "look"; + info.capabilities = FORMAT_CAPABILITY_READ; + formatInfoVec.push_back(info); + } + + CachedFileRcPtr + LocalFileFormat::Read(std::istream & istream) const + { + + // Get root element from XML file + TiXmlDocumentRcPtr doc; + TiXmlElement* rootElement; + + { + std::ostringstream rawdata; + rawdata << istream.rdbuf(); + + doc = TiXmlDocumentRcPtr(new TiXmlDocument()); + doc->Parse(rawdata.str().c_str()); + + if(doc->Error()) + { + std::ostringstream os; + os << "XML Parse Error. "; + os << doc->ErrorDesc() << " (line "; + os << doc->ErrorRow() << ", character "; + os << doc->ErrorCol() << ")"; + throw Exception(os.str().c_str()); + } + + // Check for blank file + rootElement = doc->RootElement(); + if(!rootElement) + { + std::ostringstream os; + os << "Error loading xml. Null root element."; + throw Exception(os.str().c_str()); + } + } + + // Check root element is <look> + if(std::string(rootElement->Value()) != "look") + { + std::ostringstream os; + os << "Error loading .look LUT. "; + os << "Root element is type '" << rootElement->Value() << "', "; + os << "expected 'look'."; + throw Exception(os.str().c_str()); + } + + // Fail to load file if it contains a <mask> section + if(rootElement->FirstChild("mask") && rootElement->FirstChild("mask")->FirstChild()) + { + // If root element contains "mask" child, and it is + // not empty, throw exception + std::ostringstream os; + os << "Cannot load .look LUT containing mask"; + throw Exception(os.str().c_str()); + } + + // Get <LUT> section + + // TODO: There is a LUT1D section in some .look files, + // which we could use if available. Need to check + // assumption that it is only written for 1D transforms, + // and it matches the desired output + TiXmlNode* lutsection = rootElement->FirstChild("LUT"); + + if(!lutsection) + { + std::ostringstream os; + os << "Error loading .look LUT. "; + os << "Could not find required 'LUT' section."; + throw Exception(os.str().c_str()); + } + + // Get 3D LUT size + int size_3d = -1; + + { + // Get size from <look><LUT><size>'123'</size></LUT></look> + TiXmlNode* elemsize = lutsection->FirstChild("size"); + if(!elemsize) + { + std::ostringstream os; + os << "Error loading .look LUT. "; + os << "LUT section did not contain 'size'."; + throw Exception(os.str().c_str()); + } + + std::string size_raw = std::string(elemsize->ToElement()->GetText()); + std::string size_clean = pystring::strip(size_raw, "'\" "); // strip quotes and space + + char* endptr = 0; + size_3d = static_cast<int>(strtol(size_clean.c_str(), &endptr, 10)); + + if(*endptr) + { + // strtol didn't consume entire string, there was + // remaining data, thus did not contain a single interger + std::ostringstream os; + os << "Invalid LUT size value: '" << size_raw; + os << "'. Expected quoted integer."; + throw Exception(os.str().c_str()); + } + } + + // Grab raw 3D data + std::vector<float> raw; + { + TiXmlNode* dataelem = lutsection->FirstChild("data"); + if(!dataelem) + { + std::ostringstream os; + os << "Error loading .look LUT. "; + os << "LUT section did not contain 'data'."; + throw Exception(os.str().c_str()); + } + + raw.reserve(3*(size_3d*size_3d*size_3d)); + + std::string what = dataelem->ToElement()->GetText(); + + // Remove spaces, quotes and newlines + what = pystring::replace(what, " ", ""); + what = pystring::replace(what, "\"", ""); + what = pystring::replace(what, "'", ""); + what = pystring::replace(what, "\n", ""); + + if(what.size() % 8 != 0) + { + std::ostringstream os; + os << "Error loading .look LUT. "; + os << "Number of characters in 'data' must be multiple of 8. "; + os << what.size() << " elements found."; + throw Exception(os.str().c_str()); + } + + const char * ascii = what.c_str(); + float fval = 0.0f; + for(unsigned int i=0; i<what.size()/8; ++i) + { + if(!hexasciitofloat(fval, &ascii[8*i])) + { + std::ostringstream os; + os << "Error loading .look LUT. "; + os << "Non-hex characters found in 'data' block "; + os << "at index '" << (8*i) << "'."; + throw Exception(os.str().c_str()); + } + raw.push_back(fval); + } + } + + + // Validate LUT sizes, and create cached file object + LocalCachedFileRcPtr cachedFile = LocalCachedFileRcPtr(new LocalCachedFile()); + + if((size_3d*size_3d*size_3d)*3 != static_cast<int>(raw.size())) + { + std::ostringstream os; + os << "Parse error in Iridas .look lut. "; + os << "Incorrect number of lut3d entries. "; + os << "Found " << raw.size() << " values, expected " << (size_3d*size_3d*size_3d)*3 << "."; + throw Exception(os.str().c_str()); + } + + // Reformat 3D data + cachedFile->lut3D->size[0] = size_3d; + cachedFile->lut3D->size[1] = size_3d; + cachedFile->lut3D->size[2] = size_3d; + cachedFile->lut3D->lut = raw; + + 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 Iridas .look 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()); + } + + CreateLut3DOp(ops, cachedFile->lut3D, + fileTransform.getInterpolation(), newDir); + } + } + + FileFormat * CreateFileFormatIridasLook() + { + return new LocalFileFormat(); + } +} +OCIO_NAMESPACE_EXIT + + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef OCIO_UNIT_TEST + +#include "UnitTest.h" + +OCIO_NAMESPACE_USING + + + +OIIO_ADD_TEST(FileFormatIridasLook, hexasciitoint) +{ + { + char ival = 0; + bool success = hexasciitoint(ival, 'a'); + OIIO_CHECK_EQUAL(success, true); OIIO_CHECK_EQUAL(ival, 10); + } + + { + char ival = 0; + bool success = hexasciitoint(ival, 'A'); + OIIO_CHECK_EQUAL(success, true); OIIO_CHECK_EQUAL(ival, 10); + } + + { + char ival = 0; + bool success = hexasciitoint(ival, 'f'); + OIIO_CHECK_EQUAL(success, true); OIIO_CHECK_EQUAL(ival, 15); + } + + { + char ival = 0; + bool success = hexasciitoint(ival, 'F'); + OIIO_CHECK_EQUAL(success, true); OIIO_CHECK_EQUAL(ival, 15); + } + + { + char ival = 0; + bool success = hexasciitoint(ival, '0'); + OIIO_CHECK_EQUAL(success, true); OIIO_CHECK_EQUAL(ival, 0); + } + + { + char ival = 0; + bool success = hexasciitoint(ival, '0'); + OIIO_CHECK_EQUAL(success, true); OIIO_CHECK_EQUAL(ival, 0); + } + + { + char ival = 0; + bool success = hexasciitoint(ival, '9'); + OIIO_CHECK_EQUAL(success, true); OIIO_CHECK_EQUAL(ival, 9); + } + + { + char ival = 0; + bool success = hexasciitoint(ival, '\n'); + OIIO_CHECK_EQUAL(success, false); + } + + { + char ival = 0; + bool success = hexasciitoint(ival, 'j'); + OIIO_CHECK_EQUAL(success, false); + } + + { + char ival = 0; + bool success = hexasciitoint(ival, 'x'); + OIIO_CHECK_EQUAL(success, false); + } +} + + +OIIO_ADD_TEST(FileFormatIridasLook, hexasciitofloat) +{ + //>>> import binascii, struct + //>> struct.unpack("<f", binascii.unhexlify("AD10753F"))[0] + //0.9572857022285461 + + { + float fval = 0.0f; + bool success = hexasciitofloat(fval, "0000003F"); + OIIO_CHECK_EQUAL(success, true); OIIO_CHECK_EQUAL(fval, 0.5f); + } + + { + float fval = 0.0f; + bool success = hexasciitofloat(fval, "0000803F"); + OIIO_CHECK_EQUAL(success, true); OIIO_CHECK_EQUAL(fval, 1.0f); + } + + { + float fval = 0.0f; + bool success = hexasciitofloat(fval, "AD10753F"); + OIIO_CHECK_EQUAL(success, true); OIIO_CHECK_EQUAL(fval, 0.9572857022285461f); + } + + { + float fval = 0.0f; + bool success = hexasciitofloat(fval, "AD10X53F"); + OIIO_CHECK_EQUAL(success, false); + } +} + + +OIIO_ADD_TEST(FileFormatIridasLook, simple3d) +{ + std::ostringstream strebuf; + strebuf << "<?xml version=\"1.0\" ?>" << "\n"; + strebuf << "<look>" << "\n"; + strebuf << " <shaders>" << "\n"; + strebuf << " <base>" << "\n"; + strebuf << " <visible>\"1\"</visible>" << "\n"; + strebuf << " <sublayer0>" << "\n"; + strebuf << " <opacity>\"1\"</opacity>" << "\n"; + strebuf << " <parameters>" << "\n"; + strebuf << " <Secondary1>\"1\"</Secondary1>" << "\n"; + strebuf << " <Secondary5>\"0\"</Secondary5>" << "\n"; + strebuf << " <Secondary4>\"0\"</Secondary4>" << "\n"; + strebuf << " <Secondary2>\"0\"</Secondary2>" << "\n"; + strebuf << " <Secondary6>\"0\"</Secondary6>" << "\n"; + strebuf << " <Secondary3>\"0\"</Secondary3>" << "\n"; + strebuf << " <Blur>\"0\"</Blur>" << "\n"; + strebuf << " <saturation>\"0\"</saturation>" << "\n"; + strebuf << " </parameters>" << "\n"; + strebuf << " </sublayer0>" << "\n"; + strebuf << " </base>" << "\n"; + strebuf << " </shaders>" << "\n"; + strebuf << " <LUT>" << "\n"; + strebuf << " <size>\"8\"</size>" << "\n"; + strebuf << " <data>\"" << "\n"; + strebuf << " 0000008000000080000000802CF52E3D2DF52E3D2DF52E3D2CF5AE3D2DF5AE3D" << "\n"; + strebuf << " 2DF5AE3DE237033EE237033EE237033E2CF52E3E2DF52E3E2DF52E3E78B25A3E" << "\n"; + strebuf << " 78B25A3E78B25A3EE037833EE137833EE137833E8616993E8716993E8716993E" << "\n"; + strebuf << " 4BBDAB3D4BBDAB3D4BBDAB3DF09B013EF09B013EF09B013E3C592D3E3C592D3E" << "\n"; + strebuf << " 3C592D3E8716593E8716593E8716593EE969823EE969823EE969823E8E48983E" << "\n"; + strebuf << " 8E48983E8E48983E3227AE3E3327AE3E3327AE3ED805C43ED905C43ED905C43E" << "\n"; + strebuf << " 4BBD2B3E4BBD2B3E4BBD2B3E967A573E967A573E967A573EF09B813EF09B813E" << "\n"; + strebuf << " F09B813E967A973E967A973E967A973E3C59AD3E3C59AD3E3C59AD3EE137C33E" << "\n"; + strebuf << " E137C33EE137C33E8616D93E8616D93E8616D93E2CF5EE3E2CF5EE3E2CF5EE3E" << "\n"; + strebuf << " F9CD803EF9CD803EF9CD803E9EAC963E9EAC963E9EAC963E448BAC3E448BAC3E" << "\n"; + strebuf << " 448BAC3EEA69C23EEA69C23EEA69C23E8F48D83E8F48D83E8F48D83E3527EE3E" << "\n"; + strebuf << " 3527EE3E3527EE3EED02023FED02023FED02023F40F20C3F40F20C3F40F20C3F" << "\n"; + strebuf << " 4BBDAB3E4BBDAB3E4BBDAB3EF09BC13EF09BC13EF09BC13E967AD73E967AD73E" << "\n"; + strebuf << " 967AD73E3C59ED3E3C59ED3E3C59ED3EF09B013FF09B013FF09B013F438B0C3F" << "\n"; + strebuf << " 438B0C3F438B0C3F967A173F967A173F967A173FE969223FE969223FE969223F" << "\n"; + strebuf << " 9EACD63E9EACD63E9EACD63E428BEC3E438BEC3E438BEC3EF434013FF434013F" << "\n"; + strebuf << " F434013F47240C3F47240C3F47240C3F9A13173F9A13173F9A13173FED02223F" << "\n"; + strebuf << " ED02223FED02223F3FF22C3F3FF22C3F3FF22C3F92E1373F92E1373F92E1373F" << "\n"; + strebuf << " F8CD003FF8CD003FF8CD003F49BD0B3F4ABD0B3F4ABD0B3F9DAC163F9DAC163F" << "\n"; + strebuf << " 9DAC163FF09B213FF09B213FF09B213F438B2C3F438B2C3F438B2C3F967A373F" << "\n"; + strebuf << " 967A373F967A373FE869423FE869423FE869423F3B594D3F3B594D3F3B594D3F" << "\n"; + strebuf << " A245163FA245163FA245163FF334213FF434213FF434213F47242C3F47242C3F" << "\n"; + strebuf << " 47242C3F9A13373F9A13373F9A13373FED02423FED02423FED02423F40F24C3F" << "\n"; + strebuf << " 40F24C3F40F24C3F92E1573F92E1573F92E1573FE5D0623FE5D0623FE5D0623F" << "\n"; + strebuf << " 9E69853C9E69853C9869853CFCA9713DFCA9713DFCA9713D944FD03D944FD03D" << "\n"; + strebuf << " 944FD03D14E5133E15E5133E15E5133E60A23F3E60A23F3E60A23F3EAA5F6B3E" << "\n"; + strebuf << " AB5F6B3EAB5F6B3E7A8E8B3E7A8E8B3E7A8E8B3E206DA13E206DA13E206DA13E" << "\n"; + strebuf << " B217CD3DB217CD3DB217CD3D2449123E2449123E2449123E6F063E3E6F063E3E" << "\n"; + strebuf << " 6F063E3EBAC3693EBAC3693EBAC3693E82C08A3E82C08A3E82C08A3E289FA03E" << "\n"; + strebuf << " 289FA03E289FA03ECC7DB63ECC7DB63ECC7DB63E725CCC3E715CCC3E715CCC3E" << "\n"; + strebuf << " 7E6A3C3E7E6A3C3E7E6A3C3ECA27683ECA27683ECA27683E8AF2893E8AF2893E" << "\n"; + strebuf << " 8AF2893E30D19F3E30D19F3E30D19F3ED5AFB53ED5AFB53ED5AFB53E7B8ECB3E" << "\n"; + strebuf << " 7B8ECB3E7A8ECB3E1F6DE13E1F6DE13E1E6DE13EC44BF73EC54BF73EC44BF73E" << "\n"; + strebuf << " 9224893E9224893E9224893E38039F3E38039F3E38039F3EDEE1B43EDEE1B43E" << "\n"; + strebuf << " DEE1B43E83C0CA3E83C0CA3E82C0CA3E299FE03E299FE03E289FE03ECE7DF63E" << "\n"; + strebuf << " CE7DF63ECD7DF63E392E063F392E063F382E063F8C1D113F8C1D113F8B1D113F" << "\n"; + strebuf << " E413B43EE413B43EE413B43E89F2C93E8AF2C93E89F2C93E30D1DF3E30D1DF3E" << "\n"; + strebuf << " 2FD1DF3ED5AFF53ED5AFF53ED4AFF53E3DC7053F3DC7053F3CC7053F90B6103F" << "\n"; + strebuf << " 90B6103F8FB6103FE2A51B3FE2A51B3FE1A51B3F3595263F3595263F3495263F" << "\n"; + strebuf << " 3703DF3E3703DF3E3603DF3EDCE1F43EDDE1F43EDCE1F43E4160053F4160053F" << "\n"; + strebuf << " 4060053F944F103F944F103F934F103FE73E1B3FE73E1B3FE63E1B3F392E263F" << "\n"; + strebuf << " 392E263F382E263F8C1D313F8C1D313F8B1D313FDF0C3C3FDF0C3C3FDE0C3C3F" << "\n"; + strebuf << " 44F9043F44F9043F43F9043F96E80F3F97E80F3F96E80F3FEAD71A3FEAD71A3F" << "\n"; + strebuf << " E9D71A3F3DC7253F3DC7253F3CC7253F90B6303F90B6303F8FB6303FE2A53B3F" << "\n"; + strebuf << " E2A53B3FE1A53B3F3595463F3595463F3495463F8884513F8884513F8784513F" << "\n"; + strebuf << " EE701A3FEE701A3FED701A3F4060253F4160253F4060253F944F303F944F303F" << "\n"; + strebuf << " 934F303FE73E3B3FE73E3B3FE63E3B3F3A2E463F3A2E463F392E463F8C1D513F" << "\n"; + strebuf << " 8C1D513F8B1D513FDF0C5C3FDF0C5C3FDE0C5C3F32FC663F32FC663F31FC663F" << "\n"; + strebuf << " 9E69053D9E69053D9869053D652F9A3D652F9A3D642F9A3DFCA9F13DFCA9F13D" << "\n"; + strebuf << " FCA9F13D4892243E4992243E4992243E944F503E944F503E944F503EDE0C7C3E" << "\n"; + strebuf << " DF0C7C3EDF0C7C3E14E5933E14E5933E14E5933EBAC3A93EBAC3A93EBAC3A93E" << "\n"; + strebuf << " 1A72EE3D1A72EE3D1A72EE3D58F6223E58F6223E58F6223EA3B34E3EA3B34E3E" << "\n"; + strebuf << " A3B34E3EEE707A3EEE707A3EEE707A3E1C17933E1C17933E1C17933EC2F5A83E" << "\n"; + strebuf << " C2F5A83EC2F5A83E66D4BE3E66D4BE3E66D4BE3E0CB3D43E0BB3D43E0CB3D43E" << "\n"; + strebuf << " B2174D3EB2174D3EB2174D3EFDD4783EFDD4783EFDD4783E2449923E2449923E" << "\n"; + strebuf << " 2449923ECA27A83ECA27A83ECA27A83E6F06BE3E6F06BE3E6F06BE3E15E5D33E" << "\n"; + strebuf << " 15E5D33E15E5D33EB9C3E93EB9C3E93EB9C3E93E5EA2FF3E5FA2FF3E5FA2FF3E" << "\n"; + strebuf << " 2C7B913E2C7B913E2C7B913ED259A73ED259A73ED259A73E7838BD3E7838BD3E" << "\n"; + strebuf << " 7838BD3E1D17D33E1D17D33E1D17D33EC3F5E83EC3F5E83EC3F5E83E68D4FE3E" << "\n"; + strebuf << " 68D4FE3E68D4FE3E86590A3F86590A3F86590A3FD948153FD948153FD948153F" << "\n"; + strebuf << " 7E6ABC3E7E6ABC3E7E6ABC3E2349D23E2449D23E2449D23ECA27E83ECA27E83E" << "\n"; + strebuf << " CA27E83E6F06FE3E6F06FE3E6F06FE3E8AF2093F8AF2093F8AF2093FDDE1143F" << "\n"; + strebuf << " DDE1143FDDE1143F2FD11F3F2FD11F3F2FD11F3F82C02A3F82C02A3F82C02A3F" << "\n"; + strebuf << " D159E73ED159E73ED159E73E7638FD3E7738FD3E7738FD3E8E8B093F8E8B093F" << "\n"; + strebuf << " 8E8B093FE17A143FE17A143FE17A143F346A1F3F346A1F3F346A1F3F86592A3F" << "\n"; + strebuf << " 86592A3F86592A3FD948353FD948353FD948353F2C38403F2C38403F2C38403F" << "\n"; + strebuf << " 9124093F9124093F9124093FE313143FE413143FE413143F37031F3F37031F3F" << "\n"; + strebuf << " 37031F3F8AF2293F8AF2293F8AF2293FDDE1343FDDE1343FDDE1343F2FD13F3F" << "\n"; + strebuf << " 2FD13F3F2FD13F3F82C04A3F82C04A3F81C04A3FD5AF553FD5AF553FD4AF553F" << "\n"; + strebuf << " 3B9C1E3F3B9C1E3F3B9C1E3F8D8B293F8E8B293F8E8B293FE17A343FE17A343F" << "\n"; + strebuf << " E17A343F346A3F3F346A3F3F346A3F3F87594A3F87594A3F86594A3FD948553F" << "\n"; + strebuf << " D948553FD848553F2C38603F2C38603F2B38603F7F276B3F7F276B3F7E276B3F" << "\n"; + strebuf << " 6E1E483D6E1E483D681E483DCD89BB3DCD89BB3DCC89BB3D3282093E3282093E" << "\n"; + strebuf << " 3282093E7C3F353E7D3F353E7C3F353EC8FC603EC8FC603EC8FC603E095D863E" << "\n"; + strebuf << " 095D863E095D863EAE3B9C3EAE3B9C3EAE3B9C3E541AB23E541AB23E541AB23E" << "\n"; + strebuf << " 41E6073E41E6073E40E6073E8CA3333E8CA3333E8CA3333ED7605F3ED7605F3E" << "\n"; + strebuf << " D7605F3E118F853E118F853E118F853EB66D9B3EB66D9B3EB66D9B3E5B4CB13E" << "\n"; + strebuf << " 5B4CB13E5B4CB13E002BC73E002BC73E002BC73EA609DD3EA509DD3EA609DD3E" << "\n"; + strebuf << " E6C45D3EE6C45D3EE6C45D3E18C1843E18C1843E18C1843EBE9F9A3EBE9F9A3E" << "\n"; + strebuf << " BE9F9A3E647EB03E647EB03E647EB03E095DC63E095DC63E095DC63EAE3BDC3E" << "\n"; + strebuf << " AE3BDC3EAE3BDC3E531AF23E531AF23E531AF23E7CFC033F7CFC033F7CFC033F" << "\n"; + strebuf << " C6D1993EC6D1993EC6D1993E6CB0AF3E6CB0AF3E6CB0AF3E128FC53E128FC53E" << "\n"; + strebuf << " 128FC53EB76DDB3EB76DDB3EB76DDB3E5D4CF13E5D4CF13E5D4CF13E8195033F" << "\n"; + strebuf << " 8195033F8195033FD3840E3FD3840E3FD3840E3F2674193F2674193F2674193F" << "\n"; + strebuf << " 18C1C43E18C1C43E18C1C43EBD9FDA3EBE9FDA3EBE9FDA3E647EF03E647EF03E" << "\n"; + strebuf << " 647EF03E842E033F842E033F842E033FD71D0E3FD71D0E3FD71D0E3F2A0D193F" << "\n"; + strebuf << " 2A0D193F2A0D193F7CFC233F7CFC233F7CFC233FCFEB2E3FCFEB2E3FCFEB2E3F" << "\n"; + strebuf << " 6BB0EF3E6BB0EF3E6BB0EF3E87C7023F88C7023F88C7023FDBB60D3FDBB60D3F" << "\n"; + strebuf << " DBB60D3F2EA6183F2EA6183F2EA6183F8195233F8195233F8195233FD3842E3F" << "\n"; + strebuf << " D3842E3FD3842E3F2674393F2674393F2674393F7963443F7963443F7963443F" << "\n"; + strebuf << " DE4F0D3FDE4F0D3FDE4F0D3F303F183F313F183F313F183F842E233F842E233F" << "\n"; + strebuf << " 842E233FD71D2E3FD71D2E3FD71D2E3F2A0D393F2A0D393F2A0D393F7CFC433F" << "\n"; + strebuf << " 7CFC433F7CFC433FCFEB4E3FCFEB4E3FCFEB4E3F22DB593F22DB593F22DB593F" << "\n"; + strebuf << " 88C7223F88C7223F88C7223FDAB62D3FDBB62D3FDBB62D3F2EA6383F2EA6383F" << "\n"; + strebuf << " 2EA6383F8195433F8195433F8195433FD4844E3FD4844E3FD4844E3F2674593F" << "\n"; + strebuf << " 2674593F2674593F7963643F7963643F7963643FCC526F3FCC526F3FCC526F3F" << "\n"; + strebuf << " 9E69853D9E69853D9869853D34E4DC3D34E4DC3D34E4DC3D652F1A3E652F1A3E" << "\n"; + strebuf << " 642F1A3EB1EC453EB1EC453EB0EC453EFCA9713EFCA9713EFCA9713EA3B38E3E" << "\n"; + strebuf << " A3B38E3EA3B38E3E4892A43E4892A43E4892A43EEE70BA3EEE70BA3EEE70BA3E" << "\n"; + strebuf << " 7493183E7493183E7493183EBF50443EBF50443EBE50443E0A0E703E0A0E703E" << "\n"; + strebuf << " 0A0E703EABE58D3EABE58D3EABE58D3E50C4A33E50C4A33E50C4A33EF5A2B93E" << "\n"; + strebuf << " F5A2B93EF5A2B93E9A81CF3E9981CF3E9A81CF3E4060E53E3F60E53E4060E53E" << "\n"; + strebuf << " 1A726E3E1A726E3E1A726E3EB2178D3EB2178D3EB2178D3E58F6A23E58F6A23E" << "\n"; + strebuf << " 58F6A23EFED4B83EFED4B83EFED4B83EA3B3CE3EA3B3CE3EA3B3CE3E4892E43E" << "\n"; + strebuf << " 4892E43E4892E43EED70FA3EED70FA3EED70FA3EC927083FC927083FC927083F" << "\n"; + strebuf << " 6028A23E6028A23E6028A23E0607B83E0607B83E0607B83EABE5CD3EABE5CD3E" << "\n"; + strebuf << " ABE5CD3E51C4E33E51C4E33E51C4E33EF7A2F93EF7A2F93EF7A2F93ECEC0073F" << "\n"; + strebuf << " CEC0073FCEC0073F20B0123F20B0123F20B0123F739F1D3F739F1D3F739F1D3F" << "\n"; + strebuf << " B217CD3EB217CD3EB217CD3E57F6E23E58F6E23E58F6E23EFDD4F83EFDD4F83E" << "\n"; + strebuf << " FDD4F83ED159073FD159073FD159073F2449123F2449123F2449123F77381D3F" << "\n"; + strebuf << " 77381D3F77381D3FC927283FC927283FC927283F1C17333F1C17333F1C17333F" << "\n"; + strebuf << " 0507F83E0507F83E0507F83ED4F2063FD5F2063FD5F2063F28E2113F28E2113F" << "\n"; + strebuf << " 28E2113F7BD11C3F7BD11C3F7BD11C3FCEC0273FCEC0273FCEC0273F20B0323F" << "\n"; + strebuf << " 20B0323F20B0323F739F3D3F739F3D3F739F3D3FC68E483FC68E483FC68E483F" << "\n"; + strebuf << " 2B7B113F2B7B113F2B7B113F7D6A1C3F7E6A1C3F7E6A1C3FD159273FD159273F" << "\n"; + strebuf << " D159273F2449323F2449323F2449323F77383D3F77383D3F77383D3FC927483F" << "\n"; + strebuf << " C927483FC927483F1C17533F1C17533F1C17533F6F065E3F6F065E3F6F065E3F" << "\n"; + strebuf << " D5F2263FD5F2263FD5F2263F27E2313F28E2313F28E2313F7BD13C3F7BD13C3F" << "\n"; + strebuf << " 7BD13C3FCEC0473FCEC0473FCEC0473F21B0523F21B0523F21B0523F739F5D3F" << "\n"; + strebuf << " 739F5D3F739F5D3FC68E683FC68E683FC68E683F197E733F197E733F197E733F" << "\n"; + strebuf << " 06C4A63D06C4A63D00C4A63D9C3EFE3D9C3EFE3D983EFE3D99DC2A3E99DC2A3E" << "\n"; + strebuf << " 98DC2A3EE599563EE599563EE499563E982B813E982B813E982B813E3D0A973E" << "\n"; + strebuf << " 3D0A973E3D0A973EE2E8AC3EE2E8AC3EE2E8AC3E88C7C23E88C7C23E88C7C23E" << "\n"; + strebuf << " A840293EA840293EA840293EF3FD543EF3FD543EF0FD543E9F5D803E9F5D803E" << "\n"; + strebuf << " 9F5D803E453C963E453C963E453C963EEA1AAC3EEA1AAC3EEA1AAC3E8FF9C13E" << "\n"; + strebuf << " 8FF9C13E8FF9C13E34D8D73E33D8D73E34D8D73EDAB6ED3ED9B6ED3EDAB6ED3E" << "\n"; + strebuf << " 4E1F7F3E4E1F7F3E4E1F7F3E4C6E953E4C6E953E4C6E953EF24CAB3EF24CAB3E" << "\n"; + strebuf << " F24CAB3E982BC13E982BC13E982BC13E3D0AD73E3D0AD73E3D0AD73EE2E8EC3E" << "\n"; + strebuf << " E2E8EC3EE2E8EC3EC363013FC363013FC363013F16530C3F16530C3F16530C3F" << "\n"; + strebuf << " FA7EAA3EFA7EAA3EFA7EAA3EA05DC03EA05DC03EA05DC03E453CD63E453CD63E" << "\n"; + strebuf << " 453CD63EEB1AEC3EEB1AEC3EEB1AEC3EC8FC003FC8FC003FC8FC003F1BEC0B3F" << "\n"; + strebuf << " 1BEC0B3F1BEC0B3F6DDB163F6DDB163F6DDB163FC0CA213FC0CA213FC0CA213F" << "\n"; + strebuf << " 4C6ED53E4C6ED53E4C6ED53EF14CEB3EF24CEB3EF24CEB3ECB95003FCB95003F" << "\n"; + strebuf << " CB95003F1E850B3F1E850B3F1E850B3F7174163F7174163F7174163FC463213F" << "\n"; + strebuf << " C463213FC463213F16532C3F16532C3F16532C3F6942373F6942373F6942373F" << "\n"; + strebuf << " CF2E003FCF2E003FCF2E003F211E0B3F221E0B3F221E0B3F750D163F750D163F" << "\n"; + strebuf << " 750D163FC8FC203FC8FC203FC8FC203F1BEC2B3F1BEC2B3F1BEC2B3F6DDB363F" << "\n"; + strebuf << " 6DDB363F6DDB363FC0CA413FC0CA413FC0CA413F13BA4C3F13BA4C3F13BA4C3F" << "\n"; + strebuf << " 78A6153F78A6153F78A6153FCA95203FCB95203FCB95203F1E852B3F1E852B3F" << "\n"; + strebuf << " 1E852B3F7174363F7174363F7174363FC463413FC463413FC463413F16534C3F" << "\n"; + strebuf << " 16534C3F16534C3F6942573F6942573F6942573FBC31623FBC31623FBC31623F" << "\n"; + strebuf << " 221E2B3F221E2B3F221E2B3F740D363F750D363F750D363FC8FC403FC8FC403F" << "\n"; + strebuf << " C8FC403F1BEC4B3F1BEC4B3F1BEC4B3F6EDB563F6EDB563F6EDB563FC0CA613F" << "\n"; + strebuf << " C0CA613FC0CA613F13BA6C3F13BA6C3F13BA6C3F66A9773F66A9773F66A9773F" << "\n"; + strebuf << " 6D1EC83D6D1EC83D681EC83D81CC0F3E81CC0F3E80CC0F3ECD893B3ECD893B3E" << "\n"; + strebuf << " CC893B3E1847673E1847673E1847673E3182893E3182893E3082893ED7609F3E" << "\n"; + strebuf << " D7609F3ED6609F3E7C3FB53E7C3FB53E7C3FB53E221ECB3E221ECB3E221ECB3E" << "\n"; + strebuf << " DCED393EDCED393EDCED393E26AB653E26AB653E24AB653E39B4883E39B4883E" << "\n"; + strebuf << " 38B4883EDE929E3EDE929E3EDE929E3E8371B43E8371B43E8271B43E2950CA3E" << "\n"; + strebuf << " 2850CA3E2950CA3ECE2EE03ECD2EE03ECE2EE03E740DF63E730DF63E740DF63E" << "\n"; + strebuf << " 40E6873E40E6873E40E6873EE6C49D3EE6C49D3EE6C49D3E8CA3B33E8CA3B33E" << "\n"; + strebuf << " 8CA3B33E3182C93E3182C93E3182C93ED660DF3ED660DF3ED660DF3E7C3FF53E" << "\n"; + strebuf << " 7C3FF53E7C3FF53E108F053F108F053F108F053F637E103F637E103F637E103F" << "\n"; + strebuf << " 94D5B23E94D5B23E94D5B23E39B4C83E39B4C83E39B4C83EDF92DE3EDF92DE3E" << "\n"; + strebuf << " DF92DE3E8571F43E8571F43E8571F43E1528053F1528053F1528053F6817103F" << "\n"; + strebuf << " 6817103F6817103FBA061B3FBA061B3FBA061B3F0DF6253F0DF6253F0DF6253F" << "\n"; + strebuf << " E6C4DD3EE6C4DD3EE6C4DD3E8AA3F33E8BA3F33E8BA3F33E18C1043F18C1043F" << "\n"; + strebuf << " 18C1043F6BB00F3F6BB00F3F6BB00F3FBE9F1A3FBE9F1A3FBE9F1A3F118F253F" << "\n"; + strebuf << " 118F253F118F253F637E303F637E303F637E303FB66D3B3FB66D3B3FB66D3B3F" << "\n"; + strebuf << " 1C5A043F1C5A043F1C5A043F6E490F3F6F490F3F6F490F3FC2381A3FC2381A3F" << "\n"; + strebuf << " C2381A3F1528253F1528253F1528253F6717303F6717303F6717303FBA063B3F" << "\n"; + strebuf << " BA063B3FBA063B3F0DF6453F0DF6453F0DF6453F60E5503F60E5503F60E5503F" << "\n"; + strebuf << " C5D1193FC5D1193FC5D1193F17C1243F18C1243F18C1243F6BB02F3F6BB02F3F" << "\n"; + strebuf << " 6BB02F3FBE9F3A3FBE9F3A3FBE9F3A3F108F453F108F453F108F453F637E503F" << "\n"; + strebuf << " 637E503F637E503FB66D5B3FB66D5B3FB66D5B3F095D663F095D663F095D663F" << "\n"; + strebuf << " 6F492F3F6F492F3F6F492F3FC1383A3FC2383A3FC2383A3F1528453F1528453F" << "\n"; + strebuf << " 1528453F6817503F6817503F6817503FBA065B3FBA065B3FBA065B3F0DF6653F" << "\n"; + strebuf << " 0DF6653F0DF6653F60E5703F60E5703F60E5703FB3D47B3FB3D47B3FB3D47B3F" << "\n"; + strebuf << " D578E93DD578E93DD078E93DB579203EB579203EB479203E01374C3E01374C3E" << "\n"; + strebuf << " 00374C3E4CF4773E4CF4773E4CF4773ECBD8913ECBD8913ECAD8913E71B7A73E" << "\n"; + strebuf << " 71B7A73E70B7A73E1696BD3E1696BD3E1696BD3EBC74D33EBC74D33EBC74D33E" << "\n"; + strebuf << " 109B4A3E109B4A3E109B4A3E5A58763E5A58763E5858763ED30A913ED30A913E" << "\n"; + strebuf << " D20A913E78E9A63E78E9A63E78E9A63E1DC8BC3E1DC8BC3E1CC8BC3EC3A6D23E" << "\n"; + strebuf << " C2A6D23EC2A6D23E6885E83E6785E83E6885E83E0E64FE3E0D64FE3E0E64FE3E" << "\n"; + strebuf << " DA3C903EDA3C903EDA3C903E801BA63E801BA63E801BA63E26FABB3E26FABB3E" << "\n"; + strebuf << " 26FABB3ECBD8D13ECBD8D13ECAD8D13E70B7E73E70B7E73E70B7E73E1696FD3E" << "\n"; + strebuf << " 1696FD3E1696FD3E5DBA093F5DBA093F5DBA093FB0A9143FB0A9143FB0A9143F" << "\n"; + strebuf << " 2E2CBB3E2E2CBB3E2E2CBB3ED20AD13ED30AD13ED20AD13E79E9E63E79E9E63E" << "\n"; + strebuf << " 78E9E63E1FC8FC3E1FC8FC3E1EC8FC3E6253093F6253093F6253093FB542143F" << "\n"; + strebuf << " B542143FB542143F07321F3F07321F3F07321F3F5A212A3F5A212A3F5A212A3F" << "\n"; + strebuf << " 801BE63E801BE63E801BE63E24FAFB3E25FAFB3E24FAFB3E65EC083F65EC083F" << "\n"; + strebuf << " 65EC083FB8DB133FB8DB133FB8DB133F0BCB1E3F0BCB1E3F0BCB1E3F5EBA293F" << "\n"; + strebuf << " 5EBA293F5EBA293FB0A9343FB0A9343FB0A9343F03993F3F03993F3F03993F3F" << "\n"; + strebuf << " 6985083F6985083F6985083FBB74133FBC74133FBC74133F0F641E3F0F641E3F" << "\n"; + strebuf << " 0F641E3F6253293F6253293F6253293FB442343FB442343FB442343F07323F3F" << "\n"; + strebuf << " 07323F3F07323F3F5A214A3F5A214A3F5A214A3FAD10553FAD10553FAD10553F" << "\n"; + strebuf << " 12FD1D3F12FD1D3F12FD1D3F64EC283F65EC283F65EC283FB8DB333FB8DB333F" << "\n"; + strebuf << " B8DB333F0BCB3E3F0BCB3E3F0BCB3E3F5DBA493F5DBA493F5DBA493FB0A9543F" << "\n"; + strebuf << " B0A9543FB0A9543F03995F3F03995F3F03995F3F56886A3F56886A3F56886A3F" << "\n"; + strebuf << " BC74333FBC74333FBC74333F0E643E3F0F643E3F0F643E3F6153493F6253493F" << "\n"; + strebuf << " 6253493FB542543FB542543FB542543F07325F3F07325F3F07325F3F5A216A3F" << "\n"; + strebuf << " 5A216A3F5A216A3FAD10753FAD10753FAD10753F0000803F0000803F0000803F\"" << "\n"; + strebuf << " </data>" << "\n"; + strebuf << " </LUT>" << "\n"; + strebuf << "</look>" << "\n"; + + std::istringstream simple1D; + simple1D.str(strebuf.str()); + + // Read file + LocalFileFormat tester; + CachedFileRcPtr cachedFile = tester.Read(simple1D); + LocalCachedFileRcPtr looklut = DynamicPtrCast<LocalCachedFile>(cachedFile); + + // Check LUT size is correct + OIIO_CHECK_EQUAL(looklut->lut3D->size[0], 8); + OIIO_CHECK_EQUAL(looklut->lut3D->size[1], 8); + OIIO_CHECK_EQUAL(looklut->lut3D->size[2], 8); + + // Check LUT values + OIIO_CHECK_EQUAL(looklut->lut3D->lut.size(), 8*8*8*3); + + double cube[8 * 8 * 8 * 3] = { + -0.00000, -0.00000, -0.00000, + 0.04271, 0.04271, 0.04271, + 0.08543, 0.08543, 0.08543, + 0.12814, 0.12814, 0.12814, + 0.17086, 0.17086, 0.17086, + 0.21357, 0.21357, 0.21357, + 0.25629, 0.25629, 0.25629, + 0.29900, 0.29900, 0.29900, + 0.08386, 0.08386, 0.08386, + 0.12657, 0.12657, 0.12657, + 0.16929, 0.16929, 0.16929, + 0.21200, 0.21200, 0.21200, + 0.25471, 0.25471, 0.25471, + 0.29743, 0.29743, 0.29743, + 0.34014, 0.34014, 0.34014, + 0.38286, 0.38286, 0.38286, + 0.16771, 0.16771, 0.16771, + 0.21043, 0.21043, 0.21043, + 0.25314, 0.25314, 0.25314, + 0.29586, 0.29586, 0.29586, + 0.33857, 0.33857, 0.33857, + 0.38129, 0.38129, 0.38129, + 0.42400, 0.42400, 0.42400, + 0.46671, 0.46671, 0.46671, + 0.25157, 0.25157, 0.25157, + 0.29429, 0.29429, 0.29429, + 0.33700, 0.33700, 0.33700, + 0.37971, 0.37971, 0.37971, + 0.42243, 0.42243, 0.42243, + 0.46514, 0.46514, 0.46514, + 0.50786, 0.50786, 0.50786, + 0.55057, 0.55057, 0.55057, + 0.33543, 0.33543, 0.33543, + 0.37814, 0.37814, 0.37814, + 0.42086, 0.42086, 0.42086, + 0.46357, 0.46357, 0.46357, + 0.50629, 0.50629, 0.50629, + 0.54900, 0.54900, 0.54900, + 0.59171, 0.59171, 0.59171, + 0.63443, 0.63443, 0.63443, + 0.41929, 0.41929, 0.41929, + 0.46200, 0.46200, 0.46200, + 0.50471, 0.50471, 0.50471, + 0.54743, 0.54743, 0.54743, + 0.59014, 0.59014, 0.59014, + 0.63286, 0.63286, 0.63286, + 0.67557, 0.67557, 0.67557, + 0.71829, 0.71829, 0.71829, + 0.50314, 0.50314, 0.50314, + 0.54586, 0.54586, 0.54586, + 0.58857, 0.58857, 0.58857, + 0.63129, 0.63129, 0.63129, + 0.67400, 0.67400, 0.67400, + 0.71671, 0.71671, 0.71671, + 0.75943, 0.75943, 0.75943, + 0.80214, 0.80214, 0.80214, + 0.58700, 0.58700, 0.58700, + 0.62971, 0.62971, 0.62971, + 0.67243, 0.67243, 0.67243, + 0.71514, 0.71514, 0.71514, + 0.75786, 0.75786, 0.75786, + 0.80057, 0.80057, 0.80057, + 0.84329, 0.84329, 0.84329, + 0.88600, 0.88600, 0.88600, + 0.01629, 0.01629, 0.01629, + 0.05900, 0.05900, 0.05900, + 0.10171, 0.10171, 0.10171, + 0.14443, 0.14443, 0.14443, + 0.18714, 0.18714, 0.18714, + 0.22986, 0.22986, 0.22986, + 0.27257, 0.27257, 0.27257, + 0.31529, 0.31529, 0.31529, + 0.10014, 0.10014, 0.10014, + 0.14286, 0.14286, 0.14286, + 0.18557, 0.18557, 0.18557, + 0.22829, 0.22829, 0.22829, + 0.27100, 0.27100, 0.27100, + 0.31371, 0.31371, 0.31371, + 0.35643, 0.35643, 0.35643, + 0.39914, 0.39914, 0.39914, + 0.18400, 0.18400, 0.18400, + 0.22671, 0.22671, 0.22671, + 0.26943, 0.26943, 0.26943, + 0.31214, 0.31214, 0.31214, + 0.35486, 0.35486, 0.35486, + 0.39757, 0.39757, 0.39757, + 0.44029, 0.44029, 0.44029, + 0.48300, 0.48300, 0.48300, + 0.26786, 0.26786, 0.26786, + 0.31057, 0.31057, 0.31057, + 0.35329, 0.35329, 0.35329, + 0.39600, 0.39600, 0.39600, + 0.43871, 0.43871, 0.43871, + 0.48143, 0.48143, 0.48143, + 0.52414, 0.52414, 0.52414, + 0.56686, 0.56686, 0.56686, + 0.35171, 0.35171, 0.35171, + 0.39443, 0.39443, 0.39443, + 0.43714, 0.43714, 0.43714, + 0.47986, 0.47986, 0.47986, + 0.52257, 0.52257, 0.52257, + 0.56529, 0.56529, 0.56529, + 0.60800, 0.60800, 0.60800, + 0.65071, 0.65071, 0.65071, + 0.43557, 0.43557, 0.43557, + 0.47829, 0.47829, 0.47829, + 0.52100, 0.52100, 0.52100, + 0.56371, 0.56371, 0.56371, + 0.60643, 0.60643, 0.60643, + 0.64914, 0.64914, 0.64914, + 0.69186, 0.69186, 0.69186, + 0.73457, 0.73457, 0.73457, + 0.51943, 0.51943, 0.51943, + 0.56214, 0.56214, 0.56214, + 0.60486, 0.60486, 0.60486, + 0.64757, 0.64757, 0.64757, + 0.69029, 0.69029, 0.69029, + 0.73300, 0.73300, 0.73300, + 0.77571, 0.77571, 0.77571, + 0.81843, 0.81843, 0.81843, + 0.60329, 0.60329, 0.60329, + 0.64600, 0.64600, 0.64600, + 0.68871, 0.68871, 0.68871, + 0.73143, 0.73143, 0.73143, + 0.77414, 0.77414, 0.77414, + 0.81686, 0.81686, 0.81686, + 0.85957, 0.85957, 0.85957, + 0.90229, 0.90229, 0.90229, + 0.03257, 0.03257, 0.03257, + 0.07529, 0.07529, 0.07529, + 0.11800, 0.11800, 0.11800, + 0.16071, 0.16071, 0.16071, + 0.20343, 0.20343, 0.20343, + 0.24614, 0.24614, 0.24614, + 0.28886, 0.28886, 0.28886, + 0.33157, 0.33157, 0.33157, + 0.11643, 0.11643, 0.11643, + 0.15914, 0.15914, 0.15914, + 0.20186, 0.20186, 0.20186, + 0.24457, 0.24457, 0.24457, + 0.28729, 0.28729, 0.28729, + 0.33000, 0.33000, 0.33000, + 0.37271, 0.37271, 0.37271, + 0.41543, 0.41543, 0.41543, + 0.20029, 0.20029, 0.20029, + 0.24300, 0.24300, 0.24300, + 0.28571, 0.28571, 0.28571, + 0.32843, 0.32843, 0.32843, + 0.37114, 0.37114, 0.37114, + 0.41386, 0.41386, 0.41386, + 0.45657, 0.45657, 0.45657, + 0.49929, 0.49929, 0.49929, + 0.28414, 0.28414, 0.28414, + 0.32686, 0.32686, 0.32686, + 0.36957, 0.36957, 0.36957, + 0.41229, 0.41229, 0.41229, + 0.45500, 0.45500, 0.45500, + 0.49771, 0.49771, 0.49771, + 0.54043, 0.54043, 0.54043, + 0.58314, 0.58314, 0.58314, + 0.36800, 0.36800, 0.36800, + 0.41071, 0.41071, 0.41071, + 0.45343, 0.45343, 0.45343, + 0.49614, 0.49614, 0.49614, + 0.53886, 0.53886, 0.53886, + 0.58157, 0.58157, 0.58157, + 0.62429, 0.62429, 0.62429, + 0.66700, 0.66700, 0.66700, + 0.45186, 0.45186, 0.45186, + 0.49457, 0.49457, 0.49457, + 0.53729, 0.53729, 0.53729, + 0.58000, 0.58000, 0.58000, + 0.62271, 0.62271, 0.62271, + 0.66543, 0.66543, 0.66543, + 0.70814, 0.70814, 0.70814, + 0.75086, 0.75086, 0.75086, + 0.53571, 0.53571, 0.53571, + 0.57843, 0.57843, 0.57843, + 0.62114, 0.62114, 0.62114, + 0.66386, 0.66386, 0.66386, + 0.70657, 0.70657, 0.70657, + 0.74929, 0.74929, 0.74929, + 0.79200, 0.79200, 0.79200, + 0.83471, 0.83471, 0.83471, + 0.61957, 0.61957, 0.61957, + 0.66229, 0.66229, 0.66229, + 0.70500, 0.70500, 0.70500, + 0.74771, 0.74771, 0.74771, + 0.79043, 0.79043, 0.79043, + 0.83314, 0.83314, 0.83314, + 0.87586, 0.87586, 0.87586, + 0.91857, 0.91857, 0.91857, + 0.04886, 0.04886, 0.04886, + 0.09157, 0.09157, 0.09157, + 0.13429, 0.13429, 0.13429, + 0.17700, 0.17700, 0.17700, + 0.21971, 0.21971, 0.21971, + 0.26243, 0.26243, 0.26243, + 0.30514, 0.30514, 0.30514, + 0.34786, 0.34786, 0.34786, + 0.13271, 0.13271, 0.13271, + 0.17543, 0.17543, 0.17543, + 0.21814, 0.21814, 0.21814, + 0.26086, 0.26086, 0.26086, + 0.30357, 0.30357, 0.30357, + 0.34629, 0.34629, 0.34629, + 0.38900, 0.38900, 0.38900, + 0.43171, 0.43171, 0.43171, + 0.21657, 0.21657, 0.21657, + 0.25929, 0.25929, 0.25929, + 0.30200, 0.30200, 0.30200, + 0.34471, 0.34471, 0.34471, + 0.38743, 0.38743, 0.38743, + 0.43014, 0.43014, 0.43014, + 0.47286, 0.47286, 0.47286, + 0.51557, 0.51557, 0.51557, + 0.30043, 0.30043, 0.30043, + 0.34314, 0.34314, 0.34314, + 0.38586, 0.38586, 0.38586, + 0.42857, 0.42857, 0.42857, + 0.47129, 0.47129, 0.47129, + 0.51400, 0.51400, 0.51400, + 0.55671, 0.55671, 0.55671, + 0.59943, 0.59943, 0.59943, + 0.38429, 0.38429, 0.38429, + 0.42700, 0.42700, 0.42700, + 0.46971, 0.46971, 0.46971, + 0.51243, 0.51243, 0.51243, + 0.55514, 0.55514, 0.55514, + 0.59786, 0.59786, 0.59786, + 0.64057, 0.64057, 0.64057, + 0.68329, 0.68329, 0.68329, + 0.46814, 0.46814, 0.46814, + 0.51086, 0.51086, 0.51086, + 0.55357, 0.55357, 0.55357, + 0.59629, 0.59629, 0.59629, + 0.63900, 0.63900, 0.63900, + 0.68171, 0.68171, 0.68171, + 0.72443, 0.72443, 0.72443, + 0.76714, 0.76714, 0.76714, + 0.55200, 0.55200, 0.55200, + 0.59471, 0.59471, 0.59471, + 0.63743, 0.63743, 0.63743, + 0.68014, 0.68014, 0.68014, + 0.72286, 0.72286, 0.72286, + 0.76557, 0.76557, 0.76557, + 0.80829, 0.80829, 0.80829, + 0.85100, 0.85100, 0.85100, + 0.63586, 0.63586, 0.63586, + 0.67857, 0.67857, 0.67857, + 0.72129, 0.72129, 0.72129, + 0.76400, 0.76400, 0.76400, + 0.80671, 0.80671, 0.80671, + 0.84943, 0.84943, 0.84943, + 0.89214, 0.89214, 0.89214, + 0.93486, 0.93486, 0.93486, + 0.06514, 0.06514, 0.06514, + 0.10786, 0.10786, 0.10786, + 0.15057, 0.15057, 0.15057, + 0.19329, 0.19329, 0.19329, + 0.23600, 0.23600, 0.23600, + 0.27871, 0.27871, 0.27871, + 0.32143, 0.32143, 0.32143, + 0.36414, 0.36414, 0.36414, + 0.14900, 0.14900, 0.14900, + 0.19171, 0.19171, 0.19171, + 0.23443, 0.23443, 0.23443, + 0.27714, 0.27714, 0.27714, + 0.31986, 0.31986, 0.31986, + 0.36257, 0.36257, 0.36257, + 0.40529, 0.40529, 0.40529, + 0.44800, 0.44800, 0.44800, + 0.23286, 0.23286, 0.23286, + 0.27557, 0.27557, 0.27557, + 0.31829, 0.31829, 0.31829, + 0.36100, 0.36100, 0.36100, + 0.40371, 0.40371, 0.40371, + 0.44643, 0.44643, 0.44643, + 0.48914, 0.48914, 0.48914, + 0.53186, 0.53186, 0.53186, + 0.31671, 0.31671, 0.31671, + 0.35943, 0.35943, 0.35943, + 0.40214, 0.40214, 0.40214, + 0.44486, 0.44486, 0.44486, + 0.48757, 0.48757, 0.48757, + 0.53029, 0.53029, 0.53029, + 0.57300, 0.57300, 0.57300, + 0.61571, 0.61571, 0.61571, + 0.40057, 0.40057, 0.40057, + 0.44329, 0.44329, 0.44329, + 0.48600, 0.48600, 0.48600, + 0.52871, 0.52871, 0.52871, + 0.57143, 0.57143, 0.57143, + 0.61414, 0.61414, 0.61414, + 0.65686, 0.65686, 0.65686, + 0.69957, 0.69957, 0.69957, + 0.48443, 0.48443, 0.48443, + 0.52714, 0.52714, 0.52714, + 0.56986, 0.56986, 0.56986, + 0.61257, 0.61257, 0.61257, + 0.65529, 0.65529, 0.65529, + 0.69800, 0.69800, 0.69800, + 0.74071, 0.74071, 0.74071, + 0.78343, 0.78343, 0.78343, + 0.56829, 0.56829, 0.56829, + 0.61100, 0.61100, 0.61100, + 0.65371, 0.65371, 0.65371, + 0.69643, 0.69643, 0.69643, + 0.73914, 0.73914, 0.73914, + 0.78186, 0.78186, 0.78186, + 0.82457, 0.82457, 0.82457, + 0.86729, 0.86729, 0.86729, + 0.65214, 0.65214, 0.65214, + 0.69486, 0.69486, 0.69486, + 0.73757, 0.73757, 0.73757, + 0.78029, 0.78029, 0.78029, + 0.82300, 0.82300, 0.82300, + 0.86571, 0.86571, 0.86571, + 0.90843, 0.90843, 0.90843, + 0.95114, 0.95114, 0.95114, + 0.08143, 0.08143, 0.08143, + 0.12414, 0.12414, 0.12414, + 0.16686, 0.16686, 0.16686, + 0.20957, 0.20957, 0.20957, + 0.25229, 0.25229, 0.25229, + 0.29500, 0.29500, 0.29500, + 0.33771, 0.33771, 0.33771, + 0.38043, 0.38043, 0.38043, + 0.16529, 0.16529, 0.16529, + 0.20800, 0.20800, 0.20800, + 0.25071, 0.25071, 0.25071, + 0.29343, 0.29343, 0.29343, + 0.33614, 0.33614, 0.33614, + 0.37886, 0.37886, 0.37886, + 0.42157, 0.42157, 0.42157, + 0.46429, 0.46429, 0.46429, + 0.24914, 0.24914, 0.24914, + 0.29186, 0.29186, 0.29186, + 0.33457, 0.33457, 0.33457, + 0.37729, 0.37729, 0.37729, + 0.42000, 0.42000, 0.42000, + 0.46271, 0.46271, 0.46271, + 0.50543, 0.50543, 0.50543, + 0.54814, 0.54814, 0.54814, + 0.33300, 0.33300, 0.33300, + 0.37571, 0.37571, 0.37571, + 0.41843, 0.41843, 0.41843, + 0.46114, 0.46114, 0.46114, + 0.50386, 0.50386, 0.50386, + 0.54657, 0.54657, 0.54657, + 0.58929, 0.58929, 0.58929, + 0.63200, 0.63200, 0.63200, + 0.41686, 0.41686, 0.41686, + 0.45957, 0.45957, 0.45957, + 0.50229, 0.50229, 0.50229, + 0.54500, 0.54500, 0.54500, + 0.58771, 0.58771, 0.58771, + 0.63043, 0.63043, 0.63043, + 0.67314, 0.67314, 0.67314, + 0.71586, 0.71586, 0.71586, + 0.50071, 0.50071, 0.50071, + 0.54343, 0.54343, 0.54343, + 0.58614, 0.58614, 0.58614, + 0.62886, 0.62886, 0.62886, + 0.67157, 0.67157, 0.67157, + 0.71429, 0.71429, 0.71429, + 0.75700, 0.75700, 0.75700, + 0.79971, 0.79971, 0.79971, + 0.58457, 0.58457, 0.58457, + 0.62729, 0.62729, 0.62729, + 0.67000, 0.67000, 0.67000, + 0.71271, 0.71271, 0.71271, + 0.75543, 0.75543, 0.75543, + 0.79814, 0.79814, 0.79814, + 0.84086, 0.84086, 0.84086, + 0.88357, 0.88357, 0.88357, + 0.66843, 0.66843, 0.66843, + 0.71114, 0.71114, 0.71114, + 0.75386, 0.75386, 0.75386, + 0.79657, 0.79657, 0.79657, + 0.83929, 0.83929, 0.83929, + 0.88200, 0.88200, 0.88200, + 0.92471, 0.92471, 0.92471, + 0.96743, 0.96743, 0.96743, + 0.09771, 0.09771, 0.09771, + 0.14043, 0.14043, 0.14043, + 0.18314, 0.18314, 0.18314, + 0.22586, 0.22586, 0.22586, + 0.26857, 0.26857, 0.26857, + 0.31129, 0.31129, 0.31129, + 0.35400, 0.35400, 0.35400, + 0.39671, 0.39671, 0.39671, + 0.18157, 0.18157, 0.18157, + 0.22429, 0.22429, 0.22429, + 0.26700, 0.26700, 0.26700, + 0.30971, 0.30971, 0.30971, + 0.35243, 0.35243, 0.35243, + 0.39514, 0.39514, 0.39514, + 0.43786, 0.43786, 0.43786, + 0.48057, 0.48057, 0.48057, + 0.26543, 0.26543, 0.26543, + 0.30814, 0.30814, 0.30814, + 0.35086, 0.35086, 0.35086, + 0.39357, 0.39357, 0.39357, + 0.43629, 0.43629, 0.43629, + 0.47900, 0.47900, 0.47900, + 0.52171, 0.52171, 0.52171, + 0.56443, 0.56443, 0.56443, + 0.34929, 0.34929, 0.34929, + 0.39200, 0.39200, 0.39200, + 0.43471, 0.43471, 0.43471, + 0.47743, 0.47743, 0.47743, + 0.52014, 0.52014, 0.52014, + 0.56286, 0.56286, 0.56286, + 0.60557, 0.60557, 0.60557, + 0.64829, 0.64829, 0.64829, + 0.43314, 0.43314, 0.43314, + 0.47586, 0.47586, 0.47586, + 0.51857, 0.51857, 0.51857, + 0.56129, 0.56129, 0.56129, + 0.60400, 0.60400, 0.60400, + 0.64671, 0.64671, 0.64671, + 0.68943, 0.68943, 0.68943, + 0.73214, 0.73214, 0.73214, + 0.51700, 0.51700, 0.51700, + 0.55971, 0.55971, 0.55971, + 0.60243, 0.60243, 0.60243, + 0.64514, 0.64514, 0.64514, + 0.68786, 0.68786, 0.68786, + 0.73057, 0.73057, 0.73057, + 0.77329, 0.77329, 0.77329, + 0.81600, 0.81600, 0.81600, + 0.60086, 0.60086, 0.60086, + 0.64357, 0.64357, 0.64357, + 0.68629, 0.68629, 0.68629, + 0.72900, 0.72900, 0.72900, + 0.77171, 0.77171, 0.77171, + 0.81443, 0.81443, 0.81443, + 0.85714, 0.85714, 0.85714, + 0.89986, 0.89986, 0.89986, + 0.68471, 0.68471, 0.68471, + 0.72743, 0.72743, 0.72743, + 0.77014, 0.77014, 0.77014, + 0.81286, 0.81286, 0.81286, + 0.85557, 0.85557, 0.85557, + 0.89829, 0.89829, 0.89829, + 0.94100, 0.94100, 0.94100, + 0.98371, 0.98371, 0.98371, + 0.11400, 0.11400, 0.11400, + 0.15671, 0.15671, 0.15671, + 0.19943, 0.19943, 0.19943, + 0.24214, 0.24214, 0.24214, + 0.28486, 0.28486, 0.28486, + 0.32757, 0.32757, 0.32757, + 0.37029, 0.37029, 0.37029, + 0.41300, 0.41300, 0.41300, + 0.19786, 0.19786, 0.19786, + 0.24057, 0.24057, 0.24057, + 0.28329, 0.28329, 0.28329, + 0.32600, 0.32600, 0.32600, + 0.36871, 0.36871, 0.36871, + 0.41143, 0.41143, 0.41143, + 0.45414, 0.45414, 0.45414, + 0.49686, 0.49686, 0.49686, + 0.28171, 0.28171, 0.28171, + 0.32443, 0.32443, 0.32443, + 0.36714, 0.36714, 0.36714, + 0.40986, 0.40986, 0.40986, + 0.45257, 0.45257, 0.45257, + 0.49529, 0.49529, 0.49529, + 0.53800, 0.53800, 0.53800, + 0.58071, 0.58071, 0.58071, + 0.36557, 0.36557, 0.36557, + 0.40829, 0.40829, 0.40829, + 0.45100, 0.45100, 0.45100, + 0.49371, 0.49371, 0.49371, + 0.53643, 0.53643, 0.53643, + 0.57914, 0.57914, 0.57914, + 0.62186, 0.62186, 0.62186, + 0.66457, 0.66457, 0.66457, + 0.44943, 0.44943, 0.44943, + 0.49214, 0.49214, 0.49214, + 0.53486, 0.53486, 0.53486, + 0.57757, 0.57757, 0.57757, + 0.62029, 0.62029, 0.62029, + 0.66300, 0.66300, 0.66300, + 0.70571, 0.70571, 0.70571, + 0.74843, 0.74843, 0.74843, + 0.53329, 0.53329, 0.53329, + 0.57600, 0.57600, 0.57600, + 0.61871, 0.61871, 0.61871, + 0.66143, 0.66143, 0.66143, + 0.70414, 0.70414, 0.70414, + 0.74686, 0.74686, 0.74686, + 0.78957, 0.78957, 0.78957, + 0.83229, 0.83229, 0.83229, + 0.61714, 0.61714, 0.61714, + 0.65986, 0.65986, 0.65986, + 0.70257, 0.70257, 0.70257, + 0.74529, 0.74529, 0.74529, + 0.78800, 0.78800, 0.78800, + 0.83071, 0.83071, 0.83071, + 0.87343, 0.87343, 0.87343, + 0.91614, 0.91614, 0.91614, + 0.70100, 0.70100, 0.70100, + 0.74371, 0.74371, 0.74371, + 0.78643, 0.78643, 0.78643, + 0.82914, 0.82914, 0.82914, + 0.87186, 0.87186, 0.87186, + 0.91457, 0.91457, 0.91457, + 0.95729, 0.95729, 0.95729, + 1.00000, 1.00000, 1.00000 + }; + + // check cube data + for(unsigned int i = 0; i < looklut->lut3D->lut.size(); ++i) { + OIIO_CHECK_CLOSE(cube[i], looklut->lut3D->lut[i], 1e-4); + } +} + + +OIIO_ADD_TEST(FileFormatIridasLook, fail_on_mask) +{ + std::ostringstream strebuf; + strebuf << "<?xml version=\"1.0\" ?>" << "\n"; + strebuf << "<look>" << "\n"; + strebuf << " <shaders>" << "\n"; + strebuf << " <base>" << "\n"; + strebuf << " <rangeversion>\"2\"</rangeversion>" << "\n"; + strebuf << " <visible>\"1\"</visible>" << "\n"; + strebuf << " <sublayer0>" << "\n"; + strebuf << " <opacity>\"1\"</opacity>" << "\n"; + strebuf << " <parameters>" << "\n"; + strebuf << " <LogPrinterLights>\"N1\"</LogPrinterLights>" << "\n"; + strebuf << " </parameters>" << "\n"; + strebuf << " </sublayer0>" << "\n"; + strebuf << " <sublayer3>" << "\n"; + strebuf << " <opacity>\"1\"</opacity>" << "\n"; + strebuf << " <parameters>" << "\n"; + strebuf << " <gamma.Z>\"0.49967\"</gamma.Z>" << "\n"; + strebuf << " <gain.Z>\"0.28739\"</gain.Z>" << "\n"; + strebuf << " <gamma.Y>\"0.49179\"</gamma.Y>" << "\n"; + strebuf << " <gain.Y>\"0.22243\"</gain.Y>" << "\n"; + strebuf << " <gain.X>\"0.34531\"</gain.X>" << "\n"; + strebuf << " <gamma.X>\"0.39388\"</gamma.X>" << "\n"; + strebuf << " </parameters>" << "\n"; + strebuf << " </sublayer3>" << "\n"; + strebuf << " </base>" << "\n"; + strebuf << " </shaders>" << "\n"; + strebuf << " <mask>" << "\n"; + strebuf << " <name>\"Untitled00_00_00_00\"</name>" << "\n"; + strebuf << " <activecontour>\"0\"</activecontour>" << "\n"; + strebuf << " <width>\"1024\"</width>" << "\n"; + strebuf << " <height>\"778\"</height>" << "\n"; + strebuf << " <contour>" << "\n"; + strebuf << " <positive>\"1\"</positive>" << "\n"; + strebuf << " <point>" << "\n"; + strebuf << " <inner>\"catmull-rom,value:317.5,583.5@0\"</inner>" << "\n"; + strebuf << " <innerprevtangent>\"catmull-rom,value:0,0@0\"</innerprevtangent>" << "\n"; + strebuf << " <innernexttangent>\"catmull-rom,value:0,0@0\"</innernexttangent>" << "\n"; + strebuf << " <falloffexponent>\"catmull-rom,value:1@0\"</falloffexponent>" << "\n"; + strebuf << " <falloffweight>\"catmull-rom,value:0.5@0\"</falloffweight>" << "\n"; + strebuf << " <detached>linear,value:0@0</detached>" << "\n"; + strebuf << " <outer>\"catmull-rom,value:317.5,583.5@0\"</outer>" << "\n"; + strebuf << " <outerprevtangent>\"catmull-rom,value:0,0@0\"</outerprevtangent>" << "\n"; + strebuf << " <outernexttangent>\"catmull-rom,value:0,0@0\"</outernexttangent>" << "\n"; + strebuf << " <spline>\"linear,value:0@0\"</spline>" << "\n"; + strebuf << " <smooth>\"linear,value:0@0\"</smooth>" << "\n"; + strebuf << " </point>" << "\n"; + strebuf << " </contour>" << "\n"; + strebuf << " </mask>" << "\n"; + strebuf << " <LUT>" << "\n"; + strebuf << " <size>\"8\"</size>" << "\n"; + strebuf << " <data>\"" << "\n"; + strebuf << " 000000000000000000000000878B933D000000000000000057BC563E00000000" << "\n"; + strebuf << " ...truncated, should never be parsed due to mask section\"" << "\n"; + strebuf << " </data>" << "\n"; + strebuf << " </LUT>" << "\n"; + strebuf << "</look>" << "\n"; + + std::istringstream infile; + infile.str(strebuf.str()); + + // Read file + LocalFileFormat tester; + try + { + CachedFileRcPtr cachedFile = tester.Read(infile); + OIIO_CHECK_ASSERT(false); // Fail test if previous line doesn't throw Exception + } + catch(Exception& e) + { + // Check exception message is correct error (not something + // like "cannot parse LUT data") + std::string expected_error = "Cannot load .look LUT containing mask"; + OIIO_CHECK_EQUAL(e.what(), expected_error); + } + +} +#endif // OCIO_UNIT_TEST diff --git a/src/core/FileFormatPandora.cpp b/src/core/FileFormatPandora.cpp new file mode 100644 index 0000000..eef68d2 --- /dev/null +++ b/src/core/FileFormatPandora.cpp @@ -0,0 +1,286 @@ +/* +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" + +OCIO_NAMESPACE_ENTER +{ + namespace + { + class LocalCachedFile : public CachedFile + { + public: + LocalCachedFile () + { + lut3D = Lut3D::Create(); + }; + ~LocalCachedFile() {}; + + 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 = "pandora_mga"; + info.extension = "mga"; + info.capabilities = FORMAT_CAPABILITY_READ; + formatInfoVec.push_back(info); + + FormatInfo info2; + info2.name = "pandora_m3d"; + info2.extension = "m3d"; + info2.capabilities = FORMAT_CAPABILITY_READ; + formatInfoVec.push_back(info2); + } + + CachedFileRcPtr + LocalFileFormat::Read(std::istream & istream) const + { + // this shouldn't happen + if(!istream) + { + throw Exception ("File stream empty when trying to read Pandora lut"); + } + + // Validate the file type + std::string line; + + // Parse the file + std::string format; + int lutEdgeLen = 0; + int outputBitDepthMaxValue = 0; + std::vector<int> raw3d; + + { + std::vector<std::string> parts; + std::vector<int> tmpints; + bool inLut3d = false; + + while(nextline(istream, line)) + { + // Strip, lowercase, and split the line + pystring::split(pystring::lower(pystring::strip(line)), parts); + if(parts.empty()) continue; + + // Skip all lines starting with '#' + if(pystring::startswith(parts[0],"#")) continue; + + if(parts[0] == "channel") + { + if(parts.size() != 2 || pystring::lower(parts[1]) != "3d") + { + throw Exception("Parse error in Pandora lut. Only 3d luts are currently supported. (channel: 3d)."); + } + } + else if(parts[0] == "in") + { + int inval = 0; + if(parts.size() != 2 || !StringToInt( &inval, parts[1].c_str())) + { + throw Exception("Malformed 'in' tag in Pandora lut."); + } + raw3d.reserve(inval*3); + lutEdgeLen = Get3DLutEdgeLenFromNumPixels(inval); + } + else if(parts[0] == "out") + { + if(parts.size() != 2 || !StringToInt( &outputBitDepthMaxValue, parts[1].c_str())) + { + throw Exception("Malformed 'out' tag in Pandora lut."); + } + } + else if(parts[0] == "format") + { + if(parts.size() != 2 || pystring::lower(parts[1]) != "lut") + { + throw Exception("Parse error in Pandora lut. Only luts are currently supported. (format: lut)."); + } + } + else if(parts[0] == "values") + { + if(parts.size() != 4 || + pystring::lower(parts[1]) != "red" || + pystring::lower(parts[2]) != "green" || + pystring::lower(parts[3]) != "blue") + { + throw Exception("Parse error in Pandora lut. Only rgb luts are currently supported. (values: red green blue)."); + } + + inLut3d = true; + } + else if(inLut3d) + { + if(!StringVecToIntVec(tmpints, parts) || tmpints.size() != 4) + { + std::ostringstream os; + os << "Parse error in Pandora lut. Expected to find 4 integers. Instead found '" + << line << "'"; + throw Exception(os.str().c_str()); + } + + raw3d.push_back(tmpints[1]); + raw3d.push_back(tmpints[2]); + raw3d.push_back(tmpints[3]); + } + } + } + + // Interpret the parsed data, validate lut sizes + if(lutEdgeLen*lutEdgeLen*lutEdgeLen != static_cast<int>(raw3d.size()/3)) + { + std::ostringstream os; + os << "Parse error in Pandora lut. "; + os << "Incorrect number of lut3d entries. "; + os << "Found " << raw3d.size()/3 << ", expected " << lutEdgeLen*lutEdgeLen*lutEdgeLen << "."; + throw Exception(os.str().c_str()); + } + + if(lutEdgeLen*lutEdgeLen*lutEdgeLen == 0) + { + std::ostringstream os; + os << "Parse error in Pandora lut. "; + os << "No 3D Lut entries found."; + throw Exception(os.str().c_str()); + } + + if(outputBitDepthMaxValue <= 0) + { + std::ostringstream os; + os << "Parse error in Pandora lut. "; + os << "A valid 'out' tag was not found."; + throw Exception(os.str().c_str()); + } + + LocalCachedFileRcPtr cachedFile = LocalCachedFileRcPtr(new LocalCachedFile()); + + // Reformat 3D data + cachedFile->lut3D->size[0] = lutEdgeLen; + cachedFile->lut3D->size[1] = lutEdgeLen; + cachedFile->lut3D->size[2] = lutEdgeLen; + cachedFile->lut3D->lut.reserve(raw3d.size()); + + float scale = 1.0f / ((float)outputBitDepthMaxValue - 1.0f); + + 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; + } + + 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()); + } + + if(newDir == TRANSFORM_DIR_FORWARD) + { + CreateLut3DOp(ops, cachedFile->lut3D, + fileTransform.getInterpolation(), newDir); + } + else if(newDir == TRANSFORM_DIR_INVERSE) + { + CreateLut3DOp(ops, cachedFile->lut3D, + fileTransform.getInterpolation(), newDir); + } + } + } + + FileFormat * CreateFileFormatPandora() + { + return new LocalFileFormat(); + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/FileFormatSpi1D.cpp b/src/core/FileFormatSpi1D.cpp new file mode 100644 index 0000000..2835c50 --- /dev/null +++ b/src/core/FileFormatSpi1D.cpp @@ -0,0 +1,261 @@ +/* +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 "pystring/pystring.h" + +#include <cstdio> +#include <sstream> + +/* +Version 1 +From -7.5 3.7555555555555555 +Components 1 +Length 4096 +{ + 0.031525943963232252 + 0.045645604561056156 + ... +} + +*/ + + +OCIO_NAMESPACE_ENTER +{ + //////////////////////////////////////////////////////////////// + + namespace + { + class LocalCachedFile : public CachedFile + { + public: + LocalCachedFile() + { + lut = Lut1D::Create(); + }; + ~LocalCachedFile() {}; + + Lut1DRcPtr lut; + }; + + 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 = "spi1d"; + info.extension = "spi1d"; + info.capabilities = FORMAT_CAPABILITY_READ; + formatInfoVec.push_back(info); + } + + // Try and load the format + // Raise an exception if it can't be loaded. + + CachedFileRcPtr LocalFileFormat::Read(std::istream & istream) const + { + Lut1DRcPtr lut1d = Lut1D::Create(); + + // Parse Header Info + int lut_size = -1; + float from_min = 0.0; + float from_max = 1.0; + int version = -1; + int components = -1; + + const int MAX_LINE_SIZE = 4096; + char lineBuffer[MAX_LINE_SIZE]; + + // PARSE HEADER INFO + { + std::string headerLine(""); + do + { + istream.getline(lineBuffer, MAX_LINE_SIZE); + headerLine = std::string(lineBuffer); + if(pystring::startswith(headerLine, "Version")) + { + if(sscanf(lineBuffer, "Version %d", &version)!=1) + throw Exception("Invalid 'Version' Tag"); + } + else if(pystring::startswith(headerLine, "From")) + { + if(sscanf(lineBuffer, "From %f %f", &from_min, &from_max)!=2) + throw Exception("Invalid 'From' Tag"); + } + else if(pystring::startswith(headerLine, "Components")) + { + if(sscanf(lineBuffer, "Components %d", &components)!=1) + throw Exception("Invalid 'Components' Tag"); + } + else if(pystring::startswith(headerLine, "Length")) + { + if(sscanf(lineBuffer, "Length %d", &lut_size)!=1) + throw Exception("Invalid 'Length' Tag"); + } + } + while (istream.good() && !pystring::startswith(headerLine,"{")); + } + + if(version == -1) + throw Exception("Could not find 'Version' Tag"); + if(version != 1) + throw Exception("Only format version 1 supported."); + if (lut_size == -1) + throw Exception("Could not find 'Length' Tag"); + if (components == -1) + throw Exception("Could not find 'Components' Tag"); + if (components<0 || components>3) + throw Exception("Components must be [1,2,3]"); + + + + for(int i=0; i<3; ++i) + { + lut1d->from_min[i] = from_min; + lut1d->from_max[i] = from_max; + lut1d->luts[i].clear(); + lut1d->luts[i].reserve(lut_size); + } + + { + istream.getline(lineBuffer, MAX_LINE_SIZE); + + int lineCount=0; + float values[4]; + + while (istream.good()) + { + // If 1 component is specificed, use x1 x1 x1 defaultA + if(components==1 && sscanf(lineBuffer,"%f",&values[0])==1) + { + lut1d->luts[0].push_back(values[0]); + lut1d->luts[1].push_back(values[0]); + lut1d->luts[2].push_back(values[0]); + ++lineCount; + } + // If 2 components are specificed, use x1 x2 0.0 + else if(components==2 && sscanf(lineBuffer,"%f %f",&values[0],&values[1])==2) + { + lut1d->luts[0].push_back(values[0]); + lut1d->luts[1].push_back(values[1]); + lut1d->luts[2].push_back(0.0); + ++lineCount; + } + // If 3 component is specificed, use x1 x2 x3 defaultA + else if(components==3 && sscanf(lineBuffer,"%f %f %f",&values[0],&values[1],&values[2])==3) + { + lut1d->luts[0].push_back(values[0]); + lut1d->luts[1].push_back(values[1]); + lut1d->luts[2].push_back(values[2]); + ++lineCount; + } + + if(lineCount == lut_size) break; + + istream.getline(lineBuffer, MAX_LINE_SIZE); + } + + if(lineCount!=lut_size) + throw Exception("Not enough entries found."); + } + + // 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 + lut1d->maxerror = 1e-5f; + lut1d->errortype = ERROR_RELATIVE; + + LocalCachedFileRcPtr cachedFile = LocalCachedFileRcPtr(new LocalCachedFile()); + cachedFile->lut = lut1d; + 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); + + if(!cachedFile) // This should never happen. + { + std::ostringstream os; + os << "Cannot build Spi1D Op. Invalid cache type."; + throw Exception(os.str().c_str()); + } + + TransformDirection newDir = CombineTransformDirections(dir, + fileTransform.getDirection()); + + CreateLut1DOp(ops, + cachedFile->lut, + fileTransform.getInterpolation(), + newDir); + } + } + + FileFormat * CreateFileFormatSpi1D() + { + return new LocalFileFormat(); + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/FileFormatSpi3D.cpp b/src/core/FileFormatSpi3D.cpp new file mode 100644 index 0000000..b1d8c69 --- /dev/null +++ b/src/core/FileFormatSpi3D.cpp @@ -0,0 +1,209 @@ +/* +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 "Lut3DOp.h" +#include "pystring/pystring.h" + +#include <cstdio> +#include <sstream> + +/* +SPILUT 1.0 +3 3 +32 32 32 +0 0 0 0.0132509 0.0158522 0.0156622 +0 0 1 0.0136178 0.018843 0.033921 +0 0 2 0.0136487 0.0240918 0.0563014 +0 0 3 0.015706 0.0303061 0.0774135 + +... entries can be in any order +... after the expected number of entries is found, file can contain anything +*/ + + +OCIO_NAMESPACE_ENTER +{ + //////////////////////////////////////////////////////////////// + + namespace + { + class LocalCachedFile : public CachedFile + { + public: + LocalCachedFile() + { + lut = Lut3D::Create(); + }; + ~LocalCachedFile() {}; + + Lut3DRcPtr lut; + }; + + 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 = "spi3d"; + info.extension = "spi3d"; + info.capabilities = FORMAT_CAPABILITY_READ; + formatInfoVec.push_back(info); + } + + CachedFileRcPtr + LocalFileFormat::Read(std::istream & istream) const + { + const int MAX_LINE_SIZE = 4096; + char lineBuffer[MAX_LINE_SIZE]; + + Lut3DRcPtr lut3d = Lut3D::Create(); + + // Read header information + istream.getline(lineBuffer, MAX_LINE_SIZE); + if(!pystring::startswith(pystring::lower(lineBuffer), "spilut")) + { + std::ostringstream os; + os << "Lut does not appear to be valid spilut format. "; + os << "Expected 'SPILUT'. Found, '" << lineBuffer << "'."; + throw Exception(os.str().c_str()); + } + + // TODO: Assert 2nd line is 3 3 + istream.getline(lineBuffer, MAX_LINE_SIZE); + + // Get LUT Size + // TODO: Error handling + int rSize, gSize, bSize; + istream.getline(lineBuffer, MAX_LINE_SIZE); + sscanf(lineBuffer, "%d %d %d", &rSize, &gSize, &bSize); + + lut3d->size[0] = rSize; + lut3d->size[1] = gSize; + lut3d->size[2] = bSize; + lut3d->lut.resize(rSize * gSize * bSize * 3); + + // Parse table + int index = 0; + int rIndex, gIndex, bIndex; + float redValue, greenValue, blueValue; + + int entriesRemaining = rSize * gSize * bSize; + + while (istream.good() && entriesRemaining > 0) + { + istream.getline(lineBuffer, MAX_LINE_SIZE); + + if (sscanf(lineBuffer, "%d %d %d %f %f %f", + &rIndex, &gIndex, &bIndex, + &redValue, &greenValue, &blueValue) == 6) + { + index = GetLut3DIndex_B(rIndex, gIndex, bIndex, + rSize, gSize, bSize); + if(index < 0 || index >= (int) lut3d->lut.size()) + { + std::ostringstream os; + os << "Cannot load .spi3d lut, data is invalid. "; + os << "A lut entry is specified ("; + os << rIndex << " " << gIndex << " " << bIndex; + os << " that falls outside of the cube."; + throw Exception(os.str().c_str()); + } + + lut3d->lut[index+0] = redValue; + lut3d->lut[index+1] = greenValue; + lut3d->lut[index+2] = blueValue; + + entriesRemaining--; + } + } + + // Have we fully populated the table? + if (entriesRemaining>0) + throw Exception("Not enough entries found."); + + LocalCachedFileRcPtr cachedFile = LocalCachedFileRcPtr(new LocalCachedFile()); + cachedFile->lut = lut3d; + 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); + + if(!cachedFile) // This should never happen. + { + std::ostringstream os; + os << "Cannot build Spi3D Op. Invalid cache type."; + throw Exception(os.str().c_str()); + } + + TransformDirection newDir = CombineTransformDirections(dir, + fileTransform.getDirection()); + + CreateLut3DOp(ops, + cachedFile->lut, + fileTransform.getInterpolation(), + newDir); + } + } + + FileFormat * CreateFileFormatSpi3D() + { + return new LocalFileFormat(); + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/FileFormatSpiMtx.cpp b/src/core/FileFormatSpiMtx.cpp new file mode 100644 index 0000000..e14f557 --- /dev/null +++ b/src/core/FileFormatSpiMtx.cpp @@ -0,0 +1,195 @@ +/* +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 "MatrixOps.h" +#include "ParseUtils.h" +#include "pystring/pystring.h" + +#include <cstdio> +#include <cstring> +#include <sstream> + + +OCIO_NAMESPACE_ENTER +{ + //////////////////////////////////////////////////////////////// + + namespace + { + class LocalCachedFile : public CachedFile + { + public: + LocalCachedFile() + { + memset(m44, 0, 16*sizeof(float)); + memset(offset4, 0, 4*sizeof(float)); + }; + ~LocalCachedFile() {}; + + float m44[16]; + float offset4[4]; + }; + + 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 = "spimtx"; + info.extension = "spimtx"; + info.capabilities = FORMAT_CAPABILITY_READ; + formatInfoVec.push_back(info); + } + + CachedFileRcPtr + LocalFileFormat::Read(std::istream & istream) const + { + + // Read the entire file. + std::ostringstream fileStream; + + { + const int MAX_LINE_SIZE = 4096; + char lineBuffer[MAX_LINE_SIZE]; + + while (istream.good()) + { + istream.getline(lineBuffer, MAX_LINE_SIZE); + fileStream << std::string(lineBuffer) << " "; + } + } + + // Turn it into parts + std::vector<std::string> lineParts; + pystring::split(pystring::strip(fileStream.str()), lineParts); + if(lineParts.size() != 12) + { + std::ostringstream os; + os << "Error parsing .spimtx file. "; + os << "File must contain 12 float entries. "; + os << lineParts.size() << " found."; + throw Exception(os.str().c_str()); + } + + // Turn the parts into floats + std::vector<float> floatArray; + if(!StringVecToFloatVec(floatArray, lineParts)) + { + std::ostringstream os; + os << "Error parsing .spimtx file. "; + os << "File must contain all float entries. "; + throw Exception(os.str().c_str()); + } + + + // Put the bits in the right place + LocalCachedFileRcPtr cachedFile = LocalCachedFileRcPtr(new LocalCachedFile()); + + cachedFile->m44[0] = floatArray[0]; + cachedFile->m44[1] = floatArray[1]; + cachedFile->m44[2] = floatArray[2]; + cachedFile->m44[3] = 0.0f; + + cachedFile->m44[4] = floatArray[4]; + cachedFile->m44[5] = floatArray[5]; + cachedFile->m44[6] = floatArray[6]; + cachedFile->m44[7] = 0.0f; + + cachedFile->m44[8] = floatArray[8]; + cachedFile->m44[9] = floatArray[9]; + cachedFile->m44[10] = floatArray[10]; + cachedFile->m44[11] = 0.0f; + + cachedFile->m44[12] = 0.0f; + cachedFile->m44[13] = 0.0f; + cachedFile->m44[14] = 0.0f; + cachedFile->m44[15] = 1.0f; + + cachedFile->offset4[0] = floatArray[3] / 65535.0f; + cachedFile->offset4[1] = floatArray[7] / 65535.0f; + cachedFile->offset4[2] = floatArray[11] / 65535.0f; + cachedFile->offset4[3] = 0.0f; + + 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); + + if(!cachedFile) // This should never happen. + { + std::ostringstream os; + os << "Cannot build SpiMtx Ops. Invalid cache type."; + throw Exception(os.str().c_str()); + } + + TransformDirection newDir = CombineTransformDirections(dir, + fileTransform.getDirection()); + + CreateMatrixOffsetOp(ops, + cachedFile->m44, + cachedFile->offset4, + newDir); + } + } + + FileFormat * CreateFileFormatSpiMtx() + { + return new LocalFileFormat(); + } +} +OCIO_NAMESPACE_EXIT 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 diff --git a/src/core/FileFormatVF.cpp b/src/core/FileFormatVF.cpp new file mode 100644 index 0000000..cb46ef0 --- /dev/null +++ b/src/core/FileFormatVF.cpp @@ -0,0 +1,297 @@ +/* +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 <cstring> +#include <iostream> +#include <iterator> + +#include <OpenColorIO/OpenColorIO.h> + +#include "FileTransform.h" +#include "Lut3DOp.h" +#include "MatrixOps.h" +#include "ParseUtils.h" +#include "pystring/pystring.h" + +OCIO_NAMESPACE_ENTER +{ + namespace + { + class LocalCachedFile : public CachedFile + { + public: + LocalCachedFile () : + useMatrix(false) + { + lut3D = Lut3D::Create(); + memset(m44, 0, 16*sizeof(float)); + }; + ~LocalCachedFile() {}; + + Lut3DRcPtr lut3D; + float m44[16]; + bool useMatrix; + }; + + 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 = "nukevf"; + info.extension = "vf"; + 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 .vf lut"); + } + + // Validate the file type + std::string line; + if(!nextline(istream, line) || + !pystring::startswith(pystring::lower(line), "#inventor")) + { + throw Exception("Lut doesn't seem to be a .vf lut. Expecting '#Inventor V2.1 ascii'."); + } + + // Parse the file + std::vector<float> raw3d; + int size3d[] = { 0, 0, 0 }; + std::vector<float> global_transform; + + { + std::vector<std::string> parts; + std::vector<float> tmpfloats; + + 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; + + if(pystring::startswith(parts[0],"#")) continue; + + if(!in3d) + { + if(parts[0] == "grid_size") + { + if(parts.size() != 4 || + !StringToInt( &size3d[0], parts[1].c_str()) || + !StringToInt( &size3d[1], parts[2].c_str()) || + !StringToInt( &size3d[2], parts[3].c_str())) + { + throw Exception("Malformed grid_size tag in .vf lut."); + } + + raw3d.reserve(3*size3d[0]*size3d[1]*size3d[2]); + } + else if(parts[0] == "global_transform") + { + if(parts.size() != 17) + { + throw Exception("Malformed global_transform tag. 16 floats expected."); + } + + parts.erase(parts.begin()); // Drop the 1st entry. (the tag) + if(!StringVecToFloatVec(global_transform, parts) || global_transform.size() != 16) + { + throw Exception("Malformed global_transform tag. Could not convert to float array."); + } + } + // TODO: element_size (aka scale3) + // TODO: world_origin (aka translate3) + else if(parts[0] == "data") + { + in3d = true; + } + } + else // (in3d) + { + if(StringVecToFloatVec(tmpfloats, parts) && (tmpfloats.size() == 3)) + { + raw3d.push_back(tmpfloats[0]); + raw3d.push_back(tmpfloats[1]); + raw3d.push_back(tmpfloats[2]); + } + } + } + } + + // Interpret the parsed data, validate lut sizes + if(size3d[0]*size3d[1]*size3d[2] != static_cast<int>(raw3d.size()/3)) + { + std::ostringstream os; + os << "Parse error in .vf 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()); + } + + if(size3d[0]*size3d[1]*size3d[2] == 0) + { + std::ostringstream os; + os << "Parse error in .vf lut. "; + os << "No 3D Lut entries found."; + throw Exception(os.str().c_str()); + } + + LocalCachedFileRcPtr cachedFile = LocalCachedFileRcPtr(new LocalCachedFile()); + + // Setup the global matrix. + // (Nuke pre-scales this by the 3dlut size, so we must undo that here) + if(global_transform.size() == 16) + { + for(int i=0; i<4; ++i) + { + global_transform[4*i+0] *= static_cast<float>(size3d[0]); + global_transform[4*i+1] *= static_cast<float>(size3d[1]); + global_transform[4*i+2] *= static_cast<float>(size3d[2]); + } + + memcpy(cachedFile->m44, &global_transform[0], 16*sizeof(float)); + cachedFile->useMatrix = true; + } + + // Reformat 3D data + cachedFile->lut3D->size[0] = size3d[0]; + cachedFile->lut3D->size[1] = size3d[1]; + cachedFile->lut3D->size[2] = size3d[2]; + cachedFile->lut3D->lut.reserve(raw3d.size()); + + for(int rIndex=0; rIndex<size3d[0]; ++rIndex) + { + for(int gIndex=0; gIndex<size3d[1]; ++gIndex) + { + for(int bIndex=0; bIndex<size3d[2]; ++bIndex) + { + int i = GetLut3DIndex_B(rIndex, gIndex, bIndex, + size3d[0], size3d[1], size3d[2]); + + cachedFile->lut3D->lut.push_back(static_cast<float>(raw3d[i+0])); + cachedFile->lut3D->lut.push_back(static_cast<float>(raw3d[i+1])); + cachedFile->lut3D->lut.push_back(static_cast<float>(raw3d[i+2])); + } + } + } + + 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 .vf 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()); + } + + if(newDir == TRANSFORM_DIR_FORWARD) + { + if(cachedFile->useMatrix) + { + CreateMatrixOp(ops, cachedFile->m44, newDir); + } + + CreateLut3DOp(ops, cachedFile->lut3D, + fileTransform.getInterpolation(), newDir); + } + else if(newDir == TRANSFORM_DIR_INVERSE) + { + CreateLut3DOp(ops, cachedFile->lut3D, + fileTransform.getInterpolation(), newDir); + + if(cachedFile->useMatrix) + { + CreateMatrixOp(ops, cachedFile->m44, newDir); + } + } + } + } + + FileFormat * CreateFileFormatVF() + { + return new LocalFileFormat(); + } +} +OCIO_NAMESPACE_EXIT + + +/////////////////////////////////////////////////////////////////////////////// + +// TODO: Unit test diff --git a/src/core/FileTransform.cpp b/src/core/FileTransform.cpp new file mode 100644 index 0000000..45b5a3e --- /dev/null +++ b/src/core/FileTransform.cpp @@ -0,0 +1,579 @@ +/* +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 "Logging.h" +#include "Mutex.h" +#include "NoOps.h" +#include "PathUtils.h" +#include "pystring/pystring.h" + +#include <fstream> +#include <map> +#include <sstream> + +OCIO_NAMESPACE_ENTER +{ + FileTransformRcPtr FileTransform::Create() + { + return FileTransformRcPtr(new FileTransform(), &deleter); + } + + void FileTransform::deleter(FileTransform* t) + { + delete t; + } + + + class FileTransform::Impl + { + public: + TransformDirection dir_; + std::string src_; + std::string cccid_; + Interpolation interp_; + + Impl() : + dir_(TRANSFORM_DIR_FORWARD), + interp_(INTERP_UNKNOWN) + { } + + ~Impl() + { } + + Impl& operator= (const Impl & rhs) + { + dir_ = rhs.dir_; + src_ = rhs.src_; + cccid_ = rhs.cccid_; + interp_ = rhs.interp_; + return *this; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + + FileTransform::FileTransform() + : m_impl(new FileTransform::Impl) + { + } + + TransformRcPtr FileTransform::createEditableCopy() const + { + FileTransformRcPtr transform = FileTransform::Create(); + *(transform->m_impl) = *m_impl; + return transform; + } + + FileTransform::~FileTransform() + { + delete m_impl; + m_impl = NULL; + } + + FileTransform& FileTransform::operator= (const FileTransform & rhs) + { + *m_impl = *rhs.m_impl; + return *this; + } + + TransformDirection FileTransform::getDirection() const + { + return getImpl()->dir_; + } + + void FileTransform::setDirection(TransformDirection dir) + { + getImpl()->dir_ = dir; + } + + const char * FileTransform::getSrc() const + { + return getImpl()->src_.c_str(); + } + + void FileTransform::setSrc(const char * src) + { + getImpl()->src_ = src; + } + + const char * FileTransform::getCCCId() const + { + return getImpl()->cccid_.c_str(); + } + + void FileTransform::setCCCId(const char * cccid) + { + getImpl()->cccid_ = cccid; + } + + Interpolation FileTransform::getInterpolation() const + { + return getImpl()->interp_; + } + + void FileTransform::setInterpolation(Interpolation interp) + { + getImpl()->interp_ = interp; + } + + int FileTransform::getNumFormats() + { + return FormatRegistry::GetInstance().getNumFormats(FORMAT_CAPABILITY_READ); + } + + const char * FileTransform::getFormatNameByIndex(int index) + { + return FormatRegistry::GetInstance().getFormatNameByIndex(FORMAT_CAPABILITY_READ, index); + } + + const char * FileTransform::getFormatExtensionByIndex(int index) + { + return FormatRegistry::GetInstance().getFormatExtensionByIndex(FORMAT_CAPABILITY_READ, index); + } + + std::ostream& operator<< (std::ostream& os, const FileTransform& t) + { + os << "<FileTransform "; + os << "direction=" << TransformDirectionToString(t.getDirection()) << ", "; + os << "interpolation=" << InterpolationToString(t.getInterpolation()) << ", "; + os << "src='" << t.getSrc() << "', "; + os << "cccid='" << t.getCCCId() << "'"; + os << ">"; + + return os; + } + + /////////////////////////////////////////////////////////////////////////// + + // NOTE: You must be mindful when editing this function. + // to be resiliant to the static initialization order 'fiasco' + // + // See + // http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14 + // http://stackoverflow.com/questions/335369/finding-c-static-initialization-order-problems + // for more info. + + namespace + { + FormatRegistry* g_formatRegistry = NULL; + Mutex g_formatRegistryLock; + } + + FormatRegistry & FormatRegistry::GetInstance() + { + AutoMutex lock(g_formatRegistryLock); + + if(!g_formatRegistry) + { + g_formatRegistry = new FormatRegistry(); + } + + return *g_formatRegistry; + } + + FormatRegistry::FormatRegistry() + { + registerFileFormat(CreateFileFormat3DL()); + registerFileFormat(CreateFileFormatCCC()); + registerFileFormat(CreateFileFormatCC()); + registerFileFormat(CreateFileFormatCSP()); + registerFileFormat(CreateFileFormatHDL()); + registerFileFormat(CreateFileFormatIridasItx()); + registerFileFormat(CreateFileFormatIridasCube()); + registerFileFormat(CreateFileFormatIridasLook()); + registerFileFormat(CreateFileFormatPandora()); + registerFileFormat(CreateFileFormatSpi1D()); + registerFileFormat(CreateFileFormatSpi3D()); + registerFileFormat(CreateFileFormatSpiMtx()); + registerFileFormat(CreateFileFormatTruelight()); + registerFileFormat(CreateFileFormatVF()); + } + + FormatRegistry::~FormatRegistry() + { + } + + FileFormat* FormatRegistry::getFileFormatByName(const std::string & name) const + { + FileFormatMap::const_iterator iter = m_formatsByName.find( + pystring::lower(name)); + if(iter != m_formatsByName.end()) + return iter->second; + return NULL; + } + + FileFormat* FormatRegistry::getFileFormatForExtension(const std::string & extension) const + { + FileFormatMap::const_iterator iter = m_formatsByExtension.find( + pystring::lower(extension)); + if(iter != m_formatsByExtension.end()) + return iter->second; + return NULL; + } + + void FormatRegistry::registerFileFormat(FileFormat* format) + { + FormatInfoVec formatInfoVec; + format->GetFormatInfo(formatInfoVec); + + if(formatInfoVec.empty()) + { + std::ostringstream os; + os << "FileFormat Registry error. "; + os << "A file format did not provide the required format info."; + throw Exception(os.str().c_str()); + } + + for(unsigned int i=0; i<formatInfoVec.size(); ++i) + { + if(formatInfoVec[i].capabilities == FORMAT_CAPABILITY_NONE) + { + std::ostringstream os; + os << "FileFormat Registry error. "; + os << "A file format does not define either reading or writing."; + throw Exception(os.str().c_str()); + } + + if(getFileFormatByName(formatInfoVec[i].name)) + { + std::ostringstream os; + os << "Cannot register multiple file formats named, '"; + os << formatInfoVec[i].name << "'."; + throw Exception(os.str().c_str()); + } + + m_formatsByName[formatInfoVec[i].name] = format; + + // For now, dont worry if multiple formats register the same extension + // TODO: keep track of all of em! (make the value a vector) + m_formatsByExtension[formatInfoVec[i].extension] = format; + + if(formatInfoVec[i].capabilities & FORMAT_CAPABILITY_READ) + { + m_readFormatNames.push_back(formatInfoVec[i].name); + m_readFormatExtensions.push_back(formatInfoVec[i].extension); + } + + if(formatInfoVec[i].capabilities & FORMAT_CAPABILITY_WRITE) + { + m_writeFormatNames.push_back(formatInfoVec[i].name); + m_writeFormatExtensions.push_back(formatInfoVec[i].extension); + } + } + + m_rawFormats.push_back(format); + } + + int FormatRegistry::getNumRawFormats() const + { + return static_cast<int>(m_rawFormats.size()); + } + + FileFormat* FormatRegistry::getRawFormatByIndex(int index) const + { + if(index<0 || index>=getNumRawFormats()) + { + return NULL; + } + + return m_rawFormats[index]; + } + + int FormatRegistry::getNumFormats(int capability) const + { + if(capability == FORMAT_CAPABILITY_READ) + { + return static_cast<int>(m_readFormatNames.size()); + } + else if(capability == FORMAT_CAPABILITY_WRITE) + { + return static_cast<int>(m_writeFormatNames.size()); + } + return 0; + } + + const char * FormatRegistry::getFormatNameByIndex(int capability, int index) const + { + if(capability == FORMAT_CAPABILITY_READ) + { + if(index<0 || index>=static_cast<int>(m_readFormatNames.size())) + { + return ""; + } + return m_readFormatNames[index].c_str(); + } + else if(capability == FORMAT_CAPABILITY_WRITE) + { + if(index<0 || index>=static_cast<int>(m_readFormatNames.size())) + { + return ""; + } + return m_writeFormatNames[index].c_str(); + } + return ""; + } + + const char * FormatRegistry::getFormatExtensionByIndex(int capability, int index) const + { + if(capability == FORMAT_CAPABILITY_READ) + { + if(index<0 || index>=static_cast<int>(m_readFormatExtensions.size())) + { + return ""; + } + return m_readFormatExtensions[index].c_str(); + } + else if(capability == FORMAT_CAPABILITY_WRITE) + { + if(index<0 || index>=static_cast<int>(m_writeFormatExtensions.size())) + { + return ""; + } + return m_writeFormatExtensions[index].c_str(); + } + return ""; + } + + /////////////////////////////////////////////////////////////////////////// + + FileFormat::~FileFormat() + { + + } + + std::string FileFormat::getName() const + { + FormatInfoVec infoVec; + GetFormatInfo(infoVec); + if(infoVec.size()>0) + { + return infoVec[0].name; + } + return "Unknown Format"; + } + + + + void FileFormat::Write(const Baker & /*baker*/, + const std::string & formatName, + std::ostream & /*ostream*/) const + { + std::ostringstream os; + os << "Format " << formatName << " does not support writing."; + throw Exception(os.str().c_str()); + } + + namespace + { + typedef std::pair<FileFormat*, CachedFileRcPtr> FileCachePair; + typedef std::map<std::string, FileCachePair> FileCacheMap; + + FileCacheMap g_fileCache; + Mutex g_fileCacheLock; + + // Get the FileFormat, CachedFilePtr + // or throw an exception. + + FileCachePair GetFile(const std::string & filepath) + { + // Acquire fileCache mutex + AutoMutex lock(g_fileCacheLock); + + FileCacheMap::iterator iter = g_fileCache.find(filepath); + if(iter != g_fileCache.end()) + { + return iter->second; + } + + // We did not find the file in the cache; let's read it. + { + std::ostringstream os; + os << "Opening " << filepath; + LogDebug(os.str()); + } + + // Open the filePath + std::ifstream filestream; + filestream.open(filepath.c_str(), std::ios_base::in); + if (!filestream.good()) + { + std::ostringstream os; + os << "The specified FileTransform srcfile, '"; + os << filepath <<"', could not be opened. "; + os << "Please confirm the file exists with appropriate read"; + os << " permissions."; + throw Exception(os.str().c_str()); + } + + + // Try the initial format. + std::string primaryErrorText; + std::string root, extension; + pystring::os::path::splitext(root, extension, filepath); + extension = pystring::replace(extension,".","",1); // remove the leading '.' + + FileFormat * primaryFormat = FormatRegistry::GetInstance().getFileFormatForExtension(extension); + + if(primaryFormat) + { + try + { + CachedFileRcPtr cachedFile = primaryFormat->Read(filestream); + + // Add the result to our cache, return it. + FileCachePair pair = std::make_pair(primaryFormat, cachedFile); + g_fileCache[filepath] = pair; + + if(IsDebugLoggingEnabled()) + { + std::ostringstream os; + os << " Loaded primary format "; + os << primaryFormat->getName(); + LogDebug(os.str()); + } + + return pair; + } + catch(std::exception & e) + { + primaryErrorText = e.what(); + filestream.clear(); + filestream.seekg( std::ifstream::beg ); + + if(IsDebugLoggingEnabled()) + { + std::ostringstream os; + os << " Failed primary format "; + os << primaryFormat->getName(); + os << ": " << e.what(); + LogDebug(os.str()); + } + } + } + + // If this fails, try all other formats + FormatRegistry & formats = FormatRegistry::GetInstance(); + for(int findex = 0; findex<formats.getNumRawFormats(); ++findex) + { + FileFormat * altFormat = formats.getRawFormatByIndex(findex); + + // Dont bother trying the primaryFormat twice. + if(altFormat == primaryFormat) continue; + + try + { + CachedFileRcPtr cachedFile = altFormat->Read(filestream); + + // Add the result to our cache, return it. + FileCachePair pair = std::make_pair(altFormat, cachedFile); + g_fileCache[filepath] = pair; + + if(IsDebugLoggingEnabled()) + { + std::ostringstream os; + os << " Loaded alt format "; + os << altFormat->getName(); + LogDebug(os.str()); + } + + return pair; + } + catch(std::exception & e) + { + filestream.clear(); + filestream.seekg( std::ifstream::beg ); + + if(IsDebugLoggingEnabled()) + { + std::ostringstream os; + os << " Failed alt format "; + os << altFormat->getName(); + os << ": " << e.what(); + LogDebug(os.str()); + } + } + } + + // No formats succeeded. Error out with a sensible message. + if(primaryFormat) + { + std::ostringstream os; + os << "The specified transform file '"; + os << filepath <<"' could not be loaded. "; + os << primaryErrorText; + + throw Exception(os.str().c_str()); + } + else + { + std::ostringstream os; + os << "The specified transform file '"; + os << filepath <<"' does not appear to be a valid, known LUT file format."; + throw Exception(os.str().c_str()); + } + } + } + + void ClearFileTransformCaches() + { + AutoMutex lock(g_fileCacheLock); + g_fileCache.clear(); + } + + void BuildFileOps(OpRcPtrVec & ops, + const Config& config, + const ConstContextRcPtr & context, + const FileTransform& fileTransform, + TransformDirection dir) + { + std::string src = fileTransform.getSrc(); + if(src.empty()) + { + std::ostringstream os; + os << "The transform file has not been specified."; + throw Exception(os.str().c_str()); + } + + std::string filepath = context->resolveFileLocation(src.c_str()); + CreateFileNoOp(ops, filepath); + + FileCachePair cachePair = GetFile(filepath); + FileFormat* format = cachePair.first; + CachedFileRcPtr cachedFile = cachePair.second; + + format->BuildFileOps(ops, + config, context, + cachedFile, fileTransform, + dir); + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/FileTransform.h b/src/core/FileTransform.h new file mode 100644 index 0000000..3c8f070 --- /dev/null +++ b/src/core/FileTransform.h @@ -0,0 +1,156 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_FILETRANSFORM_H +#define INCLUDED_OCIO_FILETRANSFORM_H + +#include <map> + +#include <OpenColorIO/OpenColorIO.h> + +#include "Op.h" +#include "Processor.h" + +OCIO_NAMESPACE_ENTER +{ + void ClearFileTransformCaches(); + + class CachedFile + { + public: + CachedFile() {}; + virtual ~CachedFile() {}; + }; + + typedef OCIO_SHARED_PTR<CachedFile> CachedFileRcPtr; + + const int FORMAT_CAPABILITY_NONE = 0; + const int FORMAT_CAPABILITY_READ = 1; + const int FORMAT_CAPABILITY_WRITE = 2; + const int FORMAT_CAPABILITY_ALL = (FORMAT_CAPABILITY_READ | FORMAT_CAPABILITY_WRITE); + + struct FormatInfo + { + std::string name; // name must be globally unique + std::string extension; // extension does not need to be unique + int capabilities; + + FormatInfo(): + capabilities(FORMAT_CAPABILITY_NONE) + { } + }; + + typedef std::vector<FormatInfo> FormatInfoVec; + + class FileFormat + { + public: + virtual ~FileFormat(); + + virtual void GetFormatInfo(FormatInfoVec & formatInfoVec) const = 0; + + virtual CachedFileRcPtr Read(std::istream & istream) const = 0; + + 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 cachedFile, + const FileTransform & fileTransform, + TransformDirection dir) const = 0; + + // For logging purposes + std::string getName() const; + private: + FileFormat& operator= (const FileFormat &); + }; + + typedef std::map<std::string, FileFormat*> FileFormatMap; + typedef std::vector<FileFormat*> FileFormatVector; + + // TODO: This interface is ugly. What private API is actually appropriate? + // Maybe, instead of exposing the raw formats, we wrap it? + // FileCachePair GetFile(const std::string & filepath) and all + // lookups will move internal + + class FormatRegistry + { + public: + static FormatRegistry & GetInstance(); + + // TODO: Make these return a vector of possible formats + FileFormat* getFileFormatByName(const std::string & name) const; + FileFormat* getFileFormatForExtension(const std::string & extension) const; + + int getNumRawFormats() const; + FileFormat* getRawFormatByIndex(int index) const; + + int getNumFormats(int capability) const; + const char * getFormatNameByIndex(int capability, int index) const; + const char * getFormatExtensionByIndex(int capability, int index) const; + private: + FormatRegistry(); + ~FormatRegistry(); + + void registerFileFormat(FileFormat* format); + + FileFormatMap m_formatsByName; + FileFormatMap m_formatsByExtension; + FileFormatVector m_rawFormats; + + typedef std::vector<std::string> StringVec; + StringVec m_readFormatNames; + StringVec m_readFormatExtensions; + StringVec m_writeFormatNames; + StringVec m_writeFormatExtensions; + }; + + // Registry Builders + FileFormat * CreateFileFormat3DL(); + FileFormat * CreateFileFormatCCC(); + FileFormat * CreateFileFormatCC(); + FileFormat * CreateFileFormatCSP(); + FileFormat * CreateFileFormatHDL(); + FileFormat * CreateFileFormatIridasItx(); + FileFormat * CreateFileFormatIridasCube(); + FileFormat * CreateFileFormatIridasLook(); + FileFormat * CreateFileFormatPandora(); + FileFormat * CreateFileFormatSpi1D(); + FileFormat * CreateFileFormatSpi3D(); + FileFormat * CreateFileFormatSpiMtx(); + FileFormat * CreateFileFormatTruelight(); + FileFormat * CreateFileFormatVF(); + +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/GpuShaderDesc.cpp b/src/core/GpuShaderDesc.cpp new file mode 100644 index 0000000..3b20845 --- /dev/null +++ b/src/core/GpuShaderDesc.cpp @@ -0,0 +1,131 @@ +/* +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 <sstream> + +#include <OpenColorIO/OpenColorIO.h> + +#include "Mutex.h" + +OCIO_NAMESPACE_ENTER +{ + class GpuShaderDesc::Impl + { + public: + GpuLanguage language_; + std::string functionName_; + int lut3DEdgeLen_; + + mutable std::string cacheID_; + mutable Mutex cacheIDMutex_; + + Impl() : + language_(GPU_LANGUAGE_UNKNOWN), + lut3DEdgeLen_(0) + { + } + + ~Impl() + { } + + Impl& operator= (const Impl & rhs) + { + language_ = rhs.language_; + functionName_ = rhs.functionName_; + lut3DEdgeLen_ = rhs.lut3DEdgeLen_; + cacheID_ = rhs.cacheID_; + return *this; + } + }; + + + GpuShaderDesc::GpuShaderDesc() + : m_impl(new GpuShaderDesc::Impl) + { + } + + GpuShaderDesc::~GpuShaderDesc() + { + delete m_impl; + m_impl = NULL; + } + + + void GpuShaderDesc::setLanguage(GpuLanguage lang) + { + AutoMutex lock(getImpl()->cacheIDMutex_); + getImpl()->language_ = lang; + getImpl()->cacheID_ = ""; + } + + GpuLanguage GpuShaderDesc::getLanguage() const + { + return getImpl()->language_; + } + + void GpuShaderDesc::setFunctionName(const char * name) + { + AutoMutex lock(getImpl()->cacheIDMutex_); + getImpl()->functionName_ = name; + getImpl()->cacheID_ = ""; + } + + const char * GpuShaderDesc::getFunctionName() const + { + return getImpl()->functionName_.c_str(); + } + + void GpuShaderDesc::setLut3DEdgeLen(int len) + { + AutoMutex lock(getImpl()->cacheIDMutex_); + getImpl()->lut3DEdgeLen_ = len; + getImpl()->cacheID_ = ""; + } + + int GpuShaderDesc::getLut3DEdgeLen() const + { + return getImpl()->lut3DEdgeLen_; + } + + const char * GpuShaderDesc::getCacheID() const + { + AutoMutex lock(getImpl()->cacheIDMutex_); + + if(getImpl()->cacheID_.empty()) + { + std::ostringstream os; + os << GpuLanguageToString(getImpl()->language_) << " "; + os << getImpl()->functionName_ << " "; + os << getImpl()->lut3DEdgeLen_; + getImpl()->cacheID_ = os.str(); + } + + return getImpl()->cacheID_.c_str(); + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/GpuShaderUtils.cpp b/src/core/GpuShaderUtils.cpp new file mode 100644 index 0000000..cb8d67b --- /dev/null +++ b/src/core/GpuShaderUtils.cpp @@ -0,0 +1,193 @@ +/* +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 "GpuShaderUtils.h" +#include "MathUtils.h" + +#include <cmath> +#include <sstream> + +OCIO_NAMESPACE_ENTER +{ + void Write_half4x4(std::ostream & os, const float * m44, GpuLanguage lang) + { + if(lang == GPU_LANGUAGE_CG) + { + os << "half4x4("; + for(int i=0; i<16; i++) + { + if(i!=0) os << ", "; + os << ClampToNormHalf(m44[i]); + } + os << ")"; + } + else if(lang == GPU_LANGUAGE_GLSL_1_0 || lang == GPU_LANGUAGE_GLSL_1_3) + { + os << "mat4("; + for(int i=0; i<16; i++) + { + if(i!=0) os << ", "; + os << m44[i]; // Clamping to half is not necessary + } + os << ")"; + } + else + { + throw Exception("Unsupported shader language."); + } + } + + void Write_half4(std::ostream & os, const float * v4, GpuLanguage lang) + { + if(lang == GPU_LANGUAGE_CG) + { + os << "half4("; + for(int i=0; i<4; i++) + { + if(i!=0) os << ", "; + os << ClampToNormHalf(v4[i]); + } + os << ")"; + } + else if(lang == GPU_LANGUAGE_GLSL_1_0 || lang == GPU_LANGUAGE_GLSL_1_3) + { + os << "vec4("; + for(int i=0; i<4; i++) + { + if(i!=0) os << ", "; + os << v4[i]; // Clamping to half is not necessary + } + os << ")"; + } + else + { + throw Exception("Unsupported shader language."); + } + } + + void Write_half3(std::ostream & os, const float * v3, GpuLanguage lang) + { + if(lang == GPU_LANGUAGE_CG) + { + os << "half3("; + for(int i=0; i<3; i++) + { + if(i!=0) os << ", "; + os << ClampToNormHalf(v3[i]); + } + os << ")"; + } + else if(lang == GPU_LANGUAGE_GLSL_1_0 || lang == GPU_LANGUAGE_GLSL_1_3) + { + os << "vec3("; + for(int i=0; i<3; i++) + { + if(i!=0) os << ", "; + os << v3[i]; // Clamping to half is not necessary + } + os << ")"; + } + else + { + throw Exception("Unsupported shader language."); + } + } + + + + + std::string GpuTextHalf4x4(const float * m44, GpuLanguage lang) + { + std::ostringstream os; + Write_half4x4(os, m44, lang); + return os.str(); + } + + std::string GpuTextHalf4(const float * v4, GpuLanguage lang) + { + std::ostringstream os; + Write_half4(os, v4, lang); + return os.str(); + } + + std::string GpuTextHalf3(const float * v3, GpuLanguage lang) + { + std::ostringstream os; + Write_half3(os, v3, lang); + return os.str(); + } + + // Note that Cg and GLSL have opposite ordering for vec/mtx multiplication + void Write_mtx_x_vec(std::ostream & os, + const std::string & mtx, const std::string & vec, + GpuLanguage lang) + { + if(lang == GPU_LANGUAGE_CG) + { + os << "mul( " << mtx << ", " << vec << ")"; + } + else if(lang == GPU_LANGUAGE_GLSL_1_0 || lang == GPU_LANGUAGE_GLSL_1_3) + { + os << vec << " * " << mtx; + } + else + { + throw Exception("Unsupported shader language."); + } + } + + + void Write_sampleLut3D_rgb(std::ostream & os, const std::string& variableName, + const std::string& lutName, int lut3DEdgeLen, + GpuLanguage lang) + { + float m = ((float) lut3DEdgeLen-1.0f) / (float) lut3DEdgeLen; + float b = 1.0f / (2.0f * (float) lut3DEdgeLen); + + if(lang == GPU_LANGUAGE_CG) + { + os << "tex3D("; + os << lutName << ", "; + os << m << " * " << variableName << ".rgb + " << b << ").rgb;" << std::endl; + } + else if(lang == GPU_LANGUAGE_GLSL_1_0 || lang == GPU_LANGUAGE_GLSL_1_3) + { + os << "texture3D("; + os << lutName << ", "; + os << m << " * " << variableName << ".rgb + " << b << ").rgb;" << std::endl; + } + else + { + throw Exception("Unsupported shader language."); + } + } + +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/GpuShaderUtils.h b/src/core/GpuShaderUtils.h new file mode 100644 index 0000000..43d16a4 --- /dev/null +++ b/src/core/GpuShaderUtils.h @@ -0,0 +1,58 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_GPUSHADERUTILS_H +#define INCLUDED_OCIO_GPUSHADERUTILS_H + +#include <OpenColorIO/OpenColorIO.h> + +#include <sstream> + +OCIO_NAMESPACE_ENTER +{ + std::string GpuTextHalf4x4(const float * m44, GpuLanguage lang); + std::string GpuTextHalf4(const float * v4, GpuLanguage lang); + std::string GpuTextHalf3(const float * v3, GpuLanguage lang); + + void Write_mtx_x_vec(std::ostream & os, + const std::string & mtx, const std::string & vec, + GpuLanguage lang); + + void Write_half4x4(std::ostream & os, const float * m44, GpuLanguage lang); + void Write_half4(std::ostream & os, const float * v4, GpuLanguage lang); + void Write_half3(std::ostream & os, const float * v3, GpuLanguage lang); + + // returns vec3 + void Write_sampleLut3D_rgb(std::ostream & os, const std::string & variableName, + const std::string & lutName, int lut3DEdgeLen, + GpuLanguage lang); +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/GroupTransform.cpp b/src/core/GroupTransform.cpp new file mode 100644 index 0000000..344714a --- /dev/null +++ b/src/core/GroupTransform.cpp @@ -0,0 +1,196 @@ +/* +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 "OpBuilders.h" + +#include <sstream> + +OCIO_NAMESPACE_ENTER +{ + GroupTransformRcPtr GroupTransform::Create() + { + return GroupTransformRcPtr(new GroupTransform(), &deleter); + } + + void GroupTransform::deleter(GroupTransform* t) + { + delete t; + } + + namespace + { + typedef std::vector<TransformRcPtr> TransformRcPtrVec; + } + + class GroupTransform::Impl + { + public: + TransformDirection dir_; + TransformRcPtrVec vec_; + + Impl() : + dir_(TRANSFORM_DIR_FORWARD) + { } + + ~Impl() + { + vec_.clear(); + } + + Impl& operator= (const Impl & rhs) + { + dir_ = rhs.dir_; + + vec_.clear(); + + for(unsigned int i=0; i<rhs.vec_.size(); ++i) + { + vec_.push_back(rhs.vec_[i]->createEditableCopy()); + } + + return *this; + } + }; + + + + /////////////////////////////////////////////////////////////////////////// + + + + GroupTransform::GroupTransform() + : m_impl(new GroupTransform::Impl) + { + } + + TransformRcPtr GroupTransform::createEditableCopy() const + { + GroupTransformRcPtr transform = GroupTransform::Create(); + *(transform->m_impl) = *m_impl; + return transform; + } + + GroupTransform::~GroupTransform() + { + delete m_impl; + m_impl = NULL; + } + + GroupTransform& GroupTransform::operator= (const GroupTransform & rhs) + { + *m_impl = *rhs.m_impl; + return *this; + } + + TransformDirection GroupTransform::getDirection() const + { + return getImpl()->dir_; + } + + void GroupTransform::setDirection(TransformDirection dir) + { + getImpl()->dir_ = dir; + } + + int GroupTransform::size() const + { + return static_cast<int>(getImpl()->vec_.size()); + } + + ConstTransformRcPtr GroupTransform::getTransform(int index) const + { + if(index < 0 || index >= (int)getImpl()->vec_.size()) + { + std::ostringstream os; + os << "Invalid transform index " << index << "."; + throw Exception(os.str().c_str()); + } + + return getImpl()->vec_[index]; + } + + void GroupTransform::push_back(const ConstTransformRcPtr& transform) + { + getImpl()->vec_.push_back(transform->createEditableCopy()); + } + + void GroupTransform::clear() + { + getImpl()->vec_.clear(); + } + + bool GroupTransform::empty() const + { + return getImpl()->vec_.empty(); + } + + std::ostream& operator<< (std::ostream& os, const GroupTransform& groupTransform) + { + for(int i=0; i<groupTransform.size(); ++i) + { + if(i!=groupTransform.size()-1) os << "\n"; + ConstTransformRcPtr transform = groupTransform.getTransform(i); + os << "\t" << *transform; + } + return os; + } + + + /////////////////////////////////////////////////////////////////////////// + + + void BuildGroupOps(OpRcPtrVec & ops, + const Config& config, + const ConstContextRcPtr & context, + const GroupTransform& groupTransform, + TransformDirection dir) + { + TransformDirection combinedDir = CombineTransformDirections(dir, + groupTransform.getDirection()); + + if(combinedDir == TRANSFORM_DIR_FORWARD) + { + for(int i=0; i<groupTransform.size(); ++i) + { + ConstTransformRcPtr childTransform = groupTransform.getTransform(i); + BuildOps(ops, config, context, childTransform, TRANSFORM_DIR_FORWARD); + } + } + else if(combinedDir == TRANSFORM_DIR_INVERSE) + { + for(int i=groupTransform.size()-1; i>=0; --i) + { + ConstTransformRcPtr childTransform = groupTransform.getTransform(i); + BuildOps(ops, config, context, childTransform, TRANSFORM_DIR_INVERSE); + } + } + } + +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/HashUtils.cpp b/src/core/HashUtils.cpp new file mode 100644 index 0000000..5b9acd1 --- /dev/null +++ b/src/core/HashUtils.cpp @@ -0,0 +1,71 @@ +/* +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 "HashUtils.h" +#include "md5/md5.h" + +#include <sstream> +#include <iostream> + +OCIO_NAMESPACE_ENTER +{ + std::string CacheIDHash(const char * array, int size) + { + md5_state_t state; + md5_byte_t digest[16]; + + md5_init(&state); + md5_append(&state, (const md5_byte_t *)array, size); + md5_finish(&state, digest); + + return GetPrintableHash(digest); + } + + std::string GetPrintableHash(const md5_byte_t * digest) + { + static char charmap[] = "0123456789abcdef"; + + char printableResult[34]; + char *ptr = printableResult; + + // build a printable string from unprintable chars. first character + // of hashed cache IDs is '$', to later check if it's already been hashed + *ptr++ = '$'; + for (int i=0;i<16;++i) + { + *ptr++ = charmap[(digest[i] & 0x0F)]; + *ptr++ = charmap[(digest[i] >> 4)]; + } + *ptr++ = 0; + + return std::string(printableResult); + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/HashUtils.h b/src/core/HashUtils.h new file mode 100644 index 0000000..58481db --- /dev/null +++ b/src/core/HashUtils.h @@ -0,0 +1,48 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_HASHUTILS_H +#define INCLUDED_OCIO_HASHUTILS_H + +#include <OpenColorIO/OpenColorIO.h> + +#include "md5/md5.h" +#include <string> + +OCIO_NAMESPACE_ENTER +{ + std::string CacheIDHash(const char * array, int size); + + // TODO: get rid of md5.h include, make this a generic byte array + std::string GetPrintableHash(const md5_byte_t * digest); +} +OCIO_NAMESPACE_EXIT + +#endif + diff --git a/src/core/ImageDesc.cpp b/src/core/ImageDesc.cpp new file mode 100644 index 0000000..63156c8 --- /dev/null +++ b/src/core/ImageDesc.cpp @@ -0,0 +1,392 @@ +/* +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 <cstdlib> +#include <sstream> + +#include "ImagePacking.h" + +OCIO_NAMESPACE_ENTER +{ + + std::ostream& operator<< (std::ostream& os, const ImageDesc& img) + { + if(const PackedImageDesc * packedImg = dynamic_cast<const PackedImageDesc*>(&img)) + { + os << "<PackedImageDesc "; + os << "data=" << packedImg->getData() << ", "; + os << "width=" << packedImg->getWidth() << ", "; + os << "height=" << packedImg->getHeight() << ", "; + os << "numChannels=" << packedImg->getNumChannels() << ", "; + os << "chanStrideBytes=" << packedImg->getChanStrideBytes() << ", "; + os << "xStrideBytes=" << packedImg->getXStrideBytes() << ", "; + os << "yStrideBytes=" << packedImg->getYStrideBytes() << ""; + os << ">"; + } + else if(const PlanarImageDesc * planarImg = dynamic_cast<const PlanarImageDesc*>(&img)) + { + os << "<PlanarImageDesc "; + os << "rData=" << planarImg->getRData() << ", "; + os << "gData=" << planarImg->getGData() << ", "; + os << "bData=" << planarImg->getBData() << ", "; + os << "aData=" << planarImg->getAData() << ", "; + os << "width=" << packedImg->getWidth() << ", "; + os << "height=" << packedImg->getHeight() << ", "; + os << "yStrideBytes=" << planarImg->getYStrideBytes() << ""; + os << ">"; + } + else + { + os << "<UnknownImageDesc>"; + } + + return os; + } + + ImageDesc::~ImageDesc() + { + + } + + + + GenericImageDesc::GenericImageDesc(): + width(0), + height(0), + xStrideBytes(0), + yStrideBytes(0), + rData(NULL), + gData(NULL), + bData(NULL), + aData(NULL) + { }; + + + /////////////////////////////////////////////////////////////////////////// + + GenericImageDesc::~GenericImageDesc() + { }; + + void GenericImageDesc::init(const ImageDesc& img) + { + if(const PackedImageDesc * packedImg = dynamic_cast<const PackedImageDesc*>(&img)) + { + width = packedImg->getWidth(); + height = packedImg->getHeight(); + long numChannels = packedImg->getNumChannels(); + + ptrdiff_t chanStrideBytes = packedImg->getChanStrideBytes(); + xStrideBytes = packedImg->getXStrideBytes(); + yStrideBytes = packedImg->getYStrideBytes(); + + // AutoStrides will already be resolved by here, in the constructor of the ImageDesc + if(chanStrideBytes == AutoStride || + xStrideBytes == AutoStride || + yStrideBytes == AutoStride) + { + throw Exception("Malformed PackedImageDesc: Unresolved AutoStride."); + } + + rData = packedImg->getData(); + gData = reinterpret_cast<float*>( reinterpret_cast<char*>(rData) + + chanStrideBytes ); + bData = reinterpret_cast<float*>( reinterpret_cast<char*>(rData) + + 2*chanStrideBytes ); + if(numChannels >= 4) + { + aData = reinterpret_cast<float*>( reinterpret_cast<char*>(rData) + + 3*chanStrideBytes ); + } + + if(rData == NULL) + { + std::ostringstream os; + os << "PackedImageDesc Error: A null image ptr was specified."; + throw Exception(os.str().c_str()); + } + + if(width <= 0 || height <= 0) + { + std::ostringstream os; + os << "PackedImageDesc Error: Image dimensions must be positive for both x,y. '"; + os << width << "x" << height << "' is not allowed."; + throw Exception(os.str().c_str()); + } + + if(numChannels < 3) + { + std::ostringstream os; + os << "PackedImageDesc Error: Image numChannels must be three (or more) (rgb+). '"; + os << numChannels << "' is not allowed."; + throw Exception(os.str().c_str()); + } + } + else if(const PlanarImageDesc * planarImg = dynamic_cast<const PlanarImageDesc*>(&img)) + { + width = planarImg->getWidth(); + height = planarImg->getHeight(); + xStrideBytes = sizeof(float); + yStrideBytes = planarImg->getYStrideBytes(); + + // AutoStrides will already be resolved by here, in the constructor of the ImageDesc + if(yStrideBytes == AutoStride) + { + throw Exception("Malformed PlanarImageDesc: Unresolved AutoStride."); + } + + rData = planarImg->getRData(); + gData = planarImg->getGData(); + bData = planarImg->getBData(); + aData = planarImg->getAData(); + + if(width <= 0 || height <= 0) + { + std::ostringstream os; + os << "PlanarImageDesc Error: Image dimensions must be positive for both x,y. '"; + os << width << "x" << height << "' is not allowed."; + throw Exception(os.str().c_str()); + } + + if(rData == NULL || gData == NULL || bData == NULL) + { + std::ostringstream os; + os << "PlanarImageDesc Error: Valid ptrs must be passed for all 3 image rgb color channels."; + throw Exception(os.str().c_str()); + } + } + else + { + throw Exception("Unknown ImageDesc type."); + } + } + + bool GenericImageDesc::isPackedRGBA() const + { + char* rPtr = reinterpret_cast<char*>(rData); + char* gPtr = reinterpret_cast<char*>(gData); + char* bPtr = reinterpret_cast<char*>(bData); + char* aPtr = reinterpret_cast<char*>(aData); + + if(gPtr-rPtr != sizeof(float)) return false; + if(bPtr-gPtr != sizeof(float)) return false; + if(aPtr && (aPtr-bPtr != sizeof(float))) return false; + + // Confirm xStrideBytes is a pure float-sized packing + // (I.e., it will divide evenly) + if(xStrideBytes <= 0) return false; + div_t result = div((int) xStrideBytes, (int)sizeof(float)); + if(result.rem != 0) return false; + + int implicitChannels = result.quot; + if(implicitChannels != 4) return false; + + return true; + } + + + /////////////////////////////////////////////////////////////////////////// + + + class PackedImageDesc::Impl + { + public: + float * data_; + long width_; + long height_; + long numChannels_; + ptrdiff_t chanStrideBytes_; + ptrdiff_t xStrideBytes_; + ptrdiff_t yStrideBytes_; + + Impl() : + data_(NULL), + width_(0), + height_(0), + numChannels_(0), + chanStrideBytes_(0), + xStrideBytes_(0), + yStrideBytes_(0) + { + } + + ~Impl() + { } + }; + + PackedImageDesc::PackedImageDesc(float * data, + long width, long height, + long numChannels, + ptrdiff_t chanStrideBytes, + ptrdiff_t xStrideBytes, + ptrdiff_t yStrideBytes) + : m_impl(new PackedImageDesc::Impl) + { + getImpl()->data_ = data; + getImpl()->width_ = width; + getImpl()->height_ = height; + getImpl()->numChannels_ = numChannels; + getImpl()->chanStrideBytes_ = (chanStrideBytes == AutoStride) + ? sizeof(float) : chanStrideBytes; + getImpl()->xStrideBytes_ = (xStrideBytes == AutoStride) + ? sizeof(float)*numChannels : xStrideBytes; + getImpl()->yStrideBytes_ = (yStrideBytes == AutoStride) + ? sizeof(float)*width*numChannels : yStrideBytes; + } + + PackedImageDesc::~PackedImageDesc() + { + delete m_impl; + m_impl = NULL; + } + + float * PackedImageDesc::getData() const + { + return getImpl()->data_; + } + + long PackedImageDesc::getWidth() const + { + return getImpl()->width_; + } + + long PackedImageDesc::getHeight() const + { + return getImpl()->height_; + } + + long PackedImageDesc::getNumChannels() const + { + return getImpl()->numChannels_; + } + + ptrdiff_t PackedImageDesc::getChanStrideBytes() const + { + return getImpl()->chanStrideBytes_; + } + + ptrdiff_t PackedImageDesc::getXStrideBytes() const + { + return getImpl()->xStrideBytes_; + } + + ptrdiff_t PackedImageDesc::getYStrideBytes() const + { + return getImpl()->yStrideBytes_; + } + + + /////////////////////////////////////////////////////////////////////////// + + + + class PlanarImageDesc::Impl + { + public: + float * rData_; + float * gData_; + float * bData_; + float * aData_; + long width_; + long height_; + ptrdiff_t yStrideBytes_; + + Impl() : + rData_(NULL), + gData_(NULL), + bData_(NULL), + aData_(NULL), + width_(0), + height_(0), + yStrideBytes_(0) + { } + + ~Impl() + { } + }; + + + PlanarImageDesc::PlanarImageDesc(float * rData, float * gData, float * bData, float * aData, + long width, long height, + ptrdiff_t yStrideBytes) + : m_impl(new PlanarImageDesc::Impl()) + { + getImpl()->rData_ = rData; + getImpl()->gData_ = gData; + getImpl()->bData_ = bData; + getImpl()->aData_ = aData; + getImpl()->width_ = width; + getImpl()->height_ = height; + getImpl()->yStrideBytes_ = (yStrideBytes == AutoStride) + ? sizeof(float)*width : yStrideBytes; + } + + PlanarImageDesc::~PlanarImageDesc() + { + delete m_impl; + m_impl = NULL; + } + + float* PlanarImageDesc::getRData() const + { + return getImpl()->rData_; + } + + float* PlanarImageDesc::getGData() const + { + return getImpl()->gData_; + } + + float* PlanarImageDesc::getBData() const + { + return getImpl()->bData_; + } + + float* PlanarImageDesc::getAData() const + { + return getImpl()->aData_; + } + + long PlanarImageDesc::getWidth() const + { + return getImpl()->width_; + } + + long PlanarImageDesc::getHeight() const + { + return getImpl()->height_; + } + + ptrdiff_t PlanarImageDesc::getYStrideBytes() const + { + return getImpl()->yStrideBytes_; + } + +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/ImagePacking.cpp b/src/core/ImagePacking.cpp new file mode 100644 index 0000000..256d25c --- /dev/null +++ b/src/core/ImagePacking.cpp @@ -0,0 +1,407 @@ +/* +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 <sstream> +#include <iostream> +#include <cassert> +#include <cstdlib> +#include <cstring> + +#include "ImagePacking.h" + +OCIO_NAMESPACE_ENTER +{ + + namespace + { + // GENERIC CASE, SLOW BUT ALWAYS WORKS + + void PackRGBAFromImageDesc_Generic(const GenericImageDesc& srcImg, + float* outputBuffer, + int* numPixelsCopied, + int outputBufferSize, + long imagePixelStartIndex) + { + assert(outputBuffer); + assert(numPixelsCopied); + + long imgWidth = srcImg.width; + long imgHeight = srcImg.height; + long imgPixels = imgWidth * imgHeight; + + if(imagePixelStartIndex<0 || imagePixelStartIndex>=imgPixels) + { + *numPixelsCopied = 0; + return; + } + + ptrdiff_t xStrideBytes = srcImg.xStrideBytes; + ptrdiff_t yStrideBytes = srcImg.yStrideBytes; + long yIndex = imagePixelStartIndex / imgWidth; + long xIndex = imagePixelStartIndex % imgWidth; + + // Figure out our initial ptr positions + char* rRow = reinterpret_cast<char*>(srcImg.rData) + + yStrideBytes * yIndex; + char* gRow = reinterpret_cast<char*>(srcImg.gData) + + yStrideBytes * yIndex; + char* bRow = reinterpret_cast<char*>(srcImg.bData) + + yStrideBytes * yIndex; + char* aRow = NULL; + + float* rPtr = reinterpret_cast<float*>(rRow + xStrideBytes*xIndex); + float* gPtr = reinterpret_cast<float*>(gRow + xStrideBytes*xIndex); + float* bPtr = reinterpret_cast<float*>(bRow + xStrideBytes*xIndex); + float* aPtr = NULL; + + if(srcImg.aData) + { + aRow = reinterpret_cast<char*>(srcImg.aData) + yStrideBytes * yIndex; + aPtr = reinterpret_cast<float*>(aRow + xStrideBytes*xIndex); + } + + if(aPtr) + { + int pixelsCopied = 0; + while(pixelsCopied < outputBufferSize) + { + outputBuffer[4*pixelsCopied] = *rPtr; + outputBuffer[4*pixelsCopied+1] = *gPtr; + outputBuffer[4*pixelsCopied+2] = *bPtr; + outputBuffer[4*pixelsCopied+3] = *aPtr; + pixelsCopied++; + xIndex++; + + // Jump to the next scanline + if(xIndex == imgWidth) + { + yIndex += 1; + if(yIndex == imgHeight) + { + *numPixelsCopied = pixelsCopied; + return; + } + + xIndex = 0; + rRow += yStrideBytes; + gRow += yStrideBytes; + bRow += yStrideBytes; + aRow += yStrideBytes; + + rPtr = reinterpret_cast<float*>(rRow); + gPtr = reinterpret_cast<float*>(gRow); + bPtr = reinterpret_cast<float*>(bRow); + aPtr = reinterpret_cast<float*>(aRow); + } + // Jump to the next pixel + else + { + rPtr = reinterpret_cast<float*>( + reinterpret_cast<char*>(rPtr) + xStrideBytes); + gPtr = reinterpret_cast<float*>( + reinterpret_cast<char*>(gPtr) + xStrideBytes); + bPtr = reinterpret_cast<float*>( + reinterpret_cast<char*>(bPtr) + xStrideBytes); + aPtr = reinterpret_cast<float*>( + reinterpret_cast<char*>(aPtr) + xStrideBytes); + } + } + + *numPixelsCopied = pixelsCopied; + } + else + { + int pixelsCopied = 0; + while(pixelsCopied < outputBufferSize) + { + outputBuffer[4*pixelsCopied] = *rPtr; + outputBuffer[4*pixelsCopied+1] = *gPtr; + outputBuffer[4*pixelsCopied+2] = *bPtr; + outputBuffer[4*pixelsCopied+3] = 0.0; + pixelsCopied++; + xIndex++; + + // Jump to the next scanline + if(xIndex == imgWidth) + { + yIndex += 1; + if(yIndex == imgHeight) + { + *numPixelsCopied = pixelsCopied; + return; + } + + xIndex = 0; + rRow += yStrideBytes; + gRow += yStrideBytes; + bRow += yStrideBytes; + + rPtr = reinterpret_cast<float*>(rRow); + gPtr = reinterpret_cast<float*>(gRow); + bPtr = reinterpret_cast<float*>(bRow); + } + // Jump to the next pixel + else + { + rPtr = reinterpret_cast<float*>( + reinterpret_cast<char*>(rPtr) + xStrideBytes); + gPtr = reinterpret_cast<float*>( + reinterpret_cast<char*>(gPtr) + xStrideBytes); + bPtr = reinterpret_cast<float*>( + reinterpret_cast<char*>(bPtr) + xStrideBytes); + } + } + + *numPixelsCopied = pixelsCopied; + } + } + + void UnpackRGBAToImageDesc_Generic(GenericImageDesc& dstImg, + float* inputBuffer, + int numPixelsToUnpack, + long imagePixelStartIndex) + { + assert(inputBuffer); + + long imgWidth = dstImg.width; + long imgHeight = dstImg.height; + long imgPixels = imgWidth * imgHeight; + + if(imagePixelStartIndex<0 || imagePixelStartIndex>=imgPixels) + { + return; + } + + ptrdiff_t xStrideBytes = dstImg.xStrideBytes; + ptrdiff_t yStrideBytes = dstImg.yStrideBytes; + long yIndex = imagePixelStartIndex / imgWidth; + long xIndex = imagePixelStartIndex % imgWidth; + + // Figure out our initial ptr positions + char* rRow = reinterpret_cast<char*>(dstImg.rData) + + yStrideBytes * yIndex; + char* gRow = reinterpret_cast<char*>(dstImg.gData) + + yStrideBytes * yIndex; + char* bRow = reinterpret_cast<char*>(dstImg.bData) + + yStrideBytes * yIndex; + char* aRow = NULL; + + float* rPtr = reinterpret_cast<float*>(rRow + xStrideBytes*xIndex); + float* gPtr = reinterpret_cast<float*>(gRow + xStrideBytes*xIndex); + float* bPtr = reinterpret_cast<float*>(bRow + xStrideBytes*xIndex); + float* aPtr = NULL; + + if(dstImg.aData) + { + aRow = reinterpret_cast<char*>(dstImg.aData) + yStrideBytes * yIndex; + aPtr = reinterpret_cast<float*>(aRow + xStrideBytes*xIndex); + } + + if(aPtr) + { + int pixelsCopied = 0; + while(pixelsCopied < numPixelsToUnpack) + { + *rPtr = inputBuffer[4*pixelsCopied]; + *gPtr = inputBuffer[4*pixelsCopied+1]; + *bPtr = inputBuffer[4*pixelsCopied+2]; + *aPtr = inputBuffer[4*pixelsCopied+3]; + + pixelsCopied++; + xIndex++; + + // Jump to the next scanline + if(xIndex == imgWidth) + { + yIndex += 1; + if(yIndex == imgHeight) + { + return; + } + + xIndex = 0; + rRow += yStrideBytes; + gRow += yStrideBytes; + bRow += yStrideBytes; + aRow += yStrideBytes; + + rPtr = reinterpret_cast<float*>(rRow); + gPtr = reinterpret_cast<float*>(gRow); + bPtr = reinterpret_cast<float*>(bRow); + aPtr = reinterpret_cast<float*>(aRow); + } + // Jump to the next pixel + else + { + rPtr = reinterpret_cast<float*>( + reinterpret_cast<char*>(rPtr) + xStrideBytes); + gPtr = reinterpret_cast<float*>( + reinterpret_cast<char*>(gPtr) + xStrideBytes); + bPtr = reinterpret_cast<float*>( + reinterpret_cast<char*>(bPtr) + xStrideBytes); + aPtr = reinterpret_cast<float*>( + reinterpret_cast<char*>(aPtr) + xStrideBytes); + } + } + } + else + { + int pixelsCopied = 0; + while(pixelsCopied < numPixelsToUnpack) + { + *rPtr = inputBuffer[4*pixelsCopied]; + *gPtr = inputBuffer[4*pixelsCopied+1]; + *bPtr = inputBuffer[4*pixelsCopied+2]; + + pixelsCopied++; + xIndex++; + + // Jump to the next scanline + if(xIndex == imgWidth) + { + yIndex += 1; + if(yIndex == imgHeight) + { + return; + } + + xIndex = 0; + rRow += yStrideBytes; + gRow += yStrideBytes; + bRow += yStrideBytes; + + rPtr = reinterpret_cast<float*>(rRow); + gPtr = reinterpret_cast<float*>(gRow); + bPtr = reinterpret_cast<float*>(bRow); + } + // Jump to the next pixel + else + { + rPtr = reinterpret_cast<float*>( + reinterpret_cast<char*>(rPtr) + xStrideBytes); + gPtr = reinterpret_cast<float*>( + reinterpret_cast<char*>(gPtr) + xStrideBytes); + bPtr = reinterpret_cast<float*>( + reinterpret_cast<char*>(bPtr) + xStrideBytes); + } + } + } + } + + } + + + /* + namespace + { + + void PackRGBAFromImageDesc_RGBAMemcpy(const GenericImageDesc& srcImg, + float* outputBuffer, + int* numPixelsCopied, + int outputBufferSize, + int imagePixelStartIndex) + { + assert(outputBuffer); + assert(numPixelsCopied); + + long imgWidth = srcImg.getWidth(); + long imgHeight = srcImg.getHeight(); + long imgPixels = srcImg.getNumPixels(); + + if(imagePixelStartIndex<0 || imagePixelStartIndex>=imgPixels) + { + *numPixelsCopied = 0; + return; + } + + ptrdiff_t xStrideBytes = srcImg.getXStrideBytes(); + ptrdiff_t yStrideBytes = srcImg.getYStrideBytes(); + int yIndex = imagePixelStartIndex / imgWidth; + int xIndex = imagePixelStartIndex % imgWidth; + + // Figure out our initial ptr positions + char* imgRow = reinterpret_cast<char*>(srcImg.getRData()) + + yStrideBytes * yIndex; + + char* imgPtr = imgRow + xStrideBytes*xIndex; + + int totalPixelsCopied = 0; + int pixelsRemainingToCopy = outputBufferSize; + + while(pixelsRemainingToCopy>0 && yIndex < imgHeight) + { + int imgScanlinePixels = imgWidth - xIndex; + int numPixels = std::min(imgScanlinePixels, + pixelsRemainingToCopy); + memcpy(outputBuffer+totalPixelsCopied, + imgPtr, numPixels); + + yIndex += 1; + xIndex = 0; + + imgRow += yStrideBytes; + imgPtr = imgRow; + totalPixelsCopied += numPixels; + pixelsRemainingToCopy -= numPixels; + } + + if(numPixelsCopied) *numPixelsCopied = totalPixelsCopied; + } + } + */ + + //////////////////////////////////////////////////////////////////////////// + + // TODO: Add optimized codepaths to image packing / unpacking + + void PackRGBAFromImageDesc(const GenericImageDesc& srcImg, + float* outputBuffer, + int* numPixelsCopied, + int outputBufferSize, + long imagePixelStartIndex) + { + PackRGBAFromImageDesc_Generic(srcImg, outputBuffer, + numPixelsCopied, + outputBufferSize, + imagePixelStartIndex); + } + + + void UnpackRGBAToImageDesc(GenericImageDesc& dstImg, + float* inputBuffer, + int numPixelsToUnpack, + long imagePixelStartIndex) + { + UnpackRGBAToImageDesc_Generic(dstImg, inputBuffer, + numPixelsToUnpack, + imagePixelStartIndex); + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/ImagePacking.h b/src/core/ImagePacking.h new file mode 100644 index 0000000..a03d1ea --- /dev/null +++ b/src/core/ImagePacking.h @@ -0,0 +1,71 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_IMAGEPACKING_H +#define INCLUDED_OCIO_IMAGEPACKING_H + +#include <OpenColorIO/OpenColorIO.h> + +OCIO_NAMESPACE_ENTER +{ + struct GenericImageDesc + { + long width; + long height; + ptrdiff_t xStrideBytes; + ptrdiff_t yStrideBytes; + + float* rData; + float* gData; + float* bData; + float* aData; + + GenericImageDesc(); + ~GenericImageDesc(); + + // Resolves all AutoStride + void init(const ImageDesc& img); + + bool isPackedRGBA() const; + }; + + void PackRGBAFromImageDesc(const GenericImageDesc& srcImg, + float* outputBuffer, + int* numPixelsCopied, + int outputBufferSize, + long imagePixelStartIndex); + + void UnpackRGBAToImageDesc(GenericImageDesc& dstImg, + float* inputBuffer, + int numPixelsToUnpack, + long imagePixelStartIndex); +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/LogOps.cpp b/src/core/LogOps.cpp new file mode 100644 index 0000000..bc3174e --- /dev/null +++ b/src/core/LogOps.cpp @@ -0,0 +1,499 @@ +/* +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 <cmath> +#include <cstring> +#include <iostream> + +#include <OpenColorIO/OpenColorIO.h> + +#include "GpuShaderUtils.h" +#include "LogOps.h" +#include "MathUtils.h" + + +OCIO_NAMESPACE_ENTER +{ + namespace + { + const float FLTMIN = std::numeric_limits<float>::min(); + + const int FLOAT_DECIMALS = 7; + + // k * log(mx+b, base) + kb + // the caller is responsible for base != 1.0 + // TODO: pull the precomputation into the caller? + + void ApplyLinToLog(float* rgbaBuffer, long numPixels, + const float * k, + const float * m, + const float * b, + const float * base, + const float * kb) + { + // We account for the change of base by rolling the multiplier + // in with 'k' + + float knew[3] = { k[0] / logf(base[0]), + k[1] / logf(base[1]), + k[2] / logf(base[0]) }; + + for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex) + { + rgbaBuffer[0] = knew[0] * logf(std::max(m[0]*rgbaBuffer[0] + b[0], FLTMIN)) + kb[0]; + rgbaBuffer[1] = knew[1] * logf(std::max(m[1]*rgbaBuffer[1] + b[1], FLTMIN)) + kb[1]; + rgbaBuffer[2] = knew[2] * logf(std::max(m[2]*rgbaBuffer[2] + b[2], FLTMIN)) + kb[2]; + + rgbaBuffer += 4; + } + } + + // the caller is responsible for m != 0 + // the caller is responsible for k != 0 + // TODO: pull the precomputation into the caller? + + void ApplyLogToLin(float* rgbaBuffer, long numPixels, + const float * k, + const float * m, + const float * b, + const float * base, + const float * kb) + { + float kinv[3] = { 1.0f / k[0], + 1.0f / k[1], + 1.0f / k[2] }; + + float minv[3] = { 1.0f / m[0], + 1.0f / m[1], + 1.0f / m[2] }; + + for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex) + { + rgbaBuffer[0] = minv[0] * (powf(base[0], kinv[0]*(rgbaBuffer[0]-kb[0])) - b[0]); + rgbaBuffer[1] = minv[1] * (powf(base[1], kinv[1]*(rgbaBuffer[1]-kb[1])) - b[1]); + rgbaBuffer[2] = minv[2] * (powf(base[2], kinv[2]*(rgbaBuffer[2]-kb[2])) - b[2]); + + rgbaBuffer += 4; + } + } + + } + + + + /////////////////////////////////////////////////////////////////////////// + + + namespace + { + class LogOp : public Op + { + public: + LogOp(const float * k, + const float * m, + const float * b, + const float * base, + const float * kb, + TransformDirection direction); + virtual ~LogOp(); + + virtual OpRcPtr clone() const; + + virtual std::string getInfo() const; + virtual std::string getCacheID() const; + + virtual bool isNoOp() const; + virtual bool isSameType(const OpRcPtr & op) const; + virtual bool isInverse(const OpRcPtr & op) const; + virtual bool hasChannelCrosstalk() const; + virtual void finalize(); + virtual void apply(float* rgbaBuffer, long numPixels) const; + + virtual bool supportsGpuShader() const; + virtual void writeGpuShader(std::ostream & shader, + const std::string & pixelName, + const GpuShaderDesc & shaderDesc) const; + + private: + float m_k[3]; + float m_m[3]; + float m_b[3]; + float m_base[3]; + float m_kb[3]; + TransformDirection m_direction; + + std::string m_cacheID; + }; + + typedef OCIO_SHARED_PTR<LogOp> LogOpRcPtr; + + LogOp::LogOp(const float * k, + const float * m, + const float * b, + const float * base, + const float * kb, + TransformDirection direction): + Op(), + m_direction(direction) + { + if(m_direction == TRANSFORM_DIR_UNKNOWN) + { + throw Exception("Cannot apply LogOp op, unspecified transform direction."); + } + + memcpy(m_k, k, sizeof(float)*3); + memcpy(m_m, m, sizeof(float)*3); + memcpy(m_b, b, sizeof(float)*3); + memcpy(m_base, base, sizeof(float)*3); + memcpy(m_kb, kb, sizeof(float)*3); + } + + OpRcPtr LogOp::clone() const + { + OpRcPtr op = OpRcPtr(new LogOp(m_k, m_m, m_b, m_base, m_kb, m_direction)); + return op; + } + + LogOp::~LogOp() + { } + + std::string LogOp::getInfo() const + { + return "<LogOp>"; + } + + std::string LogOp::getCacheID() const + { + return m_cacheID; + } + + bool LogOp::isNoOp() const + { + return false; + } + + bool LogOp::isSameType(const OpRcPtr & op) const + { + LogOpRcPtr typedRcPtr = DynamicPtrCast<LogOp>(op); + if(!typedRcPtr) return false; + return true; + } + + bool LogOp::isInverse(const OpRcPtr & op) const + { + LogOpRcPtr typedRcPtr = DynamicPtrCast<LogOp>(op); + if(!typedRcPtr) return false; + + if(GetInverseTransformDirection(m_direction) != typedRcPtr->m_direction) + return false; + + float error = std::numeric_limits<float>::min(); + if(!VecsEqualWithRelError(m_k, 3, typedRcPtr->m_k, 3, error)) + return false; + if(!VecsEqualWithRelError(m_m, 3, typedRcPtr->m_m, 3, error)) + return false; + if(!VecsEqualWithRelError(m_b, 3, typedRcPtr->m_b, 3, error)) + return false; + if(!VecsEqualWithRelError(m_base, 3, typedRcPtr->m_base, 3, error)) + return false; + if(!VecsEqualWithRelError(m_kb, 3, typedRcPtr->m_kb, 3, error)) + return false; + + return true; + } + + bool LogOp::hasChannelCrosstalk() const + { + return false; + } + + void LogOp::finalize() + { + if(m_direction == TRANSFORM_DIR_FORWARD) + { + if(VecContainsOne(m_base, 3)) + throw Exception("LogOp Exception, base cannot be 1."); + } + else if(m_direction == TRANSFORM_DIR_INVERSE) + { + if(VecContainsZero(m_m, 3)) + throw Exception("LogOp Exception, m (slope) cannot be 0."); + if(VecContainsZero(m_k, 3)) + throw Exception("LogOp Exception, k (multiplier) cannot be 0."); + } + + std::ostringstream cacheIDStream; + cacheIDStream << "<LogOp "; + cacheIDStream.precision(FLOAT_DECIMALS); + for(int i=0; i<3; ++i) + { + cacheIDStream << m_k[i] << " "; + cacheIDStream << m_m[i] << " "; + cacheIDStream << m_b[i] << " "; + cacheIDStream << m_base[i] << " "; + cacheIDStream << m_kb[i] << " "; + } + + cacheIDStream << TransformDirectionToString(m_direction) << " "; + cacheIDStream << ">"; + + m_cacheID = cacheIDStream.str(); + } + + void LogOp::apply(float* rgbaBuffer, long numPixels) const + { + if(m_direction == TRANSFORM_DIR_FORWARD) + { + ApplyLinToLog(rgbaBuffer, numPixels, + m_k, m_m, m_b, m_base, m_kb); + } + else if(m_direction == TRANSFORM_DIR_INVERSE) + { + ApplyLogToLin(rgbaBuffer, numPixels, + m_k, m_m, m_b, m_base, m_kb); + } + } // Op::process + + bool LogOp::supportsGpuShader() const + { + return true; + } + + void LogOp::writeGpuShader(std::ostream & shader, + const std::string & pixelName, + const GpuShaderDesc & shaderDesc) const + { + GpuLanguage lang = shaderDesc.getLanguage(); + + if(m_direction == TRANSFORM_DIR_FORWARD) + { + // Lin To Log + // k * log(mx+b, base) + kb + + // We account for the change of base by rolling the multiplier + // in with 'k' + + float knew[3] = { m_k[0] / logf(m_base[0]), + m_k[1] / logf(m_base[1]), + m_k[2] / logf(m_base[0]) }; + + float clampMin[3] = { FLTMIN, FLTMIN, FLTMIN }; + + // TODO: Switch to f32 for internal Cg processing? + if(lang == GPU_LANGUAGE_CG) + { + clampMin[0] = static_cast<float>(GetHalfNormMin()); + clampMin[1] = static_cast<float>(GetHalfNormMin()); + clampMin[2] = static_cast<float>(GetHalfNormMin()); + } + + // Decompose into 2 steps + // 1) clamp(mx+b) + // 2) knew * log(x) + kb + + shader << pixelName << ".rgb = "; + shader << "max(" << GpuTextHalf3(clampMin,lang) << ", "; + shader << GpuTextHalf3(m_m,lang) << " * "; + shader << pixelName << ".rgb + "; + shader << GpuTextHalf3(m_b,lang) << ");\n"; + + shader << pixelName << ".rgb = "; + shader << GpuTextHalf3(knew,lang) << " * "; + shader << "log(" << pixelName << ".rgb) + "; + shader << GpuTextHalf3(m_kb,lang) << ";\n"; + } + else if(m_direction == TRANSFORM_DIR_INVERSE) + { + float kinv[3] = { 1.0f / m_k[0], + 1.0f / m_k[1], + 1.0f / m_k[2] }; + + float minv[3] = { 1.0f / m_m[0], + 1.0f / m_m[1], + 1.0f / m_m[2] }; + + // Decompose into 3 steps + // 1) kinv * ( x - kb) + // 2) pow(base, x) + // 3) minv * (x - b) + + shader << pixelName << ".rgb = "; + shader << GpuTextHalf3(kinv,lang) << " * ("; + shader << pixelName << ".rgb - "; + shader << GpuTextHalf3(m_kb,lang) << ");\n"; + + shader << pixelName << ".rgb = pow("; + shader << GpuTextHalf3(m_base,lang) << ", "; + shader << pixelName << ".rgb);\n"; + + shader << pixelName << ".rgb = "; + shader << GpuTextHalf3(minv,lang) << " * ("; + shader << pixelName << ".rgb - "; + shader << GpuTextHalf3(m_b,lang) << ");\n"; + } + } + + } // Anon namespace + + /////////////////////////////////////////////////////////////////////////// + + void CreateLogOp(OpRcPtrVec & ops, + const float * k, + const float * m, + const float * b, + const float * base, + const float * kb, + TransformDirection direction) + { + ops.push_back( LogOpRcPtr(new LogOp(k, m, b, base, kb, direction)) ); + } +} +OCIO_NAMESPACE_EXIT + + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef OCIO_UNIT_TEST + +namespace OCIO = OCIO_NAMESPACE; +#include "UnitTest.h" + +OIIO_ADD_TEST(LogOps, LinToLog) +{ + float k[3] = { 0.18f, 0.18f, 0.18f }; + float m[3] = { 2.0f, 2.0f, 2.0f }; + float b[3] = { 0.1f, 0.1f, 0.1f }; + float base[3] = { 10.0f, 10.0f, 10.0f }; + float kb[3] = { 1.0f, 1.0f, 1.0f }; + + + float data[8] = { 0.01f, 0.1f, 1.0f, 1.0f, + 10.0f, 100.0f, 1000.0f, 1.0f, }; + + float result[8] = { 0.8342526242885725f, + 0.90588182584953925f, + 1.057999473052105462f, + 1.0f, + 1.23457529033568797f, + 1.41422447595451795f, + 1.59418930777214063f, + 1.0f }; + + OCIO::OpRcPtrVec ops; + CreateLogOp(ops, k, m, b, base, kb, 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, 2); + } + + for(int i=0; i<8; ++i) + { + OIIO_CHECK_CLOSE( data[i], result[i], 1.0e-3 ); + } +} + +OIIO_ADD_TEST(LogOps, LogToLin) +{ + float k[3] = { 0.18f, 0.18f, 0.18f }; + float m[3] = { 2.0f, 2.0f, 2.0f }; + float b[3] = { 0.1f, 0.1f, 0.1f }; + float base[3] = { 10.0f, 10.0f, 10.0f }; + float kb[3] = { 1.0f, 1.0f, 1.0f }; + + float data[8] = { 0.8342526242885725f, + 0.90588182584953925f, + 1.057999473052105462f, + 1.0f, + 1.23457529033568797f, + 1.41422447595451795f, + 1.59418930777214063f, + 1.0f }; + + float result[8] = { 0.01f, 0.1f, 1.0f, 1.0f, + 10.0f, 100.0f, 1000.0f, 1.0f, }; + + OCIO::OpRcPtrVec ops; + CreateLogOp(ops, k, m, b, base, kb, OCIO::TRANSFORM_DIR_INVERSE); + + FinalizeOpVec(ops); + + // Apply the result + for(OCIO::OpRcPtrVec::size_type i = 0, size = ops.size(); i < size; ++i) + { + ops[i]->apply(data, 2); + } + + for(int i=0; i<8; ++i) + { + OIIO_CHECK_CLOSE( data[i], result[i], 1.0e-3 ); + } +} + +OIIO_ADD_TEST(LogOps, Inverse) +{ + float k[3] = { 0.18f, 0.18f, 0.18f }; + float m[3] = { 2.0f, 2.0f, 2.0f }; + float b[3] = { 0.1f, 0.1f, 0.1f }; + float base[3] = { 10.0f, 10.0f, 10.0f }; + float kb[3] = { 1.0f, 1.0f, 1.0f }; + + OCIO::OpRcPtrVec ops; + CreateLogOp(ops, k, m, b, base, kb, OCIO::TRANSFORM_DIR_FORWARD); + + CreateLogOp(ops, k, m, b, base, kb, OCIO::TRANSFORM_DIR_INVERSE); + + base[0] += 1e-5f; + CreateLogOp(ops, k, m, b, base, kb, OCIO::TRANSFORM_DIR_INVERSE); + CreateLogOp(ops, k, m, b, base, kb, OCIO::TRANSFORM_DIR_FORWARD); + + OIIO_CHECK_EQUAL(ops.size(), 4); + + OIIO_CHECK_ASSERT(ops[0]->isSameType(ops[1])); + OIIO_CHECK_ASSERT(ops[0]->isSameType(ops[2])); + OIIO_CHECK_ASSERT(ops[0]->isSameType(ops[3]->clone())); + + OIIO_CHECK_EQUAL(ops[0]->isInverse(ops[0]), false); + OIIO_CHECK_EQUAL(ops[0]->isInverse(ops[1]), true); + OIIO_CHECK_EQUAL(ops[0]->isInverse(ops[2]), false); + OIIO_CHECK_EQUAL(ops[0]->isInverse(ops[3]), false); + + OIIO_CHECK_EQUAL(ops[1]->isInverse(ops[0]), true); + OIIO_CHECK_EQUAL(ops[1]->isInverse(ops[2]), false); + OIIO_CHECK_EQUAL(ops[1]->isInverse(ops[3]), false); + + OIIO_CHECK_EQUAL(ops[2]->isInverse(ops[2]), false); + OIIO_CHECK_EQUAL(ops[2]->isInverse(ops[3]), true); + + OIIO_CHECK_EQUAL(ops[3]->isInverse(ops[3]), false); +} + +#endif // OCIO_UNIT_TEST diff --git a/src/core/LogOps.h b/src/core/LogOps.h new file mode 100644 index 0000000..0356fd8 --- /dev/null +++ b/src/core/LogOps.h @@ -0,0 +1,57 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_LOGOPS_H +#define INCLUDED_OCIO_LOGOPS_H + +#include <OpenColorIO/OpenColorIO.h> + +#include "Op.h" + +#include <vector> + +OCIO_NAMESPACE_ENTER +{ + // output = k * log(mx+b, base) + kb + // This does not affect alpha + // In the forward direction this is lin->log + // All input vectors are size 3 (including base) + + void CreateLogOp(OpRcPtrVec & ops, + const float * k, + const float * m, + const float * b, + const float * base, + const float * kb, + TransformDirection direction); + +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/LogTransform.cpp b/src/core/LogTransform.cpp new file mode 100644 index 0000000..8ed7bb0 --- /dev/null +++ b/src/core/LogTransform.cpp @@ -0,0 +1,157 @@ +/* +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 <cstring> +#include <sstream> +#include <vector> + +#include <OpenColorIO/OpenColorIO.h> + +#include "LogOps.h" +#include "OpBuilders.h" + +OCIO_NAMESPACE_ENTER +{ + LogTransformRcPtr LogTransform::Create() + { + return LogTransformRcPtr(new LogTransform(), &deleter); + } + + void LogTransform::deleter(LogTransform* t) + { + delete t; + } + + + class LogTransform::Impl + { + public: + TransformDirection dir_; + float base_; + + Impl() : + dir_(TRANSFORM_DIR_FORWARD), + base_(2.0) + { } + + ~Impl() + { } + + Impl& operator= (const Impl & rhs) + { + dir_ = rhs.dir_; + base_ = rhs.base_; + return *this; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + + LogTransform::LogTransform() + : m_impl(new LogTransform::Impl) + { + } + + TransformRcPtr LogTransform::createEditableCopy() const + { + LogTransformRcPtr transform = LogTransform::Create(); + *(transform->m_impl) = *m_impl; + return transform; + } + + LogTransform::~LogTransform() + { + delete m_impl; + m_impl = NULL; + } + + LogTransform& LogTransform::operator= (const LogTransform & rhs) + { + *m_impl = *rhs.m_impl; + return *this; + } + + TransformDirection LogTransform::getDirection() const + { + return getImpl()->dir_; + } + + void LogTransform::setDirection(TransformDirection dir) + { + getImpl()->dir_ = dir; + } + + + float LogTransform::getBase() const + { + return getImpl()->base_; + } + + void LogTransform::setBase(float val) + { + getImpl()->base_ = val; + } + + std::ostream& operator<< (std::ostream& os, const LogTransform& t) + { + os << "<LogTransform "; + os << "base=" << t.getBase() << ", "; + os << "direction=" << TransformDirectionToString(t.getDirection()) << ", "; + os << ">\n"; + + return os; + } + + + /////////////////////////////////////////////////////////////////////////// + + + void BuildLogOps(OpRcPtrVec & ops, + const Config& /*config*/, + const LogTransform& transform, + TransformDirection dir) + { + TransformDirection combinedDir = CombineTransformDirections(dir, + transform.getDirection()); + + float basescalar = transform.getBase(); + float base[3] = { basescalar, basescalar, basescalar }; + + float k[3] = { 1.0f, 1.0f, 1.0f }; + float m[3] = { 1.0f, 1.0f, 1.0f }; + float b[3] = { 0.0f, 0.0f, 0.0f }; + float kb[3] = { 0.0f, 0.0f, 0.0f }; + + // output = k * log(mx+b, base) + kb + CreateLogOp(ops, + k, m, b, base, kb, + combinedDir); + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/Logging.cpp b/src/core/Logging.cpp new file mode 100644 index 0000000..7846ac2 --- /dev/null +++ b/src/core/Logging.cpp @@ -0,0 +1,156 @@ +/* +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 <cstdlib> +#include <iostream> +#include <sstream> + +#include <OpenColorIO/OpenColorIO.h> + +#include "Logging.h" +#include "Mutex.h" +#include "pystring/pystring.h" + +OCIO_NAMESPACE_ENTER +{ + namespace + { + const char * OCIO_LOGGING_LEVEL_ENVVAR = "OCIO_LOGGING_LEVEL"; + const LoggingLevel OCIO_DEFAULT_LOGGING_LEVEL = LOGGING_LEVEL_INFO; + + Mutex g_logmutex; + LoggingLevel g_logginglevel = LOGGING_LEVEL_UNKNOWN; + bool g_initialized = false; + bool g_loggingOverride = false; + + // You must manually acquire the logging mutex before calling this. + // This will set g_logginglevel, g_initialized, g_loggingOverride + void InitLogging() + { + if(g_initialized) return; + + g_initialized = true; + + char* levelstr = std::getenv(OCIO_LOGGING_LEVEL_ENVVAR); + if(levelstr) + { + g_loggingOverride = true; + g_logginglevel = LoggingLevelFromString(levelstr); + + if(g_logginglevel == LOGGING_LEVEL_UNKNOWN) + { + std::cerr << "[OpenColorIO Warning]: Invalid $OCIO_LOGGING_LEVEL specified. "; + std::cerr << "Options: none (0), warning (1), info (2), debug (3)" << std::endl; + g_logginglevel = OCIO_DEFAULT_LOGGING_LEVEL; + } + } + else + { + g_logginglevel = OCIO_DEFAULT_LOGGING_LEVEL; + } + } + } + + LoggingLevel GetLoggingLevel() + { + AutoMutex lock(g_logmutex); + InitLogging(); + + return g_logginglevel; + } + + void SetLoggingLevel(LoggingLevel level) + { + AutoMutex lock(g_logmutex); + InitLogging(); + + // Calls to SetLoggingLevel are ignored if OCIO_LOGGING_LEVEL_ENVVAR + // is specified. This is to allow users to optionally debug OCIO at + // runtime even in applications that disable logging. + + if(!g_loggingOverride) + { + g_logginglevel = level; + } + } + + void LogWarning(const std::string & text) + { + AutoMutex lock(g_logmutex); + InitLogging(); + + if(g_logginglevel<LOGGING_LEVEL_WARNING) return; + + std::vector<std::string> parts; + pystring::split( pystring::rstrip(text), parts, "\n"); + + for(unsigned int i=0; i<parts.size(); ++i) + { + std::cerr << "[OpenColorIO Warning]: " << parts[i] << std::endl; + } + } + + void LogInfo(const std::string & text) + { + AutoMutex lock(g_logmutex); + InitLogging(); + + if(g_logginglevel<LOGGING_LEVEL_INFO) return; + + std::vector<std::string> parts; + pystring::split( pystring::rstrip(text), parts, "\n"); + + for(unsigned int i=0; i<parts.size(); ++i) + { + std::cerr << "[OpenColorIO Info]: " << parts[i] << std::endl; + } + } + + void LogDebug(const std::string & text) + { + AutoMutex lock(g_logmutex); + InitLogging(); + + if(g_logginglevel<LOGGING_LEVEL_DEBUG) return; + + std::vector<std::string> parts; + pystring::split( pystring::rstrip(text), parts, "\n"); + + for(unsigned int i=0; i<parts.size(); ++i) + { + std::cerr << "[OpenColorIO Debug]: " << parts[i] << std::endl; + } + } + + bool IsDebugLoggingEnabled() + { + return (GetLoggingLevel()>=LOGGING_LEVEL_DEBUG); + } + +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/Logging.h b/src/core/Logging.h new file mode 100644 index 0000000..173e642 --- /dev/null +++ b/src/core/Logging.h @@ -0,0 +1,47 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_LOGGING_H +#define INCLUDED_OCIO_LOGGING_H + +#include <OpenColorIO/OpenColorIO.h> + +#include <string> + +OCIO_NAMESPACE_ENTER +{ + void LogWarning(const std::string & text); + void LogInfo(const std::string & text); + void LogDebug(const std::string & text); + + bool IsDebugLoggingEnabled(); +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/Look.cpp b/src/core/Look.cpp new file mode 100644 index 0000000..fd1bfe7 --- /dev/null +++ b/src/core/Look.cpp @@ -0,0 +1,163 @@ +/* +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 <cstring> +#include <sstream> +#include <vector> + +#include <OpenColorIO/OpenColorIO.h> + +OCIO_NAMESPACE_ENTER +{ + LookRcPtr Look::Create() + { + return LookRcPtr(new Look(), &deleter); + } + + void Look::deleter(Look* c) + { + delete c; + } + + class Look::Impl + { + public: + std::string name_; + std::string processSpace_; + TransformRcPtr transform_; + TransformRcPtr inverseTransform_; + + Impl() + { } + + ~Impl() + { } + + Impl& operator= (const Impl & rhs) + { + name_ = rhs.name_; + processSpace_ = rhs.processSpace_; + + transform_ = rhs.transform_; + if(transform_) transform_ = transform_->createEditableCopy(); + + inverseTransform_ = rhs.inverseTransform_; + if(inverseTransform_) inverseTransform_ = inverseTransform_->createEditableCopy(); + + return *this; + } + }; + + + /////////////////////////////////////////////////////////////////////////// + + + + Look::Look() + : m_impl(new Look::Impl) + { + } + + Look::~Look() + { + delete m_impl; + m_impl = NULL; + } + + LookRcPtr Look::createEditableCopy() const + { + LookRcPtr cs = Look::Create(); + *cs->m_impl = *m_impl; + return cs; + } + + const char * Look::getName() const + { + return getImpl()->name_.c_str(); + } + + void Look::setName(const char * name) + { + getImpl()->name_ = name; + } + + const char * Look::getProcessSpace() const + { + return getImpl()->processSpace_.c_str(); + } + + void Look::setProcessSpace(const char * processSpace) + { + getImpl()->processSpace_ = processSpace; + } + + ConstTransformRcPtr Look::getTransform() const + { + return getImpl()->transform_; + } + + void Look::setTransform(const ConstTransformRcPtr & transform) + { + getImpl()->transform_ = transform->createEditableCopy(); + } + + ConstTransformRcPtr Look::getInverseTransform() const + { + return getImpl()->inverseTransform_; + } + + void Look::setInverseTransform(const ConstTransformRcPtr & transform) + { + getImpl()->inverseTransform_ = transform->createEditableCopy(); + } + + + std::ostream& operator<< (std::ostream& os, const Look& look) + { + os << "<Look "; + os << "name=" << look.getName() << ", "; + os << "processSpace=" << look.getProcessSpace() << ", "; + + if(look.getTransform()) + { + os << "\tTransform: "; + os << *look.getTransform(); + } + + if(look.getInverseTransform()) + { + os << "\tInverseTransform: "; + os << *look.getInverseTransform(); + } + + os << ">"; + + return os; + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/LookParse.cpp b/src/core/LookParse.cpp new file mode 100644 index 0000000..3c6db03 --- /dev/null +++ b/src/core/LookParse.cpp @@ -0,0 +1,309 @@ +/* +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 <algorithm> + +#include "LookParse.h" +#include "ParseUtils.h" +#include "pystring/pystring.h" +#include <iostream> + +OCIO_NAMESPACE_ENTER +{ + void LookParseResult::Token::parse(const std::string & str) + { + // Assert no commas, colons, or | in str. + + if(pystring::startswith(str, "+")) + { + name = pystring::lstrip(str, "+"); + dir = TRANSFORM_DIR_FORWARD; + } + // TODO: Handle -- + else if(pystring::startswith(str, "-")) + { + name = pystring::lstrip(str, "-"); + dir = TRANSFORM_DIR_INVERSE; + } + else + { + name = str; + dir = TRANSFORM_DIR_FORWARD; + } + } + + void LookParseResult::Token::serialize(std::ostream & os) const + { + if(dir==TRANSFORM_DIR_FORWARD) + { + os << name; + } + else if(dir==TRANSFORM_DIR_INVERSE) + { + os << "-" << name; + } + else + { + os << "?" << name; + } + } + + void LookParseResult::serialize(std::ostream & os, const Tokens & tokens) + { + for(unsigned int i=0; i<tokens.size(); ++i) + { + if(i!=0) os << ", "; + tokens[i].serialize(os); + } + } + + const LookParseResult::Options & LookParseResult::parse(const std::string & looksstr) + { + m_options.clear(); + + std::string strippedlooks = pystring::strip(looksstr); + if(strippedlooks.empty()) + { + return m_options; + } + + std::vector<std::string> options; + pystring::split(strippedlooks, options, "|"); + + std::vector<std::string> vec; + + for(unsigned int optionsindex=0; + optionsindex<options.size(); + ++optionsindex) + { + LookParseResult::Tokens tokens; + + vec.clear(); + SplitStringEnvStyle(vec, options[optionsindex].c_str()); + for(unsigned int i=0; i<vec.size(); ++i) + { + LookParseResult::Token t; + t.parse(vec[i]); + tokens.push_back(t); + } + + m_options.push_back(tokens); + } + + return m_options; + } + + const LookParseResult::Options & LookParseResult::getOptions() const + { + return m_options; + } + + bool LookParseResult::empty() const + { + return m_options.empty(); + } + + void LookParseResult::reverse() + { + // m_options itself should NOT be reversed. + // The individual looks + // need to be applied in the inverse direction. But, the precedence + // for which option to apply is to be maintained! + + for(unsigned int optionindex=0; + optionindex<m_options.size(); + ++optionindex) + { + std::reverse(m_options[optionindex].begin(), m_options[optionindex].end()); + + for(unsigned int tokenindex=0; + tokenindex<m_options[optionindex].size(); + ++tokenindex) + { + m_options[optionindex][tokenindex].dir = + GetInverseTransformDirection( + m_options[optionindex][tokenindex].dir); + } + } + } +} +OCIO_NAMESPACE_EXIT + + + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef OCIO_UNIT_TEST + +OCIO_NAMESPACE_USING + +#include "UnitTest.h" + +OIIO_ADD_TEST(LookParse, Parse) +{ + LookParseResult r; + + { + const LookParseResult::Options & options = r.parse(""); + OIIO_CHECK_EQUAL(options.size(), 0); + OIIO_CHECK_EQUAL(options.empty(), true); + } + + { + const LookParseResult::Options & options = r.parse(" "); + OIIO_CHECK_EQUAL(options.size(), 0); + OIIO_CHECK_EQUAL(options.empty(), true); + } + + { + const LookParseResult::Options & options = r.parse("cc"); + OIIO_CHECK_EQUAL(options.size(), 1); + OIIO_CHECK_EQUAL(options[0][0].name, "cc"); + OIIO_CHECK_EQUAL(options[0][0].dir, TRANSFORM_DIR_FORWARD); + OIIO_CHECK_EQUAL(options.empty(), false); + } + + { + const LookParseResult::Options & options = r.parse("+cc"); + OIIO_CHECK_EQUAL(options.size(), 1); + OIIO_CHECK_EQUAL(options[0][0].name, "cc"); + OIIO_CHECK_EQUAL(options[0][0].dir, TRANSFORM_DIR_FORWARD); + OIIO_CHECK_EQUAL(options.empty(), false); + } + + { + const LookParseResult::Options & options = r.parse(" +cc"); + OIIO_CHECK_EQUAL(options.size(), 1); + OIIO_CHECK_EQUAL(options[0][0].name, "cc"); + OIIO_CHECK_EQUAL(options[0][0].dir, TRANSFORM_DIR_FORWARD); + OIIO_CHECK_EQUAL(options.empty(), false); + } + + { + const LookParseResult::Options & options = r.parse(" +cc "); + OIIO_CHECK_EQUAL(options.size(), 1); + OIIO_CHECK_EQUAL(options[0][0].name, "cc"); + OIIO_CHECK_EQUAL(options[0][0].dir, TRANSFORM_DIR_FORWARD); + OIIO_CHECK_EQUAL(options.empty(), false); + } + + { + const LookParseResult::Options & options = r.parse("+cc,-di"); + OIIO_CHECK_EQUAL(options.size(), 1); + OIIO_CHECK_EQUAL(options[0].size(), 2); + OIIO_CHECK_EQUAL(options[0][0].name, "cc"); + OIIO_CHECK_EQUAL(options[0][0].dir, TRANSFORM_DIR_FORWARD); + OIIO_CHECK_EQUAL(options[0][1].name, "di"); + OIIO_CHECK_EQUAL(options[0][1].dir, TRANSFORM_DIR_INVERSE); + OIIO_CHECK_EQUAL(options.empty(), false); + } + + { + const LookParseResult::Options & options = r.parse(" +cc , -di"); + OIIO_CHECK_EQUAL(options.size(), 1); + OIIO_CHECK_EQUAL(options[0].size(), 2); + OIIO_CHECK_EQUAL(options[0][0].name, "cc"); + OIIO_CHECK_EQUAL(options[0][0].dir, TRANSFORM_DIR_FORWARD); + OIIO_CHECK_EQUAL(options[0][1].name, "di"); + OIIO_CHECK_EQUAL(options[0][1].dir, TRANSFORM_DIR_INVERSE); + OIIO_CHECK_EQUAL(options.empty(), false); + } + + { + const LookParseResult::Options & options = r.parse(" +cc : -di"); + OIIO_CHECK_EQUAL(options.size(), 1); + OIIO_CHECK_EQUAL(options[0].size(), 2); + OIIO_CHECK_EQUAL(options[0][0].name, "cc"); + OIIO_CHECK_EQUAL(options[0][0].dir, TRANSFORM_DIR_FORWARD); + OIIO_CHECK_EQUAL(options[0][1].name, "di"); + OIIO_CHECK_EQUAL(options[0][1].dir, TRANSFORM_DIR_INVERSE); + OIIO_CHECK_EQUAL(options.empty(), false); + } + + { + const LookParseResult::Options & options = r.parse("+cc, -di |-cc"); + OIIO_CHECK_EQUAL(options.size(), 2); + OIIO_CHECK_EQUAL(options[0].size(), 2); + OIIO_CHECK_EQUAL(options[0][0].name, "cc"); + OIIO_CHECK_EQUAL(options[0][0].dir, TRANSFORM_DIR_FORWARD); + OIIO_CHECK_EQUAL(options[0][1].name, "di"); + OIIO_CHECK_EQUAL(options[0][1].dir, TRANSFORM_DIR_INVERSE); + OIIO_CHECK_EQUAL(options[1].size(), 1); + OIIO_CHECK_EQUAL(options.empty(), false); + OIIO_CHECK_EQUAL(options[1][0].name, "cc"); + OIIO_CHECK_EQUAL(options[1][0].dir, TRANSFORM_DIR_INVERSE); + } + + { + const LookParseResult::Options & options = r.parse("+cc, -di |-cc| "); + OIIO_CHECK_EQUAL(options.size(), 3); + OIIO_CHECK_EQUAL(options[0].size(), 2); + OIIO_CHECK_EQUAL(options[0][0].name, "cc"); + OIIO_CHECK_EQUAL(options[0][0].dir, TRANSFORM_DIR_FORWARD); + OIIO_CHECK_EQUAL(options[0][1].name, "di"); + OIIO_CHECK_EQUAL(options[0][1].dir, TRANSFORM_DIR_INVERSE); + OIIO_CHECK_EQUAL(options[1].size(), 1); + OIIO_CHECK_EQUAL(options.empty(), false); + OIIO_CHECK_EQUAL(options[1][0].name, "cc"); + OIIO_CHECK_EQUAL(options[1][0].dir, TRANSFORM_DIR_INVERSE); + OIIO_CHECK_EQUAL(options[2].size(), 1); + OIIO_CHECK_EQUAL(options[2][0].name, ""); + OIIO_CHECK_EQUAL(options[2][0].dir, TRANSFORM_DIR_FORWARD); + } +} + +OIIO_ADD_TEST(LookParse, Reverse) +{ + LookParseResult r; + + { + r.parse("+cc, -di |-cc| "); + r.reverse(); + const LookParseResult::Options & options = r.getOptions(); + + OIIO_CHECK_EQUAL(options.size(), 3); + OIIO_CHECK_EQUAL(options[0].size(), 2); + OIIO_CHECK_EQUAL(options[0][1].name, "cc"); + OIIO_CHECK_EQUAL(options[0][1].dir, TRANSFORM_DIR_INVERSE); + OIIO_CHECK_EQUAL(options[0][0].name, "di"); + OIIO_CHECK_EQUAL(options[0][0].dir, TRANSFORM_DIR_FORWARD); + OIIO_CHECK_EQUAL(options[1].size(), 1); + OIIO_CHECK_EQUAL(options.empty(), false); + OIIO_CHECK_EQUAL(options[1][0].name, "cc"); + OIIO_CHECK_EQUAL(options[1][0].dir, TRANSFORM_DIR_FORWARD); + OIIO_CHECK_EQUAL(options[2].size(), 1); + OIIO_CHECK_EQUAL(options[2][0].name, ""); + OIIO_CHECK_EQUAL(options[2][0].dir, TRANSFORM_DIR_INVERSE); + } + + +} + +#endif // OCIO_UNIT_TEST diff --git a/src/core/LookParse.h b/src/core/LookParse.h new file mode 100644 index 0000000..e867ce7 --- /dev/null +++ b/src/core/LookParse.h @@ -0,0 +1,79 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_PARSED_LOOK_H +#define INCLUDED_OCIO_PARSED_LOOK_H + +#include <OpenColorIO/OpenColorIO.h> + +#include <vector> + +OCIO_NAMESPACE_ENTER +{ + // Looks parse structures + // This is contains a list, where each option entry corresponds to + // an "or" separated looks token list. + // I.e, " +cc,-onset | +cc " parses to TWO options: (+cc,-onset), (+cc) + + class LookParseResult + { + public: + struct Token + { + std::string name; + TransformDirection dir; + + Token(): + dir(TRANSFORM_DIR_FORWARD) {} + + void parse(const std::string & str); + void serialize(std::ostream & os) const; + }; + + typedef std::vector<Token> Tokens; + + static void serialize(std::ostream & os, const Tokens & tokens); + + typedef std::vector<Tokens> Options; + + const Options & parse(const std::string & looksstr); + + const Options & getOptions() const; + bool empty() const; + + void reverse(); + + private: + Options m_options; + }; + +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/LookTransform.cpp b/src/core/LookTransform.cpp new file mode 100644 index 0000000..0092d16 --- /dev/null +++ b/src/core/LookTransform.cpp @@ -0,0 +1,405 @@ +/* +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 <algorithm> +#include <iterator> + +#include "LookParse.h" +#include "NoOps.h" +#include "OpBuilders.h" +#include "ParseUtils.h" +#include "pystring/pystring.h" + + +OCIO_NAMESPACE_ENTER +{ + LookTransformRcPtr LookTransform::Create() + { + return LookTransformRcPtr(new LookTransform(), &deleter); + } + + void LookTransform::deleter(LookTransform* t) + { + delete t; + } + + + class LookTransform::Impl + { + public: + TransformDirection dir_; + std::string src_; + std::string dst_; + std::string looks_; + + Impl() : + dir_(TRANSFORM_DIR_FORWARD) + { } + + ~Impl() + { } + + Impl& operator= (const Impl & rhs) + { + dir_ = rhs.dir_; + src_ = rhs.src_; + dst_ = rhs.dst_; + looks_ = rhs.looks_; + return *this; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + + + LookTransform::LookTransform() + : m_impl(new LookTransform::Impl) + { + } + + TransformRcPtr LookTransform::createEditableCopy() const + { + LookTransformRcPtr transform = LookTransform::Create(); + *(transform->m_impl) = *m_impl; + return transform; + } + + LookTransform::~LookTransform() + { + delete m_impl; + m_impl = NULL; + } + + LookTransform& LookTransform::operator= (const LookTransform & rhs) + { + *m_impl = *rhs.m_impl; + return *this; + } + + TransformDirection LookTransform::getDirection() const + { + return getImpl()->dir_; + } + + void LookTransform::setDirection(TransformDirection dir) + { + getImpl()->dir_ = dir; + } + + const char * LookTransform::getSrc() const + { + return getImpl()->src_.c_str(); + } + + void LookTransform::setSrc(const char * src) + { + getImpl()->src_ = src; + } + + const char * LookTransform::getDst() const + { + return getImpl()->dst_.c_str(); + } + + void LookTransform::setDst(const char * dst) + { + getImpl()->dst_ = dst; + } + + void LookTransform::setLooks(const char * looks) + { + getImpl()->looks_ = looks; + } + + const char * LookTransform::getLooks() const + { + return getImpl()->looks_.c_str(); + } + + std::ostream& operator<< (std::ostream& os, const LookTransform& t) + { + os << "<LookTransform "; + os << "src=" << t.getSrc() << ", "; + os << "dst=" << t.getDst() << ", "; + os << "looks=" << t.getLooks() << ", "; + os << "direction=" << TransformDirectionToString(t.getDirection()) << ", "; + os << ">\n"; + return os; + } + + //////////////////////////////////////////////////////////////////////////// + + + + + namespace + { + + void RunLookTokens(OpRcPtrVec & ops, + ConstColorSpaceRcPtr & currentColorSpace, + bool skipColorSpaceConversions, + const Config& config, + const ConstContextRcPtr & context, + const LookParseResult::Tokens & lookTokens) + { + if(lookTokens.empty()) return; + + for(unsigned int i=0; i<lookTokens.size(); ++i) + { + const std::string & lookName = lookTokens[i].name; + + if(lookName.empty()) continue; + + ConstLookRcPtr look = config.getLook(lookName.c_str()); + if(!look) + { + std::ostringstream os; + os << "RunLookTokens error. "; + os << "The specified look, '" << lookName; + os << "', cannot be found. "; + if(config.getNumLooks() == 0) + { + os << " (No looks defined in config)"; + } + else + { + os << " (looks: "; + for(int ii=0; ii<config.getNumLooks(); ++ii) + { + if(ii != 0) os << ", "; + os << config.getLookNameByIndex(ii); + } + os << ")"; + } + + throw Exception(os.str().c_str()); + } + + // Put the new ops into a temp array, to see if it's a no-op + // If it is a no-op, dont bother doing the colorspace conversion. + OpRcPtrVec tmpOps; + + if(lookTokens[i].dir == TRANSFORM_DIR_FORWARD) + { + CreateLookNoOp(tmpOps, lookName); + if(look->getTransform()) + { + BuildOps(tmpOps, config, context, look->getTransform(), TRANSFORM_DIR_FORWARD); + } + else if(look->getInverseTransform()) + { + BuildOps(tmpOps, config, context, look->getInverseTransform(), TRANSFORM_DIR_INVERSE); + } + } + else if(lookTokens[i].dir == TRANSFORM_DIR_INVERSE) + { + CreateLookNoOp(tmpOps, std::string("-") + lookName); + if(look->getInverseTransform()) + { + BuildOps(tmpOps, config, context, look->getInverseTransform(), TRANSFORM_DIR_FORWARD); + } + else if(look->getTransform()) + { + BuildOps(tmpOps, config, context, look->getTransform(), TRANSFORM_DIR_INVERSE); + } + } + else + { + std::ostringstream os; + os << "BuildLookOps error. "; + os << "The specified look, '" << lookTokens[i].name; + os << "' has an ill-defined transform direction."; + throw Exception(os.str().c_str()); + } + + if(!IsOpVecNoOp(tmpOps)) + { + if(!skipColorSpaceConversions) + { + ConstColorSpaceRcPtr processColorSpace = config.getColorSpace(look->getProcessSpace()); + if(!processColorSpace) + { + std::ostringstream os; + os << "RunLookTokens error. "; + os << "The specified look, '" << lookTokens[i].name; + os << "', requires processing in the ColorSpace, '"; + os << look->getProcessSpace() << "' which is not defined."; + throw Exception(os.str().c_str()); + } + + BuildColorSpaceOps(ops, config, context, + currentColorSpace, + processColorSpace); + currentColorSpace = processColorSpace; + } + + std::copy(tmpOps.begin(), tmpOps.end(), std::back_inserter(ops)); + } + } + } + + } // anon namespace + + //////////////////////////////////////////////////////////////////////////// + + + void BuildLookOps(OpRcPtrVec & ops, + const Config& config, + const ConstContextRcPtr & context, + const LookTransform & lookTransform, + TransformDirection dir) + { + ConstColorSpaceRcPtr src, dst; + src = config.getColorSpace( lookTransform.getSrc() ); + dst = config.getColorSpace( lookTransform.getDst() ); + + if(!src) + { + std::ostringstream os; + os << "BuildLookOps error."; + os << "The specified lookTransform specifies a src colorspace, '"; + os << lookTransform.getSrc() << "', which is not defined."; + throw Exception(os.str().c_str()); + } + + if(!dst) + { + std::ostringstream os; + os << "BuildLookOps error."; + os << "The specified lookTransform specifies a dst colorspace, '"; + os << lookTransform.getDst() << "', which is not defined."; + throw Exception(os.str().c_str()); + } + + LookParseResult looks; + looks.parse(lookTransform.getLooks()); + + // We must handle the inverse src/dst colorspace transformation explicitly. + if(dir == TRANSFORM_DIR_INVERSE) + { + std::swap(src, dst); + looks.reverse(); + } + else if(dir == TRANSFORM_DIR_UNKNOWN) + { + std::ostringstream os; + os << "BuildLookOps error. A valid transform direction must be specified."; + throw Exception(os.str().c_str()); + } + + ConstColorSpaceRcPtr currentColorSpace = src; + BuildLookOps(ops, + currentColorSpace, + false, + config, + context, + looks); + + BuildColorSpaceOps(ops, config, context, + currentColorSpace, + dst); + } + + void BuildLookOps(OpRcPtrVec & ops, + ConstColorSpaceRcPtr & currentColorSpace, + bool skipColorSpaceConversions, + const Config& config, + const ConstContextRcPtr & context, + const LookParseResult & looks) + { + const LookParseResult::Options & options = looks.getOptions(); + + if(options.empty()) + { + // Do nothing + } + else if(options.size() == 1) + { + // As an optimization, if we only have a single look option, + // just push back onto the final location + RunLookTokens(ops, + currentColorSpace, + skipColorSpaceConversions, + config, + context, + options[0]); + } + else + { + // If we have multiple look options, try each one in order, + // and if we can create the ops without a missing file exception, + // push back it's results and return + + bool success = false; + std::ostringstream os; + + OpRcPtrVec tmpOps; + ConstColorSpaceRcPtr cs; + + for(unsigned int i=0; i<options.size(); ++i) + { + cs = currentColorSpace; + tmpOps.clear(); + + try + { + RunLookTokens(tmpOps, + cs, + skipColorSpaceConversions, + config, + context, + options[i]); + success = true; + break; + } + catch(ExceptionMissingFile & e) + { + if(i != 0) os << " ... "; + + os << "("; + LookParseResult::serialize(os, options[i]); + os << ") " << e.what(); + } + } + + if(success) + { + currentColorSpace = cs; + std::copy(tmpOps.begin(), tmpOps.end(), std::back_inserter(ops)); + } + else + { + throw ExceptionMissingFile(os.str().c_str()); + } + } + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/Lut1DOp.cpp b/src/core/Lut1DOp.cpp new file mode 100644 index 0000000..8363536 --- /dev/null +++ b/src/core/Lut1DOp.cpp @@ -0,0 +1,1038 @@ +/* +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 "HashUtils.h" +#include "Lut1DOp.h" +#include "MathUtils.h" +#include "SSE.h" + +#include <algorithm> +#include <cmath> +#include <sstream> +#include <iostream> + +OCIO_NAMESPACE_ENTER +{ + Lut1D::Lut1D() : + maxerror(std::numeric_limits<float>::min()), + errortype(ERROR_RELATIVE) + { + for(int i=0; i<3; ++i) + { + from_min[i] = 0.0f; + from_max[i] = 1.0f; + } + } + + Lut1DRcPtr Lut1D::Create() + { + return Lut1DRcPtr(new Lut1D()); + } + + + namespace + { + bool IsLut1DNoOp(const Lut1D & lut, + float maxerror, + ErrorType errortype) + { + // If tolerance not positive, skip the check. + if(!(maxerror > 0.0)) return false; + + for(int channel = 0; channel<3; ++channel) + { + if(lut.luts[channel].size() == 0) continue; + + float inorm = 1.0f / (static_cast<float>(lut.luts[channel].size()) - 1.0f); + + float m = lut.from_max[channel] - lut.from_min[channel]; + float b = lut.from_min[channel]; + + for(unsigned int i=0; i<lut.luts[channel].size(); ++i) + { + float x = static_cast<float>(i) * inorm; + float identval = m*x+b; + float lutval = lut.luts[channel][i]; + + if(errortype == ERROR_ABSOLUTE) + { + if(!equalWithAbsError(identval, lutval, maxerror)) + { + return false; + } + } + else if(errortype == ERROR_RELATIVE) + { + if(!equalWithRelError(identval, lutval, maxerror)) + { + return false; + } + } + else + { + throw Exception("Unknown error type."); + } + } + } + + return true; + } + } + + std::string Lut1D::getCacheID() const + { + AutoMutex lock(m_mutex); + + if(luts[0].empty() || luts[1].empty() || luts[2].empty()) + throw Exception("Cannot compute cacheID of invalid Lut1D"); + + if(!m_cacheID.empty()) + return m_cacheID; + + finalize(); + return m_cacheID; + } + + bool Lut1D::isNoOp() const + { + AutoMutex lock(m_mutex); + + if(luts[0].empty() || luts[1].empty() || luts[2].empty()) + throw Exception("Cannot compute noOp of invalid Lut1D"); + + if(!m_cacheID.empty()) + return m_isNoOp; + + finalize(); + + return m_isNoOp; + } + + void Lut1D::unfinalize() + { + AutoMutex lock(m_mutex); + m_cacheID = ""; + m_isNoOp = false; + } + + void Lut1D::finalize() const + { + m_isNoOp = IsLut1DNoOp(*this, maxerror, errortype); + + if(m_isNoOp) + { + m_cacheID = "<NULL 1D>"; + } + else + { + md5_state_t state; + md5_byte_t digest[16]; + + md5_init(&state); + md5_append(&state, (const md5_byte_t *)from_min, 3*sizeof(float)); + md5_append(&state, (const md5_byte_t *)from_max, 3*sizeof(float)); + + for(int i=0; i<3; ++i) + { + md5_append( &state, (const md5_byte_t *)&(luts[i][0]), + (int) (luts[i].size()*sizeof(float)) ); + } + + md5_finish(&state, digest); + + m_cacheID = GetPrintableHash(digest); + } + } + + + namespace + { + // Note: This function assumes that minVal is less than maxVal + inline int clamp(float k, float minVal, float maxVal) + { + return static_cast<int>(roundf(std::max(std::min(k, maxVal), minVal))); + } + + + /////////////////////////////////////////////////////////////////////// + // Nearest Forward + + inline float lookupNearest_1D(float index, float maxIndex, const float * simple_lut) + { + return simple_lut[clamp(index, 0.0f, maxIndex)]; + } + + void Lut1D_Nearest(float* rgbaBuffer, long numPixels, const Lut1D & lut) + { + float maxIndex[3]; + float mInv[3]; + float b[3]; + float mInv_x_maxIndex[3]; + const float* startPos[3]; + + for(int i=0; i<3; ++i) + { + maxIndex[i] = (float) (lut.luts[i].size() - 1); + mInv[i] = 1.0f / (lut.from_max[i] - lut.from_min[i]); + b[i] = lut.from_min[i]; + mInv_x_maxIndex[i] = (float) (mInv[i] * maxIndex[i]); + startPos[i] = &(lut.luts[i][0]); + } + + for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex) + { + if(!isnan(rgbaBuffer[0])) + rgbaBuffer[0] = lookupNearest_1D(mInv_x_maxIndex[0] * (rgbaBuffer[0] - b[0]), maxIndex[0], startPos[0]); + if(!isnan(rgbaBuffer[1])) + rgbaBuffer[1] = lookupNearest_1D(mInv_x_maxIndex[1] * (rgbaBuffer[1] - b[1]), maxIndex[1], startPos[1]); + if(!isnan(rgbaBuffer[2])) + rgbaBuffer[2] = lookupNearest_1D(mInv_x_maxIndex[2] * (rgbaBuffer[2] - b[2]), maxIndex[2], startPos[2]); + + rgbaBuffer += 4; + } + } +#ifdef USE_SSE + void Lut1D_Nearest_SSE(float* rgbaBuffer, long numPixels, const Lut1D & lut) + { + // orig: 546 ms + // curr: 91 ms + + // These are all sized 4, to allow simpler sse loading + float maxIndex[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + float mInv[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + float b[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + float mInv_x_maxIndex[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + const float* startPos[3]; + + for(int i=0; i<3; ++i) + { + maxIndex[i] = (float) (lut.luts[i].size() - 1); + mInv[i] = 1.0f / (lut.from_max[i] - lut.from_min[i]); + b[i] = lut.from_min[i]; + mInv_x_maxIndex[i] = (float) (mInv[i] * maxIndex[i]); + startPos[i] = &(lut.luts[i][0]); + } + + const __m128 _zero = _mm_setzero_ps(); + const __m128 _mInv_x_maxIndex = _mm_loadu_ps(mInv_x_maxIndex); + const __m128 _b = _mm_loadu_ps(b); + const __m128 _maxIndex = _mm_loadu_ps(maxIndex); + const __m128 _half = _mm_set1_ps(0.5f); + + float result[4]; + + for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex) + { + // TODO: SSE Optimized nancheck + + __m128 p = _mm_loadu_ps(rgbaBuffer); + + // mInv_x_maxIndex * (p - b) + p = _mm_sub_ps(p, _b); + p = _mm_mul_ps(p, _mInv_x_maxIndex); + + // clamp _zero <= b <= _maxIndex + p = _mm_max_ps(p, _zero); + p = _mm_min_ps(p, _maxIndex); + + // add 0.5f for rounding + p = _mm_add_ps(p, _half); + + _mm_storeu_ps(result, p); + + + // TODO: use native SSE to convert to an int? + // _mm_cvttss_si32 + // Converts the lower single-precision, floating-point value of + // a to a 32-bit integer with truncation + // + // _mm_cvttps_pi32 converts 2 floats to 2 32-bit packed ints, + // with truncation + + if(!isnan(result[0])) + rgbaBuffer[0] = startPos[0][(int)(result[0])]; + if(!isnan(result[1])) + rgbaBuffer[1] = startPos[1][(int)(result[1])]; + if(!isnan(result[2])) + rgbaBuffer[2] = startPos[2][(int)(result[2])]; + + rgbaBuffer += 4; + } + } +#endif + + + /////////////////////////////////////////////////////////////////////// + // Linear Forward + + inline float lookupLinear_1D(float index, float maxIndex, const float * simple_lut) + { + int indexLow = clamp(std::floor(index), 0.0f, maxIndex); + int indexHigh = clamp(std::ceil(index), 0.0f, maxIndex); + float delta = index - (float)indexLow; + return (1.0f-delta) * simple_lut[indexLow] + delta * simple_lut[indexHigh]; + } + + void Lut1D_Linear(float* rgbaBuffer, long numPixels, const Lut1D & lut) + { + float maxIndex[3]; + float mInv[3]; + float b[3]; + float mInv_x_maxIndex[3]; + const float* startPos[3]; + + for(int i=0; i<3; ++i) + { + maxIndex[i] = (float) (lut.luts[i].size() - 1); + mInv[i] = 1.0f / (lut.from_max[i] - lut.from_min[i]); + b[i] = lut.from_min[i]; + mInv_x_maxIndex[i] = (float) (mInv[i] * maxIndex[i]); + startPos[i] = &(lut.luts[i][0]); + } + + for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex) + { + if(!isnan(rgbaBuffer[0])) + rgbaBuffer[0] = lookupLinear_1D(mInv_x_maxIndex[0] * (rgbaBuffer[0] - b[0]), maxIndex[0], startPos[0]); + if(!isnan(rgbaBuffer[1])) + rgbaBuffer[1] = lookupLinear_1D(mInv_x_maxIndex[1] * (rgbaBuffer[1] - b[1]), maxIndex[1], startPos[1]); + if(!isnan(rgbaBuffer[2])) + rgbaBuffer[2] = lookupLinear_1D(mInv_x_maxIndex[2] * (rgbaBuffer[2] - b[2]), maxIndex[2], startPos[2]); + + rgbaBuffer += 4; + } + } + + + + /////////////////////////////////////////////////////////////////////// + // Nearest Inverse + + inline float reverseLookupNearest_1D(const float v, const float *start, const float *end) + { + const float *lowbound = std::lower_bound(start, end, v); + if (lowbound != start) --lowbound; + + const float *highbound = lowbound; + if (highbound < end - 1) ++highbound; + + // NOTE: Not dividing result by /(size-1) anymore + if (fabsf(v - *lowbound) < fabsf(v - *highbound)) + { + return (float)(lowbound-start); + } + else + { + return (float)(highbound-start); + } + } + + void Lut1D_NearestInverse(float* rgbaBuffer, long numPixels, const Lut1D & lut) + { + float m[3]; + float b[3]; + const float* startPos[3]; + const float* endPos[3]; + + for(int i=0; i<3; ++i) + { + m[i] = (lut.from_max[i] - lut.from_min[i]); + b[i] = lut.from_min[i]; + + startPos[i] = &(lut.luts[i][0]); + endPos[i] = startPos[i] + lut.luts[i].size(); + + // Roll the size division into m as an optimization + m[i] /= (float) (lut.luts[i].size() - 1); + } + + for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex) + { + if(!isnan(rgbaBuffer[0])) + rgbaBuffer[0] = m[0] * reverseLookupNearest_1D(rgbaBuffer[0], startPos[0], endPos[0]) + b[0]; + if(!isnan(rgbaBuffer[1])) + rgbaBuffer[1] = m[1] * reverseLookupNearest_1D(rgbaBuffer[1], startPos[1], endPos[1]) + b[1]; + if(!isnan(rgbaBuffer[2])) + rgbaBuffer[2] = m[2] * reverseLookupNearest_1D(rgbaBuffer[2], startPos[2], endPos[2]) + b[2]; + + rgbaBuffer += 4; + } + } + + /////////////////////////////////////////////////////////////////////// + // Linear Inverse + + inline float reverseLookupLinear_1D(const float v, const float *start, const float *end, float invMaxIndex) + { + const float *lowbound = std::lower_bound(start, end, v); + if (lowbound != start) --lowbound; + + const float *highbound = lowbound; + if (highbound < end - 1) ++highbound; + + // lowbound is the lower bound, highbound is the upper bound. + float delta = 0.0; + if (*highbound > *lowbound) + { + delta = (v - *lowbound) / (*highbound - *lowbound); + } + + return ((float)(lowbound - start) + delta) * invMaxIndex; + } + + void Lut1D_LinearInverse(float* rgbaBuffer, long numPixels, const Lut1D & lut) + { + float m[3]; + float b[3]; + const float* startPos[3]; + const float* endPos[3]; + float invMaxIndex[3]; + + for(int i=0; i<3; ++i) + { + m[i] = (lut.from_max[i] - lut.from_min[i]); + b[i] = lut.from_min[i]; + + startPos[i] = &(lut.luts[i][0]); + endPos[i] = startPos[i] + lut.luts[i].size(); + + invMaxIndex[i] = 1.0f / (float) (lut.luts[i].size() - 1); + } + + for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex) + { + if(!isnan(rgbaBuffer[0])) + rgbaBuffer[0] = m[0] * reverseLookupLinear_1D(rgbaBuffer[0], startPos[0], endPos[0], invMaxIndex[0]) + b[0]; + if(!isnan(rgbaBuffer[1])) + rgbaBuffer[1] = m[1] * reverseLookupLinear_1D(rgbaBuffer[1], startPos[1], endPos[1], invMaxIndex[0]) + b[1]; + if(!isnan(rgbaBuffer[2])) + rgbaBuffer[2] = m[2] * reverseLookupLinear_1D(rgbaBuffer[2], startPos[2], endPos[2], invMaxIndex[0]) + b[2]; + + rgbaBuffer += 4; + } + } + + + } + + namespace + { + class Lut1DOp : public Op + { + public: + Lut1DOp(const Lut1DRcPtr & lut, + Interpolation interpolation, + TransformDirection direction); + virtual ~Lut1DOp(); + + virtual OpRcPtr clone() const; + + virtual std::string getInfo() const; + virtual std::string getCacheID() const; + + virtual bool isNoOp() const; + virtual bool isSameType(const OpRcPtr & op) const; + virtual bool isInverse(const OpRcPtr & op) const; + virtual bool hasChannelCrosstalk() const; + virtual void finalize(); + virtual void apply(float* rgbaBuffer, long numPixels) const; + + virtual bool supportsGpuShader() const; + virtual void writeGpuShader(std::ostream & shader, + const std::string & pixelName, + const GpuShaderDesc & shaderDesc) const; + + private: + const Lut1DRcPtr m_lut; + Interpolation m_interpolation; + TransformDirection m_direction; + + std::string m_cacheID; + }; + + typedef OCIO_SHARED_PTR<Lut1DOp> Lut1DOpRcPtr; + + + Lut1DOp::Lut1DOp(const Lut1DRcPtr & lut, + Interpolation interpolation, + TransformDirection direction): + Op(), + m_lut(lut), + m_interpolation(interpolation), + m_direction(direction) + { + } + + OpRcPtr Lut1DOp::clone() const + { + OpRcPtr op = OpRcPtr(new Lut1DOp(m_lut, m_interpolation, m_direction)); + return op; + } + + Lut1DOp::~Lut1DOp() + { } + + std::string Lut1DOp::getInfo() const + { + return "<Lut1DOp>"; + } + + std::string Lut1DOp::getCacheID() const + { + return m_cacheID; + } + + // TODO: compute real value for isNoOp + bool Lut1DOp::isNoOp() const + { + return false; + } + + bool Lut1DOp::isSameType(const OpRcPtr & op) const + { + Lut1DOpRcPtr typedRcPtr = DynamicPtrCast<Lut1DOp>(op); + if(!typedRcPtr) return false; + return true; + } + + bool Lut1DOp::isInverse(const OpRcPtr & op) const + { + Lut1DOpRcPtr typedRcPtr = DynamicPtrCast<Lut1DOp>(op); + if(!typedRcPtr) return false; + + if(GetInverseTransformDirection(m_direction) != typedRcPtr->m_direction) + return false; + + return (m_lut->getCacheID() == typedRcPtr->m_lut->getCacheID()); + } + + bool Lut1DOp::hasChannelCrosstalk() const + { + return false; + } + + void Lut1DOp::finalize() + { + if(m_direction == TRANSFORM_DIR_UNKNOWN) + { + throw Exception("Cannot apply lut1d op, unspecified transform direction."); + } + + // Validate the requested interpolation type + switch(m_interpolation) + { + // These are the allowed values. + case INTERP_NEAREST: + case INTERP_LINEAR: + break; + case INTERP_BEST: + m_interpolation = INTERP_LINEAR; + break; + case INTERP_UNKNOWN: + throw Exception("Cannot apply Lut1DOp, unspecified interpolation."); + break; + case INTERP_TETRAHEDRAL: + throw Exception("Cannot apply Lut1DOp, tetrahedral interpolation is not allowed for 1d luts."); + break; + default: + throw Exception("Cannot apply Lut1DOp, invalid interpolation specified."); + } + + if(m_lut->luts[0].empty() || m_lut->luts[1].empty() || m_lut->luts[2].empty()) + { + throw Exception("Cannot apply lut1d op, no lut data provided."); + } + + // Create the cacheID + std::ostringstream cacheIDStream; + cacheIDStream << "<Lut1DOp "; + cacheIDStream << m_lut->getCacheID() << " "; + cacheIDStream << InterpolationToString(m_interpolation) << " "; + cacheIDStream << TransformDirectionToString(m_direction) << " "; + cacheIDStream << ">"; + m_cacheID = cacheIDStream.str(); + } + + void Lut1DOp::apply(float* rgbaBuffer, long numPixels) const + { + if(m_direction == TRANSFORM_DIR_FORWARD) + { + if(m_interpolation == INTERP_NEAREST) + { +#ifdef USE_SSE + Lut1D_Nearest_SSE(rgbaBuffer, numPixels, *m_lut); +#else + Lut1D_Nearest(rgbaBuffer, numPixels, *m_lut); +#endif + } + else if(m_interpolation == INTERP_LINEAR) + { + Lut1D_Linear(rgbaBuffer, numPixels, *m_lut); + } + } + else if(m_direction == TRANSFORM_DIR_INVERSE) + { + if(m_interpolation == INTERP_NEAREST) + { + Lut1D_NearestInverse(rgbaBuffer, numPixels, *m_lut); + } + else if(m_interpolation == INTERP_LINEAR) + { + Lut1D_LinearInverse(rgbaBuffer, numPixels, *m_lut); + } + } + } + + bool Lut1DOp::supportsGpuShader() const + { + return false; + } + + void Lut1DOp::writeGpuShader(std::ostream & /*shader*/, + const std::string & /*pixelName*/, + const GpuShaderDesc & /*shaderDesc*/) const + { + throw Exception("Lut1DOp does not support analytical shader generation."); + } + } + + void CreateLut1DOp(OpRcPtrVec & ops, + const Lut1DRcPtr & lut, + Interpolation interpolation, + TransformDirection direction) + { + if(lut->isNoOp()) return; + + // TODO: Detect if lut1d can be exactly approximated as y = mx + b + // If so, return a mtx instead. + + ops.push_back( OpRcPtr(new Lut1DOp(lut, interpolation, direction)) ); + } + + + void GenerateIdentityLut1D(float* img, int numElements, int numChannels) + { + if(!img) return; + int numChannelsToFill = std::min(3, numChannels); + + float scale = 1.0f / ((float) numElements - 1.0f); + for(int i=0; i<numElements; i++) + { + for(int c=0; c<numChannelsToFill; ++c) + { + img[numChannels*i+c] = scale * (float)(i); + } + } + } +} +OCIO_NAMESPACE_EXIT + + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef OCIO_UNIT_TEST + +#include <cstring> + +namespace OCIO = OCIO_NAMESPACE; +#include "UnitTest.h" + +OIIO_ADD_TEST(Lut1DOp, NoOp) +{ + // Make an identity lut + OCIO::Lut1DRcPtr lut = OCIO::Lut1D::Create(); + lut->from_min[0] = 0.0f; + lut->from_min[1] = 0.0f; + lut->from_min[2] = 0.0f; + + lut->from_max[0] = 1.0f; + lut->from_max[1] = 1.0f; + lut->from_max[2] = 1.0f; + + int size = 256; + for(int i=0; i<size; ++i) + { + float x = (float)i / (float)(size-1); + for(int c=0; c<3; ++c) + { + lut->luts[c].push_back(x); + } + } + + lut->maxerror = 1e-5f; + lut->errortype = OCIO::ERROR_RELATIVE; + OIIO_CHECK_EQUAL(lut->isNoOp(), true); + + lut->unfinalize(); + lut->maxerror = 1e-5f; + lut->errortype = OCIO::ERROR_ABSOLUTE; + OIIO_CHECK_EQUAL(lut->isNoOp(), true); + + // Edit the lut + // These should NOT be identity + lut->unfinalize(); + lut->luts[0][125] += 1e-3f; + lut->maxerror = 1e-5f; + lut->errortype = OCIO::ERROR_RELATIVE; + OIIO_CHECK_EQUAL(lut->isNoOp(), false); + + lut->unfinalize(); + lut->maxerror = 1e-5f; + lut->errortype = OCIO::ERROR_ABSOLUTE; + OIIO_CHECK_EQUAL(lut->isNoOp(), false); +} + + +OIIO_ADD_TEST(Lut1DOp, FiniteValue) +{ + // Make a lut that squares the input + OCIO::Lut1DRcPtr lut = OCIO::Lut1D::Create(); + lut->from_min[0] = 0.0f; + lut->from_min[1] = 0.0f; + lut->from_min[2] = 0.0f; + + lut->from_max[0] = 1.0f; + lut->from_max[1] = 1.0f; + lut->from_max[2] = 1.0f; + + int size = 256; + for(int i=0; i<size; ++i) + { + float x = (float)i / (float)(size-1); + float x2 = x*x; + + for(int c=0; c<3; ++c) + { + lut->luts[c].push_back(x2); + } + } + + lut->maxerror = 1e-5f; + lut->errortype = OCIO::ERROR_RELATIVE; + OIIO_CHECK_EQUAL(lut->isNoOp(), false); + + float inputBuffer_linearforward[4] = { 0.5f, 0.6f, 0.7f, 0.5f }; + float outputBuffer_linearforward[4] = { 0.25f, 0.36f, 0.49f, 0.5f }; + OCIO::Lut1D_Linear(inputBuffer_linearforward, 1, *lut); + for(int i=0; i <4; ++i) + { + OIIO_CHECK_CLOSE(inputBuffer_linearforward[i], outputBuffer_linearforward[i], 1e-5f); + } + + float inputBuffer_nearestforward[4] = { 0.5f, 0.6f, 0.7f, 0.5f }; + float outputBuffer_nearestforward[4] = { 0.25f, 0.36f, 0.49f, 0.5f }; + OCIO::Lut1D_Nearest(inputBuffer_nearestforward, 1, *lut); + for(int i=0; i <4; ++i) + { + OIIO_CHECK_CLOSE(inputBuffer_nearestforward[i], outputBuffer_nearestforward[i], 1e-2f); + } + + float inputBuffer_linearinverse[4] = { 0.5f, 0.6f, 0.7f, 0.5f }; + float outputBuffer_linearinverse[4] = { 0.25f, 0.36f, 0.49f, 0.5f }; + OCIO::Lut1D_LinearInverse(outputBuffer_linearinverse, 1, *lut); + for(int i=0; i <4; ++i) + { + OIIO_CHECK_CLOSE(inputBuffer_linearinverse[i], outputBuffer_linearinverse[i], 1e-5f); + } + + float inputBuffer_nearestinverse[4] = { 0.5f, 0.6f, 0.7f, 0.5f }; + float outputBuffer_nearestinverse[4] = { 0.25f, 0.36f, 0.49f, 0.5f }; + OCIO::Lut1D_NearestInverse(outputBuffer_nearestinverse, 1, *lut); + for(int i=0; i <4; ++i) + { + OIIO_CHECK_CLOSE(inputBuffer_nearestinverse[i], outputBuffer_nearestinverse[i], 1e-2f); + } +} + + +OIIO_ADD_TEST(Lut1DOp, Inverse) +{ + // Make a lut that squares the input + OCIO::Lut1DRcPtr lut_a = OCIO::Lut1D::Create(); + { + lut_a->from_min[0] = 0.0f; + lut_a->from_min[1] = 0.0f; + lut_a->from_min[2] = 0.0f; + lut_a->from_max[0] = 1.0f; + lut_a->from_max[1] = 1.0f; + lut_a->from_max[2] = 1.0f; + int size = 256; + for(int i=0; i<size; ++i) + { + float x = (float)i / (float)(size-1); + float x2 = x*x; + + for(int c=0; c<3; ++c) + { + lut_a->luts[c].push_back(x2); + } + } + lut_a->maxerror = 1e-5f; + lut_a->errortype = OCIO::ERROR_RELATIVE; + } + + // Make another lut + OCIO::Lut1DRcPtr lut_b = OCIO::Lut1D::Create(); + { + lut_b->from_min[0] = 0.5f; + lut_b->from_min[1] = 0.6f; + lut_b->from_min[2] = 0.7f; + lut_b->from_max[0] = 1.0f; + lut_b->from_max[1] = 1.0f; + lut_b->from_max[2] = 1.0f; + int size = 256; + for(int i=0; i<size; ++i) + { + float x = (float)i / (float)(size-1); + float x2 = x*x; + + for(int c=0; c<3; ++c) + { + lut_b->luts[c].push_back(x2); + } + } + lut_b->maxerror = 1e-5f; + lut_b->errortype = OCIO::ERROR_RELATIVE; + } + + OCIO::OpRcPtrVec ops; + CreateLut1DOp(ops, lut_a, OCIO::INTERP_NEAREST, OCIO::TRANSFORM_DIR_FORWARD); + CreateLut1DOp(ops, lut_a, OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_INVERSE); + CreateLut1DOp(ops, lut_b, OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_FORWARD); + CreateLut1DOp(ops, lut_b, OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_INVERSE); + + OIIO_CHECK_EQUAL(ops.size(), 4); + + OIIO_CHECK_ASSERT(ops[0]->isSameType(ops[1])); + OIIO_CHECK_ASSERT(ops[0]->isSameType(ops[2])); + OIIO_CHECK_ASSERT(ops[0]->isSameType(ops[3]->clone())); + + OIIO_CHECK_EQUAL( ops[0]->isInverse(ops[1]), true); + OIIO_CHECK_EQUAL( ops[0]->isInverse(ops[2]), false); + OIIO_CHECK_EQUAL( ops[0]->isInverse(ops[2]), false); + OIIO_CHECK_EQUAL( ops[0]->isInverse(ops[3]), false); + OIIO_CHECK_EQUAL( ops[2]->isInverse(ops[3]), true); +} + + +#ifdef USE_SSE +OIIO_ADD_TEST(Lut1DOp, SSE) +{ + // Make a lut that squares the input + OCIO::Lut1DRcPtr lut = OCIO::Lut1D::Create(); + lut->from_min[0] = 0.0f; + lut->from_min[1] = 0.0f; + lut->from_min[2] = 0.0f; + + lut->from_max[0] = 1.0f; + lut->from_max[1] = 1.0f; + lut->from_max[2] = 1.0f; + + int size = 256; + for(int i=0; i<size; ++i) + { + float x = (float)i / (float)(size-1); + float x2 = x*x; + + for(int c=0; c<3; ++c) + { + lut->luts[c].push_back(x2); + } + } + + lut->maxerror = 1e-5f; + lut->errortype = OCIO::ERROR_RELATIVE; + OIIO_CHECK_EQUAL(lut->isNoOp(), false); + + int NUM_TEST_PIXELS = 1024; + std::vector<float> testValues(NUM_TEST_PIXELS*4); + std::vector<float> outputBuffer_cpu(NUM_TEST_PIXELS*4); + std::vector<float> outputBuffer_sse(NUM_TEST_PIXELS*4); + + float val = -1.0f; + float delta = 0.00123456789f; + + for(int i=0; i<NUM_TEST_PIXELS*4; ++i) + { + testValues[i] = val; + val += delta; + } + + memcpy(&outputBuffer_cpu[0], &testValues[0], testValues.size()*sizeof(float)); + memcpy(&outputBuffer_sse[0], &testValues[0], testValues.size()*sizeof(float)); + + OCIO::Lut1D_Nearest(&outputBuffer_cpu[0], NUM_TEST_PIXELS, *lut); + OCIO::Lut1D_Nearest_SSE(&outputBuffer_sse[0], NUM_TEST_PIXELS, *lut); + + for(int i=0; i<NUM_TEST_PIXELS*4; ++i) + { + OIIO_CHECK_CLOSE(outputBuffer_cpu[i], outputBuffer_sse[i], 1e-7f); + //OIIO_CHECK_EQUAL(outputBuffer_cpu[i], outputBuffer_sse[i]); + } + + + // Test special values + /* + NUM_TEST_PIXELS = 2; + testValues.resize(NUM_TEST_PIXELS*4); + outputBuffer_cpu.resize(NUM_TEST_PIXELS*4); + outputBuffer_sse.resize(NUM_TEST_PIXELS*4); + + testValues[0] = std::numeric_limits<float>::signaling_NaN(); + testValues[1] = std::numeric_limits<float>::quiet_NaN(); + testValues[2] = -std::numeric_limits<float>::signaling_NaN(); + testValues[3] = -std::numeric_limits<float>::signaling_NaN(); + + testValues[4] = std::numeric_limits<float>::infinity(); + testValues[5] = -std::numeric_limits<float>::infinity(); + testValues[6] = 0.0f; + + + memcpy(&outputBuffer_cpu[0], &testValues[0], testValues.size()*sizeof(float)); + memcpy(&outputBuffer_sse[0], &testValues[0], testValues.size()*sizeof(float)); + + OCIO::Lut1D_Nearest(&outputBuffer_cpu[0], NUM_TEST_PIXELS, lut); + OCIO::Lut1D_Nearest_SSE(&outputBuffer_sse[0], NUM_TEST_PIXELS, lut); + + for(int i=0; i<NUM_TEST_PIXELS*4; ++i) + { + //OIIO_CHECK_CLOSE(outputBuffer_cpu[i], outputBuffer_sse[i], 1e-7f); + OIIO_CHECK_EQUAL(outputBuffer_cpu[i], outputBuffer_sse[i]); + } + + */ +} +#endif + + +OIIO_ADD_TEST(Lut1DOp, NanInf) +{ + // Make a lut that squares the input + OCIO::Lut1DRcPtr lut = OCIO::Lut1D::Create(); + lut->from_min[0] = 0.0f; + lut->from_min[1] = 0.0f; + lut->from_min[2] = 0.0f; + + lut->from_max[0] = 1.0f; + lut->from_max[1] = 1.0f; + lut->from_max[2] = 1.0f; + + int size = 256; + for(int i=0; i<size; ++i) + { + float x = (float)i / (float)(size-1); + float x2 = x*x; + + for(int c=0; c<3; ++c) + { + lut->luts[c].push_back(x2); + } + } + + lut->maxerror = 1e-5f; + lut->errortype = OCIO::ERROR_RELATIVE; + OIIO_CHECK_EQUAL(lut->isNoOp(), false); + + const float reference[4] = { std::numeric_limits<float>::signaling_NaN(), + std::numeric_limits<float>::quiet_NaN(), + std::numeric_limits<float>::infinity(), + -std::numeric_limits<float>::infinity() }; + /* + float output[4] = { std::numeric_limits<float>::signaling_NaN(), + std::numeric_limits<float>::quiet_NaN(), + 1.0f, + -std::numeric_limits<float>::infinity() }; + */ + float color[4]; + + memcpy(color, reference, 4*sizeof(float)); + OCIO::Lut1D_Linear(color, 1, *lut); + /* + for(int i=0; i<4; ++i) + { + if(isnan(color[i])) + { + std::cerr << color[i] << " " << output[i] << std::endl; + OIIO_CHECK_EQUAL(isnan(color[i]), isnan(output[i])); + } + else + { + OIIO_CHECK_EQUAL(color[i], output[i]); + } + } + */ + memcpy(color, reference, 4*sizeof(float)); + OCIO::Lut1D_Nearest(color, 1, *lut); + /* + for(int i=0; i <4; ++i) + { + if(isnan(color[i])) + { + OIIO_CHECK_EQUAL(isnan(color[i]), isnan(output[i])); + } + else + { + OIIO_CHECK_EQUAL(color[i], output[i]); + } + } + */ + memcpy(color, reference, 4*sizeof(float)); + OCIO::Lut1D_LinearInverse(color, 1, *lut); + /* + for(int i=0; i <4; ++i) + { + if(isnan(color[i])) + { + OIIO_CHECK_EQUAL(isnan(color[i]), isnan(output[i])); + } + else + { + OIIO_CHECK_EQUAL(color[i], output[i]); + } + } + */ + memcpy(color, reference, 4*sizeof(float)); + OCIO::Lut1D_NearestInverse(color, 1, *lut); + /* + for(int i=0; i <4; ++i) + { + if(isnan(color[i])) + { + OIIO_CHECK_EQUAL(isnan(color[i]), isnan(output[i])); + } + else + { + OIIO_CHECK_EQUAL(color[i], output[i]); + } + } + */ +} + +#endif // OCIO_UNIT_TEST diff --git a/src/core/Lut1DOp.h b/src/core/Lut1DOp.h new file mode 100644 index 0000000..d4d96c1 --- /dev/null +++ b/src/core/Lut1DOp.h @@ -0,0 +1,107 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_LUT1DOP_H +#define INCLUDED_OCIO_LUT1DOP_H + +#include <OpenColorIO/OpenColorIO.h> + +#include "Mutex.h" +#include "Op.h" + +#include <vector> + +OCIO_NAMESPACE_ENTER +{ + // TODO: turn into a class instead of a struct? + + enum ErrorType + { + ERROR_ABSOLUTE = 1, + ERROR_RELATIVE + }; + + + struct Lut1D; + typedef OCIO_SHARED_PTR<Lut1D> Lut1DRcPtr; + + struct Lut1D + { + static Lut1DRcPtr Create(); + + // This will compute the cacheid, and also + // determine if the lut is a no-op. + // If this lut is being read in from float ASCII text + // a value of 1e-5 is preferable. + // If this lut is being read in from integer ASCII + // representation, the value will depend on the LSB + // at the specified integer precision. + // Example: reading 10-bit ints? Use 2/1023.0 + // If you dont want to do the noop computation, + // specify a 0.0 tolerance. + // + // TODO: Instead of having each user compute the error + // individually, maybe they should specify the original file bitdepth? + // (with appropriate precision tokens?) + float maxerror; + ErrorType errortype; + + float from_min[3]; + float from_max[3]; + + typedef std::vector<float> fv_t; + fv_t luts[3]; + + std::string getCacheID() const; + bool isNoOp() const; + + void unfinalize(); + private: + Lut1D(); + + mutable std::string m_cacheID; + mutable bool m_isNoOp; + mutable Mutex m_mutex; + + void finalize() const; + }; + + typedef OCIO_SHARED_PTR<Lut1D> Lut1DRcPtr; + + // This generates an identity 1d lut, from 0.0 to 1.0 + void GenerateIdentityLut1D(float* img, int numElements, int numChannels); + + void CreateLut1DOp(OpRcPtrVec & ops, + const Lut1DRcPtr & lut, + Interpolation interpolation, + TransformDirection direction); +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/Lut3DOp.cpp b/src/core/Lut3DOp.cpp new file mode 100644 index 0000000..d64d451 --- /dev/null +++ b/src/core/Lut3DOp.cpp @@ -0,0 +1,982 @@ +/* +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 "HashUtils.h" +#include "Lut3DOp.h" +#include "MathUtils.h" + +#include <cmath> +#include <limits> +#include <sstream> + +OCIO_NAMESPACE_ENTER +{ + Lut3D::Lut3D() + { + for(int i=0; i<3; ++i) + { + from_min[i] = 0.0f; + from_max[i] = 1.0f; + size[i] = 0; + } + } + + Lut3DRcPtr Lut3D::Create() + { + return Lut3DRcPtr(new Lut3D()); + } + + std::string Lut3D::getCacheID() const + { + AutoMutex lock(m_cacheidMutex); + + if(lut.empty()) + throw Exception("Cannot compute cacheID of invalid Lut3D"); + + if(!m_cacheID.empty()) + return m_cacheID; + + md5_state_t state; + md5_byte_t digest[16]; + + md5_init(&state); + + md5_append(&state, (const md5_byte_t *)from_min, 3*sizeof(float)); + md5_append(&state, (const md5_byte_t *)from_max, 3*sizeof(float)); + md5_append(&state, (const md5_byte_t *)size, 3*sizeof(int)); + md5_append(&state, (const md5_byte_t *)&lut[0], (int) (lut.size()*sizeof(float))); + + md5_finish(&state, digest); + + m_cacheID = GetPrintableHash(digest); + + return m_cacheID; + } + + + namespace + { + // Linear + inline float lerp(float a, float b, float z) + { return (b - a) * z + a; } + + inline void lerp_rgb(float* out, float* a, float* b, float* z) + { + out[0] = (b[0] - a[0]) * z[0] + a[0]; + out[1] = (b[1] - a[1]) * z[1] + a[1]; + out[2] = (b[2] - a[2]) * z[2] + a[2]; + } + + // Bilinear + inline float lerp(float a, float b, float c, float d, float y, float z) + { return lerp(lerp(a, b, z), lerp(c, d, z), y); } + + inline void lerp_rgb(float* out, float* a, float* b, float* c, + float* d, float* y, float* z) + { + float v1[3]; + float v2[3]; + + lerp_rgb(v1, a, b, z); + lerp_rgb(v2, c, d, z); + lerp_rgb(out, v1, v2, y); + } + + // Trilinear + inline float lerp(float a, float b, float c, float d, + float e, float f, float g, float h, + float x, float y, float z) + { return lerp(lerp(a,b,c,d,y,z), lerp(e,f,g,h,y,z), x); } + + inline void lerp_rgb(float* out, float* a, float* b, float* c, float* d, + float* e, float* f, float* g, float* h, + float* x, float* y, float* z) + { + float v1[3]; + float v2[3]; + + lerp_rgb(v1, a,b,c,d,y,z); + lerp_rgb(v2, e,f,g,h,y,z); + lerp_rgb(out, v1, v2, x); + } + + inline float lookupNearest_3D(int rIndex, int gIndex, int bIndex, + int size_red, int size_green, int size_blue, + const float* simple_rgb_lut, int channelIndex) + { + return simple_rgb_lut[ GetLut3DIndex_B(rIndex, gIndex, bIndex, + size_red, size_green, size_blue) + channelIndex]; + } + + inline void lookupNearest_3D_rgb(float* rgb, + int rIndex, int gIndex, int bIndex, + int size_red, int size_green, int size_blue, + const float* simple_rgb_lut) + { + int offset = GetLut3DIndex_B(rIndex, gIndex, bIndex, size_red, size_green, size_blue); + rgb[0] = simple_rgb_lut[offset]; + rgb[1] = simple_rgb_lut[offset+1]; + rgb[2] = simple_rgb_lut[offset+2]; + } + + // Note: This function assumes that minVal is less than maxVal + inline int clamp(float k, float minVal, float maxVal) + { + return static_cast<int>(roundf(std::max(std::min(k, maxVal), minVal))); + } + + /////////////////////////////////////////////////////////////////////// + // Nearest Forward + + void Lut3D_Nearest(float* rgbaBuffer, long numPixels, const Lut3D & lut) + { + float maxIndex[3]; + float mInv[3]; + float b[3]; + float mInv_x_maxIndex[3]; + int lutSize[3]; + const float* startPos = &(lut.lut[0]); + + for(int i=0; i<3; ++i) + { + maxIndex[i] = (float) (lut.size[i] - 1); + mInv[i] = 1.0f / (lut.from_max[i] - lut.from_min[i]); + b[i] = lut.from_min[i]; + mInv_x_maxIndex[i] = (float) (mInv[i] * maxIndex[i]); + + lutSize[i] = lut.size[i]; + } + + int localIndex[3]; + + for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex) + { + if(isnan(rgbaBuffer[0]) || isnan(rgbaBuffer[1]) || isnan(rgbaBuffer[2])) + { + rgbaBuffer[0] = std::numeric_limits<float>::quiet_NaN(); + rgbaBuffer[1] = std::numeric_limits<float>::quiet_NaN(); + rgbaBuffer[2] = std::numeric_limits<float>::quiet_NaN(); + } + else + { + localIndex[0] = clamp(mInv_x_maxIndex[0] * (rgbaBuffer[0] - b[0]), 0.0f, maxIndex[0]); + localIndex[1] = clamp(mInv_x_maxIndex[1] * (rgbaBuffer[1] - b[1]), 0.0f, maxIndex[1]); + localIndex[2] = clamp(mInv_x_maxIndex[2] * (rgbaBuffer[2] - b[2]), 0.0f, maxIndex[2]); + + lookupNearest_3D_rgb(rgbaBuffer, localIndex[0], localIndex[1], localIndex[2], + lutSize[0], lutSize[1], lutSize[2], startPos); + } + + rgbaBuffer += 4; + } + } + + /////////////////////////////////////////////////////////////////////// + // Linear Forward + + void Lut3D_Linear(float* rgbaBuffer, long numPixels, const Lut3D & lut) + { + float maxIndex[3]; + float mInv[3]; + float b[3]; + float mInv_x_maxIndex[3]; + int lutSize[3]; + const float* startPos = &(lut.lut[0]); + + for(int i=0; i<3; ++i) + { + maxIndex[i] = (float) (lut.size[i] - 1); + mInv[i] = 1.0f / (lut.from_max[i] - lut.from_min[i]); + b[i] = lut.from_min[i]; + mInv_x_maxIndex[i] = (float) (mInv[i] * maxIndex[i]); + + lutSize[i] = lut.size[i]; + } + + for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex) + { + + if(isnan(rgbaBuffer[0]) || isnan(rgbaBuffer[1]) || isnan(rgbaBuffer[2])) + { + rgbaBuffer[0] = std::numeric_limits<float>::quiet_NaN(); + rgbaBuffer[1] = std::numeric_limits<float>::quiet_NaN(); + rgbaBuffer[2] = std::numeric_limits<float>::quiet_NaN(); + } + else + { + float localIndex[3]; + int indexLow[3]; + int indexHigh[3]; + float delta[3]; + float a[3]; + float b_[3]; + float c[3]; + float d[3]; + float e[3]; + float f[3]; + float g[3]; + float h[4]; + float x[4]; + float y[4]; + float z[4]; + + localIndex[0] = std::max(std::min(mInv_x_maxIndex[0] * (rgbaBuffer[0] - b[0]), maxIndex[0]), 0.0f); + localIndex[1] = std::max(std::min(mInv_x_maxIndex[1] * (rgbaBuffer[1] - b[1]), maxIndex[1]), 0.0f); + localIndex[2] = std::max(std::min(mInv_x_maxIndex[2] * (rgbaBuffer[2] - b[2]), maxIndex[2]), 0.0f); + + indexLow[0] = static_cast<int>(std::floor(localIndex[0])); + indexLow[1] = static_cast<int>(std::floor(localIndex[1])); + indexLow[2] = static_cast<int>(std::floor(localIndex[2])); + + indexHigh[0] = static_cast<int>(std::ceil(localIndex[0])); + indexHigh[1] = static_cast<int>(std::ceil(localIndex[1])); + indexHigh[2] = static_cast<int>(std::ceil(localIndex[2])); + + delta[0] = localIndex[0] - static_cast<float>(indexLow[0]); + delta[1] = localIndex[1] - static_cast<float>(indexLow[1]); + delta[2] = localIndex[2] - static_cast<float>(indexLow[2]); + + // Lookup 8 corners of cube + lookupNearest_3D_rgb(a, indexLow[0], indexLow[1], indexLow[2], lutSize[0], lutSize[1], lutSize[2], startPos); + lookupNearest_3D_rgb(b_, indexLow[0], indexLow[1], indexHigh[2], lutSize[0], lutSize[1], lutSize[2], startPos); + lookupNearest_3D_rgb(c, indexLow[0], indexHigh[1], indexLow[2], lutSize[0], lutSize[1], lutSize[2], startPos); + lookupNearest_3D_rgb(d, indexLow[0], indexHigh[1], indexHigh[2], lutSize[0], lutSize[1], lutSize[2], startPos); + lookupNearest_3D_rgb(e, indexHigh[0], indexLow[1], indexLow[2], lutSize[0], lutSize[1], lutSize[2], startPos); + lookupNearest_3D_rgb(f, indexHigh[0], indexLow[1], indexHigh[2], lutSize[0], lutSize[1], lutSize[2], startPos); + lookupNearest_3D_rgb(g, indexHigh[0], indexHigh[1], indexLow[2], lutSize[0], lutSize[1], lutSize[2], startPos); + lookupNearest_3D_rgb(h, indexHigh[0], indexHigh[1], indexHigh[2], lutSize[0], lutSize[1], lutSize[2], startPos); + + // Also store the 3d interpolation coordinates + x[0] = delta[0]; x[1] = delta[0]; x[2] = delta[0]; + y[0] = delta[1]; y[1] = delta[1]; y[2] = delta[1]; + z[0] = delta[2]; z[1] = delta[2]; z[2] = delta[2]; + + // Do a trilinear interpolation of the 8 corners + // 4726.8 scanlines/sec + + lerp_rgb(rgbaBuffer, a, b_, c, d, e, f, g, h, + x, y, z); + } + + rgbaBuffer += 4; + } + } + } + + + void Lut3D_Tetrahedral(float* rgbaBuffer, long numPixels, const Lut3D & lut) + { + // Tetrahedral interoplation, as described by: + // http://www.filmlight.ltd.uk/pdf/whitepapers/FL-TL-TN-0057-SoftwareLib.pdf + // http://blogs.mathworks.com/steve/2006/11/24/tetrahedral-interpolation-for-colorspace-conversion/ + // http://www.hpl.hp.com/techreports/98/HPL-98-95.html + + float maxIndex[3]; + float mInv[3]; + float b[3]; + float mInv_x_maxIndex[3]; + int lutSize[3]; + const float* startPos = &(lut.lut[0]); + + for(int i=0; i<3; ++i) + { + maxIndex[i] = (float) (lut.size[i] - 1); + mInv[i] = 1.0f / (lut.from_max[i] - lut.from_min[i]); + b[i] = lut.from_min[i]; + mInv_x_maxIndex[i] = (float) (mInv[i] * maxIndex[i]); + + lutSize[i] = lut.size[i]; + } + + for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex) + { + + if(isnan(rgbaBuffer[0]) || isnan(rgbaBuffer[1]) || isnan(rgbaBuffer[2])) + { + rgbaBuffer[0] = std::numeric_limits<float>::quiet_NaN(); + rgbaBuffer[1] = std::numeric_limits<float>::quiet_NaN(); + rgbaBuffer[2] = std::numeric_limits<float>::quiet_NaN(); + } + else + { + float localIndex[3]; + int indexLow[3]; + int indexHigh[3]; + float delta[3]; + + // Same index/delta calculation as linear interpolation + localIndex[0] = std::max(std::min(mInv_x_maxIndex[0] * (rgbaBuffer[0] - b[0]), maxIndex[0]), 0.0f); + localIndex[1] = std::max(std::min(mInv_x_maxIndex[1] * (rgbaBuffer[1] - b[1]), maxIndex[1]), 0.0f); + localIndex[2] = std::max(std::min(mInv_x_maxIndex[2] * (rgbaBuffer[2] - b[2]), maxIndex[2]), 0.0f); + + indexLow[0] = static_cast<int>(std::floor(localIndex[0])); + indexLow[1] = static_cast<int>(std::floor(localIndex[1])); + indexLow[2] = static_cast<int>(std::floor(localIndex[2])); + + indexHigh[0] = static_cast<int>(std::ceil(localIndex[0])); + indexHigh[1] = static_cast<int>(std::ceil(localIndex[1])); + indexHigh[2] = static_cast<int>(std::ceil(localIndex[2])); + + delta[0] = localIndex[0] - static_cast<float>(indexLow[0]); + delta[1] = localIndex[1] - static_cast<float>(indexLow[1]); + delta[2] = localIndex[2] - static_cast<float>(indexLow[2]); + + // Rebind for consistency with Truelight paper + float fx = delta[0]; + float fy = delta[1]; + float fz = delta[2]; + + // Compute index into LUT for surrounding corners + const int n000 = GetLut3DIndex_B(indexLow[0], indexLow[1], indexLow[2], + lutSize[0], lutSize[1], lutSize[2]); + const int n100 = GetLut3DIndex_B(indexHigh[0], indexLow[1], indexLow[2], + lutSize[0], lutSize[1], lutSize[2]); + const int n010 = GetLut3DIndex_B(indexLow[0], indexHigh[1], indexLow[2], + lutSize[0], lutSize[1], lutSize[2]); + const int n001 = GetLut3DIndex_B(indexLow[0], indexLow[1], indexHigh[2], + lutSize[0], lutSize[1], lutSize[2]); + const int n110 = GetLut3DIndex_B(indexHigh[0], indexHigh[1], indexLow[2], + lutSize[0], lutSize[1], lutSize[2]); + const int n101 = GetLut3DIndex_B(indexHigh[0], indexLow[1], indexHigh[2], + lutSize[0], lutSize[1], lutSize[2]); + const int n011 = GetLut3DIndex_B(indexLow[0], indexHigh[1], indexHigh[2], + lutSize[0], lutSize[1], lutSize[2]); + const int n111 = GetLut3DIndex_B(indexHigh[0], indexHigh[1], indexHigh[2], + lutSize[0], lutSize[1], lutSize[2]); + + if (fx > fy) { + if (fy > fz) { + rgbaBuffer[0] = + (1-fx) * startPos[n000] + + (fx-fy) * startPos[n100] + + (fy-fz) * startPos[n110] + + (fz) * startPos[n111]; + + rgbaBuffer[1] = + (1-fx) * startPos[n000+1] + + (fx-fy) * startPos[n100+1] + + (fy-fz) * startPos[n110+1] + + (fz) * startPos[n111+1]; + + rgbaBuffer[2] = + (1-fx) * startPos[n000+2] + + (fx-fy) * startPos[n100+2] + + (fy-fz) * startPos[n110+2] + + (fz) * startPos[n111+2]; + } + else if (fx > fz) + { + rgbaBuffer[0] = + (1-fx) * startPos[n000] + + (fx-fz) * startPos[n100] + + (fz-fy) * startPos[n101] + + (fy) * startPos[n111]; + + rgbaBuffer[1] = + (1-fx) * startPos[n000+1] + + (fx-fz) * startPos[n100+1] + + (fz-fy) * startPos[n101+1] + + (fy) * startPos[n111+1]; + + rgbaBuffer[2] = + (1-fx) * startPos[n000+2] + + (fx-fz) * startPos[n100+2] + + (fz-fy) * startPos[n101+2] + + (fy) * startPos[n111+2]; + } + else + { + rgbaBuffer[0] = + (1-fz) * startPos[n000] + + (fz-fx) * startPos[n001] + + (fx-fy) * startPos[n101] + + (fy) * startPos[n111]; + + rgbaBuffer[1] = + (1-fz) * startPos[n000+1] + + (fz-fx) * startPos[n001+1] + + (fx-fy) * startPos[n101+1] + + (fy) * startPos[n111+1]; + + rgbaBuffer[2] = + (1-fz) * startPos[n000+2] + + (fz-fx) * startPos[n001+2] + + (fx-fy) * startPos[n101+2] + + (fy) * startPos[n111+2]; + } + } + else + { + if (fz > fy) + { + rgbaBuffer[0] = + (1-fz) * startPos[n000] + + (fz-fy) * startPos[n001] + + (fy-fx) * startPos[n011] + + (fx) * startPos[n111]; + + rgbaBuffer[1] = + (1-fz) * startPos[n000+1] + + (fz-fy) * startPos[n001+1] + + (fy-fx) * startPos[n011+1] + + (fx) * startPos[n111+1]; + + rgbaBuffer[2] = + (1-fz) * startPos[n000+2] + + (fz-fy) * startPos[n001+2] + + (fy-fx) * startPos[n011+2] + + (fx) * startPos[n111+2]; + } + else if (fz > fx) + { + rgbaBuffer[0] = + (1-fy) * startPos[n000] + + (fy-fz) * startPos[n010] + + (fz-fx) * startPos[n011] + + (fx) * startPos[n111]; + + rgbaBuffer[1] = + (1-fy) * startPos[n000+1] + + (fy-fz) * startPos[n010+1] + + (fz-fx) * startPos[n011+1] + + (fx) * startPos[n111+1]; + + rgbaBuffer[2] = + (1-fy) * startPos[n000+2] + + (fy-fz) * startPos[n010+2] + + (fz-fx) * startPos[n011+2] + + (fx) * startPos[n111+2]; + } + else + { + rgbaBuffer[0] = + (1-fy) * startPos[n000] + + (fy-fx) * startPos[n010] + + (fx-fz) * startPos[n110] + + (fz) * startPos[n111]; + + rgbaBuffer[1] = + (1-fy) * startPos[n000+1] + + (fy-fx) * startPos[n010+1] + + (fx-fz) * startPos[n110+1] + + (fz) * startPos[n111+1]; + + rgbaBuffer[2] = + (1-fy) * startPos[n000+2] + + (fy-fx) * startPos[n010+2] + + (fx-fz) * startPos[n110+2] + + (fz) * startPos[n111+2]; + } + } + } // !isnan + + rgbaBuffer += 4; + } + } + + + void GenerateIdentityLut3D(float* img, int edgeLen, int numChannels, Lut3DOrder lut3DOrder) + { + if(!img) return; + if(numChannels < 3) + { + throw Exception("Cannot generate idenitity 3d lut with less than 3 channels."); + } + + float c = 1.0f / ((float)edgeLen - 1.0f); + + if(lut3DOrder == LUT3DORDER_FAST_RED) + { + for(int i=0; i<edgeLen*edgeLen*edgeLen; i++) + { + img[numChannels*i+0] = (float)(i%edgeLen) * c; + img[numChannels*i+1] = (float)((i/edgeLen)%edgeLen) * c; + img[numChannels*i+2] = (float)((i/edgeLen/edgeLen)%edgeLen) * c; + } + } + else if(lut3DOrder == LUT3DORDER_FAST_BLUE) + { + for(int i=0; i<edgeLen*edgeLen*edgeLen; i++) + { + img[numChannels*i+0] = (float)((i/edgeLen/edgeLen)%edgeLen) * c; + img[numChannels*i+1] = (float)((i/edgeLen)%edgeLen) * c; + img[numChannels*i+2] = (float)(i%edgeLen) * c; + } + } + else + { + throw Exception("Unknown Lut3DOrder."); + } + } + + + int Get3DLutEdgeLenFromNumPixels(int numPixels) + { + int dim = static_cast<int>(roundf(powf((float) numPixels, 1.0f/3.0f))); + + if(dim*dim*dim != numPixels) + { + std::ostringstream os; + os << "Cannot infer 3D Lut size. "; + os << numPixels << " element(s) does not correspond to a "; + os << "unform cube edge length. (nearest edge length is "; + os << dim << ")."; + throw Exception(os.str().c_str()); + } + + return dim; + } + + namespace + { + class Lut3DOp : public Op + { + public: + Lut3DOp(Lut3DRcPtr lut, + Interpolation interpolation, + TransformDirection direction); + virtual ~Lut3DOp(); + + virtual OpRcPtr clone() const; + + virtual std::string getInfo() const; + virtual std::string getCacheID() const; + + virtual bool isNoOp() const; + virtual bool isSameType(const OpRcPtr & op) const; + virtual bool isInverse(const OpRcPtr & op) const; + virtual bool hasChannelCrosstalk() const; + virtual void finalize(); + virtual void apply(float* rgbaBuffer, long numPixels) const; + + virtual bool supportsGpuShader() const; + virtual void writeGpuShader(std::ostream & shader, + const std::string & pixelName, + const GpuShaderDesc & shaderDesc) const; + + private: + Lut3DRcPtr m_lut; + Interpolation m_interpolation; + TransformDirection m_direction; + + // Set in finalize + std::string m_cacheID; + }; + + typedef OCIO_SHARED_PTR<Lut3DOp> Lut3DOpRcPtr; + + + Lut3DOp::Lut3DOp(Lut3DRcPtr lut, + Interpolation interpolation, + TransformDirection direction): + Op(), + m_lut(lut), + m_interpolation(interpolation), + m_direction(direction) + { + } + + OpRcPtr Lut3DOp::clone() const + { + OpRcPtr op = OpRcPtr(new Lut3DOp(m_lut, m_interpolation, m_direction)); + return op; + } + + Lut3DOp::~Lut3DOp() + { } + + std::string Lut3DOp::getInfo() const + { + return "<Lut3DOp>"; + } + + std::string Lut3DOp::getCacheID() const + { + return m_cacheID; + } + + // TODO: compute real value for isNoOp + bool Lut3DOp::isNoOp() const + { + return false; + } + + bool Lut3DOp::isSameType(const OpRcPtr & op) const + { + Lut3DOpRcPtr typedRcPtr = DynamicPtrCast<Lut3DOp>(op); + if(!typedRcPtr) return false; + return true; + } + + bool Lut3DOp::isInverse(const OpRcPtr & op) const + { + Lut3DOpRcPtr typedRcPtr = DynamicPtrCast<Lut3DOp>(op); + if(!typedRcPtr) return false; + + if(GetInverseTransformDirection(m_direction) != typedRcPtr->m_direction) + return false; + + return (m_lut->getCacheID() == typedRcPtr->m_lut->getCacheID()); + } + + // TODO: compute real value for hasChannelCrosstalk + bool Lut3DOp::hasChannelCrosstalk() const + { + return true; + } + + void Lut3DOp::finalize() + { + if(m_direction != TRANSFORM_DIR_FORWARD) + { + std::ostringstream os; + os << "3D Luts can only be applied in the forward direction. "; + os << "(" << TransformDirectionToString(m_direction) << ")"; + os << " specified."; + throw Exception(os.str().c_str()); + } + + // Validate the requested interpolation type + switch(m_interpolation) + { + // These are the allowed values. + case INTERP_NEAREST: + case INTERP_LINEAR: + case INTERP_TETRAHEDRAL: + break; + case INTERP_BEST: + m_interpolation = INTERP_LINEAR; + break; + case INTERP_UNKNOWN: + throw Exception("Cannot apply Lut3DOp, unspecified interpolation."); + break; + default: + throw Exception("Cannot apply Lut3DOp, invalid interpolation specified."); + } + + for(int i=0; i<3; ++i) + { + if(m_lut->size[i] == 0) + { + throw Exception("Cannot apply Lut3DOp, lut object is empty."); + } + // TODO if from_min[i] == from_max[i] + } + + if(m_lut->size[0]*m_lut->size[1]*m_lut->size[2] * 3 != (int)m_lut->lut.size()) + { + throw Exception("Cannot apply Lut3DOp, specified size does not match data."); + } + + // Create the cacheID + std::ostringstream cacheIDStream; + cacheIDStream << "<Lut3DOp "; + cacheIDStream << m_lut->getCacheID() << " "; + cacheIDStream << InterpolationToString(m_interpolation) << " "; + cacheIDStream << TransformDirectionToString(m_direction) << " "; + cacheIDStream << ">"; + m_cacheID = cacheIDStream.str(); + } + + void Lut3DOp::apply(float* rgbaBuffer, long numPixels) const + { + if(m_interpolation == INTERP_NEAREST) + { + Lut3D_Nearest(rgbaBuffer, numPixels, *m_lut); + } + else if(m_interpolation == INTERP_LINEAR) + { + Lut3D_Linear(rgbaBuffer, numPixels, *m_lut); + } + else if(m_interpolation == INTERP_TETRAHEDRAL) + { + Lut3D_Tetrahedral(rgbaBuffer, numPixels, *m_lut); + } + } + + bool Lut3DOp::supportsGpuShader() const + { + return false; + } + + void Lut3DOp::writeGpuShader(std::ostream & /*shader*/, + const std::string & /*pixelName*/, + const GpuShaderDesc & /*shaderDesc*/) const + { + throw Exception("Lut3DOp does not support analytical shader generation."); + } + } + + void CreateLut3DOp(OpRcPtrVec & ops, + Lut3DRcPtr lut, + Interpolation interpolation, + TransformDirection direction) + { + ops.push_back( Lut3DOpRcPtr(new Lut3DOp(lut, interpolation, direction)) ); + } +} +OCIO_NAMESPACE_EXIT + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef OCIO_UNIT_TEST + +#include <cstring> +#include <cstdlib> +#include <sys/time.h> + +namespace OCIO = OCIO_NAMESPACE; +#include "UnitTest.h" + +OIIO_ADD_TEST(Lut3DOp, NanInfValueCheck) +{ + OCIO::Lut3DRcPtr lut = OCIO::Lut3D::Create(); + + lut->from_min[0] = 0.0f; + lut->from_min[1] = 0.0f; + lut->from_min[2] = 0.0f; + + lut->from_max[0] = 1.0f; + lut->from_max[1] = 1.0f; + lut->from_max[2] = 1.0f; + + lut->size[0] = 3; + lut->size[1] = 3; + lut->size[2] = 3; + + lut->lut.resize(lut->size[0]*lut->size[1]*lut->size[2]*3); + + GenerateIdentityLut3D(&lut->lut[0], lut->size[0], 3, OCIO::LUT3DORDER_FAST_RED); + for(unsigned int i=0; i<lut->lut.size(); ++i) + { + lut->lut[i] = powf(lut->lut[i], 2.0f); + } + + const float reference[4] = { std::numeric_limits<float>::signaling_NaN(), + std::numeric_limits<float>::quiet_NaN(), + std::numeric_limits<float>::infinity(), + -std::numeric_limits<float>::infinity() }; + float color[4]; + + memcpy(color, reference, 4*sizeof(float)); + OCIO::Lut3D_Nearest(color, 1, *lut); + + memcpy(color, reference, 4*sizeof(float)); + OCIO::Lut3D_Linear(color, 1, *lut); +} + + +OIIO_ADD_TEST(Lut3DOp, ValueCheck) +{ + OCIO::Lut3DRcPtr lut = OCIO::Lut3D::Create(); + + lut->from_min[0] = 0.0f; + lut->from_min[1] = 0.0f; + lut->from_min[2] = 0.0f; + + lut->from_max[0] = 1.0f; + lut->from_max[1] = 1.0f; + lut->from_max[2] = 1.0f; + + lut->size[0] = 32; + lut->size[1] = 32; + lut->size[2] = 32; + + lut->lut.resize(lut->size[0]*lut->size[1]*lut->size[2]*3); + GenerateIdentityLut3D(&lut->lut[0], lut->size[0], 3, OCIO::LUT3DORDER_FAST_RED); + for(unsigned int i=0; i<lut->lut.size(); ++i) + { + lut->lut[i] = powf(lut->lut[i], 2.0f); + } + + const float reference[] = { 0.0f, 0.2f, 0.3f, 1.0f, + 0.1234f, 0.4567f, 0.9876f, 1.0f, + 11.0f, -0.5f, 0.5010f, 1.0f + }; + const float nearest[] = { 0.0f, 0.03746097535f, 0.0842871964f, 1.0f, + 0.01664932258f, 0.2039542049f, 1.0f, 1.0f, + 1.0f, 0.0f, 0.2663891613f, 1.0f + }; + const float linear[] = { 0.0f, 0.04016649351f, 0.09021852165f, 1.0f, + 0.01537752338f, 0.2087130845f, 0.9756000042f, 1.0f, + 1.0f, 0.0f, 0.2512601018f, 1.0f + }; + float color[12]; + + // Check nearest + memcpy(color, reference, 12*sizeof(float)); + OCIO::Lut3D_Nearest(color, 3, *lut); + for(unsigned int i=0; i<12; ++i) + { + OIIO_CHECK_CLOSE(color[i], nearest[i], 1e-8); + } + + // Check linear + memcpy(color, reference, 12*sizeof(float)); + OCIO::Lut3D_Linear(color, 3, *lut); + for(unsigned int i=0; i<12; ++i) + { + OIIO_CHECK_CLOSE(color[i], linear[i], 1e-8); + } + + // Check tetrahedral + memcpy(color, reference, 12*sizeof(float)); + OCIO::Lut3D_Tetrahedral(color, 3, *lut); + for(unsigned int i=0; i<12; ++i) + { + OIIO_CHECK_CLOSE(color[i], linear[i], 1e-7); // Note, max delta lowered from 1e-8 + } +} + + + +OIIO_ADD_TEST(Lut3DOp, InverseComparisonCheck) +{ + OCIO::Lut3DRcPtr lut_a = OCIO::Lut3D::Create(); + lut_a->from_min[0] = 0.0f; + lut_a->from_min[1] = 0.0f; + lut_a->from_min[2] = 0.0f; + lut_a->from_max[0] = 1.0f; + lut_a->from_max[1] = 1.0f; + lut_a->from_max[2] = 1.0f; + lut_a->size[0] = 32; + lut_a->size[1] = 32; + lut_a->size[2] = 32; + lut_a->lut.resize(lut_a->size[0]*lut_a->size[1]*lut_a->size[2]*3); + GenerateIdentityLut3D(&lut_a->lut[0], lut_a->size[0], 3, OCIO::LUT3DORDER_FAST_RED); + + OCIO::Lut3DRcPtr lut_b = OCIO::Lut3D::Create(); + lut_b->from_min[0] = 0.5f; + lut_b->from_min[1] = 0.5f; + lut_b->from_min[2] = 0.5f; + lut_b->from_max[0] = 1.0f; + lut_b->from_max[1] = 1.0f; + lut_b->from_max[2] = 1.0f; + lut_b->size[0] = 32; + lut_b->size[1] = 32; + lut_b->size[2] = 32; + lut_b->lut.resize(lut_b->size[0]*lut_b->size[1]*lut_b->size[2]*3); + GenerateIdentityLut3D(&lut_b->lut[0], lut_b->size[0], 3, OCIO::LUT3DORDER_FAST_RED); + + OCIO::OpRcPtrVec ops; + CreateLut3DOp(ops, lut_a, OCIO::INTERP_NEAREST, OCIO::TRANSFORM_DIR_FORWARD); + CreateLut3DOp(ops, lut_a, OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_INVERSE); + CreateLut3DOp(ops, lut_b, OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_FORWARD); + CreateLut3DOp(ops, lut_b, OCIO::INTERP_LINEAR, OCIO::TRANSFORM_DIR_INVERSE); + + OIIO_CHECK_EQUAL(ops.size(), 4); + + OIIO_CHECK_ASSERT(ops[0]->isSameType(ops[1])); + OIIO_CHECK_ASSERT(ops[0]->isSameType(ops[2])); + OIIO_CHECK_ASSERT(ops[0]->isSameType(ops[3]->clone())); + + OIIO_CHECK_EQUAL( ops[0]->isInverse(ops[1]), true); + OIIO_CHECK_EQUAL( ops[0]->isInverse(ops[2]), false); + OIIO_CHECK_EQUAL( ops[0]->isInverse(ops[2]), false); + OIIO_CHECK_EQUAL( ops[0]->isInverse(ops[3]), false); + OIIO_CHECK_EQUAL( ops[2]->isInverse(ops[3]), true); +} + + +OIIO_ADD_TEST(Lut3DOp, PerformanceCheck) +{ + /* + OCIO::Lut3D lut; + + lut.from_min[0] = 0.0f; + lut.from_min[1] = 0.0f; + lut.from_min[2] = 0.0f; + + lut.from_max[0] = 1.0f; + lut.from_max[1] = 1.0f; + lut.from_max[2] = 1.0f; + + lut.size[0] = 32; + lut.size[1] = 32; + lut.size[2] = 32; + + lut.lut.resize(lut.size[0]*lut.size[1]*lut.size[2]*3); + GenerateIdentityLut3D(&lut.lut[0], lut.size[0], 3, OCIO::LUT3DORDER_FAST_RED); + + std::vector<float> img; + int xres = 2048; + int yres = 1; + int channels = 4; + img.resize(xres*yres*channels); + + srand48(0); + + // create random values from -0.05 to 1.05 + // (To simulate clipping performance) + + for(unsigned int i=0; i<img.size(); ++i) + { + float uniform = (float)drand48(); + img[i] = uniform*1.1f - 0.05f; + } + + timeval t; + gettimeofday(&t, 0); + double starttime = (double) t.tv_sec + (double) t.tv_usec / 1000000.0; + + int numloops = 1024; + for(int i=0; i<numloops; ++i) + { + //OCIO::Lut3D_Nearest(&img[0], xres*yres, lut); + OCIO::Lut3D_Linear(&img[0], xres*yres, lut); + } + + gettimeofday(&t, 0); + double endtime = (double) t.tv_sec + (double) t.tv_usec / 1000000.0; + double totaltime_a = (endtime-starttime)/numloops; + + printf("Linear: %0.1f ms - %0.1f fps\n", totaltime_a*1000.0, 1.0/totaltime_a); + + + // Tetrahedral + gettimeofday(&t, 0); + starttime = (double) t.tv_sec + (double) t.tv_usec / 1000000.0; + + for(int i=0; i<numloops; ++i) + { + OCIO::Lut3D_Tetrahedral(&img[0], xres*yres, lut); + } + + gettimeofday(&t, 0); + endtime = (double) t.tv_sec + (double) t.tv_usec / 1000000.0; + double totaltime_b = (endtime-starttime)/numloops; + + printf("Tetra: %0.1f ms - %0.1f fps\n", totaltime_b*1000.0, 1.0/totaltime_b); + + double speed_diff = totaltime_a/totaltime_b; + printf("Tetra is %.04f speed of Linear\n", speed_diff); + */ +} +#endif // OCIO_UNIT_TEST diff --git a/src/core/Lut3DOp.h b/src/core/Lut3DOp.h new file mode 100644 index 0000000..bba532b --- /dev/null +++ b/src/core/Lut3DOp.h @@ -0,0 +1,114 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_LUT3DOP_H +#define INCLUDED_OCIO_LUT3DOP_H + +#include <OpenColorIO/OpenColorIO.h> + +#include "Mutex.h" +#include "Op.h" + +#include <vector> + +OCIO_NAMESPACE_ENTER +{ + // TODO: turn into a class instead of a struct? + + struct Lut3D; + typedef OCIO_SHARED_PTR<Lut3D> Lut3DRcPtr; + + struct Lut3D + { + static Lut3DRcPtr Create(); + + float from_min[3]; + float from_max[3]; + int size[3]; + + typedef std::vector<float> fv_t; + fv_t lut; + + std::string getCacheID() const; + + private: + Lut3D(); + mutable std::string m_cacheID; + mutable Mutex m_cacheidMutex; + }; + + // RGB channel ordering. + // Pixels ordered in such a way that the blue coordinate changes fastest, + // then the green coordinate, and finally, the red coordinate changes slowest + + inline int GetLut3DIndex_B(int indexR, int indexG, int indexB, + int sizeR, int sizeG, int /*sizeB*/) + { + return 3 * (indexR + sizeR * (indexG + sizeG * indexB)); + } + + + // RGB channel ordering. + // Pixels ordered in such a way that the red coordinate changes fastest, + // then the green coordinate, and finally, the blue coordinate changes slowest + + inline int GetLut3DIndex_R(int indexR, int indexG, int indexB, + int /*sizeR*/, int sizeG, int sizeB) + { + return 3 * (indexB + sizeB * (indexG + sizeG * indexR)); + } + + // What is the preferred order for the lut3d? + // I.e., are the first two entries change along + // the blue direction, or the red direction? + // OpenGL expects 'red' + + enum Lut3DOrder + { + LUT3DORDER_FAST_RED = 0, + LUT3DORDER_FAST_BLUE + }; + + void GenerateIdentityLut3D(float* img, int edgeLen, int numChannels, + Lut3DOrder lut3DOrder); + + // Essentially the cube root, but will throw an exception if the + // cuberoot is not exact. + int Get3DLutEdgeLenFromNumPixels(int numPixels); + + + + void CreateLut3DOp(OpRcPtrVec & ops, + Lut3DRcPtr lut, + Interpolation interpolation, + TransformDirection direction); +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/MathUtils.cpp b/src/core/MathUtils.cpp new file mode 100644 index 0000000..19691d7 --- /dev/null +++ b/src/core/MathUtils.cpp @@ -0,0 +1,603 @@ +/* +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 <cstring> + +#include "MathUtils.h" + +OCIO_NAMESPACE_ENTER +{ + namespace + { + const float FLTMIN = std::numeric_limits<float>::min(); + } + + bool IsScalarEqualToZero(float v) + { + return equalWithAbsError(v, 0.0f, FLTMIN); + } + + bool IsScalarEqualToOne(float v) + { + return equalWithAbsError(v, 1.0f, FLTMIN); + } + + float GetSafeScalarInverse(float v, float defaultValue) + { + if(IsScalarEqualToZero(v)) return defaultValue; + return 1.0f / v; + } + + bool IsVecEqualToZero(const float* v, int size) + { + for(int i=0; i<size; ++i) + { + if(!IsScalarEqualToZero(v[i])) return false; + } + return true; + } + + bool IsVecEqualToOne(const float* v, int size) + { + for(int i=0; i<size; ++i) + { + if(!IsScalarEqualToOne(v[i])) return false; + } + return true; + } + + bool VecContainsZero(const float* v, int size) + { + for(int i=0; i<size; ++i) + { + if(IsScalarEqualToZero(v[i])) return true; + } + return false; + } + + bool VecContainsOne(const float* v, int size) + { + for(int i=0; i<size; ++i) + { + if(IsScalarEqualToOne(v[i])) return true; + } + return false; + } + + bool VecsEqualWithRelError(const float* v1, int size1, + const float* v2, int size2, + float e) + { + if(size1 != size2) return false; + for(int i=0; i<size1; ++i) + { + if(!equalWithRelError(v1[i], v2[i], e)) return false; + } + + return true; + } + + double ClampToNormHalf(double val) + { + if(val < -GetHalfMax()) + { + return -GetHalfMax(); + } + + if(val > -GetHalfNormMin() && val<GetHalfNormMin()) + { + return 0.0; + } + + if(val > GetHalfMax()) + { + return GetHalfMax(); + } + + return val; + } + + bool IsM44Identity(const float* m44) + { + int index=0; + + for(unsigned int j=0; j<4; ++j) + { + for(unsigned int i=0; i<4; ++i) + { + index = 4*j+i; + + if(i==j) + { + if(!IsScalarEqualToOne(m44[index])) return false; + } + else + { + if(!IsScalarEqualToZero(m44[index])) return false; + } + } + } + + return true; + } + + bool IsM44Diagonal(const float* m44) + { + for(int i=0; i<16; ++i) + { + if((i%5)==0) continue; // If we're on the diagonal, skip it + if(!IsScalarEqualToZero(m44[i])) return false; + } + + return true; + } + + void GetM44Diagonal(float* out4, const float* m44) + { + for(int i=0; i<4; ++i) + { + out4[i] = m44[i*5]; + } + } + + // We use an intermediate double representation to make sure + // there is minimal float precision error on the determinant's computation + // (We have seen IsScalarEqualToZero sensitivities here on 32-bit + // virtual machines) + + bool GetM44Inverse(float* inverse_out, const float* m_) + { + double m[16]; + for(unsigned int i=0; i<16; ++i) m[i] = (double)m_[i]; + + double d10_21 = m[4]*m[9] - m[5]*m[8]; + double d10_22 = m[4]*m[10] - m[6]*m[8]; + double d10_23 = m[4]*m[11] - m[7]*m[8]; + double d11_22 = m[5]*m[10] - m[6]*m[9]; + double d11_23 = m[5]*m[11] - m[7]*m[9]; + double d12_23 = m[6]*m[11] - m[7]*m[10]; + + double a00 = m[13]*d12_23 - m[14]*d11_23 + m[15]*d11_22; + double a10 = m[14]*d10_23 - m[15]*d10_22 - m[12]*d12_23; + double a20 = m[12]*d11_23 - m[13]*d10_23 + m[15]*d10_21; + double a30 = m[13]*d10_22 - m[14]*d10_21 - m[12]*d11_22; + + double det = a00*m[0] + a10*m[1] + a20*m[2] + a30*m[3]; + + if(IsScalarEqualToZero((float)det)) return false; + + det = 1.0/det; + + double d00_31 = m[0]*m[13] - m[1]*m[12]; + double d00_32 = m[0]*m[14] - m[2]*m[12]; + double d00_33 = m[0]*m[15] - m[3]*m[12]; + double d01_32 = m[1]*m[14] - m[2]*m[13]; + double d01_33 = m[1]*m[15] - m[3]*m[13]; + double d02_33 = m[2]*m[15] - m[3]*m[14]; + + double a01 = m[9]*d02_33 - m[10]*d01_33 + m[11]*d01_32; + double a11 = m[10]*d00_33 - m[11]*d00_32 - m[8]*d02_33; + double a21 = m[8]*d01_33 - m[9]*d00_33 + m[11]*d00_31; + double a31 = m[9]*d00_32 - m[10]*d00_31 - m[8]*d01_32; + + double a02 = m[6]*d01_33 - m[7]*d01_32 - m[5]*d02_33; + double a12 = m[4]*d02_33 - m[6]*d00_33 + m[7]*d00_32; + double a22 = m[5]*d00_33 - m[7]*d00_31 - m[4]*d01_33; + double a32 = m[4]*d01_32 - m[5]*d00_32 + m[6]*d00_31; + + double a03 = m[2]*d11_23 - m[3]*d11_22 - m[1]*d12_23; + double a13 = m[0]*d12_23 - m[2]*d10_23 + m[3]*d10_22; + double a23 = m[1]*d10_23 - m[3]*d10_21 - m[0]*d11_23; + double a33 = m[0]*d11_22 - m[1]*d10_22 + m[2]*d10_21; + + inverse_out[0] = (float) (a00*det); + inverse_out[1] = (float) (a01*det); + inverse_out[2] = (float) (a02*det); + inverse_out[3] = (float) (a03*det); + inverse_out[4] = (float) (a10*det); + inverse_out[5] = (float) (a11*det); + inverse_out[6] = (float) (a12*det); + inverse_out[7] = (float) (a13*det); + inverse_out[8] = (float) (a20*det); + inverse_out[9] = (float) (a21*det); + inverse_out[10] = (float) (a22*det); + inverse_out[11] = (float) (a23*det); + inverse_out[12] = (float) (a30*det); + inverse_out[13] = (float) (a31*det); + inverse_out[14] = (float) (a32*det); + inverse_out[15] = (float) (a33*det); + + return true; + } + + void GetM44M44Product(float* mout, const float* m1_, const float* m2_) + { + float m1[16]; + float m2[16]; + memcpy(m1, m1_, 16*sizeof(float)); + memcpy(m2, m2_, 16*sizeof(float)); + + mout[ 0] = m1[ 0]*m2[0] + m1[ 1]*m2[4] + m1[ 2]*m2[ 8] + m1[ 3]*m2[12]; + mout[ 1] = m1[ 0]*m2[1] + m1[ 1]*m2[5] + m1[ 2]*m2[ 9] + m1[ 3]*m2[13]; + mout[ 2] = m1[ 0]*m2[2] + m1[ 1]*m2[6] + m1[ 2]*m2[10] + m1[ 3]*m2[14]; + mout[ 3] = m1[ 0]*m2[3] + m1[ 1]*m2[7] + m1[ 2]*m2[11] + m1[ 3]*m2[15]; + mout[ 4] = m1[ 4]*m2[0] + m1[ 5]*m2[4] + m1[ 6]*m2[ 8] + m1[ 7]*m2[12]; + mout[ 5] = m1[ 4]*m2[1] + m1[ 5]*m2[5] + m1[ 6]*m2[ 9] + m1[ 7]*m2[13]; + mout[ 6] = m1[ 4]*m2[2] + m1[ 5]*m2[6] + m1[ 6]*m2[10] + m1[ 7]*m2[14]; + mout[ 7] = m1[ 4]*m2[3] + m1[ 5]*m2[7] + m1[ 6]*m2[11] + m1[ 7]*m2[15]; + mout[ 8] = m1[ 8]*m2[0] + m1[ 9]*m2[4] + m1[10]*m2[ 8] + m1[11]*m2[12]; + mout[ 9] = m1[ 8]*m2[1] + m1[ 9]*m2[5] + m1[10]*m2[ 9] + m1[11]*m2[13]; + mout[10] = m1[ 8]*m2[2] + m1[ 9]*m2[6] + m1[10]*m2[10] + m1[11]*m2[14]; + mout[11] = m1[ 8]*m2[3] + m1[ 9]*m2[7] + m1[10]*m2[11] + m1[11]*m2[15]; + mout[12] = m1[12]*m2[0] + m1[13]*m2[4] + m1[14]*m2[ 8] + m1[15]*m2[12]; + mout[13] = m1[12]*m2[1] + m1[13]*m2[5] + m1[14]*m2[ 9] + m1[15]*m2[13]; + mout[14] = m1[12]*m2[2] + m1[13]*m2[6] + m1[14]*m2[10] + m1[15]*m2[14]; + mout[15] = m1[12]*m2[3] + m1[13]*m2[7] + m1[14]*m2[11] + m1[15]*m2[15]; + } + + namespace + { + + void GetM44V4Product(float* vout, const float* m, const float* v_) + { + float v[4]; + memcpy(v, v_, 4*sizeof(float)); + + vout[0] = m[ 0]*v[0] + m[ 1]*v[1] + m[ 2]*v[2] + m[ 3]*v[3]; + vout[1] = m[ 4]*v[0] + m[ 5]*v[1] + m[ 6]*v[2] + m[ 7]*v[3]; + vout[2] = m[ 8]*v[0] + m[ 9]*v[1] + m[10]*v[2] + m[11]*v[3]; + vout[3] = m[12]*v[0] + m[13]*v[1] + m[14]*v[2] + m[15]*v[3]; + } + + void GetV4Sum(float* vout, const float* v1, const float* v2) + { + for(int i=0; i<4; ++i) + { + vout[i] = v1[i] + v2[i]; + } + } + + } // anon namespace + + // All m(s) are 4x4. All v(s) are size 4 vectors. + // Return mout, vout, where mout*x+vout == m2*(m1*x+v1)+v2 + // mout = m2*m1 + // vout = m2*v1 + v2 + void GetMxbCombine(float* mout, float* vout, + const float* m1_, const float* v1_, + const float* m2_, const float* v2_) + { + float m1[16]; + float v1[4]; + float m2[16]; + float v2[4]; + memcpy(m1, m1_, 16*sizeof(float)); + memcpy(v1, v1_, 4*sizeof(float)); + memcpy(m2, m2_, 16*sizeof(float)); + memcpy(v2, v2_, 4*sizeof(float)); + + GetM44M44Product(mout, m2, m1); + GetM44V4Product(vout, m2, v1); + GetV4Sum(vout, vout, v2); + } + + namespace + { + + void GetMxbResult(float* vout, float* m, float* x, float* v) + { + GetM44V4Product(vout, m, x); + GetV4Sum(vout, vout, v); + } + + } // anon namespace + + bool GetMxbInverse(float* mout, float* vout, + const float* m_, const float* v_) + { + float m[16]; + float v[4]; + memcpy(m, m_, 16*sizeof(float)); + memcpy(v, v_, 4*sizeof(float)); + + if(!GetM44Inverse(mout, m)) return false; + + for(int i=0; i<4; ++i) + { + v[i] = -v[i]; + } + GetM44V4Product(vout, mout, v); + + return true; + } + +} + +OCIO_NAMESPACE_EXIT + + + + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef OCIO_UNIT_TEST + +OCIO_NAMESPACE_USING + +#include "UnitTest.h" + +OIIO_ADD_TEST(MathUtils, M44_is_diagonal) +{ + { + float m44[] = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + bool isdiag = IsM44Diagonal(m44); + OIIO_CHECK_EQUAL(isdiag, true); + + m44[1] += 1e-8f; + isdiag = IsM44Diagonal(m44); + OIIO_CHECK_EQUAL(isdiag, false); + } +} + + +OIIO_ADD_TEST(MathUtils, IsScalarEqualToZero) +{ + OIIO_CHECK_EQUAL(IsScalarEqualToZero(0.0f), true); + OIIO_CHECK_EQUAL(IsScalarEqualToZero(-0.0f), true); + + OIIO_CHECK_EQUAL(IsScalarEqualToZero(-1.072883670794056e-09f), false); + OIIO_CHECK_EQUAL(IsScalarEqualToZero(1.072883670794056e-09f), false); + + OIIO_CHECK_EQUAL(IsScalarEqualToZero(-1.072883670794056e-03f), false); + OIIO_CHECK_EQUAL(IsScalarEqualToZero(1.072883670794056e-03f), false); + + OIIO_CHECK_EQUAL(IsScalarEqualToZero(-1.072883670794056e-01f), false); + OIIO_CHECK_EQUAL(IsScalarEqualToZero(1.072883670794056e-01f), false); +} + +OIIO_ADD_TEST(MathUtils, GetM44Inverse) +{ + // This is a degenerate matrix, and shouldnt be invertible. + float m[] = { 0.3f, 0.3f, 0.3f, 0.0f, + 0.3f, 0.3f, 0.3f, 0.0f, + 0.3f, 0.3f, 0.3f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + + float mout[16]; + bool invertsuccess = GetM44Inverse(mout, m); + OIIO_CHECK_EQUAL(invertsuccess, false); +} + + +OIIO_ADD_TEST(MathUtils, M44_M44_product) +{ + { + float mout[16]; + float m1[] = { 1.0f, 2.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 3.0f, 1.0f }; + float m2[] = { 1.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 2.0f, 0.0f, 0.0f, 1.0f }; + GetM44M44Product(mout, m1, m2); + + float mcorrect[] = { 1.0f, 3.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 0.0f, + 2.0f, 1.0f, 3.0f, 1.0f }; + + for(int i=0; i<16; ++i) + { + OIIO_CHECK_EQUAL(mout[i], mcorrect[i]); + } + } +} + +OIIO_ADD_TEST(MathUtils, M44_V4_product) +{ + { + float vout[4]; + float m[] = { 1.0f, 2.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 3.0f, 1.0f }; + float v[] = { 1.0f, 2.0f, 3.0f, 4.0f }; + GetM44V4Product(vout, m, v); + + float vcorrect[] = { 5.0f, 5.0f, 4.0f, 15.0f }; + + for(int i=0; i<4; ++i) + { + OIIO_CHECK_EQUAL(vout[i], vcorrect[i]); + } + } +} + +OIIO_ADD_TEST(MathUtils, V4_add) +{ + { + float vout[4]; + float v1[] = { 1.0f, 2.0f, 3.0f, 4.0f }; + float v2[] = { 3.0f, 1.0f, 4.0f, 1.0f }; + GetV4Sum(vout, v1, v2); + + float vcorrect[] = { 4.0f, 3.0f, 7.0f, 5.0f }; + + for(int i=0; i<4; ++i) + { + OIIO_CHECK_EQUAL(vout[i], vcorrect[i]); + } + } +} + +OIIO_ADD_TEST(MathUtils, mxb_eval) +{ + { + float vout[4]; + float m[] = { 1.0f, 2.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 3.0f, 1.0f }; + float x[] = { 1.0f, 1.0f, 1.0f, 1.0f }; + float v[] = { 1.0f, 2.0f, 3.0f, 4.0f }; + GetMxbResult(vout, m, x, v); + + float vcorrect[] = { 4.0f, 4.0f, 5.0f, 9.0f }; + + for(int i=0; i<4; ++i) + { + OIIO_CHECK_EQUAL(vout[i], vcorrect[i]); + } + } +} + +OIIO_ADD_TEST(MathUtils, Combine_two_mxb) +{ + float m1[] = { 1.0f, 0.0f, 2.0f, 0.0f, + 2.0f, 1.0f, 0.0f, 1.0f, + 0.0f, 1.0f, 2.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 1.0f }; + float v1[] = { 1.0f, 2.0f, 3.0f, 4.0f }; + float m2[] = { 2.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 3.0f, 0.0f, + 1.0f,1.0f, 1.0f, 1.0f }; + float v2[] = { 0.0f, 2.0f, 1.0f, 0.0f }; + float tolerance = 1e-9f; + + { + float x[] = { 1.0f, 1.0f, 1.0f, 1.0f }; + float vout[4]; + + // Combine two mx+b operations, and apply to test point + float mout[16]; + float vcombined[4]; + GetMxbCombine(mout, vout, m1, v1, m2, v2); + GetMxbResult(vcombined, mout, x, vout); + + // Sequentially apply the two mx+b operations. + GetMxbResult(vout, m1, x, v1); + GetMxbResult(vout, m2, vout, v2); + + // Compare outputs + for(int i=0; i<4; ++i) + { + OIIO_CHECK_CLOSE(vcombined[i], vout[i], tolerance); + } + } + + { + float x[] = { 6.0f, 0.5f, -2.0f, -0.1f }; + float vout[4]; + + float mout[16]; + float vcombined[4]; + GetMxbCombine(mout, vout, m1, v1, m2, v2); + GetMxbResult(vcombined, mout, x, vout); + + GetMxbResult(vout, m1, x, v1); + GetMxbResult(vout, m2, vout, v2); + + for(int i=0; i<4; ++i) + { + OIIO_CHECK_CLOSE(vcombined[i], vout[i], tolerance); + } + } + + { + float x[] = { 26.0f, -0.5f, 0.005f, 12.1f }; + float vout[4]; + + float mout[16]; + float vcombined[4]; + GetMxbCombine(mout, vout, m1, v1, m2, v2); + GetMxbResult(vcombined, mout, x, vout); + + GetMxbResult(vout, m1, x, v1); + GetMxbResult(vout, m2, vout, v2); + + // We pick a not so small tolerance, as we're dealing with + // large numbers, and the error for CHECK_CLOSE is absolute. + for(int i=0; i<4; ++i) + { + OIIO_CHECK_CLOSE(vcombined[i], vout[i], 1e-3); + } + } +} + +OIIO_ADD_TEST(MathUtils, mxb_invert) +{ + { + float m[] = { 1.0f, 2.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 3.0f, 1.0f }; + float x[] = { 1.0f, 0.5f, -1.0f, 60.0f }; + float v[] = { 1.0f, 2.0f, 3.0f, 4.0f }; + + float vresult[4]; + float mout[16]; + float vout[4]; + + GetMxbResult(vresult, m, x, v); + bool invertsuccess = GetMxbInverse(mout, vout, m, v); + OIIO_CHECK_EQUAL(invertsuccess, true); + + GetMxbResult(vresult, mout, vresult, vout); + + float tolerance = 1e-9f; + for(int i=0; i<4; ++i) + { + OIIO_CHECK_CLOSE(vresult[i], x[i], tolerance); + } + } + + { + float m[] = { 0.3f, 0.3f, 0.3f, 0.0f, + 0.3f, 0.3f, 0.3f, 0.0f, + 0.3f, 0.3f, 0.3f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + float v[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + float mout[16]; + float vout[4]; + + bool invertsuccess = GetMxbInverse(mout, vout, m, v); + OIIO_CHECK_EQUAL(invertsuccess, false); + } +} + +#endif + diff --git a/src/core/MathUtils.h b/src/core/MathUtils.h new file mode 100644 index 0000000..ffe66f3 --- /dev/null +++ b/src/core/MathUtils.h @@ -0,0 +1,186 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_MATHUTILS_H +#define INCLUDED_OCIO_MATHUTILS_H + +#include <OpenColorIO/OpenColorIO.h> + +#include <cmath> +#include <vector> + +#include "Op.h" +#include "Platform.h" + +#ifdef WINDOWS +#include <float.h> +#endif + +OCIO_NAMESPACE_ENTER +{ + // From Imath + //-------------------------------------------------------------------------- + // Compare two numbers and test if they are "approximately equal": + // + // equalWithAbsError (x1, x2, e) + // + // Returns true if x1 is the same as x2 with an absolute error of + // no more than e, + // + // abs (x1 - x2) <= e + // + // equalWithRelError (x1, x2, e) + // + // Returns true if x1 is the same as x2 with an relative error of + // no more than e, + // + // abs (x1 - x2) <= e * x1 + // + //-------------------------------------------------------------------------- + + inline bool equalWithAbsError (float x1, float x2, float e) + { + return ((x1 > x2)? x1 - x2: x2 - x1) <= e; + } + + inline bool equalWithRelError (float x1, float x2, float e) + { + return ((x1 > x2)? x1 - x2: x2 - x1) <= e * ((x1 > 0)? x1: -x1); + } + + inline float lerpf(float a, float b, float z) + { + return (b - a) * z + a; + } + +#ifdef WINDOWS + inline double + round (float val) { + return floor (val + 0.5); + } + + inline float + roundf (float val) { + return static_cast<float>(round (val)); + } + + inline int + isnan (float val) { + // Windows uses a non-standard version of 'isnan' + return _isnan (val); + } +#else + +#ifdef ANDROID +// support std::isnan - needs to be tested as it might not be part of the NDK +#define _GLIBCXX_USE_C99_MATH 1 +#endif + + // This lets all platforms just use isnan, within the OCIO namespace, + // across all platforms. (Windows defines the function above). + using std::isnan; +#endif + + // Checks within fltmin tolerance + bool IsScalarEqualToZero(float v); + bool IsScalarEqualToOne(float v); + + // Are all the vector components the specified value? + bool IsVecEqualToZero(const float* v, int size); + bool IsVecEqualToOne(const float* v, int size); + + // Is at least one of the specified components equal to 0? + bool VecContainsZero(const float* v, int size); + bool VecContainsOne(const float* v, int size); + + // Are two vectors equal? (Same size, same values?) + bool VecsEqualWithRelError(const float* v1, int size1, + const float* v2, int size2, + float e); + + inline double GetHalfMax() + { + return 65504.0; // Largest positive half + } + + inline double GetHalfMin() + { + return 5.96046448e-08; // Smallest positive half; + } + + inline double GetHalfNormMin() + { + return 6.10351562e-05; // Smallest positive normalized half + } + + //! Clamp the specified value to the valid range of normalized half. + // (can be either positive or negative though + + double ClampToNormHalf(double val); + + float GetSafeScalarInverse(float v, float defaultValue = 1.0); + + + // All matrix / vector operations use the following sizing... + // + // m : 4x4 matrix + // v : 4 column vector + + // Return the 4x4 inverse, and whether the inverse has succeeded. + // Supports in-place operations + bool GetM44Inverse(float* mout, const float* m); + + // Is an identity matrix? (with fltmin tolerance) + bool IsM44Identity(const float* m); + + // Is this a purely diagonal matrix? + bool IsM44Diagonal(const float* m); + + // Extract the diagonal + void GetM44Diagonal(float* vout, const float* m); + + // Get the product, out = m1*m2 + // Supports in-place operations + void GetM44Product(float* mout, const float* m1, const float* m2); + + // Combine two transforms in the mx+b form, into a single transform + // mout*x+vout == m2*(m1*x+v1)+v2 + // Supports in-place operations + void GetMxbCombine(float* mout, float* vout, + const float* m1, const float* v1, + const float* m2, const float* v2); + + // Supports in-place operations + bool GetMxbInverse(float* mout, float* vout, + const float* m, const float* v); + +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/MatrixOps.cpp b/src/core/MatrixOps.cpp new file mode 100644 index 0000000..a0d4ef3 --- /dev/null +++ b/src/core/MatrixOps.cpp @@ -0,0 +1,788 @@ +/* +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 "GpuShaderUtils.h" +#include "HashUtils.h" +#include "MatrixOps.h" +#include "MathUtils.h" + +#include <cstring> +#include <sstream> + +OCIO_NAMESPACE_ENTER +{ + namespace + { + void ApplyScale(float* rgbaBuffer, long numPixels, + const float* scale4) + { + for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex) + { + rgbaBuffer[0] *= scale4[0]; + rgbaBuffer[1] *= scale4[1]; + rgbaBuffer[2] *= scale4[2]; + rgbaBuffer[3] *= scale4[3]; + + rgbaBuffer += 4; + } + } + + void ApplyOffset(float* rgbaBuffer, long numPixels, + const float* offset4) + { + for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex) + { + rgbaBuffer[0] += offset4[0]; + rgbaBuffer[1] += offset4[1]; + rgbaBuffer[2] += offset4[2]; + rgbaBuffer[3] += offset4[3]; + + rgbaBuffer += 4; + } + } + + void ApplyMatrix(float* rgbaBuffer, long numPixels, + const float* mat44) + { + float r,g,b,a; + + for(long pixelIndex=0; pixelIndex<numPixels; ++pixelIndex) + { + r = rgbaBuffer[0]; + g = rgbaBuffer[1]; + b = rgbaBuffer[2]; + a = rgbaBuffer[3]; + + rgbaBuffer[0] = r*mat44[0] + g*mat44[1] + b*mat44[2] + a*mat44[3]; + rgbaBuffer[1] = r*mat44[4] + g*mat44[5] + b*mat44[6] + a*mat44[7]; + rgbaBuffer[2] = r*mat44[8] + g*mat44[9] + b*mat44[10] + a*mat44[11]; + rgbaBuffer[3] = r*mat44[12] + g*mat44[13] + b*mat44[14] + a*mat44[15]; + + rgbaBuffer += 4; + } + } + } + + + + + + + + /////////////////////////////////////////////////////////////////////////// + + + + + + + + + namespace + { + class MatrixOffsetOp : public Op + { + public: + MatrixOffsetOp(const float * m44, + const float * offset4, + TransformDirection direction); + virtual ~MatrixOffsetOp(); + + virtual OpRcPtr clone() const; + + virtual std::string getInfo() const; + virtual std::string getCacheID() const; + + virtual bool isNoOp() const; + virtual bool isSameType(const OpRcPtr & op) const; + virtual bool isInverse(const OpRcPtr & op) const; + virtual bool canCombineWith(const OpRcPtr & op) const; + virtual void combineWith(OpRcPtrVec & ops, const OpRcPtr & secondOp) const; + + virtual bool hasChannelCrosstalk() const; + virtual void finalize(); + virtual void apply(float* rgbaBuffer, long numPixels) const; + + virtual bool supportsGpuShader() const; + virtual void writeGpuShader(std::ostream & shader, + const std::string & pixelName, + const GpuShaderDesc & shaderDesc) const; + + private: + bool m_isNoOp; + float m_m44[16]; + float m_offset4[4]; + TransformDirection m_direction; + + // Set in finalize + bool m_m44IsIdentity; + bool m_m44IsDiagonal; + bool m_offset4IsIdentity; + float m_m44_inv[16]; + std::string m_cacheID; + }; + + + typedef OCIO_SHARED_PTR<MatrixOffsetOp> MatrixOffsetOpRcPtr; + + + MatrixOffsetOp::MatrixOffsetOp(const float * m44, + const float * offset4, + TransformDirection direction): + Op(), + m_isNoOp(false), + m_direction(direction), + m_m44IsIdentity(false), + m_offset4IsIdentity(false) + { + if(m_direction == TRANSFORM_DIR_UNKNOWN) + { + throw Exception("Cannot apply MatrixOffsetOp op, unspecified transform direction."); + } + + memcpy(m_m44, m44, 16*sizeof(float)); + memcpy(m_offset4, offset4, 4*sizeof(float)); + + memset(m_m44_inv, 0, 16*sizeof(float)); + + // This Op will be a NoOp if and old if both the offset and matrix + // are identity. This hold true no matter what the direction is, + // so we can compute this ahead of time. + m_isNoOp = (IsVecEqualToZero(m_offset4, 4) && IsM44Identity(m_m44)); + } + + OpRcPtr MatrixOffsetOp::clone() const + { + OpRcPtr op = OpRcPtr(new MatrixOffsetOp(m_m44, m_offset4, m_direction)); + return op; + } + + MatrixOffsetOp::~MatrixOffsetOp() + { } + + std::string MatrixOffsetOp::getInfo() const + { + return "<MatrixOffsetOp>"; + } + + std::string MatrixOffsetOp::getCacheID() const + { + return m_cacheID; + } + + bool MatrixOffsetOp::isNoOp() const + { + return m_isNoOp; + } + + bool MatrixOffsetOp::isSameType(const OpRcPtr & op) const + { + MatrixOffsetOpRcPtr typedRcPtr = DynamicPtrCast<MatrixOffsetOp>(op); + if(!typedRcPtr) return false; + return true; + } + + bool MatrixOffsetOp::isInverse(const OpRcPtr & op) const + { + MatrixOffsetOpRcPtr typedRcPtr = DynamicPtrCast<MatrixOffsetOp>(op); + if(!typedRcPtr) return false; + + if(GetInverseTransformDirection(m_direction) != typedRcPtr->m_direction) + return false; + + float error = std::numeric_limits<float>::min(); + if(!VecsEqualWithRelError(m_m44, 16, typedRcPtr->m_m44, 16, error)) + return false; + if(!VecsEqualWithRelError(m_offset4, 4,typedRcPtr->m_offset4, 4, error)) + return false; + + return true; + } + + bool MatrixOffsetOp::canCombineWith(const OpRcPtr & op) const + { + return isSameType(op); + } + + void MatrixOffsetOp::combineWith(OpRcPtrVec & ops, const OpRcPtr & secondOp) const + { + MatrixOffsetOpRcPtr typedRcPtr = DynamicPtrCast<MatrixOffsetOp>(secondOp); + if(!typedRcPtr) + { + std::ostringstream os; + os << "MatrixOffsetOp can only be combined with other "; + os << "MatrixOffsetOps. secondOp:" << secondOp->getInfo(); + throw Exception(os.str().c_str()); + } + + float mout[16]; + float vout[4]; + + if(m_direction == TRANSFORM_DIR_FORWARD && + typedRcPtr->m_direction == TRANSFORM_DIR_FORWARD) + { + GetMxbCombine(mout, vout, + m_m44, m_offset4, + typedRcPtr->m_m44, typedRcPtr->m_offset4); + } + else if(m_direction == TRANSFORM_DIR_FORWARD && + typedRcPtr->m_direction == TRANSFORM_DIR_INVERSE) + { + float minv2[16]; + float vinv2[4]; + + if(!GetMxbInverse(minv2, vinv2, typedRcPtr->m_m44, typedRcPtr->m_offset4)) + { + std::ostringstream os; + os << "Cannot invert second MatrixOffsetOp op. "; + os << "Matrix inverse does not exist for ("; + for(int i=0; i<16; ++i) + { + os << typedRcPtr->m_m44[i] << " "; + } + os << ")."; + throw Exception(os.str().c_str()); + } + + GetMxbCombine(mout, vout, + m_m44, m_offset4, + minv2, vinv2); + } + else if(m_direction == TRANSFORM_DIR_INVERSE && + typedRcPtr->m_direction == TRANSFORM_DIR_FORWARD) + { + float minv1[16]; + float vinv1[4]; + + if(!GetMxbInverse(minv1, vinv1, m_m44, m_offset4)) + { + std::ostringstream os; + os << "Cannot invert primary MatrixOffsetOp op. "; + os << "Matrix inverse does not exist for ("; + for(int i=0; i<16; ++i) + { + os << m_m44[i] << " "; + } + os << ")."; + throw Exception(os.str().c_str()); + } + + GetMxbCombine(mout, vout, + minv1, vinv1, + typedRcPtr->m_m44, typedRcPtr->m_offset4); + + } + else if(m_direction == TRANSFORM_DIR_INVERSE && + typedRcPtr->m_direction == TRANSFORM_DIR_INVERSE) + { + float minv1[16]; + float vinv1[4]; + float minv2[16]; + float vinv2[4]; + + if(!GetMxbInverse(minv1, vinv1, m_m44, m_offset4)) + { + std::ostringstream os; + os << "Cannot invert primary MatrixOffsetOp op. "; + os << "Matrix inverse does not exist for ("; + for(int i=0; i<16; ++i) + { + os << m_m44[i] << " "; + } + os << ")."; + throw Exception(os.str().c_str()); + } + + if(!GetMxbInverse(minv2, vinv2, typedRcPtr->m_m44, typedRcPtr->m_offset4)) + { + std::ostringstream os; + os << "Cannot invert second MatrixOffsetOp op. "; + os << "Matrix inverse does not exist for ("; + for(int i=0; i<16; ++i) + { + os << typedRcPtr->m_m44[i] << " "; + } + os << ")."; + throw Exception(os.str().c_str()); + } + + GetMxbCombine(mout, vout, + minv1, vinv1, + minv2, vinv2); + } + else + { + std::ostringstream os; + os << "MatrixOffsetOp cannot combine ops with unspecified "; + os << "directions. First op: " << m_direction << " "; + os << "secondOp:" << typedRcPtr->m_direction; + throw Exception(os.str().c_str()); + } + + CreateMatrixOffsetOp(ops, + mout, vout, + TRANSFORM_DIR_FORWARD); + } + + bool MatrixOffsetOp::hasChannelCrosstalk() const + { + return (!m_m44IsDiagonal); + } + + void MatrixOffsetOp::finalize() + { + m_offset4IsIdentity = IsVecEqualToZero(m_offset4, 4); + m_m44IsIdentity = IsM44Identity(m_m44); + m_m44IsDiagonal = IsM44Diagonal(m_m44); + + if(m_direction == TRANSFORM_DIR_INVERSE) + { + if(!GetM44Inverse(m_m44_inv, m_m44)) + { + std::ostringstream os; + os << "Cannot apply MatrixOffsetOp op. "; + os << "Matrix inverse does not exist for m44 ("; + for(int i=0; i<16; ++i) os << m_m44[i] << " "; + os << ")."; + throw Exception(os.str().c_str()); + } + } + + // Create the cacheID + md5_state_t state; + md5_byte_t digest[16]; + md5_init(&state); + md5_append(&state, (const md5_byte_t *)m_m44, 16*sizeof(float)); + md5_append(&state, (const md5_byte_t *)m_offset4, 4*sizeof(float)); + md5_finish(&state, digest); + + std::ostringstream cacheIDStream; + cacheIDStream << "<MatrixOffsetOp "; + cacheIDStream << GetPrintableHash(digest) << " "; + cacheIDStream << TransformDirectionToString(m_direction) << " "; + cacheIDStream << ">"; + + m_cacheID = cacheIDStream.str(); + } + + void MatrixOffsetOp::apply(float* rgbaBuffer, long numPixels) const + { + if(m_direction == TRANSFORM_DIR_FORWARD) + { + if(!m_m44IsIdentity) + { + if(m_m44IsDiagonal) + { + float scale[4]; + GetM44Diagonal(scale, m_m44); + ApplyScale(rgbaBuffer, numPixels, scale); + } + else + { + ApplyMatrix(rgbaBuffer, numPixels, m_m44); + } + } + + if(!m_offset4IsIdentity) + { + ApplyOffset(rgbaBuffer, numPixels, m_offset4); + } + } + else if(m_direction == TRANSFORM_DIR_INVERSE) + { + if(!m_offset4IsIdentity) + { + float offset_inv[] = { -m_offset4[0], + -m_offset4[1], + -m_offset4[2], + -m_offset4[3] }; + + ApplyOffset(rgbaBuffer, numPixels, offset_inv); + } + + if(!m_m44IsIdentity) + { + if(m_m44IsDiagonal) + { + float scale[4]; + GetM44Diagonal(scale, m_m44_inv); + ApplyScale(rgbaBuffer, numPixels, scale); + } + else + { + ApplyMatrix(rgbaBuffer, numPixels, m_m44_inv); + } + } + } + } // Op::process + + bool MatrixOffsetOp::supportsGpuShader() const + { + return true; + } + + void MatrixOffsetOp::writeGpuShader(std::ostream & shader, + const std::string & pixelName, + const GpuShaderDesc & shaderDesc) const + { + GpuLanguage lang = shaderDesc.getLanguage(); + + // TODO: This should not act upon alpha, + // since we dont apply it on the CPU? + + if(m_direction == TRANSFORM_DIR_FORWARD) + { + if(!m_m44IsIdentity) + { + if(m_m44IsDiagonal) + { + shader << pixelName << " = "; + float scale[4]; + GetM44Diagonal(scale, m_m44); + Write_half4(shader, scale, lang); + shader << " * " << pixelName << ";\n"; + } + else + { + shader << pixelName << " = "; + Write_mtx_x_vec(shader, + GpuTextHalf4x4(m_m44, lang), pixelName, + lang); + shader << ";\n"; + } + } + + if(!m_offset4IsIdentity) + { + shader << pixelName << " = "; + Write_half4(shader, m_offset4, lang); + shader << " + " << pixelName << ";\n"; + } + } + else if(m_direction == TRANSFORM_DIR_INVERSE) + { + if(!m_offset4IsIdentity) + { + float offset_inv[] = { -m_offset4[0], + -m_offset4[1], + -m_offset4[2], + -m_offset4[3] }; + + shader << pixelName << " = "; + Write_half4(shader, offset_inv, lang); + shader << " + " << pixelName << ";\n"; + } + + if(!m_m44IsIdentity) + { + if(m_m44IsDiagonal) + { + shader << pixelName << " = "; + float scale[4]; + GetM44Diagonal(scale, m_m44_inv); + Write_half4(shader, scale, lang); + shader << " * " << pixelName << ";\n"; + } + else + { + shader << pixelName << " = "; + Write_mtx_x_vec(shader, + GpuTextHalf4x4(m_m44_inv, lang), pixelName, + lang); + shader << ";\n"; + } + } + } + } + + } // Anon namespace + + + + + + + + + + + /////////////////////////////////////////////////////////////////////////// + + + + + + + + + + void CreateScaleOp(OpRcPtrVec & ops, + const float * scale4, + TransformDirection direction) + { + float offset4[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + CreateScaleOffsetOp(ops, scale4, offset4, direction); + } + + void CreateMatrixOp(OpRcPtrVec & ops, + const float * m44, + TransformDirection direction) + { + float offset4[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + CreateMatrixOffsetOp(ops, m44, offset4, direction); + } + + void CreateOffsetOp(OpRcPtrVec & ops, + const float * offset4, + TransformDirection direction) + { + float scale4[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + CreateScaleOffsetOp(ops, scale4, offset4, direction); + } + + void CreateScaleOffsetOp(OpRcPtrVec & ops, + const float * scale4, const float * offset4, + TransformDirection direction) + { + float m44[16]; + memset(m44, 0, 16*sizeof(float)); + + m44[0] = scale4[0]; + m44[5] = scale4[1]; + m44[10] = scale4[2]; + m44[15] = scale4[3]; + + CreateMatrixOffsetOp(ops, + m44, offset4, + direction); + } + + void CreateSaturationOp(OpRcPtrVec & ops, + float sat, + const float * lumaCoef3, + TransformDirection direction) + { + float matrix[16]; + float offset[4]; + MatrixTransform::Sat(matrix, offset, + sat, lumaCoef3); + + CreateMatrixOffsetOp(ops, matrix, offset, direction); + } + + void CreateMatrixOffsetOp(OpRcPtrVec & ops, + const float * m44, const float * offset4, + TransformDirection direction) + { + bool mtxIsIdentity = IsM44Identity(m44); + bool offsetIsIdentity = IsVecEqualToZero(offset4, 4); + if(mtxIsIdentity && offsetIsIdentity) return; + + ops.push_back( MatrixOffsetOpRcPtr(new MatrixOffsetOp(m44, + offset4, direction)) ); + } + + void CreateFitOp(OpRcPtrVec & ops, + const float * oldmin4, const float * oldmax4, + const float * newmin4, const float * newmax4, + TransformDirection direction) + { + float matrix[16]; + float offset[4]; + MatrixTransform::Fit(matrix, offset, + oldmin4, oldmax4, + newmin4, newmax4); + + CreateMatrixOffsetOp(ops, matrix, offset, direction); + } + +} +OCIO_NAMESPACE_EXIT + + + +//////////////////////////////////////////////////////////////////////////////// + + +#ifdef OCIO_UNIT_TEST + +namespace OCIO = OCIO_NAMESPACE; +#include "UnitTest.h" + +OCIO_NAMESPACE_USING + +OIIO_ADD_TEST(MatrixOps, Combining) +{ + float m1[16] = { 1.1f, 0.2f, 0.3f, 0.4f, + 0.5f, 1.6f, 0.7f, 0.8f, + 0.2f, 0.1f, 1.1f, 0.2f, + 0.3f, 0.4f, 0.5f, 1.6f }; + + float v1[4] = { -0.5f, -0.25f, 0.25f, 0.0f }; + + float m2[16] = { 1.1f, -0.1f, -0.1f, 0.0f, + 0.1f, 0.9f, -0.2f, 0.0f, + 0.05f, 0.0f, 1.1f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + float v2[4] = { -0.2f, -0.1f, -0.1f, -0.2f }; + + const float source[] = { 0.1f, 0.2f, 0.3f, 0.4f, + -0.1f, -0.2f, 50.0f, 123.4f, + 1.0f, 1.0f, 1.0f, 1.0f }; + float error = 1e-4f; + + { + OpRcPtrVec ops; + CreateMatrixOffsetOp(ops, m1, v1, TRANSFORM_DIR_FORWARD); + CreateMatrixOffsetOp(ops, m2, v2, TRANSFORM_DIR_FORWARD); + OIIO_CHECK_EQUAL(ops.size(), 2); + ops[0]->finalize(); + ops[1]->finalize(); + + OpRcPtrVec combined; + ops[0]->combineWith(combined, ops[1]); + OIIO_CHECK_EQUAL(combined.size(), 1); + combined[0]->finalize(); + + for(int test=0; test<3; ++test) + { + float tmp[4]; + memcpy(tmp, &source[4*test], 4*sizeof(float)); + ops[0]->apply(tmp, 1); + ops[1]->apply(tmp, 1); + + float tmp2[4]; + memcpy(tmp2, &source[4*test], 4*sizeof(float)); + combined[0]->apply(tmp2, 1); + + for(unsigned int i=0; i<4; ++i) + { + OIIO_CHECK_CLOSE(tmp2[i], tmp[i], error); + } + } + } + + + { + OpRcPtrVec ops; + CreateMatrixOffsetOp(ops, m1, v1, TRANSFORM_DIR_FORWARD); + CreateMatrixOffsetOp(ops, m2, v2, TRANSFORM_DIR_INVERSE); + OIIO_CHECK_EQUAL(ops.size(), 2); + ops[0]->finalize(); + ops[1]->finalize(); + + OpRcPtrVec combined; + ops[0]->combineWith(combined, ops[1]); + OIIO_CHECK_EQUAL(combined.size(), 1); + combined[0]->finalize(); + + + for(int test=0; test<3; ++test) + { + float tmp[4]; + memcpy(tmp, &source[4*test], 4*sizeof(float)); + ops[0]->apply(tmp, 1); + ops[1]->apply(tmp, 1); + + float tmp2[4]; + memcpy(tmp2, &source[4*test], 4*sizeof(float)); + combined[0]->apply(tmp2, 1); + + for(unsigned int i=0; i<4; ++i) + { + OIIO_CHECK_CLOSE(tmp2[i], tmp[i], error); + } + } + } + + { + OpRcPtrVec ops; + CreateMatrixOffsetOp(ops, m1, v1, TRANSFORM_DIR_INVERSE); + CreateMatrixOffsetOp(ops, m2, v2, TRANSFORM_DIR_FORWARD); + OIIO_CHECK_EQUAL(ops.size(), 2); + ops[0]->finalize(); + ops[1]->finalize(); + + OpRcPtrVec combined; + ops[0]->combineWith(combined, ops[1]); + OIIO_CHECK_EQUAL(combined.size(), 1); + combined[0]->finalize(); + + for(int test=0; test<3; ++test) + { + float tmp[4]; + memcpy(tmp, &source[4*test], 4*sizeof(float)); + ops[0]->apply(tmp, 1); + ops[1]->apply(tmp, 1); + + float tmp2[4]; + memcpy(tmp2, &source[4*test], 4*sizeof(float)); + combined[0]->apply(tmp2, 1); + + for(unsigned int i=0; i<4; ++i) + { + OIIO_CHECK_CLOSE(tmp2[i], tmp[i], error); + } + } + } + + { + OpRcPtrVec ops; + CreateMatrixOffsetOp(ops, m1, v1, TRANSFORM_DIR_INVERSE); + CreateMatrixOffsetOp(ops, m2, v2, TRANSFORM_DIR_INVERSE); + OIIO_CHECK_EQUAL(ops.size(), 2); + ops[0]->finalize(); + ops[1]->finalize(); + + OpRcPtrVec combined; + ops[0]->combineWith(combined, ops[1]); + OIIO_CHECK_EQUAL(combined.size(), 1); + combined[0]->finalize(); + + for(int test=0; test<3; ++test) + { + float tmp[4]; + memcpy(tmp, &source[4*test], 4*sizeof(float)); + ops[0]->apply(tmp, 1); + ops[1]->apply(tmp, 1); + + float tmp2[4]; + memcpy(tmp2, &source[4*test], 4*sizeof(float)); + combined[0]->apply(tmp2, 1); + + for(unsigned int i=0; i<4; ++i) + { + OIIO_CHECK_CLOSE(tmp2[i], tmp[i], error); + } + } + } +} + +#endif diff --git a/src/core/MatrixOps.h b/src/core/MatrixOps.h new file mode 100644 index 0000000..e462706 --- /dev/null +++ b/src/core/MatrixOps.h @@ -0,0 +1,75 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_MATRIXOFFSETOP_H +#define INCLUDED_OCIO_MATRIXOFFSETOP_H + +#include <OpenColorIO/OpenColorIO.h> + +#include "Op.h" + +#include <vector> + +OCIO_NAMESPACE_ENTER +{ + // Use whichever is most convenient; they are equally efficient + + void CreateScaleOp(OpRcPtrVec & ops, + const float * scale4, + TransformDirection direction); + + void CreateMatrixOp(OpRcPtrVec & ops, + const float * m44, + TransformDirection direction); + + void CreateOffsetOp(OpRcPtrVec & ops, + const float * offset4, + TransformDirection direction); + + void CreateMatrixOffsetOp(OpRcPtrVec & ops, + const float * m44, const float * offset4, + TransformDirection direction); + + void CreateScaleOffsetOp(OpRcPtrVec & ops, + const float * scale4, const float * offset4, + TransformDirection direction); + + void CreateFitOp(OpRcPtrVec & ops, + const float * oldmin4, const float * oldmax4, + const float * newmin4, const float * newmax4, + TransformDirection direction); + + void CreateSaturationOp(OpRcPtrVec & ops, + float sat, + const float * lumaCoef3, + TransformDirection direction); +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/MatrixTransform.cpp b/src/core/MatrixTransform.cpp new file mode 100644 index 0000000..cb88327 --- /dev/null +++ b/src/core/MatrixTransform.cpp @@ -0,0 +1,386 @@ +/* +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 <cstring> + +#include <OpenColorIO/OpenColorIO.h> + +#include "OpBuilders.h" +#include "MatrixOps.h" +#include "MathUtils.h" + + +OCIO_NAMESPACE_ENTER +{ + MatrixTransformRcPtr MatrixTransform::Create() + { + return MatrixTransformRcPtr(new MatrixTransform(), &deleter); + } + + void MatrixTransform::deleter(MatrixTransform* t) + { + delete t; + } + + class MatrixTransform::Impl + { + public: + TransformDirection dir_; + float matrix_[16]; + float offset_[4]; + + Impl() : + dir_(TRANSFORM_DIR_FORWARD) + { + Identity(matrix_, offset_); + } + + ~Impl() + { } + + Impl& operator= (const Impl & rhs) + { + dir_ = rhs.dir_; + memcpy(matrix_, rhs.matrix_, 16*sizeof(float)); + memcpy(offset_, rhs.offset_, 4*sizeof(float)); + return *this; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + + + MatrixTransform::MatrixTransform() + : m_impl(new MatrixTransform::Impl) + { + } + + TransformRcPtr MatrixTransform::createEditableCopy() const + { + MatrixTransformRcPtr transform = MatrixTransform::Create(); + *(transform->m_impl) = *m_impl; + return transform; + } + + MatrixTransform::~MatrixTransform() + { + delete m_impl; + m_impl = NULL; + } + + MatrixTransform& MatrixTransform::operator= (const MatrixTransform & rhs) + { + *m_impl = *rhs.m_impl; + return *this; + } + + TransformDirection MatrixTransform::getDirection() const + { + return getImpl()->dir_; + } + + void MatrixTransform::setDirection(TransformDirection dir) + { + getImpl()->dir_ = dir; + } + + bool MatrixTransform::equals(const MatrixTransform & other) const + { + const float abserror = 1e-9f; + + for(int i=0; i<16; ++i) + { + if(!equalWithAbsError(getImpl()->matrix_[i], + other.getImpl()->matrix_[i], abserror)) + { + return false; + } + } + + for(int i=0; i<4; ++i) + { + if(!equalWithAbsError(getImpl()->offset_[i], + other.getImpl()->offset_[i], abserror)) + { + return false; + } + } + + return true; + } + + void MatrixTransform::getValue(float * m44, float * offset4) const + { + if(m44) memcpy(m44, getImpl()->matrix_, 16*sizeof(float)); + if(offset4) memcpy(offset4, getImpl()->offset_, 4*sizeof(float)); + } + + void MatrixTransform::setValue(const float * m44, const float * offset4) + { + if(m44) memcpy(getImpl()->matrix_, m44, 16*sizeof(float)); + if(offset4) memcpy(getImpl()->offset_, offset4, 4*sizeof(float)); + } + + void MatrixTransform::setMatrix(const float * m44) + { + if(m44) memcpy(getImpl()->matrix_, m44, 16*sizeof(float)); + } + + void MatrixTransform::getMatrix(float * m44) const + { + if(m44) memcpy(m44, getImpl()->matrix_, 16*sizeof(float)); + } + + void MatrixTransform::setOffset(const float * offset4) + { + if(offset4) memcpy(getImpl()->offset_, offset4, 4*sizeof(float)); + } + + void MatrixTransform::getOffset(float * offset4) const + { + if(offset4) memcpy(offset4, getImpl()->offset_, 4*sizeof(float)); + } + + /* + Fit is canonically formulated as: + out = newmin + ((value-oldmin)/(oldmax-oldmin)*(newmax-newmin)) + I.e., subtract the old offset, descale into the [0,1] range, + scale into the new range, and add the new offset + + We algebraiclly manipulate the terms into y = mx + b form as: + m = (newmax-newmin)/(oldmax-oldmin) + b = (newmin*oldmax - newmax*oldmin) / (oldmax-oldmin) + */ + + void MatrixTransform::Fit(float * m44, float * offset4, + const float * oldmin4, const float * oldmax4, + const float * newmin4, const float * newmax4) + { + if(!oldmin4 || !oldmax4) return; + if(!newmin4 || !newmax4) return; + + if(m44) memset(m44, 0, 16*sizeof(float)); + if(offset4) memset(offset4, 0, 4*sizeof(float)); + + for(int i=0; i<4; ++i) + { + float denom = oldmax4[i] - oldmin4[i]; + if(IsScalarEqualToZero(denom)) + { + std::ostringstream os; + os << "Cannot create Fit operator. "; + os << "Max value equals min value '"; + os << oldmax4[i] << "' in channel index "; + os << i << "."; + throw Exception(os.str().c_str()); + } + + if(m44) m44[5*i] = (newmax4[i]-newmin4[i]) / denom; + if(offset4) offset4[i] = (newmin4[i]*oldmax4[i] - newmax4[i]*oldmin4[i]) / denom; + } + } + + + void MatrixTransform::Identity(float * m44, float * offset4) + { + if(m44) + { + memset(m44, 0, 16*sizeof(float)); + m44[0] = 1.0f; + m44[5] = 1.0f; + m44[10] = 1.0f; + m44[15] = 1.0f; + } + + if(offset4) + { + offset4[0] = 0.0f; + offset4[1] = 0.0f; + offset4[2] = 0.0f; + offset4[3] = 0.0f; + } + } + + void MatrixTransform::Sat(float * m44, float * offset4, + float sat, const float * lumaCoef3) + { + if(!lumaCoef3) return; + + if(m44) + { + m44[0] = (1 - sat) * lumaCoef3[0] + sat; + m44[1] = (1 - sat) * lumaCoef3[1]; + m44[2] = (1 - sat) * lumaCoef3[2]; + m44[3] = 0.0f; + + m44[4] = (1 - sat) * lumaCoef3[0]; + m44[5] = (1 - sat) * lumaCoef3[1] + sat; + m44[6] = (1 - sat) * lumaCoef3[2]; + m44[7] = 0.0f; + + m44[8] = (1 - sat) * lumaCoef3[0]; + m44[9] = (1 - sat) * lumaCoef3[1]; + m44[10] = (1 - sat) * lumaCoef3[2] + sat; + m44[11] = 0.0f; + + m44[12] = 0.0f; + m44[13] = 0.0f; + m44[14] = 0.0f; + m44[15] = 1.0f; + } + + if(offset4) + { + offset4[0] = 0.0f; + offset4[1] = 0.0f; + offset4[2] = 0.0f; + offset4[3] = 0.0f; + } + } + + void MatrixTransform::Scale(float * m44, float * offset4, + const float * scale4) + { + if(!scale4) return; + + if(m44) + { + memset(m44, 0, 16*sizeof(float)); + m44[0] = scale4[0]; + m44[5] = scale4[1]; + m44[10] = scale4[2]; + m44[15] = scale4[3]; + } + + if(offset4) + { + offset4[0] = 0.0f; + offset4[1] = 0.0f; + offset4[2] = 0.0f; + offset4[3] = 0.0f; + } + } + + void MatrixTransform::View(float * m44, float * offset4, + int * channelHot4, + const float * lumaCoef3) + { + if(!channelHot4 || !lumaCoef3) return; + + if(offset4) + { + offset4[0] = 0.0f; + offset4[1] = 0.0f; + offset4[2] = 0.0f; + offset4[3] = 0.0f; + } + + if(m44) + { + memset(m44, 0, 16*sizeof(float)); + + // All channels are hot, return identity + if(channelHot4[0] && channelHot4[1] && + channelHot4[2] && channelHot4[3]) + { + Identity(m44, 0x0); + } + // If not all the channels are hot, but alpha is, + // just show it. + else if(channelHot4[3]) + { + for(int i=0; i<4; ++i) + { + m44[4*i+3] = 1.0f; + } + } + // Blend rgb as specified, place it in all 3 output + // channels (to make a grayscale final image) + else + { + float values[3] = { 0.0f, 0.0f, 0.0f }; + + for(int i = 0; i < 3; ++i) + { + values[i] += lumaCoef3[i] * (channelHot4[i] ? 1.0f : 0.0f); + } + + float sum = values[0] + values[1] + values[2]; + if(!IsScalarEqualToZero(sum)) + { + values[0] /= sum; + values[1] /= sum; + values[2] /= sum; + } + + // Copy rgb into rgb rows + for(int row=0; row<3; ++row) + { + for(int i=0; i<3; i++) + { + m44[4*row+i] = values[i]; + } + } + + // Preserve alpha + m44[15] = 1.0f; + } + } + } + + std::ostream& operator<< (std::ostream& os, const MatrixTransform& t) + { + os << "<MatrixTransform "; + os << "direction=" << TransformDirectionToString(t.getDirection()) << ", "; + os << ">\n"; + return os; + } + + + /////////////////////////////////////////////////////////////////////////// + + void BuildMatrixOps(OpRcPtrVec & ops, + const Config& /*config*/, + const MatrixTransform & transform, + TransformDirection dir) + { + TransformDirection combinedDir = CombineTransformDirections(dir, + transform.getDirection()); + + float matrix[16]; + float offset[4]; + transform.getValue(matrix, offset); + + CreateMatrixOffsetOp(ops, + matrix, offset, + combinedDir); + } + +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/Mutex.h b/src/core/Mutex.h new file mode 100644 index 0000000..421ad29 --- /dev/null +++ b/src/core/Mutex.h @@ -0,0 +1,115 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_MUTEX_H +#define INCLUDED_OCIO_MUTEX_H + +/* +PTEX SOFTWARE +Copyright 2009 Disney Enterprises, Inc. 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. + + * The names "Disney", "Walt Disney Pictures", "Walt Disney Animation + Studios" or the names of its contributors may NOT be used to + endorse or promote products derived from this software without + specific prior written permission from Walt Disney Pictures. + +Disclaimer: THIS SOFTWARE IS PROVIDED BY WALT DISNEY PICTURES AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE, NONINFRINGEMENT AND TITLE ARE DISCLAIMED. +IN NO EVENT SHALL WALT DISNEY PICTURES, THE COPYRIGHT HOLDER 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 BASED 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 DAMAGES. +*/ + +#include "Platform.h" + +// #define DEBUG_THREADING + +/** For internal use only */ + +OCIO_NAMESPACE_ENTER +{ + +#ifndef NDEBUG + template <class T> + class DebugLock : public T { + public: + DebugLock() : _locked(0) {} + void lock() { T::lock(); _locked = 1; } + void unlock() { assert(_locked); _locked = 0; T::unlock(); } + bool locked() { return _locked != 0; } + private: + int _locked; + }; +#endif + + /** Automatically acquire and release lock within enclosing scope. */ + template <class T> + class AutoLock { + public: + AutoLock(T& m) : _m(m) { _m.lock(); } + ~AutoLock() { _m.unlock(); } + private: + T& _m; + }; + +#ifndef NDEBUG + // add debug wrappers to mutex and spinlock + typedef DebugLock<_Mutex> Mutex; + typedef DebugLock<_SpinLock> SpinLock; +#else + typedef _Mutex Mutex; + typedef _SpinLock SpinLock; +#endif + + typedef AutoLock<Mutex> AutoMutex; + typedef AutoLock<SpinLock> AutoSpin; + +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/NoOps.cpp b/src/core/NoOps.cpp new file mode 100644 index 0000000..101324e --- /dev/null +++ b/src/core/NoOps.cpp @@ -0,0 +1,641 @@ +/* +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 <sstream> + +#include <OpenColorIO/OpenColorIO.h> + +#include "AllocationOp.h" +#include "NoOps.h" +#include "OpBuilders.h" +#include "Op.h" + +OCIO_NAMESPACE_ENTER +{ + namespace + { + class AllocationNoOp : public Op + { + public: + AllocationNoOp(const AllocationData & allocationData): + m_allocationData(allocationData) {} + virtual ~AllocationNoOp() {} + + virtual OpRcPtr clone() const; + + virtual std::string getInfo() const { return "<AllocationNoOp>"; } + virtual std::string getCacheID() const { return ""; } + + virtual bool isNoOp() const { return true; } + virtual bool isSameType(const OpRcPtr & op) const; + virtual bool isInverse(const OpRcPtr & op) const; + virtual bool hasChannelCrosstalk() const { return false; } + virtual void finalize() { } + virtual void apply(float* /*rgbaBuffer*/, long /*numPixels*/) const { } + + virtual bool supportsGpuShader() const { return true; } + virtual void writeGpuShader(std::ostream & /*shader*/, + const std::string & /*pixelName*/, + const GpuShaderDesc & /*shaderDesc*/) const + { } + + void getGpuAllocation(AllocationData & allocation) const; + + private: + AllocationData m_allocationData; + }; + + typedef OCIO_SHARED_PTR<AllocationNoOp> AllocationNoOpRcPtr; + + OpRcPtr AllocationNoOp::clone() const + { + OpRcPtr op = OpRcPtr(new AllocationNoOp(m_allocationData)); + return op; + } + + bool AllocationNoOp::isSameType(const OpRcPtr & op) const + { + AllocationNoOpRcPtr typedRcPtr = DynamicPtrCast<AllocationNoOp>(op); + if(!typedRcPtr) return false; + return true; + } + + bool AllocationNoOp::isInverse(const OpRcPtr & op) const + { + if(!isSameType(op)) return false; + return true; + } + + void AllocationNoOp::getGpuAllocation(AllocationData & allocation) const + { + allocation = m_allocationData; + } + + // Return whether the op defines an Allocation + bool DefinesGpuAllocation(const OpRcPtr & op) + { + AllocationNoOpRcPtr allocationNoOpRcPtr = + DynamicPtrCast<AllocationNoOp>(op); + + if(allocationNoOpRcPtr) return true; + return false; + } + } + + void CreateGpuAllocationNoOp(OpRcPtrVec & ops, + const AllocationData & allocationData) + { + ops.push_back( AllocationNoOpRcPtr(new AllocationNoOp(allocationData)) ); + } + + + namespace + { + // Find the minimal index range in the opVec that does not support + // shader text generation. The endIndex *is* inclusive. + // + // I.e., if the entire opVec does not support GPUShaders, the + // result will be startIndex = 0, endIndex = opVec.size() - 1 + // + // If the entire opVec supports GPU generation, both the + // startIndex and endIndex will equal -1 + + void GetGpuUnsupportedIndexRange(int * startIndex, int * endIndex, + const OpRcPtrVec & opVec) + { + int start = -1; + int end = -1; + + for(unsigned int i=0; i<opVec.size(); ++i) + { + // We've found a gpu unsupported op. + // If it's the first, save it as our start. + // Otherwise, update the end. + + if(!opVec[i]->supportsGpuShader()) + { + if(start<0) + { + start = i; + end = i; + } + else end = i; + } + } + + // Now that we've found a startIndex, walk back until we find + // one that defines a GpuAllocation. (we can only upload to + // the gpu at a location are tagged with an allocation) + + while(start>0) + { + if(DefinesGpuAllocation(opVec[start])) break; + --start; + } + + if(startIndex) *startIndex = start; + if(endIndex) *endIndex = end; + } + + + bool GetGpuAllocation(AllocationData & allocation, + const OpRcPtr & op) + { + AllocationNoOpRcPtr allocationNoOpRcPtr = + DynamicPtrCast<AllocationNoOp>(op); + + if(!allocationNoOpRcPtr) + { + return false; + } + + allocationNoOpRcPtr->getGpuAllocation(allocation); + return true; + } + } + + + void PartitionGPUOps(OpRcPtrVec & gpuPreOps, + OpRcPtrVec & gpuLatticeOps, + OpRcPtrVec & gpuPostOps, + const OpRcPtrVec & ops) + { + // + // Partition the original, raw opvec into 3 segments for GPU Processing + // + // gpuLatticeOps need not support analytical gpu shader generation + // the pre and post ops must support analytical generation. + // Additional ops will be inserted to take into account allocations + // transformations. + + + // This is used to bound our analytical shader text generation + // start index and end index are inclusive. + + int gpuLut3DOpStartIndex = 0; + int gpuLut3DOpEndIndex = 0; + GetGpuUnsupportedIndexRange(&gpuLut3DOpStartIndex, + &gpuLut3DOpEndIndex, + ops); + + // Write the entire shader using only shader text (3d lut is unused) + if(gpuLut3DOpStartIndex == -1 && gpuLut3DOpEndIndex == -1) + { + for(unsigned int i=0; i<ops.size(); ++i) + { + gpuPreOps.push_back( ops[i]->clone() ); + } + } + // Analytical -> 3dlut -> analytical + else + { + // Handle analytical shader block before start index. + for(int i=0; i<gpuLut3DOpStartIndex; ++i) + { + gpuPreOps.push_back( ops[i]->clone() ); + } + + // Get the GPU Allocation at the cross-over point + // Create 2 symmetrically canceling allocation ops, + // where the shader text moves to a nicely allocated LDR + // (low dynamic range color space), and the lattice processing + // does the inverse (making the overall operation a no-op + // color-wise + + AllocationData allocation; + if(gpuLut3DOpStartIndex<0 || gpuLut3DOpStartIndex>=(int)ops.size()) + { + std::ostringstream error; + error << "Invalid GpuUnsupportedIndexRange: "; + error << "gpuLut3DOpStartIndex: " << gpuLut3DOpStartIndex << " "; + error << "gpuLut3DOpEndIndex: " << gpuLut3DOpEndIndex << " "; + error << "cpuOps.size: " << ops.size(); + throw Exception(error.str().c_str()); + } + + // If the specified location defines an allocation, use it. + // It's possible that this index wont define an allocation. + // (For example in the case of getProcessor(FileTransform) + if(GetGpuAllocation(allocation, ops[gpuLut3DOpStartIndex])) + { + CreateAllocationOps(gpuPreOps, allocation, + TRANSFORM_DIR_FORWARD); + CreateAllocationOps(gpuLatticeOps, allocation, + TRANSFORM_DIR_INVERSE); + } + + // Handle cpu lattice processing + for(int i=gpuLut3DOpStartIndex; i<=gpuLut3DOpEndIndex; ++i) + { + gpuLatticeOps.push_back( ops[i]->clone() ); + } + + // And then handle the gpu post processing + for(int i=gpuLut3DOpEndIndex+1; i<(int)ops.size(); ++i) + { + gpuPostOps.push_back( ops[i]->clone() ); + } + } + } + + void AssertPartitionIntegrity(OpRcPtrVec & gpuPreOps, + OpRcPtrVec & gpuLatticeOps, + OpRcPtrVec & gpuPostOps) + { + // All gpu pre ops must support analytical gpu shader generation + for(unsigned int i=0; i<gpuPreOps.size(); ++i) + { + if(!gpuPreOps[i]->supportsGpuShader()) + { + throw Exception("Patition failed check. gpuPreOps"); + } + } + + // If there are any lattice ops, at lease one must NOT support GPU + // shaders (otherwise this block isnt necessary!) + if(gpuLatticeOps.size()>0) + { + bool requireslattice = false; + for(unsigned int i=0; i<gpuLatticeOps.size(); ++i) + { + if(!gpuLatticeOps[i]->supportsGpuShader()) requireslattice = true; + } + + if(!requireslattice) + { + throw Exception("Patition failed check. gpuLatticeOps"); + } + } + + // All gpu post ops must support analytical gpu shader generation + for(unsigned int i=0; i<gpuPostOps.size(); ++i) + { + if(!gpuPostOps[i]->supportsGpuShader()) + { + throw Exception("Patition failed check. gpuPostOps"); + } + } + } + + //////////////////////////////////////////////////////////////////////////// + + namespace + { + class FileNoOp : public Op + { + public: + FileNoOp(const std::string & fileReference): + m_fileReference(fileReference) {} + virtual ~FileNoOp() {} + + virtual OpRcPtr clone() const; + + virtual std::string getInfo() const { return "<FileNoOp>"; } + virtual std::string getCacheID() const { return ""; } + + virtual bool isNoOp() const { return true; } + virtual bool isSameType(const OpRcPtr & op) const; + virtual bool isInverse(const OpRcPtr & op) const; + virtual bool hasChannelCrosstalk() const { return false; } + virtual void dumpMetadata(ProcessorMetadataRcPtr & metadata) const; + + virtual void finalize() {} + virtual void apply(float* /*rgbaBuffer*/, long /*numPixels*/) const {} + + virtual bool supportsGpuShader() const { return true; } + virtual void writeGpuShader(std::ostream & /*shader*/, + const std::string & /*pixelName*/, + const GpuShaderDesc & /*shaderDesc*/) const + { } + + private: + std::string m_fileReference; + }; + + typedef OCIO_SHARED_PTR<FileNoOp> FileNoOpRcPtr; + + OpRcPtr FileNoOp::clone() const + { + OpRcPtr op = OpRcPtr(new FileNoOp(m_fileReference)); + return op; + } + + bool FileNoOp::isSameType(const OpRcPtr & op) const + { + FileNoOpRcPtr typedRcPtr = DynamicPtrCast<FileNoOp>(op); + if(!typedRcPtr) return false; + return true; + } + + bool FileNoOp::isInverse(const OpRcPtr & op) const + { + return isSameType(op); + } + + void FileNoOp::dumpMetadata(ProcessorMetadataRcPtr & metadata) const + { + metadata->addFile(m_fileReference.c_str()); + } + } + + void CreateFileNoOp(OpRcPtrVec & ops, + const std::string & fileReference) + { + ops.push_back( FileNoOpRcPtr(new FileNoOp(fileReference)) ); + } + + + + + //////////////////////////////////////////////////////////////////////////// + + namespace + { + class LookNoOp : public Op + { + public: + LookNoOp(const std::string & look): + m_look(look) {} + virtual ~LookNoOp() {} + + virtual OpRcPtr clone() const; + + virtual std::string getInfo() const { return "<LookNoOp>"; } + virtual std::string getCacheID() const { return ""; } + + virtual bool isNoOp() const { return true; } + virtual bool isSameType(const OpRcPtr & op) const; + virtual bool isInverse(const OpRcPtr & op) const; + virtual bool hasChannelCrosstalk() const { return false; } + virtual void dumpMetadata(ProcessorMetadataRcPtr & metadata) const; + + virtual void finalize() {} + virtual void apply(float* /*rgbaBuffer*/, long /*numPixels*/) const {} + + virtual bool supportsGpuShader() const { return true; } + virtual void writeGpuShader(std::ostream & /*shader*/, + const std::string & /*pixelName*/, + const GpuShaderDesc & /*shaderDesc*/) const + { } + + private: + std::string m_look; + }; + + typedef OCIO_SHARED_PTR<LookNoOp> LookNoOpRcPtr; + + OpRcPtr LookNoOp::clone() const + { + OpRcPtr op = OpRcPtr(new FileNoOp(m_look)); + return op; + } + + bool LookNoOp::isSameType(const OpRcPtr & op) const + { + FileNoOpRcPtr typedRcPtr = DynamicPtrCast<FileNoOp>(op); + if(!typedRcPtr) return false; + return true; + } + + bool LookNoOp::isInverse(const OpRcPtr & op) const + { + return isSameType(op); + } + + void LookNoOp::dumpMetadata(ProcessorMetadataRcPtr & metadata) const + { + metadata->addLook(m_look.c_str()); + } + } + + void CreateLookNoOp(OpRcPtrVec & ops, + const std::string & look) + { + ops.push_back( LookNoOpRcPtr(new LookNoOp(look)) ); + } + +} +OCIO_NAMESPACE_EXIT + + + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef OCIO_UNIT_TEST + +OCIO_NAMESPACE_USING + +#include "UnitTest.h" +#include "Lut1DOp.h" +#include "MatrixOps.h" + +void CreateGenericAllocationOp(OpRcPtrVec & ops) +{ + AllocationData srcAllocation; + srcAllocation.allocation = ALLOCATION_LG2; + srcAllocation.vars.push_back(-8.0f); + srcAllocation.vars.push_back(8.0f); + CreateGpuAllocationNoOp(ops, srcAllocation); +} + +void CreateGenericScaleOp(OpRcPtrVec & ops) +{ + float scale4[4] = { 1.04f, 1.05f, 1.06f, 1.0f }; + CreateScaleOp(ops, scale4, TRANSFORM_DIR_FORWARD); +} + +void CreateGenericLutOp(OpRcPtrVec & ops) +{ + // Make a lut that squares the input + Lut1DRcPtr lut = Lut1D::Create(); + { + lut->from_min[0] = 0.0f; + lut->from_min[1] = 0.0f; + lut->from_min[2] = 0.0f; + lut->from_max[0] = 1.0f; + lut->from_max[1] = 1.0f; + lut->from_max[2] = 1.0f; + int size = 256; + for(int i=0; i<size; ++i) + { + float x = (float)i / (float)(size-1); + float x2 = x*x; + + for(int c=0; c<3; ++c) + { + lut->luts[c].push_back(x2); + } + } + } + + CreateLut1DOp(ops, lut, INTERP_LINEAR, TRANSFORM_DIR_FORWARD); +} + +OIIO_ADD_TEST(NoOps, PartitionGPUOps) +{ + { + OpRcPtrVec ops; + + OpRcPtrVec gpuPreOps, gpuLatticeOps, gpuPostOps; + PartitionGPUOps(gpuPreOps, gpuLatticeOps, gpuPostOps, ops); + + OIIO_CHECK_EQUAL(gpuPreOps.size(), 0); + OIIO_CHECK_EQUAL(gpuLatticeOps.size(), 0); + OIIO_CHECK_EQUAL(gpuPostOps.size(), 0); + + OIIO_CHECK_NO_THOW( AssertPartitionIntegrity(gpuPreOps, + gpuLatticeOps, + gpuPostOps) ); + } + + { + OpRcPtrVec ops; + CreateGenericAllocationOp(ops); + + OpRcPtrVec gpuPreOps, gpuLatticeOps, gpuPostOps; + PartitionGPUOps(gpuPreOps, gpuLatticeOps, gpuPostOps, ops); + + OIIO_CHECK_EQUAL(gpuPreOps.size(), 1); + OIIO_CHECK_EQUAL(gpuLatticeOps.size(), 0); + OIIO_CHECK_EQUAL(gpuPostOps.size(), 0); + + OIIO_CHECK_NO_THOW( AssertPartitionIntegrity(gpuPreOps, + gpuLatticeOps, + gpuPostOps) ); + } + + { + OpRcPtrVec ops; + + CreateGenericAllocationOp(ops); + CreateGenericScaleOp(ops); + + OpRcPtrVec gpuPreOps, gpuLatticeOps, gpuPostOps; + PartitionGPUOps(gpuPreOps, gpuLatticeOps, gpuPostOps, ops); + + OIIO_CHECK_EQUAL(gpuPreOps.size(), 2); + OIIO_CHECK_EQUAL(gpuLatticeOps.size(), 0); + OIIO_CHECK_EQUAL(gpuPostOps.size(), 0); + + OIIO_CHECK_NO_THOW( AssertPartitionIntegrity(gpuPreOps, + gpuLatticeOps, + gpuPostOps) ); + } + + { + OpRcPtrVec ops; + + CreateGenericAllocationOp(ops); + CreateGenericLutOp(ops); + CreateGenericScaleOp(ops); + + OpRcPtrVec gpuPreOps, gpuLatticeOps, gpuPostOps; + PartitionGPUOps(gpuPreOps, gpuLatticeOps, gpuPostOps, ops); + + OIIO_CHECK_EQUAL(gpuPreOps.size(), 2); + OIIO_CHECK_EQUAL(gpuLatticeOps.size(), 4); + OIIO_CHECK_EQUAL(gpuPostOps.size(), 1); + + OIIO_CHECK_NO_THOW( AssertPartitionIntegrity(gpuPreOps, + gpuLatticeOps, + gpuPostOps) ); + } + + { + OpRcPtrVec ops; + + CreateGenericLutOp(ops); + + OpRcPtrVec gpuPreOps, gpuLatticeOps, gpuPostOps; + PartitionGPUOps(gpuPreOps, gpuLatticeOps, gpuPostOps, ops); + + OIIO_CHECK_EQUAL(gpuPreOps.size(), 0); + OIIO_CHECK_EQUAL(gpuLatticeOps.size(), 1); + OIIO_CHECK_EQUAL(gpuPostOps.size(), 0); + + OIIO_CHECK_NO_THOW( AssertPartitionIntegrity(gpuPreOps, + gpuLatticeOps, + gpuPostOps) ); + } + + { + OpRcPtrVec ops; + + CreateGenericLutOp(ops); + CreateGenericScaleOp(ops); + CreateGenericAllocationOp(ops); + CreateGenericLutOp(ops); + CreateGenericScaleOp(ops); + CreateGenericAllocationOp(ops); + + OpRcPtrVec gpuPreOps, gpuLatticeOps, gpuPostOps; + PartitionGPUOps(gpuPreOps, gpuLatticeOps, gpuPostOps, ops); + + OIIO_CHECK_EQUAL(gpuPreOps.size(), 0); + OIIO_CHECK_EQUAL(gpuLatticeOps.size(), 4); + OIIO_CHECK_EQUAL(gpuPostOps.size(), 2); + + OIIO_CHECK_NO_THOW( AssertPartitionIntegrity(gpuPreOps, + gpuLatticeOps, + gpuPostOps) ); + } + + { + OpRcPtrVec ops; + + CreateGenericAllocationOp(ops); + CreateGenericScaleOp(ops); + CreateGenericLutOp(ops); + CreateGenericScaleOp(ops); + CreateGenericAllocationOp(ops); + CreateGenericLutOp(ops); + CreateGenericScaleOp(ops); + CreateGenericAllocationOp(ops); + + OpRcPtrVec gpuPreOps, gpuLatticeOps, gpuPostOps; + PartitionGPUOps(gpuPreOps, gpuLatticeOps, gpuPostOps, ops); + + OIIO_CHECK_EQUAL(gpuPreOps.size(), 2); + OIIO_CHECK_EQUAL(gpuLatticeOps.size(), 8); + OIIO_CHECK_EQUAL(gpuPostOps.size(), 2); + + OIIO_CHECK_NO_THOW( AssertPartitionIntegrity(gpuPreOps, + gpuLatticeOps, + gpuPostOps) ); + /* + std::cerr << "gpuPreOps" << std::endl; + std::cerr << SerializeOpVec(gpuPreOps, 4) << std::endl; + std::cerr << "gpuLatticeOps" << std::endl; + std::cerr << SerializeOpVec(gpuLatticeOps, 4) << std::endl; + std::cerr << "gpuPostOps" << std::endl; + std::cerr << SerializeOpVec(gpuPostOps, 4) << std::endl; + */ + } +} // PartitionGPUOps + +#endif // OCIO_UNIT_TEST diff --git a/src/core/NoOps.h b/src/core/NoOps.h new file mode 100644 index 0000000..dcc93e8 --- /dev/null +++ b/src/core/NoOps.h @@ -0,0 +1,67 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_GPUALLOCATIONNOOP_H +#define INCLUDED_OCIO_GPUALLOCATIONNOOP_H + +#include <OpenColorIO/OpenColorIO.h> + +#include "Op.h" + +#include <vector> + +OCIO_NAMESPACE_ENTER +{ + void CreateGpuAllocationNoOp(OpRcPtrVec & ops, + const AllocationData & allocationData); + + + // Partition an opvec into 3 segments for GPU Processing + // + // gpuLatticeOps need not support analytical gpu shader generation + // the pre and post ops must support analytical generation. + // + // Additional ops will optinally be inserted to take into account + // allocation transformations + + void PartitionGPUOps(OpRcPtrVec & gpuPreOps, + OpRcPtrVec & gpuLatticeOps, + OpRcPtrVec & gpuPostOps, + const OpRcPtrVec & ops); + + void CreateFileNoOp(OpRcPtrVec & ops, + const std::string & fname); + + void CreateLookNoOp(OpRcPtrVec & ops, + const std::string & lookName); + +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/OCIOYaml.cpp b/src/core/OCIOYaml.cpp new file mode 100644 index 0000000..7089318 --- /dev/null +++ b/src/core/OCIOYaml.cpp @@ -0,0 +1,1218 @@ +/* +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 <cstring> + +#include <OpenColorIO/OpenColorIO.h> + +#include "Logging.h" +#include "MathUtils.h" +#include "OCIOYaml.h" + +OCIO_NAMESPACE_ENTER +{ + /////////////////////////////////////////////////////////////////////////// + // Core + + void LogUnknownKeyWarning(const std::string & name, const YAML::Node& tag) + { + std::string key; + tag >> key; + + std::ostringstream os; + os << "Unknown key in " << name << ": "; + os << "'" << key << "'. (line "; + os << (tag.GetMark().line+1) << ", column "; // (yaml line numbers start at 0) + os << tag.GetMark().column << ")"; + LogWarning(os.str()); + } + + void operator >> (const YAML::Node& node, ColorSpaceRcPtr& cs) + { + if(node.Tag() != "ColorSpace") + return; // not a !<ColorSpace> tag + + std::string key, stringval; + bool boolval; + + for (YAML::Iterator iter = node.begin(); + iter != node.end(); + ++iter) + { + iter.first() >> key; + + if(key == "name") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + cs->setName(stringval.c_str()); + } + else if(key == "description") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + cs->setDescription(stringval.c_str()); + } + else if(key == "family") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + cs->setFamily(stringval.c_str()); + } + else if(key == "equalitygroup") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + cs->setEqualityGroup(stringval.c_str()); + } + else if(key == "bitdepth") + { + BitDepth ret; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<BitDepth>(ret)) + cs->setBitDepth(ret); + } + else if(key == "isdata") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<bool>(boolval)) + cs->setIsData(boolval); + } + else if(key == "allocation") + { + Allocation val; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<Allocation>(val)) + cs->setAllocation(val); + } + else if(key == "allocationvars") + { + std::vector<float> val; + if (iter.second().Type() != YAML::NodeType::Null) + { + iter.second() >> val; + if(!val.empty()) + { + cs->setAllocationVars(static_cast<int>(val.size()), &val[0]); + } + } + } + else if(key == "to_reference") + { + TransformRcPtr val; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<TransformRcPtr>(val)) + cs->setTransform(val, COLORSPACE_DIR_TO_REFERENCE); + } + else if(key == "from_reference") + { + TransformRcPtr val; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<TransformRcPtr>(val)) + cs->setTransform(val, COLORSPACE_DIR_FROM_REFERENCE); + } + else + { + LogUnknownKeyWarning(node.Tag(), iter.first()); + } + } + } + + YAML::Emitter& operator << (YAML::Emitter& out, ColorSpaceRcPtr cs) + { + out << YAML::VerbatimTag("ColorSpace"); + out << YAML::BeginMap; + + out << YAML::Key << "name" << YAML::Value << cs->getName(); + out << YAML::Key << "family" << YAML::Value << cs->getFamily(); + out << YAML::Key << "equalitygroup" << YAML::Value << cs->getEqualityGroup(); + out << YAML::Key << "bitdepth" << YAML::Value << cs->getBitDepth(); + if(strlen(cs->getDescription()) > 0) + { + out << YAML::Key << "description"; + out << YAML::Value << YAML::Literal << cs->getDescription(); + } + out << YAML::Key << "isdata" << YAML::Value << cs->isData(); + + out << YAML::Key << "allocation" << YAML::Value << cs->getAllocation(); + if(cs->getAllocationNumVars() > 0) + { + std::vector<float> allocationvars(cs->getAllocationNumVars()); + cs->getAllocationVars(&allocationvars[0]); + out << YAML::Key << "allocationvars"; + out << YAML::Flow << YAML::Value << allocationvars; + } + + ConstTransformRcPtr toref = \ + cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); + if(toref) + out << YAML::Key << "to_reference" << YAML::Value << toref; + + ConstTransformRcPtr fromref = \ + cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); + if(fromref) + out << YAML::Key << "from_reference" << YAML::Value << fromref; + + out << YAML::EndMap; + out << YAML::Newline; + + return out; + } + + + /////////////////////////////////////////////////////////////////////////// + + // Look. (not the transform, the top-level class) + + void operator >> (const YAML::Node& node, LookRcPtr& look) + { + if(node.Tag() != "Look") + return; + + std::string key, stringval; + + for (YAML::Iterator iter = node.begin(); + iter != node.end(); + ++iter) + { + iter.first() >> key; + + if(key == "name") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + look->setName(stringval.c_str()); + } + else if(key == "process_space") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + look->setProcessSpace(stringval.c_str()); + } + else if(key == "transform") + { + TransformRcPtr val; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<TransformRcPtr>(val)) + look->setTransform(val); + } + else if(key == "inverse_transform") + { + TransformRcPtr val; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<TransformRcPtr>(val)) + look->setInverseTransform(val); + } + else + { + LogUnknownKeyWarning(node.Tag(), iter.first()); + } + } + } + + YAML::Emitter& operator << (YAML::Emitter& out, LookRcPtr look) + { + out << YAML::VerbatimTag("Look"); + out << YAML::BeginMap; + out << YAML::Key << "name" << YAML::Value << look->getName(); + out << YAML::Key << "process_space" << YAML::Value << look->getProcessSpace(); + + if(look->getTransform()) + { + out << YAML::Key << "transform"; + out << YAML::Value << look->getTransform(); + } + + if(look->getInverseTransform()) + { + out << YAML::Key << "inverse_transform"; + out << YAML::Value << look->getInverseTransform(); + } + + out << YAML::EndMap; + out << YAML::Newline; + + return out; + } + + + + /////////////////////////////////////////////////////////////////////////// + + + namespace + { + void EmitBaseTransformKeyValues(YAML::Emitter & out, + const ConstTransformRcPtr & t) + { + if(t->getDirection() != TRANSFORM_DIR_FORWARD) + { + out << YAML::Key << "direction"; + out << YAML::Value << YAML::Flow << t->getDirection(); + } + } + } + + void operator >> (const YAML::Node& node, TransformRcPtr& t) + { + if(node.Type() != YAML::NodeType::Map) + { + std::ostringstream os; + os << "Unsupported Transform type encountered: (" << node.Type() << ") in OCIO profile. "; + os << "Only Mapping types supported. (line "; + os << (node.GetMark().line+1) << ", column "; // (yaml line numbers start at 0) + os << node.GetMark().column << ")"; + throw Exception(os.str().c_str()); + } + + std::string type = node.Tag(); + + if(type == "AllocationTransform") { + AllocationTransformRcPtr temp; + node.Read<AllocationTransformRcPtr>(temp); + t = temp; + } + else if(type == "CDLTransform") { + CDLTransformRcPtr temp; + node.Read<CDLTransformRcPtr>(temp); + t = temp; + } + else if(type == "ColorSpaceTransform") { + ColorSpaceTransformRcPtr temp; + node.Read<ColorSpaceTransformRcPtr>(temp); + t = temp; + } + // TODO: DisplayTransform + else if(type == "ExponentTransform") { + ExponentTransformRcPtr temp; + node.Read<ExponentTransformRcPtr>(temp); + t = temp; + } + else if(type == "FileTransform") { + FileTransformRcPtr temp; + node.Read<FileTransformRcPtr>(temp); + t = temp; + } + else if(type == "GroupTransform") { + GroupTransformRcPtr temp; + node.Read<GroupTransformRcPtr>(temp); + t = temp; + } + else if(type == "LogTransform") { + LogTransformRcPtr temp; + node.Read<LogTransformRcPtr>(temp); + t = temp; + } + else if(type == "LookTransform") { + LookTransformRcPtr temp; + node.Read<LookTransformRcPtr>(temp); + t = temp; + } + else if(type == "MatrixTransform") { + MatrixTransformRcPtr temp; + node.Read<MatrixTransformRcPtr>(temp); + t = temp; + } + else if(type == "TruelightTransform") { + TruelightTransformRcPtr temp; + node.Read<TruelightTransformRcPtr>(temp); + t = temp; + } + else + { + // TODO: add a new empty (better name?) aka passthru Transform() + // which does nothing. This is so upsupported !<tag> types don't + // throw an exception. Alternativly this could be caught in the + // GroupTransformRcPtr >> operator with some type of + // supported_tag() method + + // TODO: consider the forwards-compatibility implication of + // throwing an exception. Should this be a warning, instead? + + // t = EmptyTransformRcPtr(new EmptyTransform(), &deleter); + std::ostringstream os; + os << "Unsupported transform type !<" << type << "> in OCIO profile. "; + os << " (line "; + os << (node.GetMark().line+1) << ", column "; // (yaml line numbers start at 0) + os << node.GetMark().column << ")"; + throw Exception(os.str().c_str()); + } + } + + YAML::Emitter& operator << (YAML::Emitter& out, ConstTransformRcPtr t) + { + if(ConstAllocationTransformRcPtr Allocation_tran = \ + DynamicPtrCast<const AllocationTransform>(t)) + out << Allocation_tran; + else if(ConstCDLTransformRcPtr CDL_tran = \ + DynamicPtrCast<const CDLTransform>(t)) + out << CDL_tran; + else if(ConstColorSpaceTransformRcPtr ColorSpace_tran = \ + DynamicPtrCast<const ColorSpaceTransform>(t)) + out << ColorSpace_tran; + else if(ConstExponentTransformRcPtr Exponent_tran = \ + DynamicPtrCast<const ExponentTransform>(t)) + out << Exponent_tran; + else if(ConstFileTransformRcPtr File_tran = \ + DynamicPtrCast<const FileTransform>(t)) + out << File_tran; + else if(ConstGroupTransformRcPtr Group_tran = \ + DynamicPtrCast<const GroupTransform>(t)) + out << Group_tran; + else if(ConstLogTransformRcPtr Log_tran = \ + DynamicPtrCast<const LogTransform>(t)) + out << Log_tran; + else if(ConstLookTransformRcPtr Look_tran = \ + DynamicPtrCast<const LookTransform>(t)) + out << Look_tran; + else if(ConstMatrixTransformRcPtr Matrix_tran = \ + DynamicPtrCast<const MatrixTransform>(t)) + out << Matrix_tran; + else if(ConstTruelightTransformRcPtr Truelight_tran = \ + DynamicPtrCast<const TruelightTransform>(t)) + out << Truelight_tran; + else + throw Exception("Unsupported Transform() type for serialization."); + + return out; + } + + + /////////////////////////////////////////////////////////////////////////// + // Transforms + + void operator >> (const YAML::Node& node, GroupTransformRcPtr& t) + { + t = GroupTransform::Create(); + + std::string key; + + for (YAML::Iterator iter = node.begin(); + iter != node.end(); + ++iter) + { + iter.first() >> key; + + if(key == "children") + { + const YAML::Node & children = iter.second(); + for(unsigned i = 0; i <children.size(); ++i) + { + TransformRcPtr childTransform; + children[i].Read<TransformRcPtr>(childTransform); + + // TODO: consider the forwards-compatibility implication of + // throwing an exception. Should this be a warning, instead? + if(!childTransform) + { + throw Exception("Child transform could not be parsed."); + } + + t->push_back(childTransform); + } + } + else if(key == "direction") + { + TransformDirection val; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<TransformDirection>(val)) + t->setDirection(val); + } + else + { + LogUnknownKeyWarning(node.Tag(), iter.first()); + } + } + } + + YAML::Emitter& operator << (YAML::Emitter& out, ConstGroupTransformRcPtr t) + { + out << YAML::VerbatimTag("GroupTransform"); + out << YAML::BeginMap; + EmitBaseTransformKeyValues(out, t); + + out << YAML::Key << "children"; + out << YAML::Value; + + out << YAML::BeginSeq; + for(int i = 0; i < t->size(); ++i) + { + out << t->getTransform(i); + } + out << YAML::EndSeq; + + out << YAML::EndMap; + + return out; + } + + + + void operator >> (const YAML::Node& node, FileTransformRcPtr& t) + { + t = FileTransform::Create(); + + std::string key, stringval; + + for (YAML::Iterator iter = node.begin(); + iter != node.end(); + ++iter) + { + iter.first() >> key; + + if(key == "src") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + t->setSrc(stringval.c_str()); + } + else if(key == "cccid") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + t->setCCCId(stringval.c_str()); + } + else if(key == "interpolation") + { + Interpolation val; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<Interpolation>(val)) + t->setInterpolation(val); + } + else if(key == "direction") + { + TransformDirection val; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<TransformDirection>(val)) + t->setDirection(val); + } + else + { + LogUnknownKeyWarning(node.Tag(), iter.first()); + } + } + } + + YAML::Emitter& operator << (YAML::Emitter& out, ConstFileTransformRcPtr t) + { + out << YAML::VerbatimTag("FileTransform"); + out << YAML::Flow << YAML::BeginMap; + out << YAML::Key << "src" << YAML::Value << t->getSrc(); + const char * cccid = t->getCCCId(); + if(cccid && *cccid) + { + out << YAML::Key << "cccid" << YAML::Value << t->getCCCId(); + } + out << YAML::Key << "interpolation"; + out << YAML::Value << t->getInterpolation(); + + EmitBaseTransformKeyValues(out, t); + out << YAML::EndMap; + return out; + } + + void operator >> (const YAML::Node& node, ColorSpaceTransformRcPtr& t) + { + t = ColorSpaceTransform::Create(); + + std::string key, stringval; + + for (YAML::Iterator iter = node.begin(); + iter != node.end(); + ++iter) + { + iter.first() >> key; + + if(key == "src") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + t->setSrc(stringval.c_str()); + } + else if(key == "dst") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + t->setDst(stringval.c_str()); + } + else if(key == "direction") + { + TransformDirection val; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<TransformDirection>(val)) + t->setDirection(val); + } + else + { + LogUnknownKeyWarning(node.Tag(), iter.first()); + } + } + } + + YAML::Emitter& operator << (YAML::Emitter& out, ConstColorSpaceTransformRcPtr t) + { + out << YAML::VerbatimTag("ColorSpaceTransform"); + out << YAML::Flow << YAML::BeginMap; + out << YAML::Key << "src" << YAML::Value << t->getSrc(); + out << YAML::Key << "dst" << YAML::Value << t->getDst(); + EmitBaseTransformKeyValues(out, t); + out << YAML::EndMap; + return out; + } + + void operator >> (const YAML::Node& node, LookTransformRcPtr& t) + { + t = LookTransform::Create(); + + std::string key, stringval; + + for (YAML::Iterator iter = node.begin(); + iter != node.end(); + ++iter) + { + iter.first() >> key; + + if(key == "src") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + t->setSrc(stringval.c_str()); + } + else if(key == "dst") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + t->setDst(stringval.c_str()); + } + else if(key == "looks") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + t->setLooks(stringval.c_str()); + } + else if(key == "direction") + { + TransformDirection val; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<TransformDirection>(val)) + t->setDirection(val); + } + else + { + LogUnknownKeyWarning(node.Tag(), iter.first()); + } + } + } + + YAML::Emitter& operator << (YAML::Emitter& out, ConstLookTransformRcPtr t) + { + out << YAML::VerbatimTag("LookTransform"); + out << YAML::Flow << YAML::BeginMap; + out << YAML::Key << "src" << YAML::Value << t->getSrc(); + out << YAML::Key << "dst" << YAML::Value << t->getDst(); + out << YAML::Key << "looks" << YAML::Value << t->getLooks(); + EmitBaseTransformKeyValues(out, t); + out << YAML::EndMap; + return out; + } + + void operator >> (const YAML::Node& node, ExponentTransformRcPtr& t) + { + t = ExponentTransform::Create(); + + std::string key; + + for (YAML::Iterator iter = node.begin(); + iter != node.end(); + ++iter) + { + iter.first() >> key; + + if(key == "value") + { + std::vector<float> val; + if (iter.second().Type() != YAML::NodeType::Null) + { + iter.second() >> val; + if(val.size() != 4) + { + std::ostringstream os; + os << "ExponentTransform parse error, value field must be 4 "; + os << "floats. Found '" << val.size() << "'."; + throw Exception(os.str().c_str()); + } + t->setValue(&val[0]); + } + } + else if(key == "direction") + { + TransformDirection val; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<TransformDirection>(val)) + t->setDirection(val); + } + else + { + LogUnknownKeyWarning(node.Tag(), iter.first()); + } + } + } + + YAML::Emitter& operator << (YAML::Emitter& out, ConstExponentTransformRcPtr t) + { + out << YAML::VerbatimTag("ExponentTransform"); + out << YAML::Flow << YAML::BeginMap; + + std::vector<float> value(4, 0.0); + t->getValue(&value[0]); + out << YAML::Key << "value"; + out << YAML::Value << YAML::Flow << value; + EmitBaseTransformKeyValues(out, t); + out << YAML::EndMap; + return out; + } + + void operator >> (const YAML::Node& node, LogTransformRcPtr& t) + { + t = LogTransform::Create(); + + std::string key; + + for (YAML::Iterator iter = node.begin(); + iter != node.end(); + ++iter) + { + iter.first() >> key; + + if(key == "base") + { + float val = 0.0f; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<float>(val)) + t->setBase(val); + } + else if(key == "direction") + { + TransformDirection val; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<TransformDirection>(val)) + t->setDirection(val); + } + else + { + LogUnknownKeyWarning(node.Tag(), iter.first()); + } + } + } + + YAML::Emitter& operator << (YAML::Emitter& out, ConstLogTransformRcPtr t) + { + out << YAML::VerbatimTag("LogTransform"); + out << YAML::Flow << YAML::BeginMap; + out << YAML::Key << "base" << YAML::Value << t->getBase(); + EmitBaseTransformKeyValues(out, t); + out << YAML::EndMap; + return out; + } + + void operator >> (const YAML::Node& node, MatrixTransformRcPtr& t) + { + t = MatrixTransform::Create(); + + std::string key; + + for (YAML::Iterator iter = node.begin(); + iter != node.end(); + ++iter) + { + iter.first() >> key; + + if(key == "matrix") + { + std::vector<float> val; + if (iter.second().Type() != YAML::NodeType::Null) + { + iter.second() >> val; + if(val.size() != 16) + { + std::ostringstream os; + os << "MatrixTransform parse error, matrix field must be 16 "; + os << "floats. Found '" << val.size() << "'."; + throw Exception(os.str().c_str()); + } + t->setMatrix(&val[0]); + } + } + else if(key == "offset") + { + std::vector<float> val; + if (iter.second().Type() != YAML::NodeType::Null) + { + iter.second() >> val; + if(val.size() != 4) + { + std::ostringstream os; + os << "MatrixTransform parse error, offset field must be 4 "; + os << "floats. Found '" << val.size() << "'."; + throw Exception(os.str().c_str()); + } + t->setOffset(&val[0]); + } + } + else if(key == "direction") + { + TransformDirection val; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<TransformDirection>(val)) + t->setDirection(val); + } + else + { + LogUnknownKeyWarning(node.Tag(), iter.first()); + } + } + } + + YAML::Emitter& operator << (YAML::Emitter& out, ConstMatrixTransformRcPtr t) + { + out << YAML::VerbatimTag("MatrixTransform"); + out << YAML::Flow << YAML::BeginMap; + + std::vector<float> matrix(16, 0.0); + t->getMatrix(&matrix[0]); + if(!IsM44Identity(&matrix[0])) + { + out << YAML::Key << "matrix"; + out << YAML::Value << YAML::Flow << matrix; + } + + std::vector<float> offset(4, 0.0); + t->getOffset(&offset[0]); + if(!IsVecEqualToZero(&offset[0],4)) + { + out << YAML::Key << "offset"; + out << YAML::Value << YAML::Flow << offset; + } + + EmitBaseTransformKeyValues(out, t); + out << YAML::EndMap; + return out; + } + + void operator >> (const YAML::Node& node, CDLTransformRcPtr& t) + { + t = CDLTransform::Create(); + + std::string key; + std::vector<float> floatvecval; + + for (YAML::Iterator iter = node.begin(); + iter != node.end(); + ++iter) + { + iter.first() >> key; + + if(key == "slope") + { + if (iter.second().Type() != YAML::NodeType::Null) + { + iter.second() >> floatvecval; + if(floatvecval.size() != 3) + { + std::ostringstream os; + os << "CDLTransform parse error, 'slope' field must be 3 "; + os << "floats. Found '" << floatvecval.size() << "'."; + throw Exception(os.str().c_str()); + } + t->setSlope(&floatvecval[0]); + } + } + else if(key == "offset") + { + if (iter.second().Type() != YAML::NodeType::Null) + { + iter.second() >> floatvecval; + if(floatvecval.size() != 3) + { + std::ostringstream os; + os << "CDLTransform parse error, 'offset' field must be 3 "; + os << "floats. Found '" << floatvecval.size() << "'."; + throw Exception(os.str().c_str()); + } + t->setOffset(&floatvecval[0]); + } + } + else if(key == "power") + { + if (iter.second().Type() != YAML::NodeType::Null) + { + iter.second() >> floatvecval; + if(floatvecval.size() != 3) + { + std::ostringstream os; + os << "CDLTransform parse error, 'power' field must be 3 "; + os << "floats. Found '" << floatvecval.size() << "'."; + throw Exception(os.str().c_str()); + } + t->setPower(&floatvecval[0]); + } + } + else if(key == "saturation" || key == "sat") + { + float val = 0.0f; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<float>(val)) + t->setSat(val); + } + else if(key == "direction") + { + TransformDirection val; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<TransformDirection>(val)) + t->setDirection(val); + } + else + { + LogUnknownKeyWarning(node.Tag(), iter.first()); + } + } + } + + YAML::Emitter& operator << (YAML::Emitter& out, ConstCDLTransformRcPtr t) + { + out << YAML::VerbatimTag("CDLTransform"); + out << YAML::Flow << YAML::BeginMap; + + std::vector<float> slope(3); + t->getSlope(&slope[0]); + if(!IsVecEqualToOne(&slope[0], 3)) + { + out << YAML::Key << "slope"; + out << YAML::Value << YAML::Flow << slope; + } + + std::vector<float> offset(3); + t->getOffset(&offset[0]); + if(!IsVecEqualToZero(&offset[0], 3)) + { + out << YAML::Key << "offset"; + out << YAML::Value << YAML::Flow << offset; + } + + std::vector<float> power(3); + t->getPower(&power[0]); + if(!IsVecEqualToOne(&power[0], 3)) + { + out << YAML::Key << "power"; + out << YAML::Value << YAML::Flow << power; + } + + if(!IsScalarEqualToOne(t->getSat())) + { + out << YAML::Key << "sat" << YAML::Value << t->getSat(); + } + + EmitBaseTransformKeyValues(out, t); + out << YAML::EndMap; + return out; + } + + void operator >> (const YAML::Node& node, AllocationTransformRcPtr& t) + { + t = AllocationTransform::Create(); + + std::string key; + + for (YAML::Iterator iter = node.begin(); + iter != node.end(); + ++iter) + { + iter.first() >> key; + + if(key == "allocation") + { + Allocation val; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<Allocation>(val)) + t->setAllocation(val); + } + else if(key == "vars") + { + std::vector<float> val; + if (iter.second().Type() != YAML::NodeType::Null) + { + iter.second() >> val; + if(!val.empty()) + { + t->setVars(static_cast<int>(val.size()), &val[0]); + } + } + } + else if(key == "direction") + { + TransformDirection val; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<TransformDirection>(val)) + t->setDirection(val); + } + else + { + LogUnknownKeyWarning(node.Tag(), iter.first()); + } + } + } + + YAML::Emitter& operator << (YAML::Emitter& out, ConstAllocationTransformRcPtr t) + { + out << YAML::VerbatimTag("AllocationTransform"); + out << YAML::Flow << YAML::BeginMap; + + out << YAML::Key << "allocation"; + out << YAML::Value << YAML::Flow << t->getAllocation(); + + if(t->getNumVars() > 0) + { + std::vector<float> vars(t->getNumVars()); + t->getVars(&vars[0]); + out << YAML::Key << "vars"; + out << YAML::Flow << YAML::Value << vars; + } + + EmitBaseTransformKeyValues(out, t); + out << YAML::EndMap; + return out; + } + + void operator >> (const YAML::Node& node, TruelightTransformRcPtr& t) + { + t = TruelightTransform::Create(); + + std::string key, stringval; + + for (YAML::Iterator iter = node.begin(); + iter != node.end(); + ++iter) + { + iter.first() >> key; + + if(key == "config_root") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + t->setConfigRoot(stringval.c_str()); + } + else if(key == "profile") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + t->setProfile(stringval.c_str()); + } + else if(key == "camera") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + t->setCamera(stringval.c_str()); + } + else if(key == "input_display") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + t->setInputDisplay(stringval.c_str()); + } + else if(key == "recorder") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + t->setRecorder(stringval.c_str()); + } + else if(key == "print") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + t->setPrint(stringval.c_str()); + } + else if(key == "lamp") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + t->setLamp(stringval.c_str()); + } + else if(key == "output_camera") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + t->setOutputCamera(stringval.c_str()); + } + else if(key == "display") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + t->setDisplay(stringval.c_str()); + } + else if(key == "cube_input") + { + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<std::string>(stringval)) + t->setCubeInput(stringval.c_str()); + } + else if(key == "direction") + { + TransformDirection val; + if (iter.second().Type() != YAML::NodeType::Null && + iter.second().Read<TransformDirection>(val)) + t->setDirection(val); + } + else + { + LogUnknownKeyWarning(node.Tag(), iter.first()); + } + } + } + + YAML::Emitter& operator << (YAML::Emitter& out, ConstTruelightTransformRcPtr t) + { + + out << YAML::VerbatimTag("TruelightTransform"); + out << YAML::Flow << YAML::BeginMap; + if(strcmp(t->getConfigRoot(), "") != 0) + { + out << YAML::Key << "config_root"; + out << YAML::Value << YAML::Flow << t->getConfigRoot(); + } + if(strcmp(t->getProfile(), "") != 0) + { + out << YAML::Key << "profile"; + out << YAML::Value << YAML::Flow << t->getProfile(); + } + if(strcmp(t->getCamera(), "") != 0) + { + out << YAML::Key << "camera"; + out << YAML::Value << YAML::Flow << t->getCamera(); + } + if(strcmp(t->getInputDisplay(), "") != 0) + { + out << YAML::Key << "input_display"; + out << YAML::Value << YAML::Flow << t->getInputDisplay(); + } + if(strcmp(t->getRecorder(), "") != 0) + { + out << YAML::Key << "recorder"; + out << YAML::Value << YAML::Flow << t->getRecorder(); + } + if(strcmp(t->getPrint(), "") != 0) + { + out << YAML::Key << "print"; + out << YAML::Value << YAML::Flow << t->getPrint(); + } + if(strcmp(t->getLamp(), "") != 0) + { + out << YAML::Key << "lamp"; + out << YAML::Value << YAML::Flow << t->getLamp(); + } + if(strcmp(t->getOutputCamera(), "") != 0) + { + out << YAML::Key << "output_camera"; + out << YAML::Value << YAML::Flow << t->getOutputCamera(); + } + if(strcmp(t->getDisplay(), "") != 0) + { + out << YAML::Key << "display"; + out << YAML::Value << YAML::Flow << t->getDisplay(); + } + if(strcmp(t->getCubeInput(), "") != 0) + { + out << YAML::Key << "cube_input"; + out << YAML::Value << YAML::Flow << t->getCubeInput(); + } + + EmitBaseTransformKeyValues(out, t); + + out << YAML::EndMap; + return out; + } + + /////////////////////////////////////////////////////////////////////////// + // Enums + + YAML::Emitter& operator << (YAML::Emitter& out, BitDepth depth) { + out << BitDepthToString(depth); + return out; + } + + void operator >> (const YAML::Node& node, BitDepth& depth) { + std::string str; + node.Read<std::string>(str); + depth = BitDepthFromString(str.c_str()); + } + + YAML::Emitter& operator << (YAML::Emitter& out, Allocation alloc) { + out << AllocationToString(alloc); + return out; + } + + void operator >> (const YAML::Node& node, Allocation& alloc) { + std::string str; + node.Read<std::string>(str); + alloc = AllocationFromString(str.c_str()); + } + + YAML::Emitter& operator << (YAML::Emitter& out, ColorSpaceDirection dir) { + out << ColorSpaceDirectionToString(dir); + return out; + } + + void operator >> (const YAML::Node& node, ColorSpaceDirection& dir) { + std::string str; + node.Read<std::string>(str); + dir = ColorSpaceDirectionFromString(str.c_str()); + } + + YAML::Emitter& operator << (YAML::Emitter& out, TransformDirection dir) { + out << TransformDirectionToString(dir); + return out; + } + + void operator >> (const YAML::Node& node, TransformDirection& dir) { + std::string str; + node.Read<std::string>(str); + dir = TransformDirectionFromString(str.c_str()); + } + + YAML::Emitter& operator << (YAML::Emitter& out, Interpolation interp) { + out << InterpolationToString(interp); + return out; + } + + void operator >> (const YAML::Node& node, Interpolation& interp) { + std::string str; + node.Read<std::string>(str); + interp = InterpolationFromString(str.c_str()); + } + +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/OCIOYaml.h b/src/core/OCIOYaml.h new file mode 100644 index 0000000..7104123 --- /dev/null +++ b/src/core/OCIOYaml.h @@ -0,0 +1,125 @@ +/* +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 "Platform.h" + +#ifndef WINDOWS + +// fwd declare yaml-cpp visibility +#pragma GCC visibility push(hidden) +namespace YAML { + class Exception; + class BadDereference; + class RepresentationException; + class EmitterException; + class ParserException; + class InvalidScalar; + class KeyNotFound; + template <typename T> class TypedKeyNotFound; + template <> class TypedKeyNotFound<OCIO_NAMESPACE::ColorSpace>; + template <> class TypedKeyNotFound<OCIO_NAMESPACE::Config>; + template <> class TypedKeyNotFound<OCIO_NAMESPACE::Exception>; + template <> class TypedKeyNotFound<OCIO_NAMESPACE::GpuShaderDesc>; + template <> class TypedKeyNotFound<OCIO_NAMESPACE::ImageDesc>; + template <> class TypedKeyNotFound<OCIO_NAMESPACE::Look>; + template <> class TypedKeyNotFound<OCIO_NAMESPACE::Processor>; + + template <> class TypedKeyNotFound<OCIO_NAMESPACE::Transform>; + template <> class TypedKeyNotFound<OCIO_NAMESPACE::AllocationTransform>; + template <> class TypedKeyNotFound<OCIO_NAMESPACE::CDLTransform>; + template <> class TypedKeyNotFound<OCIO_NAMESPACE::ColorSpaceTransform>; + template <> class TypedKeyNotFound<OCIO_NAMESPACE::DisplayTransform>; + template <> class TypedKeyNotFound<OCIO_NAMESPACE::ExponentTransform>; + template <> class TypedKeyNotFound<OCIO_NAMESPACE::FileTransform>; + template <> class TypedKeyNotFound<OCIO_NAMESPACE::GroupTransform>; + template <> class TypedKeyNotFound<OCIO_NAMESPACE::LogTransform>; + template <> class TypedKeyNotFound<OCIO_NAMESPACE::LookTransform>; + template <> class TypedKeyNotFound<OCIO_NAMESPACE::MatrixTransform>; + template <> class TypedKeyNotFound<OCIO_NAMESPACE::TruelightTransform>; +} +#pragma GCC visibility pop + +#endif + +#include <yaml-cpp/yaml.h> + +#ifndef INCLUDED_OCIO_YAML_H +#define INCLUDED_OCIO_YAML_H + +OCIO_NAMESPACE_ENTER +{ + + // Core + OCIOHIDDEN void operator >> (const YAML::Node& node, ColorSpaceRcPtr& cs); + OCIOHIDDEN YAML::Emitter& operator << (YAML::Emitter& out, ColorSpaceRcPtr cs); + OCIOHIDDEN void operator >> (const YAML::Node& node, GroupTransformRcPtr& t); + OCIOHIDDEN YAML::Emitter& operator << (YAML::Emitter& out, ConstGroupTransformRcPtr t); + OCIOHIDDEN void operator >> (const YAML::Node& node, TransformRcPtr& t); + OCIOHIDDEN YAML::Emitter& operator << (YAML::Emitter& out, ConstTransformRcPtr t); + OCIOHIDDEN void operator >> (const YAML::Node& node, LookRcPtr& cs); + OCIOHIDDEN YAML::Emitter& operator << (YAML::Emitter& out, LookRcPtr cs); + + // Transforms + OCIOHIDDEN void operator >> (const YAML::Node& node, AllocationTransformRcPtr& t); + OCIOHIDDEN YAML::Emitter& operator << (YAML::Emitter& out, ConstAllocationTransformRcPtr t); + OCIOHIDDEN void operator >> (const YAML::Node& node, CDLTransformRcPtr& t); + OCIOHIDDEN YAML::Emitter& operator << (YAML::Emitter& out, ConstCDLTransformRcPtr t); + OCIOHIDDEN void operator >> (const YAML::Node& node, ColorSpaceTransformRcPtr& t); + OCIOHIDDEN YAML::Emitter& operator << (YAML::Emitter& out, ConstColorSpaceTransformRcPtr t); + OCIOHIDDEN void operator >> (const YAML::Node& node, ExponentTransformRcPtr& t); + OCIOHIDDEN YAML::Emitter& operator << (YAML::Emitter& out, ConstExponentTransformRcPtr t); + OCIOHIDDEN void operator >> (const YAML::Node& node, FileTransformRcPtr& t); + OCIOHIDDEN YAML::Emitter& operator << (YAML::Emitter& out, ConstFileTransformRcPtr t); + OCIOHIDDEN void operator >> (const YAML::Node& node, LogTransformRcPtr& t); + OCIOHIDDEN YAML::Emitter& operator << (YAML::Emitter& out, ConstLogTransformRcPtr t); + OCIOHIDDEN void operator >> (const YAML::Node& node, LookTransformRcPtr& t); + OCIOHIDDEN YAML::Emitter& operator << (YAML::Emitter& out, ConstLookTransformRcPtr t); + OCIOHIDDEN void operator >> (const YAML::Node& node, MatrixTransformRcPtr& t); + OCIOHIDDEN YAML::Emitter& operator << (YAML::Emitter& out, ConstMatrixTransformRcPtr t); + OCIOHIDDEN void operator >> (const YAML::Node& node, TruelightTransformRcPtr& t); + OCIOHIDDEN YAML::Emitter& operator << (YAML::Emitter& out, ConstTruelightTransformRcPtr t); + + // Enums + OCIOHIDDEN YAML::Emitter& operator << (YAML::Emitter& out, BitDepth depth); + OCIOHIDDEN void operator >> (const YAML::Node& node, BitDepth& depth); + OCIOHIDDEN YAML::Emitter& operator << (YAML::Emitter& out, Allocation alloc); + OCIOHIDDEN void operator >> (const YAML::Node& node, Allocation& alloc); + OCIOHIDDEN YAML::Emitter& operator << (YAML::Emitter& out, ColorSpaceDirection dir); + OCIOHIDDEN void operator >> (const YAML::Node& node, ColorSpaceDirection& dir); + OCIOHIDDEN YAML::Emitter& operator << (YAML::Emitter& out, TransformDirection dir); + OCIOHIDDEN void operator >> (const YAML::Node& node, TransformDirection& dir); + OCIOHIDDEN YAML::Emitter& operator << (YAML::Emitter& out, Interpolation iterp); + OCIOHIDDEN void operator >> (const YAML::Node& node, Interpolation& iterp); + + void LogUnknownKeyWarning(const std::string & name, const YAML::Node& tag); +} +OCIO_NAMESPACE_EXIT + +#endif // INCLUDED_OCIO_YAML_H diff --git a/src/core/Op.cpp b/src/core/Op.cpp new file mode 100644 index 0000000..e4e9da2 --- /dev/null +++ b/src/core/Op.cpp @@ -0,0 +1,126 @@ +/* +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 "Op.h" +#include "pystring/pystring.h" + +#include <sstream> + +OCIO_NAMESPACE_ENTER +{ + Op::~Op() + { } + + bool Op::canCombineWith(const OpRcPtr & /*op*/) const + { + return false; + } + + void Op::combineWith(OpRcPtrVec & /*ops*/, + const OpRcPtr & /*secondOp*/) const + { + std::ostringstream os; + os << "Op: " << getInfo() << " cannot be combined. "; + os << "A type-specific combining function is not defined."; + throw Exception(os.str().c_str()); + } + + std::ostream& operator<< (std::ostream & os, const Op & op) + { + os << op.getInfo(); + return os; + } + + namespace + { + const int FLOAT_DECIMALS = 7; + } + + std::string AllocationData::getCacheID() const + { + std::ostringstream os; + os.precision(FLOAT_DECIMALS); + os << AllocationToString(allocation) << " "; + + for(unsigned int i=0; i<vars.size(); ++i) + { + os << vars[i] << " "; + } + + return os.str(); + } + + std::ostream& operator<< (std::ostream & os, const AllocationData & allocation) + { + os << allocation.getCacheID(); + return os; + } + + std::string SerializeOpVec(const OpRcPtrVec & ops, int indent) + { + std::ostringstream os; + + for(OpRcPtrVec::size_type i = 0, size = ops.size(); i < size; ++i) + { + os << pystring::mul(" ", indent); + os << "Op " << i << ": " << *ops[i] << " "; + os << ops[i]->getCacheID() << " supports_gpu:" << ops[i]->supportsGpuShader(); + os << "\n"; + } + + return os.str(); + } + + bool IsOpVecNoOp(const OpRcPtrVec & ops) + { + for(OpRcPtrVec::size_type i = 0, size = ops.size(); i < size; ++i) + { + if(!ops[i]->isNoOp()) return false; + } + + return true; + } + + void FinalizeOpVec(OpRcPtrVec & ops, bool optimize) + { + // TODO: Add envvar to force disable optimizations + + if(optimize) + { + OptimizeOpVec(ops); + } + + for(OpRcPtrVec::size_type i = 0, size = ops.size(); i < size; ++i) + { + ops[i]->finalize(); + } + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/Op.h b/src/core/Op.h new file mode 100644 index 0000000..831c072 --- /dev/null +++ b/src/core/Op.h @@ -0,0 +1,136 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_OP_H +#define INCLUDED_OCIO_OP_H + +#include <OpenColorIO/OpenColorIO.h> + +#include <sstream> +#include <vector> + +OCIO_NAMESPACE_ENTER +{ + struct AllocationData + { + Allocation allocation; + std::vector<float> vars; + + AllocationData(): + allocation(ALLOCATION_UNIFORM) + {}; + + std::string getCacheID() const; + }; + + std::ostream& operator<< (std::ostream&, const AllocationData&); + + class Op; + typedef OCIO_SHARED_PTR<Op> OpRcPtr; + typedef std::vector<OpRcPtr> OpRcPtrVec; + + std::string SerializeOpVec(const OpRcPtrVec & ops, int indent=0); + bool IsOpVecNoOp(const OpRcPtrVec & ops); + + void FinalizeOpVec(OpRcPtrVec & opVec, bool optimize=true); + + void OptimizeOpVec(OpRcPtrVec & result); + + class Op + { + public: + virtual ~Op(); + + virtual OpRcPtr clone() const = 0; + + //! Something short, and printable. + // The type of stuff you'd want to see in debugging. + virtual std::string getInfo() const = 0; + + //! This should yield a string of not unreasonable length. + //! It can only be called after finalize() + virtual std::string getCacheID() const = 0; + + //! Is the processing a noop? I.e, does apply do nothing. + //! (Even no-ops may define Allocation though.) + //! This must be implmented in a manner where its valid to call + //! *prior* to finalize. (Optimizers may make use of it) + virtual bool isNoOp() const = 0; + + virtual bool isSameType(const OpRcPtr & op) const = 0; + + virtual bool isInverse(const OpRcPtr & op) const = 0; + + virtual bool canCombineWith(const OpRcPtr & op) const; + + // Return a vector of result ops, which correspond to + // THIS combinedWith secondOp. + // + // If the result is a noOp, it is valid for the resulting opsVec + // to be empty. + + virtual void combineWith(OpRcPtrVec & ops, const OpRcPtr & secondOp) const; + + virtual bool hasChannelCrosstalk() const = 0; + + virtual void dumpMetadata(ProcessorMetadataRcPtr & /*metadata*/) const + { } + + // This is called a single time after construction. + // Final pre-processing and safety checks should happen here, + // rather than in the constructor. + + virtual void finalize() = 0; + + // Render the specified pixels. + // + // This must be safe to call in a multi-threaded context. + // Ops that have mutable data internally, or rely on external + // caching, must thus be appropriately mutexed. + + virtual void apply(float* rgbaBuffer, long numPixels) const = 0; + + + //! Does this op support gpu shader text generation + virtual bool supportsGpuShader() const = 0; + + // TODO: If temp variables are ever needed, also pass tempvar prefix. + virtual void writeGpuShader(std::ostream & shader, + const std::string & pixelName, + const GpuShaderDesc & shaderDesc) const = 0; + + private: + Op& operator= (const Op &); + }; + + std::ostream& operator<< (std::ostream&, const Op&); +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/OpBuilders.h b/src/core/OpBuilders.h new file mode 100644 index 0000000..f981478 --- /dev/null +++ b/src/core/OpBuilders.h @@ -0,0 +1,125 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_OPBUILDERS_H +#define INCLUDED_OCIO_OPBUILDERS_H + +#include <OpenColorIO/OpenColorIO.h> + +#include "Op.h" +#include "LookParse.h" +#include "PrivateTypes.h" + +OCIO_NAMESPACE_ENTER +{ + void BuildOps(OpRcPtrVec & ops, + const Config & config, + const ConstContextRcPtr & context, + const ConstTransformRcPtr & transform, + TransformDirection dir); + + + //////////////////////////////////////////////////////////////////////// + + void BuildAllocationOps(OpRcPtrVec & ops, + const Config & config, + const AllocationTransform & transform, + TransformDirection dir); + + void BuildCDLOps(OpRcPtrVec & ops, + const Config & config, + const CDLTransform & transform, + TransformDirection dir); + + void BuildColorSpaceOps(OpRcPtrVec & ops, + const Config& config, + const ConstContextRcPtr & context, + const ColorSpaceTransform & transform, + TransformDirection dir); + + void BuildColorSpaceOps(OpRcPtrVec & ops, + const Config & config, + const ConstContextRcPtr & context, + const ConstColorSpaceRcPtr & srcColorSpace, + const ConstColorSpaceRcPtr & dstColorSpace); + + void BuildDisplayOps(OpRcPtrVec & ops, + const Config & config, + const ConstContextRcPtr & context, + const DisplayTransform & transform, + TransformDirection dir); + + void BuildExponentOps(OpRcPtrVec & ops, + const Config& config, + const ExponentTransform & transform, + TransformDirection dir); + + void BuildFileOps(OpRcPtrVec & ops, + const Config& config, + const ConstContextRcPtr & context, + const FileTransform & transform, + TransformDirection dir); + + void BuildGroupOps(OpRcPtrVec & ops, + const Config& config, + const ConstContextRcPtr & context, + const GroupTransform & transform, + TransformDirection dir); + + void BuildLogOps(OpRcPtrVec & ops, + const Config& config, + const LogTransform& transform, + TransformDirection dir); + + void BuildLookOps(OpRcPtrVec & ops, + const Config& config, + const ConstContextRcPtr & context, + const LookTransform & lookTransform, + TransformDirection dir); + + void BuildLookOps(OpRcPtrVec & ops, + ConstColorSpaceRcPtr & currentColorSpace, + bool skipColorSpaceConversions, + const Config& config, + const ConstContextRcPtr & context, + const LookParseResult & looks); + + void BuildMatrixOps(OpRcPtrVec & ops, + const Config& config, + const MatrixTransform & transform, + TransformDirection dir); + + void BuildTruelightOps(OpRcPtrVec & ops, + const Config & config, + const TruelightTransform & transform, + TransformDirection dir); +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/OpOptimizers.cpp b/src/core/OpOptimizers.cpp new file mode 100644 index 0000000..6e975eb --- /dev/null +++ b/src/core/OpOptimizers.cpp @@ -0,0 +1,364 @@ +/* +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 "Logging.h" +#include "Op.h" + +#include <iterator> +#include <sstream> + +OCIO_NAMESPACE_ENTER +{ + namespace + { + const int MAX_OPTIMIZATION_PASSES = 8; + + int RemoveNoOps(OpRcPtrVec & opVec) + { + int count = 0; + + OpRcPtrVec::iterator iter = opVec.begin(); + while(iter != opVec.end()) + { + if((*iter)->isNoOp()) + { + iter = opVec.erase(iter); + ++count; + } + else + { + ++iter; + } + } + + return count; + } + + int RemoveInverseOps(OpRcPtrVec & opVec) + { + int count = 0; + int firstindex = 0; // this must be a signed int + + while(firstindex < static_cast<int>(opVec.size()-1)) + { + const OpRcPtr & first = opVec[firstindex]; + const OpRcPtr & second = opVec[firstindex+1]; + + // The common case of inverse ops is to have a deep nesting: + // ..., A, B, B', A', ... + // + // Consider the above, when firstindex reaches B: + // + // | + // ..., A, B, B', A', ... + // + // We will remove B and B'. + // Firstindex remains pointing at the original location: + // + // | + // ..., A, A', ... + // + // We then decrement firstindex by 1, + // to backstep and reconsider the A, A' case: + // + // | <-- firstindex decremented + // ..., A, A', ... + // + + if(first->isSameType(second) && first->isInverse(second)) + { + opVec.erase(opVec.begin() + firstindex, + opVec.begin() + firstindex + 2); + ++count; + + firstindex = std::max(0, firstindex-1); + } + else + { + ++firstindex; + } + } + + return count; + } + + int CombineOps(OpRcPtrVec & opVec) + { + int count = 0; + int firstindex = 0; // this must be a signed int + + OpRcPtrVec tmpops; + + while(firstindex < static_cast<int>(opVec.size()-1)) + { + const OpRcPtr & first = opVec[firstindex]; + const OpRcPtr & second = opVec[firstindex+1]; + + if(first->canCombineWith(second)) + { + tmpops.clear(); + first->combineWith(tmpops, second); + + // tmpops may have any number of ops in it. (0, 1, 2, ...) + // (size 0 would occur potentially iff the combination + // results in a no-op) + // + // No matter the number, we need to swap them in for the + // original ops + + // Erase the initial two ops we've combined + opVec.erase(opVec.begin() + firstindex, + opVec.begin() + firstindex + 2); + + // Insert the new ops (which may be empty) at + // this location + std::copy(tmpops.begin(), tmpops.end(), + std::inserter(opVec, opVec.begin() + firstindex)); + + // Decrement firstindex by 1, + // to backstep and reconsider the A, A' case. + // See RemoveInverseOps for the full discussion of + // why this is appropriate + firstindex = std::max(0, firstindex-1); + + // We've done something so increment the count! + ++count; + } + else + { + ++firstindex; + } + } + + return count; + } + } + + void OptimizeOpVec(OpRcPtrVec & ops) + { + if(ops.empty()) return; + + + if(IsDebugLoggingEnabled()) + { + LogDebug("Optimizing Op Vec..."); + LogDebug(SerializeOpVec(ops, 4)); + } + + OpRcPtrVec::size_type originalSize = ops.size(); + int total_noops = 0; + int total_inverseops = 0; + int total_combines = 0; + int passes = 0; + + while(passes<=MAX_OPTIMIZATION_PASSES) + { + int noops = RemoveNoOps(ops); + int inverseops = RemoveInverseOps(ops); + int combines = CombineOps(ops); + + if(noops == 0 && inverseops==0 && combines==0) + { + // No optimization progress was made, so stop trying. + break; + } + + total_noops += noops; + total_inverseops += inverseops; + total_combines += combines; + + ++passes; + } + + OpRcPtrVec::size_type finalSize = ops.size(); + + if(passes == MAX_OPTIMIZATION_PASSES) + { + std::ostringstream os; + os << "The max number of passes, " << passes << ", "; + os << "was reached during optimization. This is likely a sign "; + os << "that either the complexity of the color transform is "; + os << "very high, or that some internal optimizers are in conflict "; + os << "(undo-ing / redo-ing the other's results)."; + LogDebug(os.str().c_str()); + } + + if(IsDebugLoggingEnabled()) + { + std::ostringstream os; + os << "Optimized "; + os << originalSize << "->" << finalSize << ", "; + os << passes << " passes, "; + os << total_noops << " noops removed, "; + os << total_inverseops << " inverse ops removed\n"; + os << total_combines << " ops combines\n"; + os << SerializeOpVec(ops, 4); + LogDebug(os.str()); + } + } +} +OCIO_NAMESPACE_EXIT + + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef OCIO_UNIT_TEST + +namespace OCIO = OCIO_NAMESPACE; +#include "UnitTest.h" + +#include "ExponentOps.h" +#include "LogOps.h" +#include "Lut1DOp.h" +#include "Lut3DOp.h" +#include "MatrixOps.h" + +OIIO_ADD_TEST(OpOptimizers, RemoveInverseOps) +{ + OCIO::OpRcPtrVec ops; + + float exp[4] = { 1.2f, 1.3f, 1.4f, 1.5f }; + + + float k[3] = { 0.18f, 0.18f, 0.18f }; + float m[3] = { 2.0f, 2.0f, 2.0f }; + float b[3] = { 0.1f, 0.1f, 0.1f }; + float base[3] = { 10.0f, 10.0f, 10.0f }; + float kb[3] = { 1.0f, 1.0f, 1.0f }; + + OCIO::CreateExponentOp(ops, exp, OCIO::TRANSFORM_DIR_FORWARD); + OCIO::CreateLogOp(ops, k, m, b, base, kb, OCIO::TRANSFORM_DIR_FORWARD); + OCIO::CreateLogOp(ops, k, m, b, base, kb, OCIO::TRANSFORM_DIR_INVERSE); + OCIO::CreateExponentOp(ops, exp, OCIO::TRANSFORM_DIR_INVERSE); + + OIIO_CHECK_EQUAL(ops.size(), 4); + OCIO::RemoveInverseOps(ops); + OIIO_CHECK_EQUAL(ops.size(), 0); + + + ops.clear(); + OCIO::CreateExponentOp(ops, exp, OCIO::TRANSFORM_DIR_FORWARD); + OCIO::CreateExponentOp(ops, exp, OCIO::TRANSFORM_DIR_INVERSE); + OCIO::CreateLogOp(ops, k, m, b, base, kb, OCIO::TRANSFORM_DIR_INVERSE); + OCIO::CreateLogOp(ops, k, m, b, base, kb, OCIO::TRANSFORM_DIR_FORWARD); + OCIO::CreateExponentOp(ops, exp, OCIO::TRANSFORM_DIR_FORWARD); + + OIIO_CHECK_EQUAL(ops.size(), 5); + OCIO::RemoveInverseOps(ops); + OIIO_CHECK_EQUAL(ops.size(), 1); +} + + +OIIO_ADD_TEST(OpOptimizers, CombineOps) +{ + float m1[4] = { 2.0f, 2.0f, 2.0f, 1.0f }; + float m2[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; + float m3[4] = { 0.6f, 0.6f, 0.6f, 1.0f }; + float m4[4] = { 0.7f, 0.7f, 0.7f, 1.0f }; + + float exp[4] = { 1.2f, 1.3f, 1.4f, 1.5f }; + + { + OCIO::OpRcPtrVec ops; + OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD); + + OIIO_CHECK_EQUAL(ops.size(), 1); + OCIO::CombineOps(ops); + OIIO_CHECK_EQUAL(ops.size(), 1); + } + + { + OCIO::OpRcPtrVec ops; + OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD); + OCIO::CreateScaleOp(ops, m3, OCIO::TRANSFORM_DIR_FORWARD); + + OIIO_CHECK_EQUAL(ops.size(), 2); + OCIO::CombineOps(ops); + OIIO_CHECK_EQUAL(ops.size(), 1); + } + + { + OCIO::OpRcPtrVec ops; + OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD); + OCIO::CreateScaleOp(ops, m3, OCIO::TRANSFORM_DIR_FORWARD); + OCIO::CreateScaleOp(ops, m4, OCIO::TRANSFORM_DIR_FORWARD); + + OIIO_CHECK_EQUAL(ops.size(), 3); + OCIO::CombineOps(ops); + OIIO_CHECK_EQUAL(ops.size(), 1); + } + + { + OCIO::OpRcPtrVec ops; + OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD); + OCIO::CreateScaleOp(ops, m2, OCIO::TRANSFORM_DIR_FORWARD); + + OIIO_CHECK_EQUAL(ops.size(), 2); + OCIO::CombineOps(ops); + OIIO_CHECK_EQUAL(ops.size(), 0); + } + + { + OCIO::OpRcPtrVec ops; + OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD); + OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_INVERSE); + + OIIO_CHECK_EQUAL(ops.size(), 2); + OCIO::CombineOps(ops); + OIIO_CHECK_EQUAL(ops.size(), 0); + } + + { + OCIO::OpRcPtrVec ops; + OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD); + OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD); + OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD); + OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD); + OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD); + + OIIO_CHECK_EQUAL(ops.size(), 5); + OCIO::CombineOps(ops); + OIIO_CHECK_EQUAL(ops.size(), 1); + } + + { + OCIO::OpRcPtrVec ops; + OCIO::CreateExponentOp(ops, exp, OCIO::TRANSFORM_DIR_FORWARD); + OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD); + OCIO::CreateScaleOp(ops, m2, OCIO::TRANSFORM_DIR_FORWARD); + OCIO::CreateExponentOp(ops, exp, OCIO::TRANSFORM_DIR_INVERSE); + + OIIO_CHECK_EQUAL(ops.size(), 4); + OCIO::CombineOps(ops); + OIIO_CHECK_EQUAL(ops.size(), 0); + } +} + +#endif // OCIO_UNIT_TEST diff --git a/src/core/ParseUtils.cpp b/src/core/ParseUtils.cpp new file mode 100644 index 0000000..9110161 --- /dev/null +++ b/src/core/ParseUtils.cpp @@ -0,0 +1,438 @@ +/* +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 <iostream> +#include <set> +#include <sstream> + +#include <OpenColorIO/OpenColorIO.h> + +#include "ParseUtils.h" +#include "pystring/pystring.h" + +OCIO_NAMESPACE_ENTER +{ + const char * BoolToString(bool val) + { + if(val) return "true"; + return "false"; + } + + bool BoolFromString(const char * s) + { + std::string str = pystring::lower(s); + if((str == "true") || (str=="yes")) return true; + return false; + } + + const char * LoggingLevelToString(LoggingLevel level) + { + if(level == LOGGING_LEVEL_NONE) return "none"; + else if(level == LOGGING_LEVEL_WARNING) return "warning"; + else if(level == LOGGING_LEVEL_INFO) return "info"; + else if(level == LOGGING_LEVEL_DEBUG) return "debug"; + return "unknown"; + } + + LoggingLevel LoggingLevelFromString(const char * s) + { + std::string str = pystring::lower(s); + if(str == "0" || str == "none") return LOGGING_LEVEL_NONE; + else if(str == "1" || str == "warning") return LOGGING_LEVEL_WARNING; + else if(str == "2" || str == "info") return LOGGING_LEVEL_INFO; + else if(str == "3" || str == "debug") return LOGGING_LEVEL_DEBUG; + return LOGGING_LEVEL_UNKNOWN; + } + + const char * TransformDirectionToString(TransformDirection dir) + { + if(dir == TRANSFORM_DIR_FORWARD) return "forward"; + else if(dir == TRANSFORM_DIR_INVERSE) return "inverse"; + return "unknown"; + } + + TransformDirection TransformDirectionFromString(const char * s) + { + std::string str = pystring::lower(s); + if(str == "forward") return TRANSFORM_DIR_FORWARD; + else if(str == "inverse") return TRANSFORM_DIR_INVERSE; + return TRANSFORM_DIR_UNKNOWN; + } + + TransformDirection CombineTransformDirections(TransformDirection d1, + TransformDirection d2) + { + // Any unknowns always combine to be unknown. + if(d1 == TRANSFORM_DIR_UNKNOWN || d2 == TRANSFORM_DIR_UNKNOWN) + return TRANSFORM_DIR_UNKNOWN; + + if(d1 == TRANSFORM_DIR_FORWARD && d2 == TRANSFORM_DIR_FORWARD) + return TRANSFORM_DIR_FORWARD; + + if(d1 == TRANSFORM_DIR_INVERSE && d2 == TRANSFORM_DIR_INVERSE) + return TRANSFORM_DIR_FORWARD; + + return TRANSFORM_DIR_INVERSE; + } + + TransformDirection GetInverseTransformDirection(TransformDirection dir) + { + if(dir == TRANSFORM_DIR_FORWARD) return TRANSFORM_DIR_INVERSE; + else if(dir == TRANSFORM_DIR_INVERSE) return TRANSFORM_DIR_FORWARD; + return TRANSFORM_DIR_UNKNOWN; + } + + const char * ColorSpaceDirectionToString(ColorSpaceDirection dir) + { + if(dir == COLORSPACE_DIR_TO_REFERENCE) return "to_reference"; + else if(dir == COLORSPACE_DIR_FROM_REFERENCE) return "from_reference"; + return "unknown"; + } + + ColorSpaceDirection ColorSpaceDirectionFromString(const char * s) + { + std::string str = pystring::lower(s); + if(str == "to_reference") return COLORSPACE_DIR_TO_REFERENCE; + else if(str == "from_reference") return COLORSPACE_DIR_FROM_REFERENCE; + return COLORSPACE_DIR_UNKNOWN; + } + + const char * BitDepthToString(BitDepth bitDepth) + { + if(bitDepth == BIT_DEPTH_UINT8) return "8ui"; + else if(bitDepth == BIT_DEPTH_UINT10) return "10ui"; + else if(bitDepth == BIT_DEPTH_UINT12) return "12ui"; + else if(bitDepth == BIT_DEPTH_UINT14) return "14ui"; + else if(bitDepth == BIT_DEPTH_UINT16) return "16ui"; + else if(bitDepth == BIT_DEPTH_UINT32) return "32ui"; + else if(bitDepth == BIT_DEPTH_F16) return "16f"; + else if(bitDepth == BIT_DEPTH_F32) return "32f"; + return "unknown"; + } + + BitDepth BitDepthFromString(const char * s) + { + std::string str = pystring::lower(s); + if(str == "8ui") return BIT_DEPTH_UINT8; + else if(str == "10ui") return BIT_DEPTH_UINT10; + else if(str == "12ui") return BIT_DEPTH_UINT12; + else if(str == "14ui") return BIT_DEPTH_UINT14; + else if(str == "16ui") return BIT_DEPTH_UINT16; + else if(str == "32ui") return BIT_DEPTH_UINT32; + else if(str == "16f") return BIT_DEPTH_F16; + else if(str == "32f") return BIT_DEPTH_F32; + return BIT_DEPTH_UNKNOWN; + } + + bool BitDepthIsFloat(BitDepth bitDepth) + { + if(bitDepth == BIT_DEPTH_F16) return true; + else if(bitDepth == BIT_DEPTH_F32) return true; + return false; + } + + int BitDepthToInt(BitDepth bitDepth) + { + if(bitDepth == BIT_DEPTH_UINT8) return 8; + else if(bitDepth == BIT_DEPTH_UINT10) return 10; + else if(bitDepth == BIT_DEPTH_UINT12) return 12; + else if(bitDepth == BIT_DEPTH_UINT14) return 14; + else if(bitDepth == BIT_DEPTH_UINT16) return 16; + else if(bitDepth == BIT_DEPTH_UINT32) return 32; + + return 0; + } + + const char * AllocationToString(Allocation alloc) + { + if(alloc == ALLOCATION_UNIFORM) return "uniform"; + else if(alloc == ALLOCATION_LG2) return "lg2"; + return "unknown"; + } + + Allocation AllocationFromString(const char * s) + { + std::string str = pystring::lower(s); + if(str == "uniform") return ALLOCATION_UNIFORM; + else if(str == "lg2") return ALLOCATION_LG2; + return ALLOCATION_UNKNOWN; + } + + const char * InterpolationToString(Interpolation interp) + { + if(interp == INTERP_NEAREST) return "nearest"; + else if(interp == INTERP_LINEAR) return "linear"; + else if(interp == INTERP_TETRAHEDRAL) return "tetrahedral"; + else if(interp == INTERP_BEST) return "best"; + return "unknown"; + } + + Interpolation InterpolationFromString(const char * s) + { + std::string str = pystring::lower(s); + if(str == "nearest") return INTERP_NEAREST; + else if(str == "linear") return INTERP_LINEAR; + else if(str == "tetrahedral") return INTERP_TETRAHEDRAL; + else if(str == "best") return INTERP_BEST; + return INTERP_UNKNOWN; + } + + const char * GpuLanguageToString(GpuLanguage language) + { + if(language == GPU_LANGUAGE_CG) return "cg"; + else if(language == GPU_LANGUAGE_GLSL_1_0) return "glsl_1.0"; + else if(language == GPU_LANGUAGE_GLSL_1_3) return "glsl_1.3"; + return "unknown"; + } + + GpuLanguage GpuLanguageFromString(const char * s) + { + std::string str = pystring::lower(s); + if(str == "cg") return GPU_LANGUAGE_CG; + else if(str == "glsl_1.0") return GPU_LANGUAGE_GLSL_1_0; + else if(str == "glsl_1.3") return GPU_LANGUAGE_GLSL_1_3; + return GPU_LANGUAGE_UNKNOWN; + } + + + const char * ROLE_DEFAULT = "default"; + const char * ROLE_REFERENCE = "reference"; + const char * ROLE_DATA = "data"; + const char * ROLE_COLOR_PICKING = "color_picking"; + const char * ROLE_SCENE_LINEAR = "scene_linear"; + const char * ROLE_COMPOSITING_LOG = "compositing_log"; + const char * ROLE_COLOR_TIMING = "color_timing"; + const char * ROLE_TEXTURE_PAINT = "texture_paint"; + const char * ROLE_MATTE_PAINT = "matte_paint"; + + namespace + { + const int FLOAT_DECIMALS = 7; + const int DOUBLE_DECIMALS = 16; + } + + std::string FloatToString(float value) + { + std::ostringstream pretty; + pretty.precision(FLOAT_DECIMALS); + pretty << value; + return pretty.str(); + } + + std::string FloatVecToString(const float * fval, unsigned int size) + { + if(size<=0) return ""; + + std::ostringstream pretty; + pretty.precision(FLOAT_DECIMALS); + for(unsigned int i=0; i<size; ++i) + { + if(i!=0) pretty << " "; + pretty << fval[i]; + } + + return pretty.str(); + } + + bool StringToFloat(float * fval, const char * str) + { + if(!str) return false; + + std::istringstream inputStringstream(str); + float x; + if(!(inputStringstream >> x)) + { + return false; + } + + if(fval) *fval = x; + return true; + } + + bool StringToInt(int * ival, const char * str) + { + if(!str) return false; + + std::istringstream inputStringstream(str); + int x; + if(!(inputStringstream >> x)) + { + return false; + } + + if(ival) *ival = x; + return true; + } + + + std::string DoubleToString(double value) + { + std::ostringstream pretty; + pretty.precision(DOUBLE_DECIMALS); + pretty << value; + return pretty.str(); + } + + + bool StringVecToFloatVec(std::vector<float> &floatArray, + const std::vector<std::string> &lineParts) + { + floatArray.resize(lineParts.size()); + + for(unsigned int i=0; i<lineParts.size(); i++) + { + std::istringstream inputStringstream(lineParts[i]); + float x; + if(!(inputStringstream >> x)) + { + return false; + } + floatArray[i] = x; + } + + return true; + } + + + bool StringVecToIntVec(std::vector<int> &intArray, + const std::vector<std::string> &lineParts) + { + intArray.resize(lineParts.size()); + + for(unsigned int i=0; i<lineParts.size(); i++) + { + std::istringstream inputStringstream(lineParts[i]); + int x; + if(!(inputStringstream >> x)) + { + return false; + } + intArray[i] = x; + } + + return true; + } + + //////////////////////////////////////////////////////////////////////////// + + // read the next non empty line, and store it in 'line' + // return 'true' on success + + bool nextline(std::istream &istream, std::string &line) + { + while ( istream.good() ) + { + std::getline(istream, line); + if(!pystring::strip(line).empty()) + { + return true; + } + } + + line = ""; + return false; + } + + + bool StrEqualsCaseIgnore(const std::string & a, const std::string & b) + { + return (pystring::lower(a) == pystring::lower(b)); + } + + // If a ',' is in the string, split on it + // If a ':' is in the string, split on it + // Otherwise, assume a single string. + // Also, strip whitespace from all parts. + + void SplitStringEnvStyle(std::vector<std::string> & outputvec, const char * str) + { + if(!str) return; + + std::string s = pystring::strip(str); + if(pystring::find(s, ",") > -1) + { + pystring::split(s, outputvec, ","); + } + else if(pystring::find(s, ":") > -1) + { + pystring::split(s, outputvec, ":"); + } + else + { + outputvec.push_back(s); + } + + for(unsigned int i=0; i<outputvec.size(); ++i) + { + outputvec[i] = pystring::strip(outputvec[i]); + } + } + + std::string JoinStringEnvStyle(const std::vector<std::string> & outputvec) + { + return pystring::join(", ", outputvec); + } + + // Ordering and capitalization from vec1 is preserved + std::vector<std::string> IntersectStringVecsCaseIgnore(const std::vector<std::string> & vec1, + const std::vector<std::string> & vec2) + { + std::vector<std::string> newvec; + std::set<std::string> allvalues; + + // Seed the set with all values from vec2 + for(unsigned int i=0; i<vec2.size(); ++i) + { + allvalues.insert(pystring::lower(vec2[i])); + } + + for(unsigned int i=0; i<vec1.size(); ++i) + { + std::string key = pystring::lower(vec1[i]); + if(allvalues.find(key) != allvalues.end()) + { + newvec.push_back(vec1[i]); + } + } + + return newvec; + } + + + int FindInStringVecCaseIgnore(const std::vector<std::string> & vec, const std::string & str) + { + std::string teststr = pystring::lower(str); + for(unsigned int i=0; i<vec.size(); ++i) + { + if(pystring::lower(vec[i]) == teststr) return static_cast<int>(i); + } + + return -1; + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/ParseUtils.h b/src/core/ParseUtils.h new file mode 100644 index 0000000..efc07d4 --- /dev/null +++ b/src/core/ParseUtils.h @@ -0,0 +1,89 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_PARSEUTILS_H +#define INCLUDED_OCIO_PARSEUTILS_H + +#include <OpenColorIO/OpenColorIO.h> + +#include "PrivateTypes.h" + +#include <sstream> +#include <string> +#include <vector> + +OCIO_NAMESPACE_ENTER +{ + + std::string FloatToString(float fval); + std::string FloatVecToString(const float * fval, unsigned int size); + + std::string DoubleToString(double value); + + bool StringToFloat(float * fval, const char * str); + bool StringToInt(int * ival, const char * str); + + bool StringVecToFloatVec(std::vector<float> & floatArray, + const std::vector<std::string> & lineParts); + + bool StringVecToIntVec(std::vector<int> & intArray, + const std::vector<std::string> & lineParts); + + ////////////////////////////////////////////////////////////////////////// + + // read the next non empty line, and store it in 'line' + // return 'true' on success + + bool nextline(std::istream &istream, std::string &line); + + bool StrEqualsCaseIgnore(const std::string & a, const std::string & b); + + // If a ',' is in the string, split on it + // If a ':' is in the string, split on it + // Otherwise, assume a single string. + // Also, strip whitespace from all parts. + + void SplitStringEnvStyle(std::vector<std::string> & outputvec, const char * str); + + // Join on ',' + std::string JoinStringEnvStyle(const std::vector<std::string> & outputvec); + + // Ordering and capitalization from vec1 is preserved + std::vector<std::string> IntersectStringVecsCaseIgnore(const std::vector<std::string> & vec1, + const std::vector<std::string> & vec2); + + // Find the index of the specified string, ignoring case. + // return -1 if not found. + + int FindInStringVecCaseIgnore(const std::vector<std::string> & vec, const std::string & str); + +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/PathUtils.cpp b/src/core/PathUtils.cpp new file mode 100644 index 0000000..a04ecee --- /dev/null +++ b/src/core/PathUtils.cpp @@ -0,0 +1,229 @@ +/* +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 <cstdlib> +#include <fstream> +#include <iostream> +#include <limits> +#include <sstream> +#include <sys/stat.h> + +#include <OpenColorIO/OpenColorIO.h> + +#include "Mutex.h" +#include "PathUtils.h" +#include "Platform.h" +#include "pystring/pystring.h" + +#if !defined(WINDOWS) +#include <sys/param.h> +#else +#include <direct.h> +#define MAXPATHLEN 4096 +#endif + +#if defined(__APPLE__) && !defined(__IPHONE__) +#include <crt_externs.h> // _NSGetEnviron() +#elif !defined(WINDOWS) +#include <unistd.h> +extern char **environ; +#endif + +OCIO_NAMESPACE_ENTER +{ + namespace + { + typedef std::map<std::string, std::string> StringMap; + + StringMap g_fastFileHashCache; + Mutex g_fastFileHashCache_mutex; + + std::string ComputeHash(const std::string & filename) + { + struct stat results; + if (stat(filename.c_str(), &results) == 0) + { + // Treat the mtime + inode as a proxy for the contents + std::ostringstream fasthash; + fasthash << results.st_ino << ":"; + fasthash << results.st_mtime; + return fasthash.str(); + } + + return ""; + } + } + + std::string GetFastFileHash(const std::string & filename) + { + AutoMutex lock(g_fastFileHashCache_mutex); + + StringMap::iterator iter = g_fastFileHashCache.find(filename); + if(iter != g_fastFileHashCache.end()) + { + return iter->second; + } + + std::string hash = ComputeHash(filename); + g_fastFileHashCache[filename] = hash; + + return hash; + } + + bool FileExists(const std::string & filename) + { + std::string hash = GetFastFileHash(filename); + return (!hash.empty()); + } + + void ClearPathCaches() + { + AutoMutex lock(g_fastFileHashCache_mutex); + g_fastFileHashCache.clear(); + } + + namespace pystring + { + namespace os + { + std::string getcwd() + { +#ifdef WINDOWS + char path[MAXPATHLEN]; + _getcwd(path, MAXPATHLEN); + return path; +#else + char path[MAXPATHLEN]; + ::getcwd(path, MAXPATHLEN); + return path; +#endif + } + + namespace path + { + std::string abspath(const std::string & path) + { + std::string p = path; + if(!isabs(p)) p = join(getcwd(), p); + return normpath(p); + } + } // namespace path + } // namespace os + } + + namespace + { + inline char** GetEnviron() + { +#if __IPHONE__ + // TODO: fix this + return NULL; +#elif __APPLE__ + return (*_NSGetEnviron()); +#else + return environ; +#endif + } + + const int MAX_PATH_LENGTH = 4096; + } + + void LoadEnvironment(EnvMap & map) + { + for (char **env = GetEnviron(); *env != NULL; ++env) + { + // split environment up into std::map[name] = value + std::string env_str = (char*)*env; + int pos = static_cast<int>(env_str.find_first_of('=')); + map.insert( + EnvMap::value_type(env_str.substr(0, pos), + env_str.substr(pos+1, env_str.length())) + ); + } + } + + std::string EnvExpand(const std::string & str, const EnvMap & map) + { + // Early exit if no magic characters are found. + if(pystring::find(str, "$") == -1 && + pystring::find(str, "%") == -1) return str; + + std::string orig = str; + std::string newstr = str; + + // This walks through the envmap in key order, + // from longest to shortest to handle envvars which are + // substrings. + // ie. '$TEST_$TESTING_$TE' will expand in this order '2 1 3' + + for (EnvMap::const_iterator iter = map.begin(); + iter != map.end(); ++iter) + { + newstr = pystring::replace(newstr, + ("${"+iter->first+"}"), iter->second); + newstr = pystring::replace(newstr, + ("$"+iter->first), iter->second); + newstr = pystring::replace(newstr, + ("%"+iter->first+"%"), iter->second); + } + + // recursively call till string doesn't expand anymore + if(newstr != orig) + { + return EnvExpand(newstr, map); + } + + return orig; + } +} +OCIO_NAMESPACE_EXIT + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef OCIO_UNIT_TEST + +namespace OCIO = OCIO_NAMESPACE; +#include "UnitTest.h" + +OIIO_ADD_TEST(PathUtils, EnvExpand) +{ + // build env by hand for unit test + OCIO::EnvMap env_map; // = OCIO::GetEnvMap(); + + // add some fake env vars so the test runs + env_map.insert(OCIO::EnvMap::value_type("TEST1", "foo.bar")); + env_map.insert(OCIO::EnvMap::value_type("TEST1NG", "bar.foo")); + env_map.insert(OCIO::EnvMap::value_type("FOO_foo.bar", "cheese")); + + // + std::string foo = "/a/b/${TEST1}/${TEST1NG}/$TEST1/$TEST1NG/${FOO_${TEST1}}/"; + std::string foo_result = "/a/b/foo.bar/bar.foo/foo.bar/bar.foo/cheese/"; + std::string testresult = OCIO::EnvExpand(foo, env_map); + OIIO_CHECK_ASSERT( testresult == foo_result ); +} + +#endif // OCIO_BUILD_TESTS diff --git a/src/core/PathUtils.h b/src/core/PathUtils.h new file mode 100644 index 0000000..649f52c --- /dev/null +++ b/src/core/PathUtils.h @@ -0,0 +1,95 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_PATHUTILS_H +#define INCLUDED_OCIO_PATHUTILS_H + +#include <OpenColorIO/OpenColorIO.h> + +#include <map> + +OCIO_NAMESPACE_ENTER +{ + namespace pystring + { + namespace os + { + namespace path + { + // This is not currently included in pystring, but we need it + // So let's define it locally for now + + std::string abspath(const std::string & path); + } + } + } + + // The EnvMap is ordered by the length of the keys (long -> short). This + // is so that recursive string expansion will deal with similar prefixed + // keys as expected. + // ie. '$TEST_$TESTING_$TE' will expand in this order '2 1 3' + template <class T> + struct EnvMapKey : std::binary_function <T, T, bool> + { + bool + operator() (const T &x, const T &y) const + { + // If the lengths are unequal, sort by length + if(x.length() != y.length()) + { + return (x.length() > y.length()); + } + // Otherwise, use the standard string sort comparison + else + { + return (x<y); + } + } + }; + typedef std::map< std::string, std::string, EnvMapKey< std::string > > EnvMap; + + // Get map of current env key = value, + void LoadEnvironment(EnvMap & map); + + // Expand a string with $VAR, ${VAR} or %VAR% with the keys passed + // in the EnvMap. + std::string EnvExpand(const std::string & str, const EnvMap & map); + + // Check if a file exists + bool FileExists(const std::string & filename); + + // Get a fast hash for a file, without reading all the contents. + // Currently, this checks the mtime and the inode number. + std::string GetFastFileHash(const std::string & filename); + + void ClearPathCaches(); +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/Platform.h b/src/core/Platform.h new file mode 100644 index 0000000..56d0971 --- /dev/null +++ b/src/core/Platform.h @@ -0,0 +1,202 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_PLATFORM_H +#define INCLUDED_OCIO_PLATFORM_H + +/* +PTEX SOFTWARE +Copyright 2009 Disney Enterprises, Inc. 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. + + * The names "Disney", "Walt Disney Pictures", "Walt Disney Animation + Studios" or the names of its contributors may NOT be used to + endorse or promote products derived from this software without + specific prior written permission from Walt Disney Pictures. + +Disclaimer: THIS SOFTWARE IS PROVIDED BY WALT DISNEY PICTURES AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE, NONINFRINGEMENT AND TITLE ARE DISCLAIMED. +IN NO EVENT SHALL WALT DISNEY PICTURES, THE COPYRIGHT HOLDER 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 BASED 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 DAMAGES. +*/ + +// platform-specific includes +#if defined(_WIN32) || defined(_WIN64) || defined(_WINDOWS) || defined(_MSC_VER) +#ifndef WINDOWS +#define WINDOWS +#endif +#define _CRT_NONSTDC_NO_DEPRECATE 1 +#define _CRT_SECURE_NO_DEPRECATE 1 +#define NOMINMAX 1 + +// windows - defined for both Win32 and Win64 +#include <Windows.h> +#include <malloc.h> +#include <io.h> +#include <tchar.h> +#include <process.h> + +#else + +// linux/unix/posix +#include <stdlib.h> +#include <alloca.h> +#include <string.h> +#include <pthread.h> +// OS for spinlock +#ifdef __APPLE__ +#include <libkern/OSAtomic.h> +#include <sys/types.h> +#endif +#endif + +// general includes +#include <stdio.h> +#include <math.h> +#include <assert.h> + +// missing functions on Windows +#ifdef WINDOWS +#define snprintf sprintf_s +#define strtok_r strtok_s +typedef __int64 FilePos; +#define fseeko _fseeki64 +#define ftello _ftelli64 + +inline double log2(double x) { + return log(x) * 1.4426950408889634; +} + +#else +typedef off_t FilePos; +#endif + + +OCIO_NAMESPACE_ENTER +{ + +// TODO: Add proper endian detection using architecture / compiler mojo +// In the meantime, hardcode to x86 +#define OCIO_LITTLE_ENDIAN 1 // This is correct on x86 + + /* + * Mutex/SpinLock classes + */ + +#ifdef WINDOWS + + class _Mutex { + public: + _Mutex() { _mutex = CreateMutex(NULL, FALSE, NULL); } + ~_Mutex() { CloseHandle(_mutex); } + void lock() { WaitForSingleObject(_mutex, INFINITE); } + void unlock() { ReleaseMutex(_mutex); } + private: + HANDLE _mutex; + }; + + class _SpinLock { + public: + _SpinLock() { InitializeCriticalSection(&_spinlock); } + ~_SpinLock() { DeleteCriticalSection(&_spinlock); } + void lock() { EnterCriticalSection(&_spinlock); } + void unlock() { LeaveCriticalSection(&_spinlock); } + private: + CRITICAL_SECTION _spinlock; + }; + +#else + // assume linux/unix/posix + + class _Mutex { + public: + _Mutex() { pthread_mutex_init(&_mutex, 0); } + ~_Mutex() { pthread_mutex_destroy(&_mutex); } + void lock() { pthread_mutex_lock(&_mutex); } + void unlock() { pthread_mutex_unlock(&_mutex); } + private: + pthread_mutex_t _mutex; + }; + +#if __APPLE__ + class _SpinLock { + public: + _SpinLock() { _spinlock = 0; } + ~_SpinLock() { } + void lock() { OSSpinLockLock(&_spinlock); } + void unlock() { OSSpinLockUnlock(&_spinlock); } + private: + OSSpinLock _spinlock; + }; +#elif ANDROID + // we don't have access to pthread on andriod so we just make an empty + // class that does nothing. + class _SpinLock { + public: + _SpinLock() { } + ~_SpinLock() { } + void lock() { } + void unlock() { } + }; +#else + class _SpinLock { + public: + _SpinLock() { pthread_spin_init(&_spinlock, PTHREAD_PROCESS_PRIVATE); } + ~_SpinLock() { pthread_spin_destroy(&_spinlock); } + void lock() { pthread_spin_lock(&_spinlock); } + void unlock() { pthread_spin_unlock(&_spinlock); } + private: + pthread_spinlock_t _spinlock; + }; +#endif // __APPLE__ +#endif // WINDOWS + +} +OCIO_NAMESPACE_EXIT + +#endif // INCLUDED_OCIO_PLATFORM_H diff --git a/src/core/PrivateTypes.h b/src/core/PrivateTypes.h new file mode 100644 index 0000000..4f0b1ee --- /dev/null +++ b/src/core/PrivateTypes.h @@ -0,0 +1,54 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_PRIVATE_TYPES_H +#define INCLUDED_OCIO_PRIVATE_TYPES_H + +#include <OpenColorIO/OpenColorIO.h> + +#include <map> +#include <set> +#include <vector> + +OCIO_NAMESPACE_ENTER +{ + // Stl types of OCIO classes + typedef std::map<std::string, std::string> StringMap; + typedef std::vector<std::string> StringVec; + typedef std::set<std::string> StringSet; + + typedef std::vector<ConstTransformRcPtr> ConstTransformVec; + typedef std::vector<ColorSpaceRcPtr> ColorSpaceVec; + typedef std::vector<LookRcPtr> LookVec; + + typedef std::vector<TransformDirection> TransformDirectionVec; +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/Processor.cpp b/src/core/Processor.cpp new file mode 100644 index 0000000..06b08d2 --- /dev/null +++ b/src/core/Processor.cpp @@ -0,0 +1,640 @@ +/* +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 "AllocationOp.h" +#include "GpuShaderUtils.h" +#include "HashUtils.h" +#include "Logging.h" +#include "Lut3DOp.h" +#include "NoOps.h" +#include "OpBuilders.h" +#include "Processor.h" +#include "ScanlineHelper.h" + +#include <algorithm> +#include <cstring> +#include <sstream> + +OCIO_NAMESPACE_ENTER +{ + + + + ////////////////////////////////////////////////////////////////////////// + + class ProcessorMetadata::Impl + { + public: + StringSet files; + StringVec looks; + + Impl() + { } + + ~Impl() + { } + }; + + ProcessorMetadataRcPtr ProcessorMetadata::Create() + { + return ProcessorMetadataRcPtr(new ProcessorMetadata(), &deleter); + } + + ProcessorMetadata::ProcessorMetadata() + : m_impl(new ProcessorMetadata::Impl) + { } + + ProcessorMetadata::~ProcessorMetadata() + { + delete m_impl; + m_impl = NULL; + } + + void ProcessorMetadata::deleter(ProcessorMetadata* c) + { + delete c; + } + + int ProcessorMetadata::getNumFiles() const + { + return static_cast<int>(getImpl()->files.size()); + } + + const char * ProcessorMetadata::getFile(int index) const + { + if(index < 0 || + index >= (static_cast<int>(getImpl()->files.size()))) + { + return ""; + } + + StringSet::const_iterator iter = getImpl()->files.begin(); + std::advance( iter, index ); + + return iter->c_str(); + } + + void ProcessorMetadata::addFile(const char * fname) + { + getImpl()->files.insert(fname); + } + + + + int ProcessorMetadata::getNumLooks() const + { + return static_cast<int>(getImpl()->looks.size()); + } + + const char * ProcessorMetadata::getLook(int index) const + { + if(index < 0 || + index >= (static_cast<int>(getImpl()->looks.size()))) + { + return ""; + } + + return getImpl()->looks[index].c_str(); + } + + void ProcessorMetadata::addLook(const char * look) + { + getImpl()->looks.push_back(look); + } + + + + ////////////////////////////////////////////////////////////////////////// + + + ProcessorRcPtr Processor::Create() + { + return ProcessorRcPtr(new Processor(), &deleter); + } + + void Processor::deleter(Processor* c) + { + delete c; + } + + Processor::Processor() + : m_impl(new Processor::Impl) + { + } + + Processor::~Processor() + { + delete m_impl; + m_impl = NULL; + } + + bool Processor::isNoOp() const + { + return getImpl()->isNoOp(); + } + + bool Processor::hasChannelCrosstalk() const + { + return getImpl()->hasChannelCrosstalk(); + } + + ConstProcessorMetadataRcPtr Processor::getMetadata() const + { + return getImpl()->getMetadata(); + } + + void Processor::apply(ImageDesc& img) const + { + getImpl()->apply(img); + } + void Processor::applyRGB(float * pixel) const + { + getImpl()->applyRGB(pixel); + } + + void Processor::applyRGBA(float * pixel) const + { + getImpl()->applyRGBA(pixel); + } + + const char * Processor::getCpuCacheID() const + { + return getImpl()->getCpuCacheID(); + } + + const char * Processor::getGpuShaderText(const GpuShaderDesc & shaderDesc) const + { + return getImpl()->getGpuShaderText(shaderDesc); + } + + const char * Processor::getGpuShaderTextCacheID(const GpuShaderDesc & shaderDesc) const + { + return getImpl()->getGpuShaderTextCacheID(shaderDesc); + } + + void Processor::getGpuLut3D(float* lut3d, const GpuShaderDesc & shaderDesc) const + { + return getImpl()->getGpuLut3D(lut3d, shaderDesc); + } + + const char * Processor::getGpuLut3DCacheID(const GpuShaderDesc & shaderDesc) const + { + return getImpl()->getGpuLut3DCacheID(shaderDesc); + } + + + + ////////////////////////////////////////////////////////////////////////// + + + + namespace + { + void WriteShaderHeader(std::ostream & shader, + const std::string & pixelName, + const GpuShaderDesc & shaderDesc) + { + if(!shader) return; + + std::string lut3dName = "lut3d"; + + shader << "\n// Generated by OpenColorIO\n\n"; + + GpuLanguage lang = shaderDesc.getLanguage(); + + std::string fcnName = shaderDesc.getFunctionName(); + + if(lang == GPU_LANGUAGE_CG) + { + shader << "half4 " << fcnName << "(in half4 inPixel," << "\n"; + shader << " const uniform sampler3D " << lut3dName << ") \n"; + } + else if(lang == GPU_LANGUAGE_GLSL_1_0) + { + shader << "vec4 " << fcnName << "(vec4 inPixel, \n"; + shader << " sampler3D " << lut3dName << ") \n"; + } + else if(lang == GPU_LANGUAGE_GLSL_1_3) + { + shader << "vec4 " << fcnName << "(in vec4 inPixel, \n"; + shader << " const sampler3D " << lut3dName << ") \n"; + } + else throw Exception("Unsupported shader language."); + + shader << "{" << "\n"; + + if(lang == GPU_LANGUAGE_CG) + { + shader << "half4 " << pixelName << " = inPixel; \n"; + } + else if(lang == GPU_LANGUAGE_GLSL_1_0 || lang == GPU_LANGUAGE_GLSL_1_3) + { + shader << "vec4 " << pixelName << " = inPixel; \n"; + } + else throw Exception("Unsupported shader language."); + } + + + void WriteShaderFooter(std::ostream & shader, + const std::string & pixelName, + const GpuShaderDesc & /*shaderDesc*/) + { + shader << "return " << pixelName << ";\n"; + shader << "}" << "\n\n"; + } + } + + + ////////////////////////////////////////////////////////////////////////// + + + Processor::Impl::Impl(): + m_metadata(ProcessorMetadata::Create()) + { + } + + Processor::Impl::~Impl() + { } + + bool Processor::Impl::isNoOp() const + { + return IsOpVecNoOp(m_cpuOps); + } + + bool Processor::Impl::hasChannelCrosstalk() const + { + for(OpRcPtrVec::size_type i=0, size = m_cpuOps.size(); i<size; ++i) + { + if(m_cpuOps[i]->hasChannelCrosstalk()) return true; + } + + return false; + } + + ConstProcessorMetadataRcPtr Processor::Impl::getMetadata() const + { + return m_metadata; + } + + void Processor::Impl::apply(ImageDesc& img) const + { + if(m_cpuOps.empty()) return; + + ScanlineHelper scanlineHelper(img); + float * rgbaBuffer = 0; + long numPixels = 0; + + while(true) + { + scanlineHelper.prepRGBAScanline(&rgbaBuffer, &numPixels); + if(numPixels == 0) break; + if(!rgbaBuffer) + throw Exception("Cannot apply transform; null image."); + + for(OpRcPtrVec::size_type i=0, size = m_cpuOps.size(); i<size; ++i) + { + m_cpuOps[i]->apply(rgbaBuffer, numPixels); + } + + scanlineHelper.finishRGBAScanline(); + } + } + + void Processor::Impl::applyRGB(float * pixel) const + { + if(m_cpuOps.empty()) return; + + // We need to allocate a temp array as the pixel must be 4 floats in size + // (otherwise, sse loads will potentially fail) + + float rgbaBuffer[4] = { pixel[0], pixel[1], pixel[2], 0.0f }; + + for(OpRcPtrVec::size_type i=0, size = m_cpuOps.size(); i<size; ++i) + { + m_cpuOps[i]->apply(rgbaBuffer, 1); + } + + pixel[0] = rgbaBuffer[0]; + pixel[1] = rgbaBuffer[1]; + pixel[2] = rgbaBuffer[2]; + } + + void Processor::Impl::applyRGBA(float * pixel) const + { + for(OpRcPtrVec::size_type i=0, size = m_cpuOps.size(); i<size; ++i) + { + m_cpuOps[i]->apply(pixel, 1); + } + } + + const char * Processor::Impl::getCpuCacheID() const + { + AutoMutex lock(m_resultsCacheMutex); + + if(!m_cpuCacheID.empty()) return m_cpuCacheID.c_str(); + + if(m_cpuOps.empty()) + { + m_cpuCacheID = "<NOOP>"; + } + else + { + std::ostringstream cacheid; + for(OpRcPtrVec::size_type i=0, size = m_cpuOps.size(); i<size; ++i) + { + cacheid << m_cpuOps[i]->getCacheID() << " "; + } + std::string fullstr = cacheid.str(); + + m_cpuCacheID = CacheIDHash(fullstr.c_str(), (int)fullstr.size()); + } + + return m_cpuCacheID.c_str(); + } + + + /////////////////////////////////////////////////////////////////////////// + + + + + const char * Processor::Impl::getGpuShaderText(const GpuShaderDesc & shaderDesc) const + { + AutoMutex lock(m_resultsCacheMutex); + + if(m_lastShaderDesc != shaderDesc.getCacheID()) + { + m_lastShaderDesc = shaderDesc.getCacheID(); + m_shader = ""; + m_shaderCacheID = ""; + m_lut3D.clear(); + m_lut3DCacheID = ""; + } + + if(m_shader.empty()) + { + std::ostringstream shader; + calcGpuShaderText(shader, shaderDesc); + m_shader = shader.str(); + + if(IsDebugLoggingEnabled()) + { + LogDebug("GPU Shader"); + LogDebug(m_shader); + } + } + + return m_shader.c_str(); + } + + const char * Processor::Impl::getGpuShaderTextCacheID(const GpuShaderDesc & shaderDesc) const + { + AutoMutex lock(m_resultsCacheMutex); + + if(m_lastShaderDesc != shaderDesc.getCacheID()) + { + m_lastShaderDesc = shaderDesc.getCacheID(); + m_shader = ""; + m_shaderCacheID = ""; + m_lut3D.clear(); + m_lut3DCacheID = ""; + } + + if(m_shader.empty()) + { + std::ostringstream shader; + calcGpuShaderText(shader, shaderDesc); + m_shader = shader.str(); + } + + if(m_shaderCacheID.empty()) + { + m_shaderCacheID = CacheIDHash(m_shader.c_str(), (int)m_shader.size()); + } + + return m_shaderCacheID.c_str(); + } + + + const char * Processor::Impl::getGpuLut3DCacheID(const GpuShaderDesc & shaderDesc) const + { + AutoMutex lock(m_resultsCacheMutex); + + if(m_lastShaderDesc != shaderDesc.getCacheID()) + { + m_lastShaderDesc = shaderDesc.getCacheID(); + m_shader = ""; + m_shaderCacheID = ""; + m_lut3D.clear(); + m_lut3DCacheID = ""; + } + + if(m_lut3DCacheID.empty()) + { + if(m_gpuOpsCpuLatticeProcess.empty()) + { + m_lut3DCacheID = "<NULL>"; + } + else + { + std::ostringstream cacheid; + for(OpRcPtrVec::size_type i=0, size = m_gpuOpsCpuLatticeProcess.size(); i<size; ++i) + { + cacheid << m_gpuOpsCpuLatticeProcess[i]->getCacheID() << " "; + } + // Also, add a hash of the shader description + cacheid << shaderDesc.getCacheID(); + std::string fullstr = cacheid.str(); + m_lut3DCacheID = CacheIDHash(fullstr.c_str(), (int)fullstr.size()); + } + } + + return m_lut3DCacheID.c_str(); + } + + void Processor::Impl::getGpuLut3D(float* lut3d, const GpuShaderDesc & shaderDesc) const + { + if(!lut3d) return; + + AutoMutex lock(m_resultsCacheMutex); + + if(m_lastShaderDesc != shaderDesc.getCacheID()) + { + m_lastShaderDesc = shaderDesc.getCacheID(); + m_shader = ""; + m_shaderCacheID = ""; + m_lut3D.clear(); + m_lut3DCacheID = ""; + } + + int lut3DEdgeLen = shaderDesc.getLut3DEdgeLen(); + int lut3DNumPixels = lut3DEdgeLen*lut3DEdgeLen*lut3DEdgeLen; + + // Can we write the entire shader using only shader text? + // If so, the lut3D is not needed so clear it. + // This is preferable to identity, as it lets people notice if + // it's accidentally being used. + if(m_gpuOpsCpuLatticeProcess.empty()) + { + memset(lut3d, 0, sizeof(float) * 3 * lut3DNumPixels); + return; + } + + if(m_lut3D.empty()) + { + // Allocate 3dlut image, RGBA + m_lut3D.resize(lut3DNumPixels*4); + GenerateIdentityLut3D(&m_lut3D[0], lut3DEdgeLen, 4, LUT3DORDER_FAST_RED); + + // Apply the lattice ops to it + for(int i=0; i<(int)m_gpuOpsCpuLatticeProcess.size(); ++i) + { + m_gpuOpsCpuLatticeProcess[i]->apply(&m_lut3D[0], lut3DNumPixels); + } + + // Convert the RGBA image to an RGB image, in place. + // Of course, this only works because we're doing it from left to right + // so old pixels are read before they're written over + // TODO: is this bad for memory access patterns? + // see if this is faster with a 2nd temp float array + + for(int i=1; i<lut3DNumPixels; ++i) // skip the 1st pixel, it's ok. + { + m_lut3D[3*i+0] = m_lut3D[4*i+0]; + m_lut3D[3*i+1] = m_lut3D[4*i+1]; + m_lut3D[3*i+2] = m_lut3D[4*i+2]; + } + } + + // Copy to the destination + memcpy(lut3d, &m_lut3D[0], sizeof(float) * 3 * lut3DNumPixels); + } + + + + /////////////////////////////////////////////////////////////////////////// + + + + void Processor::Impl::addColorSpaceConversion(const Config & config, + const ConstContextRcPtr & context, + const ConstColorSpaceRcPtr & srcColorSpace, + const ConstColorSpaceRcPtr & dstColorSpace) + { + BuildColorSpaceOps(m_cpuOps, config, context, srcColorSpace, dstColorSpace); + } + + + void Processor::Impl::addTransform(const Config & config, + const ConstContextRcPtr & context, + const ConstTransformRcPtr& transform, + TransformDirection direction) + { + BuildOps(m_cpuOps, config, context, transform, direction); + } + + void Processor::Impl::finalize() + { + // Pull out metadata, before the no-ops are removed. + for(unsigned int i=0; i<m_cpuOps.size(); ++i) + { + m_cpuOps[i]->dumpMetadata(m_metadata); + } + + // GPU Process setup + // + // Partition the original, raw opvec into 3 segments for GPU Processing + // + // Interior index range does not support the gpu shader. + // This is used to bound our analytical shader text generation + // start index and end index are inclusive. + + PartitionGPUOps(m_gpuOpsHwPreProcess, + m_gpuOpsCpuLatticeProcess, + m_gpuOpsHwPostProcess, + m_cpuOps); + + LogDebug("GPU Ops: Pre-3DLUT"); + FinalizeOpVec(m_gpuOpsHwPreProcess); + + LogDebug("GPU Ops: 3DLUT"); + FinalizeOpVec(m_gpuOpsCpuLatticeProcess); + + LogDebug("GPU Ops: Post-3DLUT"); + FinalizeOpVec(m_gpuOpsHwPostProcess); + + LogDebug("CPU Ops"); + FinalizeOpVec(m_cpuOps); + } + + void Processor::Impl::calcGpuShaderText(std::ostream & shader, + const GpuShaderDesc & shaderDesc) const + { + std::string pixelName = "out_pixel"; + std::string lut3dName = "lut3d"; + + WriteShaderHeader(shader, pixelName, shaderDesc); + + + for(unsigned int i=0; i<m_gpuOpsHwPreProcess.size(); ++i) + { + m_gpuOpsHwPreProcess[i]->writeGpuShader(shader, pixelName, shaderDesc); + } + + if(!m_gpuOpsCpuLatticeProcess.empty()) + { + // Sample the 3D LUT. + int lut3DEdgeLen = shaderDesc.getLut3DEdgeLen(); + shader << pixelName << ".rgb = "; + Write_sampleLut3D_rgb(shader, pixelName, + lut3dName, lut3DEdgeLen, + shaderDesc.getLanguage()); + } +#ifdef __APPLE__ + else + { + // Force a no-op sampling of the 3d lut on OSX to work around a segfault. + int lut3DEdgeLen = shaderDesc.getLut3DEdgeLen(); + shader << "// OSX segfault work-around: Force a no-op sampling of the 3d lut.\n"; + Write_sampleLut3D_rgb(shader, pixelName, + lut3dName, lut3DEdgeLen, + shaderDesc.getLanguage()); + } +#endif // __APPLE__ + for(unsigned int i=0; i<m_gpuOpsHwPostProcess.size(); ++i) + { + m_gpuOpsHwPostProcess[i]->writeGpuShader(shader, pixelName, shaderDesc); + } + + WriteShaderFooter(shader, pixelName, shaderDesc); + } + +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/Processor.h b/src/core/Processor.h new file mode 100644 index 0000000..c0bdb51 --- /dev/null +++ b/src/core/Processor.h @@ -0,0 +1,132 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_PROCESSOR_H +#define INCLUDED_OCIO_PROCESSOR_H + +#include <sstream> + +#include <OpenColorIO/OpenColorIO.h> + +#include "Mutex.h" +#include "Op.h" +#include "PrivateTypes.h" + +OCIO_NAMESPACE_ENTER +{ + class Processor::Impl + { + private: + ProcessorMetadataRcPtr m_metadata; + + OpRcPtrVec m_cpuOps; + + // These 3 op vecs represent the 3 stages in our gpu pipe. + // 1) preprocess shader text + // 2) 3d lut process lookup + // 3) postprocess shader text + + OpRcPtrVec m_gpuOpsHwPreProcess; + OpRcPtrVec m_gpuOpsCpuLatticeProcess; + OpRcPtrVec m_gpuOpsHwPostProcess; + + mutable std::string m_cpuCacheID; + + // Cache the last last queried value, + // for the specified shader description + mutable std::string m_lastShaderDesc; + mutable std::string m_shader; + mutable std::string m_shaderCacheID; + mutable std::vector<float> m_lut3D; + mutable std::string m_lut3DCacheID; + + mutable Mutex m_resultsCacheMutex; + + public: + Impl(); + ~Impl(); + + bool isNoOp() const; + bool hasChannelCrosstalk() const; + + ConstProcessorMetadataRcPtr getMetadata() const; + + void apply(ImageDesc& img) const; + + void applyRGB(float * pixel) const; + void applyRGBA(float * pixel) const; + const char * getCpuCacheID() const; + + const char * getGpuShaderText(const GpuShaderDesc & gpuDesc) const; + const char * getGpuShaderTextCacheID(const GpuShaderDesc & shaderDesc) const; + + void getGpuLut3D(float* lut3d, const GpuShaderDesc & shaderDesc) const; + const char * getGpuLut3DCacheID(const GpuShaderDesc & shaderDesc) const; + + //////////////////////////////////////////// + // + // Builder functions, Not exposed + + void addColorSpaceConversion(const Config & config, + const ConstContextRcPtr & context, + const ConstColorSpaceRcPtr & srcColorSpace, + const ConstColorSpaceRcPtr & dstColorSpace); + + void addTransform(const Config & config, + const ConstContextRcPtr & context, + const ConstTransformRcPtr& transform, + TransformDirection direction); + + void finalize(); + + void calcGpuShaderText(std::ostream & shader, + const GpuShaderDesc & shaderDesc) const; + + }; + + // TODO: Move these! + // TODO: Its not ideal that buildops requires a config to be passed around + // but the only alternative is to make build ops a function on it? + // and even if it were, what about the build calls it dispatches to... + + // TODO: all of the build op functions shouldnt take a LocalProcessor class + // Instead, they should take an abstract interface class that defines + // registerOp(OpRcPtr op), annotateColorSpace, finalizeOps, etc. + // of which LocalProcessor happens to be one example. + // Then the only location in the codebase that knows of LocalProcessor is + // in Config.cpp, which creates one. + + void BuildOps(OpRcPtrVec & ops, + const Config & config, + const ConstTransformRcPtr & transform, + TransformDirection dir); +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/SSE.h b/src/core/SSE.h new file mode 100644 index 0000000..5b825e2 --- /dev/null +++ b/src/core/SSE.h @@ -0,0 +1,37 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_SSE_H +#define INCLUDED_OCIO_SSE_H + +#ifdef USE_SSE +#include <xmmintrin.h> +#endif + +#endif diff --git a/src/core/ScanlineHelper.cpp b/src/core/ScanlineHelper.cpp new file mode 100644 index 0000000..b090ae7 --- /dev/null +++ b/src/core/ScanlineHelper.cpp @@ -0,0 +1,123 @@ +/* +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 "ScanlineHelper.h" + +#include <cassert> +#include <cstdlib> +#include <sstream> + +OCIO_NAMESPACE_ENTER +{ + namespace + { + const int PIXELS_PER_LINE = 4096; + } + + //////////////////////////////////////////////////////////////////////////// + + ScanlineHelper::ScanlineHelper(ImageDesc& img): + m_buffer(0), + m_imagePixelIndex(0), + m_numPixelsCopied(0), + m_yIndex(0), + m_inPlaceMode(false) + { + m_img.init(img); + + if(m_img.isPackedRGBA()) + { + m_inPlaceMode = true; + } + else + { + // TODO: Re-use memory from thread-safe memory pool, rather + // than doing a new allocation each time. + + m_buffer = (float*)malloc(sizeof(float)*PIXELS_PER_LINE*4); + } + } + + ScanlineHelper::~ScanlineHelper() + { + free(m_buffer); + } + + // Copy from the src image to our scanline, in our preferred + // pixel layout. + + void ScanlineHelper::prepRGBAScanline(float** buffer, long* numPixels) + { + if(m_inPlaceMode) + { + // TODO: what if scanline is too short, or too long? + if(m_yIndex >= m_img.height) + { + *numPixels = 0; + return; + } + + char* rowPtr = reinterpret_cast<char*>(m_img.rData); + rowPtr += m_img.yStrideBytes*m_yIndex; + + *buffer = reinterpret_cast<float*>(rowPtr); + *numPixels = m_img.width; + } + else + { + PackRGBAFromImageDesc(m_img, m_buffer, + &m_numPixelsCopied, + PIXELS_PER_LINE, + m_imagePixelIndex); + *buffer = m_buffer; + *numPixels = m_numPixelsCopied; + } + } + + // Write back the result of our work, from the scanline to our + // destination image. + + void ScanlineHelper::finishRGBAScanline() + { + if(m_inPlaceMode) + { + m_yIndex += 1; + } + else + { + UnpackRGBAToImageDesc(m_img, + m_buffer, + m_numPixelsCopied, + m_imagePixelIndex); + m_imagePixelIndex += m_numPixelsCopied; + } + } + +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/ScanlineHelper.h b/src/core/ScanlineHelper.h new file mode 100644 index 0000000..016f1d9 --- /dev/null +++ b/src/core/ScanlineHelper.h @@ -0,0 +1,78 @@ +/* +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. +*/ + + +#ifndef INCLUDED_OCIO_SCANLINEHELPER_H +#define INCLUDED_OCIO_SCANLINEHELPER_H + +#include <OpenColorIO/OpenColorIO.h> + +#include "ImagePacking.h" + +OCIO_NAMESPACE_ENTER +{ + + class ScanlineHelper + { + public: + + ScanlineHelper(ImageDesc& img); + + ~ScanlineHelper(); + + // Copy from the src image to our scanline, in our preferred + // pixel layout. Return the number of pixels to process; + + void prepRGBAScanline(float** buffer, long* numPixels); + + // Write back the result of our work, from the scanline to our + // destination image. + + void finishRGBAScanline(); + + private: + GenericImageDesc m_img; + + // Copy mode + float* m_buffer; + long m_imagePixelIndex; + int m_numPixelsCopied; + + // In place mode + int m_yIndex; + + bool m_inPlaceMode; + + ScanlineHelper(const ScanlineHelper &); + ScanlineHelper& operator= (const ScanlineHelper &); + }; + +} +OCIO_NAMESPACE_EXIT + +#endif diff --git a/src/core/Transform.cpp b/src/core/Transform.cpp new file mode 100644 index 0000000..2f4bf7f --- /dev/null +++ b/src/core/Transform.cpp @@ -0,0 +1,175 @@ +/* +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 "OpBuilders.h" +#include "Processor.h" + +#include <sstream> + +OCIO_NAMESPACE_ENTER +{ + Transform::~Transform() + { } + + + void BuildOps(OpRcPtrVec & ops, + const Config & config, + const ConstContextRcPtr & context, + const ConstTransformRcPtr & transform, + TransformDirection dir) + { + // A null transform is valid, and corresponds to a no-op. + if(!transform) + return; + + if(ConstAllocationTransformRcPtr allocationTransform = \ + DynamicPtrCast<const AllocationTransform>(transform)) + { + BuildAllocationOps(ops, config, *allocationTransform, dir); + } + else if(ConstCDLTransformRcPtr cdlTransform = \ + DynamicPtrCast<const CDLTransform>(transform)) + { + BuildCDLOps(ops, config, *cdlTransform, dir); + } + else if(ConstColorSpaceTransformRcPtr colorSpaceTransform = \ + DynamicPtrCast<const ColorSpaceTransform>(transform)) + { + BuildColorSpaceOps(ops, config, context, *colorSpaceTransform, dir); + } + else if(ConstDisplayTransformRcPtr displayTransform = \ + DynamicPtrCast<const DisplayTransform>(transform)) + { + BuildDisplayOps(ops, config, context, *displayTransform, dir); + } + else if(ConstExponentTransformRcPtr exponentTransform = \ + DynamicPtrCast<const ExponentTransform>(transform)) + { + BuildExponentOps(ops, config, *exponentTransform, dir); + } + else if(ConstFileTransformRcPtr fileTransform = \ + DynamicPtrCast<const FileTransform>(transform)) + { + BuildFileOps(ops, config, context, *fileTransform, dir); + } + else if(ConstGroupTransformRcPtr groupTransform = \ + DynamicPtrCast<const GroupTransform>(transform)) + { + BuildGroupOps(ops, config, context, *groupTransform, dir); + } + else if(ConstLogTransformRcPtr logTransform = \ + DynamicPtrCast<const LogTransform>(transform)) + { + BuildLogOps(ops, config, *logTransform, dir); + } + else if(ConstLookTransformRcPtr lookTransform = \ + DynamicPtrCast<const LookTransform>(transform)) + { + BuildLookOps(ops, config, context, *lookTransform, dir); + } + else if(ConstMatrixTransformRcPtr matrixTransform = \ + DynamicPtrCast<const MatrixTransform>(transform)) + { + BuildMatrixOps(ops, config, *matrixTransform, dir); + } + else if(ConstTruelightTransformRcPtr truelightTransform = \ + DynamicPtrCast<const TruelightTransform>(transform)) + { + BuildTruelightOps(ops, config, *truelightTransform, dir); + } + else + { + std::ostringstream os; + os << "Unknown transform type for Op Creation."; + throw Exception(os.str().c_str()); + } + } + + std::ostream& operator<< (std::ostream & os, const Transform & transform) + { + const Transform* t = &transform; + + if(const AllocationTransform * allocationTransform = \ + dynamic_cast<const AllocationTransform*>(t)) + { + os << *allocationTransform; + } + else if(const CDLTransform * cdlTransform = \ + dynamic_cast<const CDLTransform*>(t)) + { + os << *cdlTransform; + } + else if(const ColorSpaceTransform * colorSpaceTransform = \ + dynamic_cast<const ColorSpaceTransform*>(t)) + { + os << *colorSpaceTransform; + } + else if(const DisplayTransform * displayTransform = \ + dynamic_cast<const DisplayTransform*>(t)) + { + os << *displayTransform; + } + else if(const ExponentTransform * exponentTransform = \ + dynamic_cast<const ExponentTransform*>(t)) + { + os << *exponentTransform; + } + else if(const FileTransform * fileTransform = \ + dynamic_cast<const FileTransform*>(t)) + { + os << *fileTransform; + } + else if(const GroupTransform * groupTransform = \ + dynamic_cast<const GroupTransform*>(t)) + { + os << *groupTransform; + } + else if(const MatrixTransform * matrixTransform = \ + dynamic_cast<const MatrixTransform*>(t)) + { + os << *matrixTransform; + } + else if(const TruelightTransform * truelightTransform = \ + dynamic_cast<const TruelightTransform*>(t)) + { + os << *truelightTransform; + } + else + { + std::ostringstream error; + os << "Unknown transform type for serialization."; + throw Exception(error.str().c_str()); + } + + return os; + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/TruelightOp.cpp b/src/core/TruelightOp.cpp new file mode 100644 index 0000000..50f3915 --- /dev/null +++ b/src/core/TruelightOp.cpp @@ -0,0 +1,395 @@ +/* +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 <iostream> + +#ifdef OCIO_TRUELIGHT_SUPPORT +#include <truelight.h> +#else +#define TL_INPUT_LOG 0 +#define TL_INPUT_LIN 1 +#define TL_INPUT_VID 2 +#endif // OCIO_TRUELIGHT_SUPPORT + +#include <OpenColorIO/OpenColorIO.h> + +#include "TruelightOp.h" +#include "pystring/pystring.h" + +OCIO_NAMESPACE_ENTER +{ + namespace + { + class TruelightOp : public Op + { + public: + TruelightOp(const char * configroot, + const char * profile, + const char * camera, + const char * inputdisplay, + const char * recorder, + const char * print, + const char * lamp, + const char * outputcamera, + const char * display, + const char * cubeinput, + TransformDirection direction); + virtual ~TruelightOp(); + + virtual OpRcPtr clone() const; + + virtual std::string getInfo() const; + virtual std::string getCacheID() const; + + virtual bool isNoOp() const; + virtual bool isSameType(const OpRcPtr & op) const; + virtual bool isInverse(const OpRcPtr & op) const; + virtual bool hasChannelCrosstalk() const; + + virtual void finalize(); + virtual void apply(float* rgbaBuffer, long numPixels) const; + + virtual bool supportsGpuShader() const; + virtual void writeGpuShader(std::ostream & shader, + const std::string & pixelName, + const GpuShaderDesc & shaderDesc) const; + + private: + TransformDirection m_direction; + void *m_truelight; + std::string m_configroot; + std::string m_profile; + std::string m_camera; + std::string m_inputdisplay; + std::string m_recorder; + std::string m_print; + std::string m_lamp; + std::string m_outputcamera; + std::string m_display; + int m_cubeinput; + std::string m_cacheID; + }; + + TruelightOp::TruelightOp(const char * configroot, + const char * profile, + const char * camera, + const char * inputdisplay, + const char * recorder, + const char * print, + const char * lamp, + const char * outputcamera, + const char * display, + const char * cubeinput, + TransformDirection direction): + Op(), + m_direction(direction), + m_configroot(configroot), + m_profile(profile), + m_camera(camera), + m_inputdisplay(inputdisplay), + m_recorder(recorder), + m_print(print), + m_lamp(lamp), + m_outputcamera(outputcamera), + m_display(display) + { + + if(m_direction == TRANSFORM_DIR_UNKNOWN) + { + throw Exception("Cannot apply TruelightOp op, unspecified transform direction."); + } + + std::string _tmp = pystring::lower(cubeinput); + if(_tmp == "log") m_cubeinput = TL_INPUT_LOG; + else if(_tmp == "linear") m_cubeinput = TL_INPUT_LIN; + else if(_tmp == "video") m_cubeinput = TL_INPUT_VID; + else + { + std::ostringstream err; + err << "we don't support cubeinput of type " << cubeinput; + err << " try log, linear or video."; + throw Exception(err.str().c_str()); + } + +#ifdef OCIO_TRUELIGHT_SUPPORT + + if((TruelightBegin("")) == 0) + { + std::ostringstream err; + err << "Error: " << TruelightGetErrorString(); + throw Exception(err.str().c_str()); + } + + m_truelight = TruelightCreateInstance(); + if(!m_truelight) + { + std::ostringstream err; + err << "Error: '" << TruelightGetErrorString(); + throw Exception(err.str().c_str()); + } + + // floating point + TruelightInstanceSetMax(m_truelight, 1); + + // where too look for the profiles, prints etc + TruelightSetRoot(m_configroot.c_str()); + + // invert the transform depending on direction + if(m_direction == TRANSFORM_DIR_FORWARD) + { + TruelightInstanceSetInvertFlag(m_truelight, 0); + } + else if(m_direction == TRANSFORM_DIR_INVERSE) + { + TruelightInstanceSetInvertFlag(m_truelight, 1); + } + +#endif // OCIO_TRUELIGHT_SUPPORT + + } + + OpRcPtr TruelightOp::clone() const + { + std::string _cubeinput = "unknown"; + if(m_cubeinput == TL_INPUT_LOG) _cubeinput = "log"; + else if(m_cubeinput == TL_INPUT_LIN) _cubeinput = "linear"; + else if(m_cubeinput == TL_INPUT_VID) _cubeinput = "video"; + OpRcPtr op = OpRcPtr(new TruelightOp(m_configroot.c_str(), + m_profile.c_str(), + m_camera.c_str(), + m_inputdisplay.c_str(), + m_recorder.c_str(), + m_print.c_str(), + m_lamp.c_str(), + m_outputcamera.c_str(), + m_display.c_str(), + _cubeinput.c_str(), + m_direction)); + return op; + } + + TruelightOp::~TruelightOp() + { +#ifdef OCIO_TRUELIGHT_SUPPORT + if(m_truelight) TruelightDestroyInstance(m_truelight); +#endif // OCIO_TRUELIGHT_SUPPORT + } + + std::string TruelightOp::getInfo() const + { + return "<TruelightOp>"; + } + + std::string TruelightOp::getCacheID() const + { + return m_cacheID; + } + + bool TruelightOp::isNoOp() const + { + return false; + } + bool TruelightOp::isSameType(const OpRcPtr & /*op*/) const + { + // TODO: TruelightOp::isSameType + return false; + } + + bool TruelightOp::isInverse(const OpRcPtr & /*op*/) const + { + // TODO: TruelightOp::isInverse + return false; + } + + bool TruelightOp::hasChannelCrosstalk() const + { + return true; + } + + void TruelightOp::finalize() + { +#ifndef OCIO_TRUELIGHT_SUPPORT + std::ostringstream err; + err << "OCIO has been built without Truelight support"; + throw Exception(err.str().c_str()); +#else + if(m_profile != "") + { + if(TruelightInstanceSetProfile(m_truelight, m_profile.c_str()) == 0) + { + std::ostringstream err; + err << "Error: " << TruelightGetErrorString(); + throw Exception(err.str().c_str()); + } + } + + if(m_camera != "") + { + if(TruelightInstanceSetCamera(m_truelight, m_camera.c_str()) == 0) + { + std::ostringstream err; + err << "Error: " << TruelightGetErrorString(); + throw Exception(err.str().c_str()); + } + } + + if(m_inputdisplay != "") + { + if(TruelightInstanceSetInputDisplay(m_truelight, m_inputdisplay.c_str()) == 0) + { + std::ostringstream err; + err << "Error: " << TruelightGetErrorString(); + throw Exception(err.str().c_str()); + } + } + + if(m_recorder != "") + { + if(TruelightInstanceSetRecorder(m_truelight, m_recorder.c_str()) == 0) + { + std::ostringstream err; + err << "Error: " << TruelightGetErrorString(); + throw Exception(err.str().c_str()); + } + } + + if(m_print != "") + { + if(TruelightInstanceSetPrint(m_truelight, m_print.c_str()) == 0) + { + std::ostringstream err; + err << "Error: " << TruelightGetErrorString(); + throw Exception(err.str().c_str()); + } + } + + if(m_lamp != "") + { + if(TruelightInstanceSetLamp(m_truelight, m_lamp.c_str()) == 0) + { + std::ostringstream err; + err << "Error: " << TruelightGetErrorString(); + throw Exception(err.str().c_str()); + } + } + + if(m_outputcamera != "") + { + if(TruelightInstanceSetOutputCamera(m_truelight, m_outputcamera.c_str()) == 0) + { + std::ostringstream err; + err << "Error: " << TruelightGetErrorString(); + throw Exception(err.str().c_str()); + } + } + + if(m_display != "") + { + if(TruelightInstanceSetDisplay(m_truelight, m_display.c_str()) == 0) + { + std::ostringstream err; + err << "Error: " << TruelightGetErrorString(); + throw Exception(err.str().c_str()); + } + } + + if(TruelightInstanceSetCubeInput(m_truelight, m_cubeinput) == 0) + { + std::ostringstream err; + err << "Error: " << TruelightGetErrorString(); + throw Exception(err.str().c_str()); + } + + if(TruelightInstanceSetUp(m_truelight) == 0) + { + std::ostringstream err; + err << "Error: " << TruelightGetErrorString(); + throw Exception(err.str().c_str()); + } +#endif // OCIO_TRUELIGHT_SUPPORT + + // build cache id + std::ostringstream cacheIDStream; + cacheIDStream << "<TruelightOp "; + cacheIDStream << m_profile << " "; + cacheIDStream << m_camera << " "; + cacheIDStream << m_inputdisplay << " "; + cacheIDStream << m_recorder << " "; + cacheIDStream << m_print << " "; + cacheIDStream << m_lamp << " "; + cacheIDStream << m_outputcamera << " "; + cacheIDStream << m_display << " "; + cacheIDStream << m_cubeinput << " "; + cacheIDStream << TransformDirectionToString(m_direction) << " "; + cacheIDStream << ">"; + m_cacheID = cacheIDStream.str(); + } + + void TruelightOp::apply(float* rgbaBuffer, long numPixels) const + { + for(long pixelIndex = 0; pixelIndex < numPixels; ++pixelIndex) + { +#ifdef OCIO_TRUELIGHT_SUPPORT + TruelightInstanceTransformF(m_truelight, rgbaBuffer); +#endif // OCIO_TRUELIGHT_SUPPORT + rgbaBuffer += 4; // skip alpha + } + } + + bool TruelightOp::supportsGpuShader() const + { + return false; + } + + void TruelightOp::writeGpuShader(std::ostream & /*shader*/, + const std::string & /*pixelName*/, + const GpuShaderDesc & /*shaderDesc*/) const + { + throw Exception("TruelightOp does not define an gpu shader."); + } + + } // anonymous namespace + + void CreateTruelightOps(OpRcPtrVec & ops, + const TruelightTransform & data, + TransformDirection direction) + { + ops.push_back(OpRcPtr(new TruelightOp(data.getConfigRoot(), + data.getProfile(), + data.getCamera(), + data.getInputDisplay(), + data.getRecorder(), + data.getPrint(), + data.getLamp(), + data.getOutputCamera(), + data.getDisplay(), + data.getCubeInput(), + direction))); + } +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/TruelightOp.h b/src/core/TruelightOp.h new file mode 100644 index 0000000..fe78b6a --- /dev/null +++ b/src/core/TruelightOp.h @@ -0,0 +1,44 @@ +/* +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. +*/ + +#ifndef INCLUDED_OCIO_TRUELIGHTOP_H +#define INCLUDED_OCIO_TRUELIGHTOP_H + +#include <OpenColorIO/OpenColorIO.h> + +#include "Op.h" + +OCIO_NAMESPACE_ENTER +{ + void CreateTruelightOps(OpRcPtrVec & ops, + const TruelightTransform & data, + TransformDirection dir); +} +OCIO_NAMESPACE_EXIT + +#endif // INCLUDED_OCIO_TRUELIGHTOP_H diff --git a/src/core/TruelightTransform.cpp b/src/core/TruelightTransform.cpp new file mode 100644 index 0000000..7e95875 --- /dev/null +++ b/src/core/TruelightTransform.cpp @@ -0,0 +1,365 @@ +/* +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 <iostream> + +#include <OpenColorIO/OpenColorIO.h> + +#include "OpBuilders.h" +#include "TruelightOp.h" +#include "pystring/pystring.h" + +OCIO_NAMESPACE_ENTER +{ + + TruelightTransformRcPtr TruelightTransform::Create() + { + return TruelightTransformRcPtr(new TruelightTransform(), &deleter); + } + + void TruelightTransform::deleter(TruelightTransform* t) + { + delete t; + } + + class TruelightTransform::Impl + { + public: + TransformDirection dir_; + std::string configroot_; + std::string profile_; + std::string camera_; + std::string inputdisplay_; + std::string recorder_; + std::string print_; + std::string lamp_; + std::string outputcamera_; + std::string display_; + std::string cubeinput_; + + Impl() : dir_(TRANSFORM_DIR_FORWARD) + { } + + ~Impl() + { } + + Impl& operator= (const Impl & rhs) + { + dir_ = rhs.dir_; + configroot_ = rhs.configroot_; + profile_ = rhs.profile_; + camera_ = rhs.camera_; + inputdisplay_ = rhs.inputdisplay_; + recorder_ = rhs.recorder_; + print_ = rhs.print_; + lamp_ = rhs.lamp_; + outputcamera_ = rhs.outputcamera_; + display_ = rhs.display_; + cubeinput_ = rhs.cubeinput_; + return *this; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + TruelightTransform::TruelightTransform() + : m_impl(new TruelightTransform::Impl) + { + getImpl()->configroot_ = "/usr/fl/truelight"; + getImpl()->profile_ = ""; + getImpl()->camera_ = ""; + getImpl()->inputdisplay_ = ""; + getImpl()->recorder_ = ""; + getImpl()->print_ = ""; + getImpl()->lamp_ = ""; + getImpl()->outputcamera_ = ""; + getImpl()->display_ = ""; + getImpl()->cubeinput_ = "log"; + } + + TransformRcPtr TruelightTransform::createEditableCopy() const + { + TruelightTransformRcPtr transform = TruelightTransform::Create(); + *(transform->m_impl) = *m_impl; + return transform; + } + + TruelightTransform::~TruelightTransform() + { + delete m_impl; + m_impl = NULL; + } + + TruelightTransform& TruelightTransform::operator= (const TruelightTransform & rhs) + { + *m_impl = *rhs.m_impl; + return *this; + } + + TransformDirection TruelightTransform::getDirection() const + { + return getImpl()->dir_; + } + + void TruelightTransform::setDirection(TransformDirection dir) + { + getImpl()->dir_ = dir; + } + + void TruelightTransform::setConfigRoot(const char * configroot) + { + getImpl()->configroot_ = configroot; + } + + const char * TruelightTransform::getConfigRoot() const + { + return getImpl()->configroot_.c_str(); + } + + void TruelightTransform::setProfile(const char * profile) + { + getImpl()->profile_ = profile; + } + + const char * TruelightTransform::getProfile() const + { + return getImpl()->profile_.c_str(); + } + + void TruelightTransform::setCamera(const char * camera) + { + getImpl()->camera_ = camera; + } + + const char * TruelightTransform::getCamera() const + { + return getImpl()->camera_.c_str(); + } + + void TruelightTransform::setInputDisplay(const char * display) + { + getImpl()->inputdisplay_ = display; + } + + const char * TruelightTransform::getInputDisplay() const + { + return getImpl()->inputdisplay_.c_str(); + } + + void TruelightTransform::setRecorder(const char * recorder) + { + getImpl()->recorder_ = recorder; + } + + const char * TruelightTransform::getRecorder() const + { + return getImpl()->recorder_.c_str(); + } + + void TruelightTransform::setPrint(const char * print) + { + getImpl()->print_ = print; + } + + const char * TruelightTransform::getPrint() const + { + return getImpl()->print_.c_str(); + } + + void TruelightTransform::setLamp(const char * lamp) + { + getImpl()->lamp_ = lamp; + } + + const char * TruelightTransform::getLamp() const + { + return getImpl()->lamp_.c_str(); + } + + void TruelightTransform::setOutputCamera(const char * camera) + { + getImpl()->outputcamera_ = camera; + } + + const char * TruelightTransform::getOutputCamera() const + { + return getImpl()->outputcamera_.c_str(); + } + + void TruelightTransform::setDisplay(const char * display) + { + getImpl()->display_ = display; + } + + const char * TruelightTransform::getDisplay() const + { + return getImpl()->display_.c_str(); + } + + void TruelightTransform::setCubeInput(const char * cubeinput) + { + getImpl()->cubeinput_ = pystring::lower(cubeinput); + } + + const char * TruelightTransform::getCubeInput() const + { + return getImpl()->cubeinput_.c_str(); + } + + std::ostream& operator<< (std::ostream& os, const TruelightTransform& t) + { + os << "<TruelightTransform "; + os << "direction=" << TransformDirectionToString(t.getDirection()) << ", "; + os << ">\n"; + return os; + } + + /////////////////////////////////////////////////////////////////////////// + + void BuildTruelightOps(OpRcPtrVec & ops, + const Config& /*config*/, + const TruelightTransform & transform, + TransformDirection dir) + { + TransformDirection combinedDir = CombineTransformDirections(dir, + transform.getDirection()); + CreateTruelightOps(ops, transform, combinedDir); + } + +} +OCIO_NAMESPACE_EXIT + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef OCIO_UNIT_TEST + +namespace OCIO = OCIO_NAMESPACE; +#include "UnitTest.h" + +OIIO_ADD_TEST(TruelightTransform, simpletest) +{ + + OCIO::ConfigRcPtr config = OCIO::Config::Create(); + { + OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); + cs->setName("log"); + cs->setFamily("log"); + config->addColorSpace(cs); + config->setRole(OCIO::ROLE_COMPOSITING_LOG, cs->getName()); + } + { + OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); + cs->setName("sRGB"); + cs->setFamily("srgb"); + OCIO::TruelightTransformRcPtr transform1 = OCIO::TruelightTransform::Create(); + transform1->setConfigRoot("/usr/fl/truelight"); + transform1->setPrint("internal-LowContrast"); + //transform1->setInputDisplay("DCIrgb"); + transform1->setDisplay("sRGB"); + transform1->setCubeInput("log"); + cs->setTransform(transform1, OCIO::COLORSPACE_DIR_FROM_REFERENCE); + config->addColorSpace(cs); + } + + // check the transform round trip + OCIO::ConstProcessorRcPtr tosrgb; + OCIO::ConstProcessorRcPtr tolog; + +#ifdef OCIO_TRUELIGHT_SUPPORT + OIIO_CHECK_NO_THOW(tosrgb = config->getProcessor("log", "sRGB")); + OIIO_CHECK_NO_THOW(tolog = config->getProcessor("sRGB", "log")); +#else + OIIO_CHECK_THOW(tosrgb = config->getProcessor("log", "sRGB"), OCIO::Exception); + OIIO_CHECK_THOW(tolog = config->getProcessor("sRGB", "log"), OCIO::Exception); +#endif + +#ifdef OCIO_TRUELIGHT_SUPPORT + float input[3] = {0.5f, 0.5f, 0.5f}; + float output[3] = {0.500098f, 0.500317f, 0.501134f}; + OIIO_CHECK_NO_THOW(tosrgb->applyRGB(input)); + OIIO_CHECK_NO_THOW(tolog->applyRGB(input)); + OIIO_CHECK_CLOSE(input[0], output[0], 1e-4); + OIIO_CHECK_CLOSE(input[1], output[1], 1e-4); + OIIO_CHECK_CLOSE(input[2], output[2], 1e-4); +#endif + + std::ostringstream os; + OIIO_CHECK_NO_THOW(config->serialize(os)); + + std::string referenceconfig = + "ocio_profile_version: 1\n" + "\n" + "search_path: \"\"\n" + "strictparsing: true\n" + "luma: [0.2126, 0.7152, 0.0722]\n" + "\n" + "roles:\n" + " compositing_log: log\n" + "\n" + "displays:\n" + " {}\n" + "\n" + "active_displays: []\n" + "active_views: []\n" + "\n" + "colorspaces:\n" + " - !<ColorSpace>\n" + " name: log\n" + " family: log\n" + " equalitygroup: \"\"\n" + " bitdepth: unknown\n" + " isdata: false\n" + " allocation: uniform\n" + "\n" + " - !<ColorSpace>\n" + " name: sRGB\n" + " family: srgb\n" + " equalitygroup: \"\"\n" + " bitdepth: unknown\n" + " isdata: false\n" + " allocation: uniform\n" + " from_reference: !<TruelightTransform> {config_root: /usr/fl/truelight, print: internal-LowContrast, display: sRGB, cube_input: log}\n"; + + + std::vector<std::string> osvec; + OCIO::pystring::splitlines(os.str(), osvec); + std::vector<std::string> referenceconfigvec; + OCIO::pystring::splitlines(referenceconfig, referenceconfigvec); + + OIIO_CHECK_EQUAL(osvec.size(), referenceconfigvec.size()); + for(unsigned int i = 0; i < referenceconfigvec.size(); ++i) + OIIO_CHECK_EQUAL(osvec[i], referenceconfigvec[i]); + + std::istringstream is; + is.str(referenceconfig); + OCIO::ConstConfigRcPtr rtconfig; + OIIO_CHECK_NO_THOW(rtconfig = OCIO::Config::CreateFromStream(is)); + +} + +#endif // OCIO_BUILD_TESTS diff --git a/src/core/UnitTest.cpp b/src/core/UnitTest.cpp new file mode 100644 index 0000000..8b1270d --- /dev/null +++ b/src/core/UnitTest.cpp @@ -0,0 +1,39 @@ +/* +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. +*/ + +#ifndef INCLUDED_OCIO_UNITTEST_H +#define INCLUDED_OCIO_UNITTEST_H + +#ifdef OCIO_UNIT_TEST +#pragma GCC visibility push(default) +#include <unittest.h> // OIIO unit tests header +OIIO_TEST_APP(OpenColorIO_Core_Unit_Tests) +#pragma GCC visibility pop +#endif // OCIO_UNIT_TEST + +#endif // INCLUDED_OCIO_UNITTEST_H diff --git a/src/core/UnitTest.h b/src/core/UnitTest.h new file mode 100644 index 0000000..33625c5 --- /dev/null +++ b/src/core/UnitTest.h @@ -0,0 +1,38 @@ +/* +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. +*/ + +#ifndef INCLUDED_OCIO_UNITTEST_H +#define INCLUDED_OCIO_UNITTEST_H + +#ifdef OCIO_UNIT_TEST +#pragma GCC visibility push(default) +#include <unittest.h> // OIIO unit tests header +#pragma GCC visibility pop +#endif // OCIO_UNIT_TEST + +#endif // INCLUDED_OCIO_UNITTEST_H diff --git a/src/core/md5/md5.cpp b/src/core/md5/md5.cpp new file mode 100644 index 0000000..3eb5673 --- /dev/null +++ b/src/core/md5/md5.cpp @@ -0,0 +1,391 @@ +/* + Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.c is L. Peter Deutsch + <ghost@aladdin.com>. Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order + either statically or dynamically; added missing #include <string.h> + in library. + 2002-03-11 lpd Corrected argument list for main(), and added int return + type, in test program and T value program. + 2002-02-21 lpd Added missing #include <stdio.h> in test program. + 2000-07-03 lpd Patched to eliminate warnings about "constant is + unsigned in ANSI C, signed in traditional"; made test program + self-checking. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). + 1999-05-03 lpd Original version. + */ + +// This file was altered for OCIO compilation purposes + +#include "md5.h" +#include <cstring> + + +OCIO_NAMESPACE_ENTER +{ + + +#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ +#ifdef ARCH_IS_BIG_ENDIAN +# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) +#else +# define BYTE_ORDER 0 +#endif + +#define T_MASK ((md5_word_t)~0) +#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) +#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) +#define T3 0x242070db +#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) +#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) +#define T6 0x4787c62a +#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) +#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) +#define T9 0x698098d8 +#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) +#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) +#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) +#define T13 0x6b901122 +#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) +#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) +#define T16 0x49b40821 +#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) +#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) +#define T19 0x265e5a51 +#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) +#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) +#define T22 0x02441453 +#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) +#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) +#define T25 0x21e1cde6 +#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) +#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) +#define T28 0x455a14ed +#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) +#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) +#define T31 0x676f02d9 +#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) +#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) +#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) +#define T35 0x6d9d6122 +#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) +#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) +#define T38 0x4bdecfa9 +#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) +#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) +#define T41 0x289b7ec6 +#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) +#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) +#define T44 0x04881d05 +#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) +#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) +#define T47 0x1fa27cf8 +#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) +#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) +#define T50 0x432aff97 +#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) +#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) +#define T53 0x655b59c3 +#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) +#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) +#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) +#define T57 0x6fa87e4f +#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) +#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) +#define T60 0x4e0811a1 +#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) +#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) +#define T63 0x2ad7d2bb +#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) + + +static void +md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) +{ + md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t t; +#if BYTE_ORDER > 0 + /* Define storage only for big-endian CPUs. */ + md5_word_t X[16]; +#else + /* Define storage for little-endian or both types of CPUs. */ + md5_word_t xbuf[16]; + const md5_word_t *X; +#endif + + { +#if BYTE_ORDER == 0 + /* + * Determine dynamically whether this is a big-endian or + * little-endian machine, since we can use a more efficient + * algorithm on the latter. + */ + static const int w = 1; + + if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ +#endif +#if BYTE_ORDER <= 0 /* little-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (const md5_byte_t *)0) & 3)) { + /* data are properly aligned */ + X = (const md5_word_t *)data; + } else { + /* not aligned */ + memcpy(xbuf, data, 64); + X = xbuf; + } + } +#endif +#if BYTE_ORDER == 0 + else /* dynamic big-endian */ +#endif +#if BYTE_ORDER >= 0 /* big-endian */ + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; + +# if BYTE_ORDER == 0 + X = xbuf; /* (dynamic only) */ +# else +# define xbuf X /* (static only) */ +# endif + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } +#endif + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + F(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); + SET(c, d, a, b, 10, 17, T11); + SET(b, c, d, a, 11, 22, T12); + SET(a, b, c, d, 12, 7, T13); + SET(d, a, b, c, 13, 12, T14); + SET(c, d, a, b, 14, 17, T15); + SET(b, c, d, a, 15, 22, T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + G(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); + SET(c, d, a, b, 11, 14, T19); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); + SET(c, d, a, b, 15, 14, T23); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 12, 20, T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + H(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); + SET(c, d, a, b, 11, 16, T35); + SET(b, c, d, a, 14, 23, T36); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); + SET(b, c, d, a, 10, 23, T40); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); + SET(d, a, b, c, 12, 11, T46); + SET(c, d, a, b, 15, 16, T47); + SET(b, c, d, a, 2, 23, T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + I(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); + SET(c, d, a, b, 14, 15, T51); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); + SET(c, d, a, b, 10, 15, T55); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); + SET(d, a, b, c, 15, 10, T58); + SET(c, d, a, b, 6, 15, T59); + SET(b, c, d, a, 13, 21, T60); + SET(a, b, c, d, 4, 6, T61); + SET(d, a, b, c, 11, 10, T62); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); +#undef SET + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void +md5_init(md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void +md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) +{ + const md5_byte_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + memcpy(pms->buf, p, left); +} + +void +md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +{ + static const md5_byte_t pad[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + md5_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} + +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/md5/md5.h b/src/core/md5/md5.h new file mode 100644 index 0000000..cb36edb --- /dev/null +++ b/src/core/md5/md5.h @@ -0,0 +1,97 @@ +/* + Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.h is L. Peter Deutsch + <ghost@aladdin.com>. Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Removed support for non-ANSI compilers; removed + references to Ghostscript; clarified derivation from RFC 1321; + now handles byte order either statically or dynamically. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); + added conditionalization for C++ compilation from Martin + Purschke <purschke@bnl.gov>. + 1999-05-03 lpd Original version. + */ + +// This file was altered for OCIO compilation purposes + +#ifndef INCLUDED_OCIO_md5_INCLUDED +#define INCLUDED_OCIO_md5_INCLUDED + + +#include <OpenColorIO/OpenColorIO.h> + +OCIO_NAMESPACE_ENTER +{ + +// Note: the md5 functions should not be wrapped in extern "C', otherwise +// the symbols will not be appropriately wrapped in the OCIO namespace + +/* + * This package supports both compile-time and run-time determination of CPU + * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be + * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is + * defined as non-zero, the code will be compiled to run only on big-endian + * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to + * run on either big- or little-endian CPUs, but will run slightly less + * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. + */ + +typedef unsigned char md5_byte_t; /* 8-bit byte */ +typedef unsigned int md5_word_t; /* 32-bit word */ + +/* Define the state of the MD5 Algorithm. */ +typedef struct md5_state_s { + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; + +/* Initialize the algorithm. */ +void md5_init(md5_state_t *pms); + +/* Append a string to the message. */ +void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); + +/* Finish the message and return the digest. */ +void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); + + +} +OCIO_NAMESPACE_EXIT + +#endif /* md5_INCLUDED */ diff --git a/src/core/pystring/pystring.cpp b/src/core/pystring/pystring.cpp new file mode 100644 index 0000000..7805162 --- /dev/null +++ b/src/core/pystring/pystring.cpp @@ -0,0 +1,1658 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008-2010, Sony Pictures Imageworks Inc +// 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 the organization 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 "pystring.h" + +#include <algorithm> +#include <cctype> +#include <cstring> +#include <iostream> +#include <sstream> + +OCIO_NAMESPACE_ENTER +{ + +namespace pystring +{ + +#if defined(_WIN32) || defined(_WIN64) || defined(_WINDOWS) || defined(_MSC_VER) +#ifndef WINDOWS +#define WINDOWS +#endif +#endif + +// This definition codes from configure.in in the python src. +// Strictly speaking this limits us to str sizes of 2**31. +// Should we wish to handle this limit, we could use an architecture +// specific #defines and read from ssize_t (unistd.h) if the header exists. +// But in the meantime, the use of int assures maximum arch compatibility. +// This must also equal the size used in the end = MAX_32BIT_INT default arg. + +typedef int Py_ssize_t; + +/* helper macro to fixup start/end slice values */ +#define ADJUST_INDICES(start, end, len) \ + if (end > len) \ + end = len; \ + else if (end < 0) { \ + end += len; \ + if (end < 0) \ + end = 0; \ + } \ + if (start < 0) { \ + start += len; \ + if (start < 0) \ + start = 0; \ + } + + + namespace { + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// why doesn't the std::reverse work? + /// + void reverse_strings( std::vector< std::string > & result) + { + for (std::vector< std::string >::size_type i = 0; i < result.size() / 2; i++ ) + { + std::swap(result[i], result[result.size() - 1 - i]); + } + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + void split_whitespace( const std::string & str, std::vector< std::string > & result, int maxsplit ) + { + std::string::size_type i, j, len = str.size(); + for (i = j = 0; i < len; ) + { + + while ( i < len && ::isspace( str[i] ) ) i++; + j = i; + + while ( i < len && ! ::isspace( str[i]) ) i++; + + + + if (j < i) + { + if ( maxsplit-- <= 0 ) break; + + result.push_back( str.substr( j, i - j )); + + while ( i < len && ::isspace( str[i])) i++; + j = i; + } + } + if (j < len) + { + result.push_back( str.substr( j, len - j )); + } + } + + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + void rsplit_whitespace( const std::string & str, std::vector< std::string > & result, int maxsplit ) + { + std::string::size_type len = str.size(); + std::string::size_type i, j; + for (i = j = len; i > 0; ) + { + + while ( i > 0 && ::isspace( str[i - 1] ) ) i--; + j = i; + + while ( i > 0 && ! ::isspace( str[i - 1]) ) i--; + + + + if (j > i) + { + if ( maxsplit-- <= 0 ) break; + + result.push_back( str.substr( i, j - i )); + + while ( i > 0 && ::isspace( str[i - 1])) i--; + j = i; + } + } + if (j > 0) + { + result.push_back( str.substr( 0, j )); + } + //std::reverse( result, result.begin(), result.end() ); + reverse_strings( result ); + } + + } //anonymous namespace + + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + void split( const std::string & str, std::vector< std::string > & result, const std::string & sep, int maxsplit ) + { + result.clear(); + + if ( maxsplit < 0 ) maxsplit = MAX_32BIT_INT;//result.max_size(); + + + if ( sep.size() == 0 ) + { + split_whitespace( str, result, maxsplit ); + return; + } + + std::string::size_type i,j, len = str.size(), n = sep.size(); + + i = j = 0; + + while ( i+n <= len ) + { + if ( str[i] == sep[0] && str.substr( i, n ) == sep ) + { + if ( maxsplit-- <= 0 ) break; + + result.push_back( str.substr( j, i - j ) ); + i = j = i + n; + } + else + { + i++; + } + } + + result.push_back( str.substr( j, len-j ) ); + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + void rsplit( const std::string & str, std::vector< std::string > & result, const std::string & sep, int maxsplit ) + { + if ( maxsplit < 0 ) + { + split( str, result, sep, 0 ); + return; + } + + result.clear(); + + if ( sep.size() == 0 ) + { + rsplit_whitespace( str, result, maxsplit ); + return; + } + + std::string::size_type i,j, len = str.size(), n = sep.size(); + + i = j = len; + + while ( i > n ) + { + if ( str[i - 1] == sep[n - 1] && str.substr( i - n, n ) == sep ) + { + if ( maxsplit-- <= 0 ) break; + + result.push_back( str.substr( i, j - i ) ); + i = j = i - n; + } + else + { + i--; + } + } + + result.push_back( str.substr( 0, j ) ); + reverse_strings( result ); + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + #define LEFTSTRIP 0 + #define RIGHTSTRIP 1 + #define BOTHSTRIP 2 + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string do_strip( const std::string & str, int striptype, const std::string & chars ) + { + Py_ssize_t len = (Py_ssize_t) str.size(), i, j, charslen = (Py_ssize_t) chars.size(); + + if ( charslen == 0 ) + { + i = 0; + if ( striptype != RIGHTSTRIP ) + { + while ( i < len && ::isspace( str[i] ) ) + { + i++; + } + } + + j = len; + if ( striptype != LEFTSTRIP ) + { + do + { + j--; + } + while (j >= i && ::isspace(str[j])); + + j++; + } + + + } + else + { + const char * sep = chars.c_str(); + + i = 0; + if ( striptype != RIGHTSTRIP ) + { + while ( i < len && memchr(sep, str[i], charslen) ) + { + i++; + } + } + + j = len; + if (striptype != LEFTSTRIP) + { + do + { + j--; + } + while (j >= i && memchr(sep, str[j], charslen) ); + j++; + } + + + } + + if ( i == 0 && j == len ) + { + return str; + } + else + { + return str.substr( i, j - i ); + } + + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + void partition( const std::string & str, const std::string & sep, std::vector< std::string > & result ) + { + result.resize(3); + int index = find( str, sep ); + if ( index < 0 ) + { + result[0] = str; + result[1] = ""; + result[2] = ""; + } + else + { + result[0] = str.substr( 0, index ); + result[1] = sep; + result[2] = str.substr( index + sep.size(), str.size() ); + } + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + void rpartition( const std::string & str, const std::string & sep, std::vector< std::string > & result ) + { + result.resize(3); + int index = rfind( str, sep ); + if ( index < 0 ) + { + result[0] = ""; + result[1] = ""; + result[2] = str; + } + else + { + result[0] = str.substr( 0, index ); + result[1] = sep; + result[2] = str.substr( index + sep.size(), str.size() ); + } + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string strip( const std::string & str, const std::string & chars ) + { + return do_strip( str, BOTHSTRIP, chars ); + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string lstrip( const std::string & str, const std::string & chars ) + { + return do_strip( str, LEFTSTRIP, chars ); + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string rstrip( const std::string & str, const std::string & chars ) + { + return do_strip( str, RIGHTSTRIP, chars ); + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string join( const std::string & str, const std::vector< std::string > & seq ) + { + std::vector< std::string >::size_type seqlen = seq.size(), i; + + if ( seqlen == 0 ) return ""; + if ( seqlen == 1 ) return seq[0]; + + std::string result( seq[0] ); + + for ( i = 1; i < seqlen; ++i ) + { + result += str + seq[i]; + + } + + + return result; + } + + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + + namespace + { + /* Matches the end (direction >= 0) or start (direction < 0) of self + * against substr, using the start and end arguments. Returns + * -1 on error, 0 if not found and 1 if found. + */ + + int _string_tailmatch(const std::string & self, const std::string & substr, + Py_ssize_t start, Py_ssize_t end, + int direction) + { + Py_ssize_t len = (Py_ssize_t) self.size(); + Py_ssize_t slen = (Py_ssize_t) substr.size(); + + const char* sub = substr.c_str(); + const char* str = self.c_str(); + + ADJUST_INDICES(start, end, len); + + if (direction < 0) { + // startswith + if (start+slen > len) + return 0; + } else { + // endswith + if (end-start < slen || start > len) + return 0; + if (end-slen > start) + start = end - slen; + } + if (end-start >= slen) + return (!std::memcmp(str+start, sub, slen)); + + return 0; + } + } + + bool endswith( const std::string & str, const std::string & suffix, int start, int end ) + { + int result = _string_tailmatch(str, suffix, + (Py_ssize_t) start, (Py_ssize_t) end, +1); + //if (result == -1) // TODO: Error condition + + return static_cast<bool>(result); + } + + + bool startswith( const std::string & str, const std::string & prefix, int start, int end ) + { + int result = _string_tailmatch(str, prefix, + (Py_ssize_t) start, (Py_ssize_t) end, -1); + //if (result == -1) // TODO: Error condition + + return static_cast<bool>(result); + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + + bool isalnum( const std::string & str ) + { + std::string::size_type len = str.size(), i; + if ( len == 0 ) return false; + + + if( len == 1 ) + { + return ::isalnum( str[0] ); + } + + for ( i = 0; i < len; ++i ) + { + if ( !::isalnum( str[i] ) ) return false; + } + return true; + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + bool isalpha( const std::string & str ) + { + std::string::size_type len = str.size(), i; + if ( len == 0 ) return false; + if( len == 1 ) return ::isalpha( (int) str[0] ); + + for ( i = 0; i < len; ++i ) + { + if ( !::isalpha( (int) str[i] ) ) return false; + } + return true; + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + bool isdigit( const std::string & str ) + { + std::string::size_type len = str.size(), i; + if ( len == 0 ) return false; + if( len == 1 ) return ::isdigit( str[0] ); + + for ( i = 0; i < len; ++i ) + { + if ( ! ::isdigit( str[i] ) ) return false; + } + return true; + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + bool islower( const std::string & str ) + { + std::string::size_type len = str.size(), i; + if ( len == 0 ) return false; + if( len == 1 ) return ::islower( str[0] ); + + for ( i = 0; i < len; ++i ) + { + if ( !::islower( str[i] ) ) return false; + } + return true; + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + bool isspace( const std::string & str ) + { + std::string::size_type len = str.size(), i; + if ( len == 0 ) return false; + if( len == 1 ) return ::isspace( str[0] ); + + for ( i = 0; i < len; ++i ) + { + if ( !::isspace( str[i] ) ) return false; + } + return true; + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + bool istitle( const std::string & str ) + { + std::string::size_type len = str.size(), i; + + if ( len == 0 ) return false; + if ( len == 1 ) return ::isupper( str[0] ); + + bool cased = false, previous_is_cased = false; + + for ( i = 0; i < len; ++i ) + { + if ( ::isupper( str[i] ) ) + { + if ( previous_is_cased ) + { + return false; + } + + previous_is_cased = true; + cased = true; + } + else if ( ::islower( str[i] ) ) + { + if (!previous_is_cased) + { + return false; + } + + previous_is_cased = true; + cased = true; + + } + else + { + previous_is_cased = false; + } + } + + return cased; + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + bool isupper( const std::string & str ) + { + std::string::size_type len = str.size(), i; + if ( len == 0 ) return false; + if( len == 1 ) return ::isupper( str[0] ); + + for ( i = 0; i < len; ++i ) + { + if ( !::isupper( str[i] ) ) return false; + } + return true; + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string capitalize( const std::string & str ) + { + std::string s( str ); + std::string::size_type len = s.size(), i; + + if ( len > 0) + { + if (::islower(s[0])) s[0] = (char) ::toupper( s[0] ); + } + + for ( i = 1; i < len; ++i ) + { + if (::isupper(s[i])) s[i] = (char) ::tolower( s[i] ); + } + + return s; + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string lower( const std::string & str ) + { + std::string s( str ); + std::string::size_type len = s.size(), i; + + for ( i = 0; i < len; ++i ) + { + if ( ::isupper( s[i] ) ) s[i] = (char) ::tolower( s[i] ); + } + + return s; + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string upper( const std::string & str ) + { + std::string s( str ) ; + std::string::size_type len = s.size(), i; + + for ( i = 0; i < len; ++i ) + { + if ( ::islower( s[i] ) ) s[i] = (char) ::toupper( s[i] ); + } + + return s; + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string swapcase( const std::string & str ) + { + std::string s( str ); + std::string::size_type len = s.size(), i; + + for ( i = 0; i < len; ++i ) + { + if ( ::islower( s[i] ) ) s[i] = (char) ::toupper( s[i] ); + else if (::isupper( s[i] ) ) s[i] = (char) ::tolower( s[i] ); + } + + return s; + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string title( const std::string & str ) + { + std::string s( str ); + std::string::size_type len = s.size(), i; + bool previous_is_cased = false; + + for ( i = 0; i < len; ++i ) + { + int c = s[i]; + if ( ::islower(c) ) + { + if ( !previous_is_cased ) + { + s[i] = (char) ::toupper(c); + } + previous_is_cased = true; + } + else if ( ::isupper(c) ) + { + if ( previous_is_cased ) + { + s[i] = (char) ::tolower(c); + } + previous_is_cased = true; + } + else + { + previous_is_cased = false; + } + } + + return s; + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string translate( const std::string & str, const std::string & table, const std::string & deletechars ) + { + std::string s; + std::string::size_type len = str.size(), dellen = deletechars.size(); + + if ( table.size() != 256 ) + { + // TODO : raise exception instead + return str; + } + + //if nothing is deleted, use faster code + if ( dellen == 0 ) + { + s = str; + for ( std::string::size_type i = 0; i < len; ++i ) + { + s[i] = table[ s[i] ]; + } + return s; + } + + + int trans_table[256]; + for ( int i = 0; i < 256; i++) + { + trans_table[i] = table[i]; + } + + for ( std::string::size_type i = 0; i < dellen; i++) + { + trans_table[(int) deletechars[i] ] = -1; + } + + for ( std::string::size_type i = 0; i < len; ++i ) + { + if ( trans_table[ (int) str[i] ] != -1 ) + { + s += table[ str[i] ]; + } + } + + return s; + + } + + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string zfill( const std::string & str, int width ) + { + int len = (int)str.size(); + + if ( len >= width ) + { + return str; + } + + std::string s( str ); + + int fill = width - len; + + s = std::string( fill, '0' ) + s; + + + if ( s[fill] == '+' || s[fill] == '-' ) + { + s[0] = s[fill]; + s[fill] = '0'; + } + + return s; + + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string ljust( const std::string & str, int width ) + { + std::string::size_type len = str.size(); + if ( (( int ) len ) >= width ) return str; + return str + std::string( width - len, ' ' ); + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string rjust( const std::string & str, int width ) + { + std::string::size_type len = str.size(); + if ( (( int ) len ) >= width ) return str; + return std::string( width - len, ' ' ) + str; + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string center( const std::string & str, int width ) + { + int len = (int) str.size(); + int marg, left; + + if ( len >= width ) return str; + + marg = width - len; + left = marg / 2 + (marg & width & 1); + + return std::string( left, ' ' ) + str + std::string( marg - left, ' ' ); + + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string slice( const std::string & str, int start, int end ) + { + ADJUST_INDICES(start, end, (int) str.size()); + if ( start >= end ) return ""; + return str.substr( start, end - start ); + } + + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + int find( const std::string & str, const std::string & sub, int start, int end ) + { + ADJUST_INDICES(start, end, (int) str.size()); + + std::string::size_type result = str.find( sub, start ); + + // If we cannot find the string, or if the end-point of our found substring is past + // the allowed end limit, return that it can't be found. + if( result == std::string::npos || + (result + sub.size() > (std::string::size_type)end) ) + { + return -1; + } + + return (int) result; + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + int index( const std::string & str, const std::string & sub, int start, int end ) + { + return find( str, sub, start, end ); + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + int rfind( const std::string & str, const std::string & sub, int start, int end ) + { + ADJUST_INDICES(start, end, (int) str.size()); + + std::string::size_type result = str.rfind( sub, end ); + + if( result == std::string::npos || + result < (std::string::size_type)start || + (result + sub.size() > (std::string::size_type)end)) + return -1; + + return (int)result; + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + int rindex( const std::string & str, const std::string & sub, int start, int end ) + { + return rfind( str, sub, start, end ); + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string expandtabs( const std::string & str, int tabsize ) + { + std::string s( str ); + + std::string::size_type len = str.size(), i = 0; + int offset = 0; + + int j = 0; + + for ( i = 0; i < len; ++i ) + { + if ( str[i] == '\t' ) + { + + if ( tabsize > 0 ) + { + int fillsize = tabsize - (j % tabsize); + j += fillsize; + s.replace( i + offset, 1, std::string( fillsize, ' ' )); + offset += fillsize - 1; + } + else + { + s.replace( i + offset, 1, "" ); + offset -= 1; + } + + } + else + { + j++; + + if (str[i] == '\n' || str[i] == '\r') + { + j = 0; + } + } + } + + return s; + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + int count( const std::string & str, const std::string & substr, int start, int end ) + { + int nummatches = 0; + int cursor = start; + + while ( 1 ) + { + cursor = find( str, substr, cursor, end ); + + if ( cursor < 0 ) break; + + cursor += (int) substr.size(); + nummatches += 1; + } + + return nummatches; + + + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string replace( const std::string & str, const std::string & oldstr, const std::string & newstr, int count ) + { + int sofar = 0; + int cursor = 0; + std::string s( str ); + + std::string::size_type oldlen = oldstr.size(), newlen = newstr.size(); + + while ( ( cursor = find( s, oldstr, cursor ) ) != -1 ) + { + if ( count > -1 && sofar >= count ) + { + break; + } + + s.replace( cursor, oldlen, newstr ); + + cursor += (int) newlen; + ++sofar; + } + + return s; + + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + void splitlines( const std::string & str, std::vector< std::string > & result, bool keepends ) + { + result.clear(); + std::string::size_type len = str.size(), i, j, eol; + + for (i = j = 0; i < len; ) + { + while (i < len && str[i] != '\n' && str[i] != '\r') i++; + + eol = i; + if (i < len) + { + if (str[i] == '\r' && i + 1 < len && str[i+1] == '\n') + { + i += 2; + } + else + { + i++; + } + if (keepends) + eol = i; + + } + + result.push_back( str.substr( j, eol - j ) ); + j = i; + + } + + if (j < len) + { + result.push_back( str.substr( j, len - j ) ); + } + + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + std::string mul( const std::string & str, int n ) + { + // Early exits + if (n <= 0) return ""; + if (n == 1) return str; + + std::ostringstream os; + for(int i=0; i<n; ++i) + { + os << str; + } + return os.str(); + } + + + +namespace os +{ +namespace path +{ + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + /// These functions are C++ ports of the python2.6 versions of os.path, + /// and come from genericpath.py, ntpath.py, posixpath.py + + /// Split a pathname into drive and path specifiers. + /// Returns drivespec, pathspec. Either part may be empty. + void splitdrive_nt(std::string & drivespec, std::string & pathspec, + const std::string & p) + { + if(pystring::slice(p, 1, 2) == ":") + { + std::string path = p; // In case drivespec == p + drivespec = pystring::slice(path, 0, 2); + pathspec = pystring::slice(path, 2); + } + else + { + drivespec = ""; + pathspec = p; + } + } + + // On Posix, drive is always empty + void splitdrive_posix(std::string & drivespec, std::string & pathspec, + const std::string & path) + { + drivespec = ""; + pathspec = path; + } + + void splitdrive(std::string & drivespec, std::string & pathspec, + const std::string & path) + { +#ifdef WINDOWS + return splitdrive_nt(drivespec, pathspec, path); +#else + return splitdrive_posix(drivespec, pathspec, path); +#endif + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + + // Test whether a path is absolute + // In windows, if the character to the right of the colon + // is a forward or backslash it's absolute. + bool isabs_nt(const std::string & path) + { + std::string drivespec, pathspec; + splitdrive_nt(drivespec, pathspec, path); + if(pathspec.empty()) return false; + return ((pathspec[0] == '/') || (pathspec[0] == '\\')); + } + + bool isabs_posix(const std::string & s) + { + return pystring::startswith(s, "/"); + } + + bool isabs(const std::string & path) + { +#ifdef WINDOWS + return isabs_nt(path); +#else + return isabs_posix(path); +#endif + } + + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + + std::string abspath_nt(const std::string & path, const std::string & cwd) + { + std::string p = path; + if(!isabs_nt(p)) p = join_nt(cwd, p); + return normpath_nt(p); + } + + std::string abspath_posix(const std::string & path, const std::string & cwd) + { + std::string p = path; + if(!isabs_posix(p)) p = join_posix(cwd, p); + return normpath_posix(p); + } + + std::string abspath(const std::string & path, const std::string & cwd) + { +#ifdef WINDOWS + return abspath_nt(path, cwd); +#else + return abspath_posix(path, cwd); +#endif + } + + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + + std::string join_nt(const std::vector< std::string > & paths) + { + if(paths.empty()) return ""; + if(paths.size() == 1) return paths[0]; + + std::string path = paths[0]; + + for(unsigned int i=1; i<paths.size(); ++i) + { + std::string b = paths[i]; + + bool b_nts = false; + if(path.empty()) + { + b_nts = true; + } + else if(isabs_nt(b)) + { + // This probably wipes out path so far. However, it's more + // complicated if path begins with a drive letter: + // 1. join('c:', '/a') == 'c:/a' + // 2. join('c:/', '/a') == 'c:/a' + // But + // 3. join('c:/a', '/b') == '/b' + // 4. join('c:', 'd:/') = 'd:/' + // 5. join('c:/', 'd:/') = 'd:/' + + if( (pystring::slice(path, 1, 2) != ":") || + (pystring::slice(b, 1, 2) == ":") ) + { + // Path doesnt start with a drive letter + b_nts = true; + } + // Else path has a drive letter, and b doesn't but is absolute. + else if((path.size()>3) || + ((path.size()==3) && !pystring::endswith(path, "/") && !pystring::endswith(path, "\\"))) + { + b_nts = true; + } + } + + if(b_nts) + { + path = b; + } + else + { + // Join, and ensure there's a separator. + // assert len(path) > 0 + if( pystring::endswith(path, "/") || pystring::endswith(path, "\\")) + { + if(pystring::startswith(b,"/") || pystring::startswith(b,"\\")) + { + path += pystring::slice(b, 1); + } + else + { + path += b; + } + } + else if(pystring::endswith(path, ":")) + { + path += b; + } + else if(!b.empty()) + { + if(pystring::startswith(b,"/") || pystring::startswith(b,"\\")) + { + path += b; + } + else + { + path += "\\" + b; + } + } + else + { + // path is not empty and does not end with a backslash, + // but b is empty; since, e.g., split('a/') produces + // ('a', ''), it's best if join() adds a backslash in + // this case. + path += "\\"; + } + } + } + + return path; + } + + // Join two or more pathname components, inserting "\\" as needed. + std::string join_nt(const std::string & a, const std::string & b) + { + std::vector< std::string > paths(2); + paths[0] = a; + paths[1] = b; + return join_nt(paths); + } + + // Join pathnames. + // If any component is an absolute path, all previous path components + // will be discarded. + // Ignore the previous parts if a part is absolute. + // Insert a '/' unless the first part is empty or already ends in '/'. + + std::string join_posix(const std::vector< std::string > & paths) + { + if(paths.empty()) return ""; + if(paths.size() == 1) return paths[0]; + + std::string path = paths[0]; + + for(unsigned int i=1; i<paths.size(); ++i) + { + std::string b = paths[i]; + if(pystring::startswith(b, "/")) + { + path = b; + } + else if(path.empty() || pystring::endswith(path, "/")) + { + path += b; + } + else + { + path += "/" + b; + } + } + + return path; + } + + std::string join_posix(const std::string & a, const std::string & b) + { + std::vector< std::string > paths(2); + paths[0] = a; + paths[1] = b; + return join_posix(paths); + } + + std::string join(const std::string & path1, const std::string & path2) + { +#ifdef WINDOWS + return join_nt(path1, path2); +#else + return join_posix(path1, path2); +#endif + } + + + std::string join(const std::vector< std::string > & paths) + { +#ifdef WINDOWS + return join_nt(paths); +#else + return join_posix(paths); +#endif + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + + + // Split a pathname. + // Return (head, tail) where tail is everything after the final slash. + // Either part may be empty + + void split_nt(std::string & head, std::string & tail, const std::string & path) + { + std::string d, p; + splitdrive_nt(d, p, path); + + // set i to index beyond p's last slash + int i = (int)p.size(); + + while(i>0 && (p[i-1] != '\\') && (p[i-1] != '/')) + { + i = i - 1; + } + + head = pystring::slice(p,0,i); + tail = pystring::slice(p,i); // now tail has no slashes + + // remove trailing slashes from head, unless it's all slashes + std::string head2 = head; + while(!head2.empty() && ((pystring::slice(head2,-1) == "/") || + (pystring::slice(head2,-1) == "\\"))) + { + head2 = pystring::slice(head,0,-1); + } + + if(!head2.empty()) head = head2; + head = d + head; + } + + + // Split a path in head (everything up to the last '/') and tail (the + // rest). If the path ends in '/', tail will be empty. If there is no + // '/' in the path, head will be empty. + // Trailing '/'es are stripped from head unless it is the root. + + void split_posix(std::string & head, std::string & tail, const std::string & p) + { + int i = pystring::rfind(p, "/") + 1; + + head = pystring::slice(p,0,i); + tail = pystring::slice(p,i); + + if(!head.empty() && (head != pystring::mul("/", (int) head.size()))) + { + head = pystring::rstrip(head, "/"); + } + } + + void split(std::string & head, std::string & tail, const std::string & path) + { +#ifdef WINDOWS + return split_nt(head, tail, path); +#else + return split_posix(head, tail, path); +#endif + } + + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + + std::string basename_nt(const std::string & path) + { + std::string head, tail; + split_nt(head, tail, path); + return tail; + } + + std::string basename_posix(const std::string & path) + { + std::string head, tail; + split_posix(head, tail, path); + return tail; + } + + std::string basename(const std::string & path) + { +#ifdef WINDOWS + return basename_nt(path); +#else + return basename_posix(path); +#endif + } + + std::string dirname_nt(const std::string & path) + { + std::string head, tail; + split_nt(head, tail, path); + return head; + } + + std::string dirname_posix(const std::string & path) + { + std::string head, tail; + split_posix(head, tail, path); + return head; + } + + std::string dirname(const std::string & path) + { +#ifdef WINDOWS + return dirname_nt(path); +#else + return dirname_posix(path); +#endif + } + + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + + // Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B. + std::string normpath_nt(const std::string & p) + { + std::string path = p; + path = pystring::replace(path, "/","\\"); + + std::string prefix; + splitdrive_nt(prefix, path, path); + + // We need to be careful here. If the prefix is empty, and the path starts + // with a backslash, it could either be an absolute path on the current + // drive (\dir1\dir2\file) or a UNC filename (\\server\mount\dir1\file). It + // is therefore imperative NOT to collapse multiple backslashes blindly in + // that case. + // The code below preserves multiple backslashes when there is no drive + // letter. This means that the invalid filename \\\a\b is preserved + // unchanged, where a\\\b is normalised to a\b. It's not clear that there + // is any better behaviour for such edge cases. + + if(prefix.empty()) + { + // No drive letter - preserve initial backslashes + while(pystring::slice(path,0,1) == "\\") + { + prefix = prefix + "\\"; + path = pystring::slice(path,1); + } + } + else + { + // We have a drive letter - collapse initial backslashes + if(pystring::startswith(path, "\\")) + { + prefix = prefix + "\\"; + path = pystring::lstrip(path, "\\"); + } + } + + std::vector<std::string> comps; + pystring::split(path, comps, "\\"); + + int i = 0; + + while(i<(int)comps.size()) + { + if(comps[i].empty() || comps[i] == ".") + { + comps.erase(comps.begin()+i); + } + else if(comps[i] == "..") + { + if(i>0 && comps[i-1] != "..") + { + comps.erase(comps.begin()+i-1, comps.begin()+i+1); + i -= 1; + } + else if(i == 0 && pystring::endswith(prefix, "\\")) + { + comps.erase(comps.begin()+i); + } + else + { + i += 1; + } + } + else + { + i += 1; + } + } + + // If the path is now empty, substitute '.' + if(prefix.empty() && comps.empty()) + { + comps.push_back("."); + } + + return prefix + pystring::join("\\", comps); + } + + // Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B. + // It should be understood that this may change the meaning of the path + // if it contains symbolic links! + // Normalize path, eliminating double slashes, etc. + + std::string normpath_posix(const std::string & p) + { + if(p.empty()) return "."; + + std::string path = p; + + int initial_slashes = pystring::startswith(path,"/") ? 1 : 0; + + // POSIX allows one or two initial slashes, but treats three or more + // as single slash. + + if (initial_slashes && pystring::startswith(path,"//") + && !pystring::startswith(path,"///")) + initial_slashes = 2; + + std::vector<std::string> comps, new_comps; + pystring::split(path, comps, "/"); + + for(unsigned int i=0; i<comps.size(); ++i) + { + std::string comp = comps[i]; + if(comp.empty() || comp == ".") + continue; + + if( (comp != "..") || ((initial_slashes == 0) && new_comps.empty()) || + (!new_comps.empty() && new_comps[new_comps.size()-1] == "..")) + { + new_comps.push_back(comp); + } + else if (!new_comps.empty()) + { + new_comps.pop_back(); + } + } + + path = pystring::join("/", new_comps); + + if (initial_slashes > 0) + path = pystring::mul("/",initial_slashes) + path; + + if(path.empty()) return "."; + return path; + } + + std::string normpath(const std::string & path) + { +#ifdef WINDOWS + return normpath_nt(path); +#else + return normpath_posix(path); +#endif + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// + /// + + // Split the extension from a pathname. + // Extension is everything from the last dot to the end, ignoring + // leading dots. Returns "(root, ext)"; ext may be empty. + // It is always true that root + ext == p + + void splitext_generic(std::string & root, std::string & ext, + const std::string & p, + const std::string & sep, + const std::string & altsep, + const std::string & extsep) + { + int sepIndex = pystring::rfind(p, sep); + if(!altsep.empty()) + { + int altsepIndex = pystring::rfind(p, altsep); + sepIndex = std::max(sepIndex, altsepIndex); + } + + int dotIndex = pystring::rfind(p, extsep); + if(dotIndex > sepIndex) + { + // Skip all leading dots + int filenameIndex = sepIndex + 1; + + while(filenameIndex < dotIndex) + { + if(pystring::slice(p,filenameIndex) != extsep) + { + root = pystring::slice(p, 0, dotIndex); + ext = pystring::slice(p, dotIndex); + return; + } + + filenameIndex += 1; + } + } + + root = p; + ext = ""; + } + + void splitext_nt(std::string & root, std::string & ext, const std::string & path) + { + return splitext_generic(root, ext, path, + "\\", "/", "."); + } + + void splitext_posix(std::string & root, std::string & ext, const std::string & path) + { + return splitext_generic(root, ext, path, + "/", "", "."); + } + + void splitext(std::string & root, std::string & ext, const std::string & path) + { +#ifdef WINDOWS + return splitext_nt(root, ext, path); +#else + return splitext_posix(root, ext, path); +#endif + } + +} // namespace path +} // namespace os + + +}//namespace pystring + +} +OCIO_NAMESPACE_EXIT diff --git a/src/core/pystring/pystring.h b/src/core/pystring/pystring.h new file mode 100644 index 0000000..f0729db --- /dev/null +++ b/src/core/pystring/pystring.h @@ -0,0 +1,438 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2008-2010, Sony Pictures Imageworks Inc +// 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 the organization 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. +/////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDED_OCIO_PYSTRING_H +#define INCLUDED_OCIO_PYSTRING_H + +#include <OpenColorIO/OpenColorIO.h> + +#include <string> +#include <vector> + +OCIO_NAMESPACE_ENTER +{ +// Version 1.1.2 +// https://github.com/imageworks/pystring/tarball/v1.1.2 + +namespace pystring +{ + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @mainpage pystring + /// + /// This is a set of functions matching the interface and behaviors of python string methods + /// (as of python 2.3) using std::string. + /// + /// Overlapping functionality ( such as index and slice/substr ) of std::string is included + /// to match python interfaces. + /// + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @defgroup functions pystring + /// @{ + + + #define MAX_32BIT_INT 2147483647 + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return a copy of the string with only its first character capitalized. + /// + std::string capitalize( const std::string & str ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return centered in a string of length width. Padding is done using spaces. + /// + std::string center( const std::string & str, int width ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return the number of occurrences of substring sub in string S[start:end]. Optional + /// arguments start and end are interpreted as in slice notation. + /// + int count( const std::string & str, const std::string & substr, int start = 0, int end = MAX_32BIT_INT); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return True if the string ends with the specified suffix, otherwise return False. With + /// optional start, test beginning at that position. With optional end, stop comparing at that position. + /// + bool endswith( const std::string & str, const std::string & suffix, int start = 0, int end = MAX_32BIT_INT ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return a copy of the string where all tab characters are expanded using spaces. If tabsize + /// is not given, a tab size of 8 characters is assumed. + /// + std::string expandtabs( const std::string & str, int tabsize = 8); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return the lowest index in the string where substring sub is found, such that sub is + /// contained in the range [start, end). Optional arguments start and end are interpreted as + /// in slice notation. Return -1 if sub is not found. + /// + int find( const std::string & str, const std::string & sub, int start = 0, int end = MAX_32BIT_INT ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Synonym of find right now. Python version throws exceptions. This one currently doesn't + /// + int index( const std::string & str, const std::string & sub, int start = 0, int end = MAX_32BIT_INT ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return true if all characters in the string are alphanumeric and there is at least one + /// character, false otherwise. + /// + bool isalnum( const std::string & str ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return true if all characters in the string are alphabetic and there is at least one + /// character, false otherwise + /// + bool isalpha( const std::string & str ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return true if all characters in the string are digits and there is at least one + /// character, false otherwise. + /// + bool isdigit( const std::string & str ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return true if all cased characters in the string are lowercase and there is at least one + /// cased character, false otherwise. + /// + bool islower( const std::string & str ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return true if there are only whitespace characters in the string and there is at least + /// one character, false otherwise. + /// + bool isspace( const std::string & str ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return true if the string is a titlecased string and there is at least one character, + /// i.e. uppercase characters may only follow uncased characters and lowercase characters only + /// cased ones. Return false otherwise. + /// + bool istitle( const std::string & str ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return true if all cased characters in the string are uppercase and there is at least one + /// cased character, false otherwise. + /// + bool isupper( const std::string & str ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return a string which is the concatenation of the strings in the sequence seq. + /// The separator between elements is the str argument + /// + std::string join( const std::string & str, const std::vector< std::string > & seq ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return the string left justified in a string of length width. Padding is done using + /// spaces. The original string is returned if width is less than str.size(). + /// + std::string ljust( const std::string & str, int width ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return a copy of the string converted to lowercase. + /// + std::string lower( const std::string & str ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return a copy of the string with leading characters removed. If chars is omitted or None, + /// whitespace characters are removed. If given and not "", chars must be a string; the + /// characters in the string will be stripped from the beginning of the string this method + /// is called on (argument "str" ). + /// + std::string lstrip( const std::string & str, const std::string & chars = "" ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return a copy of the string, concatenated N times, together. + /// Corresponds to the __mul__ operator. + /// + std::string mul( const std::string & str, int n); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Split the string around first occurance of sep. + /// Three strings will always placed into result. If sep is found, the strings will + /// be the text before sep, sep itself, and the remaining text. If sep is + /// not found, the original string will be returned with two empty strings. + /// + void partition( const std::string & str, const std::string & sep, std::vector< std::string > & result ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return a copy of the string with all occurrences of substring old replaced by new. If + /// the optional argument count is given, only the first count occurrences are replaced. + /// + std::string replace( const std::string & str, const std::string & oldstr, const std::string & newstr, int count = -1); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return the highest index in the string where substring sub is found, such that sub is + /// contained within s[start,end]. Optional arguments start and end are interpreted as in + /// slice notation. Return -1 on failure. + /// + int rfind( const std::string & str, const std::string & sub, int start = 0, int end = MAX_32BIT_INT ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Currently a synonym of rfind. The python version raises exceptions. This one currently + /// does not + /// + int rindex( const std::string & str, const std::string & sub, int start = 0, int end = MAX_32BIT_INT ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return the string right justified in a string of length width. Padding is done using + /// spaces. The original string is returned if width is less than str.size(). + /// + std::string rjust( const std::string & str, int width); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Split the string around last occurance of sep. + /// Three strings will always placed into result. If sep is found, the strings will + /// be the text before sep, sep itself, and the remaining text. If sep is + /// not found, the original string will be returned with two empty strings. + /// + void rpartition( const std::string & str, const std::string & sep, std::vector< std::string > & result ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return a copy of the string with trailing characters removed. If chars is "", whitespace + /// characters are removed. If not "", the characters in the string will be stripped from the + /// end of the string this method is called on. + /// + std::string rstrip( const std::string & str, const std::string & chars = "" ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Fills the "result" list with the words in the string, using sep as the delimiter string. + /// If maxsplit is > -1, at most maxsplit splits are done. If sep is "", + /// any whitespace string is a separator. + /// + void split( const std::string & str, std::vector< std::string > & result, const std::string & sep = "", int maxsplit = -1); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Fills the "result" list with the words in the string, using sep as the delimiter string. + /// Does a number of splits starting at the end of the string, the result still has the + /// split strings in their original order. + /// If maxsplit is > -1, at most maxsplit splits are done. If sep is "", + /// any whitespace string is a separator. + /// + void rsplit( const std::string & str, std::vector< std::string > & result, const std::string & sep = "", int maxsplit = -1); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return a list of the lines in the string, breaking at line boundaries. Line breaks + /// are not included in the resulting list unless keepends is given and true. + /// + void splitlines( const std::string & str, std::vector< std::string > & result, bool keepends = false ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return True if string starts with the prefix, otherwise return False. With optional start, + /// test string beginning at that position. With optional end, stop comparing string at that + /// position + /// + bool startswith( const std::string & str, const std::string & prefix, int start = 0, int end = MAX_32BIT_INT ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return a copy of the string with leading and trailing characters removed. If chars is "", + /// whitespace characters are removed. If given not "", the characters in the string will be + /// stripped from the both ends of the string this method is called on. + /// + std::string strip( const std::string & str, const std::string & chars = "" ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return a copy of the string with uppercase characters converted to lowercase and vice versa. + /// + std::string swapcase( const std::string & str ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return a titlecased version of the string: words start with uppercase characters, + /// all remaining cased characters are lowercase. + /// + std::string title( const std::string & str ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return a copy of the string where all characters occurring in the optional argument + /// deletechars are removed, and the remaining characters have been mapped through the given + /// translation table, which must be a string of length 256. + /// + std::string translate( const std::string & str, const std::string & table, const std::string & deletechars = ""); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return a copy of the string converted to uppercase. + /// + std::string upper( const std::string & str ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return the numeric string left filled with zeros in a string of length width. The original + /// string is returned if width is less than str.size(). + /// + std::string zfill( const std::string & str, int width ); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief function matching python's slice functionality. + /// + std::string slice( const std::string & str, int start = 0, int end = MAX_32BIT_INT); + + /// + /// @ } + /// + + +namespace os +{ +namespace path +{ + // All of the function below have three versions. + // Example: + // join(...) + // join_nt(...) + // join_posix(...) + // + // The regular function dispatches to the other versions - based on the OS + // at compile time - to match the result you'd get from the python + // interepreter on the same operating system + // + // Should you want to 'lock off' to a particular version of the string + // manipulation across *all* operating systems, use the version with the + // _OS you are interested in. I.e., you can use posix style path joining, + // even on Windows, with join_posix. + // + // The naming, (nt, posix) matches the cpython source implementation. + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @defgroup functions pystring::os::path + /// @{ + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return the base name of pathname path. This is the second half of the pair returned + /// by split(path). Note that the result of this function is different from the Unix basename + /// program; where basename for '/foo/bar/' returns 'bar', the basename() function returns an + /// empty string (''). + + std::string basename(const std::string & path); + std::string basename_nt(const std::string & path); + std::string basename_posix(const std::string & path); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return the directory name of pathname path. This is the first half of the pair + /// returned by split(path). + + std::string dirname(const std::string & path); + std::string dirname_nt(const std::string & path); + std::string dirname_posix(const std::string & path); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return True if path is an absolute pathname. On Unix, that means it begins with a + /// slash, on Windows that it begins with a (back)slash after chopping off a potential drive + /// letter. + + bool isabs(const std::string & path); + bool isabs_nt(const std::string & path); + bool isabs_posix(const std::string & s); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Return a normalized absolutized version of the pathname path. + /// + /// NOTE: This differs from the interface of the python equivalent in that it requires you + /// to pass in the current working directory as an argument. + + std::string abspath(const std::string & path, const std::string & cwd); + std::string abspath_nt(const std::string & path, const std::string & cwd); + std::string abspath_posix(const std::string & path, const std::string & cwd); + + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Join one or more path components intelligently. If any component is an absolute + /// path, all previous components (on Windows, including the previous drive letter, if there + /// was one) are thrown away, and joining continues. The return value is the concatenation of + /// path1, and optionally path2, etc., with exactly one directory separator (os.sep) inserted + /// between components, unless path2 is empty. Note that on Windows, since there is a current + /// directory for each drive, os.path.join("c:", "foo") represents a path relative to the + /// current directory on drive C: (c:foo), not c:\foo. + + /// This dispatches based on the compilation OS + std::string join(const std::string & path1, const std::string & path2); + std::string join_nt(const std::string & path1, const std::string & path2); + std::string join_posix(const std::string & path1, const std::string & path2); + + std::string join(const std::vector< std::string > & paths); + std::string join_nt(const std::vector< std::string > & paths); + std::string join_posix(const std::vector< std::string > & paths); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Normalize a pathname. This collapses redundant separators and up-level references + /// so that A//B, A/B/, A/./B and A/foo/../B all become A/B. It does not normalize the case + /// (use normcase() for that). On Windows, it converts forward slashes to backward slashes. + /// It should be understood that this may change the meaning of the path if it contains + /// symbolic links! + + std::string normpath(const std::string & path); + std::string normpath_nt(const std::string & path); + std::string normpath_posix(const std::string & path); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Split the pathname path into a pair, (head, tail) where tail is the last pathname + /// component and head is everything leading up to that. The tail part will never contain a + /// slash; if path ends in a slash, tail will be empty. If there is no slash in path, head + /// will be empty. If path is empty, both head and tail are empty. Trailing slashes are + /// stripped from head unless it is the root (one or more slashes only). In all cases, + /// join(head, tail) returns a path to the same location as path (but the strings may + /// differ). + + void split(std::string & head, std::string & tail, const std::string & path); + void split_nt(std::string & head, std::string & tail, const std::string & path); + void split_posix(std::string & head, std::string & tail, const std::string & path); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Split the pathname path into a pair (drive, tail) where drive is either a drive + /// specification or the empty string. On systems which do not use drive specifications, + /// drive will always be the empty string. In all cases, drive + tail will be the same as + /// path. + + void splitdrive(std::string & drivespec, std::string & pathspec, const std::string & path); + void splitdrive_nt(std::string & drivespec, std::string & pathspec, const std::string & p); + void splitdrive_posix(std::string & drivespec, std::string & pathspec, const std::string & path); + + ////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Split the pathname path into a pair (root, ext) such that root + ext == path, and + /// ext is empty or begins with a period and contains at most one period. Leading periods on + /// the basename are ignored; splitext('.cshrc') returns ('.cshrc', ''). + + void splitext(std::string & root, std::string & ext, const std::string & path); + void splitext_nt(std::string & root, std::string & ext, const std::string & path); + void splitext_posix(std::string & root, std::string & ext, const std::string & path); + + /// + /// @ } + /// +} // namespace path +} // namespace os + +} // namespace pystring +} +OCIO_NAMESPACE_EXIT + +#endif |