diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f076d1b87d..1df8eedab9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -331,7 +331,7 @@ jobs: simd: avx2,f16c setenvs: export LIBJPEGTURBO_VERSION=3.0.3 LIBRAW_VERSION=0.21.2 - LIBTIFF_VERSION=v4.6.0 + LIBTIFF_VERSION=v4.7.0 OPENJPEG_VERSION=v2.5.2 PTEX_VERSION=v2.4.3 PUGIXML_VERSION=v1.14 @@ -377,8 +377,8 @@ jobs: python_ver: "3.10" simd: avx2,f16c setenvs: export OpenImageIO_BUILD_LOCAL_DEPS=all - LIBJPEGTURBO_VERSION=3.0.1 - LIBRAW_VERSION=0.21.2 + LIBJPEGTURBO_VERSION=3.0.4 + LIBRAW_VERSION=0.21.3 OPENJPEG_VERSION=v2.4.0 PTEX_VERSION=v2.4.2 PUGIXML_VERSION=v1.14 diff --git a/src/build-scripts/build_Freetype.bash b/src/build-scripts/build_Freetype.bash index f14c351051..0f2e485433 100755 --- a/src/build-scripts/build_Freetype.bash +++ b/src/build-scripts/build_Freetype.bash @@ -11,7 +11,7 @@ set -ex # Repo and branch/tag/commit of Freetype to download if we don't have it yet FREETYPE_REPO=${FREETYPE_REPO:=https://github.com/freetype/freetype.git} -FREETYPE_VERSION=${FREETYPE_VERSION:=VER-2-13-2} +FREETYPE_VERSION=${FREETYPE_VERSION:=VER-2-13-3} # Where to put Freetype repo source (default to the ext area) LOCAL_DEPS_DIR=${LOCAL_DEPS_DIR:=${PWD}/ext} diff --git a/src/build-scripts/build_OpenJPEG.bash b/src/build-scripts/build_OpenJPEG.bash index ae2bd7e05b..2aa013374e 100755 --- a/src/build-scripts/build_OpenJPEG.bash +++ b/src/build-scripts/build_OpenJPEG.bash @@ -11,7 +11,7 @@ set -ex # Repo and branch/tag/commit of OpenJPEG to download if we don't have it yet OPENJPEG_REPO=${OPENJPEG_REPO:=https://github.com/uclouvain/openjpeg.git} -OPENJPEG_VERSION=${OPENJPEG_VERSION:=v2.4.0} +OPENJPEG_VERSION=${OPENJPEG_VERSION:=v2.5.2} # Where to put OpenJPEG repo source (default to the ext area) LOCAL_DEPS_DIR=${LOCAL_DEPS_DIR:=${PWD}/ext} diff --git a/src/build-scripts/build_libjpeg-turbo.bash b/src/build-scripts/build_libjpeg-turbo.bash index 37046840cf..bfa829db58 100755 --- a/src/build-scripts/build_libjpeg-turbo.bash +++ b/src/build-scripts/build_libjpeg-turbo.bash @@ -11,7 +11,7 @@ set -ex # Repo and branch/tag/commit of libjpeg-turbo to download if we don't have it yet LIBJPEGTURBO_REPO=${LIBJPEGTURBO_REPO:=https://github.com/libjpeg-turbo/libjpeg-turbo.git} -LIBJPEGTURBO_VERSION=${LIBJPEGTURBO_VERSION:=3.0.0} +LIBJPEGTURBO_VERSION=${LIBJPEGTURBO_VERSION:=3.0.4} # Where to put libjpeg-turbo repo source (default to the ext area) LOCAL_DEPS_DIR=${LOCAL_DEPS_DIR:=${PWD}/ext} diff --git a/src/build-scripts/build_libraw.bash b/src/build-scripts/build_libraw.bash index 38159f0b99..1e6ea024e7 100755 --- a/src/build-scripts/build_libraw.bash +++ b/src/build-scripts/build_libraw.bash @@ -11,7 +11,7 @@ set -ex # Which LibRaw to retrieve, how to build it LIBRAW_REPO=${LIBRAW_REPO:=https://github.com/LibRaw/LibRaw.git} -LIBRAW_VERSION=${LIBRAW_VERSION:=0.21.2} +LIBRAW_VERSION=${LIBRAW_VERSION:=0.21.3} # Where to install the final results LOCAL_DEPS_DIR=${LOCAL_DEPS_DIR:=${PWD}/ext} diff --git a/src/build-scripts/build_libtiff.bash b/src/build-scripts/build_libtiff.bash index ac1724bc2c..763fb4bc75 100755 --- a/src/build-scripts/build_libtiff.bash +++ b/src/build-scripts/build_libtiff.bash @@ -13,7 +13,7 @@ LIBTIFF_REPO=${LIBTIFF_REPO:=https://gitlab.com/libtiff/libtiff.git} LOCAL_DEPS_DIR=${LOCAL_DEPS_DIR:=${PWD}/ext} LIBTIFF_BUILD_DIR=${LIBTIFF_BUILD_DIR:=${LOCAL_DEPS_DIR}/libtiff} LIBTIFF_INSTALL_DIR=${LIBTIFF_INSTALL_DIR:=${PWD}/ext/dist} -LIBTIFF_VERSION=${LIBTIFF_VERSION:=v4.3.0} +LIBTIFF_VERSION=${LIBTIFF_VERSION:=v4.6.0} LIBTIFF_BUILD_TYPE=${LIBTIFF_BUILD_TYPE:=Release} if [[ `uname` == "Linux" ]] ; then LIBTIFF_CXX_FLAGS=${LIBTIFF_CXX_FLAGS:="-O3 -Wno-unused-function -Wno-deprecated-declarations -Wno-cast-qual -Wno-write-strings"} diff --git a/src/cmake/build_libjpeg-turbo.cmake b/src/cmake/build_libjpeg-turbo.cmake index 1404c8935e..4b2fa31de8 100644 --- a/src/cmake/build_libjpeg-turbo.cmake +++ b/src/cmake/build_libjpeg-turbo.cmake @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/Academ SoftwareFoundation/OpenImageIO -set_cache (libjpeg-turbo_BUILD_VERSION 3.0.3 "libjpeg-turbo version for local builds") +set_cache (libjpeg-turbo_BUILD_VERSION 3.0.4 "libjpeg-turbo version for local builds") set (libjpeg-turbo_GIT_REPOSITORY "https://github.com/libjpeg-turbo/libjpeg-turbo") set (libjpeg-turbo_GIT_TAG "${libjpeg-turbo_BUILD_VERSION}") set_cache (libjpeg-turbo_BUILD_SHARED_LIBS OFF #${LOCAL_BUILD_SHARED_LIBS_DEFAULT} diff --git a/src/cmake/compiler.cmake b/src/cmake/compiler.cmake index 434541d0a8..ddd6f99034 100644 --- a/src/cmake/compiler.cmake +++ b/src/cmake/compiler.cmake @@ -420,6 +420,16 @@ if (CODECOV AND (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_CLANG)) endif () +########################################################################### +# Profiling +# +set_cache (PROFILER "" "Build executables with profiler support (choices: gperftools)") +if (PROFILER STREQUAL "gperftools") + find_library(PROFILER_LIBRARIES NAMES profiler) + message (STATUS "Compiling for profiling with ${PROFILER}, found ${PROFILER_LIBRARIES}") +endif () + + ########################################################################### # Sanitizer options # diff --git a/src/doc/imagebuf.rst b/src/doc/imagebuf.rst index 7697e92c8d..5142d30b7d 100644 --- a/src/doc/imagebuf.rst +++ b/src/doc/imagebuf.rst @@ -163,10 +163,10 @@ Getting and setting pixel values .. doxygenfunction:: OIIO::ImageBuf::getchannel .. doxygenfunction:: OIIO::ImageBuf::getpixel(int x, int y, int z, float *pixel, int maxchannels = 1000, WrapMode wrap = WrapBlack) const -.. doxygenfunction:: OIIO::ImageBuf::interppixel -.. doxygenfunction:: OIIO::ImageBuf::interppixel_bicubic -.. doxygenfunction:: OIIO::ImageBuf::interppixel_NDC -.. doxygenfunction:: OIIO::ImageBuf::interppixel_bicubic_NDC +.. doxygenfunction:: OIIO::ImageBuf::interppixel(float, float, span, WrapMode) const +.. doxygenfunction:: OIIO::ImageBuf::interppixel_bicubic(float, float, span, WrapMode) const +.. doxygenfunction:: OIIO::ImageBuf::interppixel_NDC(float, float, span, WrapMode) const +.. doxygenfunction:: OIIO::ImageBuf::interppixel_bicubic_NDC(float, float, span, WrapMode) const .. doxygenfunction:: OIIO::ImageBuf::setpixel(int x, int y, int z, cspan pixel) .. doxygenfunction:: OIIO::ImageBuf::setpixel(int i, cspan pixel) @@ -175,8 +175,10 @@ Getting and setting pixel values **Getting and setting regions of pixels -- fast** -.. doxygenfunction:: OIIO::ImageBuf::get_pixels -.. doxygenfunction:: OIIO::ImageBuf::set_pixels +.. doxygenfunction:: OIIO::ImageBuf::get_pixels(ROI, span, stride_t, stride_t, stride_t) const +.. doxygenfunction:: OIIO::ImageBuf::get_pixels(ROI, span, T*, stride_t, stride_t, stride_t) const +.. doxygenfunction:: OIIO::ImageBuf::set_pixels(ROI, span, stride_t, stride_t, stride_t) +.. doxygenfunction:: OIIO::ImageBuf::set_pixels(ROI, span, const T*, stride_t, stride_t, stride_t) diff --git a/src/doc/imagebufalgo.rst b/src/doc/imagebufalgo.rst index eaf288e256..2559dc9f1f 100644 --- a/src/doc/imagebufalgo.rst +++ b/src/doc/imagebufalgo.rst @@ -1112,6 +1112,43 @@ Shuffling channels | +.. doxygengroup:: st_warp +.. + +.. + Examples: + + FIX ME! This is not complete, examples need to be added to docs-examples-imagebufalgo.{cpp,py} + .. tabs:: + + .. tab:: C++ + .. literalinclude:: ../../testsuite/docs-examples-cpp/src/docs-examples-imagebufalgo.cpp + :language: c++ + :start-after: BEGIN-imagebufalgo-st_warp + :end-before: END-imagebufalgo-st_warp + :dedent: 4 + + .. tab:: Python + .. literalinclude:: ../../testsuite/docs-examples-python/src/docs-examples-imagebufalgo.py + :language: py + :start-after: BEGIN-imagebufalgo-st_warp + :end-before: END-imagebufalgo-st_warp + :dedent: 4 + + .. tab:: oiiotool + .. code-block:: bash + + oiiotool mandrill.tif distortion_st.tif --st_warp -o mandrill_distorted.tif + + # Using an `st` map authored in terms of a lower-left origin (e.g. by + # Nuke), so flip the vertical (`t`) coordinate. + oiiotool mandrill.tif st_from_nuke.tif --st_warp:filter=triangle:flip_t=1 -o mandrill_distorted.tif + + oiiotool grid.exr --st_warp 0.7071068,0.7071068,0,-0.7071068,0.7071068,0,20,-8.284271,1 -o out.exr + +| + + .. doxygenfunction:: demosaic(const ImageBuf &src, KWArgs options = {}, ROI roi = {}, int nthreads = 0) .. diff --git a/src/doc/pythonbindings.rst b/src/doc/pythonbindings.rst index ba304408ac..65eee9ff75 100644 --- a/src/doc/pythonbindings.rst +++ b/src/doc/pythonbindings.rst @@ -2696,6 +2696,26 @@ Image transformations and data movement +.. py:method:: ImageBuf ImageBufAlgo.st_warp (src, M, filtername="", filtersize=0.0, wrap="default", recompute_roi=False, roi=ROI.All, nthreads=0) + bool ImageBufAlgo.st_warp (dst, src, M, filtername="", filtersize=0.0, wrap="default", recompute_roi=False, roi=ROI.All, nthreads=0) + + Compute a warped (transformed) copy of `src`, with the warp specified by + `M` consisting of 9 floating-point numbers representing a 3x3 + transformation matrix. If the filter and size are not specified, an + appropriate default will be chosen. + + Example: + + .. code-block:: python + + # distortion_st.tif is a map where every pixel value contains the 2D + # coordinate of where to copy from. + Distort = ImageBuf("distortion_st.tif") + Src = ImageBuf("tahoe.exr") + Dst = ImageBufAlgo.st_warp(Src, Distort) + + + .. py:method:: ImageBuf ImageBufAlgo.resize (src, filtername="", filtersize=0.0, roi=ROI.All, nthreads=0) bool ImageBufAlgo.resize (dst, src, filtername="", filtersize=0.0, roi=ROI.All, nthreads=0) diff --git a/src/include/OpenImageIO/imagebuf.h b/src/include/OpenImageIO/imagebuf.h index af31cf0675..e209dba08e 100644 --- a/src/include/OpenImageIO/imagebuf.h +++ b/src/include/OpenImageIO/imagebuf.h @@ -435,7 +435,7 @@ class OIIO_API ImageBuf { /// `convert` parameter requests a data format conversion to a type that /// is not the native file type and also is not one of the internal /// types supported by the ImageCache (specifically, `float` and - /// `UINT8`); (c) if the ImageBuf already has local pixel memory + /// `uint8`); (c) if the ImageBuf already has local pixel memory /// allocated, or "wraps" an application buffer. /// /// Note that `read()` is not strictly necessary. If you are happy with @@ -446,18 +446,18 @@ class OIIO_API ImageBuf { /// will be automatically read the first time you make any other /// ImageBuf API call that requires the spec or pixel values. The only /// reason to call `read()` yourself is if you are changing the - /// filename, subimage, or MIP level, or if you want to use `force = - /// true` or a specific `convert` value to force data format conversion. + /// filename, subimage, or MIP level, or if you want to use `force=true` + /// or a specific `convert` value to force data format conversion. /// - /// @param subimage/miplevel + /// @param subimage/miplevel /// The subimage and MIP level to read. - /// @param force + /// @param force /// If `true`, will force an immediate full read into /// ImageBuf-owned local pixel memory (yielding a /// `LOCALPIXELS` storage buffer). Otherwise, it is up to /// the implementation whether to immediately read or have /// the image backed by an ImageCache (storage - /// `IMAGECACHE`, if the ImageBuf was originall constructed + /// `IMAGECACHE`, if the ImageBuf was originally constructed /// or reset with an ImageCache specified). /// @param convert /// If set to a specific type (not`UNKNOWN`), the ImageBuf @@ -466,7 +466,7 @@ class OIIO_API ImageBuf { /// @param progress_callback/progress_callback_data /// If `progress_callback` is non-NULL, the underlying /// read, if expensive, may make several calls to - /// `progress_callback(progress_callback_data, portion_done)` + /// `progress_callback(progress_callback_data, portion_done)` /// which allows you to implement some sort of progress /// meter. Note that if the ImageBuf is backed by an /// ImageCache, the progress callback will never be called, @@ -480,7 +480,7 @@ class OIIO_API ImageBuf { /// message via `geterror()`). /// bool read(int subimage = 0, int miplevel = 0, bool force = false, - TypeDesc convert = TypeDesc::UNKNOWN, + TypeDesc convert = TypeUnknown, ProgressCallback progress_callback = nullptr, void* progress_callback_data = nullptr); @@ -721,7 +721,9 @@ class OIIO_API ImageBuf { /// @param x/y/z /// The pixel coordinates. /// @param c - /// The channel index to retrieve. + /// The channel index to retrieve. If `c` is not in the + /// valid channel range 0..nchannels-1, then `getchannel()` + /// will return 0. /// @param wrap /// WrapMode that determines the behavior if the pixel /// coordinates are outside the data window: `WrapBlack`, @@ -732,67 +734,114 @@ class OIIO_API ImageBuf { WrapMode wrap = WrapBlack) const; /// Retrieve the pixel value by x, y, z pixel indices, placing its - /// contents in `pixel[0..n-1]` where *n* is the smaller of - /// `maxchannels` the actual number of channels stored in the buffer. + /// contents in `pixel[0..n-1]` where *n* is the smaller of the span's + /// size and the actual number of channels stored in the buffer. /// /// @param x/y/z /// The pixel coordinates. /// @param pixel - /// The results are stored in `pixel[0..nchannels-1]`. It is - /// up to the caller to ensure that `pixel` points to enough - /// memory to hold the required number of channels. - /// @param maxchannels - /// Optional clamp to the number of channels retrieved. + /// A span giving the location where results will be stored. /// @param wrap /// WrapMode that determines the behavior if the pixel /// coordinates are outside the data window: `WrapBlack`, /// `WrapClamp`, `WrapPeriodic`, `WrapMirror`. - void getpixel(int x, int y, int z, float* pixel, int maxchannels = 1000, + void getpixel(int x, int y, int z, span pixel, WrapMode wrap = WrapBlack) const; - // Simplified version: 2D, black wrap. + /// Simplified version of getpixel(): 2D, black wrap. + void getpixel(int x, int y, span pixel) const + { + getpixel(x, y, 0, pixel); + } + + /// Unsafe version of getpixel using raw pointer. Avoid if possible. + OIIO_IB_DEPRECATE_RAW_PTR + void getpixel(int x, int y, int z, float* pixel, int maxchannels = 1000, + WrapMode wrap = WrapBlack) const + { + getpixel(x, y, z, make_span(pixel, size_t(maxchannels)), wrap); + } + + /// Unsafe version of getpixel using raw pointer. Avoid if possible. + OIIO_IB_DEPRECATE_RAW_PTR void getpixel(int x, int y, float* pixel, int maxchannels = 1000) const { getpixel(x, y, 0, pixel, maxchannels); } /// Sample the image plane at pixel coordinates (x,y), using linear - /// interpolation between pixels, placing the result in `pixel[]`. + /// interpolation between pixels, placing the result in `pixel[0..n-1]` + /// where *n* is the smaller of the span's size and the actual number of + /// channels stored in the buffer. /// /// @param x/y /// The pixel coordinates. Note that pixel data values /// themselves are at the pixel centers, so pixel (i,j) is /// at image plane coordinate (i+0.5, j+0.5). /// @param pixel - /// The results are stored in `pixel[0..nchannels-1]`. It is - /// up to the caller to ensure that `pixel` points to enough - /// memory to hold the number of channels in the image. + /// A span giving the location where results will be stored. /// @param wrap /// WrapMode that determines the behavior if the pixel /// coordinates are outside the data window: `WrapBlack`, /// `WrapClamp`, `WrapPeriodic`, `WrapMirror`. - void interppixel(float x, float y, float* pixel, + void interppixel(float x, float y, span pixel, WrapMode wrap = WrapBlack) const; + /// Unsafe version of interppixel using raw pointer. Avoid if possible. + OIIO_IB_DEPRECATE_RAW_PTR + void interppixel(float x, float y, float* pixel, + WrapMode wrap = WrapBlack) const + { + interppixel(x, y, make_span(pixel, size_t(nchannels())), wrap); + } + /// Linearly interpolate at NDC coordinates (s,t), where (0,0) is /// the upper left corner of the display window, (1,1) the lower /// right corner of the display window. /// /// @note `interppixel()` uses pixel coordinates (ranging 0..resolution) /// whereas `interppixel_NDC()` uses NDC coordinates (ranging 0..1). - void interppixel_NDC(float s, float t, float* pixel, + void interppixel_NDC(float s, float t, span pixel, WrapMode wrap = WrapBlack) const; + /// Unsafe version of interppixel_NDC using raw pointer. Avoid if + /// possible. + OIIO_IB_DEPRECATE_RAW_PTR + void interppixel_NDC(float s, float t, float* pixel, + WrapMode wrap = WrapBlack) const + { + interppixel_NDC(s, t, make_span(pixel, size_t(nchannels())), wrap); + } + /// Bicubic interpolation at pixel coordinates (x,y). - void interppixel_bicubic(float x, float y, float* pixel, + void interppixel_bicubic(float x, float y, span pixel, WrapMode wrap = WrapBlack) const; + /// Unsafe version of interppixel_bicubic_NDC using raw pointer. + /// Avoid if possible. + OIIO_IB_DEPRECATE_RAW_PTR + void interppixel_bicubic(float x, float y, float* pixel, + WrapMode wrap = WrapBlack) const + { + interppixel_bicubic(x, y, make_span(pixel, size_t(nchannels())), wrap); + } + /// Bicubic interpolation at NDC space coordinates (s,t), where (0,0) /// is the upper left corner of the display (a.k.a. "full") window, /// (1,1) the lower right corner of the display window. - void interppixel_bicubic_NDC(float s, float t, float* pixel, + void interppixel_bicubic_NDC(float s, float t, span pixel, WrapMode wrap = WrapBlack) const; + /// Unsafe version of interppixel_bicubic_NDC using raw pointer. + /// Avoid if possible. + OIIO_IB_DEPRECATE_RAW_PTR + void interppixel_bicubic_NDC(float s, float t, float* pixel, + WrapMode wrap = WrapBlack) const + { + interppixel_bicubic_NDC(s, t, make_span(pixel, size_t(nchannels())), + wrap); + } + /// Set the pixel with coordinates (x,y,0) to have the values in span /// `pixel[]`. The number of channels copied is the minimum of the span @@ -805,10 +854,7 @@ class OIIO_API ImageBuf { /// Set the pixel with coordinates (x,y,z) to have the values in span /// `pixel[]`. The number of channels copied is the minimum of the span /// length and the actual number of channels in the image. - void setpixel(int x, int y, int z, cspan pixel) - { - setpixel(x, y, z, pixel.data(), int(pixel.size())); - } + void setpixel(int x, int y, int z, cspan pixel); /// Set the `i`-th pixel value of the image (out of width*height*depth), /// from floating-point values in span `pixel[]`. The number of @@ -816,41 +862,106 @@ class OIIO_API ImageBuf { /// number of channels in the image. void setpixel(int i, cspan pixel) { - setpixel(i, pixel.data(), int(pixel.size())); + setpixel(spec().x + (i % spec().width), spec().y + (i / spec().width), + pixel); } /// Set the pixel with coordinates (x,y,0) to have the values in /// pixel[0..n-1]. The number of channels copied, n, is the minimum /// of maxchannels and the actual number of channels in the image. + OIIO_IB_DEPRECATE_RAW_PTR void setpixel(int x, int y, const float* pixel, int maxchannels = 1000) { - setpixel(x, y, 0, pixel, maxchannels); + int n = std::min(spec().nchannels, maxchannels); + setpixel(x, y, 0, make_cspan(pixel, size_t(n))); } /// Set the pixel with coordinates (x,y,z) to have the values in /// `pixel[0..n-1]`. The number of channels copied, n, is the minimum /// of `maxchannels` and the actual number of channels in the image. + OIIO_IB_DEPRECATE_RAW_PTR void setpixel(int x, int y, int z, const float* pixel, - int maxchannels = 1000); + int maxchannels = 1000) + { + int n = std::min(spec().nchannels, maxchannels); + setpixel(x, y, z, make_cspan(pixel, size_t(n))); + } /// Set the `i`-th pixel value of the image (out of width*height*depth), /// from floating-point values in `pixel[]`. Set at most /// `maxchannels` (will be clamped to the actual number of channels). - void setpixel(int i, const float* pixel, int maxchannels = 1000); + OIIO_IB_DEPRECATE_RAW_PTR + void setpixel(int i, const float* pixel, int maxchannels = 1000) + { + int n = std::min(spec().nchannels, maxchannels); + setpixel(i, make_cspan(pixel, size_t(n))); + } /// Retrieve the rectangle of pixels spanning the ROI (including /// channels) at the current subimage and MIP-map level, storing the - /// pixel values beginning at the address specified by result and with - /// the given strides (by default, AutoStride means the usual contiguous - /// packing of pixels) and converting into the data type described by - /// `format`. It is up to the caller to ensure that result points to an - /// area of memory big enough to accommodate the requested rectangle. - /// Return true if the operation could be completed, otherwise return - /// false. + /// pixel values into the `buffer`. + /// + /// @param roi + /// The region of interest to copy into. A default + /// uninitialized ROI means the entire image. + /// @param buffer + /// A span delineating the extent of the safely accessible + /// memory where the results should be stored. + /// @param xstride/ystride/zstride + /// The distance in bytes between successive pixels, + /// scanlines, and image planes in the buffer (or + /// `AutoStride` to indicate "contiguous" data in any of + /// those dimensions). + /// @returns + /// Return true if the operation could be completed, + /// otherwise return false. + /// + template + bool get_pixels(ROI roi, span buffer, stride_t xstride = AutoStride, + stride_t ystride = AutoStride, + stride_t zstride = AutoStride) const + { + static_assert(!std::is_const_v); + return get_pixels(roi, TypeDescFromC::value(), + as_writable_bytes(buffer), buffer.data(), xstride, + ystride, zstride); + } + + /// get_pixels() with an extra parameter: + /// + /// @param buforigin + /// A pointer to the first pixel of the buffer. If null, + /// it will be assumed to be the beginning of the buffer. + /// This is useful if any negative strides are used to + /// give an unusual layout of pixels within the buffer. + /// + template + bool get_pixels(ROI roi, span buffer, T* buforigin, + stride_t xstride = AutoStride, + stride_t ystride = AutoStride, + stride_t zstride = AutoStride) const + { + static_assert(!std::is_const_v); + return get_pixels(roi, TypeDescFromC::value(), + as_writable_bytes(buffer), buforigin, xstride, + ystride, zstride); + } + +#ifndef OIIO_DOXYGEN + /// Base case of get_pixels: read into a span of generic bytes. The + /// requested data type is supplied by `format. + bool get_pixels(ROI roi, TypeDesc format, span buffer, + void* buforigin = nullptr, stride_t xstride = AutoStride, + stride_t ystride = AutoStride, + stride_t zstride = AutoStride) const; + + /// Potentially unsafe get_pixels() using raw pointers. Use with catution! + OIIO_IB_DEPRECATE_RAW_PTR bool get_pixels(ROI roi, TypeDesc format, void* result, stride_t xstride = AutoStride, stride_t ystride = AutoStride, stride_t zstride = AutoStride) const; +#endif /// Copy the data into the given ROI of the ImageBuf. The data points to /// values specified by `format`, with layout detailed by the stride @@ -860,10 +971,70 @@ class OIIO_API ImageBuf { /// the data buffer is assumed to have the same resolution as the ImageBuf /// itself. Return true if the operation could be completed, otherwise /// return false. + + /// Set the rectangle of pixels within the ROI to the values in the + /// `buffer`. + /// + /// @param roi + /// The region of interest to copy into. A default + /// uninitialized ROI means the entire image. + /// @param buffer + /// A span delineating the extent of the safely accessible + /// memory where the results should be copied from. + /// @param xstride/ystride/zstride + /// The distance in bytes between successive pixels, + /// scanlines, and image planes in the buffer (or + /// `AutoStride` to indicate "contiguous" data in any of + /// those dimensions). + /// @returns + /// Return true if the operation could be completed, + /// otherwise return false. + /// + template + bool set_pixels(ROI roi, span buffer, stride_t xstride = AutoStride, + stride_t ystride = AutoStride, + stride_t zstride = AutoStride) + { + return set_pixels(roi, TypeDescFromC>::value(), + as_bytes(buffer), buffer.data(), xstride, ystride, + zstride); + } + + /// set_pixels() with an extra parameter: + /// + /// @param buforigin + /// A pointer to the first pixel of the buffer. If null, + /// it will be assumed to be the beginning of the buffer. + /// This is useful if any negative strides are used to + /// give an unusual layout of pixels within the buffer. + /// + template + bool set_pixels(ROI roi, span buffer, const T* buforigin, + stride_t xstride = AutoStride, + stride_t ystride = AutoStride, + stride_t zstride = AutoStride) + { + return set_pixels(roi, TypeDescFromC>::value(), + as_bytes(buffer), buforigin, xstride, ystride, + zstride); + } + +#ifndef OIIO_DOXYGEN + /// Base case of get_pixels: read into a span of generic bytes. The + /// requested data type is supplied by `format. + bool set_pixels(ROI roi, TypeDesc format, cspan buffer, + const void* buforigin = nullptr, + stride_t xstride = AutoStride, + stride_t ystride = AutoStride, + stride_t zstride = AutoStride); + + /// Potentially unsafe set_pixels() using raw pointers. Use with catution! + OIIO_IB_DEPRECATE_RAW_PTR bool set_pixels(ROI roi, TypeDesc format, const void* data, stride_t xstride = AutoStride, stride_t ystride = AutoStride, stride_t zstride = AutoStride); +#endif /// @} @@ -1045,7 +1216,7 @@ class OIIO_API ImageBuf { /// Is the specified roi completely contained in the data window of /// this ImageBuf? - bool contains_roi(ROI roi) const; + bool contains_roi(const ROI& roi) const; bool pixels_valid(void) const; @@ -1125,8 +1296,8 @@ class OIIO_API ImageBuf { /// Error reporting for ImageBuf: call this with std::format style /// formatting specification. It is not necessary to have the error /// message contain a trailing newline. - template - void errorfmt(const char* fmt, const Args&... args) const + template + void errorfmt(const Str& fmt, Args&&... args) const { error(Strutil::fmt::format(fmt, args...)); } diff --git a/src/include/OpenImageIO/imageio.h b/src/include/OpenImageIO/imageio.h index 4cd773501f..7d3499b7c1 100644 --- a/src/include/OpenImageIO/imageio.h +++ b/src/include/OpenImageIO/imageio.h @@ -3370,6 +3370,19 @@ OIIO_API bool copy_image (int nchannels, int width, int height, int depth, void *dst, stride_t dst_xstride, stride_t dst_ystride, stride_t dst_zstride); +/// Helper: manufacture a span given an image pointer, format, size, and +/// strides. Use with caution! This is making a lot of assumptions that the +/// data pointer really does point to memory that's ok to access according to +/// the sizes and strides you give. +OIIO_API span +span_from_buffer(void* data, TypeDesc format, int nchannels, int width, + int height, int depth, stride_t xstride = AutoStride, stride_t ystride = AutoStride, + stride_t zstride = AutoStride); +OIIO_API cspan +cspan_from_buffer(const void* data, TypeDesc format, int nchannels, int width, + int height, int depth, stride_t xstride = AutoStride, stride_t ystride = AutoStride, + stride_t zstride = AutoStride); + // All the wrap_foo functions implement a wrap mode, wherein coord is // altered to be origin <= coord < origin+width. The return value diff --git a/src/include/OpenImageIO/span.h b/src/include/OpenImageIO/span.h index 675f51e7a8..ca5382b197 100644 --- a/src/include/OpenImageIO/span.h +++ b/src/include/OpenImageIO/span.h @@ -558,6 +558,35 @@ spanzero(span dst, size_t offset = 0, size_t n = size_t(-1)) } + +/// Does the byte span `query` lie entirely within the safe `bounds` span? +inline bool +span_within(cspan bounds, cspan query) +{ + return query.data() >= bounds.data() + && query.data() + query.size() <= bounds.data() + bounds.size(); +} + + + +/// Verify the `ptr[0..len-1]` lies entirely within the given span `s`, which +/// does not need to be the same data type. Return true if that is the case, +/// false if it extends beyond the safe limits fo the span. +template +inline bool +check_span(span s, const PtrType* ptr, size_t len = 1) +{ + return span_within(as_bytes(s), as_bytes(make_cspan(ptr, len))); +} + + + +/// OIIO_ALLOCASPAN is used to allocate smallish amount of memory on the +/// stack, equivalent of C99 type var_name[size], and then return a span +/// encompassing it. +#define OIIO_ALLOCA_SPAN(type, size) span(OIIO_ALLOCA(type, size), size) + + OIIO_NAMESPACE_END diff --git a/src/iv/imageviewer.h b/src/iv/imageviewer.h index f1c0622547..e3b8066543 100644 --- a/src/iv/imageviewer.h +++ b/src/iv/imageviewer.h @@ -116,7 +116,7 @@ class IvImage final : public ImageBuf { /// color space correction when indicated. void pixel_transform(bool srgb_to_linear, int color_mode, int channel); - bool get_pixels(ROI roi, TypeDesc format, void* result) + bool get_pixels(ROI roi, TypeDesc format, span result) { if (m_corrected_image.localpixels()) return m_corrected_image.get_pixels(roi, format, result); diff --git a/src/iv/ivgl.cpp b/src/iv/ivgl.cpp index 206f8783d1..7bc3ad9f27 100644 --- a/src/iv/ivgl.cpp +++ b/src/iv/ivgl.cpp @@ -785,9 +785,10 @@ IvGL::paint_pixelview() m_viewer.current_color_mode()); } - void* zoombuffer = OIIO_ALLOCA(char, (xend - xbegin) * (yend - ybegin) - * nchannels - * spec.channel_bytes()); + auto zoombuffer = OIIO_ALLOCA_SPAN(std::byte, + (xend - xbegin) * (yend - ybegin) + * nchannels + * spec.channel_bytes()); if (!m_use_shaders) { img->get_pixels(ROI(spec.x + xbegin, spec.x + xend, spec.y + ybegin, spec.y + yend), @@ -805,7 +806,7 @@ IvGL::paint_pixelview() glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); glBindTexture(GL_TEXTURE_2D, m_pixelview_tex); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, xend - xbegin, yend - ybegin, - glformat, gltype, zoombuffer); + glformat, gltype, zoombuffer.data()); print_error("After tsi2d"); } else { smin = -1; @@ -1527,12 +1528,14 @@ IvGL::load_texture(int x, int y, int width, int height) // may not be resident at once. if (!m_use_shaders) { m_current_image->get_pixels(ROI(x, x + width, y, y + height), - spec.format, &m_tex_buffer[0]); + spec.format, + as_writable_bytes(make_span(m_tex_buffer))); } else { m_current_image->get_pixels(ROI(x, x + width, y, y + height, 0, 1, m_viewer.current_channel(), m_viewer.current_channel() + nchannels), - spec.format, &m_tex_buffer[0]); + spec.format, + as_writable_bytes(make_span(m_tex_buffer))); } glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pbo_objects[m_last_pbo_used]); diff --git a/src/libOpenImageIO/imagebuf.cpp b/src/libOpenImageIO/imagebuf.cpp index 45f9ee105c..8304295439 100644 --- a/src/libOpenImageIO/imagebuf.cpp +++ b/src/libOpenImageIO/imagebuf.cpp @@ -84,6 +84,54 @@ set_roi_full(ImageSpec& spec, const ROI& newroi) +span +span_from_buffer(void* data, TypeDesc format, int nchannels, int width, + int height, int depth, stride_t xstride, stride_t ystride, + stride_t zstride) +{ + ImageSpec::auto_stride(xstride, ystride, zstride, format.size(), nchannels, + width, height); + // Need to figure out the span based on the origin and strides. + // Start with the span range of one pixel. + std::byte* bufstart = (std::byte*)data; + std::byte* bufend = bufstart + format.size() * nchannels; + // Expand to the span range for one row. Remember negative strides! + if (xstride >= 0) { + bufend += xstride * (width - 1); + } else { + bufstart -= xstride * (width - 1); + } + // Expand to the span range for a whole image plane. + if (ystride >= 0) { + bufend += ystride * (height - 1); + } else { + bufstart -= ystride * (height - 1); + } + // Expand to the span range for a whole volume. + if (depth > 1 && zstride != 0) { + if (zstride >= 0) { + bufend += zstride * (depth - 1); + } else { + bufstart -= zstride * (depth - 1); + } + } + return { bufstart, size_t(bufend - bufstart) }; +} + + + +cspan +cspan_from_buffer(const void* data, TypeDesc format, int nchannels, int width, + int height, int depth, stride_t xstride, stride_t ystride, + stride_t zstride) +{ + auto s = span_from_buffer(const_cast(data), format, nchannels, width, + height, depth, xstride, ystride, zstride); + return { s.data(), s.size() }; +} + + + // Expansion of the opaque type that hides all the ImageBuf implementation // detail. class ImageBufImpl { @@ -836,34 +884,14 @@ ImageBufImpl::set_bufspan_localpixels(span bufspan, if (bufspan.size() && !buforigin) { buforigin = bufspan.data(); } else if (buforigin && (!bufspan.data() || bufspan.empty())) { - // Need to figure out the span based on the origin and strides. - // Start with the span range of one pixel. - std::byte* bufstart = (std::byte*)buforigin; - std::byte* bufend = bufstart + m_spec.format.size(); - // Expand to the span range for one row. Remember negative strides! - if (m_xstride >= 0) { - bufend += m_xstride * (m_spec.width - 1); - } else { - bufstart -= m_xstride * (m_spec.width - 1); - } - // Expand to the span range for a whole image plane. - if (m_ystride >= 0) { - bufend += m_ystride * (m_spec.height - 1); - } else { - bufstart -= m_ystride * (m_spec.height - 1); - } - // Expand to the span range for a whole volume. - if (m_spec.depth > 1 && m_zstride != 0) { - if (m_zstride >= 0) { - bufend += m_zstride * (m_spec.depth - 1); - } else { - bufstart -= m_zstride * (m_spec.depth - 1); - } - } - bufspan = span { bufstart, size_t(bufend - bufstart) }; + bufspan = span_from_buffer(const_cast(buforigin), m_spec.format, + m_spec.nchannels, m_spec.width, + m_spec.height, m_spec.depth, m_xstride, + m_ystride, m_zstride); } m_bufspan = bufspan; m_localpixels = (char*)buforigin; + OIIO_DASSERT(check_span(m_bufspan, m_localpixels, spec().format)); } @@ -1460,9 +1488,9 @@ ImageBuf::write(ImageOutput* out, ProgressCallback progress_callback, imagesize_t imagesize = bufspec.image_bytes(); if (imagesize <= budget) { // whole image can fit within our budget - std::unique_ptr tmp(new char[imagesize]); - ok &= get_pixels(roi(), bufformat, &tmp[0]); - ok &= out->write_image(bufformat, &tmp[0], AutoStride, AutoStride, + std::unique_ptr tmp(new std::byte[imagesize]); + ok &= get_pixels(roi(), bufformat, make_span(tmp.get(), imagesize)); + ok &= out->write_image(bufformat, tmp.get(), AutoStride, AutoStride, AutoStride, progress_callback, progress_callback_data); } else if (outspec.tile_width) { @@ -1470,7 +1498,8 @@ ImageBuf::write(ImageOutput* out, ProgressCallback progress_callback, size_t pixelsize = bufspec.pixel_bytes(); size_t chunksize = pixelsize * outspec.width * outspec.tile_height * outspec.tile_depth; - std::unique_ptr tmp(new char[chunksize]); + std::unique_ptr tmp(new std::byte[chunksize]); + auto tmpspan = make_span(tmp.get(), chunksize); for (int z = 0; z < outspec.depth; z += outspec.tile_depth) { int zend = std::min(z + outspec.z + outspec.tile_depth, outspec.z + outspec.depth); @@ -1481,7 +1510,7 @@ ImageBuf::write(ImageOutput* out, ProgressCallback progress_callback, ok &= get_pixels(ROI(outspec.x, outspec.x + outspec.width, outspec.y + y, yend, outspec.z + z, zend), - bufformat, &tmp[0]); + bufformat, tmpspan); ok &= out->write_tiles(outspec.x, outspec.x + outspec.width, y + outspec.y, yend, z + outspec.z, zend, bufformat, &tmp[0]); @@ -1498,7 +1527,8 @@ ImageBuf::write(ImageOutput* out, ProgressCallback progress_callback, imagesize_t slsize = bufspec.scanline_bytes(); int chunk = clamp(round_to_multiple(int(budget / slsize), 64), 1, 1024); - std::unique_ptr tmp(new char[chunk * slsize]); + std::unique_ptr tmp(new std::byte[chunk * slsize]); + auto tmpspan = make_span(tmp.get(), chunk * slsize); // Special handling for flipped vertical scanline order. Right now, OpenEXR // is the only format that allows it, so we special case it by name. For @@ -1521,7 +1551,7 @@ ImageBuf::write(ImageOutput* out, ProgressCallback progress_callback, ok &= get_pixels(ROI(outspec.x, outspec.x + outspec.width, outspec.y + y, yend, outspec.z, outspec.z + outspec.depth), - bufformat, &tmp[0]); + bufformat, tmpspan); ok &= out->write_scanlines(y + outspec.y, yend, z + outspec.z, bufformat, &tmp[0]); @@ -2188,11 +2218,12 @@ ImageBuf::getchannel(int x, int y, int z, int c, WrapMode wrap) const template static bool -getpixel_(const ImageBuf& buf, int x, int y, int z, float* result, int chans, +getpixel_(const ImageBuf& buf, int x, int y, int z, span result, ImageBuf::WrapMode wrap) { + OIIO_DASSERT(result.size() <= size_t(buf.spec().nchannels)); ImageBuf::ConstIterator pixel(buf, x, y, z, wrap); - for (int i = 0; i < chans; ++i) + for (size_t i = 0, e = result.size(); i < e; ++i) result[i] = pixel[i]; return true; } @@ -2200,33 +2231,32 @@ getpixel_(const ImageBuf& buf, int x, int y, int z, float* result, int chans, inline bool -getpixel_wrapper(int x, int y, int z, float* pixel, int nchans, +getpixel_wrapper(int x, int y, int z, span pixel, ImageBuf::WrapMode wrap, const ImageBuf& ib) { bool ok; OIIO_DISPATCH_TYPES(ok, "getpixel", getpixel_, ib.spec().format, ib, x, y, - z, pixel, nchans, wrap); + z, pixel, wrap); return ok; } void -ImageBuf::getpixel(int x, int y, int z, float* pixel, int maxchannels, - WrapMode wrap) const +ImageBuf::getpixel(int x, int y, int z, span pixel, WrapMode wrap) const { - int nchans = std::min(spec().nchannels, maxchannels); - getpixel_wrapper(x, y, z, pixel, nchans, wrap, *this); + pixel = pixel.subspan(0, std::min(size_t(spec().nchannels), pixel.size())); + getpixel_wrapper(x, y, z, pixel, wrap, *this); } template static bool -interppixel_(const ImageBuf& img, float x, float y, float* pixel, +interppixel_(const ImageBuf& img, float x, float y, span pixel, ImageBuf::WrapMode wrap) { - int n = img.spec().nchannels; + int n = std::min(int(pixel.size()), img.spec().nchannels); float* localpixel = OIIO_ALLOCA(float, n * 4); float* p[4] = { localpixel, localpixel + n, localpixel + 2 * n, localpixel + 3 * n }; @@ -2241,15 +2271,15 @@ interppixel_(const ImageBuf& img, float x, float y, float* pixel, for (int i = 0; i < 4; ++i, ++it) for (int c = 0; c < n; ++c) p[i][c] = it[c]; //NOSONAR - bilerp(p[0], p[1], p[2], p[3], xfrac, yfrac, n, pixel); + bilerp(p[0], p[1], p[2], p[3], xfrac, yfrac, n, pixel.data()); return true; } inline bool -interppixel_wrapper(float x, float y, float* pixel, ImageBuf::WrapMode wrap, - const ImageBuf& img) +interppixel_wrapper(float x, float y, span pixel, + ImageBuf::WrapMode wrap, const ImageBuf& img) { bool ok; OIIO_DISPATCH_TYPES(ok, "interppixel", interppixel_, img.spec().format, img, @@ -2260,7 +2290,7 @@ interppixel_wrapper(float x, float y, float* pixel, ImageBuf::WrapMode wrap, void -ImageBuf::interppixel(float x, float y, float* pixel, WrapMode wrap) const +ImageBuf::interppixel(float x, float y, span pixel, WrapMode wrap) const { interppixel_wrapper(x, y, pixel, wrap, *this); } @@ -2268,7 +2298,8 @@ ImageBuf::interppixel(float x, float y, float* pixel, WrapMode wrap) const void -ImageBuf::interppixel_NDC(float x, float y, float* pixel, WrapMode wrap) const +ImageBuf::interppixel_NDC(float x, float y, span pixel, + WrapMode wrap) const { const ImageSpec& spec(m_impl->spec()); interppixel(static_cast(spec.full_x) @@ -2282,10 +2313,10 @@ ImageBuf::interppixel_NDC(float x, float y, float* pixel, WrapMode wrap) const template static bool -interppixel_bicubic_(const ImageBuf& img, float x, float y, float* pixel, +interppixel_bicubic_(const ImageBuf& img, float x, float y, span pixel, ImageBuf::WrapMode wrap) { - int n = img.spec().nchannels; + int n = std::min(img.spec().nchannels, int(pixel.size())); x -= 0.5f; y -= 0.5f; int xtexel, ytexel; @@ -2314,7 +2345,7 @@ interppixel_bicubic_(const ImageBuf& img, float x, float y, float* pixel, inline bool -interppixel_bicubic_wrapper(float x, float y, float* pixel, +interppixel_bicubic_wrapper(float x, float y, span pixel, ImageBuf::WrapMode wrap, const ImageBuf& img) { bool ok; @@ -2326,7 +2357,7 @@ interppixel_bicubic_wrapper(float x, float y, float* pixel, void -ImageBuf::interppixel_bicubic(float x, float y, float* pixel, +ImageBuf::interppixel_bicubic(float x, float y, span pixel, WrapMode wrap) const { interppixel_bicubic_wrapper(x, y, pixel, wrap, *this); @@ -2335,7 +2366,7 @@ ImageBuf::interppixel_bicubic(float x, float y, float* pixel, void -ImageBuf::interppixel_bicubic_NDC(float x, float y, float* pixel, +ImageBuf::interppixel_bicubic_NDC(float x, float y, span pixel, WrapMode wrap) const { const ImageSpec& spec(m_impl->spec()); @@ -2362,9 +2393,10 @@ setpixel_(ImageBuf& buf, int x, int y, int z, const float* data, int chans) void -ImageBuf::setpixel(int x, int y, int z, const float* pixel, int maxchannels) +ImageBuf::setpixel(int x, int y, int z, cspan pixelspan) { - int n = std::min(spec().nchannels, maxchannels); + const float* pixel = pixelspan.data(); + int n = std::min(spec().nchannels, int(pixelspan.size())); switch (spec().format.basetype) { case TypeDesc::FLOAT: setpixel_(*this, x, y, z, pixel, n); break; case TypeDesc::UINT8: @@ -2393,15 +2425,6 @@ ImageBuf::setpixel(int x, int y, int z, const float* pixel, int maxchannels) -void -ImageBuf::setpixel(int i, const float* pixel, int maxchannels) -{ - setpixel(spec().x + (i % spec().width), spec().y + (i / spec().width), - pixel, maxchannels); -} - - - template static bool get_pixels_(const ImageBuf& buf, const ImageBuf& /*dummy*/, ROI whole_roi, @@ -2431,14 +2454,23 @@ get_pixels_(const ImageBuf& buf, const ImageBuf& /*dummy*/, ROI whole_roi, bool -ImageBuf::get_pixels(ROI roi, TypeDesc format, void* result, stride_t xstride, - stride_t ystride, stride_t zstride) const +ImageBuf::get_pixels(ROI roi, TypeDesc format, span buffer, + void* buforigin, stride_t xstride, stride_t ystride, + stride_t zstride) const { if (!roi.defined()) roi = this->roi(); roi.chend = std::min(roi.chend, nchannels()); ImageSpec::auto_stride(xstride, ystride, zstride, format.size(), roi.nchannels(), roi.width(), roi.height()); + void* result = buforigin ? buforigin : buffer.data(); + auto range = span_from_buffer(result, format, roi.nchannels(), roi.width(), + roi.height(), roi.depth(), xstride, ystride, + zstride); + if (!span_within(buffer, range)) { + errorfmt("get_pixels: buffer span does not contain the ROI dimensions"); + return false; + } if (localpixels() && this->roi().contains(roi)) { // Easy case -- if the buffer is already fully in memory and the roi // is completely contained in the pixel window, this reduces to a @@ -2462,14 +2494,30 @@ ImageBuf::get_pixels(ROI roi, TypeDesc format, void* result, stride_t xstride, +bool +ImageBuf::get_pixels(ROI roi, TypeDesc format, void* result, stride_t xstride, + stride_t ystride, stride_t zstride) const +{ + if (!roi.defined()) + roi = this->roi(); + roi.chend = std::min(roi.chend, nchannels()); + ImageSpec::auto_stride(xstride, ystride, zstride, format.size(), + roi.nchannels(), roi.width(), roi.height()); + auto range = span_from_buffer(result, format, roi.nchannels(), roi.width(), + roi.height(), roi.depth(), xstride, ystride, + zstride); + return get_pixels(roi, format, range, result, xstride, ystride, zstride); +} + + + template static bool set_pixels_(ImageBuf& buf, ROI roi, const void* data_, stride_t xstride, stride_t ystride, stride_t zstride) { const D* data = (const D*)data_; - int w = roi.width(), h = roi.height(), nchans = roi.nchannels(); - ImageSpec::auto_stride(xstride, ystride, zstride, sizeof(S), nchans, w, h); + int nchans = roi.nchannels(); for (ImageBuf::Iterator p(buf, roi); !p.done(); ++p) { if (!p.exists()) continue; @@ -2493,10 +2541,14 @@ ImageBuf::set_pixels(ROI roi, TypeDesc format, const void* data, errorfmt("Cannot set_pixels() on an uninitialized ImageBuf"); return false; } - bool ok; if (!roi.defined()) roi = this->roi(); roi.chend = std::min(roi.chend, nchannels()); + + ImageSpec::auto_stride(xstride, ystride, zstride, format.size(), + roi.nchannels(), roi.width(), roi.height()); + + bool ok; OIIO_DISPATCH_TYPES2(ok, "set_pixels", set_pixels_, spec().format, format, *this, roi, data, xstride, ystride, zstride); return ok; @@ -2504,6 +2556,38 @@ ImageBuf::set_pixels(ROI roi, TypeDesc format, const void* data, +bool +ImageBuf::set_pixels(ROI roi, TypeDesc format, cspan buffer, + const void* buforigin, stride_t xstride, stride_t ystride, + stride_t zstride) +{ + if (!initialized()) { + errorfmt("Cannot set_pixels() on an uninitialized ImageBuf"); + return false; + } + bool ok; + if (!roi.defined()) + roi = this->roi(); + roi.chend = std::min(roi.chend, nchannels()); + + ImageSpec::auto_stride(xstride, ystride, zstride, format.size(), + roi.nchannels(), roi.width(), roi.height()); + const void* result = buforigin ? buforigin : buffer.data(); + auto range = cspan_from_buffer(result, format, roi.nchannels(), roi.width(), + roi.height(), roi.depth(), xstride, ystride, + zstride); + if (!span_within(buffer, range)) { + errorfmt("set_pixels: buffer span does not contain the ROI dimensions"); + return false; + } + + OIIO_DISPATCH_TYPES2(ok, "set_pixels", set_pixels_, spec().format, format, + *this, roi, result, xstride, ystride, zstride); + return ok; +} + + + int ImageBuf::deep_samples(int x, int y, int z) const { @@ -2843,7 +2927,7 @@ ImageBuf::set_roi_full(const ROI& newroi) bool -ImageBuf::contains_roi(ROI roi) const +ImageBuf::contains_roi(const ROI& roi) const { ROI myroi = this->roi(); return (roi.defined() && myroi.defined() && roi.xbegin >= myroi.xbegin diff --git a/src/libOpenImageIO/imagebuf_test.cpp b/src/libOpenImageIO/imagebuf_test.cpp index d2673ea6dc..2785ccf96c 100644 --- a/src/libOpenImageIO/imagebuf_test.cpp +++ b/src/libOpenImageIO/imagebuf_test.cpp @@ -219,7 +219,7 @@ ImageBuf_test_appbuffer() // Make sure we can write to the buffer float pix[CHANNELS] = { 0.0, 42.0, 0 }; - A.setpixel(3, 2, 0, pix); + A.setpixel(3, 2, 0, make_span(pix)); OIIO_CHECK_EQUAL(buf[2][3][1], 42.0); // Make sure we can copy-construct the ImageBuf and it points to the @@ -289,7 +289,7 @@ ImageBuf_test_appbuffer_strided() for (int y = 0; y < res; ++y) { for (int x = 0; x < res; ++x) { float pixel[nchans]; - test.getpixel(x, y, pixel); + test.getpixel(x, y, make_span(pixel)); if ((x == 4 || x == 6 || x == 8) && (y == 4 || y == 6 || y == 8)) { OIIO_CHECK_ASSERT(cspan(pixel) == cspan(red)); @@ -357,16 +357,16 @@ test_set_get_pixels() { std::cout << "\nTesting set_pixels, get_pixels:\n"; const int nchans = 3; - ImageBuf A(ImageSpec(4, 4, nchans, TypeDesc::FLOAT)); + ImageBuf A(ImageSpec(4, 4, nchans, TypeFloat)); ImageBufAlgo::zero(A); std::cout << " Cleared:\n"; print(A); float newdata[2 * 2 * nchans] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; - A.set_pixels(ROI(1, 3, 1, 3), TypeDesc::FLOAT, newdata); + A.set_pixels(ROI(1, 3, 1, 3), make_span(newdata)); std::cout << " After set:\n"; print(A); float retrieved[2 * 2 * nchans] = { 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9 }; - A.get_pixels(ROI(1, 3, 1, 3, 0, 1), TypeDesc::FLOAT, retrieved); + A.get_pixels(ROI(1, 3, 1, 3, 0, 1), make_span(retrieved)); OIIO_CHECK_ASSERT(0 == memcmp(retrieved, newdata, 2 * 2 * nchans)); } @@ -383,22 +383,23 @@ time_get_pixels() ImageBufAlgo::zero(A); // bench.work (size_t(xres*yres*nchans)); - std::unique_ptr fbuf(new float[xres * yres * nchans]); + size_t nvals = size_t(xres * yres * nchans); + std::vector fbuf(nvals); bench("get_pixels 1Mpelx4 float[4]->float[4] ", - [&]() { A.get_pixels(A.roi(), TypeFloat, fbuf.get()); }); + [&]() { A.get_pixels(A.roi(), make_span(fbuf)); }); bench("get_pixels 1Mpelx4 float[4]->float[3] ", [&]() { ROI roi3 = A.roi(); roi3.chend = 3; - A.get_pixels(roi3, TypeFloat, fbuf.get()); + A.get_pixels(roi3, make_span(fbuf)); }); - std::unique_ptr ucbuf(new uint8_t[xres * yres * nchans]); + std::vector ucbuf(nvals); bench("get_pixels 1Mpelx4 float[4]->uint8[4] ", - [&]() { A.get_pixels(A.roi(), TypeUInt8, ucbuf.get()); }); + [&]() { A.get_pixels(A.roi(), make_span(ucbuf)); }); - std::unique_ptr usbuf(new uint16_t[xres * yres * nchans]); + std::vector usbuf(nvals); bench("get_pixels 1Mpelx4 float[4]->uint16[4] ", - [&]() { A.get_pixels(A.roi(), TypeUInt8, usbuf.get()); }); + [&]() { A.get_pixels(A.roi(), make_span(usbuf)); }); } @@ -492,7 +493,7 @@ test_write_over() // Read the image float pixel[3]; ImageBuf A("tmp-green.tif"); - A.getpixel(4, 4, pixel); + A.getpixel(4, 4, make_span(pixel)); OIIO_CHECK_ASSERT(pixel[0] == 0 && pixel[1] == 1 && pixel[2] == 0); A.reset(); // make sure A isn't held open, we're about to remove it @@ -504,7 +505,7 @@ test_write_over() // We expect it to have the new color, not have the underlying // ImageCache misremember the old color! ImageBuf B("tmp-green.tif"); - B.getpixel(4, 4, pixel); + B.getpixel(4, 4, make_span(pixel)); OIIO_CHECK_ASSERT(pixel[0] == 1 && pixel[1] == 0 && pixel[2] == 0); B.reset(); // make sure B isn't held open, we're about to remove it diff --git a/src/libOpenImageIO/imagebufalgo.cpp b/src/libOpenImageIO/imagebufalgo.cpp index 04fe1653ab..9ca9f406ee 100644 --- a/src/libOpenImageIO/imagebufalgo.cpp +++ b/src/libOpenImageIO/imagebufalgo.cpp @@ -923,7 +923,7 @@ ImageBufAlgo::make_kernel(string_view name, float width, float height, } else if (Strutil::iequals(name, "laplacian") && w == 3 && h == 3 && d == 1) { const float vals[9] = { 0, 1, 0, 1, -4, 1, 0, 1, 0 }; - dst.set_pixels(dst.roi(), TypeDesc::FLOAT, vals, sizeof(float), + dst.set_pixels(dst.roi(), make_cspan(vals), sizeof(float), h * sizeof(float)); normalize = false; // sums to zero, so don't normalize it */ } else { diff --git a/src/libOpenImageIO/imagebufalgo_compare.cpp b/src/libOpenImageIO/imagebufalgo_compare.cpp index c7ec27f094..09de210540 100644 --- a/src/libOpenImageIO/imagebufalgo_compare.cpp +++ b/src/libOpenImageIO/imagebufalgo_compare.cpp @@ -826,7 +826,7 @@ simplePixelHashSHA1(const ImageBuf& src, string_view extrainfo, ROI roi) // Do it a few scanlines at a time int chunk = std::max(1, int(16 * 1024 * 1024 / scanline_bytes)); - std::vector tmp; + std::vector tmp; if (!localpixels) tmp.resize(chunk * scanline_bytes); @@ -839,7 +839,7 @@ simplePixelHashSHA1(const ImageBuf& src, string_view extrainfo, ROI roi) size_t(scanline_bytes * (y1 - y))); } else { src.get_pixels(ROI(roi.xbegin, roi.xend, y, y1, z, z + 1), - src.spec().format, &tmp[0]); + src.spec().format, tmp); sha.append(&tmp[0], size_t(scanline_bytes) * (y1 - y)); } } diff --git a/src/libOpenImageIO/imagebufalgo_deep.cpp b/src/libOpenImageIO/imagebufalgo_deep.cpp index 8b35114b3d..b2195583a3 100644 --- a/src/libOpenImageIO/imagebufalgo_deep.cpp +++ b/src/libOpenImageIO/imagebufalgo_deep.cpp @@ -177,7 +177,7 @@ ImageBufAlgo::deepen(ImageBuf& dst, const ImageBuf& src, float zvalue, ROI roi, return false; } - float* pixel = OIIO_ALLOCA(float, nc); + span pixel = OIIO_ALLOCA_SPAN(float, nc); // First, figure out which pixels get a sample and which do not for (int z = roi.zbegin; z < roi.zend; ++z) diff --git a/src/libOpenImageIO/imagebufalgo_draw.cpp b/src/libOpenImageIO/imagebufalgo_draw.cpp index 4508841504..69c0ed57a5 100644 --- a/src/libOpenImageIO/imagebufalgo_draw.cpp +++ b/src/libOpenImageIO/imagebufalgo_draw.cpp @@ -1138,7 +1138,7 @@ ImageBufAlgo::render_text(ImageBuf& R, int x, int y, string_view text, int rx = x + i + slot->bitmap_left; float b = slot->bitmap.buffer[slot->bitmap.pitch * j + i] / 255.0f; - textimg.setpixel(rx, ry, &b, 1); + textimg.setpixel(rx, ry, b); } } // increment pen position @@ -1160,7 +1160,7 @@ ImageBufAlgo::render_text(ImageBuf& R, int x, int y, string_view text, roi = roi_intersection(textroi, R.roi()); // Now fill in the pixels of our destination image - float* pixelcolor = OIIO_ALLOCA(float, nchannels); + span pixelcolor = OIIO_ALLOCA_SPAN(float, nchannels); ImageBuf::ConstIterator t(textimg, roi, ImageBuf::WrapBlack); ImageBuf::ConstIterator a(alphaimg, roi, ImageBuf::WrapBlack); ImageBuf::Iterator r(R, roi); diff --git a/src/libOpenImageIO/imagebufalgo_test.cpp b/src/libOpenImageIO/imagebufalgo_test.cpp index b01bac67ee..af42d673fc 100644 --- a/src/libOpenImageIO/imagebufalgo_test.cpp +++ b/src/libOpenImageIO/imagebufalgo_test.cpp @@ -140,9 +140,9 @@ test_zero_fill() // Set a pixel to an odd value, make sure it takes const float arbitrary1[CHANNELS] = { 0.2f, 0.3f, 0.4f, 0.5f }; - A.setpixel(1, 1, arbitrary1); + A.setpixel(1, 1, make_span(arbitrary1)); float pixel[CHANNELS]; // test pixel - A.getpixel(1, 1, pixel); + A.getpixel(1, 1, make_span(pixel)); for (int c = 0; c < CHANNELS; ++c) OIIO_CHECK_EQUAL(pixel[c], arbitrary1[c]); @@ -151,7 +151,7 @@ test_zero_fill() for (int j = 0; j < HEIGHT; ++j) { for (int i = 0; i < WIDTH; ++i) { float pixel[CHANNELS]; - A.getpixel(i, j, pixel); + A.getpixel(i, j, make_span(pixel)); for (int c = 0; c < CHANNELS; ++c) OIIO_CHECK_EQUAL(pixel[c], 0.0f); } @@ -163,7 +163,7 @@ test_zero_fill() for (int j = 0; j < HEIGHT; ++j) { for (int i = 0; i < WIDTH; ++i) { float pixel[CHANNELS]; - A.getpixel(i, j, pixel); + A.getpixel(i, j, make_span(pixel)); for (int c = 0; c < CHANNELS; ++c) OIIO_CHECK_EQUAL(pixel[c], arbitrary2[c]); } @@ -178,7 +178,7 @@ test_zero_fill() for (int j = 0; j < HEIGHT; ++j) { for (int i = 0; i < WIDTH; ++i) { float pixel[CHANNELS]; - A.getpixel(i, j, pixel); + A.getpixel(i, j, make_span(pixel)); if (j >= ybegin && j < yend && i >= xbegin && i < xend) { for (int c = 0; c < CHANNELS; ++c) OIIO_CHECK_EQUAL(pixel[c], arbitrary3[c]); @@ -307,7 +307,7 @@ test_crop() OIIO_CHECK_EQUAL(B.spec().width, xend - xbegin); OIIO_CHECK_EQUAL(B.spec().y, ybegin); OIIO_CHECK_EQUAL(B.spec().height, yend - ybegin); - float* pixel = OIIO_ALLOCA(float, CHANNELS); + span pixel = OIIO_ALLOCA_SPAN(float, CHANNELS); for (int j = 0; j < B.spec().height; ++j) { for (int i = 0; i < B.spec().width; ++i) { B.getpixel(i + B.xbegin(), j + B.ybegin(), pixel); @@ -345,19 +345,19 @@ test_paste() // Spot check float a[3], b[3]; - B.getpixel(1, 1, 0, b); + B.getpixel(1, 1, 0, make_span(b)); OIIO_CHECK_EQUAL(b[0], gray[0]); OIIO_CHECK_EQUAL(b[1], gray[1]); OIIO_CHECK_EQUAL(b[2], gray[2]); - B.getpixel(2, 2, 0, b); - A.getpixel(1, 1, 0, a); + B.getpixel(2, 2, 0, make_span(b)); + A.getpixel(1, 1, 0, make_span(a)); OIIO_CHECK_EQUAL(b[0], gray[0]); OIIO_CHECK_EQUAL(b[1], a[0]); OIIO_CHECK_EQUAL(b[2], a[1]); - B.getpixel(3, 4, 0, b); - A.getpixel(2, 3, 0, a); + B.getpixel(3, 4, 0, make_span(b)); + A.getpixel(2, 3, 0, make_span(a)); OIIO_CHECK_EQUAL(b[0], gray[0]); OIIO_CHECK_EQUAL(b[1], a[0]); OIIO_CHECK_EQUAL(b[2], a[1]); @@ -784,7 +784,7 @@ test_isMonochrome() // Now introduce a tiny difference const float another[CHANNELS] = { 0.25f, 0.25f, 0.26f }; - A.setpixel(2, 2, 0, another, 3); + A.setpixel(2, 2, 0, make_span(another)); // It should still pass if within the threshold OIIO_CHECK_EQUAL(ImageBufAlgo::isMonochrome(A, 0.015f), true); // But not with lower threshold @@ -807,10 +807,10 @@ test_computePixelStats() std::cout << "test computePixelStats\n"; ImageBuf img(ImageSpec(2, 2, 3, TypeDesc::FLOAT)); float black[3] = { 0, 0, 0 }, white[3] = { 1, 1, 1 }; - img.setpixel(0, 0, black); - img.setpixel(1, 0, white); - img.setpixel(0, 1, black); - img.setpixel(1, 1, white); + img.setpixel(0, 0, make_span(black)); + img.setpixel(1, 0, make_span(white)); + img.setpixel(0, 1, make_span(black)); + img.setpixel(1, 1, make_span(white)); auto stats = ImageBufAlgo::computePixelStats(img); for (int c = 0; c < 3; ++c) { OIIO_CHECK_EQUAL(stats.min[c], 0.0f); diff --git a/src/libOpenImageIO/imagebufalgo_xform.cpp b/src/libOpenImageIO/imagebufalgo_xform.cpp index 79a8e14237..5de60fb497 100644 --- a/src/libOpenImageIO/imagebufalgo_xform.cpp +++ b/src/libOpenImageIO/imagebufalgo_xform.cpp @@ -1094,7 +1094,7 @@ resample_(ImageBuf& dst, const ImageBuf& src, bool interpolate, ROI roi, float dstfh = dstspec.full_height; float dstpixelwidth = 1.0f / dstfw; float dstpixelheight = 1.0f / dstfh; - float* pel = OIIO_ALLOCA(float, nchannels); + span pel = OIIO_ALLOCA_SPAN(float, nchannels); ImageBuf::Iterator out(dst, roi); ImageBuf::ConstIterator srcpel(src); diff --git a/src/libOpenImageIO/maketexture.cpp b/src/libOpenImageIO/maketexture.cpp index 75b11c8aa5..55156a935b 100644 --- a/src/libOpenImageIO/maketexture.cpp +++ b/src/libOpenImageIO/maketexture.cpp @@ -138,8 +138,8 @@ datestring(time_t t) template static void -interppixel_NDC_clamped(const ImageBuf& buf, float x, float y, float* pixel, - bool envlatlmode) +interppixel_NDC_clamped(const ImageBuf& buf, float x, float y, + span pixel, bool envlatlmode) { int fx = buf.spec().full_x; int fy = buf.spec().full_y; @@ -190,7 +190,7 @@ interppixel_NDC_clamped(const ImageBuf& buf, float x, float y, float* pixel, } // Bilinearly interpolate - bilerp(p0, p1, p2, p3, xfrac, yfrac, n, pixel); + bilerp(p0, p1, p2, p3, xfrac, yfrac, n, pixel.data()); } @@ -211,12 +211,12 @@ resize_block_(ImageBuf& dst, const ImageBuf& src, ROI roi, bool envlatlmode) || srcspec.z + srcspec.depth < srcspec.full_z + srcspec.full_depth); const ImageSpec& dstspec(dst.spec()); - float* pel = OIIO_ALLOCA(float, dstspec.nchannels); - float xoffset = (float)dstspec.full_x; - float yoffset = (float)dstspec.full_y; - float xscale = 1.0f / (float)dstspec.full_width; - float yscale = 1.0f / (float)dstspec.full_height; - int nchannels = dst.nchannels(); + span pel = OIIO_ALLOCA_SPAN(float, dstspec.nchannels); + float xoffset = (float)dstspec.full_x; + float yoffset = (float)dstspec.full_y; + float xscale = 1.0f / (float)dstspec.full_width; + float yscale = 1.0f / (float)dstspec.full_height; + int nchannels = dst.nchannels(); OIIO_DASSERT(dst.spec().format == TypeFloat); ImageBuf::Iterator d(dst, roi); for (int y = y0; y < y1; ++y) { @@ -335,7 +335,7 @@ check_nan_block(const ImageBuf& src, ROI roi, int& found_nonfinite) { int x0 = roi.xbegin, x1 = roi.xend, y0 = roi.ybegin, y1 = roi.yend; const ImageSpec& spec(src.spec()); - float* pel = OIIO_ALLOCA(float, spec.nchannels); + span pel = OIIO_ALLOCA_SPAN(float, spec.nchannels); for (int y = y0; y < y1; ++y) { for (int x = x0; x < x1; ++x) { src.getpixel(x, y, pel); @@ -385,7 +385,7 @@ lightprobe_to_envlatl(ImageBuf& dst, const ImageBuf& src, bool y_is_up, const ImageSpec& dstspec(dst.spec()); int nchannels = dstspec.nchannels; - float* pixel = OIIO_ALLOCA(float, nchannels); + span pixel = OIIO_ALLOCA_SPAN(float, nchannels); float dw = dstspec.width, dh = dstspec.height; for (ImageBuf::Iterator d(dst, roi); !d.done(); ++d) { Imath::V3f V = latlong_to_dir((d.x() + 0.5f) / dw, @@ -445,7 +445,7 @@ normal_gradient(const ImageBuf& src, const ImageBuf::Iterator& dstpix, { // assume a normal defined in the tangent space float n[3]; - src.getpixel(dstpix.x(), dstpix.y(), n, 3); + src.getpixel(dstpix.x(), dstpix.y(), make_span(n)); *h = -1.0f; *dh_ds = -n[0] / n[2]; *dh_dt = -n[1] / n[2]; @@ -540,9 +540,9 @@ bump_to_bumpslopes(ImageBuf& dst, const ImageBuf& src, static void fix_latl_edges(ImageBuf& buf) { - int n = buf.nchannels(); - float* left = OIIO_ALLOCA(float, n); - float* right = OIIO_ALLOCA(float, n); + int n = buf.nchannels(); + span left = OIIO_ALLOCA_SPAN(float, n); + span right = OIIO_ALLOCA_SPAN(float, n); // Make the whole first and last row be solid, since they are exactly // on the pole diff --git a/src/libtexture/imagecache.cpp b/src/libtexture/imagecache.cpp index bc03cbece6..46ab9f1173 100644 --- a/src/libtexture/imagecache.cpp +++ b/src/libtexture/imagecache.cpp @@ -964,9 +964,9 @@ ImageCacheFile::read_unmipped(ImageCachePerThreadInfo* thread_info, // lookups form the next finer subimage. const ImageSpec& upspec( this->spec(subimage, miplevel - 1)); // next higher level - float* bilerppels = OIIO_ALLOCA(float, 4 * nchans); - float* resultpel = OIIO_ALLOCA(float, nchans); - bool ok = true; + span bilerppels = OIIO_ALLOCA_SPAN(float, 4 * nchans); + span resultpel = OIIO_ALLOCA_SPAN(float, nchans); + bool ok = true; // FIXME(volume) -- loop over z, too for (int j = y0; j <= y1; ++j) { float yf = (j + 0.5f) / spec.full_height; @@ -979,15 +979,19 @@ ImageCacheFile::read_unmipped(ImageCachePerThreadInfo* thread_info, ok &= imagecache().get_pixels(this, thread_info, subimage, miplevel - 1, xlow, xlow + 2, ylow, ylow + 2, 0, 1, chbegin, chend, - TypeDesc::FLOAT, bilerppels); - bilerp(bilerppels + 0, bilerppels + nchans, bilerppels + 2 * nchans, - bilerppels + 3 * nchans, xfrac, yfrac, nchans, resultpel); + TypeFloat, bilerppels.data()); + bilerp(bilerppels.data() + 0, bilerppels.data() + nchans, + bilerppels.data() + 2 * nchans, + bilerppels.data() + 3 * nchans, xfrac, yfrac, nchans, + resultpel.data()); lores.setpixel(i - x0, j - y0, resultpel); } } // Now convert and copy those values out to the caller's buffer - lores.get_pixels(ROI(0, tw, 0, th, 0, 1, 0, nchans), format, data); + lores.get_pixels(ROI(0, tw, 0, th, 0, 1, 0, nchans), format, + make_span((std::byte*)data, + size_t(tw * th * nchans) * format.size())); // Restore the microcache to the way it was before. thread_info->tile = oldtile; diff --git a/src/libtexture/texturesys.cpp b/src/libtexture/texturesys.cpp index 9770b6a95e..300f1b038b 100644 --- a/src/libtexture/texturesys.cpp +++ b/src/libtexture/texturesys.cpp @@ -3349,15 +3349,15 @@ TextureSystemImpl::visualize_ellipse(const std::string& name, float dsdx, float x = (i - w / 2) / scale; float d2 = ABCF[0] * x * x + ABCF[1] * x * y + ABCF[2] * y * y; if (d2 < 1.0f) - ib.setpixel(i, h - 1 - j, dark); + ib.setpixel(i, h - 1 - j, make_span(dark)); } } // Draw red and green axes for the dx and dy derivatives, respectively ImageBufAlgo::render_line(ib, w / 2, h / 2, w / 2 + int(dsdx * scale), - h / 2 - int(dtdx * scale), red); + h / 2 - int(dtdx * scale), make_span(red)); ImageBufAlgo::render_line(ib, w / 2, h / 2, w / 2 + int(dsdy * scale), - h / 2 - int(dtdy * scale), green); + h / 2 - int(dtdy * scale), make_span(green)); // Draw yellow and blue axes for the ellipse axes, with blur ImageBufAlgo::render_line(ib, w / 2, h / 2, diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index 3e8a261054..bdeb415aeb 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -202,6 +202,27 @@ Oiiotool::clear_options() +ColorConfig& +Oiiotool::colorconfig() +{ + // It's safe to check the pointer and if it exists, return it, since + // once it's created, it will never be changed. + if (ColorConfig* cc = m_colorconfig.get()) + return *cc; + + // Otherwise, we need to create it. But we need to be thread-safe. + static std::mutex colorconfig_mutex; + std::lock_guard lock(colorconfig_mutex); + if (!m_colorconfig) { + if (debug) + print("oiiotool Creating ColorConfig\n"); + m_colorconfig.reset(new ColorConfig); + } + return *m_colorconfig.get(); +} + + + void Oiiotool::clear_input_config() { @@ -2192,9 +2213,9 @@ static void set_colorconfig(Oiiotool& ot, cspan argv) { OIIO_DASSERT(argv.size() == 2); - ot.colorconfig.reset(argv[1]); - if (ot.colorconfig.has_error()) { - ot.errorfmt("--colorconfig", "{}", ot.colorconfig.geterror()); + ot.colorconfig().reset(argv[1]); + if (ot.colorconfig().has_error()) { + ot.errorfmt("--colorconfig", "{}", ot.colorconfig().geterror()); } } @@ -2279,7 +2300,7 @@ class OpColorConvert final : public OiiotoolOp { } bool ok = ImageBufAlgo::colorconvert(*img[0], *img[1], fromspace, tospace, unpremult, contextkey, - contextvalue, &ot.colorconfig); + contextvalue, &ot.colorconfig()); if (!ok && !strict) { // The color transform failed, but we were told not to be // strict, so ignore the error and just copy destination to @@ -2354,7 +2375,7 @@ OIIOTOOL_OP(ociolook, 1, [&](OiiotoolOp& op, span img) { tospace = img[1]->spec().get_string_attribute("oiio:Colorspace"); return ImageBufAlgo::ociolook(*img[0], *img[1], lookname, fromspace, tospace, unpremult, inverse, contextkey, - contextvalue, &ot.colorconfig); + contextvalue, &ot.colorconfig()); }); @@ -2373,7 +2394,8 @@ OIIOTOOL_OP(ociodisplay, 1, [&](OiiotoolOp& op, span img) { fromspace = img[1]->spec().get_string_attribute("oiio:Colorspace"); return ImageBufAlgo::ociodisplay(*img[0], *img[1], displayname, viewname, fromspace, looks, unpremult, inverse, - contextkey, contextvalue, &ot.colorconfig); + contextkey, contextvalue, + &ot.colorconfig()); }); @@ -2384,7 +2406,7 @@ OIIOTOOL_OP(ociofiletransform, 1, [&](OiiotoolOp& op, span img) { bool inverse = op.options().get_int("inverse"); bool unpremult = op.options().get_int("unpremult"); return ImageBufAlgo::ociofiletransform(*img[0], *img[1], name, unpremult, - inverse, &ot.colorconfig); + inverse, &ot.colorconfig()); }); @@ -2398,7 +2420,7 @@ OIIOTOOL_OP(ocionamedtransform, 1, [&](OiiotoolOp& op, span img) { bool inverse = op.options().get_int("inverse"); return ImageBufAlgo::ocionamedtransform(*img[0], *img[1], name, unpremult, inverse, contextkey, contextvalue, - &ot.colorconfig); + &ot.colorconfig()); }); @@ -5161,7 +5183,7 @@ input_file(Oiiotool& ot, cspan argv) if (autocc) { // Try to deduce the color space it's in std::string colorspace( - ot.colorconfig.getColorSpaceFromFilepath(filename)); + ot.colorconfig().getColorSpaceFromFilepath(filename)); if (colorspace.size() && ot.debug) print(" From {}, we deduce color space \"{}\"\n", filename, colorspace); @@ -5173,9 +5195,9 @@ input_file(Oiiotool& ot, cspan argv) print(" Metadata of {} indicates color space \"{}\"\n", colorspace, filename); } - std::string linearspace = ot.colorconfig.resolve("linear"); + std::string linearspace = ot.colorconfig().resolve("linear"); if (colorspace.size() - && !ot.colorconfig.equivalent(colorspace, linearspace)) { + && !ot.colorconfig().equivalent(colorspace, linearspace)) { std::string cmd = "colorconvert:strict=0"; if (autoccunpremult) cmd += ":unpremult=1"; @@ -5459,12 +5481,12 @@ output_file(Oiiotool& ot, cspan argv) // automatically set -d based on the name if --autocc is used. bool autocc = fileoptions.get_int("autocc", ot.autocc); bool autoccunpremult = fileoptions.get_int("unpremult", ot.autoccunpremult); - std::string outcolorspace = ot.colorconfig.getColorSpaceFromFilepath( + std::string outcolorspace = ot.colorconfig().getColorSpaceFromFilepath( filename); if (autocc && outcolorspace.size()) { TypeDesc type; int bits; - type = ot.colorconfig.getColorSpaceDataType(outcolorspace, &bits); + type = ot.colorconfig().getColorSpaceDataType(outcolorspace, &bits); if (type.basetype != TypeDesc::UNKNOWN) { if (ot.debug) std::cout << " Deduced data type " << type << " (" << bits @@ -5482,7 +5504,7 @@ output_file(Oiiotool& ot, cspan argv) } } if (autocc) { - string_view linearspace = ot.colorconfig.resolve("linear"); + string_view linearspace = ot.colorconfig().resolve("linear"); std::string currentspace = ir->spec()->get_string_attribute("oiio:ColorSpace", linearspace); // Special cases where we know formats should be particular color @@ -5978,24 +6000,24 @@ print_ocio_info(Oiiotool& ot, std::ostream& out) using Strutil::print; int columns = Sysutil::terminal_columns() - 1; - int ociover = ot.colorconfig.OpenColorIO_version_hex(); - if (ociover) + ColorConfig& colorconfig = ot.colorconfig(); + if (int ociover = colorconfig.OpenColorIO_version_hex()) out << "OpenColorIO " << (ociover >> 24) << '.' << ((ociover >> 16) & 0xff) << '.' << ((ociover >> 8) & 0xff); else out << "No OpenColorIO"; - out << "\nColor config: " << ot.colorconfig.configname() << "\n"; + out << "\nColor config: " << colorconfig.configname() << "\n"; out << "Known color spaces: \n"; - const char* linear = ot.colorconfig.getColorSpaceNameByRole("linear"); - for (int i = 0, e = ot.colorconfig.getNumColorSpaces(); i < e; ++i) { - const char* n = ot.colorconfig.getColorSpaceNameByIndex(i); + const char* linear = colorconfig.getColorSpaceNameByRole("linear"); + for (int i = 0, e = colorconfig.getNumColorSpaces(); i < e; ++i) { + const char* n = colorconfig.getColorSpaceNameByIndex(i); out << " - " << quote_if_spaces(n); - if ((linear && !ot.colorconfig.equivalent(n, "linear") - && ot.colorconfig.equivalent(n, linear)) - || ot.colorconfig.isColorSpaceLinear(n)) + if ((linear && !colorconfig.equivalent(n, "linear") + && colorconfig.equivalent(n, linear)) + || colorconfig.isColorSpaceLinear(n)) out << " (linear)"; out << "\n"; - auto aliases = ot.colorconfig.getAliases(n); + auto aliases = colorconfig.getAliases(n); if (aliases.size()) { std::stringstream s; s << " aliases: " << join_with_quotes(aliases, ", "); @@ -6003,41 +6025,41 @@ print_ocio_info(Oiiotool& ot, std::ostream& out) } } - int roles = ot.colorconfig.getNumRoles(); + int roles = colorconfig.getNumRoles(); if (roles) { print(out, "Known roles:\n"); for (int i = 0; i < roles; ++i) { - const char* r = ot.colorconfig.getRoleByIndex(i); + const char* r = colorconfig.getRoleByIndex(i); print(out, " - {} -> {}\n", quote_if_spaces(r), - quote_if_spaces(ot.colorconfig.getColorSpaceNameByRole(r))); + quote_if_spaces(colorconfig.getColorSpaceNameByRole(r))); } } - int nlooks = ot.colorconfig.getNumLooks(); + int nlooks = colorconfig.getNumLooks(); if (nlooks) { print(out, "Known looks:\n"); for (int i = 0; i < nlooks; ++i) print(out, " - {}\n", - quote_if_spaces(ot.colorconfig.getLookNameByIndex(i))); + quote_if_spaces(colorconfig.getLookNameByIndex(i))); } - const char* default_display = ot.colorconfig.getDefaultDisplayName(); - int ndisplays = ot.colorconfig.getNumDisplays(); + const char* default_display = colorconfig.getDefaultDisplayName(); + int ndisplays = colorconfig.getNumDisplays(); if (ndisplays) { out << "Known displays: (* indicates default)\n"; for (int i = 0; i < ndisplays; ++i) { - const char* d = ot.colorconfig.getDisplayNameByIndex(i); + const char* d = colorconfig.getDisplayNameByIndex(i); out << " - " << quote_if_spaces(d); if (!strcmp(d, default_display)) out << " (*)"; - const char* default_view = ot.colorconfig.getDefaultViewName(d); - int nviews = ot.colorconfig.getNumViews(d); + const char* default_view = colorconfig.getDefaultViewName(d); + int nviews = colorconfig.getNumViews(d); if (nviews) { out << "\n "; std::stringstream s; s << "views: "; for (int i = 0; i < nviews; ++i) { - const char* v = ot.colorconfig.getViewNameByIndex(d, i); + const char* v = colorconfig.getViewNameByIndex(d, i); s << quote_if_spaces(v); if (!strcmp(v, default_view)) s << " (*)"; @@ -6050,15 +6072,15 @@ print_ocio_info(Oiiotool& ot, std::ostream& out) } } - int nnamed_transforms = ot.colorconfig.getNumNamedTransforms(); + int nnamed_transforms = colorconfig.getNumNamedTransforms(); if (nnamed_transforms) { out << "Named transforms:\n"; for (int i = 0; i < nnamed_transforms; ++i) { - const char* x = ot.colorconfig.getNamedTransformNameByIndex(i); + const char* x = colorconfig.getNamedTransformNameByIndex(i); out << " - " << quote_if_spaces(x) << "\n"; } } - if (!ot.colorconfig.supportsOpenColorIO()) + if (!colorconfig.supportsOpenColorIO()) out << "No OpenColorIO support was enabled at build time.\n"; } @@ -6115,12 +6137,12 @@ print_help_end(Oiiotool& ot, std::ostream& out) out << formatted_format_list("Input", "input_format_list") << "\n"; out << formatted_format_list("Output", "output_format_list") << "\n"; - if (int ociover = ot.colorconfig.OpenColorIO_version_hex()) + if (int ociover = ot.colorconfig().OpenColorIO_version_hex()) print(out, "OpenColorIO {}.{}.{}\n", (ociover >> 24), ((ociover >> 16) & 0xff), ((ociover >> 8) & 0xff)); else print(out, "No OpenColorIO\n"); - print(out, " Color config: {}\n", ot.colorconfig.configname()); + print(out, " Color config: {}\n", ot.colorconfig().configname()); print(out, " Run `oiiotool --colorconfiginfo` for a " "full color management inventory.\n"); diff --git a/src/oiiotool/oiiotool.h b/src/oiiotool/oiiotool.h index 8f05bf58d5..6a56d5df92 100644 --- a/src/oiiotool/oiiotool.h +++ b/src/oiiotool/oiiotool.h @@ -141,7 +141,7 @@ class Oiiotool { std::vector image_stack; // stack of previous images std::map image_labels; // labeled images std::shared_ptr imagecache; // back ptr to ImageCache - ColorConfig colorconfig; // OCIO color config + std::unique_ptr m_colorconfig; // OCIO color config Timer total_runtime; // total_readtime is the amount of time for direct reads, and does not // count time spent inside ImageCache. @@ -370,6 +370,8 @@ class Oiiotool { // Merge stats from another Oiiotool void merge_stats(const Oiiotool& ot); + ColorConfig& colorconfig(); + private: CallbackFunction m_pending_callback; std::vector m_pending_argv; diff --git a/src/python/py_imagebuf.cpp b/src/python/py_imagebuf.cpp index 263b0e82d4..409e0ba53d 100644 --- a/src/python/py_imagebuf.cpp +++ b/src/python/py_imagebuf.cpp @@ -33,30 +33,44 @@ ImageBuf_from_buffer(const py::buffer& buffer) return ib; } + int width = 1, height = 1, depth = 1, nchans = 1; + stride_t xstride = AutoStride, ystride = AutoStride, zstride = AutoStride; if (info.ndim == 3) { // Assume [y][x][c] - ImageSpec spec(info.shape[1], info.shape[0], info.shape[2], format); - ib.reset(spec, InitializePixels::No); - ib.set_pixels(get_roi(spec), format, info.ptr, info.strides[1], - info.strides[0]); + width = info.shape[1]; + height = info.shape[0]; + nchans = info.shape[2]; + xstride = info.strides[1]; + ystride = info.strides[0]; } else if (info.ndim == 2) { // Assume [y][x], single channel - ImageSpec spec(info.shape[1], info.shape[0], 1, format); - ib.reset(spec, InitializePixels::No); - ib.set_pixels(get_roi(spec), format, info.ptr, info.strides[1], - info.strides[0]); + width = info.shape[1]; + height = info.shape[0]; + xstride = info.strides[1]; + ystride = info.strides[0]; } else if (info.ndim == 4) { // Assume volume [z][y][x][c] - ImageSpec spec(info.shape[2], info.shape[1], info.shape[3], format); - spec.depth = info.shape[0]; - spec.full_depth = spec.depth; - ib.reset(spec, InitializePixels::No); - ib.set_pixels(get_roi(spec), format, info.ptr, info.strides[2], - info.strides[1], info.strides[0]); + width = info.shape[2]; + height = info.shape[1]; + depth = info.shape[0]; + nchans = info.shape[3]; + xstride = info.strides[2]; + ystride = info.strides[1]; + zstride = info.strides[0]; } else { ib.errorfmt( "ImageBuf-from-numpy-array must have 2, 3, or 4 dimensions"); + return ib; } + + ImageSpec spec(width, height, nchans, format); + spec.depth = depth; + spec.full_depth = depth; + ib.reset(spec, InitializePixels::No); + auto bufspan = cspan_from_buffer(info.ptr, format, nchans, width, height, + depth, xstride, ystride, zstride); + ib.set_pixels(get_roi(spec), format, bufspan, nullptr, xstride, ystride, + zstride); return ib; } @@ -68,9 +82,9 @@ ImageBuf_getpixel(const ImageBuf& buf, int x, int y, int z = 0, { ImageBuf::WrapMode wrap = ImageBuf::WrapMode_from_string(wrapname); int nchans = buf.nchannels(); - float* pixel = OIIO_ALLOCA(float, nchans); - buf.getpixel(x, y, z, pixel, nchans, wrap); - return C_to_tuple(pixel, nchans); + span pixel = OIIO_ALLOCA_SPAN(float, nchans); + buf.getpixel(x, y, z, pixel, wrap); + return C_to_tuple(pixel); } @@ -81,9 +95,9 @@ ImageBuf_interppixel(const ImageBuf& buf, float x, float y, { ImageBuf::WrapMode wrap = ImageBuf::WrapMode_from_string(wrapname); int nchans = buf.nchannels(); - float* pixel = OIIO_ALLOCA(float, nchans); + span pixel = OIIO_ALLOCA_SPAN(float, nchans); buf.interppixel(x, y, pixel, wrap); - return C_to_tuple(pixel, nchans); + return C_to_tuple(pixel); } @@ -94,9 +108,9 @@ ImageBuf_interppixel_NDC(const ImageBuf& buf, float x, float y, { ImageBuf::WrapMode wrap = ImageBuf::WrapMode_from_string(wrapname); int nchans = buf.nchannels(); - float* pixel = OIIO_ALLOCA(float, nchans); + span pixel = OIIO_ALLOCA_SPAN(float, nchans); buf.interppixel_NDC(x, y, pixel, wrap); - return C_to_tuple(pixel, nchans); + return C_to_tuple(pixel); } @@ -107,9 +121,9 @@ ImageBuf_interppixel_bicubic(const ImageBuf& buf, float x, float y, { ImageBuf::WrapMode wrap = ImageBuf::WrapMode_from_string(wrapname); int nchans = buf.nchannels(); - float* pixel = OIIO_ALLOCA(float, nchans); + span pixel = OIIO_ALLOCA_SPAN(float, nchans); buf.interppixel_bicubic(x, y, pixel, wrap); - return C_to_tuple(pixel, nchans); + return C_to_tuple(pixel); } @@ -120,9 +134,9 @@ ImageBuf_interppixel_bicubic_NDC(const ImageBuf& buf, float x, float y, { ImageBuf::WrapMode wrap = ImageBuf::WrapMode_from_string(wrapname); int nchans = buf.nchannels(); - float* pixel = OIIO_ALLOCA(float, nchans); + span pixel = OIIO_ALLOCA_SPAN(float, nchans); buf.interppixel_bicubic_NDC(x, y, pixel, wrap); - return C_to_tuple(pixel, nchans); + return C_to_tuple(pixel); } @@ -164,8 +178,8 @@ ImageBuf_get_pixels(const ImageBuf& buf, TypeDesc format, ROI roi = ROI::All()) roi.chend = std::min(roi.chend, buf.nchannels()); size_t size = (size_t)roi.npixels() * roi.nchannels() * format.size(); - std::unique_ptr data(new char[size]); - if (buf.get_pixels(roi, format, &data[0])) + std::unique_ptr data(new std::byte[size]); + if (buf.get_pixels(roi, format, make_span(data.get(), size))) return make_numpy_array(format, data.release(), buf.spec().depth > 1 ? 4 : 3, roi.nchannels(), roi.width(), roi.height(), roi.depth()); @@ -217,8 +231,11 @@ ImageBuf_set_pixels_buffer(ImageBuf& self, ROI roi, py::buffer& buffer) } py::gil_scoped_release gil; - return self.set_pixels(roi, buf.format, buf.data, buf.xstride, buf.ystride, - buf.zstride); + auto bufspan = cspan_from_buffer(buf.data, buf.format, roi.nchannels(), + roi.width(), roi.height(), roi.depth(), + buf.xstride, buf.ystride, buf.zstride); + return self.set_pixels(roi, buf.format, bufspan, nullptr, buf.xstride, + buf.ystride, buf.zstride); } diff --git a/src/python/py_oiio.h b/src/python/py_oiio.h index e80fb81c76..93189feb30 100644 --- a/src/python/py_oiio.h +++ b/src/python/py_oiio.h @@ -390,6 +390,18 @@ C_to_tuple(cspan vals) } +template +inline py::tuple +C_to_tuple(span vals) +{ + size_t size = vals.size(); + py::tuple result(size); + for (size_t i = 0; i < size; ++i) + result[i] = typename PyTypeForCType::type(vals[i]); + return result; +} + + template inline py::tuple C_to_tuple(const T* vals, size_t size) diff --git a/src/term.imageio/termoutput.cpp b/src/term.imageio/termoutput.cpp index b750590fe2..2de08062db 100644 --- a/src/term.imageio/termoutput.cpp +++ b/src/term.imageio/termoutput.cpp @@ -93,7 +93,12 @@ TermOutput::write_scanline(int y, int z, TypeDesc format, const void* data, } ROI roi(m_spec.x, m_spec.x + m_spec.width, y, y + 1, z, z + 1, 0, m_spec.nchannels); - return m_buf.set_pixels(roi, format, data, xstride); + // N.B. Unsafely assume an impied span given pointer and sizes/strides. + // Some day, write_scanline should be updated to take a span directly. + auto dataspan = cspan_from_buffer(data, format, m_spec.nchannels, + roi.width(), roi.height(), roi.depth(), + xstride, AutoStride, AutoStride); + return m_buf.set_pixels(roi, format, dataspan, nullptr, xstride); } @@ -106,7 +111,13 @@ TermOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, std::min(y + m_spec.tile_height, m_spec.y + m_spec.height), z, std::min(z + m_spec.tile_depth, m_spec.z + m_spec.depth), 0, m_spec.nchannels); - return m_buf.set_pixels(roi, format, data, xstride, ystride, zstride); + // N.B. Unsafely assume an impied span given pointer and sizes/strides. + // Some day, write_tile should be updated to take a span directly. + auto dataspan = cspan_from_buffer(data, format, m_spec.nchannels, + roi.width(), roi.height(), roi.depth(), + xstride, ystride, zstride); + return m_buf.set_pixels(roi, format, dataspan, nullptr, xstride, ystride, + zstride); } @@ -177,9 +188,9 @@ TermOutput::output() print(s, "P3\n{} {}\n255\n", m_buf.spec().width, m_buf.spec().height); for (int y = m_buf.ybegin(), ye = m_buf.yend(); y < ye; y += 1) { for (int x = m_buf.xbegin(), xe = m_buf.xend(); x < xe; ++x) { - unsigned char rgb[3]; + uint8_t rgb[3]; m_buf.get_pixels(ROI(x, x + 1, y, y + 1, 0, 1, 0, 3), - TypeDesc::UINT8, &rgb); + make_span(rgb)); print(s, "{} {} {}\n", int(rgb[0]), int(rgb[1]), int(rgb[2])); } } @@ -195,9 +206,9 @@ TermOutput::output() int z = m_buf.spec().z; for (int y = m_buf.ybegin(), ye = m_buf.yend(); y < ye; y += 2) { for (int x = m_buf.xbegin(), xe = m_buf.xend(); x < xe; ++x) { - unsigned char rgb[2][3]; + uint8_t rgb[2][3]; m_buf.get_pixels(ROI(x, x + 1, y, y + 2, z, z + 1, 0, 3), - TypeDesc::UINT8, &rgb); + make_span((uint8_t*)rgb, 2 * 3)); print(outfile, "{}{}\x5C\x75\x32\x35\x38\x30", term.ansi_fgcolor(rgb[0][0], rgb[0][1], rgb[0][2]), term.ansi_bgcolor(rgb[1][0], rgb[1][1], rgb[1][2])); @@ -213,9 +224,9 @@ TermOutput::output() int z = m_buf.spec().z; for (int y = m_buf.ybegin(), ye = m_buf.yend(); y < ye; ++y) { for (int x = m_buf.xbegin(), xe = m_buf.xend(); x < xe; ++x) { - unsigned char rgb[3]; + uint8_t rgb[3]; m_buf.get_pixels(ROI(x, x + 1, y, y + 1, z, z + 1, 0, 3), - TypeDesc::UINT8, &rgb); + make_span(rgb)); print(outfile, "{} ", term.ansi_bgcolor(rgb[0], rgb[1], rgb[2])); } @@ -233,7 +244,7 @@ TermOutput::output() for (int x = m_buf.xbegin(), xe = m_buf.xend(); x < xe; ++x) { simd::vfloat4 rgborig; m_buf.get_pixels(ROI(x, x + 1, y, y + 1, z, z + 1, 0, 3), - TypeDesc::FLOAT, &rgborig); + span((float*)&rgborig, 4)); rgborig += leftover; simd::vfloat4 rgb = 5.0f * rgborig; simd::vint4 rgbi; @@ -255,7 +266,7 @@ TermOutput::output() for (int x = m_buf.xbegin(), xe = m_buf.xend(); x < xe; ++x) { simd::vfloat4 rgborig; m_buf.get_pixels(ROI(x, x + 1, y, y + 1, z, z + 1, 0, 3), - TypeDesc::FLOAT, &rgborig); + span((float*)&rgborig, 4)); simd::vfloat4 rgb = 5.0f * rgborig; simd::vint4 rgbi; OIIO_MAYBE_UNUSED simd::vfloat4 frac = floorfrac(rgb, &rgbi); diff --git a/src/testtex/testtex.cpp b/src/testtex/testtex.cpp index 95eebb43f7..a1aa94b75b 100644 --- a/src/testtex/testtex.cpp +++ b/src/testtex/testtex.cpp @@ -722,10 +722,10 @@ plain_tex_region(ImageBuf& image, ustring filename, Mapping2D mapping, // Save filtered pixels back to the image. for (int i = 0; i < nchannels; ++i) result[i] *= scalefactor; - image.setpixel(p.x(), p.y(), result); + image.setpixel(p.x(), p.y(), make_span(result, nchannels)); if (test_derivs) { - image_ds->setpixel(p.x(), p.y(), dresultds); - image_dt->setpixel(p.x(), p.y(), dresultdt); + image_ds->setpixel(p.x(), p.y(), make_span(dresultds, nchannels)); + image_dt->setpixel(p.x(), p.y(), make_span(dresultdt, nchannels)); } } } @@ -985,7 +985,7 @@ tex3d_region(ImageBuf& image, ustring filename, Mapping3D mapping, ROI roi) // Save filtered pixels back to the image. for (int i = 0; i < nchannels; ++i) result[i] *= scalefactor; - image.setpixel(p.x(), p.y(), result); + image.setpixel(p.x(), p.y(), make_span(result, nchannels)); } } @@ -1161,11 +1161,11 @@ env_region(ImageBuf& image, ustring filename, MappingEnv mapping, // Save filtered pixels back to the image. for (int i = 0; i < nchannels; ++i) result[i] *= scalefactor; - image.setpixel(p.x(), p.y(), result); + image.setpixel(p.x(), p.y(), make_span(result, nchannels)); if (image_ds) - image_ds->setpixel(p.x(), p.y(), dresultds); + image_ds->setpixel(p.x(), p.y(), make_span(dresultds, nchannels)); if (image_dt) - image_dt->setpixel(p.x(), p.y(), dresultdt); + image_dt->setpixel(p.x(), p.y(), make_span(dresultdt, nchannels)); } } @@ -1381,7 +1381,8 @@ test_getimagespec_gettexels(ustring filename) for (int y = 0; y < h; ++y) for (int x = 0; x < w; ++x) { imagesize_t texoffset = (y * w + x) * spec.nchannels; - buf.setpixel(x, y, &tmp[texoffset]); + buf.setpixel(x, y, + make_span(tmp.data() + texoffset, spec.nchannels)); } TypeDesc fmt(dataformatname); if (fmt != TypeDesc::UNKNOWN) diff --git a/testsuite/oiiotool-control/ref/out.txt b/testsuite/oiiotool-control/ref/out.txt index ba8028348d..e3e0fecb21 100644 --- a/testsuite/oiiotool-control/ref/out.txt +++ b/testsuite/oiiotool-control/ref/out.txt @@ -176,6 +176,7 @@ Begin sequence iteration 0 copyB.#.jpg -> copyB.0001.jpg Reading ./copyA.0001.jpg Output: copyB.0001.jpg +oiiotool Creating ColorConfig Writing copyB.0001.jpg Begin sequence iteration 1 @@ -183,6 +184,7 @@ Begin sequence iteration 1 copyB.#.jpg -> copyB.0002.jpg Reading ./copyA.0002.jpg Output: copyB.0002.jpg +oiiotool Creating ColorConfig Writing copyB.0002.jpg Begin sequence iteration 2 @@ -190,6 +192,7 @@ Begin sequence iteration 2 copyB.#.jpg -> copyB.0003.jpg Reading ./copyA.0003.jpg Output: copyB.0003.jpg +oiiotool Creating ColorConfig Writing copyB.0003.jpg Begin sequence iteration 3 @@ -197,6 +200,7 @@ Begin sequence iteration 3 copyB.#.jpg -> copyB.0004.jpg Reading ./copyA.0004.jpg Output: copyB.0004.jpg +oiiotool Creating ColorConfig Writing copyB.0004.jpg Begin sequence iteration 4 @@ -204,6 +208,7 @@ Begin sequence iteration 4 copyB.#.jpg -> copyB.0005.jpg Reading ./copyA.0005.jpg Output: copyB.0005.jpg +oiiotool Creating ColorConfig Writing copyB.0005.jpg Begin sequence iteration 5 @@ -211,6 +216,7 @@ Begin sequence iteration 5 copyB.#.jpg -> copyB.0006.jpg Reading ./copyA.0006.jpg Output: copyB.0006.jpg +oiiotool Creating ColorConfig Writing copyB.0006.jpg Begin sequence iteration 6 @@ -218,6 +224,7 @@ Begin sequence iteration 6 copyB.#.jpg -> copyB.0007.jpg Reading ./copyA.0007.jpg Output: copyB.0007.jpg +oiiotool Creating ColorConfig Writing copyB.0007.jpg Begin sequence iteration 7 @@ -225,6 +232,7 @@ Begin sequence iteration 7 copyB.#.jpg -> copyB.0008.jpg Reading ./copyA.0008.jpg Output: copyB.0008.jpg +oiiotool Creating ColorConfig Writing copyB.0008.jpg Begin sequence iteration 8 @@ -232,6 +240,7 @@ Begin sequence iteration 8 copyB.#.jpg -> copyB.0009.jpg Reading ./copyA.0009.jpg Output: copyB.0009.jpg +oiiotool Creating ColorConfig Writing copyB.0009.jpg Begin sequence iteration 9 @@ -239,6 +248,7 @@ Begin sequence iteration 9 copyB.#.jpg -> copyB.0010.jpg Reading ./copyA.0010.jpg Output: copyB.0010.jpg +oiiotool Creating ColorConfig Writing copyB.0010.jpg copyA.0001.jpg : 128 x 96, 3 channel, uint8 jpeg