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 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
+
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,33 @@ 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.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
+
972
1036
spec.format = TypeDesc::UNKNOWN;
973
1037
bool all_one_format = true ;
974
1038
for (int c = 0 ; c < spec.nchannels ; ++c) {
@@ -991,7 +1055,8 @@ OpenEXRInput::PartInfo::query_channels(OpenEXRInput* in,
991
1055
in->errorfmt (
992
1056
" Subsampled channels are not supported (channel \" {}\" has sampling {},{})." ,
993
1057
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).
995
1060
}
996
1061
}
997
1062
OIIO_DASSERT ((int )spec.channelnames .size () == spec.nchannels );
@@ -1088,8 +1153,18 @@ OpenEXRInput::seek_subimage(int subimage, int miplevel)
1088
1153
m_deep_scanline_input_part = NULL ;
1089
1154
delete m_deep_tiled_input_part;
1090
1155
m_deep_tiled_input_part = NULL ;
1156
+ delete m_input_rgba;
1157
+ m_input_rgba = NULL ;
1091
1158
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 ) {
1093
1168
if (part.spec .tile_width )
1094
1169
m_deep_tiled_input_part
1095
1170
= new Imf::DeepTiledInputPart (*m_input_multipart,
@@ -1112,13 +1187,15 @@ OpenEXRInput::seek_subimage(int subimage, int miplevel)
1112
1187
m_tiled_input_part = NULL ;
1113
1188
m_deep_scanline_input_part = NULL ;
1114
1189
m_deep_tiled_input_part = NULL ;
1190
+ m_input_rgba = NULL ;
1115
1191
return false ;
1116
1192
} catch (...) { // catch-all for edge cases or compiler bugs
1117
1193
errorf (" OpenEXR exception: unknown" );
1118
1194
m_scanline_input_part = NULL ;
1119
1195
m_tiled_input_part = NULL ;
1120
1196
m_deep_scanline_input_part = NULL ;
1121
1197
m_deep_tiled_input_part = NULL ;
1198
+ m_input_rgba = NULL ;
1122
1199
return false ;
1123
1200
}
1124
1201
}
@@ -1200,6 +1277,7 @@ OpenEXRInput::close()
1200
1277
delete m_tiled_input_part;
1201
1278
delete m_deep_scanline_input_part;
1202
1279
delete m_deep_tiled_input_part;
1280
+ delete m_input_rgba;
1203
1281
delete m_input_stream;
1204
1282
init (); // Reset to initial state
1205
1283
return true ;
@@ -1226,7 +1304,6 @@ OpenEXRInput::read_native_scanlines(int subimage, int miplevel, int ybegin,
1226
1304
}
1227
1305
1228
1306
1229
-
1230
1307
bool
1231
1308
OpenEXRInput::read_native_scanlines (int subimage, int miplevel, int ybegin,
1232
1309
int yend, int /* z*/ , int chbegin, int chend,
@@ -1238,11 +1315,6 @@ OpenEXRInput::read_native_scanlines(int subimage, int miplevel, int ybegin,
1238
1315
chend = clamp (chend, chbegin + 1 , m_spec.nchannels );
1239
1316
// std::cerr << "openexr rns " << ybegin << ' ' << yend << ", channels "
1240
1317
// << 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
1318
1247
1319
// Compute where OpenEXR needs to think the full buffers starts.
1248
1320
// OpenImageIO requires that 'data' points to where the client wants
@@ -1255,6 +1327,51 @@ OpenEXRInput::read_native_scanlines(int subimage, int miplevel, int ybegin,
1255
1327
char * buf = (char *)data - m_spec.x * pixelbytes - ybegin * scanlinebytes;
1256
1328
1257
1329
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
+
1258
1375
Imf::FrameBuffer frameBuffer;
1259
1376
size_t chanoffset = 0 ;
1260
1377
for (int c = chbegin; c < chend; ++c) {
@@ -1320,6 +1437,12 @@ OpenEXRInput::read_native_tiles(int subimage, int miplevel, int xbegin,
1320
1437
if (!seek_subimage (subimage, miplevel))
1321
1438
return false ;
1322
1439
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
+ }
1323
1446
#if 0
1324
1447
std::cerr << "openexr rnt " << xbegin << ' ' << xend << ' ' << ybegin
1325
1448
<< ' ' << yend << ", chans " << chbegin
@@ -1336,7 +1459,6 @@ OpenEXRInput::read_native_tiles(int subimage, int miplevel, int xbegin,
1336
1459
// to put the pixels being read, but OpenEXR's frameBuffer.insert()
1337
1460
// wants where the address of the "virtual framebuffer" for the
1338
1461
// whole image.
1339
- const PartInfo& part (m_parts[m_subimage]);
1340
1462
size_t pixelbytes = m_spec.pixel_bytes (chbegin, chend, true );
1341
1463
int firstxtile = (xbegin - m_spec.x ) / m_spec.tile_width ;
1342
1464
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,
1492
1614
lock_guard lock (*this );
1493
1615
if (!seek_subimage (subimage, miplevel))
1494
1616
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
+ }
1495
1623
if (m_deep_scanline_input_part == NULL ) {
1496
1624
errorf (
1497
1625
" called OpenEXRInput::read_native_deep_scanlines without an open file" );
1498
1626
return false ;
1499
1627
}
1500
1628
1501
1629
try {
1502
- const PartInfo& part (m_parts[m_subimage]);
1503
1630
size_t npixels = (yend - ybegin) * m_spec.width ;
1504
1631
chend = clamp (chend, chbegin + 1 , m_spec.nchannels );
1505
1632
int nchans = chend - chbegin;
@@ -1564,14 +1691,19 @@ OpenEXRInput::read_native_deep_tiles(int subimage, int miplevel, int xbegin,
1564
1691
lock_guard lock (*this );
1565
1692
if (!seek_subimage (subimage, miplevel))
1566
1693
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
+ }
1567
1700
if (m_deep_tiled_input_part == NULL ) {
1568
1701
errorf (
1569
1702
" called OpenEXRInput::read_native_deep_tiles without an open file" );
1570
1703
return false ;
1571
1704
}
1572
1705
1573
1706
try {
1574
- const PartInfo& part (m_parts[m_subimage]);
1575
1707
size_t width = xend - xbegin;
1576
1708
size_t height = yend - ybegin;
1577
1709
size_t npixels = width * height;
0 commit comments