Skip to content

Commit f243897

Browse files
authored
fix: ImageOutput::check_open logic was flawed (#4779)
We documented this function as checking the valid range of pixel coordinates, but we CALLED it everywhere as if we were only checking the resolution. But we IMPLEMENTED as if we were checking the range, generating errors for outputting exr files with negative pixel window origins. But why was nobody seeing those errors when writing exr files with negative origins? Because the openexr writer BOTCHED it by not noticing the return value and considering it a failure. Oh boy. All my fault. So the fix: * Change the comment documenting check_open() to match the way we had been using it at the call sites. * Change the implementation of check_open() to match this by no longer checking whether all four corners were in the range -- only check if each dimension's size is within the limit for that dimension. * Fix the OpenEXR writer's failure to notice the check_open failure. * Does this eliminate necessary checks? No! We already separately checked that nonzero origins were only allowed for formats that support it, and that negative origins were only allowed for formats that support that. So the flawed check was redundant anyway. Ha! * Add a test verifying that we can write exr files with negative origins. (Technically, that would have passed before, but only because we were ignoring the incorrect error.) --------- Signed-off-by: Larry Gritz <lg@larrygritz.com>
1 parent 25eb718 commit f243897

6 files changed

Lines changed: 35 additions & 29 deletions

File tree

src/include/OpenImageIO/imageio.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2164,11 +2164,12 @@ class OIIO_API ImageInput {
21642164
/// The ImageSpec that we are validating.
21652165
///
21662166
/// @param range
2167-
/// An ROI that describes the allowable pixel coordinates and channel
2168-
/// indices as half-open intervals. For example, the default value
2169-
/// `{0, 65535, 0, 65535, 0, 1, 0, 4}` means that pixel coordinates
2170-
/// must be non-negative and the width and height be representable by
2171-
/// a uint16 value, up to 4 channels are allowed, and volumes are not
2167+
/// An ROI that describes the allowable resolution and channel count:
2168+
/// the width, height, depth, and channel count of the ROI are the
2169+
/// maximum allowed for the file type. For example, the default value
2170+
/// `{0, 65535, 0, 65535, 0, 1, 0, 4}` means that pixel data width
2171+
/// and height must be non-negative and representable by uint16
2172+
/// values, up to 4 channels are allowed, and volumes are not
21722173
/// permitted (z coordinate may only be 0). File formats that can
21732174
/// handle larger resolutions, or volumes, or >4 channels must
21742175
/// override these limits!

src/libOpenImageIO/imageoutput.cpp

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,24 +1124,6 @@ ImageOutput::check_open(OpenMode mode, const ImageSpec& userspec, ROI range,
11241124
m_spec.z = 0;
11251125
}
11261126
}
1127-
if (m_spec.x < range.xbegin || m_spec.x + m_spec.width > range.xend
1128-
|| m_spec.y < range.ybegin || m_spec.y + m_spec.height > range.yend
1129-
|| m_spec.z < range.zbegin
1130-
|| m_spec.z + m_spec.depth > range.zend) {
1131-
if (m_spec.depth == 1)
1132-
errorfmt(
1133-
"{} requested pixel data window [{}, {}) x [{}, {}) exceeds the allowable range of [{}, {}) x [{}, {})",
1134-
format_name(), m_spec.x, m_spec.x + m_spec.width, m_spec.y,
1135-
m_spec.y + m_spec.height, range.xbegin, range.xend,
1136-
range.ybegin, range.yend);
1137-
else
1138-
errorfmt(
1139-
"{} requested pixel data window [{}, {}) x [{}, {}) x [{}, {}) exceeds the allowable range of [{}, {}) x [{}, {}) x [{}, {})\n{} vs {}\n",
1140-
format_name(), m_spec.x, m_spec.x + m_spec.width, m_spec.y,
1141-
m_spec.y + m_spec.height, m_spec.z, m_spec.z + m_spec.depth,
1142-
range.xbegin, range.xend, range.ybegin, range.yend,
1143-
range.zbegin, range.zend);
1144-
}
11451127
}
11461128

11471129
if (m_spec.extra_attribs.contains("ioproxy") && !supports("ioproxy")) {

src/openexr.imageio/exrinput.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,8 +1359,8 @@ OpenEXRInput::read_native_tiles(int subimage, int miplevel, int xbegin,
13591359
tmpbuf.resize(nxtiles * nytiles * m_spec.tile_bytes(true));
13601360
data = tmpbuf.data();
13611361
}
1362-
char* buf = (char*)data - xbegin * pixelbytes
1363-
- ybegin * pixelbytes * m_spec.tile_width * nxtiles;
1362+
char* buf = (char*)data - ptrdiff_t(xbegin * pixelbytes)
1363+
- ptrdiff_t(ybegin * pixelbytes * m_spec.tile_width * nxtiles);
13641364

13651365
try {
13661366
Imf::FrameBuffer frameBuffer;

src/openexr.imageio/exroutput.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,8 @@ OpenEXROutput::open(const std::string& name, const ImageSpec& userspec,
375375
m_nmiplevels = 1;
376376
m_miplevel = 0;
377377
m_headers.resize(1);
378-
copy_and_check_spec(userspec, m_spec);
378+
if (!copy_and_check_spec(userspec, m_spec))
379+
return false;
379380
sanity_check_channelnames();
380381
const ParamValue* param = m_spec.find_attribute("oiio:ioproxy",
381382
TypeDesc::PTR);
@@ -1321,7 +1322,7 @@ OpenEXROutput::sanity_check_channelnames()
13211322
if (m_spec.channelnames[c].empty()
13221323
|| m_spec.channelnames[c] == m_spec.channelnames[i]) {
13231324
// Duplicate or missing channel name! We don't want
1324-
// libIlmImf to drop the channel (as it will do for
1325+
// libOpenEXR to drop the channel (as it will do for
13251326
// duplicates), so rename it and hope for the best.
13261327
m_spec.channelnames[c] = Strutil::fmt::format("channel{}", c);
13271328
break;
@@ -1384,7 +1385,8 @@ OpenEXROutput::write_scanline(int y, int z, TypeDesc format, const void* data,
13841385
// where the address of the "virtual framebuffer" for the whole
13851386
// image.
13861387
imagesize_t scanlinebytes = m_spec.scanline_bytes(native);
1387-
char* buf = (char*)data - m_spec.x * pixel_bytes - y * scanlinebytes;
1388+
char* buf = (char*)data - ptrdiff_t(m_spec.x * pixel_bytes)
1389+
- ptrdiff_t(y * scanlinebytes);
13881390
13891391
try {
13901392
Imf::FrameBuffer frameBuffer;
@@ -1647,7 +1649,8 @@ OpenEXROutput::write_tiles(int xbegin, int xend, int ybegin, int yend,
16471649
data = &padded[0];
16481650
}
16491651

1650-
char* buf = (char*)data - xbegin * pixelbytes - ybegin * widthbytes;
1652+
char* buf = (char*)data - ptrdiff_t(xbegin * pixelbytes)
1653+
- ptrdiff_t(ybegin * widthbytes);
16511654

16521655
try {
16531656
Imf::FrameBuffer frameBuffer;

testsuite/openexr-suite/ref/out.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,3 +347,17 @@ Reading ../openexr-images/Beachball/singlepart.0001.exr
347347
openexr:lineOrder: "increasingY"
348348
Comparing "../openexr-images/Beachball/singlepart.0001.exr" and "singlepart.0001.exr"
349349
PASS
350+
Reading negoverscan.exr
351+
negoverscan.exr : 64 x 64, 3 channel, half openexr
352+
SHA-1: EBDD38B69CD5B9F2D00D273C981E16960FBBB4F7
353+
channel list: R, G, B
354+
pixel data origin: x=-16, y=-16
355+
full/display size: 64 x 64
356+
full/display origin: -16, -16
357+
compression: "zip"
358+
PixelAspectRatio: 1
359+
screenWindowCenter: 0, 0
360+
screenWindowWidth: 1
361+
oiio:ColorSpace: "lin_rec709"
362+
oiio:subimages: 1
363+
openexr:lineOrder: "increasingY"

testsuite/openexr-suite/run.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
# SPDX-License-Identifier: Apache-2.0
55
# https://github.com/AcademySoftwareFoundation/OpenImageIO
66

7+
redirect = " >> out.txt 2>&1"
8+
79
# ../openexr-images/ScanLines:
810
# Blobbies.exr Cannon.exr MtTamWest.exr StillLife.exr
911
# CandleGlass.exr Desk.exr PrismsLenses.exr Tree.exr
@@ -44,3 +46,7 @@
4446
files = [ "singlepart.0001.exr" ]
4547
for f in files:
4648
command += rw_command (imagedir, f)
49+
50+
# Check writing overscan and negative range
51+
command += oiiotool("--create 64x64-16-16 3 -d half -o negoverscan.exr")
52+
command += info_command("negoverscan.exr", safematch=True)

0 commit comments

Comments
 (0)