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/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 5e5227113b..1255021f50 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,429 @@ 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 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` (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, pixels, 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). + /// + + /// 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 byte strides for each dimension (channel, x, + /// y, and z). + /// @returns `true` upon success, or `false` upon failure. + /// + 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(const 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) 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. + /// + /// 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` + /// 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, 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, const image_span& data) + { + // reduce to type + image_span + 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, span data) + { + // reduce to type + image_span + return write_scanline(y, image_span(data.data(), m_spec.nchannels, + m_spec.width, 1, 1)); + } + + /// 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 + /// 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. + /// + /// 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` + /// 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, TypeDesc format, + 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, const image_span& data) + { + // image_span: reduces to type + byte_buffer + return write_scanlines(ybegin, yend, 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, 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, ispan); + } + + + /// 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` 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_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. + /// @param zbegin/zend The z range of the pixels being passed + /// (for a 2D image, zbegin=0 and zend=1). + /// @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. + /// + /// 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); + + /// 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, const 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); + } + + /// Write a rectangular region of tiles of pixels given by the range + /// + /// [xbegin,xend) X [ybegin,yend) X [zbegin,zend) + /// + /// 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_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. + /// @param zbegin/zend The z range of the pixels being passed + /// (for a 2D image, zbegin=0 and zend=1). + /// @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_tiles(int xbegin, int xend, int ybegin, int yend, + int zbegin, int zend, TypeDesc format, + 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, const 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); + } + + /// 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. + /// + /// 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_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. + /// @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_rectangle(int xbegin, int xend, int ybegin, int yend, + int zbegin, int zend, TypeDesc format, + 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 + /// 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, const 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); + } + + // 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 +2863,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 +3131,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, const 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, + const 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 @@ -3463,7 +3863,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() @@ -3483,8 +3883,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() @@ -3515,8 +3915,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() @@ -3534,9 +3934,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() @@ -3602,7 +4002,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. @@ -3612,7 +4013,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/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/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/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; } 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 6be88ae7de..a5319923f3 100644 --- a/src/libOpenImageIO/imageoutput.cpp +++ b/src/libOpenImageIO/imageoutput.cpp @@ -110,6 +110,26 @@ ImageOutput::write_scanline(int /*y*/, int /*z*/, TypeDesc /*format*/, +bool +ImageOutput::write_scanline(int y, TypeDesc format, + const image_span& data) +{ + 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, 0, 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 +152,27 @@ ImageOutput::write_scanlines(int ybegin, int yend, int z, TypeDesc format, +bool +ImageOutput::write_scanlines(int ybegin, int yend, TypeDesc format, + const image_span& data) +{ + 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, 0, 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 +184,27 @@ ImageOutput::write_tile(int /*x*/, int /*y*/, int /*z*/, TypeDesc /*format*/, +bool +ImageOutput::write_tile(int x, int y, int z, TypeDesc format, + const image_span& data) +{ + 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 +264,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, + const 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 +289,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*/, + const image_span& /*data*/) +{ + return false; +} + + + bool ImageOutput::write_deep_scanlines(int /*ybegin*/, int /*yend*/, int /*z*/, const DeepData& /*deepdata*/) @@ -481,6 +567,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, + const image_span& data, + std::vector& scratch, unsigned int dither, + int xorigin, int yorigin, int zorigin) +{ + // 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, + 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 +681,24 @@ ImageOutput::write_image(TypeDesc format, const void* data, stride_t xstride, +bool +ImageOutput::write_image(TypeDesc format, + const 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()); +} + + + 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..825954e67b 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,11 @@ 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, make_span(scanline)); } out->close(); // END-imageoutput-scanlines