16
16
17
17
#include < boost/version.hpp>
18
18
19
+ #include < OpenEXR/ImfArray.h>
19
20
#include < OpenEXR/ImfChannelList.h>
20
21
#include < OpenEXR/ImfEnvmap.h>
21
22
#include < OpenEXR/ImfInputFile.h>
23
+ #include < OpenEXR/ImfRgba.h>
22
24
#include < OpenEXR/ImfTestFile.h>
23
25
#include < OpenEXR/ImfTiledInputFile.h>
24
26
@@ -51,6 +53,7 @@ OIIO_GCC_PRAGMA(GCC diagnostic ignored "-Wunused-parameter")
51
53
#include < OpenEXR/ImfMultiPartInputFile.h>
52
54
#include < OpenEXR/ImfPartType.h>
53
55
#include < OpenEXR/ImfRationalAttribute.h>
56
+ #include < OpenEXR/ImfRgbaFile.h>
54
57
#include < OpenEXR/ImfStringAttribute.h>
55
58
#include < OpenEXR/ImfStringVectorAttribute.h>
56
59
#include < OpenEXR/ImfTiledInputPart.h>
@@ -179,12 +182,13 @@ class OpenEXRInput final : public ImageInput {
179
182
struct PartInfo {
180
183
std::atomic_bool initialized;
181
184
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?
188
192
Imath::Box2i top_datawindow;
189
193
Imath::Box2i top_displaywindow;
190
194
std::vector<Imf::PixelType> pixeltype; // /< Imf pixel type for each chan
@@ -202,6 +206,7 @@ class OpenEXRInput final : public ImageInput {
202
206
, levelmode(p.levelmode)
203
207
, roundingmode(p.roundingmode)
204
208
, cubeface(p.cubeface)
209
+ , luminance_chroma(p.luminance_chroma)
205
210
, nmiplevels(p.nmiplevels)
206
211
, top_datawindow(p.top_datawindow)
207
212
, top_displaywindow(p.top_displaywindow)
@@ -223,6 +228,7 @@ class OpenEXRInput final : public ImageInput {
223
228
Imf::TiledInputPart* m_tiled_input_part;
224
229
Imf::DeepScanLineInputPart* m_deep_scanline_input_part;
225
230
Imf::DeepTiledInputPart* m_deep_tiled_input_part;
231
+ Imf::RgbaInputFile* m_input_rgba;
226
232
Filesystem::IOProxy* m_io = nullptr ;
227
233
std::unique_ptr<Filesystem::IOProxy> m_local_io;
228
234
int m_subimage; // /< What subimage are we looking at?
@@ -238,6 +244,7 @@ class OpenEXRInput final : public ImageInput {
238
244
m_tiled_input_part = NULL ;
239
245
m_deep_scanline_input_part = NULL ;
240
246
m_deep_tiled_input_part = NULL ;
247
+ m_input_rgba = NULL ;
241
248
m_subimage = -1 ;
242
249
m_miplevel = -1 ;
243
250
m_io = nullptr ;
@@ -910,6 +917,38 @@ suffixfound(string_view name, span<ChanNameHolder> chans)
910
917
}
911
918
912
919
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 luminance-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
+
913
952
} // namespace
914
953
915
954
@@ -919,10 +958,8 @@ OpenEXRInput::PartInfo::query_channels(OpenEXRInput* in,
919
958
const Imf::Header* header)
920
959
{
921
960
OIIO_DASSERT (!initialized);
922
- bool ok = true ;
923
- spec.nchannels = 0 ;
961
+ bool ok = true ;
924
962
const Imf::ChannelList& channels (header->channels ());
925
- std::vector<std::string> channelnames; // Order of channels in file
926
963
std::vector<ChanNameHolder> cnh;
927
964
int c = 0 ;
928
965
for (auto ci = channels.begin (); ci != channels.end (); ++c, ++ci)
@@ -969,6 +1006,34 @@ OpenEXRInput::PartInfo::query_channels(OpenEXRInput* in,
969
1006
// Now we should have cnh sorted into the order that we want to present
970
1007
// to the OIIO client.
971
1008
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.attribute (" openexr:luminancechroma" , 1 );
1014
+ spec.format = TypeDesc::HALF;
1015
+ spec.nchannels = cnh.size ();
1016
+ if (spec.nchannels == 3 ) {
1017
+ spec.channelnames = { " R" , " G" , " B" };
1018
+ spec.alpha_channel = -1 ;
1019
+ spec.z_channel = -1 ;
1020
+ } else {
1021
+ OIIO_ASSERT (spec.nchannels == 4 );
1022
+ int index_a = get_index_of_suffix (" A" , cnh);
1023
+ if (index_a != -1 ) {
1024
+ spec.channelnames = { " R" , " G" , " B" , " A" };
1025
+ spec.alpha_channel = index_a;
1026
+ } else {
1027
+ spec.channelnames = { " R" , " G" , " B" , " Alpha" };
1028
+ spec.alpha_channel = get_index_of_suffix (" Alpha" , cnh);
1029
+ OIIO_ASSERT (spec.alpha_channel != -1 );
1030
+ }
1031
+ spec.z_channel = -1 ;
1032
+ }
1033
+ spec.channelformats .clear ();
1034
+ return true ;
1035
+ }
1036
+
972
1037
spec.format = TypeDesc::UNKNOWN;
973
1038
bool all_one_format = true ;
974
1039
for (int c = 0 ; c < spec.nchannels ; ++c) {
@@ -991,7 +1056,8 @@ OpenEXRInput::PartInfo::query_channels(OpenEXRInput* in,
991
1056
in->errorfmt (
992
1057
" Subsampled channels are not supported (channel \" {}\" has sampling {},{})." ,
993
1058
cnh[c].fullname , cnh[c].xSampling , cnh[c].ySampling );
994
- // FIXME: Some day, we should handle channel subsampling.
1059
+ // FIXME: Some day, we should handle channel subsampling (beyond the luminance chroma
1060
+ // special case, possibly replacing it).
995
1061
}
996
1062
}
997
1063
OIIO_DASSERT ((int )spec.channelnames .size () == spec.nchannels );
@@ -1088,8 +1154,18 @@ OpenEXRInput::seek_subimage(int subimage, int miplevel)
1088
1154
m_deep_scanline_input_part = NULL ;
1089
1155
delete m_deep_tiled_input_part;
1090
1156
m_deep_tiled_input_part = NULL ;
1157
+ delete m_input_rgba;
1158
+ m_input_rgba = NULL ;
1091
1159
try {
1092
- if (part.spec .deep ) {
1160
+ if (part.luminance_chroma ) {
1161
+ if (subimage != 0 || miplevel != 0 ) {
1162
+ errorf (
1163
+ " Non-zero subimage or miplevel are not supported for luminance-chroma images." );
1164
+ return false ;
1165
+ }
1166
+ m_input_stream->seekg (0 );
1167
+ m_input_rgba = new Imf::RgbaInputFile (*m_input_stream);
1168
+ } else if (part.spec .deep ) {
1093
1169
if (part.spec .tile_width )
1094
1170
m_deep_tiled_input_part
1095
1171
= new Imf::DeepTiledInputPart (*m_input_multipart,
@@ -1112,13 +1188,15 @@ OpenEXRInput::seek_subimage(int subimage, int miplevel)
1112
1188
m_tiled_input_part = NULL ;
1113
1189
m_deep_scanline_input_part = NULL ;
1114
1190
m_deep_tiled_input_part = NULL ;
1191
+ m_input_rgba = NULL ;
1115
1192
return false ;
1116
1193
} catch (...) { // catch-all for edge cases or compiler bugs
1117
1194
errorf (" OpenEXR exception: unknown" );
1118
1195
m_scanline_input_part = NULL ;
1119
1196
m_tiled_input_part = NULL ;
1120
1197
m_deep_scanline_input_part = NULL ;
1121
1198
m_deep_tiled_input_part = NULL ;
1199
+ m_input_rgba = NULL ;
1122
1200
return false ;
1123
1201
}
1124
1202
}
@@ -1200,6 +1278,7 @@ OpenEXRInput::close()
1200
1278
delete m_tiled_input_part;
1201
1279
delete m_deep_scanline_input_part;
1202
1280
delete m_deep_tiled_input_part;
1281
+ delete m_input_rgba;
1203
1282
delete m_input_stream;
1204
1283
init (); // Reset to initial state
1205
1284
return true ;
@@ -1226,7 +1305,6 @@ OpenEXRInput::read_native_scanlines(int subimage, int miplevel, int ybegin,
1226
1305
}
1227
1306
1228
1307
1229
-
1230
1308
bool
1231
1309
OpenEXRInput::read_native_scanlines (int subimage, int miplevel, int ybegin,
1232
1310
int yend, int /* z*/ , int chbegin, int chend,
@@ -1238,11 +1316,6 @@ OpenEXRInput::read_native_scanlines(int subimage, int miplevel, int ybegin,
1238
1316
chend = clamp (chend, chbegin + 1 , m_spec.nchannels );
1239
1317
// std::cerr << "openexr rns " << ybegin << ' ' << yend << ", channels "
1240
1318
// << 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
- }
1246
1319
1247
1320
// Compute where OpenEXR needs to think the full buffers starts.
1248
1321
// OpenImageIO requires that 'data' points to where the client wants
@@ -1255,6 +1328,51 @@ OpenEXRInput::read_native_scanlines(int subimage, int miplevel, int ybegin,
1255
1328
char * buf = (char *)data - m_spec.x * pixelbytes - ybegin * scanlinebytes;
1256
1329
1257
1330
try {
1331
+ if (part.luminance_chroma ) {
1332
+ Imath::Box2i dw = m_input_rgba->dataWindow ();
1333
+ if (dw.min .x != 0 || dw.min .y != 0
1334
+ || dw != m_input_rgba->displayWindow ()) {
1335
+ errorf (
1336
+ " Non-trivial data and/or display windows are not supported for luminance-chroma images." );
1337
+ return false ;
1338
+ }
1339
+ int dw_width = dw.max .x - dw.min .x + 1 ;
1340
+ int dw_height = dw.max .y - dw.min .y + 1 ;
1341
+ int chunk_height = yend - ybegin;
1342
+ // FIXME Are these assumptions correct?
1343
+ OIIO_ASSERT (ybegin >= dw.min .y );
1344
+ OIIO_ASSERT (yend <= dw.max .y + 1 );
1345
+ OIIO_ASSERT (chunk_height <= dw_height);
1346
+
1347
+ Imf::Array2D<Imf::Rgba> pixels (chunk_height, dw_width);
1348
+ m_input_rgba->setFrameBuffer (&pixels[0 ][0 ] - dw.min .x
1349
+ - ybegin * dw_width,
1350
+ 1 , dw_width);
1351
+ m_input_rgba->readPixels (ybegin, yend - 1 );
1352
+
1353
+ // FIXME There is probably some optimized code for this somewhere.
1354
+ for (int c = chbegin; c < chend; ++c) {
1355
+ size_t chanbytes = m_spec.channelformat (c).size ();
1356
+ half* src = &pixels[0 ][0 ].r + c;
1357
+ half* dst = (half*)((char *)data + c * chanbytes);
1358
+ for (int y = ybegin; y < yend; ++y) {
1359
+ for (int x = 0 ; x < m_spec.width ; ++x) {
1360
+ *dst = *src;
1361
+ src += 4 ; // always advance 4 RGBA halfs
1362
+ dst += m_spec.nchannels ;
1363
+ }
1364
+ }
1365
+ }
1366
+
1367
+ return true ;
1368
+ }
1369
+
1370
+ if (!m_scanline_input_part) {
1371
+ errorf (
1372
+ " called OpenEXRInput::read_native_scanlines without an open file" );
1373
+ return false ;
1374
+ }
1375
+
1258
1376
Imf::FrameBuffer frameBuffer;
1259
1377
size_t chanoffset = 0 ;
1260
1378
for (int c = chbegin; c < chend; ++c) {
@@ -1320,6 +1438,12 @@ OpenEXRInput::read_native_tiles(int subimage, int miplevel, int xbegin,
1320
1438
if (!seek_subimage (subimage, miplevel))
1321
1439
return false ;
1322
1440
chend = clamp (chend, chbegin + 1 , m_spec.nchannels );
1441
+ const PartInfo& part (m_parts[m_subimage]);
1442
+ if (part.luminance_chroma ) {
1443
+ errorf (
1444
+ " OpenEXRInput::read_native_tiles is not supported for luminance-chroma images" );
1445
+ return false ;
1446
+ }
1323
1447
#if 0
1324
1448
std::cerr << "openexr rnt " << xbegin << ' ' << xend << ' ' << ybegin
1325
1449
<< ' ' << yend << ", chans " << chbegin
@@ -1336,7 +1460,6 @@ OpenEXRInput::read_native_tiles(int subimage, int miplevel, int xbegin,
1336
1460
// to put the pixels being read, but OpenEXR's frameBuffer.insert()
1337
1461
// wants where the address of the "virtual framebuffer" for the
1338
1462
// whole image.
1339
- const PartInfo& part (m_parts[m_subimage]);
1340
1463
size_t pixelbytes = m_spec.pixel_bytes (chbegin, chend, true );
1341
1464
int firstxtile = (xbegin - m_spec.x ) / m_spec.tile_width ;
1342
1465
int firstytile = (ybegin - m_spec.y ) / m_spec.tile_height ;
@@ -1492,14 +1615,19 @@ OpenEXRInput::read_native_deep_scanlines(int subimage, int miplevel, int ybegin,
1492
1615
lock_guard lock (*this );
1493
1616
if (!seek_subimage (subimage, miplevel))
1494
1617
return false ;
1618
+ const PartInfo& part (m_parts[m_subimage]);
1619
+ if (part.luminance_chroma ) {
1620
+ errorf (
1621
+ " OpenEXRInput::read_native_deep_scanlines is not supported for luminance-chroma images" );
1622
+ return false ;
1623
+ }
1495
1624
if (m_deep_scanline_input_part == NULL ) {
1496
1625
errorf (
1497
1626
" called OpenEXRInput::read_native_deep_scanlines without an open file" );
1498
1627
return false ;
1499
1628
}
1500
1629
1501
1630
try {
1502
- const PartInfo& part (m_parts[m_subimage]);
1503
1631
size_t npixels = (yend - ybegin) * m_spec.width ;
1504
1632
chend = clamp (chend, chbegin + 1 , m_spec.nchannels );
1505
1633
int nchans = chend - chbegin;
@@ -1564,14 +1692,19 @@ OpenEXRInput::read_native_deep_tiles(int subimage, int miplevel, int xbegin,
1564
1692
lock_guard lock (*this );
1565
1693
if (!seek_subimage (subimage, miplevel))
1566
1694
return false ;
1695
+ const PartInfo& part (m_parts[m_subimage]);
1696
+ if (part.luminance_chroma ) {
1697
+ errorf (
1698
+ " OpenEXRInput::read_native_deep_tiles is not supported for luminance-chroma images" );
1699
+ return false ;
1700
+ }
1567
1701
if (m_deep_tiled_input_part == NULL ) {
1568
1702
errorf (
1569
1703
" called OpenEXRInput::read_native_deep_tiles without an open file" );
1570
1704
return false ;
1571
1705
}
1572
1706
1573
1707
try {
1574
- const PartInfo& part (m_parts[m_subimage]);
1575
1708
size_t width = xend - xbegin;
1576
1709
size_t height = yend - ybegin;
1577
1710
size_t npixels = width * height;
0 commit comments