Cryptomatte: Manifest Parsing.

This patch adds manifest parsing to Cryptomatte. Normally when loading
cryptomatte layer from an OpenEXR file the manifest contains data to
convert a hash to its original name of the object/material. In the
future we want to use this to support lookup of cryptomatte
hashes and show it to the user.

Currently this logic isn't available to users (for now), but is required
by D3959 where a new cryptomatte workflow is implemented.
This commit is contained in:
Jeroen Bakker 2021-02-26 14:13:15 +01:00
parent c489bb7c01
commit 87ace46827
9 changed files with 348 additions and 139 deletions

View File

@ -50,8 +50,7 @@ uint32_t BKE_cryptomatte_asset_hash(struct CryptomatteSession *session,
float BKE_cryptomatte_hash_to_float(uint32_t cryptomatte_hash);
char *BKE_cryptomatte_entries_to_matte_id(struct NodeCryptomatte *node_storage);
void BKE_cryptomatte_matte_id_to_entries(const struct Main *bmain,
struct NodeCryptomatte *node_storage,
void BKE_cryptomatte_matte_id_to_entries(struct NodeCryptomatte *node_storage,
const char *matte_id);
void BKE_cryptomatte_store_metadata(struct CryptomatteSession *session,

View File

@ -23,11 +23,15 @@
#pragma once
#include <optional>
#include <string>
#include "BLI_map.hh"
#include "BLI_string_ref.hh"
namespace blender {
struct ID;
namespace blender::bke::cryptomatte {
/* Format to a cryptomatte meta data key.
*
@ -56,4 +60,30 @@ std::string BKE_cryptomatte_meta_data_key(const StringRef layer_name,
*/
StringRef BKE_cryptomatte_extract_layer_name(const StringRef render_pass_name);
} // namespace blender
struct CryptomatteHash {
uint32_t hash;
CryptomatteHash(uint32_t hash);
CryptomatteHash(const char *name, const int name_len);
static CryptomatteHash from_hex_encoded(blender::StringRef hex_encoded);
std::string hex_encoded() const;
float float_encoded() const;
};
struct CryptomatteLayer {
blender::Map<std::string, CryptomatteHash> hashes;
#ifdef WITH_CXX_GUARDEDALLOC
MEM_CXX_CLASS_ALLOC_FUNCS("cryptomatte:CryptomatteLayer")
#endif
static std::unique_ptr<CryptomatteLayer> read_from_manifest(blender::StringRefNull manifest);
uint32_t add_ID(const struct ID &id);
void add_hash(blender::StringRef name, CryptomatteHash cryptomatte_hash);
std::string manifest();
std::optional<std::string> operator[](float encoded_hash) const;
};
} // namespace blender::bke::cryptomatte

View File

@ -35,7 +35,6 @@
#include "BLI_dynstr.h"
#include "BLI_hash_mm3.h"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_string.h"
#include "MEM_guardedalloc.h"
@ -47,61 +46,44 @@
#include <string>
#include <string_view>
struct CryptomatteLayer {
blender::Map<std::string, std::string> hashes;
#ifdef WITH_CXX_GUARDEDALLOC
MEM_CXX_CLASS_ALLOC_FUNCS("cryptomatte:CryptomatteLayer")
#endif
std::string encode_hash(uint32_t cryptomatte_hash)
{
std::stringstream encoded;
encoded << std::setfill('0') << std::setw(sizeof(uint32_t) * 2) << std::hex
<< cryptomatte_hash;
return encoded.str();
}
void add_hash(blender::StringRef name, uint32_t cryptomatte_hash)
{
add_encoded_hash(name, encode_hash(cryptomatte_hash));
}
void add_encoded_hash(blender::StringRef name, blender::StringRefNull cryptomatte_encoded_hash)
{
hashes.add_overwrite(name, cryptomatte_encoded_hash);
}
std::string manifest()
{
std::stringstream manifest;
bool is_first = true;
const blender::Map<std::string, std::string> &const_map = hashes;
manifest << "{";
for (blender::Map<std::string, std::string>::Item item : const_map.items()) {
if (is_first) {
is_first = false;
}
else {
manifest << ",";
}
manifest << quoted(item.key) << ":\"" << item.value << "\"";
}
manifest << "}";
return manifest.str();
}
};
struct CryptomatteSession {
CryptomatteLayer objects;
CryptomatteLayer assets;
CryptomatteLayer materials;
blender::bke::cryptomatte::CryptomatteLayer objects;
blender::bke::cryptomatte::CryptomatteLayer assets;
blender::bke::cryptomatte::CryptomatteLayer materials;
CryptomatteSession();
CryptomatteSession(const Main *bmain);
std::optional<std::string> operator[](float encoded_hash) const;
#ifdef WITH_CXX_GUARDEDALLOC
MEM_CXX_CLASS_ALLOC_FUNCS("cryptomatte:CryptomatteSession")
#endif
};
CryptomatteSession::CryptomatteSession()
{
}
CryptomatteSession::CryptomatteSession(const Main *bmain)
{
LISTBASE_FOREACH (ID *, id, &bmain->objects) {
objects.add_ID(*id);
}
LISTBASE_FOREACH (ID *, id, &bmain->materials) {
materials.add_ID(*id);
}
}
std::optional<std::string> CryptomatteSession::operator[](float encoded_hash) const
{
std::optional<std::string> result = objects[encoded_hash];
if (result) {
return result;
}
return materials[encoded_hash];
}
CryptomatteSession *BKE_cryptomatte_init(void)
{
CryptomatteSession *session = new CryptomatteSession();
@ -116,26 +98,13 @@ void BKE_cryptomatte_free(CryptomatteSession *session)
uint32_t BKE_cryptomatte_hash(const char *name, const int name_len)
{
uint32_t cryptohash_int = BLI_hash_mm3((const unsigned char *)name, name_len, 0);
return cryptohash_int;
}
static uint32_t cryptomatte_hash(CryptomatteLayer *layer, const ID *id)
{
const char *name = &id->name[2];
const int name_len = BLI_strnlen(name, MAX_NAME - 2);
uint32_t cryptohash_int = BKE_cryptomatte_hash(name, name_len);
if (layer != nullptr) {
layer->add_hash(blender::StringRef(name, name_len), cryptohash_int);
}
return cryptohash_int;
blender::bke::cryptomatte::CryptomatteHash hash(name, name_len);
return hash.hash;
}
uint32_t BKE_cryptomatte_object_hash(CryptomatteSession *session, const Object *object)
{
return cryptomatte_hash(&session->objects, &object->id);
return session->objects.add_ID(object->id);
}
uint32_t BKE_cryptomatte_material_hash(CryptomatteSession *session, const Material *material)
@ -143,7 +112,7 @@ uint32_t BKE_cryptomatte_material_hash(CryptomatteSession *session, const Materi
if (material == nullptr) {
return 0.0f;
}
return cryptomatte_hash(&session->materials, &material->id);
return session->materials.add_ID(material->id);
}
uint32_t BKE_cryptomatte_asset_hash(CryptomatteSession *session, const Object *object)
@ -152,54 +121,12 @@ uint32_t BKE_cryptomatte_asset_hash(CryptomatteSession *session, const Object *o
while (asset_object->parent != nullptr) {
asset_object = asset_object->parent;
}
return cryptomatte_hash(&session->assets, &asset_object->id);
return session->assets.add_ID(asset_object->id);
}
/* Convert a cryptomatte hash to a float.
*
* Cryptomatte hashes are stored in float textures and images. The conversion is taken from the
* cryptomatte specification. See Floating point conversion section in
* https://github.com/Psyop/Cryptomatte/blob/master/specification/cryptomatte_specification.pdf.
*
* The conversion uses as many 32 bit floating point values as possible to minimize hash
* collisions. Unfortunately not all 32 bits can be used as NaN and Inf can be problematic.
*
* Note that this conversion assumes to be running on a L-endian system. */
float BKE_cryptomatte_hash_to_float(uint32_t cryptomatte_hash)
{
uint32_t mantissa = cryptomatte_hash & ((1 << 23) - 1);
uint32_t exponent = (cryptomatte_hash >> 23) & ((1 << 8) - 1);
exponent = MAX2(exponent, (uint32_t)1);
exponent = MIN2(exponent, (uint32_t)254);
exponent = exponent << 23;
uint32_t sign = (cryptomatte_hash >> 31);
sign = sign << 31;
uint32_t float_bits = sign | exponent | mantissa;
float f;
memcpy(&f, &float_bits, sizeof(uint32_t));
return f;
}
static ID *cryptomatte_find_id(const ListBase *ids, const float encoded_hash)
{
LISTBASE_FOREACH (ID *, id, ids) {
uint32_t hash = BKE_cryptomatte_hash((id->name + 2), BLI_strnlen(id->name + 2, MAX_NAME));
if (BKE_cryptomatte_hash_to_float(hash) == encoded_hash) {
return id;
}
}
return nullptr;
}
/* Find an ID in the given main that matches the given encoded float. */
static struct ID *BKE_cryptomatte_find_id(const Main *bmain, const float encoded_hash)
{
ID *result;
result = cryptomatte_find_id(&bmain->objects, encoded_hash);
if (result == nullptr) {
result = cryptomatte_find_id(&bmain->materials, encoded_hash);
}
return result;
return blender::bke::cryptomatte::CryptomatteHash(cryptomatte_hash).float_encoded();
}
char *BKE_cryptomatte_entries_to_matte_id(NodeCryptomatte *node_storage)
@ -223,11 +150,10 @@ char *BKE_cryptomatte_entries_to_matte_id(NodeCryptomatte *node_storage)
return result;
}
void BKE_cryptomatte_matte_id_to_entries(const Main *bmain,
NodeCryptomatte *node_storage,
const char *matte_id)
void BKE_cryptomatte_matte_id_to_entries(NodeCryptomatte *node_storage, const char *matte_id)
{
BLI_freelistN(&node_storage->entries);
std::optional<CryptomatteSession> session = std::nullopt;
std::istringstream ss(matte_id);
while (ss.good()) {
@ -246,18 +172,12 @@ void BKE_cryptomatte_matte_id_to_entries(const Main *bmain,
float encoded_hash = atof(token.substr(1, token.length() - 2).c_str());
entry = (CryptomatteEntry *)MEM_callocN(sizeof(CryptomatteEntry), __func__);
entry->encoded_hash = encoded_hash;
if (bmain) {
ID *id = BKE_cryptomatte_find_id(bmain, encoded_hash);
if (id != nullptr) {
BLI_strncpy(entry->name, id->name + 2, sizeof(entry->name));
}
}
}
else {
const char *name = token.c_str();
int name_len = token.length();
entry = (CryptomatteEntry *)MEM_callocN(sizeof(CryptomatteEntry), __func__);
BLI_strncpy(entry->name, name, sizeof(entry->name));
STRNCPY(entry->name, name);
uint32_t hash = BKE_cryptomatte_hash(name, name_len);
entry->encoded_hash = BKE_cryptomatte_hash_to_float(hash);
}
@ -289,7 +209,7 @@ static void add_render_result_meta_data(RenderResult *render_result,
{
BKE_render_result_stamp_data(
render_result,
blender::BKE_cryptomatte_meta_data_key(layer_name, key_name).c_str(),
blender::bke::cryptomatte::BKE_cryptomatte_meta_data_key(layer_name, key_name).c_str(),
value.data());
}
@ -300,7 +220,7 @@ void BKE_cryptomatte_store_metadata(struct CryptomatteSession *session,
const char *cryptomatte_layer_name)
{
/* Create Manifest. */
CryptomatteLayer *layer = nullptr;
blender::bke::cryptomatte::CryptomatteLayer *layer = nullptr;
switch (cryptomatte_layer) {
case VIEW_LAYER_CRYPTOMATTE_OBJECT:
layer = &session->objects;
@ -326,7 +246,134 @@ void BKE_cryptomatte_store_metadata(struct CryptomatteSession *session,
add_render_result_meta_data(render_result, name, "manifest", manifest);
}
namespace blender {
namespace blender::bke::cryptomatte {
namespace manifest {
static constexpr int skip_whitespaces_len_(blender::StringRef ref)
{
int skip_len = 0;
while (skip_len < ref.size()) {
char front = ref[skip_len];
if (!std::isspace<char>(front, std::locale::classic())) {
break;
}
skip_len++;
}
return skip_len;
}
static constexpr blender::StringRef skip_whitespaces_(blender::StringRef ref)
{
return ref.drop_prefix(skip_whitespaces_len_(ref));
}
static constexpr int quoted_string_len_(blender::StringRef ref)
{
int len = 1;
bool skip_next = false;
while (len < ref.size()) {
char current_char = ref[len];
if (skip_next) {
skip_next = false;
}
else {
if (current_char == '\\') {
skip_next = true;
}
if (current_char == '\"') {
len += 1;
break;
}
}
len += 1;
}
return len;
}
static std::string unquote_(const blender::StringRef ref)
{
std::ostringstream stream;
for (char c : ref) {
if (c != '\\') {
stream << c;
}
}
return stream.str();
}
static bool from_manifest(CryptomatteLayer &layer, blender::StringRefNull manifest)
{
StringRef ref = manifest;
ref = skip_whitespaces_(ref);
if (ref.is_empty() || ref.front() != '{') {
return false;
}
ref = ref.drop_prefix(1);
while (!ref.is_empty()) {
char front = ref.front();
if (front == '\"') {
const int quoted_name_len = quoted_string_len_(ref);
const int name_len = quoted_name_len - 2;
std::string name = unquote_(ref.substr(1, name_len));
ref = ref.drop_prefix(quoted_name_len);
ref = skip_whitespaces_(ref);
char colon = ref.front();
if (colon != ':') {
return false;
}
ref = ref.drop_prefix(1);
ref = skip_whitespaces_(ref);
if (ref.front() != '\"') {
return false;
}
const int quoted_hash_len = quoted_string_len_(ref);
const int hash_len = quoted_hash_len - 2;
CryptomatteHash hash = CryptomatteHash::from_hex_encoded(ref.substr(1, hash_len));
ref = ref.drop_prefix(quoted_hash_len);
layer.add_hash(name, hash);
}
else if (front == ',') {
ref = ref.drop_prefix(1);
}
else if (front == '}') {
ref = ref.drop_prefix(1);
ref = skip_whitespaces_(ref);
break;
}
ref = skip_whitespaces_(ref);
}
if (!ref.is_empty()) {
return false;
}
return true;
}
static std::string to_manifest(const CryptomatteLayer *layer)
{
std::stringstream manifest;
bool is_first = true;
const blender::Map<std::string, CryptomatteHash> &const_map = layer->hashes;
manifest << "{";
for (blender::Map<std::string, CryptomatteHash>::Item item : const_map.items()) {
if (is_first) {
is_first = false;
}
else {
manifest << ",";
}
manifest << quoted(item.key) << ":\"" << (item.value.hex_encoded()) << "\"";
}
manifest << "}";
return manifest.str();
}
} // namespace manifest
/* Return the hash of the given cryptomatte layer name.
*
@ -361,4 +408,92 @@ StringRef BKE_cryptomatte_extract_layer_name(const StringRef render_pass_name)
return render_pass_name.substr(0, last_token);
}
} // namespace blender
CryptomatteHash::CryptomatteHash(uint32_t hash) : hash(hash)
{
}
CryptomatteHash::CryptomatteHash(const char *name, const int name_len)
{
hash = BLI_hash_mm3((const unsigned char *)name, name_len, 0);
}
CryptomatteHash CryptomatteHash::from_hex_encoded(blender::StringRef hex_encoded)
{
CryptomatteHash result(0);
std::istringstream(hex_encoded) >> std::hex >> result.hash;
return result;
}
std::string CryptomatteHash::hex_encoded() const
{
std::stringstream encoded;
encoded << std::setfill('0') << std::setw(sizeof(uint32_t) * 2) << std::hex << hash;
return encoded.str();
}
/* Convert a cryptomatte hash to a float.
*
* Cryptomatte hashes are stored in float textures and images. The conversion is taken from the
* cryptomatte specification. See Floating point conversion section in
* https://github.com/Psyop/Cryptomatte/blob/master/specification/cryptomatte_specification.pdf.
*
* The conversion uses as many 32 bit floating point values as possible to minimize hash
* collisions. Unfortunately not all 32 bits can be used as NaN and Inf can be problematic.
*
* Note that this conversion assumes to be running on a L-endian system. */
float CryptomatteHash::float_encoded() const
{
uint32_t mantissa = hash & ((1 << 23) - 1);
uint32_t exponent = (hash >> 23) & ((1 << 8) - 1);
exponent = MAX2(exponent, (uint32_t)1);
exponent = MIN2(exponent, (uint32_t)254);
exponent = exponent << 23;
uint32_t sign = (hash >> 31);
sign = sign << 31;
uint32_t float_bits = sign | exponent | mantissa;
float f;
memcpy(&f, &float_bits, sizeof(uint32_t));
return f;
}
std::unique_ptr<CryptomatteLayer> CryptomatteLayer::read_from_manifest(
blender::StringRefNull manifest)
{
std::unique_ptr<CryptomatteLayer> layer = std::make_unique<CryptomatteLayer>();
blender::bke::cryptomatte::manifest::from_manifest(*layer.get(), manifest);
return layer;
}
uint32_t CryptomatteLayer::add_ID(const ID &id)
{
const char *name = &id.name[2];
const int name_len = BLI_strnlen(name, MAX_NAME - 2);
uint32_t cryptohash_int = BKE_cryptomatte_hash(name, name_len);
add_hash(blender::StringRef(name, name_len), cryptohash_int);
return cryptohash_int;
}
void CryptomatteLayer::add_hash(blender::StringRef name, CryptomatteHash cryptomatte_hash)
{
hashes.add_overwrite(name, cryptomatte_hash);
}
std::optional<std::string> CryptomatteLayer::operator[](float encoded_hash) const
{
const blender::Map<std::string, CryptomatteHash> &const_map = hashes;
for (blender::Map<std::string, CryptomatteHash>::Item item : const_map.items()) {
if (BKE_cryptomatte_hash_to_float(item.value.hash) == encoded_hash) {
return std::make_optional(item.key);
}
}
return std::nullopt;
}
std::string CryptomatteLayer::manifest()
{
return blender::bke::cryptomatte::manifest::to_manifest(this);
}
} // namespace blender::bke::cryptomatte

View File

@ -19,7 +19,7 @@
#include "BKE_cryptomatte.hh"
namespace blender::bke::tests {
namespace blender::bke::cryptomatte::tests {
TEST(cryptomatte, meta_data_key)
{
@ -41,4 +41,45 @@ TEST(cryptomatte, extract_layer_name)
ASSERT_EQ("", BKE_cryptomatte_extract_layer_name(""));
}
} // namespace blender::bke::tests
TEST(cryptomatte, cryptomatte_layer)
{
blender::bke::cryptomatte::CryptomatteLayer layer;
ASSERT_EQ("{}", layer.manifest());
layer.add_hash("Object", 123);
ASSERT_EQ("{\"Object\":\"0000007b\"}", layer.manifest());
layer.add_hash("Object2", 123245678);
ASSERT_EQ("{\"Object\":\"0000007b\",\"Object2\":\"0758946e\"}", layer.manifest());
}
TEST(cryptomatte, cryptomatte_layer_quoted)
{
blender::bke::cryptomatte::CryptomatteLayer layer;
layer.add_hash("\"Object\"", 123);
ASSERT_EQ("{\"\\\"Object\\\"\":\"0000007b\"}", layer.manifest());
}
static void test_cryptomatte_manifest(std::string expected, std::string manifest)
{
EXPECT_EQ(expected,
blender::bke::cryptomatte::CryptomatteLayer::read_from_manifest(manifest)->manifest());
}
TEST(cryptomatte, cryptomatte_layer_from_manifest)
{
test_cryptomatte_manifest("{}", "{}");
test_cryptomatte_manifest("{\"Object\":\"12345678\"}", "{\"Object\": \"12345678\"}");
test_cryptomatte_manifest("{\"Object\":\"12345678\",\"Object2\":\"87654321\"}",
"{\"Object\":\"12345678\",\"Object2\":\"87654321\"}");
test_cryptomatte_manifest(
"{\"Object\":\"12345678\",\"Object2\":\"87654321\"}",
" { \"Object\" : \"12345678\" , \"Object2\" : \"87654321\" } ");
test_cryptomatte_manifest("{\"Object\\\"01\\\"\":\"12345678\"}",
"{\"Object\\\"01\\\"\": \"12345678\"}");
test_cryptomatte_manifest(
"{\"Object\\\"01\\\"\":\"12345678\",\"Object\":\"12345678\",\"Object2\":\"87654321\"}",
"{\"Object\\\"01\\\"\":\"12345678\",\"Object\":\"12345678\", \"Object2\":\"87654321\"}");
}
} // namespace blender::bke::cryptomatte::tests

View File

@ -1431,7 +1431,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
if (matte_id == NULL || strlen(storage->matte_id) == 0) {
continue;
}
BKE_cryptomatte_matte_id_to_entries(NULL, storage, storage->matte_id);
BKE_cryptomatte_matte_id_to_entries(storage, storage->matte_id);
MEM_SAFE_FREE(storage->matte_id);
}
}

View File

@ -34,7 +34,7 @@ void MetaData::addCryptomatteEntry(const blender::StringRef layer_name,
const blender::StringRefNull key,
const blender::StringRef value)
{
add(blender::BKE_cryptomatte_meta_data_key(layer_name, key), value);
add(blender::bke::cryptomatte::BKE_cryptomatte_meta_data_key(layer_name, key), value);
}
/* Replace the hash neutral cryptomatte keys with hashed versions.

View File

@ -347,8 +347,9 @@ StampData *OutputOpenExrMultiLayerOperation::createStampData() const
}
std::unique_ptr<MetaData> meta_data = layer->imageInput->getMetaData();
if (meta_data) {
blender::StringRef layer_name = blender::BKE_cryptomatte_extract_layer_name(
blender::StringRef(layer->name, BLI_strnlen(layer->name, sizeof(layer->name))));
blender::StringRef layer_name =
blender::bke::cryptomatte::BKE_cryptomatte_extract_layer_name(
blender::StringRef(layer->name, BLI_strnlen(layer->name, sizeof(layer->name))));
meta_data->replaceHashNeutralCryptomatteKeys(layer_name);
meta_data->addToRenderResult(&render_result);
}

View File

@ -233,9 +233,12 @@ struct CallbackData {
void setCryptomatteKeys(blender::StringRef cryptomatte_layer_name)
{
manifest_key = blender::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name, "manifest");
hash_key = blender::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name, "hash");
conversion_key = blender::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name, "conversion");
manifest_key = blender::bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name,
"manifest");
hash_key = blender::bke::cryptomatte::BKE_cryptomatte_meta_data_key(cryptomatte_layer_name,
"hash");
conversion_key = blender::bke::cryptomatte::BKE_cryptomatte_meta_data_key(
cryptomatte_layer_name, "conversion");
}
};
@ -276,7 +279,7 @@ std::unique_ptr<MetaData> RenderLayersProg::getMetaData() const
view_layer->name,
BLI_strnlen(view_layer->name, sizeof(view_layer->name))) +
"." + m_passName;
blender::StringRef cryptomatte_layer_name = blender::BKE_cryptomatte_extract_layer_name(
blender::StringRef cryptomatte_layer_name = blender::bke::cryptomatte::BKE_cryptomatte_extract_layer_name(
full_layer_name);
callback_data.setCryptomatteKeys(cryptomatte_layer_name);

View File

@ -3728,7 +3728,7 @@ static void rna_NodeCryptomatte_matte_set(PointerRNA *ptr, const char *value)
{
bNode *node = (bNode *)ptr->data;
NodeCryptomatte *nc = node->storage;
BKE_cryptomatte_matte_id_to_entries(NULL, nc, value);
BKE_cryptomatte_matte_id_to_entries(nc, value);
}
static void rna_NodeCryptomatte_update_add(Main *bmain, Scene *scene, PointerRNA *ptr)