Skip to content

Commit 488dfb2

Browse files
authored
api!: add image_span versions of ImageInput methods (#4748)
ImageInput methods that read scanlines, tiles, and image are given new flavors that take image_span, much like the changes of PR #4727 did for ImageOutput. Generally, for each, there are three versions: (1) a low-level virtual method base case that takes an `image_span<byte>` that describes the underlying memory, and an explicit TypeDesc that specifies the data type conversion (including allowing TypeUnknown to indicate keeping channels in their native per-channel format from the file); (2) a templated version taking an `image_span<T>` that infers the data type conversion from `T` (must be a single type for all channels); (3) a templated version taking a `span<T>` that further implies a contiguous buffer in all dimensions. For now, the default implementations of these new ImageInput methods are just wrappers that call the old pointer-based ones. One by one, over time, we can swap them, changing the format implementations to have a full implementation of the new bounded versions, and make their raw pointer versions call the wrappers. The raw pointer ones will be understood to be "unsafe", merely assuming that the pointers always refer to appropriately-sized memory areas. Meanwhile, the ones using spans and image_spans will, due to assertions in their implementations, make it easier to verify (at least in debug mode), that we never touch memory outside these bounds. --------- Signed-off-by: Larry Gritz <[email protected]>
1 parent d694b60 commit 488dfb2

File tree

15 files changed

+631
-98
lines changed

15 files changed

+631
-98
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
cmake_minimum_required (VERSION 3.18.2...4.0)
66

7-
set (OpenImageIO_VERSION "3.1.2.0")
7+
set (OpenImageIO_VERSION "3.1.3.0")
88
set (OpenImageIO_VERSION_OVERRIDE "" CACHE STRING
99
"Version override (use with caution)!")
1010
mark_as_advanced (OpenImageIO_VERSION_OVERRIDE)

src/doc/imageoutput.rst

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -246,26 +246,20 @@ individual plugin.
246246

247247
.. tabs::
248248

249-
.. code-tab:: c++
250-
251-
unsigned char tile[tilesize*tilesize*channels];
252-
int z = 0; // Always zero for 2D images
253-
for (int y = 0; y < yres; y += tilesize) {
254-
for (int x = 0; x < xres; x += tilesize) {
255-
... generate data in tile[] ..
256-
out->write_tile (x, y, z, TypeDesc::UINT8, tile);
257-
}
258-
}
259-
out->close ();
249+
.. tab:: C++
250+
.. literalinclude:: ../../testsuite/docs-examples-cpp/src/docs-examples-imageoutput.cpp
251+
:language: c++
252+
:start-after: BEGIN-imageoutput-tiles
253+
:end-before: END-imageoutput-tiles
254+
:dedent: 4
260255

261-
.. code-tab:: py
256+
.. tab:: Python
262257

263-
z = 0 # Always zero for 2D images
264-
for y in range(0, yres, tilesize) :
265-
for x in range(0, xres, tilesize) :
266-
# ... generate data in tile[][][] ..
267-
out.write_tile (x, y, z, tile)
268-
out.close ()
258+
.. literalinclude:: ../../testsuite/docs-examples-python/src/docs-examples-imageoutput.py
259+
:language: py
260+
:start-after: BEGIN-imageoutput-tiles
261+
:end-before: END-imageoutput-tiles
262+
:dedent: 8
269263

270264
The first three arguments to ``write_tile()`` specify which tile is being
271265
written by the pixel coordinates of any pixel contained in the tile: *x*

src/include/OpenImageIO/imageio.h

Lines changed: 342 additions & 61 deletions
Large diffs are not rendered by default.

src/libOpenImageIO/imageinput.cpp

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,37 @@ ImageInput::read_scanline(int y, int z, TypeDesc format, void* data,
286286

287287

288288

289+
bool
290+
ImageInput::read_scanlines(int subimage, int miplevel, int ybegin, int yend,
291+
int chbegin, int chend, TypeDesc format,
292+
const image_span<std::byte>& data)
293+
{
294+
ImageSpec spec = spec_dimensions(subimage, miplevel);
295+
if (chend < 0 || chend > spec.nchannels)
296+
chend = spec.nchannels;
297+
if (chbegin < 0 || chbegin >= chend) {
298+
errorfmt("read_scanlines: invalid channel range [{},{})", chbegin,
299+
chend);
300+
return false;
301+
}
302+
size_t isize = (format == TypeUnknown
303+
? spec.pixel_bytes(chbegin, chend, true /*native*/)
304+
: format.size() * (chend - chbegin))
305+
* size_t(spec.width);
306+
if (isize != data.size_bytes()) {
307+
errorfmt(
308+
"read_scanlines: Buffer size is incorrect ({} bytes vs {} needed)",
309+
isize, data.size_bytes());
310+
return false;
311+
}
312+
313+
// Default implementation (for now): call the old pointer+stride
314+
return read_scanlines(subimage, miplevel, ybegin, yend, 0, chbegin, chend,
315+
format, data.data(), data.xstride());
316+
}
317+
318+
319+
289320
bool
290321
ImageInput::read_scanlines(int subimage, int miplevel, int ybegin, int yend,
291322
int z, int chbegin, int chend, TypeDesc format,
@@ -611,6 +642,38 @@ ImageInput::read_tile(int x, int y, int z, TypeDesc format, void* data,
611642

612643

613644

645+
bool
646+
ImageInput::read_tiles(int subimage, int miplevel, int xbegin, int xend,
647+
int ybegin, int yend, int zbegin, int zend, int chbegin,
648+
int chend, TypeDesc format,
649+
const image_span<std::byte>& data)
650+
{
651+
ImageSpec spec = spec_dimensions(subimage, miplevel);
652+
if (chend < 0 || chend > spec.nchannels)
653+
chend = spec.nchannels;
654+
if (chbegin < 0 || chbegin >= chend) {
655+
errorfmt("read_tiles: invalid channel range [{},{})", chbegin, chend);
656+
return false;
657+
}
658+
size_t isize = (format == TypeUnknown
659+
? spec.pixel_bytes(chbegin, chend, true /*native*/)
660+
: format.size() * (chend - chbegin))
661+
* size_t(xend - xbegin) * size_t(yend - ybegin)
662+
* size_t(zend - zbegin);
663+
if (isize != data.size_bytes()) {
664+
errorfmt("read_tiles: Buffer size is incorrect ({} bytes vs {} needed)",
665+
isize, data.size_bytes());
666+
return false;
667+
}
668+
669+
// Default implementation (for now): call the old pointer+stride
670+
return read_tiles(subimage, miplevel, ybegin, yend, xbegin, xend, zbegin,
671+
zend, chbegin, chend, format, data.data(),
672+
data.xstride());
673+
}
674+
675+
676+
614677
bool
615678
ImageInput::read_tiles(int subimage, int miplevel, int xbegin, int xend,
616679
int ybegin, int yend, int zbegin, int zend, int chbegin,
@@ -1096,6 +1159,110 @@ ImageInput::read_image(int subimage, int miplevel, int chbegin, int chend,
10961159

10971160

10981161

1162+
bool
1163+
ImageInput::read_image(int subimage, int miplevel, int chbegin, int chend,
1164+
TypeDesc format, const image_span<std::byte>& data)
1165+
{
1166+
#if 0
1167+
ImageSpec spec = spec_dimensions(subimage, miplevel);
1168+
if (chend < 0 || chend > spec.nchannels)
1169+
chend = spec.nchannels;
1170+
size_t isize = (format == TypeUnknown
1171+
? spec.pixel_bytes(chbegin, chend, true /*native*/)
1172+
: format.size() * (chend - chbegin))
1173+
* spec.image_pixels();
1174+
if (isize != data.size_bytes()) {
1175+
errorfmt("read_image: Buffer size is incorrect ({} bytes vs {} needed)",
1176+
sz, data.size_bytes());
1177+
return false;
1178+
}
1179+
1180+
// Default implementation (for now): call the old pointer+stride
1181+
return read_image(subimage, miplevel, chbegin, chend, format, data.data(),
1182+
data.xstride(), data.ystride(), data.zstride());
1183+
#else
1184+
pvt::LoggedTimer logtime("II::read_image");
1185+
ImageSpec spec;
1186+
int rps = 0;
1187+
{
1188+
// We need to lock briefly to retrieve rps from the spec
1189+
lock_guard lock(*this);
1190+
if (!seek_subimage(subimage, miplevel))
1191+
return false;
1192+
// Copying the dimensions of the designated subimage/miplevel to a
1193+
// local `spec` means that we can release the lock! (Calls to
1194+
// read_native_* will internally lock again if necessary.)
1195+
spec.copy_dimensions(m_spec);
1196+
// For scanline files, we also need one piece of metadata
1197+
if (!spec.tile_width)
1198+
rps = m_spec.get_int_attribute("tiff:RowsPerStrip", 64);
1199+
}
1200+
if (spec.image_bytes() < 1) {
1201+
errorfmt("Invalid image size {} x {} ({} chans)", m_spec.width,
1202+
m_spec.height, m_spec.nchannels);
1203+
return false;
1204+
}
1205+
1206+
if (chend < 0 || chend > spec.nchannels)
1207+
chend = spec.nchannels;
1208+
if (chbegin < 0 || chbegin >= chend) {
1209+
errorfmt("read_image: invalid channel range [{},{})", chbegin, chend);
1210+
return false;
1211+
}
1212+
int nchans = chend - chbegin;
1213+
bool native = (format == TypeUnknown);
1214+
size_t pixel_bytes = native ? spec.pixel_bytes(chbegin, chend, native)
1215+
: (format.size() * nchans);
1216+
size_t isize = pixel_bytes * spec.image_pixels();
1217+
if (isize != data.size_bytes()) {
1218+
errorfmt("read_image: Buffer size is incorrect ({} bytes vs {} needed)",
1219+
isize, data.size_bytes());
1220+
return false;
1221+
}
1222+
1223+
bool ok = true;
1224+
if (spec.tile_width) { // Tiled image -- rely on read_tiles
1225+
// Read in chunks of a whole row of tiles at once. If tiles are
1226+
// 64x64, a 2k image has 32 tiles across. That's fine for now (for
1227+
// parallelization purposes), but as typical core counts increase,
1228+
// we may someday want to revisit this to batch multiple rows.
1229+
for (int z = 0; z < spec.depth; z += spec.tile_depth) {
1230+
int zend = std::min(z + spec.z + spec.tile_depth,
1231+
spec.z + spec.depth);
1232+
for (int y = 0; y < spec.height && ok; y += spec.tile_height) {
1233+
int yend = std::min(y + spec.y + spec.tile_height,
1234+
spec.y + spec.height);
1235+
ok &= read_tiles(subimage, miplevel, spec.x,
1236+
spec.x + spec.width, y + spec.y, yend,
1237+
z + spec.z, zend, chbegin, chend, format,
1238+
data.subspan(spec.x, spec.x + spec.width,
1239+
y + spec.y, yend, z + spec.z,
1240+
zend));
1241+
}
1242+
}
1243+
} else { // Scanline image -- rely on read_scanlines.
1244+
// Split into reasonable chunks -- try to use around 64 MB or the
1245+
// oiio_read_chunk value, which ever is bigger, but also round up to
1246+
// a multiple of the TIFF rows per strip (or 64).
1247+
int chunk = std::max(1, (1 << 26) / int(spec.scanline_bytes(true)));
1248+
chunk = std::max(chunk, int(oiio_read_chunk));
1249+
chunk = round_to_multiple(chunk, rps);
1250+
for (int z = 0; z < spec.depth; ++z) {
1251+
for (int y = 0; y < spec.height && ok; y += chunk) {
1252+
int yend = std::min(y + spec.y + chunk, spec.y + spec.height);
1253+
ok &= read_scanlines(subimage, miplevel, y + spec.y, yend,
1254+
chbegin, chend, format,
1255+
data.subspan(spec.x, spec.x + spec.width,
1256+
y + spec.y, yend));
1257+
}
1258+
}
1259+
}
1260+
return ok;
1261+
#endif
1262+
}
1263+
1264+
1265+
10991266
bool
11001267
ImageInput::read_native_deep_scanlines(int /*subimage*/, int /*miplevel*/,
11011268
int /*ybegin*/, int /*yend*/, int /*z*/,

testsuite/docs-examples-cpp/ref/out-arm.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,5 @@ Comparing "simple.tif" and "ref/simple.tif"
144144
PASS
145145
Comparing "scanlines.tif" and "ref/scanlines.tif"
146146
PASS
147+
Comparing "tiles.tif" and "ref/tiles.tif"
148+
PASS

testsuite/docs-examples-cpp/ref/out.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,5 @@ Comparing "simple.tif" and "ref/simple.tif"
144144
PASS
145145
Comparing "scanlines.tif" and "ref/scanlines.tif"
146146
PASS
147+
Comparing "tiles.tif" and "ref/tiles.tif"
148+
PASS
1.06 KB
Binary file not shown.

testsuite/docs-examples-cpp/run.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
# and need the images checked into the ref directory.
101101
outputs = [
102102
# Outputs from the ImageOutput chapter:
103-
"simple.tif", "scanlines.tif",
103+
"simple.tif", "scanlines.tif", "tiles.tif",
104104
# Outputs from the ImageInput chapter:
105105

106106
# Outputs from the ImageBuf chapter:

testsuite/docs-examples-cpp/src/docs-examples-imageinput.cpp

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,14 @@ simple_read()
4949
int xres = spec.width;
5050
int yres = spec.height;
5151
int nchannels = spec.nchannels;
52-
auto pixels = std::unique_ptr<unsigned char[]>(
53-
new unsigned char[xres * yres * nchannels]);
54-
inp->read_image(0, 0, 0, nchannels, TypeDesc::UINT8, &pixels[0]);
52+
std::vector<uint8_t> pixels(xres * yres * nchannels);
53+
inp->read_image(0 /*subimage*/, 0 /*miplevel*/, 0 /*chbegin*/,
54+
nchannels /*chend*/, make_span(pixels));
5555
inp->close();
5656
}
5757
// END-imageinput-simple
5858

59+
5960
void
6061
scanlines_read()
6162
{
@@ -65,10 +66,11 @@ scanlines_read()
6566
auto inp = ImageInput::open(filename);
6667
const ImageSpec& spec = inp->spec();
6768
if (spec.tile_width == 0) {
68-
auto scanline = std::unique_ptr<unsigned char[]>(
69-
new unsigned char[spec.width * spec.nchannels]);
70-
for (int y = 0; y < spec.height; ++y) {
71-
inp->read_scanline(y, 0, TypeDesc::UINT8, &scanline[0]);
69+
std::vector<uint8_t> scanline(spec.width * spec.nchannels);
70+
for (int y = spec.y; y < spec.y + spec.height; ++y) {
71+
inp->read_scanlines(0 /*subimage*/, 0 /*miplevel*/, y, y + 1,
72+
0 /*chbegin*/, spec.nchannels /*chend*/,
73+
make_span(scanline));
7274
// ... process data in scanline[0..width*channels-1] ...
7375
}
7476
} else {
@@ -93,19 +95,31 @@ tiles_read()
9395
} else {
9496
// Tiles
9597
int tilesize = spec.tile_width * spec.tile_height;
96-
auto tile = std::unique_ptr<unsigned char[]>(
97-
new unsigned char[tilesize * spec.nchannels]);
98-
for (int y = 0; y < spec.height; y += spec.tile_height) {
99-
for (int x = 0; x < spec.width; x += spec.tile_width) {
100-
inp->read_tile(x, y, 0, TypeDesc::UINT8, &tile[0]);
98+
std::vector<uint8_t> tile(tilesize * spec.nchannels);
99+
for (int y = spec.y; y < spec.y + spec.height; y += spec.tile_height) {
100+
for (int x = spec.x; x < spec.x + spec.width;
101+
x += spec.tile_width) {
102+
inp->read_tiles(0 /*subimage*/, 0 /*miplevel*/, x,
103+
std::min(x + spec.tile_width, spec.width), y,
104+
std::min(y + spec.tile_height, spec.height), 0,
105+
1, 0 /*chbegin*/, spec.nchannels /*chend*/,
106+
make_span(tile));
101107
// ... process the pixels in tile[] ..
108+
// Watch out for "edge tiles" that are smaller than the full
109+
// tile size.
110+
// For example, if the image is 100x100 and the tile size is
111+
// 32x32, the last tile in each row will be 4x32, the bottom
112+
// row of tiles will be 32x4, and the very last
113+
// tile of the whole images will be 4x4.
102114
}
103115
}
104116
}
105117
inp->close();
106118
// END-imageinput-tiles
107119
}
108120

121+
122+
109123
void
110124
unassociatedalpha()
111125
{

testsuite/docs-examples-cpp/src/docs-examples-imageoutput.cpp

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ scanlines_write()
6565
std::unique_ptr<ImageOutput> out = ImageOutput::create(filename);
6666
if (!out)
6767
return; // error
68-
ImageSpec spec(xres, yres, channels, TypeDesc::UINT8);
6968

7069
// BEGIN-imageoutput-scanlines
70+
ImageSpec spec(xres, yres, channels, TypeDesc::UINT8);
7171
std::vector<unsigned char> scanline(xres * channels);
7272
out->open(filename, spec);
7373
for (int y = 0; y < yres; ++y) {
@@ -80,10 +80,56 @@ scanlines_write()
8080

8181

8282

83+
void
84+
tiles_write()
85+
{
86+
const char* filename = "tiles.tif";
87+
const int xres = 320, yres = 240, channels = 3;
88+
const int tilesize = 64;
89+
90+
// BEGIN-imageoutput-tiles-create
91+
std::unique_ptr<ImageOutput> out = ImageOutput::create(filename);
92+
if (!out)
93+
return; // error: could not create output at all
94+
if (!out->supports("tiles")) {
95+
// Tiles are not supported
96+
}
97+
// END-imageoutput-tiles-create
98+
99+
// BEGIN-imageoutput-tiles-make-spec-open
100+
ImageSpec spec(xres, yres, channels, TypeDesc::UINT8);
101+
spec.tile_width = tilesize;
102+
spec.tile_height = tilesize;
103+
out->open(filename, spec);
104+
// END-imageoutput-tiles-make-spec-open
105+
106+
// BEGIN-imageoutput-tiles
107+
std::vector<uint8_t> tile(tilesize * tilesize * spec.nchannels);
108+
for (int y = 0; y < yres; y += tilesize) {
109+
for (int x = 0; x < xres; x += tilesize) {
110+
out->write_tiles(x, std::min(x + spec.tile_width, spec.width), y,
111+
std::min(y + spec.tile_height, spec.height), 0, 1,
112+
make_span(tile));
113+
// ... process the pixels in tile[] ..
114+
// Watch out for "edge tiles" that are smaller than the full
115+
// tile size.
116+
// For example, if the image is 100x100 and the tile size is
117+
// 32x32, the last tile in each row will be 4x32, the bottom
118+
// row of tiles will be 32x4, and the very last
119+
// tile of the whole images will be 4x4.
120+
}
121+
}
122+
out->close();
123+
// END-imageoutput-tiles
124+
}
125+
126+
127+
83128
int
84129
main(int /*argc*/, char** /*argv*/)
85130
{
86131
simple_write();
87132
scanlines_write();
133+
tiles_write();
88134
return 0;
89135
}

0 commit comments

Comments
 (0)