Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
90fb3ca
- Exploratory work on SMPTE CLF format, currently supports reading th…
cozdas Jan 28, 2026
845dba9
- Modifying the CLF writer to handle the SMPTE variant in a basic for…
cozdas Jan 29, 2026
21a735d
- error out if version and xmlns are present at the same time, "only …
cozdas Jan 29, 2026
bf7b0dd
- Fix the tests
cozdas Jan 29, 2026
991f458
- Added ST2136-1:2024 compliant ID generation
cozdas Jan 31, 2026
a41911e
- removing the st2136 schema file.
cozdas Jan 31, 2026
c3e08ad
- Adding the missing test file
cozdas Jan 31, 2026
f54a6f8
- Changed the ociomakeclf format specifiying argument from --smpte to…
cozdas Feb 3, 2026
d1bd6cb
- removing the okld smpte test files
cozdas Feb 5, 2026
8440fa5
- Backing up from using separate file formats for Academy and SMPTE CLF
cozdas Feb 5, 2026
07fed48
- Add format restriction to version string checks.
cozdas Feb 6, 2026
614a22f
Fix the tests
cozdas Feb 6, 2026
163308d
- Adding bare-bone tests exercising the newly added clf files.
cozdas Feb 6, 2026
c12e375
- minor improvement to the tests
cozdas Feb 7, 2026
60bf463
- Added GroupTransform::GetCacheID() function to generate UUID based …
cozdas Feb 8, 2026
6f34f24
- Fix the test: we can now load the namespaces.clf.
cozdas Feb 8, 2026
83bae93
- Undo adding getCacheID() to group transform
cozdas Feb 10, 2026
1c3751b
- various fixes
cozdas Feb 10, 2026
c647d35
- Name space stripping is now compile-time enabled all the time but n…
cozdas Feb 10, 2026
5b95961
- Make the picky compilers happy.
cozdas Feb 10, 2026
cf788cd
- Signature.clf test file is removed until its finalized.
cozdas Feb 10, 2026
e71ac36
- typo fixes
cozdas Feb 11, 2026
65fb7fb
- Make the descriptor parsers robust for receiving the strings in pie…
cozdas Feb 11, 2026
06aa892
- Addressing the review comments.
cozdas Feb 11, 2026
c5e444f
Trying to change the line endings of this file back to CRLF
Feb 11, 2026
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
Loading