Skip to content

Commit bfdb694

Browse files
committed
feat(openexr): Support for idManifest and deepImageState
Support for retrieving and setting the idManifest for deep image files with object ID channels. This is an OpenEXR feature where the image header can contian metadata that maps all the object ID channel integer codes to strings of the object names. We report this in the ImageSpec as the metadata "openexr:compressedIDManifest", whose type is an array of uint8 (byte) values. It is encoded the same way it appears in the exr file itself: the first 8 bytes are a little endian uint64_t giving the *uncompressed* size of a serialized OpenEXR IDManifest object, and then starting at byte 8, the zip-compressed seriailized IDManifest. Therefore, the compressed block size is the size of the metadata blob, minus 8 bytes for the length. It is up to the caller to use the OpenEXR APIs to turn this compressed serialized IDManifest into a fully expanded IDManifest, if that's what they want to do. It works for output, too, so it should copy the manifests correctly between files, even if the app doesn't know what to do with the binary data it contains. Also added support for deepImageState, which we previously had ignored. We report this as the metadata "openexr:deepImageState", a string, which if present has one of the values "messy", "sorted", "non_overlapping", or "tidy". Signed-off-by: Larry Gritz <lg@larrygritz.com>
1 parent 81acb23 commit bfdb694

File tree

8 files changed

+176
-10
lines changed

8 files changed

+176
-10
lines changed

src/cmake/testing.cmake

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,10 @@ macro (oiio_add_all_tests)
292292
# properly supported all compression types (DWA in particular).
293293
list (APPEND all_openexr_tests openexr-compression)
294294
endif ()
295+
if (OpenEXR_VERSION VERSION_GREATER_EQUAL 3.3)
296+
# OpenEXR 3.3 is when IDManifest was introduced
297+
list (APPEND all_openexr_tests openexr-idmanifest)
298+
endif ()
295299
# Run all OpenEXR tests without core library
296300
oiio_add_tests (${all_openexr_tests} openexr-luminance-chroma
297301
ENVIRONMENT OPENIMAGEIO_OPTIONS=openexr:core=0

src/doc/builtinplugins.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1590,6 +1590,19 @@ The official OpenEXR site is http://www.openexr.com/.
15901590
- If nonzero, indicates whether the image is a luminance-chroma image.
15911591
Upon reading, the subsampled Y/BY/RY(/A) channels of luminance-chroma
15921592
images are automatically converted to RGB(A) channels.
1593+
* - ``openexr::deepImageState``
1594+
- string
1595+
- If present in a deep file, reveals the deep image state, one of:
1596+
``"messy"``, ``"sorted"``, ``"non_overlapping"``, or ``"tidy"``.
1597+
See the OpenEXR documentation for explanations. This metadata was
1598+
added in OpenImageIO 3.1.
1599+
* - ``openexr::compressedIDManifest``
1600+
- uint8[]
1601+
- A byte array whose first 8 bytes are the uncompressed size of the
1602+
manifest, as a little-endian uint64. Then beginning at byte 8,
1603+
the remainder is the zip-compressed serialized manifest.
1604+
This metadata was added in OpenImageIO 3.1, and is only supported when
1605+
OIIO is built against OpenEXR 3.1 or newer.
15931606
* - *other*
15941607
-
15951608
- All other attributes will be added to the ImageSpec by their name and

src/openexr.imageio/exrinput.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,15 @@ OIIO_GCC_PRAGMA(GCC diagnostic ignored "-Wunused-parameter")
3636
#include <OpenEXR/ImfChromaticitiesAttribute.h>
3737
#include <OpenEXR/ImfCompressionAttribute.h>
3838
#include <OpenEXR/ImfDeepFrameBuffer.h>
39+
#include <OpenEXR/ImfDeepImageStateAttribute.h>
3940
#include <OpenEXR/ImfDeepScanLineInputPart.h>
4041
#include <OpenEXR/ImfDeepTiledInputPart.h>
4142
#include <OpenEXR/ImfDoubleAttribute.h>
4243
#include <OpenEXR/ImfEnvmapAttribute.h>
4344
#include <OpenEXR/ImfFloatAttribute.h>
4445
#include <OpenEXR/ImfFloatVectorAttribute.h>
4546
#include <OpenEXR/ImfHeader.h>
47+
#include <OpenEXR/ImfIDManifestAttribute.h>
4648
#include <OpenEXR/ImfInputPart.h>
4749
#include <OpenEXR/ImfIntAttribute.h>
4850
#include <OpenEXR/ImfKeyCodeAttribute.h>
@@ -642,6 +644,44 @@ OpenEXRInput::PartInfo::parse_header(OpenEXRInput* in,
642644
default: break;
643645
}
644646
spec.attribute("openexr:lineOrder", lineOrder);
647+
} else if (type == "deepImageState") {
648+
auto attr = header->findTypedAttribute<Imf::DeepImageStateAttribute>(
649+
name);
650+
if (attr) {
651+
const char* val = "messy";
652+
switch (attr->value()) {
653+
case Imf::DIS_MESSY: val = "messy"; break;
654+
case Imf::DIS_SORTED: val = "sorted"; break;
655+
case Imf::DIS_NON_OVERLAPPING: val = "non_overlapping"; break;
656+
case Imf::DIS_TIDY: val = "tidy"; break;
657+
default: break;
658+
}
659+
spec.attribute("openexr:deepImageState", val);
660+
}
661+
} else if (type == "idmanifest") {
662+
auto attr = header->findTypedAttribute<Imf::IDManifestAttribute>(
663+
name);
664+
if (attr) {
665+
// print("CompressedIDManifest size {}\n",
666+
// attr->value()._compressedDataSize);
667+
size_t csize(attr->value()._compressedDataSize);
668+
// NOTE: The blob of bytes we're making consists of:
669+
// Bytes 0-7: little endian uint64 giving the *uncompressed*
670+
// size that will be needed for the serialized IDM.
671+
// Bytes 8-(csize-1): the zip-compressed serialized IDManifest.
672+
size_t blobsize = csize + 8;
673+
std::unique_ptr<std::byte[]> blob(new std::byte[blobsize]);
674+
uint64_t usize = attr->value()._uncompressedDataSize;
675+
if constexpr (bigendian())
676+
usize = byteswap(usize);
677+
memcpy(blob.get(), &usize, sizeof(usize));
678+
memcpy(blob.get() + 8, attr->value()._data, csize);
679+
spec.attribute("openexr:compressedIDManifest",
680+
TypeDesc(TypeDesc::UINT8, blobsize), blob.get());
681+
} else {
682+
// print("idManifest found but not retrieved?\n");
683+
}
684+
645685
} else {
646686
#if 0
647687
print(std::cerr, " unknown attribute '{}' name '{}'\n",

src/openexr.imageio/exrinput_c.cpp

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ static std::map<std::string, std::string> cexr_tag_to_oiio_std {
270270
{ "chunkCount", "openexr:chunkCount" },
271271
{ "maxSamplesPerPixel", "openexr:maxSamplesPerPixel" },
272272
{ "dwaCompressionLevel", "openexr:dwaCompressionLevel" },
273+
{ "idManifest", "openexr:compressedIDManifest" },
273274
// Ones to skip because we handle specially or consider them irrelevant
274275
{ "channels", "" },
275276
{ "compression", "" },
@@ -742,8 +743,42 @@ OpenEXRCoreInput::PartInfo::parse_header(OpenEXRCoreInput* in,
742743
break;
743744
}
744745

746+
#if OPENEXR_CODED_VERSION >= 30300
747+
case EXR_ATTR_DEEP_IMAGE_STATE: {
748+
const char* val = "messy";
749+
switch (attr->uc) {
750+
case EXR_DIS_MESSY: val = "messy"; break;
751+
case EXR_DIS_SORTED: val = "sorted"; break;
752+
case EXR_DIS_NON_OVERLAPPING: val = "non_overlapping"; break;
753+
case EXR_DIS_TIDY: val = "tidy"; break;
754+
default: break;
755+
}
756+
spec.attribute("openexr:deepImageState", val);
757+
break;
758+
}
759+
#endif
760+
761+
#if OPENEXR_CODED_VERSION >= 30400
762+
case EXR_ATTR_BYTES: {
763+
spec.attribute(oname, TypeDesc(TypeDesc::UINT8, attr->bytes->size),
764+
make_span(attr->bytes->data, attr->bytes->size));
765+
break;
766+
}
767+
#endif
768+
769+
case EXR_ATTR_OPAQUE: {
770+
if (Strutil::iequals(oname, "idManifest"))
771+
oname = "openexr:compressedIDManifeset"; // our name for this
772+
spec.attribute(oname, TypeDesc(TypeDesc::UINT8, attr->opaque->size),
773+
attr->opaque->packed_data);
774+
// NOTE: The blob of bytes we're making consists of:
775+
// Bytes 0-7: little endian uint64 giving the *uncompressed*
776+
// size that will be needed for the serialized IDM.
777+
// Bytes 8-(size-1): the zip-compressed serialized IDManifest.
778+
break;
779+
}
780+
745781
case EXR_ATTR_PREVIEW:
746-
case EXR_ATTR_OPAQUE:
747782
case EXR_ATTR_ENVMAP:
748783
case EXR_ATTR_COMPRESSION:
749784
case EXR_ATTR_CHLIST:

src/openexr.imageio/exroutput.cpp

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ OIIO_GCC_PRAGMA(GCC diagnostic ignored "-Wunused-parameter")
3333
#include <OpenEXR/ImfCRgbaFile.h> // JUST to get symbols to figure out version!
3434
#include <OpenEXR/ImfChromaticitiesAttribute.h>
3535
#include <OpenEXR/ImfCompressionAttribute.h>
36+
#include <OpenEXR/ImfDeepImageStateAttribute.h>
3637
#include <OpenEXR/ImfEnvmapAttribute.h>
3738
#include <OpenEXR/ImfFloatAttribute.h>
3839
#include <OpenEXR/ImfFloatVectorAttribute.h>
3940
#include <OpenEXR/ImfHeader.h>
41+
#include <OpenEXR/ImfIDManifestAttribute.h>
4042
#include <OpenEXR/ImfIntAttribute.h>
4143
#include <OpenEXR/ImfKeyCodeAttribute.h>
4244
#include <OpenEXR/ImfMatrixAttribute.h>
@@ -937,7 +939,7 @@ OpenEXROutput::put_parameter(const std::string& name, TypeDesc type,
937939

938940
// Special cases
939941
if (Strutil::iequals(xname, "Compression") && type == TypeString) {
940-
const char* str = *(char**)data;
942+
const char* str = *(const char**)data;
941943
header.compression() = Imf::ZIP_COMPRESSION; // Default
942944
if (str) {
943945
if (Strutil::iequals(str, "none"))
@@ -974,7 +976,7 @@ OpenEXROutput::put_parameter(const std::string& name, TypeDesc type,
974976
}
975977

976978
if (Strutil::iequals(xname, "openexr:lineOrder") && type == TypeString) {
977-
const char* str = *(char**)data;
979+
const char* str = *(const char**)data;
978980
header.lineOrder() = Imf::INCREASING_Y; // Default
979981
if (str) {
980982
if (Strutil::iequals(str, "randomY")
@@ -987,6 +989,36 @@ OpenEXROutput::put_parameter(const std::string& name, TypeDesc type,
987989
return true;
988990
}
989991

992+
if (Strutil::iequals(xname, "openexr:deepImageState")
993+
&& type == TypeString) {
994+
const char* str = *(const char**)data;
995+
Imf::DeepImageState val = Imf::DeepImageState::DIS_MESSY;
996+
if (!strcmp(str, "sorted"))
997+
val = Imf::DeepImageState::DIS_SORTED;
998+
else if (!strcmp(str, "non_overlapping"))
999+
val = Imf::DeepImageState::DIS_NON_OVERLAPPING;
1000+
else if (!strcmp(str, "tidy"))
1001+
val = Imf::DeepImageState::DIS_TIDY;
1002+
header.insert(xname.c_str(), Imf::DeepImageStateAttribute(val));
1003+
return true;
1004+
}
1005+
1006+
if (Strutil::iequals(xname, "openexr:compressedIDManifest")
1007+
&& type.basetype == TypeDesc::UINT8 && type.arraylen > 8) {
1008+
const unsigned char* bdata = (const unsigned char*)data;
1009+
uint64_t usize = 0;
1010+
memcpy(&usize, bdata, sizeof(usize));
1011+
if constexpr (bigendian())
1012+
usize = byteswap(usize);
1013+
Imf::CompressedIDManifest idm;
1014+
idm._compressedDataSize = static_cast<int>(type.size() - 8);
1015+
idm._uncompressedDataSize = usize;
1016+
idm._data = const_cast<unsigned char*>(bdata + 8);
1017+
header.insert("idManifest", Imf::IDManifestAttribute(idm));
1018+
idm._data = nullptr;
1019+
return true;
1020+
}
1021+
9901022
// Special handling of any remaining "oiio:*" metadata.
9911023
if (Strutil::istarts_with(xname, "oiio:")) {
9921024
if (Strutil::iequals(xname, "oiio:ConstantColor")
@@ -1052,27 +1084,28 @@ OpenEXROutput::put_parameter(const std::string& name, TypeDesc type,
10521084
if (type.aggregate == TypeDesc::SCALAR) {
10531085
if (type == TypeDesc::INT || type == TypeDesc::UINT) {
10541086
header.insert(xname.c_str(),
1055-
Imf::IntAttribute(*(int*)data));
1087+
Imf::IntAttribute(*(const int*)data));
10561088
return true;
10571089
}
10581090
if (type == TypeDesc::INT16) {
10591091
header.insert(xname.c_str(),
1060-
Imf::IntAttribute(*(short*)data));
1092+
Imf::IntAttribute(*(const short*)data));
10611093
return true;
10621094
}
10631095
if (type == TypeDesc::UINT16) {
10641096
header.insert(xname.c_str(),
1065-
Imf::IntAttribute(*(unsigned short*)data));
1097+
Imf::IntAttribute(
1098+
*(const unsigned short*)data));
10661099
return true;
10671100
}
10681101
if (type == TypeDesc::FLOAT) {
10691102
header.insert(xname.c_str(),
1070-
Imf::FloatAttribute(*(float*)data));
1103+
Imf::FloatAttribute(*(const float*)data));
10711104
return true;
10721105
}
10731106
if (type == TypeDesc::HALF) {
1074-
header.insert(xname.c_str(),
1075-
Imf::FloatAttribute((float)*(half*)data));
1107+
header.insert(xname.c_str(), Imf::FloatAttribute((float)*(
1108+
const half*)data));
10761109
return true;
10771110
}
10781111
if (type == TypeString && !((const ustring*)data)->empty()) {
@@ -1083,7 +1116,7 @@ OpenEXROutput::put_parameter(const std::string& name, TypeDesc type,
10831116
}
10841117
if (type == TypeDesc::DOUBLE) {
10851118
header.insert(xname.c_str(),
1086-
Imf::DoubleAttribute(*(double*)data));
1119+
Imf::DoubleAttribute(*(const double*)data));
10871120
return true;
10881121
}
10891122
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Reading src/manifest.exr
2+
src/manifest.exr : 16 x 16, 8 channel, deep half/half/half/half/half/uint/uint/uint openexr
3+
SHA-1: 526B00A9E52F6EF667E02168CF20772957ACA451
4+
channel list: R (half), G (half), B (half), A (half), Z (half), materialid (uint), modelid (uint), particleid (uint)
5+
compression: "zips"
6+
PixelAspectRatio: 1
7+
screenWindowCenter: 0, 0
8+
screenWindowWidth: 1
9+
version: 1
10+
oiio:subimages: 1
11+
openexr:chunkCount: 16
12+
openexr:compressedIDManifest: 55, 1, 0, 0, 0, 0, 0, 0, 120, 94, 99, 0, 2, 110, 32, 230, ... [250 x uint8]
13+
openexr:deepImageState: "tidy"
14+
openexr:lineOrder: "increasingY"
15+
Reading manifest.exr
16+
manifest.exr : 16 x 16, 8 channel, deep half/half/half/half/half/uint/uint/uint openexr
17+
SHA-1: 526B00A9E52F6EF667E02168CF20772957ACA451
18+
channel list: R (half), G (half), B (half), A (half), Z (half), materialid (uint), modelid (uint), particleid (uint)
19+
compression: "zips"
20+
PixelAspectRatio: 1
21+
screenWindowCenter: 0, 0
22+
screenWindowWidth: 1
23+
version: 1
24+
oiio:subimages: 1
25+
openexr:chunkCount: 16
26+
openexr:compressedIDManifest: 55, 1, 0, 0, 0, 0, 0, 0, 120, 94, 99, 0, 2, 110, 32, 230, ... [250 x uint8]
27+
openexr:deepImageState: "tidy"
28+
openexr:lineOrder: "increasingY"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env python
2+
3+
# Copyright Contributors to the OpenImageIO project.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# https://github.com/AcademySoftwareFoundation/OpenImageIO
6+
7+
8+
# Multi-part, not deep
9+
command += info_command ("src/manifest.exr", safematch = True)
10+
command += oiiotool ("src/manifest.exr -o manifest.exr")
11+
command += info_command ("./manifest.exr", safematch = True)
12+
13+
outputs += [ "out.txt" ]
20.6 KB
Binary file not shown.

0 commit comments

Comments
 (0)