Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions include/OpenColorIO/OpenColorTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,13 @@ extern OCIOEXPORT const char * METADATA_NAME;
*/
extern OCIOEXPORT const char * METADATA_ID;

/**
* An ID when stored as an XML element rather than as an attribute. This is the
* preferred mechanism in the SMPTE ST 2036-1 version of the CLF format. If
* present, it is only available from the top-level FormatMetadata.
*/
extern OCIOEXPORT const char * METADATA_ID_ELEMENT;

/*!rst::
Caches
******
Expand Down
24 changes: 24 additions & 0 deletions src/OpenColorIO/HashUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Copyright Contributors to the OpenColorIO Project.

#include <sstream>
#include <iomanip>

#include <OpenColorIO/OpenColorIO.h>

Expand All @@ -25,4 +26,27 @@ std::string CacheIDHash(const char * array, std::size_t size)
return oss.str();
}

std::string CacheIDHashUUID(const char * array, std::size_t size)
{
XXH128_hash_t hash = XXH3_128bits(array, size);

// Make sure that we have full, zero-padded 32 chars.
std::stringstream oss;
oss << std::hex << std::setfill('0');
oss << std::setw(16) << hash.high64;
oss << std::setw(16) << hash.low64;

// Format into 8-4-4-4-12 form.
std::string hex = oss.str();
std::string uuid =
hex.substr(0, 8) + "-" +
hex.substr(8, 4) + "-" +
hex.substr(12, 4) + "-" +
hex.substr(16, 4) + "-" +
hex.substr(20, 12);

return uuid;
}


} // namespace OCIO_NAMESPACE
4 changes: 4 additions & 0 deletions src/OpenColorIO/HashUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ namespace OCIO_NAMESPACE

std::string CacheIDHash(const char * array, std::size_t size);

// Generates 128 bit UUID in the form of 8-4-4-4-12 using the hash of the passed
// string.
std::string CacheIDHashUUID(const char * array, std::size_t size);

} // namespace OCIO_NAMESPACE

#endif
12 changes: 3 additions & 9 deletions src/OpenColorIO/Processor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,15 +330,9 @@ const char * Processor::Impl::getCacheID() const

if(!m_cacheID.empty()) return m_cacheID.c_str();

if(m_ops.empty())
{
m_cacheID = "<NOOP>";
}
else
{
const std::string fullstr = m_ops.getCacheID();
m_cacheID = CacheIDHash(fullstr.c_str(), fullstr.size());
}
// Note: empty ops vector will also create a UUID.
const std::string fullstr = m_ops.getCacheID();
m_cacheID = CacheIDHashUUID(fullstr.c_str(), fullstr.size());

return m_cacheID.c_str();
}
Expand Down
150 changes: 113 additions & 37 deletions src/OpenColorIO/fileformats/FileFormatCTF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "TransformBuilder.h"
#include "transforms/FileTransform.h"
#include "utils/StringUtils.h"
#include "HashUtils.h"


/*
Expand All @@ -40,7 +41,14 @@ to agree on a common LUT format for this industry. Support for CLF is a
requirement in order to obtain ACES Logo Certification from the Academy (in
several product categories). CLF files are expressed using XML. The spec,
AMPAS S-2014-006, is available from:
<https://acescentral.com/t/aces-documentation/53>
<https://docs.acescentral.com/clf/introduction/>
In 2026, SMPTE will publish ST 2136-1 to standardize the Academy/ASC format.
The main change is how versions are declared. The SMPTE spec sets the xmlns
attribute of the ProcessList to a specific value rather than using the
compCLFversion attribute. Since the differences are so minimal, OCIO writes
both the xmlns and compCLFversion in order to maximize compatibility with
different readers.
The Autodesk CTF format is based on the Academy/ASC CLF format and adds several
operators that allow higher quality results by avoiding the need to bake
Expand Down Expand Up @@ -146,27 +154,36 @@ class LocalFileFormat : public FileFormat

void LocalFileFormat::getFormatInfo(FormatInfoVec & formatInfoVec) const
{
FormatInfo info;
info.name = FILEFORMAT_CLF;
info.extension = "clf";
info.capabilities = FormatCapabilityFlags(FORMAT_CAPABILITY_READ |
FORMAT_CAPABILITY_BAKE |
FORMAT_CAPABILITY_WRITE);
info.bake_capabilities = FormatBakeFlags(FORMAT_BAKE_CAPABILITY_3DLUT |
FORMAT_BAKE_CAPABILITY_1DLUT |
FORMAT_BAKE_CAPABILITY_1D_3D_LUT);
formatInfoVec.push_back(info);

FormatInfo info2;
info2.name = FILEFORMAT_CTF;
info2.extension = "ctf";
info2.capabilities = FormatCapabilityFlags(FORMAT_CAPABILITY_READ |
FORMAT_CAPABILITY_BAKE |
FORMAT_CAPABILITY_WRITE);
info.bake_capabilities = FormatBakeFlags(FORMAT_BAKE_CAPABILITY_3DLUT |
FORMAT_BAKE_CAPABILITY_1DLUT |
FORMAT_BAKE_CAPABILITY_1D_3D_LUT);
formatInfoVec.push_back(info2);
// CLF - Academy/ASC & SMPTE uses the same format
{
FormatInfo info;
info.name = FILEFORMAT_CLF;
info.extension = "clf";
info.capabilities = FormatCapabilityFlags(FORMAT_CAPABILITY_READ |
FORMAT_CAPABILITY_BAKE |
FORMAT_CAPABILITY_WRITE);

info.bake_capabilities = FormatBakeFlags( FORMAT_BAKE_CAPABILITY_3DLUT |
FORMAT_BAKE_CAPABILITY_1DLUT |
FORMAT_BAKE_CAPABILITY_1D_3D_LUT);
formatInfoVec.push_back(info);
}

// CTF
{
FormatInfo info;
info.name = FILEFORMAT_CTF;
info.extension = "ctf";
info.capabilities = FormatCapabilityFlags(FORMAT_CAPABILITY_READ |
FORMAT_CAPABILITY_BAKE |
FORMAT_CAPABILITY_WRITE);

info.bake_capabilities = FormatBakeFlags( FORMAT_BAKE_CAPABILITY_3DLUT |
FORMAT_BAKE_CAPABILITY_1DLUT |
FORMAT_BAKE_CAPABILITY_1D_3D_LUT);

formatInfoVec.push_back(info);
}
}

class XMLParserHelper
Expand Down Expand Up @@ -227,7 +244,7 @@ class XMLParserHelper
throwMessage(error);
}

if (pT->getOps().empty())
if (pT->getOpDataVec().empty())
{
static const std::string error(
"CTF/CLF parsing error: No color operator in file.");
Expand Down Expand Up @@ -420,7 +437,7 @@ class XMLParserHelper

// Start the parsing of one element.
static void StartElementHandler(void * userData,
const XML_Char * name,
const XML_Char * name_full,
const XML_Char ** atts)
{
static const std::vector<const char *> rangeSubElements = {
Expand Down Expand Up @@ -478,7 +495,7 @@ class XMLParserHelper

XMLParserHelper * pImpl = (XMLParserHelper*)userData;

if (!pImpl || !name || !*name)
if (!pImpl || !name_full || !*name_full)
{
if (!pImpl)
{
Expand All @@ -490,6 +507,14 @@ class XMLParserHelper
}
}

// Strip the name spaces
const char *name = name_full;
if (pImpl->m_keepNamespaces <= 0)
{
name = strrchr(name_full, ':');
name = name ? (name+1) : name_full;
}

if (!pImpl->m_elms.empty())
{
// Check if we are still processing a metadata structure.
Expand Down Expand Up @@ -717,6 +742,16 @@ class XMLParserHelper
pImpl->getXmLineNumber(),
pImpl->getXmlFilename()));
}
else if (SupportedElement(name, pElt, TAG_ID, "", recognizedName))
{
pImpl->m_elms.push_back(
std::make_shared<CTFReaderIdElt>(
name,
pContainer,
pImpl->getXmLineNumber(),
pImpl->getXmlFilename()));
}

// Dynamic Property is valid under any operator parent. First
// test if the tag is supported to set the recognizedName
// accordingly, without testing for parents. Test for the
Expand Down Expand Up @@ -790,6 +825,8 @@ class XMLParserHelper
else if (SupportedElement(name, pElt, TAG_INFO,
TAG_PROCESS_LIST, recognizedName))
{
pImpl->m_keepNamespaces++;

pImpl->m_elms.push_back(
std::make_shared<CTFReaderInfoElt>(
name,
Expand Down Expand Up @@ -992,14 +1029,22 @@ class XMLParserHelper

// End the parsing of one element.
static void EndElementHandler(void * userData,
const XML_Char * name)
const XML_Char * name_full)
{
XMLParserHelper * pImpl = (XMLParserHelper*)userData;
if (!pImpl || !name || !*name)
if (!pImpl || !name_full || !*name_full)
{
throw Exception("CTF/CLF internal parsing error.");
}

// Strip the name spaces
const char *name = name_full;
if (pImpl->m_keepNamespaces <= 0)
{
name = strrchr(name_full, ':');
name = name ? (name+1) : name_full;
}

// Is the expected element present?
auto pElt(pImpl->m_elms.back());
if (!pElt.get())
Expand Down Expand Up @@ -1054,6 +1099,12 @@ class XMLParserHelper
}
}

// Exiting the info element; decrease keep namespace counter.
if(std::dynamic_pointer_cast<CTFReaderInfoElt>(pElt))
{
pImpl->m_keepNamespaces--;
}

pElt->end();
}

Expand Down Expand Up @@ -1160,15 +1211,18 @@ class XMLParserHelper
bool m_isCLF;
XmlReaderElementStack m_elms; // Parsing stack
CTFReaderTransformPtr m_transform;
int m_keepNamespaces = 0; // if >0, name spaces will be preserved

};

bool isLoadableCTF(std::istream & istream)
{
std::streampos curPos = istream.tellg();

const unsigned limit(5 * 1024); // 5 kilobytes.
const char *pattern = "<ProcessList";
constexpr unsigned limit(5 * 1024); // 5 kilobytes.
constexpr const char *pattern1 = "<ProcessList";
constexpr const char *pattern2 = ":ProcessList";

bool foundPattern = false;
unsigned sizeProcessed(0);
char line[limit + 1];
Expand All @@ -1180,7 +1234,14 @@ bool isLoadableCTF(std::istream & istream)
while (istream.good() && !foundPattern && (sizeProcessed < limit))
{
istream.getline(line, limit);
if (strstr(line, pattern)) foundPattern = true;
if (strstr(line, pattern1))
{
foundPattern = true;
}
else if (strstr(line, pattern2))
{
foundPattern = true;
}
sizeProcessed += (unsigned)strlen(line);
}
}
Expand Down Expand Up @@ -1308,7 +1369,7 @@ void LocalFileFormat::buildFileOps(OpRcPtrVec & ops,
cachedFile->m_transform->toMetadata(processorData);

// Resolve reference path using context and load referenced files.
const ConstOpDataVec & opDataVec = cachedFile->m_transform->getOps();
const ConstOpDataVec & opDataVec = cachedFile->m_transform->getOpDataVec();

// Try to use the FileTransform interpolation for any Lut1D or Lut3D that does not specify
// an interpolation in the CTF itself. If the interpolation can not be used, ignore it.
Expand Down Expand Up @@ -1558,14 +1619,19 @@ void LocalFileFormat::write(const ConstConfigRcPtr & config,
const std::string & formatName,
std::ostream & ostream) const
{
bool isCLF = false;

TransformWriter::SubFormat subFormat{TransformWriter::SubFormat::FORMAT_UNKNOWN};

if (Platform::Strcasecmp(formatName.c_str(), FILEFORMAT_CLF) == 0)
{
isCLF = true;
}
else if (Platform::Strcasecmp(formatName.c_str(), FILEFORMAT_CTF) != 0)
subFormat = TransformWriter::SubFormat::FORMAT_CLF;
}
else if (Platform::Strcasecmp(formatName.c_str(), FILEFORMAT_CTF) == 0)
{
subFormat = TransformWriter::SubFormat::FORMAT_CTF;
}
else
{
// Neither a clf nor a ctf.
std::ostringstream os;
os << "Error: CLF/CTF writer does not also write format " << formatName << ".";
throw Exception(os.str().c_str());
Expand All @@ -1583,11 +1649,21 @@ void LocalFileFormat::write(const ConstConfigRcPtr & config,
const FormatMetadataImpl & metadata = group.getFormatMetadata();
CTFReaderTransformPtr transform = std::make_shared<CTFReaderTransform>(ops, metadata);

// It it doesn't have an id, create one based on the op list.
if (transform->getID().empty())
{
std::string opId = ops.getCacheID();

std::ostringstream ss;
ss << "urn:uuid:" << CacheIDHashUUID(opId.c_str(), opId.size());
transform->setID(ss.str().c_str());
}

// Write XML Header.
ostream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
XmlFormatter fmt(ostream);

TransformWriter writer(fmt, transform, isCLF);
TransformWriter writer(fmt, transform, subFormat);
writer.write();
}

Expand Down
3 changes: 3 additions & 0 deletions src/OpenColorIO/fileformats/FormatMetadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ const char * METADATA_INFO = "Info";
const char * METADATA_INPUT_DESCRIPTOR = "InputDescriptor";
const char * METADATA_OUTPUT_DESCRIPTOR = "OutputDescriptor";

// CLF XML elements described in ST2136-1
const char * METADATA_ID_ELEMENT = "Id";

// NAME and ID are CLF XML attributes described in S-2014-006.
const char * METADATA_NAME = "name";
const char * METADATA_ID = "id";
Expand Down
2 changes: 1 addition & 1 deletion src/OpenColorIO/fileformats/cdl/CDLParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class CDLParser
{
public:
explicit CDLParser(const std::string& xmlFile);
virtual ~CDLParser();
~CDLParser();

void parse(std::istream & istream) const;

Expand Down
Loading