Skip to content

Commit 737206c

Browse files
Add support for luminance-chroma OpenEXR images.
Signed-off-by: Joachim Reichel <[email protected]>
1 parent c098d7f commit 737206c

File tree

3 files changed

+212
-26
lines changed

3 files changed

+212
-26
lines changed

src/openexr.imageio/exrinput.cpp

Lines changed: 152 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616

1717
#include <boost/version.hpp>
1818

19+
#include <OpenEXR/ImfArray.h>
1920
#include <OpenEXR/ImfChannelList.h>
2021
#include <OpenEXR/ImfEnvmap.h>
2122
#include <OpenEXR/ImfInputFile.h>
23+
#include <OpenEXR/ImfRgba.h>
2224
#include <OpenEXR/ImfTestFile.h>
2325
#include <OpenEXR/ImfTiledInputFile.h>
2426

@@ -51,6 +53,7 @@ OIIO_GCC_PRAGMA(GCC diagnostic ignored "-Wunused-parameter")
5153
#include <OpenEXR/ImfMultiPartInputFile.h>
5254
#include <OpenEXR/ImfPartType.h>
5355
#include <OpenEXR/ImfRationalAttribute.h>
56+
#include <OpenEXR/ImfRgbaFile.h>
5457
#include <OpenEXR/ImfStringAttribute.h>
5558
#include <OpenEXR/ImfStringVectorAttribute.h>
5659
#include <OpenEXR/ImfTiledInputPart.h>
@@ -179,12 +182,13 @@ class OpenEXRInput final : public ImageInput {
179182
struct PartInfo {
180183
std::atomic_bool initialized;
181184
ImageSpec spec;
182-
int topwidth; ///< Width of top mip level
183-
int topheight; ///< Height of top mip level
184-
int levelmode; ///< The level mode
185-
int roundingmode; ///< Rounding mode
186-
bool cubeface; ///< It's a cubeface environment map
187-
int nmiplevels; ///< How many MIP levels are there?
185+
int topwidth; ///< Width of top mip level
186+
int topheight; ///< Height of top mip level
187+
int levelmode; ///< The level mode
188+
int roundingmode; ///< Rounding mode
189+
bool cubeface; ///< It's a cubeface environment map
190+
bool luminance_chroma; ///< It's a luminance chroma image
191+
int nmiplevels; ///< How many MIP levels are there?
188192
Imath::Box2i top_datawindow;
189193
Imath::Box2i top_displaywindow;
190194
std::vector<Imf::PixelType> pixeltype; ///< Imf pixel type for each chan
@@ -202,6 +206,7 @@ class OpenEXRInput final : public ImageInput {
202206
, levelmode(p.levelmode)
203207
, roundingmode(p.roundingmode)
204208
, cubeface(p.cubeface)
209+
, luminance_chroma(p.luminance_chroma)
205210
, nmiplevels(p.nmiplevels)
206211
, top_datawindow(p.top_datawindow)
207212
, top_displaywindow(p.top_displaywindow)
@@ -223,6 +228,7 @@ class OpenEXRInput final : public ImageInput {
223228
Imf::TiledInputPart* m_tiled_input_part;
224229
Imf::DeepScanLineInputPart* m_deep_scanline_input_part;
225230
Imf::DeepTiledInputPart* m_deep_tiled_input_part;
231+
Imf::RgbaInputFile* m_input_rgba;
226232
Filesystem::IOProxy* m_io = nullptr;
227233
std::unique_ptr<Filesystem::IOProxy> m_local_io;
228234
int m_subimage; ///< What subimage are we looking at?
@@ -238,6 +244,7 @@ class OpenEXRInput final : public ImageInput {
238244
m_tiled_input_part = NULL;
239245
m_deep_scanline_input_part = NULL;
240246
m_deep_tiled_input_part = NULL;
247+
m_input_rgba = NULL;
241248
m_subimage = -1;
242249
m_miplevel = -1;
243250
m_io = nullptr;
@@ -910,6 +917,38 @@ suffixfound(string_view name, span<ChanNameHolder> chans)
910917
}
911918

912919

920+
// Returns the index of that channel name (suffix only) in the list, or -1 in case of failure.
921+
static int
922+
get_index_of_suffix(string_view name, span<ChanNameHolder> chans)
923+
{
924+
for (size_t i = 0, n = chans.size(); i < n; ++i)
925+
if (Strutil::iequals(name, chans[i].suffix))
926+
return static_cast<int>(i);
927+
return -1;
928+
}
929+
930+
931+
// Is this a luminanc-chroma image, i.e., Y/BY/RY or Y/BY/RY/A or Y/BY/RY/Alpha?
932+
//
933+
// Note that extra channels are not supported.
934+
static bool
935+
is_luminance_chroma(span<ChanNameHolder> chans)
936+
{
937+
if (chans.size() < 3 || chans.size() > 4)
938+
return false;
939+
if (!suffixfound("Y", chans))
940+
return false;
941+
if (!suffixfound("BY", chans))
942+
return false;
943+
if (!suffixfound("RY", chans))
944+
return false;
945+
if (chans.size() == 4 && !suffixfound("A", chans)
946+
&& !suffixfound("Alpha", chans))
947+
return false;
948+
return true;
949+
}
950+
951+
913952
} // namespace
914953

915954

@@ -919,10 +958,8 @@ OpenEXRInput::PartInfo::query_channels(OpenEXRInput* in,
919958
const Imf::Header* header)
920959
{
921960
OIIO_DASSERT(!initialized);
922-
bool ok = true;
923-
spec.nchannels = 0;
961+
bool ok = true;
924962
const Imf::ChannelList& channels(header->channels());
925-
std::vector<std::string> channelnames; // Order of channels in file
926963
std::vector<ChanNameHolder> cnh;
927964
int c = 0;
928965
for (auto ci = channels.begin(); ci != channels.end(); ++c, ++ci)
@@ -969,6 +1006,33 @@ OpenEXRInput::PartInfo::query_channels(OpenEXRInput* in,
9691006
// Now we should have cnh sorted into the order that we want to present
9701007
// to the OIIO client.
9711008

1009+
// Limitations for luminance-chroma images: no tiling, no deep samples, no
1010+
// miplevels/subimages, no extra channels.
1011+
luminance_chroma = is_luminance_chroma(cnh);
1012+
if (luminance_chroma) {
1013+
spec.format = TypeDesc::HALF;
1014+
spec.nchannels = cnh.size();
1015+
if (spec.nchannels == 3) {
1016+
spec.channelnames = { "R", "G", "B" };
1017+
spec.alpha_channel = -1;
1018+
spec.z_channel = -1;
1019+
} else {
1020+
OIIO_ASSERT(spec.nchannels == 4);
1021+
int index_a = get_index_of_suffix("A", cnh);
1022+
if (index_a != -1) {
1023+
spec.channelnames = { "R", "G", "B", "A" };
1024+
spec.alpha_channel = index_a;
1025+
} else {
1026+
spec.channelnames = { "R", "G", "B", "Alpha" };
1027+
spec.alpha_channel = get_index_of_suffix("Alpha", cnh);
1028+
OIIO_ASSERT(spec.alpha_channel != -1);
1029+
}
1030+
spec.z_channel = -1;
1031+
}
1032+
spec.channelformats.clear();
1033+
return true;
1034+
}
1035+
9721036
spec.format = TypeDesc::UNKNOWN;
9731037
bool all_one_format = true;
9741038
for (int c = 0; c < spec.nchannels; ++c) {
@@ -991,7 +1055,8 @@ OpenEXRInput::PartInfo::query_channels(OpenEXRInput* in,
9911055
in->errorfmt(
9921056
"Subsampled channels are not supported (channel \"{}\" has sampling {},{}).",
9931057
cnh[c].fullname, cnh[c].xSampling, cnh[c].ySampling);
994-
// FIXME: Some day, we should handle channel subsampling.
1058+
// FIXME: Some day, we should handle channel subsampling (beyond the luminance chroma
1059+
// special case, possibly replacing it).
9951060
}
9961061
}
9971062
OIIO_DASSERT((int)spec.channelnames.size() == spec.nchannels);
@@ -1088,8 +1153,18 @@ OpenEXRInput::seek_subimage(int subimage, int miplevel)
10881153
m_deep_scanline_input_part = NULL;
10891154
delete m_deep_tiled_input_part;
10901155
m_deep_tiled_input_part = NULL;
1156+
delete m_input_rgba;
1157+
m_input_rgba = NULL;
10911158
try {
1092-
if (part.spec.deep) {
1159+
if (part.luminance_chroma) {
1160+
if (subimage != 0 || miplevel != 0) {
1161+
errorf(
1162+
"Non-zero subimage or miplevel are not supported for luminance-chroma images.");
1163+
return false;
1164+
}
1165+
m_input_stream->seekg(0);
1166+
m_input_rgba = new Imf::RgbaInputFile(*m_input_stream);
1167+
} else if (part.spec.deep) {
10931168
if (part.spec.tile_width)
10941169
m_deep_tiled_input_part
10951170
= new Imf::DeepTiledInputPart(*m_input_multipart,
@@ -1112,13 +1187,15 @@ OpenEXRInput::seek_subimage(int subimage, int miplevel)
11121187
m_tiled_input_part = NULL;
11131188
m_deep_scanline_input_part = NULL;
11141189
m_deep_tiled_input_part = NULL;
1190+
m_input_rgba = NULL;
11151191
return false;
11161192
} catch (...) { // catch-all for edge cases or compiler bugs
11171193
errorf("OpenEXR exception: unknown");
11181194
m_scanline_input_part = NULL;
11191195
m_tiled_input_part = NULL;
11201196
m_deep_scanline_input_part = NULL;
11211197
m_deep_tiled_input_part = NULL;
1198+
m_input_rgba = NULL;
11221199
return false;
11231200
}
11241201
}
@@ -1200,6 +1277,7 @@ OpenEXRInput::close()
12001277
delete m_tiled_input_part;
12011278
delete m_deep_scanline_input_part;
12021279
delete m_deep_tiled_input_part;
1280+
delete m_input_rgba;
12031281
delete m_input_stream;
12041282
init(); // Reset to initial state
12051283
return true;
@@ -1226,7 +1304,6 @@ OpenEXRInput::read_native_scanlines(int subimage, int miplevel, int ybegin,
12261304
}
12271305

12281306

1229-
12301307
bool
12311308
OpenEXRInput::read_native_scanlines(int subimage, int miplevel, int ybegin,
12321309
int yend, int /*z*/, int chbegin, int chend,
@@ -1238,11 +1315,6 @@ OpenEXRInput::read_native_scanlines(int subimage, int miplevel, int ybegin,
12381315
chend = clamp(chend, chbegin + 1, m_spec.nchannels);
12391316
// std::cerr << "openexr rns " << ybegin << ' ' << yend << ", channels "
12401317
// << chbegin << "-" << (chend-1) << "\n";
1241-
if (!m_scanline_input_part) {
1242-
errorf(
1243-
"called OpenEXRInput::read_native_scanlines without an open file");
1244-
return false;
1245-
}
12461318

12471319
// Compute where OpenEXR needs to think the full buffers starts.
12481320
// OpenImageIO requires that 'data' points to where the client wants
@@ -1255,6 +1327,51 @@ OpenEXRInput::read_native_scanlines(int subimage, int miplevel, int ybegin,
12551327
char* buf = (char*)data - m_spec.x * pixelbytes - ybegin * scanlinebytes;
12561328

12571329
try {
1330+
if (part.luminance_chroma) {
1331+
Imath::Box2i dw = m_input_rgba->dataWindow();
1332+
if (dw.min.x != 0 || dw.min.y != 0
1333+
|| dw != m_input_rgba->displayWindow()) {
1334+
errorf(
1335+
"Non-trivial data and/or display windows are not supported for luminance-chroma images.");
1336+
return false;
1337+
}
1338+
int dw_width = dw.max.x - dw.min.x + 1;
1339+
int dw_height = dw.max.y - dw.min.y + 1;
1340+
int chunk_height = yend - ybegin;
1341+
// FIXME Are these assumptions correct?
1342+
OIIO_ASSERT(ybegin >= dw.min.y);
1343+
OIIO_ASSERT(yend <= dw.max.y + 1);
1344+
OIIO_ASSERT(chunk_height <= dw_height);
1345+
1346+
Imf::Array2D<Imf::Rgba> pixels(chunk_height, dw_width);
1347+
m_input_rgba->setFrameBuffer(&pixels[0][0] - dw.min.x
1348+
- ybegin * dw_width,
1349+
1, dw_width);
1350+
m_input_rgba->readPixels(ybegin, yend - 1);
1351+
1352+
// FIXME There is probably some optimized code for this somewhere.
1353+
for (int c = chbegin; c < chend; ++c) {
1354+
size_t chanbytes = m_spec.channelformat(c).size();
1355+
Imath::half* src = &pixels[0][0].r + c;
1356+
Imath::half* dst = (Imath::half*)((char*)data + c * chanbytes);
1357+
for (int y = ybegin; y < yend; ++y) {
1358+
for (int x = 0; x < m_spec.width; ++x) {
1359+
*dst = *src;
1360+
src += 4; // always advance 4 RGBA halfs
1361+
dst += m_spec.nchannels;
1362+
}
1363+
}
1364+
}
1365+
1366+
return true;
1367+
}
1368+
1369+
if (!m_scanline_input_part) {
1370+
errorf(
1371+
"called OpenEXRInput::read_native_scanlines without an open file");
1372+
return false;
1373+
}
1374+
12581375
Imf::FrameBuffer frameBuffer;
12591376
size_t chanoffset = 0;
12601377
for (int c = chbegin; c < chend; ++c) {
@@ -1320,6 +1437,12 @@ OpenEXRInput::read_native_tiles(int subimage, int miplevel, int xbegin,
13201437
if (!seek_subimage(subimage, miplevel))
13211438
return false;
13221439
chend = clamp(chend, chbegin + 1, m_spec.nchannels);
1440+
const PartInfo& part(m_parts[m_subimage]);
1441+
if (part.luminance_chroma) {
1442+
errorf(
1443+
"OpenEXRInput::read_native_deep_scanlines is not supported for luminance-chroma images");
1444+
return false;
1445+
}
13231446
#if 0
13241447
std::cerr << "openexr rnt " << xbegin << ' ' << xend << ' ' << ybegin
13251448
<< ' ' << yend << ", chans " << chbegin
@@ -1336,7 +1459,6 @@ OpenEXRInput::read_native_tiles(int subimage, int miplevel, int xbegin,
13361459
// to put the pixels being read, but OpenEXR's frameBuffer.insert()
13371460
// wants where the address of the "virtual framebuffer" for the
13381461
// whole image.
1339-
const PartInfo& part(m_parts[m_subimage]);
13401462
size_t pixelbytes = m_spec.pixel_bytes(chbegin, chend, true);
13411463
int firstxtile = (xbegin - m_spec.x) / m_spec.tile_width;
13421464
int firstytile = (ybegin - m_spec.y) / m_spec.tile_height;
@@ -1492,14 +1614,19 @@ OpenEXRInput::read_native_deep_scanlines(int subimage, int miplevel, int ybegin,
14921614
lock_guard lock(*this);
14931615
if (!seek_subimage(subimage, miplevel))
14941616
return false;
1617+
const PartInfo& part(m_parts[m_subimage]);
1618+
if (part.luminance_chroma) {
1619+
errorf(
1620+
"OpenEXRInput::read_native_deep_scanlines is not supported for luminance-chroma images");
1621+
return false;
1622+
}
14951623
if (m_deep_scanline_input_part == NULL) {
14961624
errorf(
14971625
"called OpenEXRInput::read_native_deep_scanlines without an open file");
14981626
return false;
14991627
}
15001628

15011629
try {
1502-
const PartInfo& part(m_parts[m_subimage]);
15031630
size_t npixels = (yend - ybegin) * m_spec.width;
15041631
chend = clamp(chend, chbegin + 1, m_spec.nchannels);
15051632
int nchans = chend - chbegin;
@@ -1564,14 +1691,19 @@ OpenEXRInput::read_native_deep_tiles(int subimage, int miplevel, int xbegin,
15641691
lock_guard lock(*this);
15651692
if (!seek_subimage(subimage, miplevel))
15661693
return false;
1694+
const PartInfo& part(m_parts[m_subimage]);
1695+
if (part.luminance_chroma) {
1696+
errorf(
1697+
"OpenEXRInput::read_native_deep_tiles is not supported for luminance-chroma images");
1698+
return false;
1699+
}
15671700
if (m_deep_tiled_input_part == NULL) {
15681701
errorf(
15691702
"called OpenEXRInput::read_native_deep_tiles without an open file");
15701703
return false;
15711704
}
15721705

15731706
try {
1574-
const PartInfo& part(m_parts[m_subimage]);
15751707
size_t width = xend - xbegin;
15761708
size_t height = yend - ybegin;
15771709
size_t npixels = width * height;

0 commit comments

Comments
 (0)