From c059f6a3e1ffc9257f5106b39e26bfb0e7912447 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Mon, 28 Apr 2025 00:30:41 -0700 Subject: [PATCH 1/9] api!: add image_span versions of ImageOutput methods * ImageOutput methods that write scanlines, tiles, and images, which are the main customization points for format output implementations, are given additional methods that take `image_view` in place of raw pointers and strides. * Generally, for each, there is a templated version that takes `image_view` that communicates both the memory area and the data type conversion requested, a "base case" that takes an explicit TypeDesc for the conversion type and a `image_view` giving the raw memory layout, and a `cspan` convenience version for when there are contiguous strides. Note that when reading mixed channel data types in "native" mode (no type conversion, just leave the data in its original types), you have to use the std::byte image_span version, since the idea is not to do any format conversion, and there may not be a single type involved. * For now, the default implementations of these new ImageOutput 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", still 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 --- CMakeLists.txt | 2 +- src/include/OpenImageIO/imageio.h | 535 ++++++++++++++++-- src/include/OpenImageIO/span.h | 15 +- src/include/OpenImageIO/typedesc.h | 22 +- src/libOpenImageIO/imageoutput.cpp | 137 +++++ src/libutil/typedesc_test.cpp | 32 ++ .../src/docs-examples-imageoutput.cpp | 8 +- 7 files changed, 680 insertions(+), 71 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 354accaad5..bc24a2fee9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required (VERSION 3.18.2...4.0) -set (OpenImageIO_VERSION "3.1.1.0") +set (OpenImageIO_VERSION "3.1.2.0") set (OpenImageIO_VERSION_OVERRIDE "" CACHE STRING "Version override (use with caution)!") mark_as_advanced (OpenImageIO_VERSION_OVERRIDE) diff --git a/src/include/OpenImageIO/imageio.h b/src/include/OpenImageIO/imageio.h index 5e5227113b..f50380bb9e 100644 --- a/src/include/OpenImageIO/imageio.h +++ b/src/include/OpenImageIO/imageio.h @@ -55,10 +55,6 @@ using imagesize_t = uint64_t; /// planes. using stride_t = int64_t; -/// Type we use to express how many pixels (or bytes) constitute an image, -/// tile, or scanline. -using imagesize_t = uint64_t; - /// Special value to indicate a stride length that should be /// auto-computed. inline constexpr stride_t AutoStride = std::numeric_limits::min(); @@ -2241,8 +2237,440 @@ class OIIO_API ImageOutput { virtual bool close () = 0; /// @} + // clang-format on + /// @{ - /// @name Writing pixels + /// @name Writing pixels ("safe" methods with bounded spans) + /// + /// Common features of all the `write` methods: + /// + /// * There is a base case that takes a `image_span` describing + /// untyped memory layout and a `TypeDesc` describing the data type + /// that the values should be converted to (or TypeUnknown to keep + /// the data in its "native" file types with no conversion). + /// + /// * The type-aware versions that accept an `image_span` and + /// understand to convert the data into the approprate `T` type. + /// + /// * The image_span (in either case) includes the memory bounds and + /// stride lengths (in bytes) between channels, scanlines, and + /// volumetric slices. + /// + /// * Any *range* parameters (such as `ybegin` and `yend`) describe a + /// "half open interval", meaning that `begin` is the first item and + /// `end` is *one past the last item*. That means that the number of + /// items is `end - begin`. + /// + /// * For ordinary 2D (non-volumetric) images, any `z` or `zbegin` + /// coordinates should be 0 and any `zend` should be 1, indicating + /// that only a single image "plane" exists. + /// + /// * Scanlines or tiles must be written in successive increasing + /// coordinate order, unless the particular output file driver allows + /// random access (indicated by `supports("random_access")`). + /// + /// * All write functions return `true` for success, `false` for failure + /// (after which a call to `geterror()` may retrieve a specific error + /// message). + /// + + /// Write the full scanline that includes pixels (*,y,z), taking the + /// values from an `image_span`. For 2D non-volume images, `z` should be + /// 0. + /// + /// @param y/z The y & z coordinates of the scanline. + /// @param data A full description of the pixel data location, + /// dimensions, and strides. + /// @returns `true` upon success, or `false` upon failure. + template bool write_scanline(int y, int z, image_span data) + { + // reduce to type + image_span + return write_scanline(y, z, TypeDescFromC::value(), + as_image_span_bytes(data)); + } + + /// Write the full scanline that includes pixels (*,y,z), taking + /// contiguous values from a `span`. For 2D non-volume images, `z` should + /// be 0. + /// + /// @param y/z The y & z coordinates of the scanline. + /// @param data A span of the contiguous data to write. + /// @returns `true` upon success, or `false` upon failure. + template bool write_scanline(int y, int z, span data) + { + // reduce to type + image_span + auto isize = m_spec.image_bytes(TypeDescFromC::value()); + return write_scanline(y, z, + image_span(data.data(), m_spec.nchannels, + m_spec.width, 1, 1)); + } + + /// Base/explicit case of write_scanline: Given an explicit data type and + /// an `image_span` of untyped bytes, write the full scanline that + /// includes pixels (*,y,z) from the buffer. For 2D non-volume images, `z` + /// should be 0. + /// + /// @param y/z The y & z coordinates of the scanline. + /// @param format A TypeDesc describing the type of the pixel data + /// that `data`'s memory contains. Use `TypeUnknown` + /// to indicate that the data is already in the native + /// format and needs no type conversion. + /// @param data An `image_span` of the pixel data to write. + /// @returns `true` upon success, or `false` upon failure. + virtual bool write_scanline(int y, int z, TypeDesc format, + image_span data); + + /// Write multiple scanlines that include pixels (*,y,z) for all `ybegin + /// <= y < yend`, from data. You can write a single scanline by passing + /// `yend == ybegin + 1`, but passing multiple scanlines in each call + /// often has performance advantages for many file formats. + /// + /// The image_span must have a width equal to a full scanline width, + /// and its height must be yend - ybegin. + /// + /// @param ybegin/yend The y range of the scanlines being passed. + /// @param z The z coordinate of the scanline. + /// @param data A full description of the pixel data location, + /// dimensions, and strides. + /// @returns `true` upon success, or `false` upon failure. + /// + template + bool write_scanlines(int ybegin, int yend, int z, image_span data) + { + // image_span: reduces to type + byte_buffer + return write_scanlines(ybegin, yend, z, TypeDescFromC::value(), + data.as_image_span_bytes()); + } + + /// A version of `write_scanlines()` taking a cspan, which assumes + /// contiguous strides in all dimensions. This is a convenience wrapper + /// around the `write_scanlines()` that takes an `image_span`. + template + bool write_scanlines(int ybegin, int yend, int z, TypeDesc format, + span data) + { + auto ispan = image_span(data.data(), m_spec.nchannels, m_spec.width, + yend - ybegin, 1); + OIIO_DASSERT(data.size_bytes() == ispan.size_bytes() + && ispan.is_contiguous()); + return write_scanlines(ybegin, yend, z, ispan); + } + + /// Base/explicit case of write_scanlines: Given an explicit data type and + /// an `image_span` of untyped bytes, write the scanlines of pixels from + /// the buffer. + /// + /// @param format A TypeDesc describing the type of the pixel data + /// that `data`'s memory contains. Use `TypeUnknown` + /// to indicate that the data is already in the native + /// format and needs no type conversion. + /// + virtual bool write_scanlines(int ybegin, int yend, int z, TypeDesc format, + image_span data); + + + /// Write a single tile of pixels whose upper left pixel coordinate is (x, + /// y, z) from a buffer described by `data`, which is an `image_span` + /// incorporating its bounded dimensions, strides, and data type. + /// + /// Added in OIIO 3.1, this is the "safe" preferred alternative to + /// the version of write_image that takes raw pointers. + /// + /// @param x/y/z The x range of the pixels being passed. + /// @param ybegin/yend The y range of the pixels being passed. + /// @param zbegin/zend The z range of the pixels being passed + /// (for a 2D image, zbegin=0 and zend=1). + /// @param data An `image_span` describing the buffer and + /// including its sizes and byte strides for each + /// dimension (channel, x, y, z), and the data + /// type `T`. + /// @returns `true` upon success, or `false` upon failure. + /// + template + bool write_tile(int x, int y, int z, image_span data) + { + return write_tile(x, y, z, TypeDescFromC::value(), + as_image_span_bytes(data)); + } + /// A version of `write_tile()` taking a cspan, which assumes + /// contiguous strides in all dimensions. This is a convenience wrapper + /// around the `write_tile()` that takes an `image_span`. + template bool write_tile(int x, int y, int z, span data) + { + auto ispan = image_span(data.data(), m_spec.nchannels, + m_spec.tile_width, m_spec.tile_height, + m_spec.tile_depth); + OIIO_DASSERT(data.size_bytes() == ispan.size_bytes() + && ispan.is_contiguous()); + return write_tile(x, y, z, ispan); + } + /// Base/explicit case of write_tile: Given an explicit data type and + /// an `image_span` of untyped bytes, write the tile of pixels from + /// the buffer. + /// + /// @param format A TypeDesc describing the type of the pixel data + /// that `data`'s memory contains. Use `TypeUnknown` + /// to indicate that the data is already in the native + /// format and needs no type conversion. + /// + virtual bool write_tile(int x, int y, int z, TypeDesc format, + image_span data); + + /// Write a rectangular regions of tiles of pixels given by the range + /// + /// [xbegin,xend) X [ybegin,yend) X [zbegin,zend) + /// + /// from a buffer described by `data`, which is an `image_span` + /// incorporating its bounded dimensions, strides, and data type. + /// The begin/end coordinates must be at tile or image boundaries. + /// + /// Added in OIIO 3.1, this is the "safe" preferred alternative to + /// the version of write_image that takes raw pointers. + /// + /// @param xbegin/xend The x range of the pixels being passed. + /// @param ybegin/yend The y range of the pixels being passed. + /// @param zbegin/zend The z range of the pixels being passed + /// (for a 2D image, zbegin=0 and zend=1). + /// @param data An `image_span` describing the buffer and + /// including its sizes and byte strides for each + /// dimension (channel, x, y, z), and the data + /// type `T`. + /// @returns `true` upon success, or `false` upon failure. + /// + template + bool write_tiles(int xbegin, int xend, int ybegin, int yend, int zbegin, + int zend, image_span data) + { + return write_tiles(xbegin, xend, ybegin, yend, zbegin, zend, + TypeDescFromC::value(), + as_image_span_bytes(data)); + } + /// A version of `write_tiles()` taking a cspan, which assumes + /// contiguous strides in all dimensions. This is a convenience wrapper + /// around the `write_tiles()` that takes an `image_span`. + template + bool write_tiles(int xbegin, int xend, int ybegin, int yend, int zbegin, + int zend, span data) + { + auto ispan = image_span(data.data(), m_spec.nchannels, xend - xbegin, + yend - ybegin, zend - zbegin); + OIIO_DASSERT(data.size_bytes() == ispan.size_bytes() + && ispan.is_contiguous()); + return write_tiles(xbegin, xend, ybegin, yend, zbegin, zend, ispan); + } + /// Base/explicit case of write_tiles: Given an explicit data type and + /// an `image_span` of untyped bytes, write the tiles of pixels from + /// the buffer. + /// + /// @param format A TypeDesc describing the type of the pixel data + /// that `data`'s memory contains. Use `TypeUnknown` + /// to indicate that the data is already in the native + /// format and needs no type conversion. + /// + virtual bool write_tiles(int xbegin, int xend, int ybegin, int yend, + int zbegin, int zend, TypeDesc format, + image_span data); + + /// Write the entire image of `spec.width x spec.height x spec.depth` + /// pixels, from a buffer described by `data`, which is an `image_span` + /// incorporating its bounded dimensions, strides, and data type. + /// + /// Depending on the spec, this will write either all tiles or all + /// scanlines. Assume that data points to a layout in row-major order. + /// + /// Added in OIIO 3.1, this is the "safe" preferred alternative to + /// the version of write_image that takes raw pointers. + /// + /// @param data An `image_span` describing the buffer and + /// including its sizes and byte strides for each + /// dimension (x, y, z, and channel), and the data + /// type `T`. + /// @returns `true` upon success, or `false` upon failure. + template bool write_image(image_span data) + { + return write_image(TypeDescFromC::value(), + as_image_span_bytes(data)); + } + + /// Write the entire image of `spec.width x spec.height x spec.depth` + /// pixels, from a buffer described by `data`, which is an `cspan` + /// assuming it points to a contiguously laid out buffer. + /// + /// Depending on the spec, this will write either all tiles or all + /// scanlines. Assume that data points to a layout in row-major order. + /// + /// Added in OIIO 3.1, this is the "safe" preferred alternative to + /// the version of write_image that takes raw pointers. + /// + /// @param data An `span` describing the bounds of + /// the buffer containing contiguously laid out + /// `T` data for the entire image. + /// @returns `true` upon success, or `false` upon failure. + template bool write_image(span data) + { + // auto isize = m_spec.image_bytes(TypeDescFromC::value()); + auto ispan = image_span(data.data(), m_spec.nchannels, + m_spec.width, m_spec.height, + m_spec.depth); + OIIO_DASSERT(data.size_bytes() == ispan.size_bytes() + && ispan.is_contiguous()); + return write_image(ispan); + } + + /// Base/explicit case of write_image: Given an explicit data type and an + /// `image_span` of untyped bytes, write the entire image of `spec.width x + /// spec.height x spec.depth` pixels from the buffer. + /// + /// Depending on the spec, this will write either all tiles or all + /// scanlines. Assume that data points to a layout in row-major order. + /// + /// Added in OIIO 3.1, this is the "safe" preferred alternative to + /// the version of write_image that takes raw pointers. + /// + /// @param format A TypeDesc describing the type of the pixel data + /// that `data`'s memory contains. Use `TypeUnknown` + /// to indicate that the data is already in the native + /// format and needs no type conversion. + /// @param data An `image_span` describing the memory extent + /// of the data buffer and including the sizes and + /// and byte strides for each dimension (channel, x, + /// y, and z). + /// @returns `true` upon success, or `false` upon failure. + /// + virtual bool write_image(TypeDesc format, image_span data); + + /// Write a rectangle of pixels given by the range + /// + /// [xbegin,xend) X [ybegin,yend) X [zbegin,zend) + /// + /// from a buffer described by `data`, which is an `image_span` + /// incorporating its bounded dimensions, strides, and data type. + /// + /// Added in OIIO 3.1, this is the "safe" preferred alternative to + /// the version of write_image that takes raw pointers. + /// + /// @param xbegin/xend The x range of the pixels being passed. + /// @param ybegin/yend The y range of the pixels being passed. + /// @param zbegin/zend The z range of the pixels being passed + /// (for a 2D image, zbegin=0 and zend=1). + /// @param data An `image_span` describing the buffer and + /// including its sizes and byte strides for each + /// dimension (channel, x, y, z), and the data + /// type `T`. + /// @returns `true` upon success, or `false` upon failure. + /// + /// @note The call will fail for a format plugin that does not return + /// true for `supports("rectangles")`. + template + bool write_rectangle(int xbegin, int xend, int ybegin, int yend, int zbegin, + int zend, image_span data) + { + return write_rectangle(xbegin, xend, ybegin, yend, zbegin, zend, + TypeDescFromC::value(), + as_image_span_bytes(data)); + } + + /// A version of `write_rectangle()` taking a cspan, which assumes + /// contiguous strides in all dimensions. This is a convenience wrapper + /// around the `write_rectangle()` that takes an `image_span`. + template + bool write_rectangle(int xbegin, int xend, int ybegin, int yend, int zbegin, + int zend, span data) + { + auto ispan = image_span(data.data(), m_spec.nchannels, + xend - xbegin, yend - ybegin, + zend - zbegin); + OIIO_DASSERT(data.size_bytes() == ispan.size_bytes() + && ispan.is_contiguous()); + return write_rectangle(xbegin, xend, ybegin, yend, zbegin, zend, ispan); + } + + /// Base/explicit case of write_rectangle: Given an explicit data type and + /// an `image_span` of untyped bytes, write the rectangle of pixels from + /// the buffer. + /// + /// @param format A TypeDesc describing the type of the pixel data + /// that `data`'s memory contains. Use `TypeUnknown` + /// to indicate that the data is already in the native + /// format and needs no type conversion. + /// + virtual bool write_rectangle(int xbegin, int xend, int ybegin, int yend, + int zbegin, int zend, TypeDesc format, + image_span data); + + // clang-format off + + /// Write deep scanlines containing pixels (*,y,z), for all y in the + /// range [ybegin,yend), to a deep file. This will fail if it is not a + /// deep file. + /// + /// @param ybegin/yend The y range of the scanlines being passed. + /// @param z The z coordinate of the scanline. + /// @param deepdata A `DeepData` object with the data for these + /// scanlines. + /// @returns `true` upon success, or `false` upon failure. + virtual bool write_deep_scanlines (int ybegin, int yend, int z, + const DeepData &deepdata); + + /// Write the block of deep tiles that include all pixels in + /// the range + /// + /// [xbegin,xend) X [ybegin,yend) X [zbegin,zend) + /// + /// The begin/end pairs must correctly delineate tile boundaries, with + /// the exception that it may also be the end of the image data if the + /// image resolution is not a whole multiple of the tile size. + /// + /// @param xbegin/xend The x range of the pixels covered by the group + /// of tiles passed. + /// @param ybegin/yend The y range of the pixels covered by the tiles. + /// @param zbegin/zend The z range of the pixels covered by the tiles + /// (for a 2D image, zbegin=0 and zend=1). + /// @param deepdata A `DeepData` object with the data for the tiles. + /// @returns `true` upon success, or `false` upon failure. + /// + /// @note The call will fail if the image is not tiled, or if the pixel + /// ranges do not fall along tile (or image) boundaries, or if it is not + /// a valid tile range. + virtual bool write_deep_tiles (int xbegin, int xend, int ybegin, int yend, + int zbegin, int zend, + const DeepData &deepdata); + + /// Write the entire deep image described by `deepdata`. Depending on + /// the spec, this will write either all tiles or all scanlines. + /// + /// @param deepdata A `DeepData` object with the data for the image. + /// @returns `true` upon success, or `false` upon failure. + virtual bool write_deep_image (const DeepData &deepdata); + + /// Specify a reduced-resolution ("thumbnail") version of the image. + /// Note that many image formats may require the thumbnail to be + /// specified prior to writing the pixels. + /// + /// @param thumb + /// A reference to an `ImageBuf` containing the thumbnail image. + /// @returns + /// `true` upon success, `false` if it was not possible to write + /// the thumbnail, or if this file format (or writer) does not + /// support thumbnails. + /// + /// @note This method was added to OpenImageIO 2.3. + virtual bool set_thumbnail(const ImageBuf& thumb) { return false; } + + /// @} + + /// @{ + /// @name Writing pixels (unsafe methods with pointers and strides) + /// + /// These methods are the "unsafe" versions of the `write` methods, which + /// take raw pointers and strides. They are provided for backwards + /// compatibility with older code, but the preferred interface is to use + /// the `write` methods that take `image_span` or `cspan` + /// arguments, which are bounds-safe and type-aware. + /// + /// These pointer-based versions are considered "soft-deprecated" in + /// OpenImageIO 3.1, will be marked/warned as deprecated in 3.2, and will + /// be removed in 4.0. /// /// Common features of all the `write` methods: /// @@ -2446,63 +2874,6 @@ class OIIO_API ImageOutput { ProgressCallback progress_callback=nullptr, void *progress_callback_data=nullptr); - /// Write deep scanlines containing pixels (*,y,z), for all y in the - /// range [ybegin,yend), to a deep file. This will fail if it is not a - /// deep file. - /// - /// @param ybegin/yend The y range of the scanlines being passed. - /// @param z The z coordinate of the scanline. - /// @param deepdata A `DeepData` object with the data for these - /// scanlines. - /// @returns `true` upon success, or `false` upon failure. - virtual bool write_deep_scanlines (int ybegin, int yend, int z, - const DeepData &deepdata); - - /// Write the block of deep tiles that include all pixels in - /// the range - /// - /// [xbegin,xend) X [ybegin,yend) X [zbegin,zend) - /// - /// The begin/end pairs must correctly delineate tile boundaries, with - /// the exception that it may also be the end of the image data if the - /// image resolution is not a whole multiple of the tile size. - /// - /// @param xbegin/xend The x range of the pixels covered by the group - /// of tiles passed. - /// @param ybegin/yend The y range of the pixels covered by the tiles. - /// @param zbegin/zend The z range of the pixels covered by the tiles - /// (for a 2D image, zbegin=0 and zend=1). - /// @param deepdata A `DeepData` object with the data for the tiles. - /// @returns `true` upon success, or `false` upon failure. - /// - /// @note The call will fail if the image is not tiled, or if the pixel - /// ranges do not fall along tile (or image) boundaries, or if it is not - /// a valid tile range. - virtual bool write_deep_tiles (int xbegin, int xend, int ybegin, int yend, - int zbegin, int zend, - const DeepData &deepdata); - - /// Write the entire deep image described by `deepdata`. Depending on - /// the spec, this will write either all tiles or all scanlines. - /// - /// @param deepdata A `DeepData` object with the data for the image. - /// @returns `true` upon success, or `false` upon failure. - virtual bool write_deep_image (const DeepData &deepdata); - - /// Specify a reduced-resolution ("thumbnail") version of the image. - /// Note that many image formats may require the thumbnail to be - /// specified prior to writing the pixels. - /// - /// @param thumb - /// A reference to an `ImageBuf` containing the thumbnail image. - /// @returns - /// `true` upon success, `false` if it was not possible to write - /// the thumbnail, or if this file format (or writer) does not - /// support thumbnails. - /// - /// @note This method was added to OpenImageIO 2.3. - virtual bool set_thumbnail(const ImageBuf& thumb) { return false; } - /// @} /// Read the pixels of the current subimage of `in`, and write it as the @@ -2771,6 +3142,46 @@ class OIIO_API ImageOutput { unsigned int dither=0, int xorigin=0, int yorigin=0, int zorigin=0); + // clang-format on + + /// Helper routine used by write_* implementations: convert data (in + /// the given format and strides) to the "native" format of the file + /// (described by the 'spec' member variable), in contiguous order. This + /// requires a scratch space to be passed in so that there are no memory + /// leaks. Returns a span referring to the native data, which may be the + /// original data if it was already in native format and contiguous, or + /// it may point to the scratch space if it needed to make a copy or do + /// conversions. For float->uint8 conversions only, if dither is + /// nonzero, random dither will be added to reduce quantization banding + /// artifacts; in this case, the specific nonzero dither value is used + /// as a seed for the hash function that produces the per-pixel dither + /// amounts, and the optional [xyz]origin parameters help it to align + /// the pixels to the right position in the dither pattern. + template + cspan to_native(int xbegin, int xend, int ybegin, int yend, + int zbegin, int zend, image_span data, + std::vector& scratch, + unsigned int dither = 0, int xorigin = 0, + int yorigin = 0, int zorigin = 0) + { + auto ispan = image_span(data.data(), m_spec.nchannels, xend - xbegin, + yend - ybegin, zend - zbegin); + OIIO_DASSERT(data.size_bytes() == ispan.size_bytes() + && ispan.is_contiguous()); + return to_native(xbegin, xend, ybegin, yend, zbegin, zend, ispan, + scratch, dither, xorigin, yorigin, zorigin); + } + + /// This version of the to_native helper takes an explicit data type + /// `format` and an image_span of generic (std::byte) data. + cspan to_native(int xbegin, int xend, int ybegin, int yend, + int zbegin, int zend, TypeDesc format, + image_span data, + std::vector& scratch, + unsigned int dither = 0, int xorigin = 0, + int yorigin = 0, int zorigin = 0); + // clang-format off + /// Helper function to copy a rectangle of data into the right spot in /// an image-sized buffer. In addition to copying to the right place, /// this handles data format conversion and dither (if the spec's diff --git a/src/include/OpenImageIO/span.h b/src/include/OpenImageIO/span.h index f0c925fa17..a854167b99 100644 --- a/src/include/OpenImageIO/span.h +++ b/src/include/OpenImageIO/span.h @@ -41,7 +41,8 @@ OIIO_NAMESPACE_BEGIN using span_size_t = size_t; using oiio_span_size_type = OIIO::span_size_t; // back-compat alias -inline constexpr span_size_t dynamic_extent = -1; +inline constexpr span_size_t dynamic_extent + = std::numeric_limits::max(); @@ -586,6 +587,18 @@ as_bytes_ref(const T& ref) noexcept +/// Copy the memory contents of `src` to `dst`. They must have the same +/// total size. +template +inline void +spancpy(span dst, span src) +{ + OIIO_DASSERT(dst.size_bytes() == src.size_bytes()); + memcpy(dst.data(), src.data(), src.size_bytes()); +} + + + /// Try to copy `n` items of type `T` from `src[srcoffset...]` to /// `dst[dstoffset...]`. Don't read or write outside the respective span /// boundaries. Return the number of items actually copied, which should be diff --git a/src/include/OpenImageIO/typedesc.h b/src/include/OpenImageIO/typedesc.h index 91de0e3a64..2c24cf6223 100644 --- a/src/include/OpenImageIO/typedesc.h +++ b/src/include/OpenImageIO/typedesc.h @@ -394,44 +394,60 @@ inline constexpr TypeDesc TypeUstringhash(TypeDesc::USTRINGHASH); /// template struct BaseTypeFromC {}; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::UINT8; }; +template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::UINT8; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::INT8; }; +template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::INT8; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::UINT16; }; +template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::UINT16; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::INT16; }; +template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::INT16; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::UINT; }; +template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::UINT; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::INT; }; +template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::INT; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::UINT64; }; +template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::UINT64; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::INT64; }; +template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::INT64; }; #if defined(__GNUC__) && __WORDSIZE == 64 && !(defined(__APPLE__) && defined(__MACH__)) // Some platforms consider int64_t and long long to be different types, even // though they are actually the same size. static_assert(!std::is_same_v); static_assert(!std::is_same_v); template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::UINT64; }; +template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::UINT64; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::INT64; }; +template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::INT64; }; #endif #if defined(_HALF_H_) || defined(IMATH_HALF_H_) template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::HALF; }; +template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::HALF; }; #endif template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::FLOAT; }; +template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::FLOAT; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::DOUBLE; }; -template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::STRING; }; +template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::DOUBLE; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::STRING; }; +template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::STRING; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::STRING; }; +template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::STRING; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::STRING; }; +template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::STRING; }; class ustring; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::STRING; }; +template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::STRING; }; template struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::STRING; }; template struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::STRING; }; template struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::PTR; }; /// `BaseTypeFromC_v` is shorthand for `BaseTypeFromC::value()`. template -constexpr TypeDesc::BASETYPE BaseTypeFromC_v = BaseTypeFromC>::value(); +constexpr TypeDesc::BASETYPE BaseTypeFromC_v = BaseTypeFromC>::value; /// A template mechanism for getting the TypeDesc from a C type. /// The default for simple types is just the TypeDesc based on BaseTypeFromC. /// But we can specialize more complex types. -template struct TypeDescFromC { static const constexpr TypeDesc value() { return TypeDesc(BaseTypeFromC::value); } }; +template struct TypeDescFromC { static const constexpr TypeDesc value() { return TypeDesc(BaseTypeFromC_v); } }; template<> struct TypeDescFromC { static const constexpr TypeDesc value() { return TypeDesc::INT32; } }; template<> struct TypeDescFromC { static const constexpr TypeDesc value() { return TypeDesc::UINT32; } }; template<> struct TypeDescFromC { static const constexpr TypeDesc value() { return TypeDesc::INT16; } }; diff --git a/src/libOpenImageIO/imageoutput.cpp b/src/libOpenImageIO/imageoutput.cpp index 6be88ae7de..831bd78048 100644 --- a/src/libOpenImageIO/imageoutput.cpp +++ b/src/libOpenImageIO/imageoutput.cpp @@ -110,6 +110,32 @@ ImageOutput::write_scanline(int /*y*/, int /*z*/, TypeDesc /*format*/, +bool +ImageOutput::write_scanline(int y, int z, TypeDesc format, + image_span data) +{ + if (pvt::oiio_print_debug +#ifndef NDEBUG + || true +#endif + ) { + size_t sz = (format == TypeUnknown ? m_spec.pixel_bytes(true /*native*/) + : format.size() * m_spec.nchannels) + * size_t(m_spec.width); + if (sz != data.size_bytes()) { + errorfmt( + "write_scanline: Buffer size is incorrect ({} bytes vs {} needed)", + sz, data.size_bytes()); + return false; + } + } + + // Default implementation (for now): call the old pointer+stride + return write_scanline(y, z, format, data.data(), data.xstride()); +} + + + bool ImageOutput::write_scanlines(int ybegin, int yend, int z, TypeDesc format, const void* data, stride_t xstride, @@ -132,6 +158,33 @@ ImageOutput::write_scanlines(int ybegin, int yend, int z, TypeDesc format, +bool +ImageOutput::write_scanlines(int ybegin, int yend, int z, TypeDesc format, + image_span data) +{ + if (pvt::oiio_print_debug +#ifndef NDEBUG + || true +#endif + ) { + size_t sz = (format == TypeUnknown ? m_spec.pixel_bytes(true /*native*/) + : format.size() * m_spec.nchannels) + * size_t(yend - ybegin) * size_t(m_spec.width); + if (sz != data.size_bytes()) { + errorfmt( + "write_scanlines: Buffer size is incorrect ({} bytes vs {} needed)", + sz, data.size_bytes()); + return false; + } + } + + // Default implementation (for now): call the old pointer+stride + return write_scanlines(ybegin, yend, z, format, data.data(), data.xstride(), + data.ystride()); +} + + + bool ImageOutput::write_tile(int /*x*/, int /*y*/, int /*z*/, TypeDesc /*format*/, const void* /*data*/, stride_t /*xstride*/, @@ -143,6 +196,34 @@ ImageOutput::write_tile(int /*x*/, int /*y*/, int /*z*/, TypeDesc /*format*/, +bool +ImageOutput::write_tile(int x, int y, int z, TypeDesc format, + image_span data) +{ + if (pvt::oiio_print_debug +#ifndef NDEBUG + || true +#endif + ) { + size_t sz = format == TypeUnknown + ? m_spec.pixel_bytes(true /*native*/) + : m_spec.tile_pixels() * size_t(m_spec.nchannels) + * format.size(); + if (sz != data.size_bytes()) { + errorfmt( + "write_tile: Buffer size is incorrect ({} bytes vs {} needed)", + sz, data.size_bytes()); + return false; + } + } + + // Default implementation (for now): call the old pointer+stride + return write_tile(x, y, z, format, data.data(), data.xstride(), + data.ystride(), data.zstride()); +} + + + bool ImageOutput::write_tiles(int xbegin, int xend, int ybegin, int yend, int zbegin, int zend, TypeDesc format, const void* data, @@ -202,6 +283,19 @@ ImageOutput::write_tiles(int xbegin, int xend, int ybegin, int yend, int zbegin, +bool +ImageOutput::write_tiles(int xbegin, int xend, int ybegin, int yend, int zbegin, + int zend, TypeDesc format, + image_span data) +{ + // Default implementation (for now): call the old pointer+stride + return write_tiles(xbegin, xend, ybegin, yend, zbegin, zend, format, + data.data(), data.xstride(), data.ystride(), + data.zstride()); +} + + + bool ImageOutput::write_rectangle(int /*xbegin*/, int /*xend*/, int /*ybegin*/, int /*yend*/, int /*zbegin*/, int /*zend*/, @@ -214,6 +308,17 @@ ImageOutput::write_rectangle(int /*xbegin*/, int /*xend*/, int /*ybegin*/, +bool +ImageOutput::write_rectangle(int /*xbegin*/, int /*xend*/, int /*ybegin*/, + int /*yend*/, int /*zbegin*/, int /*zend*/, + TypeDesc /*format*/, + image_span /*data*/) +{ + return false; +} + + + bool ImageOutput::write_deep_scanlines(int /*ybegin*/, int /*yend*/, int /*z*/, const DeepData& /*deepdata*/) @@ -481,6 +586,28 @@ ImageOutput::to_native_rectangle(int xbegin, int xend, int ybegin, int yend, +cspan +ImageOutput::to_native(int xbegin, int xend, int ybegin, int yend, int zbegin, + int zend, TypeDesc format, + image_span data, + std::vector& scratch, unsigned int dither, + int xorigin, int yorigin, int zorigin) +{ + // Eventually, we will make a fully save, span-native implementation of + // this function. For now, we will just call the old version for the + // heavy lifting. + auto ptr = ImageOutput::to_native_rectangle(xbegin, xend, ybegin, yend, + zbegin, zend, format, + data.data(), data.xstride(), + data.ystride(), data.zstride(), + scratch, dither, xorigin, + yorigin, zorigin); + return cspan(reinterpret_cast(ptr), + m_spec.pixel_bytes(true) * data.npixels()); +} + + + bool ImageOutput::write_image(TypeDesc format, const void* data, stride_t xstride, stride_t ystride, stride_t zstride, @@ -573,6 +700,16 @@ ImageOutput::write_image(TypeDesc format, const void* data, stride_t xstride, +bool +ImageOutput::write_image(TypeDesc format, image_span data) +{ + // Default implementation (for now): call the old pointer+stride + return write_image(format, data.data(), data.xstride(), data.ystride(), + data.zstride()); +} + + + bool ImageOutput::copy_image(ImageInput* in) { diff --git a/src/libutil/typedesc_test.cpp b/src/libutil/typedesc_test.cpp index a8c342b5d5..b7c2c57ae8 100644 --- a/src/libutil/typedesc_test.cpp +++ b/src/libutil/typedesc_test.cpp @@ -87,20 +87,52 @@ test_templates() { print("Testing templates\n"); OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::FLOAT); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::FLOAT); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::HALF); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::HALF); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::DOUBLE); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::DOUBLE); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::INT); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::INT); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::INT); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::INT); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::UINT); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::UINT); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::UINT); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::UINT); + + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::INT16); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::INT16); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::INT16); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::INT16); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::UINT16); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, + TypeDesc::UINT16); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::UINT16); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::UINT16); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::STRING); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::STRING); OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::STRING); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::STRING); OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::PTR); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::PTR); OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::PTR); + OIIO_CHECK_EQUAL(BaseTypeFromC::value, TypeDesc::PTR); OIIO_CHECK_EQUAL(TypeDescFromC::value(), TypeFloat); + OIIO_CHECK_EQUAL(TypeDescFromC::value(), TypeFloat); OIIO_CHECK_EQUAL(TypeDescFromC::value(), TypeInt); + OIIO_CHECK_EQUAL(TypeDescFromC::value(), TypeInt); OIIO_CHECK_EQUAL(TypeDescFromC::value(), TypeString); + OIIO_CHECK_EQUAL(TypeDescFromC::value(), TypeString); OIIO_CHECK_EQUAL(TypeDescFromC::value(), TypeString); OIIO_CHECK_EQUAL(TypeDescFromC::value(), TypeString); OIIO_CHECK_EQUAL(TypeDescFromC::value(), TypePointer); OIIO_CHECK_EQUAL(TypeDescFromC::value(), TypePointer); OIIO_CHECK_EQUAL(TypeDescFromC::value(), TypePointer); + OIIO_CHECK_EQUAL(TypeDescFromC::value(), TypePointer); } diff --git a/testsuite/docs-examples-cpp/src/docs-examples-imageoutput.cpp b/testsuite/docs-examples-cpp/src/docs-examples-imageoutput.cpp index e815465ed5..5cb948bde0 100644 --- a/testsuite/docs-examples-cpp/src/docs-examples-imageoutput.cpp +++ b/testsuite/docs-examples-cpp/src/docs-examples-imageoutput.cpp @@ -42,14 +42,14 @@ simple_write() { const char* filename = "simple.tif"; const int xres = 320, yres = 240, channels = 3; - unsigned char pixels[xres * yres * channels] = { 0 }; + std::vector pixels(xres * yres * channels); std::unique_ptr out = ImageOutput::create(filename); if (!out) return; // error ImageSpec spec(xres, yres, channels, TypeDesc::UINT8); out->open(filename, spec); - out->write_image(TypeDesc::UINT8, pixels); + out->write_image(make_cspan(pixels)); out->close(); } // END-imageoutput-simple @@ -68,12 +68,12 @@ scanlines_write() ImageSpec spec(xres, yres, channels, TypeDesc::UINT8); // BEGIN-imageoutput-scanlines - unsigned char scanline[xres * channels] = { 0 }; + std::vector scanline(xres * channels); out->open(filename, spec); int z = 0; // Always zero for 2D images for (int y = 0; y < yres; ++y) { // ... generate data in scanline[0..xres*channels-1] ... - out->write_scanline(y, z, TypeDesc::UINT8, scanline); + out->write_scanline(y, z, make_span(scanline)); } out->close(); // END-imageoutput-scanlines From e7f363e6bcd2bf212c8b08b87426279dcd1c9d8b Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Mon, 5 May 2025 11:21:14 -0700 Subject: [PATCH 2/9] Address review comments Signed-off-by: Larry Gritz --- src/include/OpenImageIO/imageio.h | 3 +- src/libOpenImageIO/imageoutput.cpp | 69 +++++++++++------------------- 2 files changed, 26 insertions(+), 46 deletions(-) diff --git a/src/include/OpenImageIO/imageio.h b/src/include/OpenImageIO/imageio.h index f50380bb9e..92c4a4226e 100644 --- a/src/include/OpenImageIO/imageio.h +++ b/src/include/OpenImageIO/imageio.h @@ -2346,8 +2346,7 @@ class OIIO_API ImageOutput { /// contiguous strides in all dimensions. This is a convenience wrapper /// around the `write_scanlines()` that takes an `image_span`. template - bool write_scanlines(int ybegin, int yend, int z, TypeDesc format, - span data) + bool write_scanlines(int ybegin, int yend, int z, span data) { auto ispan = image_span(data.data(), m_spec.nchannels, m_spec.width, yend - ybegin, 1); diff --git a/src/libOpenImageIO/imageoutput.cpp b/src/libOpenImageIO/imageoutput.cpp index 831bd78048..0d4348e79c 100644 --- a/src/libOpenImageIO/imageoutput.cpp +++ b/src/libOpenImageIO/imageoutput.cpp @@ -114,20 +114,14 @@ bool ImageOutput::write_scanline(int y, int z, TypeDesc format, image_span data) { - if (pvt::oiio_print_debug -#ifndef NDEBUG - || true -#endif - ) { - size_t sz = (format == TypeUnknown ? m_spec.pixel_bytes(true /*native*/) - : format.size() * m_spec.nchannels) - * size_t(m_spec.width); - if (sz != data.size_bytes()) { - errorfmt( - "write_scanline: Buffer size is incorrect ({} bytes vs {} needed)", - sz, data.size_bytes()); - return false; - } + size_t sz = (format == TypeUnknown ? m_spec.pixel_bytes(true /*native*/) + : format.size() * m_spec.nchannels) + * size_t(m_spec.width); + if (sz != data.size_bytes()) { + errorfmt( + "write_scanline: Buffer size is incorrect ({} bytes vs {} needed)", + sz, data.size_bytes()); + return false; } // Default implementation (for now): call the old pointer+stride @@ -162,20 +156,14 @@ bool ImageOutput::write_scanlines(int ybegin, int yend, int z, TypeDesc format, image_span data) { - if (pvt::oiio_print_debug -#ifndef NDEBUG - || true -#endif - ) { - size_t sz = (format == TypeUnknown ? m_spec.pixel_bytes(true /*native*/) - : format.size() * m_spec.nchannels) - * size_t(yend - ybegin) * size_t(m_spec.width); - if (sz != data.size_bytes()) { - errorfmt( - "write_scanlines: Buffer size is incorrect ({} bytes vs {} needed)", - sz, data.size_bytes()); - return false; - } + size_t sz = (format == TypeUnknown ? m_spec.pixel_bytes(true /*native*/) + : format.size() * m_spec.nchannels) + * size_t(yend - ybegin) * size_t(m_spec.width); + if (sz != data.size_bytes()) { + errorfmt( + "write_scanlines: Buffer size is incorrect ({} bytes vs {} needed)", + sz, data.size_bytes()); + return false; } // Default implementation (for now): call the old pointer+stride @@ -200,21 +188,14 @@ bool ImageOutput::write_tile(int x, int y, int z, TypeDesc format, image_span data) { - if (pvt::oiio_print_debug -#ifndef NDEBUG - || true -#endif - ) { - size_t sz = format == TypeUnknown - ? m_spec.pixel_bytes(true /*native*/) - : m_spec.tile_pixels() * size_t(m_spec.nchannels) - * format.size(); - if (sz != data.size_bytes()) { - errorfmt( - "write_tile: Buffer size is incorrect ({} bytes vs {} needed)", - sz, data.size_bytes()); - return false; - } + size_t sz = format == TypeUnknown + ? m_spec.pixel_bytes(true /*native*/) + : m_spec.tile_pixels() * size_t(m_spec.nchannels) + * format.size(); + if (sz != data.size_bytes()) { + errorfmt("write_tile: Buffer size is incorrect ({} bytes vs {} needed)", + sz, data.size_bytes()); + return false; } // Default implementation (for now): call the old pointer+stride @@ -593,7 +574,7 @@ ImageOutput::to_native(int xbegin, int xend, int ybegin, int yend, int zbegin, std::vector& scratch, unsigned int dither, int xorigin, int yorigin, int zorigin) { - // Eventually, we will make a fully save, span-native implementation of + // Eventually, we will make a fully safe, span-native implementation of // this function. For now, we will just call the old version for the // heavy lifting. auto ptr = ImageOutput::to_native_rectangle(xbegin, xend, ybegin, yend, From 1b9466766dd6c835ae43e6a0cab3a912b3ba2d5a Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Mon, 5 May 2025 12:05:18 -0700 Subject: [PATCH 3/9] Re-order and better explain the new methods For each triplet, explain in order of: * Generic: TypeDesc + image_span (full explanation) * implied type: image_span (brief explanation) * implied type + contiguous: span (brief explanation) Also, move write_image FIRST, followed by write_scanline(s) and write_tile(s), since the whole image will be the most common use case. Signed-off-by: Larry Gritz --- src/include/OpenImageIO/imageio.h | 323 ++++++++++++++---------------- 1 file changed, 152 insertions(+), 171 deletions(-) diff --git a/src/include/OpenImageIO/imageio.h b/src/include/OpenImageIO/imageio.h index 92c4a4226e..844e72e0e1 100644 --- a/src/include/OpenImageIO/imageio.h +++ b/src/include/OpenImageIO/imageio.h @@ -2274,14 +2274,76 @@ class OIIO_API ImageOutput { /// message). /// - /// Write the full scanline that includes pixels (*,y,z), taking the - /// values from an `image_span`. For 2D non-volume images, `z` should be - /// 0. + /// Given an explicit data type and an `image_span` of untyped bytes, + /// write the entire image of `spec.width x spec.height x spec.depth` + /// pixels from the buffer. + /// + /// Depending on the spec, this will write either all tiles or all + /// scanlines. Assume that data points to a layout in row-major order. + /// + /// Added in OIIO 3.1, this is the "safe" preferred alternative to + /// the version of write_image that takes raw pointers. + /// + /// @param format A TypeDesc describing the type of the pixel data + /// that `data`'s memory contains. Use `TypeUnknown` + /// to indicate that the data is already in the native + /// format and needs no type conversion. + /// @param data An `image_span` describing the memory extent + /// of the data buffer and including the sizes and + /// and byte strides for each dimension (channel, x, + /// y, and z). + /// @returns `true` upon success, or `false` upon failure. + /// + virtual bool write_image(TypeDesc format, image_span data); + + /// A version of `write_image()` taking an `image_span`, where the type + /// of the underlying data is `T`. This is a convenience wrapper around + /// the `write_image()` that takes an `image_span`. + template bool write_image(image_span data) + { + return write_image(TypeDescFromC::value(), + as_image_span_bytes(data)); + } + + /// A version of `write_image()` taking a `cspan`, which assumes + /// contiguous strides in all dimensions. This is a convenience wrapper + /// around the `write_image()` that takes an `image_span`. + template bool write_image(span data) + { + auto ispan = image_span(data.data(), m_spec.nchannels, + m_spec.width, m_spec.height, + m_spec.depth); + OIIO_DASSERT(data.size_bytes() == ispan.size_bytes() + && ispan.is_contiguous()); + return write_image(ispan); + } + + /// Write one scanline that include pixels (*,y,z) from `data`, which is + /// an `image_span` of un-typed bytes incorporating its bounded dimensions + /// and strides. The data type is given explicity by the `format` + /// argument, and will be automatically converted to the type being stored + /// in the file. + /// + /// The image_span must have a width equal to a full scanline width, + /// and its height and depth must be 1. /// /// @param y/z The y & z coordinates of the scanline. - /// @param data A full description of the pixel data location, - /// dimensions, and strides. + /// @param format A TypeDesc describing the type of the pixel data + /// that `data`'s memory contains. Use `TypeUnknown` + /// to indicate that the data is already in the native + /// format and needs no type conversion. + /// @param data An `image_span` describing the buffer and + /// including its sizes and byte strides for each + /// dimension (channel, x, y, z). /// @returns `true` upon success, or `false` upon failure. + /// + virtual bool write_scanline(int y, int z, TypeDesc format, + image_span data); + + /// A version of `write_scanline()` taking an `image_span`, where the + /// type of the underlying data is `T`. This is a convenience wrapper + /// around the `write_scanline()` that takes an `image_span`. template bool write_scanline(int y, int z, image_span data) { // reduce to type + image_span @@ -2289,13 +2351,9 @@ class OIIO_API ImageOutput { as_image_span_bytes(data)); } - /// Write the full scanline that includes pixels (*,y,z), taking - /// contiguous values from a `span`. For 2D non-volume images, `z` should - /// be 0. - /// - /// @param y/z The y & z coordinates of the scanline. - /// @param data A span of the contiguous data to write. - /// @returns `true` upon success, or `false` upon failure. + /// A version of `write_scanline()` taking a `cspan`, which assumes + /// contiguous strides in all dimensions. This is a convenience wrapper + /// around the `write_scanline()` that takes an `image_span`. template bool write_scanline(int y, int z, span data) { // reduce to type + image_span @@ -2305,35 +2363,36 @@ class OIIO_API ImageOutput { m_spec.width, 1, 1)); } - /// Base/explicit case of write_scanline: Given an explicit data type and - /// an `image_span` of untyped bytes, write the full scanline that - /// includes pixels (*,y,z) from the buffer. For 2D non-volume images, `z` - /// should be 0. - /// - /// @param y/z The y & z coordinates of the scanline. - /// @param format A TypeDesc describing the type of the pixel data - /// that `data`'s memory contains. Use `TypeUnknown` - /// to indicate that the data is already in the native - /// format and needs no type conversion. - /// @param data An `image_span` of the pixel data to write. - /// @returns `true` upon success, or `false` upon failure. - virtual bool write_scanline(int y, int z, TypeDesc format, - image_span data); - /// Write multiple scanlines that include pixels (*,y,z) for all `ybegin - /// <= y < yend`, from data. You can write a single scanline by passing - /// `yend == ybegin + 1`, but passing multiple scanlines in each call - /// often has performance advantages for many file formats. + /// <= y < yend`, from `data`, which is an `image_span` of un-typed bytes + /// incorporating its bounded dimensions and strides. The data type is + /// given explicity by the `format` argument, and will be automatically + /// converted to the type being stored in the file. You can write a single + /// scanline by passing `yend == ybegin + 1`, but passing multiple + /// scanlines in each call often has performance advantages for many file + /// formats. /// /// The image_span must have a width equal to a full scanline width, /// and its height must be yend - ybegin. /// /// @param ybegin/yend The y range of the scanlines being passed. /// @param z The z coordinate of the scanline. - /// @param data A full description of the pixel data location, - /// dimensions, and strides. + /// @param format A TypeDesc describing the type of the pixel data + /// that `data`'s memory contains. Use `TypeUnknown` + /// to indicate that the data is already in the native + /// format and needs no type conversion. + /// @param data An `image_span` describing the buffer and + /// including its sizes and byte strides for each + /// dimension (channel, x, y, z). /// @returns `true` upon success, or `false` upon failure. /// + virtual bool write_scanlines(int ybegin, int yend, int z, TypeDesc format, + image_span data); + + /// A version of `write_scanlines()` taking an `image_span`, where the + /// type of the underlying data is `T`. This is a convenience wrapper + /// around the `write_scanlines()` that takes an `image_span`. template bool write_scanlines(int ybegin, int yend, int z, image_span data) { @@ -2342,7 +2401,7 @@ class OIIO_API ImageOutput { data.as_image_span_bytes()); } - /// A version of `write_scanlines()` taking a cspan, which assumes + /// A version of `write_scanlines()` taking a `cspan`, which assumes /// contiguous strides in all dimensions. This is a convenience wrapper /// around the `write_scanlines()` that takes an `image_span`. template @@ -2355,22 +2414,14 @@ class OIIO_API ImageOutput { return write_scanlines(ybegin, yend, z, ispan); } - /// Base/explicit case of write_scanlines: Given an explicit data type and - /// an `image_span` of untyped bytes, write the scanlines of pixels from - /// the buffer. - /// - /// @param format A TypeDesc describing the type of the pixel data - /// that `data`'s memory contains. Use `TypeUnknown` - /// to indicate that the data is already in the native - /// format and needs no type conversion. - /// - virtual bool write_scanlines(int ybegin, int yend, int z, TypeDesc format, - image_span data); - /// Write a single tile of pixels whose upper left pixel coordinate is (x, - /// y, z) from a buffer described by `data`, which is an `image_span` - /// incorporating its bounded dimensions, strides, and data type. + /// y, z) from a buffer described by `data`, which is an `image_span` of + /// un-typed bytes incorporating its bounded dimensions and strides. The + /// data type is given explicity by the `format` argument, and will be + /// automatically converted to the type being stored in the file. The + /// (x,y,z) coordinates must be the pixel coordinates of the first (upper + /// left corner) pixel of a tile. /// /// Added in OIIO 3.1, this is the "safe" preferred alternative to /// the version of write_image that takes raw pointers. @@ -2379,19 +2430,29 @@ class OIIO_API ImageOutput { /// @param ybegin/yend The y range of the pixels being passed. /// @param zbegin/zend The z range of the pixels being passed /// (for a 2D image, zbegin=0 and zend=1). - /// @param data An `image_span` describing the buffer and + /// @param format A TypeDesc describing the type of the pixel data + /// that `data`'s memory contains. Use `TypeUnknown` + /// to indicate that the data is already in the native + /// format and needs no type conversion. + /// @param data An `image_span` describing the buffer and /// including its sizes and byte strides for each - /// dimension (channel, x, y, z), and the data - /// type `T`. + /// dimension (channel, x, y, z). /// @returns `true` upon success, or `false` upon failure. /// + virtual bool write_tile(int x, int y, int z, TypeDesc format, + image_span data); + + /// A version of `write_tile()` taking an `image_span`, where the type + /// of the underlying data is `T`. This is a convenience wrapper around + /// the `write_tile()` that takes an `image_span`. template bool write_tile(int x, int y, int z, image_span data) { return write_tile(x, y, z, TypeDescFromC::value(), as_image_span_bytes(data)); } - /// A version of `write_tile()` taking a cspan, which assumes + + /// A version of `write_tile()` taking a `cspan`, which assumes /// contiguous strides in all dimensions. This is a convenience wrapper /// around the `write_tile()` that takes an `image_span`. template bool write_tile(int x, int y, int z, span data) @@ -2403,25 +2464,16 @@ class OIIO_API ImageOutput { && ispan.is_contiguous()); return write_tile(x, y, z, ispan); } - /// Base/explicit case of write_tile: Given an explicit data type and - /// an `image_span` of untyped bytes, write the tile of pixels from - /// the buffer. - /// - /// @param format A TypeDesc describing the type of the pixel data - /// that `data`'s memory contains. Use `TypeUnknown` - /// to indicate that the data is already in the native - /// format and needs no type conversion. - /// - virtual bool write_tile(int x, int y, int z, TypeDesc format, - image_span data); - /// Write a rectangular regions of tiles of pixels given by the range + /// Write a rectangular region of tiles of pixels given by the range /// /// [xbegin,xend) X [ybegin,yend) X [zbegin,zend) /// - /// from a buffer described by `data`, which is an `image_span` - /// incorporating its bounded dimensions, strides, and data type. - /// The begin/end coordinates must be at tile or image boundaries. + /// contained in `data`, which is an `image_span` of un-typed bytes + /// incorporating its bounded dimensions and strides. The data type is + /// given explicity by the `format` argument, and will be automatically + /// converted to the type being stored in the file. The begin/end + /// coordinates must be at tile or image boundaries. /// /// Added in OIIO 3.1, this is the "safe" preferred alternative to /// the version of write_image that takes raw pointers. @@ -2430,12 +2482,22 @@ class OIIO_API ImageOutput { /// @param ybegin/yend The y range of the pixels being passed. /// @param zbegin/zend The z range of the pixels being passed /// (for a 2D image, zbegin=0 and zend=1). - /// @param data An `image_span` describing the buffer and + /// @param format A TypeDesc describing the type of the pixel data + /// that `data`'s memory contains. Use `TypeUnknown` + /// to indicate that the data is already in the native + /// format and needs no type conversion. + /// @param data An `image_span` describing the buffer and /// including its sizes and byte strides for each - /// dimension (channel, x, y, z), and the data - /// type `T`. + /// dimension (channel, x, y, z). /// @returns `true` upon success, or `false` upon failure. /// + virtual bool write_tiles(int xbegin, int xend, int ybegin, int yend, + int zbegin, int zend, TypeDesc format, + image_span data); + + /// A version of `write_tiles()` taking an `image_span`, where the type + /// of the underlying data is `T`. This is a convenience wrapper around + /// the `write_tiles()` that takes an `image_span`. template bool write_tiles(int xbegin, int xend, int ybegin, int yend, int zbegin, int zend, image_span data) @@ -2444,7 +2506,8 @@ class OIIO_API ImageOutput { TypeDescFromC::value(), as_image_span_bytes(data)); } - /// A version of `write_tiles()` taking a cspan, which assumes + + /// A version of `write_tiles()` taking a `cspan`, which assumes /// contiguous strides in all dimensions. This is a convenience wrapper /// around the `write_tiles()` that takes an `image_span`. template @@ -2457,86 +2520,6 @@ class OIIO_API ImageOutput { && ispan.is_contiguous()); return write_tiles(xbegin, xend, ybegin, yend, zbegin, zend, ispan); } - /// Base/explicit case of write_tiles: Given an explicit data type and - /// an `image_span` of untyped bytes, write the tiles of pixels from - /// the buffer. - /// - /// @param format A TypeDesc describing the type of the pixel data - /// that `data`'s memory contains. Use `TypeUnknown` - /// to indicate that the data is already in the native - /// format and needs no type conversion. - /// - virtual bool write_tiles(int xbegin, int xend, int ybegin, int yend, - int zbegin, int zend, TypeDesc format, - image_span data); - - /// Write the entire image of `spec.width x spec.height x spec.depth` - /// pixels, from a buffer described by `data`, which is an `image_span` - /// incorporating its bounded dimensions, strides, and data type. - /// - /// Depending on the spec, this will write either all tiles or all - /// scanlines. Assume that data points to a layout in row-major order. - /// - /// Added in OIIO 3.1, this is the "safe" preferred alternative to - /// the version of write_image that takes raw pointers. - /// - /// @param data An `image_span` describing the buffer and - /// including its sizes and byte strides for each - /// dimension (x, y, z, and channel), and the data - /// type `T`. - /// @returns `true` upon success, or `false` upon failure. - template bool write_image(image_span data) - { - return write_image(TypeDescFromC::value(), - as_image_span_bytes(data)); - } - - /// Write the entire image of `spec.width x spec.height x spec.depth` - /// pixels, from a buffer described by `data`, which is an `cspan` - /// assuming it points to a contiguously laid out buffer. - /// - /// Depending on the spec, this will write either all tiles or all - /// scanlines. Assume that data points to a layout in row-major order. - /// - /// Added in OIIO 3.1, this is the "safe" preferred alternative to - /// the version of write_image that takes raw pointers. - /// - /// @param data An `span` describing the bounds of - /// the buffer containing contiguously laid out - /// `T` data for the entire image. - /// @returns `true` upon success, or `false` upon failure. - template bool write_image(span data) - { - // auto isize = m_spec.image_bytes(TypeDescFromC::value()); - auto ispan = image_span(data.data(), m_spec.nchannels, - m_spec.width, m_spec.height, - m_spec.depth); - OIIO_DASSERT(data.size_bytes() == ispan.size_bytes() - && ispan.is_contiguous()); - return write_image(ispan); - } - - /// Base/explicit case of write_image: Given an explicit data type and an - /// `image_span` of untyped bytes, write the entire image of `spec.width x - /// spec.height x spec.depth` pixels from the buffer. - /// - /// Depending on the spec, this will write either all tiles or all - /// scanlines. Assume that data points to a layout in row-major order. - /// - /// Added in OIIO 3.1, this is the "safe" preferred alternative to - /// the version of write_image that takes raw pointers. - /// - /// @param format A TypeDesc describing the type of the pixel data - /// that `data`'s memory contains. Use `TypeUnknown` - /// to indicate that the data is already in the native - /// format and needs no type conversion. - /// @param data An `image_span` describing the memory extent - /// of the data buffer and including the sizes and - /// and byte strides for each dimension (channel, x, - /// y, and z). - /// @returns `true` upon success, or `false` upon failure. - /// - virtual bool write_image(TypeDesc format, image_span data); /// Write a rectangle of pixels given by the range /// @@ -2545,21 +2528,32 @@ class OIIO_API ImageOutput { /// from a buffer described by `data`, which is an `image_span` /// incorporating its bounded dimensions, strides, and data type. /// + /// The begin/end coordinates do not need to be tile boundaries, but the + /// call is only supported for format plugins that return true for + /// `supports("rectangles")`. + /// /// Added in OIIO 3.1, this is the "safe" preferred alternative to /// the version of write_image that takes raw pointers. /// /// @param xbegin/xend The x range of the pixels being passed. /// @param ybegin/yend The y range of the pixels being passed. - /// @param zbegin/zend The z range of the pixels being passed - /// (for a 2D image, zbegin=0 and zend=1). - /// @param data An `image_span` describing the buffer and + /// @param format A TypeDesc describing the type of the pixel data + /// that `data`'s memory contains. Use `TypeUnknown` + /// to indicate that the data is already in the native + /// format and needs no type conversion. + /// @param data An `image_span` describing the buffer and /// including its sizes and byte strides for each - /// dimension (channel, x, y, z), and the data - /// type `T`. + /// dimension (channel, x, y, z). /// @returns `true` upon success, or `false` upon failure. /// - /// @note The call will fail for a format plugin that does not return - /// true for `supports("rectangles")`. + virtual bool write_rectangle(int xbegin, int xend, int ybegin, int yend, + int zbegin, int zend, TypeDesc format, + image_span data); + + /// A version of `write_rectangle()` taking an `image_span`, where the + /// type of the underlying data is `T`. This is a convenience wrapper + /// around the `write_rectangle()` that takes an `image_span`. template bool write_rectangle(int xbegin, int xend, int ybegin, int yend, int zbegin, int zend, image_span data) @@ -2569,7 +2563,7 @@ class OIIO_API ImageOutput { as_image_span_bytes(data)); } - /// A version of `write_rectangle()` taking a cspan, which assumes + /// A version of `write_rectangle()` taking a `cspan`, which assumes /// contiguous strides in all dimensions. This is a convenience wrapper /// around the `write_rectangle()` that takes an `image_span`. template @@ -2584,19 +2578,6 @@ class OIIO_API ImageOutput { return write_rectangle(xbegin, xend, ybegin, yend, zbegin, zend, ispan); } - /// Base/explicit case of write_rectangle: Given an explicit data type and - /// an `image_span` of untyped bytes, write the rectangle of pixels from - /// the buffer. - /// - /// @param format A TypeDesc describing the type of the pixel data - /// that `data`'s memory contains. Use `TypeUnknown` - /// to indicate that the data is already in the native - /// format and needs no type conversion. - /// - virtual bool write_rectangle(int xbegin, int xend, int ybegin, int yend, - int zbegin, int zend, TypeDesc format, - image_span data); - // clang-format off /// Write deep scanlines containing pixels (*,y,z), for all y in the From 25a01f20458899257207f2872946c3bd2abf355d Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Thu, 8 May 2025 10:26:06 -0700 Subject: [PATCH 4/9] Typo fixes in docs and one more size safety check in write_image Signed-off-by: Larry Gritz --- src/include/OpenImageIO/imageio.h | 16 +++++++++------- src/libOpenImageIO/imageoutput.cpp | 8 ++++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/include/OpenImageIO/imageio.h b/src/include/OpenImageIO/imageio.h index 844e72e0e1..0c09956ef8 100644 --- a/src/include/OpenImageIO/imageio.h +++ b/src/include/OpenImageIO/imageio.h @@ -2246,14 +2246,16 @@ class OIIO_API ImageOutput { /// /// * There is a base case that takes a `image_span` describing /// untyped memory layout and a `TypeDesc` describing the data type - /// that the values should be converted to (or TypeUnknown to keep - /// the data in its "native" file types with no conversion). + /// that the values should be converted from (or TypeUnknown to + /// indicate that the data is already in its "native" file types with + /// no conversion needed). /// - /// * The type-aware versions that accept an `image_span` and - /// understand to convert the data into the approprate `T` type. + /// * The type-aware versions that accept an `image_span` (for + /// optionally non-contiguous data) or `span` (for contiguous data) + /// and understand to convert the data from the given `T` type. /// /// * The image_span (in either case) includes the memory bounds and - /// stride lengths (in bytes) between channels, scanlines, and + /// stride lengths (in bytes) between channels, pixels, scanlines, and /// volumetric slices. /// /// * Any *range* parameters (such as `ybegin` and `yend`) describe a @@ -2288,8 +2290,8 @@ class OIIO_API ImageOutput { /// that `data`'s memory contains. Use `TypeUnknown` /// to indicate that the data is already in the native /// format and needs no type conversion. - /// @param data An `image_span` describing the memory extent - /// of the data buffer and including the sizes and + /// @param data An `image_span` describing the memory + /// extent of the data buffer and including the sizes /// and byte strides for each dimension (channel, x, /// y, and z). /// @returns `true` upon success, or `false` upon failure. diff --git a/src/libOpenImageIO/imageoutput.cpp b/src/libOpenImageIO/imageoutput.cpp index 0d4348e79c..0a1241047a 100644 --- a/src/libOpenImageIO/imageoutput.cpp +++ b/src/libOpenImageIO/imageoutput.cpp @@ -684,6 +684,14 @@ ImageOutput::write_image(TypeDesc format, const void* data, stride_t xstride, bool ImageOutput::write_image(TypeDesc format, image_span data) { + size_t sz = m_spec.image_bytes(/*native=*/ format == TypeUnknown); + if (sz != data.size_bytes()) { + errorfmt( + "write_image: Buffer size is incorrect ({} bytes vs {} needed)", + sz, data.size_bytes()); + return false; + } + // Default implementation (for now): call the old pointer+stride return write_image(format, data.data(), data.xstride(), data.ystride(), data.zstride()); From cc12d98673af6972a532e2cb25d68d577e9a9b58 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Thu, 8 May 2025 20:43:21 -0700 Subject: [PATCH 5/9] fix formatting Signed-off-by: Larry Gritz --- src/libOpenImageIO/imageoutput.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libOpenImageIO/imageoutput.cpp b/src/libOpenImageIO/imageoutput.cpp index 0a1241047a..87194609a7 100644 --- a/src/libOpenImageIO/imageoutput.cpp +++ b/src/libOpenImageIO/imageoutput.cpp @@ -684,11 +684,10 @@ ImageOutput::write_image(TypeDesc format, const void* data, stride_t xstride, bool ImageOutput::write_image(TypeDesc format, image_span data) { - size_t sz = m_spec.image_bytes(/*native=*/ format == TypeUnknown); + size_t sz = m_spec.image_bytes(/*native=*/format == TypeUnknown); if (sz != data.size_bytes()) { - errorfmt( - "write_image: Buffer size is incorrect ({} bytes vs {} needed)", - sz, data.size_bytes()); + errorfmt("write_image: Buffer size is incorrect ({} bytes vs {} needed)", + sz, data.size_bytes()); return false; } From 098ff8d68d4a525f8a68df27c662278d7ea6995e Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Fri, 9 May 2025 13:08:26 -0700 Subject: [PATCH 6/9] Add to image_span_test.cpp benchmarks of pass by image_span val vs ref Signed-off-by: Larry Gritz --- src/libOpenImageIO/image_span_test.cpp | 120 +++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/src/libOpenImageIO/image_span_test.cpp b/src/libOpenImageIO/image_span_test.cpp index 643e6fc7da..a06b453600 100644 --- a/src/libOpenImageIO/image_span_test.cpp +++ b/src/libOpenImageIO/image_span_test.cpp @@ -349,6 +349,124 @@ test_image_span_convert_image() +// Sum all values in an image using a pass-by-value image_span +float +sum_image_span_val(image_span img) +{ + float sum = 0; + for (uint32_t z = 0; z < img.depth(); ++z) { + for (uint32_t y = 0; y < img.height(); ++y) { + for (uint32_t x = 0; x < img.width(); ++x) { + for (uint32_t c = 0; c < img.nchannels(); ++c) { + sum += img.get(c, x, y, z); + } + } + } + } + return sum; +} + + +// Sum all values in an image using a pass-by-reference image_span +float +sum_image_span_ref(const image_span& img) +{ + float sum = 0; + for (uint32_t z = 0; z < img.depth(); ++z) { + for (uint32_t y = 0; y < img.height(); ++y) { + for (uint32_t x = 0; x < img.width(); ++x) { + for (uint32_t c = 0; c < img.nchannels(); ++c) { + sum += img.get(c, x, y, z); + } + } + } + } + return sum; +} + + +// Sum all values in an image using raw pointers, sizes, strides +float +sum_image_span_ptr(const float* ptr, uint32_t chans, uint32_t width, + uint32_t height, uint32_t depth, int64_t chstride, + int64_t xstride, int64_t ystride, int64_t zstride) +{ + float sum = 0; + for (uint32_t z = 0; z < depth; ++z) { + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + for (uint32_t c = 0; c < chans; ++c) { + const float* p = reinterpret_cast( + (const char*)ptr + c * chstride + x * xstride + + y * ystride + z * zstride); + sum += *p; + } + } + } + } + return sum; +} + + + +void +benchmark_image_span_passing() +{ + print("\nbenchmark_image_span_passing\n"); + const int xres = 2048, yres = 1536, nchans = 4; + std::vector sbuf(xres * yres * nchans, 1.0f); + image_span ispan(sbuf.data(), nchans, xres, yres, 1); + + Benchmarker bench; + bench.units(Benchmarker::Unit::us); + float sum = 0.0f; + + bench(" pass by value (big)", + [=, &sum]() { sum += sum_image_span_val(ispan); }); + bench(" pass by value imm (big)", [=, &sum]() { + sum += sum_image_span_val( + image_span(sbuf.data(), nchans, xres, yres, 1)); + }); + bench(" pass by ref (big)", + [=, &sum]() { sum += sum_image_span_ref(ispan); }); + bench(" pass by ref imm (big)", [=, &sum]() { + sum += sum_image_span_ref( + image_span(sbuf.data(), nchans, xres, yres, 1)); + }); + bench(" pass by ptr (big)", [=, &sum]() { + sum += sum_image_span_ptr(sbuf.data(), nchans, xres, yres, 1, + sizeof(float), nchans * sizeof(float), + nchans * sizeof(float) * xres, + nchans * sizeof(float) * xres * yres); + }); + + // Do it all again for a SMALL image + bench.units(Benchmarker::Unit::ns); + int small = 16; + image_span smispan(sbuf.data(), nchans, small, small, 1); + bench(" pass by value (small)", + [=, &sum]() { sum += sum_image_span_val(smispan); }); + bench(" pass by value imm (small)", [=, &sum]() { + sum += sum_image_span_val( + image_span(sbuf.data(), nchans, small, small, 1)); + }); + bench(" pass by ref (small)", + [=, &sum]() { sum += sum_image_span_ref(smispan); }); + bench(" pass by ref imm (small)", [=, &sum]() { + sum += sum_image_span_ref( + image_span(sbuf.data(), nchans, small, small, 1)); + }); + bench(" pass by ptr (small)", [=, &sum]() { + sum += sum_image_span_ptr(sbuf.data(), nchans, small, small, 1, + sizeof(float), nchans * sizeof(float), + nchans * sizeof(float) * small, + nchans * sizeof(float) * small * small); + }); + print(" [sum={}]\n", sum); // seems necessary to not optimize away +} + + + int main(int /*argc*/, char* /*argv*/[]) { @@ -378,5 +496,7 @@ main(int /*argc*/, char* /*argv*/[]) test_image_span_convert_image(); test_image_span_convert_image(); + benchmark_image_span_passing(); + return unit_test_failures; } From 0c4d8d3b887695e41730e32f1a55ca729fbc7e82 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Fri, 9 May 2025 21:53:22 -0700 Subject: [PATCH 7/9] Change calling convention to const ref Signed-off-by: Larry Gritz --- src/include/OpenImageIO/image_span.h | 4 +-- src/include/OpenImageIO/imageio.h | 52 +++++++++++++++------------- src/include/imageio_pvt.h | 2 +- src/libOpenImageIO/imageio.cpp | 8 +++-- src/libOpenImageIO/imageoutput.cpp | 15 ++++---- 5 files changed, 44 insertions(+), 37 deletions(-) diff --git a/src/include/OpenImageIO/image_span.h b/src/include/OpenImageIO/image_span.h index 374b37c704..90c3112f40 100644 --- a/src/include/OpenImageIO/image_span.h +++ b/src/include/OpenImageIO/image_span.h @@ -365,7 +365,7 @@ template using image1d_span = image_span; /// covering the same range of memory. template image_span -as_image_span_bytes(image_span src) noexcept +as_image_span_bytes(const image_span& src) noexcept { return image_span( reinterpret_cast(src.data()), src.nchannels(), @@ -378,7 +378,7 @@ as_image_span_bytes(image_span src) noexcept /// the same range of memory. template image_span -as_image_span_writable_bytes(image_span src) noexcept +as_image_span_writable_bytes(const image_span& src) noexcept { return image_span(reinterpret_cast(src.data()), src.nchannels(), src.width(), src.height(), diff --git a/src/include/OpenImageIO/imageio.h b/src/include/OpenImageIO/imageio.h index 0c09956ef8..93023f9505 100644 --- a/src/include/OpenImageIO/imageio.h +++ b/src/include/OpenImageIO/imageio.h @@ -2296,12 +2296,13 @@ class OIIO_API ImageOutput { /// y, and z). /// @returns `true` upon success, or `false` upon failure. /// - virtual bool write_image(TypeDesc format, image_span data); + virtual bool write_image(TypeDesc format, + const image_span& data); /// A version of `write_image()` taking an `image_span`, where the type /// of the underlying data is `T`. This is a convenience wrapper around /// the `write_image()` that takes an `image_span`. - template bool write_image(image_span data) + template bool write_image(const image_span& data) { return write_image(TypeDescFromC::value(), as_image_span_bytes(data)); @@ -2340,13 +2341,14 @@ class OIIO_API ImageOutput { /// @returns `true` upon success, or `false` upon failure. /// virtual bool write_scanline(int y, int z, TypeDesc format, - image_span data); + const image_span& data); /// A version of `write_scanline()` taking an `image_span`, where the /// type of the underlying data is `T`. This is a convenience wrapper /// around the `write_scanline()` that takes an `image_span`. - template bool write_scanline(int y, int z, image_span data) + template + bool write_scanline(int y, int z, const image_span& data) { // reduce to type + image_span return write_scanline(y, z, TypeDescFromC::value(), @@ -2389,14 +2391,14 @@ class OIIO_API ImageOutput { /// @returns `true` upon success, or `false` upon failure. /// virtual bool write_scanlines(int ybegin, int yend, int z, TypeDesc format, - image_span data); + const image_span& data); /// A version of `write_scanlines()` taking an `image_span`, where the /// type of the underlying data is `T`. This is a convenience wrapper /// around the `write_scanlines()` that takes an `image_span`. template - bool write_scanlines(int ybegin, int yend, int z, image_span data) + bool write_scanlines(int ybegin, int yend, int z, const image_span& data) { // image_span: reduces to type + byte_buffer return write_scanlines(ybegin, yend, z, TypeDescFromC::value(), @@ -2442,13 +2444,13 @@ class OIIO_API ImageOutput { /// @returns `true` upon success, or `false` upon failure. /// virtual bool write_tile(int x, int y, int z, TypeDesc format, - image_span data); + const image_span& data); /// A version of `write_tile()` taking an `image_span`, where the type /// of the underlying data is `T`. This is a convenience wrapper around /// the `write_tile()` that takes an `image_span`. template - bool write_tile(int x, int y, int z, image_span data) + bool write_tile(int x, int y, int z, const image_span& data) { return write_tile(x, y, z, TypeDescFromC::value(), as_image_span_bytes(data)); @@ -2495,14 +2497,14 @@ class OIIO_API ImageOutput { /// virtual bool write_tiles(int xbegin, int xend, int ybegin, int yend, int zbegin, int zend, TypeDesc format, - image_span data); + const image_span& data); /// A version of `write_tiles()` taking an `image_span`, where the type /// of the underlying data is `T`. This is a convenience wrapper around /// the `write_tiles()` that takes an `image_span`. template bool write_tiles(int xbegin, int xend, int ybegin, int yend, int zbegin, - int zend, image_span data) + int zend, const image_span& data) { return write_tiles(xbegin, xend, ybegin, yend, zbegin, zend, TypeDescFromC::value(), @@ -2550,7 +2552,7 @@ class OIIO_API ImageOutput { /// virtual bool write_rectangle(int xbegin, int xend, int ybegin, int yend, int zbegin, int zend, TypeDesc format, - image_span data); + const image_span& data); /// A version of `write_rectangle()` taking an `image_span`, where the /// type of the underlying data is `T`. This is a convenience wrapper @@ -2558,7 +2560,7 @@ class OIIO_API ImageOutput { /// std::byte>`. template bool write_rectangle(int xbegin, int xend, int ybegin, int yend, int zbegin, - int zend, image_span data) + int zend, const image_span& data) { return write_rectangle(xbegin, xend, ybegin, yend, zbegin, zend, TypeDescFromC::value(), @@ -3141,7 +3143,7 @@ class OIIO_API ImageOutput { /// the pixels to the right position in the dither pattern. template cspan to_native(int xbegin, int xend, int ybegin, int yend, - int zbegin, int zend, image_span data, + int zbegin, int zend, const image_span& data, std::vector& scratch, unsigned int dither = 0, int xorigin = 0, int yorigin = 0, int zorigin = 0) @@ -3158,7 +3160,7 @@ class OIIO_API ImageOutput { /// `format` and an image_span of generic (std::byte) data. cspan to_native(int xbegin, int xend, int ybegin, int yend, int zbegin, int zend, TypeDesc format, - image_span data, + const image_span& data, std::vector& scratch, unsigned int dither = 0, int xorigin = 0, int yorigin = 0, int zorigin = 0); @@ -3856,7 +3858,7 @@ OIIO_API bool convert_image (int nchannels, int width, int height, int depth, /// Return true if ok, false if it didn't know how to do the conversion. template bool -convert_image(image_span src, image_span dst) +convert_image(const image_span& src, const image_span& dst) { // For now, just implement by wrapping the pointer-based version. OIIO_DASSERT(src.nchannels() == dst.nchannels() @@ -3876,8 +3878,8 @@ convert_image(image_span src, image_span dst) /// `TypeDesc`, and the spans are untyped bytes that provide the dimensions /// and memory layout. inline bool -convert_image(image_span src, TypeDesc src_type, - image_span dst, TypeDesc dst_type) +convert_image(const image_span& src, TypeDesc src_type, + const image_span& dst, TypeDesc dst_type) { // For now, just implement by wrapping the pointer-based version. OIIO_DASSERT(src.nchannels() == dst.nchannels() @@ -3908,8 +3910,8 @@ OIIO_API bool parallel_convert_image ( /// threads. The data types are taken from the spans. template bool -parallel_convert_image(image_span src, image_span dst, - int nthreads = 0) +parallel_convert_image(const image_span& src, + const image_span& dst, int nthreads = 0) { // For now, just implement by wrapping the pointer-based version. OIIO_DASSERT(src.nchannels() == dst.nchannels() @@ -3927,9 +3929,9 @@ parallel_convert_image(image_span src, image_span dst, /// threads. The data types are passed as `TypeDesc`, and the spans are /// untyped bytes that provide the dimensions and memory layout. inline bool -parallel_convert_image(image_span src, TypeDesc src_type, - image_span dst, TypeDesc dst_type, - int nthreads = 0) +parallel_convert_image(const image_span& src, + TypeDesc src_type, const image_span& dst, + TypeDesc dst_type, int nthreads = 0) { // For now, just implement by wrapping the pointer-based version. OIIO_DASSERT(src.nchannels() == dst.nchannels() @@ -3995,7 +3997,8 @@ OIIO_API bool copy_image (int nchannels, int width, int height, int depth, /// strides. Return true if ok, false if it couldn't do it. (Reserved for /// future use; currently is always succeeds) template -bool copy_image(image_span dst, image_span src) +bool copy_image(const image_span& dst, + const image_span& src) { // Arbitrary types are handled by just converting to generic byte // image_spans. @@ -4005,7 +4008,8 @@ bool copy_image(image_span dst, image_span src) /// copy_image base case: generic span of bytes. OIIO_API bool -copy_image(image_span dst, image_span src); +copy_image(const image_span &dst, + const image_span& src); diff --git a/src/include/imageio_pvt.h b/src/include/imageio_pvt.h index 84cb7c1f35..ae28898fa3 100644 --- a/src/include/imageio_pvt.h +++ b/src/include/imageio_pvt.h @@ -89,7 +89,7 @@ get_default_quantize(TypeDesc format, long long& quant_min, /// which is either dst or src (if the strides indicated that data were /// already contiguous). OIIO_API span -contiguize(image_span src, span dst); +contiguize(const image_span& src, span dst); /// Turn potentially non-contiguous-stride data (e.g. "RGBxRGBx") into /// contiguous-stride ("RGBRGB"), for any format or stride values diff --git a/src/libOpenImageIO/imageio.cpp b/src/libOpenImageIO/imageio.cpp index 0b1dcc610d..e49939d9fb 100644 --- a/src/libOpenImageIO/imageio.cpp +++ b/src/libOpenImageIO/imageio.cpp @@ -734,7 +734,7 @@ _contiguize(const T* src, int nchannels, stride_t xstride, stride_t ystride, span -pvt::contiguize(image_span src, span dst) +pvt::contiguize(const image_span& src, span dst) { // Contiguized result must fit in dst OIIO_DASSERT(src.size_bytes() <= dst.size_bytes()); @@ -1053,7 +1053,8 @@ copy_image(int nchannels, int width, int height, int depth, const void* src, template void -aligned_copy_image(image_span dst, image_span src) +aligned_copy_image(const image_span& dst, + const image_span& src) { size_t systride = src.ystride(); size_t dystride = dst.ystride(); @@ -1081,7 +1082,8 @@ aligned_copy_image(image_span dst, image_span src) bool -copy_image(image_span dst, image_span src) +copy_image(const image_span& dst, + const image_span& src) { OIIO_DASSERT(src.width() == dst.width() && src.height() == dst.height() && src.depth() == dst.depth() diff --git a/src/libOpenImageIO/imageoutput.cpp b/src/libOpenImageIO/imageoutput.cpp index 87194609a7..aaa357a853 100644 --- a/src/libOpenImageIO/imageoutput.cpp +++ b/src/libOpenImageIO/imageoutput.cpp @@ -112,7 +112,7 @@ ImageOutput::write_scanline(int /*y*/, int /*z*/, TypeDesc /*format*/, bool ImageOutput::write_scanline(int y, int z, TypeDesc format, - image_span data) + const image_span& data) { size_t sz = (format == TypeUnknown ? m_spec.pixel_bytes(true /*native*/) : format.size() * m_spec.nchannels) @@ -154,7 +154,7 @@ ImageOutput::write_scanlines(int ybegin, int yend, int z, TypeDesc format, bool ImageOutput::write_scanlines(int ybegin, int yend, int z, TypeDesc format, - image_span data) + const image_span& data) { size_t sz = (format == TypeUnknown ? m_spec.pixel_bytes(true /*native*/) : format.size() * m_spec.nchannels) @@ -186,7 +186,7 @@ ImageOutput::write_tile(int /*x*/, int /*y*/, int /*z*/, TypeDesc /*format*/, bool ImageOutput::write_tile(int x, int y, int z, TypeDesc format, - image_span data) + const image_span& data) { size_t sz = format == TypeUnknown ? m_spec.pixel_bytes(true /*native*/) @@ -267,7 +267,7 @@ ImageOutput::write_tiles(int xbegin, int xend, int ybegin, int yend, int zbegin, bool ImageOutput::write_tiles(int xbegin, int xend, int ybegin, int yend, int zbegin, int zend, TypeDesc format, - image_span data) + const image_span& data) { // Default implementation (for now): call the old pointer+stride return write_tiles(xbegin, xend, ybegin, yend, zbegin, zend, format, @@ -293,7 +293,7 @@ bool ImageOutput::write_rectangle(int /*xbegin*/, int /*xend*/, int /*ybegin*/, int /*yend*/, int /*zbegin*/, int /*zend*/, TypeDesc /*format*/, - image_span /*data*/) + const image_span& /*data*/) { return false; } @@ -570,7 +570,7 @@ ImageOutput::to_native_rectangle(int xbegin, int xend, int ybegin, int yend, cspan ImageOutput::to_native(int xbegin, int xend, int ybegin, int yend, int zbegin, int zend, TypeDesc format, - image_span data, + const image_span& data, std::vector& scratch, unsigned int dither, int xorigin, int yorigin, int zorigin) { @@ -682,7 +682,8 @@ ImageOutput::write_image(TypeDesc format, const void* data, stride_t xstride, bool -ImageOutput::write_image(TypeDesc format, image_span data) +ImageOutput::write_image(TypeDesc format, + const image_span& data) { size_t sz = m_spec.image_bytes(/*native=*/format == TypeUnknown); if (sz != data.size_bytes()) { From 6dfbf5be0af437f3bbe57b07cbfb1ccb0fe1f1a0 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 10 May 2025 09:20:02 -0700 Subject: [PATCH 8/9] Amendment: get rid of z parameter to write_scanline Signed-off-by: Larry Gritz --- src/include/OpenImageIO/imageio.h | 34 ++++++++----------- src/libOpenImageIO/imageoutput.cpp | 8 ++--- .../src/docs-examples-imageoutput.cpp | 3 +- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/include/OpenImageIO/imageio.h b/src/include/OpenImageIO/imageio.h index 93023f9505..70fbfa3544 100644 --- a/src/include/OpenImageIO/imageio.h +++ b/src/include/OpenImageIO/imageio.h @@ -2321,8 +2321,8 @@ class OIIO_API ImageOutput { return write_image(ispan); } - /// Write one scanline that include pixels (*,y,z) from `data`, which is - /// an `image_span` of un-typed bytes incorporating its bounded dimensions + /// Write one scanline that include pixels (*,y) from `data`, which is an + /// `image_span` of un-typed bytes incorporating its bounded dimensions /// and strides. The data type is given explicity by the `format` /// argument, and will be automatically converted to the type being stored /// in the file. @@ -2330,7 +2330,7 @@ class OIIO_API ImageOutput { /// The image_span must have a width equal to a full scanline width, /// and its height and depth must be 1. /// - /// @param y/z The y & z coordinates of the scanline. + /// @param y The y coordinate of the scanline. /// @param format A TypeDesc describing the type of the pixel data /// that `data`'s memory contains. Use `TypeUnknown` /// to indicate that the data is already in the native @@ -2340,34 +2340,31 @@ class OIIO_API ImageOutput { /// dimension (channel, x, y, z). /// @returns `true` upon success, or `false` upon failure. /// - virtual bool write_scanline(int y, int z, TypeDesc format, + virtual bool write_scanline(int y, TypeDesc format, const image_span& data); /// A version of `write_scanline()` taking an `image_span`, where the /// type of the underlying data is `T`. This is a convenience wrapper /// around the `write_scanline()` that takes an `image_span`. - template - bool write_scanline(int y, int z, const image_span& data) + template bool write_scanline(int y, const image_span& data) { // reduce to type + image_span - return write_scanline(y, z, TypeDescFromC::value(), + return write_scanline(y, TypeDescFromC::value(), as_image_span_bytes(data)); } /// A version of `write_scanline()` taking a `cspan`, which assumes /// contiguous strides in all dimensions. This is a convenience wrapper /// around the `write_scanline()` that takes an `image_span`. - template bool write_scanline(int y, int z, span data) + template bool write_scanline(int y, span data) { // reduce to type + image_span - auto isize = m_spec.image_bytes(TypeDescFromC::value()); - return write_scanline(y, z, - image_span(data.data(), m_spec.nchannels, - m_spec.width, 1, 1)); + return write_scanline(y, image_span(data.data(), m_spec.nchannels, + m_spec.width, 1, 1)); } - /// Write multiple scanlines that include pixels (*,y,z) for all `ybegin + /// Write multiple scanlines that include pixels (*,y) for all `ybegin /// <= y < yend`, from `data`, which is an `image_span` of un-typed bytes /// incorporating its bounded dimensions and strides. The data type is /// given explicity by the `format` argument, and will be automatically @@ -2380,7 +2377,6 @@ class OIIO_API ImageOutput { /// and its height must be yend - ybegin. /// /// @param ybegin/yend The y range of the scanlines being passed. - /// @param z The z coordinate of the scanline. /// @param format A TypeDesc describing the type of the pixel data /// that `data`'s memory contains. Use `TypeUnknown` /// to indicate that the data is already in the native @@ -2390,7 +2386,7 @@ class OIIO_API ImageOutput { /// dimension (channel, x, y, z). /// @returns `true` upon success, or `false` upon failure. /// - virtual bool write_scanlines(int ybegin, int yend, int z, TypeDesc format, + virtual bool write_scanlines(int ybegin, int yend, TypeDesc format, const image_span& data); /// A version of `write_scanlines()` taking an `image_span`, where the @@ -2398,10 +2394,10 @@ class OIIO_API ImageOutput { /// around the `write_scanlines()` that takes an `image_span`. template - bool write_scanlines(int ybegin, int yend, int z, const image_span& data) + bool write_scanlines(int ybegin, int yend, const image_span& data) { // image_span: reduces to type + byte_buffer - return write_scanlines(ybegin, yend, z, TypeDescFromC::value(), + return write_scanlines(ybegin, yend, TypeDescFromC::value(), data.as_image_span_bytes()); } @@ -2409,13 +2405,13 @@ class OIIO_API ImageOutput { /// contiguous strides in all dimensions. This is a convenience wrapper /// around the `write_scanlines()` that takes an `image_span`. template - bool write_scanlines(int ybegin, int yend, int z, span data) + bool write_scanlines(int ybegin, int yend, span data) { auto ispan = image_span(data.data(), m_spec.nchannels, m_spec.width, yend - ybegin, 1); OIIO_DASSERT(data.size_bytes() == ispan.size_bytes() && ispan.is_contiguous()); - return write_scanlines(ybegin, yend, z, ispan); + return write_scanlines(ybegin, yend, ispan); } diff --git a/src/libOpenImageIO/imageoutput.cpp b/src/libOpenImageIO/imageoutput.cpp index aaa357a853..a5319923f3 100644 --- a/src/libOpenImageIO/imageoutput.cpp +++ b/src/libOpenImageIO/imageoutput.cpp @@ -111,7 +111,7 @@ ImageOutput::write_scanline(int /*y*/, int /*z*/, TypeDesc /*format*/, bool -ImageOutput::write_scanline(int y, int z, TypeDesc format, +ImageOutput::write_scanline(int y, TypeDesc format, const image_span& data) { size_t sz = (format == TypeUnknown ? m_spec.pixel_bytes(true /*native*/) @@ -125,7 +125,7 @@ ImageOutput::write_scanline(int y, int z, TypeDesc format, } // Default implementation (for now): call the old pointer+stride - return write_scanline(y, z, format, data.data(), data.xstride()); + return write_scanline(y, 0, format, data.data(), data.xstride()); } @@ -153,7 +153,7 @@ ImageOutput::write_scanlines(int ybegin, int yend, int z, TypeDesc format, bool -ImageOutput::write_scanlines(int ybegin, int yend, int z, TypeDesc format, +ImageOutput::write_scanlines(int ybegin, int yend, TypeDesc format, const image_span& data) { size_t sz = (format == TypeUnknown ? m_spec.pixel_bytes(true /*native*/) @@ -167,7 +167,7 @@ ImageOutput::write_scanlines(int ybegin, int yend, int z, TypeDesc format, } // Default implementation (for now): call the old pointer+stride - return write_scanlines(ybegin, yend, z, format, data.data(), data.xstride(), + return write_scanlines(ybegin, yend, 0, format, data.data(), data.xstride(), data.ystride()); } diff --git a/testsuite/docs-examples-cpp/src/docs-examples-imageoutput.cpp b/testsuite/docs-examples-cpp/src/docs-examples-imageoutput.cpp index 5cb948bde0..825954e67b 100644 --- a/testsuite/docs-examples-cpp/src/docs-examples-imageoutput.cpp +++ b/testsuite/docs-examples-cpp/src/docs-examples-imageoutput.cpp @@ -70,10 +70,9 @@ scanlines_write() // BEGIN-imageoutput-scanlines std::vector scanline(xres * channels); out->open(filename, spec); - int z = 0; // Always zero for 2D images for (int y = 0; y < yres; ++y) { // ... generate data in scanline[0..xres*channels-1] ... - out->write_scanline(y, z, make_span(scanline)); + out->write_scanline(y, make_span(scanline)); } out->close(); // END-imageoutput-scanlines From 96c8fe4d8caaba1190ba7560926d8358440d54ff Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 10 May 2025 11:22:47 -0700 Subject: [PATCH 9/9] Improve comments and fix cut and paste errors Signed-off-by: Larry Gritz --- src/include/OpenImageIO/imageio.h | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/include/OpenImageIO/imageio.h b/src/include/OpenImageIO/imageio.h index 70fbfa3544..1255021f50 100644 --- a/src/include/OpenImageIO/imageio.h +++ b/src/include/OpenImageIO/imageio.h @@ -2330,6 +2330,9 @@ class OIIO_API ImageOutput { /// The image_span must have a width equal to a full scanline width, /// and its height and depth must be 1. /// + /// Added in OIIO 3.1, this is the "safe" preferred alternative to + /// the version of write_scanline that takes raw pointers. + /// /// @param y The y coordinate of the scanline. /// @param format A TypeDesc describing the type of the pixel data /// that `data`'s memory contains. Use `TypeUnknown` @@ -2376,6 +2379,9 @@ class OIIO_API ImageOutput { /// The image_span must have a width equal to a full scanline width, /// and its height must be yend - ybegin. /// + /// Added in OIIO 3.1, this is the "safe" preferred alternative to + /// the version of write_scanlines that takes raw pointers. + /// /// @param ybegin/yend The y range of the scanlines being passed. /// @param format A TypeDesc describing the type of the pixel data /// that `data`'s memory contains. Use `TypeUnknown` @@ -2424,7 +2430,7 @@ class OIIO_API ImageOutput { /// left corner) pixel of a tile. /// /// Added in OIIO 3.1, this is the "safe" preferred alternative to - /// the version of write_image that takes raw pointers. + /// the version of write_tile that takes raw pointers. /// /// @param x/y/z The x range of the pixels being passed. /// @param ybegin/yend The y range of the pixels being passed. @@ -2439,6 +2445,9 @@ class OIIO_API ImageOutput { /// dimension (channel, x, y, z). /// @returns `true` upon success, or `false` upon failure. /// + /// Added in OIIO 3.1, this is the "safe" preferred alternative to + /// the version of write_tile that takes raw pointers. + /// virtual bool write_tile(int x, int y, int z, TypeDesc format, const image_span& data); @@ -2476,7 +2485,7 @@ class OIIO_API ImageOutput { /// coordinates must be at tile or image boundaries. /// /// Added in OIIO 3.1, this is the "safe" preferred alternative to - /// the version of write_image that takes raw pointers. + /// the version of write_tiles that takes raw pointers. /// /// @param xbegin/xend The x range of the pixels being passed. /// @param ybegin/yend The y range of the pixels being passed. @@ -2533,7 +2542,7 @@ class OIIO_API ImageOutput { /// `supports("rectangles")`. /// /// Added in OIIO 3.1, this is the "safe" preferred alternative to - /// the version of write_image that takes raw pointers. + /// the version of write_rectangle that takes raw pointers. /// /// @param xbegin/xend The x range of the pixels being passed. /// @param ybegin/yend The y range of the pixels being passed.