Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

cmake_minimum_required (VERSION 3.18.2...4.0)

set (OpenImageIO_VERSION "3.1.2.0")
set (OpenImageIO_VERSION "3.1.3.0")
set (OpenImageIO_VERSION_OVERRIDE "" CACHE STRING
"Version override (use with caution)!")
mark_as_advanced (OpenImageIO_VERSION_OVERRIDE)
Expand Down
30 changes: 12 additions & 18 deletions src/doc/imageoutput.rst
Original file line number Diff line number Diff line change
Expand Up @@ -246,26 +246,20 @@ individual plugin.

.. tabs::

.. code-tab:: c++

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

.. code-tab:: py
.. tab:: Python

z = 0 # Always zero for 2D images
for y in range(0, yres, tilesize) :
for x in range(0, xres, tilesize) :
# ... generate data in tile[][][] ..
out.write_tile (x, y, z, tile)
out.close ()
.. literalinclude:: ../../testsuite/docs-examples-python/src/docs-examples-imageoutput.py
:language: py
:start-after: BEGIN-imageoutput-tiles
:end-before: END-imageoutput-tiles
:dedent: 8

The first three arguments to ``write_tile()`` specify which tile is being
written by the pixel coordinates of any pixel contained in the tile: *x*
Expand Down
403 changes: 342 additions & 61 deletions src/include/OpenImageIO/imageio.h

Large diffs are not rendered by default.

168 changes: 168 additions & 0 deletions src/libOpenImageIO/imageinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,37 @@ ImageInput::read_scanline(int y, int z, TypeDesc format, void* data,



bool
ImageInput::read_scanlines(int subimage, int miplevel, int ybegin, int yend,
int chbegin, int chend, TypeDesc format,
const image_span<std::byte>& data)
{
ImageSpec spec = spec_dimensions(subimage, miplevel);
if (chend < 0 || chend > spec.nchannels)
chend = spec.nchannels;
if (chbegin < 0 || chbegin >= chend) {
errorfmt("read_scanlines: invalid channel range [{},{})", chbegin,
chend);
return false;
}
size_t isize = (format == TypeUnknown
? spec.pixel_bytes(chbegin, chend, true /*native*/)
: format.size() * (chend - chbegin))
* size_t(spec.width);
if (isize != data.size_bytes()) {
errorfmt(
"read_scanlines: Buffer size is incorrect ({} bytes vs {} needed)",
isize, data.size_bytes());
return false;
}

// Default implementation (for now): call the old pointer+stride
return read_scanlines(subimage, miplevel, ybegin, yend, 0, chbegin, chend,
format, data.data(), data.xstride());
}



bool
ImageInput::read_scanlines(int subimage, int miplevel, int ybegin, int yend,
int z, int chbegin, int chend, TypeDesc format,
Expand Down Expand Up @@ -610,6 +641,38 @@ ImageInput::read_tile(int x, int y, int z, TypeDesc format, void* data,



bool
ImageInput::read_tiles(int subimage, int miplevel, int xbegin, int xend,
int ybegin, int yend, int zbegin, int zend, int chbegin,
int chend, TypeDesc format,
const image_span<std::byte>& data)
{
ImageSpec spec = spec_dimensions(subimage, miplevel);
if (chend < 0 || chend > spec.nchannels)
chend = spec.nchannels;
if (chbegin < 0 || chbegin >= chend) {
errorfmt("read_tiles: invalid channel range [{},{})", chbegin, chend);
return false;
}
size_t isize = (format == TypeUnknown
? spec.pixel_bytes(chbegin, chend, true /*native*/)
: format.size() * (chend - chbegin))
* size_t(xend - xbegin) * size_t(yend - ybegin)
* size_t(zend - zbegin);
if (isize != data.size_bytes()) {
errorfmt("read_tiles: Buffer size is incorrect ({} bytes vs {} needed)",
isize, data.size_bytes());
return false;
}

// Default implementation (for now): call the old pointer+stride
return read_tiles(subimage, miplevel, ybegin, yend, xbegin, xend, zbegin,
zend, chbegin, chend, format, data.data(),
data.xstride());
}



bool
ImageInput::read_tiles(int subimage, int miplevel, int xbegin, int xend,
int ybegin, int yend, int zbegin, int zend, int chbegin,
Expand Down Expand Up @@ -1095,6 +1158,111 @@ ImageInput::read_image(int subimage, int miplevel, int chbegin, int chend,



bool
ImageInput::read_image(int subimage, int miplevel, int chbegin, int chend,
TypeDesc format, const image_span<std::byte>& data)
{
#if 0
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to leave this #if 0 in?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the new methods I added are (currently) just having the new API call the old one underneah. This is the one where I did a full implementation to make sure it was working as intended, and I wanted (for now) to have a means of switching back and forth between new and old for debugging purposes.

ImageSpec spec = spec_dimensions(subimage, miplevel);
if (chend < 0 || chend > spec.nchannels)
chend = spec.nchannels;
size_t isize = (format == TypeUnknown
? spec.pixel_bytes(chbegin, chend, true /*native*/)
: format.size() * (chend - chbegin))
* spec.image_pixels();
if (isize != data.size_bytes()) {
errorfmt("read_image: Buffer size is incorrect ({} bytes vs {} needed)",
sz, data.size_bytes());
return false;
}

// Default implementation (for now): call the old pointer+stride
return read_image(subimage, miplevel, chbegin, chend, format, data.data(),
data.xstride(), data.ystride(), data.zstride());
#else
pvt::LoggedTimer logtime("II::read_image");
ImageSpec spec;
int rps = 0;
{
// We need to lock briefly to retrieve rps from the spec
lock_guard lock(*this);
if (!seek_subimage(subimage, miplevel))
return false;
// Copying the dimensions of the designated subimage/miplevel to a
// local `spec` means that we can release the lock! (Calls to
// read_native_* will internally lock again if necessary.)
spec.copy_dimensions(m_spec);
// For scanline files, we also need one piece of metadata
if (!spec.tile_width)
rps = m_spec.get_int_attribute("tiff:RowsPerStrip", 64);
}
if (spec.image_bytes() < 1) {
errorfmt("Invalid image size {} x {} ({} chans)", m_spec.width,
m_spec.height, m_spec.nchannels);
return false;
}

if (chend < 0 || chend > spec.nchannels)
chend = spec.nchannels;
if (chbegin < 0 || chbegin >= chend) {
errorfmt("read_image: invalid channel range [{},{})", chbegin,
chend);
return false;
}
int nchans = chend - chbegin;
bool native = (format == TypeUnknown);
size_t pixel_bytes = native ? spec.pixel_bytes(chbegin, chend, native)
: (format.size() * nchans);
size_t isize = pixel_bytes * spec.image_pixels();
if (isize != data.size_bytes()) {
errorfmt("read_image: Buffer size is incorrect ({} bytes vs {} needed)",
isize, data.size_bytes());
return false;
}

bool ok = true;
if (spec.tile_width) { // Tiled image -- rely on read_tiles
// Read in chunks of a whole row of tiles at once. If tiles are
// 64x64, a 2k image has 32 tiles across. That's fine for now (for
// parallelization purposes), but as typical core counts increase,
// we may someday want to revisit this to batch multiple rows.
for (int z = 0; z < spec.depth; z += spec.tile_depth) {
int zend = std::min(z + spec.z + spec.tile_depth,
spec.z + spec.depth);
for (int y = 0; y < spec.height && ok; y += spec.tile_height) {
int yend = std::min(y + spec.y + spec.tile_height,
spec.y + spec.height);
ok &= read_tiles(subimage, miplevel, spec.x,
spec.x + spec.width, y + spec.y, yend,
z + spec.z, zend, chbegin, chend, format,
data.subspan(spec.x, spec.x + spec.width,
y + spec.y, yend, z + spec.z,
zend));
}
}
} else { // Scanline image -- rely on read_scanlines.
// Split into reasonable chunks -- try to use around 64 MB or the
// oiio_read_chunk value, which ever is bigger, but also round up to
// a multiple of the TIFF rows per strip (or 64).
int chunk = std::max(1, (1 << 26) / int(spec.scanline_bytes(true)));
chunk = std::max(chunk, int(oiio_read_chunk));
chunk = round_to_multiple(chunk, rps);
for (int z = 0; z < spec.depth; ++z) {
for (int y = 0; y < spec.height && ok; y += chunk) {
int yend = std::min(y + spec.y + chunk, spec.y + spec.height);
ok &= read_scanlines(subimage, miplevel, y + spec.y, yend,
chbegin, chend, format,
data.subspan(spec.x, spec.x + spec.width,
y + spec.y, yend));
}
}
}
return ok;
#endif
}



bool
ImageInput::read_native_deep_scanlines(int /*subimage*/, int /*miplevel*/,
int /*ybegin*/, int /*yend*/, int /*z*/,
Expand Down
2 changes: 2 additions & 0 deletions testsuite/docs-examples-cpp/ref/out-arm.txt
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,5 @@ Comparing "simple.tif" and "ref/simple.tif"
PASS
Comparing "scanlines.tif" and "ref/scanlines.tif"
PASS
Comparing "tiles.tif" and "ref/tiles.tif"
PASS
2 changes: 2 additions & 0 deletions testsuite/docs-examples-cpp/ref/out.txt
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,5 @@ Comparing "simple.tif" and "ref/simple.tif"
PASS
Comparing "scanlines.tif" and "ref/scanlines.tif"
PASS
Comparing "tiles.tif" and "ref/tiles.tif"
PASS
Binary file added testsuite/docs-examples-cpp/ref/tiles.tif
Binary file not shown.
2 changes: 1 addition & 1 deletion testsuite/docs-examples-cpp/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
# and need the images checked into the ref directory.
outputs = [
# Outputs from the ImageOutput chapter:
"simple.tif", "scanlines.tif",
"simple.tif", "scanlines.tif", "tiles.tif",
# Outputs from the ImageInput chapter:

# Outputs from the ImageBuf chapter:
Expand Down
38 changes: 26 additions & 12 deletions testsuite/docs-examples-cpp/src/docs-examples-imageinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,14 @@ simple_read()
int xres = spec.width;
int yres = spec.height;
int nchannels = spec.nchannels;
auto pixels = std::unique_ptr<unsigned char[]>(
new unsigned char[xres * yres * nchannels]);
inp->read_image(0, 0, 0, nchannels, TypeDesc::UINT8, &pixels[0]);
std::vector<uint8_t> pixels(xres * yres * nchannels);
inp->read_image(0 /*subimage*/, 0 /*miplevel*/, 0 /*chbegin*/,
nchannels /*chend*/, make_span(pixels));
inp->close();
}
// END-imageinput-simple


void
scanlines_read()
{
Expand All @@ -65,10 +66,11 @@ scanlines_read()
auto inp = ImageInput::open(filename);
const ImageSpec& spec = inp->spec();
if (spec.tile_width == 0) {
auto scanline = std::unique_ptr<unsigned char[]>(
new unsigned char[spec.width * spec.nchannels]);
for (int y = 0; y < spec.height; ++y) {
inp->read_scanline(y, 0, TypeDesc::UINT8, &scanline[0]);
std::vector<uint8_t> scanline(spec.width * spec.nchannels);
for (int y = spec.y; y < spec.y + spec.height; ++y) {
inp->read_scanlines(0 /*subimage*/, 0 /*miplevel*/, y, y + 1,
0 /*chbegin*/, spec.nchannels /*chend*/,
make_span(scanline));
// ... process data in scanline[0..width*channels-1] ...
}
} else {
Expand All @@ -93,19 +95,31 @@ tiles_read()
} else {
// Tiles
int tilesize = spec.tile_width * spec.tile_height;
auto tile = std::unique_ptr<unsigned char[]>(
new unsigned char[tilesize * spec.nchannels]);
for (int y = 0; y < spec.height; y += spec.tile_height) {
for (int x = 0; x < spec.width; x += spec.tile_width) {
inp->read_tile(x, y, 0, TypeDesc::UINT8, &tile[0]);
std::vector<uint8_t> tile(tilesize * spec.nchannels);
for (int y = spec.y; y < spec.y + spec.height; y += spec.tile_height) {
for (int x = spec.x; x < spec.x + spec.width;
x += spec.tile_width) {
inp->read_tiles(0 /*subimage*/, 0 /*miplevel*/, x,
std::min(x + spec.tile_width, spec.width), y,
std::min(y + spec.tile_height, spec.height), 0,
1, 0 /*chbegin*/, spec.nchannels /*chend*/,
make_span(tile));
// ... process the pixels in tile[] ..
// Watch out for "edge tiles" that are smaller than the full
// tile size.
// For example, if the image is 100x100 and the tile size is
// 32x32, the last tile in each row will be 4x32, the bottom
// row of tiles will be 32x4, and the very last
// tile of the whole images will be 4x4.
}
}
}
inp->close();
// END-imageinput-tiles
}



void
unassociatedalpha()
{
Expand Down
48 changes: 47 additions & 1 deletion testsuite/docs-examples-cpp/src/docs-examples-imageoutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ scanlines_write()
std::unique_ptr<ImageOutput> out = ImageOutput::create(filename);
if (!out)
return; // error
ImageSpec spec(xres, yres, channels, TypeDesc::UINT8);

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



void
tiles_write()
{
const char* filename = "tiles.tif";
const int xres = 320, yres = 240, channels = 3;
const int tilesize = 64;

// BEGIN-imageoutput-tiles-create
std::unique_ptr<ImageOutput> out = ImageOutput::create(filename);
if (!out)
return; // error: could not create output at all
if (!out->supports("tiles")) {
// Tiles are not supported
}
// END-imageoutput-tiles-create

// BEGIN-imageoutput-tiles-make-spec-open
ImageSpec spec(xres, yres, channels, TypeDesc::UINT8);
spec.tile_width = tilesize;
spec.tile_height = tilesize;
out->open(filename, spec);
// END-imageoutput-tiles-make-spec-open

// BEGIN-imageoutput-tiles
std::vector<uint8_t> tile(tilesize * tilesize * spec.nchannels);
for (int y = 0; y < yres; y += tilesize) {
for (int x = 0; x < xres; x += tilesize) {
out->write_tiles(x, std::min(x + spec.tile_width, spec.width), y,
std::min(y + spec.tile_height, spec.height), 0, 1,
make_span(tile));
// ... process the pixels in tile[] ..
// Watch out for "edge tiles" that are smaller than the full
// tile size.
// For example, if the image is 100x100 and the tile size is
// 32x32, the last tile in each row will be 4x32, the bottom
// row of tiles will be 32x4, and the very last
// tile of the whole images will be 4x4.
}
}
out->close();
// END-imageoutput-tiles
}



int
main(int /*argc*/, char** /*argv*/)
{
simple_write();
scanlines_write();
tiles_write();
return 0;
}
Loading
Loading