Skip to content

Commit 672f350

Browse files
committed
fix(ImageBuf): Better errors for nonexistant subimages/mips
An important clarification: ImageInput::seek_subimage() returns false but does not set an error message if the only thing wrong is requesting a non-existent subimage or miplevel, and similarly, ImageInput::spec() and spec_dimensions() set an empty spec (easy to tell by spec.format == TypeUnknown) in that case. All three of these only set an error message if the reason it couldn't perform the task because there was actually something wrong with the file or the I/O. (Put another way, it's common to use these to simply probe which subimages or mip levels exist, and returning false for "no it doesn't" isn't really an error, it's expected behavior.) OK, then. We weren't always following these rules, sometimes setting error messages when we shouldn't. Other times (in different routines) when we didn't set error messages but should have. And also, there are some downstream places such as ImageBuf::init_spec() that assumed that seek_subimage / spec / spec_dimensions set an error message (but they didn't, by design) and should have raised their own errors when discovering that a requested subimage or miplevel didn't exist. Also, there were a couple spots in ImageBuf internals where we tried to do the right thing, but instead of calling ImageBufImpl::error(), we called errorfmt(), which didn't exist in ImageBufImpl, and so was calling the global OIIO::errorfmt(), which therefore was setting the error on the wrong object entirely. (Fix that by adding an errorfmt method in ImageBufImpl so that mistake is not possible. Should have been like that all along.) All this leads to more consistent error reporting (and not reporting, in the cases we weren't supposed to), especially for people calling ImageBuf::read() and ImageBuf::init_spec(). Signed-off-by: Larry Gritz <[email protected]>
1 parent 731b90b commit 672f350

File tree

12 files changed

+197
-39
lines changed

12 files changed

+197
-39
lines changed

src/include/OpenImageIO/imageio.h

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,6 +1142,10 @@ class OIIO_API ImageInput {
11421142
/// `seek_subimage()`. It is thus not thread-safe, since the spec may
11431143
/// change if another thread calls `seek_subimage`, or any of the
11441144
/// `read_*()` functions that take explicit subimage/miplevel.
1145+
///
1146+
/// This method should be considered deprecated, and we advise
1147+
/// always using the thread-safe `spec(subimage, miplevel)` method
1148+
/// instead.
11451149
virtual const ImageSpec &spec (void) const { return m_spec; }
11461150

11471151
/// Return a full copy of the ImageSpec of the designated subimage and
@@ -1150,7 +1154,9 @@ class OIIO_API ImageInput {
11501154
/// ImageSpec if there is lots of named metadata to allocate and copy.
11511155
/// See also the less expensive `spec_dimensions()`. Errors (such as
11521156
/// having requested a nonexistent subimage) are indicated by returning
1153-
/// an ImageSpec with `format==TypeUnknown`.
1157+
/// an ImageSpec with `format==TypeUnknown`, but does not call
1158+
/// errorfmt() to set an error message merely for being an out-of-range
1159+
/// subimage or miplevel.
11541160
virtual ImageSpec spec (int subimage, int miplevel=0);
11551161

11561162
/// Return a copy of the ImageSpec of the designated subimage and
@@ -1160,7 +1166,9 @@ class OIIO_API ImageInput {
11601166
/// a relatively inexpensive operation if you don't need that
11611167
/// information. It is guaranteed to be thread-safe. Errors (such as
11621168
/// having requested a nonexistent subimage) are indicated by returning
1163-
/// an ImageSpec with `format==TypeUnknown`.
1169+
/// an ImageSpec with `format==TypeUnknown`, but does not call
1170+
/// errorfmt() to set an error message merely for being an out-of-range
1171+
/// subimage or miplevel.
11641172
virtual ImageSpec spec_dimensions (int subimage, int miplevel=0);
11651173

11661174
/// Retrieve a reduced-resolution ("thumbnail") version of the given
@@ -1204,12 +1212,16 @@ class OIIO_API ImageInput {
12041212
/// Seek to the given subimage and MIP-map level within the open image
12051213
/// file. The first subimage of the file has index 0, the highest-
12061214
/// resolution MIP level has index 0. The new subimage's vital
1207-
/// statistics=may be retrieved by `this->spec()`. The reader is
1215+
/// statistics may be retrieved by `this->spec()`. The reader is
12081216
/// expected to give the appearance of random access to subimages and
12091217
/// MIP levels -- in other words, if it can't randomly seek to the given
12101218
/// subimage/level, it should transparently close, reopen, and
12111219
/// sequentially read through prior subimages and levels.
12121220
///
1221+
/// Inability to seek to an out-of-range subimage or miplevel is indicated
1222+
/// by returning false, but it does not call errorfmt() to set an error
1223+
/// message unless it's the result of a damaged file.
1224+
///
12131225
/// @returns
12141226
/// `true` upon success, or `false` upon failure. A failure may
12151227
/// indicate that no such subimage or MIP level exists in the

src/libOpenImageIO/imagebuf.cpp

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,14 @@ class ImageBufImpl {
185185
error(Strutil::fmt::format(fmt, args...));
186186
}
187187

188+
// Another alias for error so we don't get mixed up with the global
189+
// OIIO::errorfmt.
190+
template<typename... Args>
191+
void errorfmt(const char* fmt, const Args&... args) const
192+
{
193+
error(Strutil::fmt::format(fmt, args...));
194+
}
195+
188196
void error(string_view message) const;
189197

190198
ImageBuf::IBStorage storage() const { return m_storage; }
@@ -1088,18 +1096,49 @@ ImageBufImpl::init_spec(string_view filename, int subimage, int miplevel,
10881096
// If no configspec, just do a regular soft invalidate
10891097
invalidate(m_name, false);
10901098
}
1091-
m_imagecache->get_image_info(m_name, subimage, miplevel, s_subimages,
1092-
TypeInt, &m_nsubimages);
1093-
m_imagecache->get_image_info(m_name, subimage, miplevel, s_miplevels,
1094-
TypeInt, &m_nmiplevels);
10951099
const char* fmt = NULL;
1096-
m_imagecache->get_image_info(m_name, subimage, miplevel, s_fileformat,
1097-
TypeString, &fmt);
1100+
if (!(m_imagecache->get_image_info(m_name, subimage, miplevel,
1101+
s_subimages, TypeInt, &m_nsubimages)
1102+
&& m_imagecache->get_image_info(m_name, subimage, miplevel,
1103+
s_miplevels, TypeInt,
1104+
&m_nmiplevels)
1105+
&& m_imagecache->get_image_info(m_name, subimage, miplevel,
1106+
s_fileformat, TypeString, &fmt))) {
1107+
std::string err = m_imagecache->geterror();
1108+
error(err.size()
1109+
? err
1110+
: Strutil::format("trouble reading from {}", m_name));
1111+
m_spec = ImageSpec();
1112+
m_nativespec = m_spec;
1113+
return false;
1114+
}
10981115
m_fileformat = ustring(fmt);
10991116

1100-
m_imagecache->get_imagespec(m_name, m_nativespec, subimage);
1101-
m_spec = m_nativespec;
1102-
m_imagecache->get_cache_dimensions(m_name, m_spec, subimage, miplevel);
1117+
if (subimage < 0 || subimage >= m_nsubimages) {
1118+
error("Invalid subimage {} requested (of {} subimages)", subimage,
1119+
m_nsubimages);
1120+
m_spec = ImageSpec();
1121+
m_nativespec = m_spec;
1122+
return false;
1123+
}
1124+
if (miplevel < 0 || miplevel >= m_nmiplevels) {
1125+
error(
1126+
"Invalid MIP level {} requested for subimage {} (of {} levels)",
1127+
miplevel, subimage, m_nmiplevels);
1128+
m_spec = ImageSpec();
1129+
m_nativespec = m_spec;
1130+
return false;
1131+
}
1132+
bool ok = m_imagecache->get_imagespec(m_name, m_nativespec, subimage);
1133+
m_spec = m_nativespec;
1134+
ok &= m_imagecache->get_cache_dimensions(m_name, m_spec, subimage,
1135+
miplevel);
1136+
if (!ok || m_nativespec.format == TypeUnknown) {
1137+
std::string cacheerr = m_imagecache->geterror();
1138+
error("Unable to find subimage={}, miplevel={}{}{}",
1139+
cacheerr.size() ? ": " : "", cacheerr);
1140+
return false;
1141+
}
11031142

11041143
m_xstride = m_spec.pixel_bytes();
11051144
m_ystride = m_spec.scanline_bytes();
@@ -1165,20 +1204,26 @@ ImageBufImpl::init_spec(string_view filename, int subimage, int miplevel,
11651204
m_current_miplevel = -1;
11661205
auto input = ImageInput::open(filename, m_configspec.get(), m_rioproxy);
11671206
if (!input) {
1168-
m_err = OIIO::geterror();
1207+
error("Could not open file: {}", OIIO::geterror());
11691208
atomic_fetch_add(pvt::IB_total_open_time, float(timer()));
11701209
return false;
11711210
}
1172-
m_spec = input->spec(subimage, miplevel);
1211+
m_spec = input->spec(subimage, miplevel);
1212+
m_nativespec = m_spec;
11731213
if (input->has_error()) {
1174-
m_err = input->geterror();
1214+
errorfmt("Error reading: {}", input->geterror());
1215+
atomic_fetch_add(pvt::IB_total_open_time, float(timer()));
1216+
return false;
1217+
}
1218+
if (m_spec.format == TypeUnknown) {
1219+
errorfmt("Could not seek to subimage={} miplevel={}", subimage,
1220+
miplevel);
11751221
atomic_fetch_add(pvt::IB_total_open_time, float(timer()));
11761222
return false;
11771223
}
11781224
m_badfile = false;
11791225
m_spec_valid = true;
11801226
m_fileformat = ustring(input->format_name());
1181-
m_nativespec = m_spec;
11821227
m_xstride = m_spec.pixel_bytes();
11831228
m_ystride = m_spec.scanline_bytes();
11841229
m_zstride = clamped_mult64(m_ystride, (imagesize_t)m_spec.height);

src/openexr.imageio/exrinput.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1011,8 +1011,11 @@ OpenEXRInput::seek_subimage(int subimage, int miplevel)
10111011
const Imf::Header* header = NULL;
10121012
if (m_input_multipart)
10131013
header = &(m_input_multipart->header(subimage));
1014-
if (!part.parse_header(this, header))
1014+
if (!part.parse_header(this, header)) {
1015+
errorfmt("Could not seek to subimage={}: unable to parse header",
1016+
subimage, miplevel);
10151017
return false;
1018+
}
10161019
part.initialized = true;
10171020
}
10181021

src/openexr.imageio/exrinput_c.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -904,8 +904,10 @@ OpenEXRCoreInput::PartInfo::query_channels(OpenEXRCoreInput* in,
904904
spec.nchannels = 0;
905905
const exr_attr_chlist_t* chlist;
906906
exr_result_t rv = exr_get_channels(ctxt, subimage, &chlist);
907-
if (rv != EXR_ERR_SUCCESS)
907+
if (rv != EXR_ERR_SUCCESS) {
908+
in->errorfmt("exr_get_channels failed");
908909
return false;
910+
}
909911

910912
std::vector<CChanNameHolder> cnh;
911913
int c = 0;
@@ -1053,8 +1055,11 @@ OpenEXRCoreInput::seek_subimage(int subimage, int miplevel)
10531055

10541056
PartInfo& part(m_parts[subimage]);
10551057
if (!part.initialized) {
1056-
if (!part.parse_header(this, m_exr_context, subimage, miplevel))
1058+
if (!part.parse_header(this, m_exr_context, subimage, miplevel)) {
1059+
errorfmt("Could not seek to subimage={}: unable to parse header",
1060+
subimage, miplevel);
10571061
return false;
1062+
}
10581063
part.initialized = true;
10591064
}
10601065

@@ -1087,6 +1092,8 @@ OpenEXRCoreInput::seek_subimage(int subimage, int miplevel)
10871092
ImageSpec
10881093
OpenEXRCoreInput::spec(int subimage, int miplevel)
10891094
{
1095+
// By design, spec() inicates failure with empty spec, it does not call
1096+
// errorfmt().
10901097
ImageSpec ret;
10911098
if (subimage < 0 || subimage >= m_nsubimages)
10921099
return ret; // invalid
@@ -1112,6 +1119,8 @@ OpenEXRCoreInput::spec(int subimage, int miplevel)
11121119
ImageSpec
11131120
OpenEXRCoreInput::spec_dimensions(int subimage, int miplevel)
11141121
{
1122+
// By design, spec_dimensions() inicates failure with empty spec, it does
1123+
// not call errorfmt().
11151124
ImageSpec ret;
11161125
if (subimage < 0 || subimage >= m_nsubimages)
11171126
return ret; // invalid
@@ -1434,7 +1443,7 @@ OpenEXRCoreInput::read_native_tiles(int subimage, int miplevel, int xbegin,
14341443
int zend, void* data)
14351444
{
14361445
if (!m_exr_context) {
1437-
errorfmt("called OpenEXRInput::read_native_tile without an open file");
1446+
errorfmt("called OpenEXRInput::read_native_tiles without an open file");
14381447
return false;
14391448
}
14401449

@@ -1453,7 +1462,7 @@ OpenEXRCoreInput::read_native_tiles(int subimage, int miplevel, int xbegin,
14531462
void* data)
14541463
{
14551464
if (!m_exr_context) {
1456-
errorfmt("called OpenEXRInput::read_native_tile without an open file");
1465+
errorfmt("called OpenEXRInput::read_native_tiles without an open file");
14571466
return false;
14581467
}
14591468

src/tiff.imageio/tiffinput.cpp

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,13 @@ class TIFFInput final : public ImageInput {
113113
{
114114
// If m_emulate_mipmap is true, pretend subimages are mipmap levels
115115
lock_guard lock(*this);
116-
return m_emulate_mipmap ? 0 : m_subimage;
116+
return m_subimage;
117117
}
118118
int current_miplevel(void) const override
119119
{
120120
// If m_emulate_mipmap is true, pretend subimages are mipmap levels
121121
lock_guard lock(*this);
122-
return m_emulate_mipmap ? m_subimage : 0;
122+
return m_miplevel;
123123
}
124124
bool seek_subimage(int subimage, int miplevel) override;
125125
ImageSpec spec(int subimage, int miplevel) override;
@@ -173,7 +173,9 @@ class TIFFInput final : public ImageInput {
173173
std::string m_filename; ///< Stash the filename
174174
std::vector<unsigned char> m_scratch; ///< Scratch space for us to use
175175
std::vector<unsigned char> m_scratch2; ///< More scratch
176-
int m_subimage; ///< What subimage are we looking at?
176+
int m_subimage; ///< What subimage do we think we're on?
177+
int m_miplevel; ///< Which mip level do we think we're on?
178+
int m_actual_subimage; ///< Actual subimage we're on
177179
int m_next_scanline; ///< Next scanline we'll read, relative to ymin
178180
bool m_no_random_access; ///< Should we avoid random access?
179181
bool m_emulate_mipmap; ///< Should we emulate mip with subimage?
@@ -202,6 +204,8 @@ class TIFFInput final : public ImageInput {
202204
{
203205
m_tif = NULL;
204206
m_subimage = -1;
207+
m_miplevel = -1;
208+
m_actual_subimage = -1;
205209
m_emulate_mipmap = false;
206210
m_keep_unassociated_alpha = false;
207211
m_raw_color = false;
@@ -742,8 +746,10 @@ TIFFInput::valid_file(Filesystem::IOProxy* ioproxy) const
742746
bool
743747
TIFFInput::open(const std::string& name, ImageSpec& newspec)
744748
{
745-
m_filename = name;
746-
m_subimage = -1;
749+
m_filename = name;
750+
m_subimage = -1;
751+
m_miplevel = -1;
752+
m_actual_subimage = -1;
747753

748754
bool ok = seek_subimage(0, 0);
749755
newspec = spec();
@@ -775,8 +781,14 @@ TIFFInput::open(const std::string& name, ImageSpec& newspec,
775781
bool
776782
TIFFInput::seek_subimage(int subimage, int miplevel)
777783
{
778-
if (subimage < 0) // Illegal
784+
if (subimage == m_subimage && miplevel == m_miplevel) {
785+
// We're already pointing to the right subimage
786+
return true;
787+
}
788+
789+
if (subimage < 0 || miplevel < 0) // Illegal
779790
return false;
791+
int orig_subimage = subimage; // the original request
780792
if (m_emulate_mipmap) {
781793
// Emulating MIPmap? Pretend one subimage, many MIP levels.
782794
if (subimage != 0)
@@ -788,15 +800,12 @@ TIFFInput::seek_subimage(int subimage, int miplevel)
788800
return false;
789801
}
790802

791-
if (subimage == m_subimage) {
792-
// We're already pointing to the right subimage
793-
return true;
794-
}
795-
796803
// If we're emulating a MIPmap, only resolution is allowed to change
797804
// between MIP levels, so if we already have a valid level in m_spec,
798805
// we don't need to re-parse metadata, it's guaranteed to be the same.
799-
bool read_meta = !(m_emulate_mipmap && m_tif && m_subimage >= 0);
806+
bool read_meta = true;
807+
if (m_emulate_mipmap && m_tif && m_miplevel >= 0)
808+
read_meta = false;
800809

801810
if (!m_tif) {
802811
#if OIIO_TIFFLIB_VERSION >= 40500
@@ -850,12 +859,12 @@ TIFFInput::seek_subimage(int subimage, int miplevel)
850859
return false;
851860
}
852861
m_is_byte_swapped = TIFFIsByteSwapped(m_tif);
853-
m_subimage = 0;
862+
m_actual_subimage = 0;
854863
}
855864

856865
m_next_scanline = 0; // next scanline we'll read
857-
if (subimage == m_subimage || TIFFSetDirectory(m_tif, subimage)) {
858-
m_subimage = subimage;
866+
if (subimage == m_actual_subimage || TIFFSetDirectory(m_tif, subimage)) {
867+
m_actual_subimage = subimage;
859868
if (!readspec(read_meta))
860869
return false;
861870

@@ -878,11 +887,17 @@ TIFFInput::seek_subimage(int subimage, int miplevel)
878887
if (!check_open(m_spec,
879888
{ 0, 1 << 20, 0, 1 << 20, 0, 1 << 16, 0, 1 << 16 }))
880889
return false;
890+
m_subimage = orig_subimage;
891+
m_miplevel = miplevel;
881892
return true;
882893
} else {
883894
std::string e = oiio_tiff_last_error();
884-
errorfmt("Err: {}", e.length() ? e : m_filename);
885-
m_subimage = -1;
895+
errorfmt("could not seek to {} {}",
896+
m_emulate_mipmap ? "miplevel" : "subimage", subimage,
897+
e.length() ? ": " : "", e.length() ? e : std::string(""));
898+
m_subimage = -1;
899+
m_miplevel = -1;
900+
m_actual_subimage = -1;
886901
return false;
887902
}
888903
}
@@ -905,6 +920,10 @@ TIFFInput::spec(int subimage, int miplevel)
905920
// Index into the spec list by miplevel instead, because that's
906921
// what it really contains.
907922
s = miplevel;
923+
} else {
924+
// Not emulating MIP levels -> there are none
925+
if (miplevel)
926+
return ret;
908927
}
909928

910929
lock_guard lock(*this);
@@ -937,6 +956,10 @@ TIFFInput::spec_dimensions(int subimage, int miplevel)
937956
// Index into the spec list by miplevel instead, because that's
938957
// what it really contains.
939958
s = miplevel;
959+
} else {
960+
// Not emulating MIP levels -> there are none
961+
if (miplevel)
962+
return ret;
940963
}
941964

942965
lock_guard lock(*this);
@@ -1298,7 +1321,7 @@ TIFFInput::readspec(bool read_meta)
12981321
}
12991322
// TIFFReadEXIFDirectory seems to do something to the internal state
13001323
// that requires a TIFFSetDirectory to set things straight again.
1301-
TIFFSetDirectory(m_tif, m_subimage);
1324+
TIFFSetDirectory(m_tif, m_actual_subimage);
13021325
}
13031326

13041327
// Search for IPTC metadata in IIM form -- but older versions of

testsuite/jpeg2000/ref/out.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ Comparing "../oiio-images/jpeg2000/broken/issue_3427.jp2" and "issue_3427.jp2"
44
idiff ERROR: Could not read ../oiio-images/jpeg2000/broken/issue_3427.jp2:
55
Invalid image file "../oiio-images/jpeg2000/broken/issue_3427.jp2": Tile part length size inconsistent with stream length
66
Failed to decode the codestream in the JP2 file
7+
Invalid image file "../oiio-images/jpeg2000/broken/issue_3427.jp2": Tile part length size inconsistent with stream length
8+
Failed to decode the codestream in the JP2 file

testsuite/png-damaged/ref/out.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ libpng error: tEXt: Read error: hit end of file in png reader
99
idiff ERROR: Could not read invalid_gray_alpha_sbit.png:
1010
Invalid image file "invalid_gray_alpha_sbit.png": Read error: hit end of file in png reader
1111
PNG read error: tEXt: Read error: hit end of file in png reader
12+
Invalid image file "invalid_gray_alpha_sbit.png": Read error: hit end of file in png reader
13+
PNG read error: tEXt: Read error: hit end of file in png reader

0 commit comments

Comments
 (0)