Skip to content

Commit c5af2b5

Browse files
committed
api(IB)!: Add span based ImageBuf methods for getpixel/setpixel
Next round of making safer interfaces. Primarily, this adds new ImageBuf::get_pixels, set_pixels, setpixel, getpixel, interppixel, interppixel_NDC, interppixel_bucibuc, interppixel_bicubic_NDC. The old ones took raw pointers and implied sizes -- these still exist but are considered "unsafe" and aimed at experts and special cases. (If a downstream project wants to be sure not to use them, they can define symbol "OIIO_IMAGEBUF_DEPRECATE_RAW_PTR" before including imagebuf.h to force deprecation warnings.) The new additions instead take a span -- a combined pointer and length that can be bounds-checked (in debug mode). I strongly encourage people to use the new interface, as it forces the caller to think hard about the exact region of memory that's ok to read from or write to, and then for the OIIO side to honor that and enfoce/debug with bounds checks against that if we add such plumbing. Along the way, I added some interesting helper functions: In span.h: * `span_within()` checks that one byte span lies entirely within another. * `check_span()` checks if a pointer and length lies entirely within a span. * `OIIO_ALLOCA_SPAN` is a macro much like OIIO_ALLOCA, except what it returns is a span rather than a raw pointer to the stack-allocated region. In imageio.h: * `span_from_buffer()`/`cspan_from_bufffer()` : Given a raw pointer, pixel data type (TypeDesc), and image dimensions and strides, returns a byte stride describing the extend of memory that encompasses all pixels laid out by that description. I hope the switch to spans it not very onerous for people. I really think that this will help us write more solid and deliberate code to begin with, and when things do go wrong, the various debug-mode checks and assertions that spans provide should help us more quickly identify exactly where buffer overruns and the like occurred. Signed-off-by: Larry Gritz <lg@larrygritz.com>
1 parent a3c6453 commit c5af2b5

21 files changed

+538
-220
lines changed

CMakeLists.txt

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

55
cmake_minimum_required (VERSION 3.15)
66

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

src/include/OpenImageIO/imagebuf.h

Lines changed: 178 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,9 @@ class OIIO_API ImageBuf {
721721
/// @param x/y/z
722722
/// The pixel coordinates.
723723
/// @param c
724-
/// The channel index to retrieve.
724+
/// The channel index to retrieve. If `c` is not in the
725+
/// valid channel range 0..nchannels-1, then `getchannel()`
726+
/// will return 0.
725727
/// @param wrap
726728
/// WrapMode that determines the behavior if the pixel
727729
/// coordinates are outside the data window: `WrapBlack`,
@@ -732,67 +734,114 @@ class OIIO_API ImageBuf {
732734
WrapMode wrap = WrapBlack) const;
733735

734736
/// Retrieve the pixel value by x, y, z pixel indices, placing its
735-
/// contents in `pixel[0..n-1]` where *n* is the smaller of
736-
/// `maxchannels` the actual number of channels stored in the buffer.
737+
/// contents in `pixel[0..n-1]` where *n* is the smaller of the span's
738+
/// size and the actual number of channels stored in the buffer.
737739
///
738740
/// @param x/y/z
739741
/// The pixel coordinates.
740742
/// @param pixel
741-
/// The results are stored in `pixel[0..nchannels-1]`. It is
742-
/// up to the caller to ensure that `pixel` points to enough
743-
/// memory to hold the required number of channels.
744-
/// @param maxchannels
745-
/// Optional clamp to the number of channels retrieved.
743+
/// A span giving the location where results will be stored.
746744
/// @param wrap
747745
/// WrapMode that determines the behavior if the pixel
748746
/// coordinates are outside the data window: `WrapBlack`,
749747
/// `WrapClamp`, `WrapPeriodic`, `WrapMirror`.
750-
void getpixel(int x, int y, int z, float* pixel, int maxchannels = 1000,
748+
void getpixel(int x, int y, int z, span<float> pixel,
751749
WrapMode wrap = WrapBlack) const;
752750

753-
// Simplified version: 2D, black wrap.
751+
/// Simplified version of getpixel(): 2D, black wrap.
752+
void getpixel(int x, int y, span<float> pixel) const
753+
{
754+
getpixel(x, y, 0, pixel);
755+
}
756+
757+
/// Unsafe version of getpixel using raw pointer. Avoid if possible.
758+
OIIO_IB_DEPRECATE_RAW_PTR
759+
void getpixel(int x, int y, int z, float* pixel, int maxchannels = 1000,
760+
WrapMode wrap = WrapBlack) const
761+
{
762+
getpixel(x, y, z, make_span(pixel, size_t(maxchannels)), wrap);
763+
}
764+
765+
/// Unsafe version of getpixel using raw pointer. Avoid if possible.
766+
OIIO_IB_DEPRECATE_RAW_PTR
754767
void getpixel(int x, int y, float* pixel, int maxchannels = 1000) const
755768
{
756769
getpixel(x, y, 0, pixel, maxchannels);
757770
}
758771

759772
/// Sample the image plane at pixel coordinates (x,y), using linear
760-
/// interpolation between pixels, placing the result in `pixel[]`.
773+
/// interpolation between pixels, placing the result in `pixel[0..n-1]`
774+
/// where *n* is the smaller of the span's size and the actual number of
775+
/// channels stored in the buffer.
761776
///
762777
/// @param x/y
763778
/// The pixel coordinates. Note that pixel data values
764779
/// themselves are at the pixel centers, so pixel (i,j) is
765780
/// at image plane coordinate (i+0.5, j+0.5).
766781
/// @param pixel
767-
/// The results are stored in `pixel[0..nchannels-1]`. It is
768-
/// up to the caller to ensure that `pixel` points to enough
769-
/// memory to hold the number of channels in the image.
782+
/// A span giving the location where results will be stored.
770783
/// @param wrap
771784
/// WrapMode that determines the behavior if the pixel
772785
/// coordinates are outside the data window: `WrapBlack`,
773786
/// `WrapClamp`, `WrapPeriodic`, `WrapMirror`.
774-
void interppixel(float x, float y, float* pixel,
787+
void interppixel(float x, float y, span<float> pixel,
775788
WrapMode wrap = WrapBlack) const;
776789

790+
/// Unsafe version of interppixel using raw pointer. Avoid if possible.
791+
OIIO_IB_DEPRECATE_RAW_PTR
792+
void interppixel(float x, float y, float* pixel,
793+
WrapMode wrap = WrapBlack) const
794+
{
795+
interppixel(x, y, make_span(pixel, size_t(nchannels())), wrap);
796+
}
797+
777798
/// Linearly interpolate at NDC coordinates (s,t), where (0,0) is
778799
/// the upper left corner of the display window, (1,1) the lower
779800
/// right corner of the display window.
780801
///
781802
/// @note `interppixel()` uses pixel coordinates (ranging 0..resolution)
782803
/// whereas `interppixel_NDC()` uses NDC coordinates (ranging 0..1).
783-
void interppixel_NDC(float s, float t, float* pixel,
804+
void interppixel_NDC(float s, float t, span<float> pixel,
784805
WrapMode wrap = WrapBlack) const;
785806

807+
/// Unsafe version of interppixel_NDC using raw pointer. Avoid if
808+
/// possible.
809+
OIIO_IB_DEPRECATE_RAW_PTR
810+
void interppixel_NDC(float s, float t, float* pixel,
811+
WrapMode wrap = WrapBlack) const
812+
{
813+
interppixel_NDC(s, t, make_span(pixel, size_t(nchannels())), wrap);
814+
}
815+
786816
/// Bicubic interpolation at pixel coordinates (x,y).
787-
void interppixel_bicubic(float x, float y, float* pixel,
817+
void interppixel_bicubic(float x, float y, span<float> pixel,
788818
WrapMode wrap = WrapBlack) const;
789819

820+
/// Unsafe version of interppixel_bicubic_NDC using raw pointer.
821+
/// Avoid if possible.
822+
OIIO_IB_DEPRECATE_RAW_PTR
823+
void interppixel_bicubic(float x, float y, float* pixel,
824+
WrapMode wrap = WrapBlack) const
825+
{
826+
interppixel_bicubic(x, y, make_span(pixel, size_t(nchannels())), wrap);
827+
}
828+
790829
/// Bicubic interpolation at NDC space coordinates (s,t), where (0,0)
791830
/// is the upper left corner of the display (a.k.a. "full") window,
792831
/// (1,1) the lower right corner of the display window.
793-
void interppixel_bicubic_NDC(float s, float t, float* pixel,
832+
void interppixel_bicubic_NDC(float s, float t, span<float> pixel,
794833
WrapMode wrap = WrapBlack) const;
795834

835+
/// Unsafe version of interppixel_bicubic_NDC using raw pointer.
836+
/// Avoid if possible.
837+
OIIO_IB_DEPRECATE_RAW_PTR
838+
void interppixel_bicubic_NDC(float s, float t, float* pixel,
839+
WrapMode wrap = WrapBlack) const
840+
{
841+
interppixel_bicubic_NDC(s, t, make_span(pixel, size_t(nchannels())),
842+
wrap);
843+
}
844+
796845

797846
/// Set the pixel with coordinates (x,y,0) to have the values in span
798847
/// `pixel[]`. The number of channels copied is the minimum of the span
@@ -805,48 +854,111 @@ class OIIO_API ImageBuf {
805854
/// Set the pixel with coordinates (x,y,z) to have the values in span
806855
/// `pixel[]`. The number of channels copied is the minimum of the span
807856
/// length and the actual number of channels in the image.
808-
void setpixel(int x, int y, int z, cspan<float> pixel)
809-
{
810-
setpixel(x, y, z, pixel.data(), int(pixel.size()));
811-
}
857+
void setpixel(int x, int y, int z, cspan<float> pixel);
812858

813859
/// Set the `i`-th pixel value of the image (out of width*height*depth),
814860
/// from floating-point values in span `pixel[]`. The number of
815861
/// channels copied is the minimum of the span length and the actual
816862
/// number of channels in the image.
817863
void setpixel(int i, cspan<float> pixel)
818864
{
819-
setpixel(i, pixel.data(), int(pixel.size()));
865+
setpixel(spec().x + (i % spec().width), spec().y + (i / spec().width),
866+
pixel);
820867
}
821868

822869
/// Set the pixel with coordinates (x,y,0) to have the values in
823870
/// pixel[0..n-1]. The number of channels copied, n, is the minimum
824871
/// of maxchannels and the actual number of channels in the image.
872+
OIIO_IB_DEPRECATE_RAW_PTR
825873
void setpixel(int x, int y, const float* pixel, int maxchannels = 1000)
826874
{
827-
setpixel(x, y, 0, pixel, maxchannels);
875+
int n = std::min(spec().nchannels, maxchannels);
876+
setpixel(x, y, 0, make_cspan(pixel, size_t(n)));
828877
}
829878

830879
/// Set the pixel with coordinates (x,y,z) to have the values in
831880
/// `pixel[0..n-1]`. The number of channels copied, n, is the minimum
832881
/// of `maxchannels` and the actual number of channels in the image.
882+
OIIO_IB_DEPRECATE_RAW_PTR
833883
void setpixel(int x, int y, int z, const float* pixel,
834-
int maxchannels = 1000);
884+
int maxchannels = 1000)
885+
{
886+
int n = std::min(spec().nchannels, maxchannels);
887+
setpixel(x, y, z, make_cspan(pixel, size_t(n)));
888+
}
835889

836890
/// Set the `i`-th pixel value of the image (out of width*height*depth),
837891
/// from floating-point values in `pixel[]`. Set at most
838892
/// `maxchannels` (will be clamped to the actual number of channels).
839-
void setpixel(int i, const float* pixel, int maxchannels = 1000);
893+
OIIO_IB_DEPRECATE_RAW_PTR
894+
void setpixel(int i, const float* pixel, int maxchannels = 1000)
895+
{
896+
int n = std::min(spec().nchannels, maxchannels);
897+
setpixel(i, make_cspan(pixel, size_t(n)));
898+
}
840899

841900
/// Retrieve the rectangle of pixels spanning the ROI (including
842901
/// channels) at the current subimage and MIP-map level, storing the
843-
/// pixel values beginning at the address specified by result and with
844-
/// the given strides (by default, AutoStride means the usual contiguous
845-
/// packing of pixels) and converting into the data type described by
846-
/// `format`. It is up to the caller to ensure that result points to an
847-
/// area of memory big enough to accommodate the requested rectangle.
848-
/// Return true if the operation could be completed, otherwise return
849-
/// false.
902+
/// pixel values into the `buffer`.
903+
///
904+
/// @param roi
905+
/// The region of interest to copy.
906+
/// @param spec
907+
/// An ImageSpec describing the image and its metadata. If
908+
/// not enough information is given to know the "shape" of
909+
/// the image (width, height, depth, channels, and data
910+
/// format), the ImageBuf will remain in an UNINITIALIZED
911+
/// state.
912+
/// @param buffer
913+
/// A span delineating the extent of the safely accessible
914+
/// memory where the results should be stored.
915+
/// @param buforigin
916+
/// In the version of the template that takes this parameter,
917+
/// it is a pointer to the first pixel of the buffer. If null,
918+
/// it will be assumed to be the beginning of the buffer.
919+
/// THis is useful if any negative strides are used to
920+
/// give an unusual layout of pixels within the buffer.
921+
/// @param xstride/ystride/zstride
922+
/// The distance in bytes between successive pixels,
923+
/// scanlines, and image planes in the buffer (or
924+
/// `AutoStride` to indicate "contiguous" data in any of
925+
/// those dimensions).
926+
/// @returns
927+
/// Return true if the operation could be completed,
928+
/// otherwise return false.
929+
///
930+
template<typename T, OIIO_ENABLE_IF(!std::is_const_v<T>
931+
&& !std::is_same_v<T, std::byte>)>
932+
bool get_pixels(ROI roi, span<T> buffer, stride_t xstride = AutoStride,
933+
stride_t ystride = AutoStride,
934+
stride_t zstride = AutoStride) const
935+
{
936+
return get_pixels(roi, TypeDescFromC<T>::value(),
937+
as_writable_bytes(buffer), buffer.data(), xstride,
938+
ystride, zstride);
939+
}
940+
941+
template<typename T, OIIO_ENABLE_IF(!std::is_const_v<T>
942+
&& !std::is_same_v<T, std::byte>)>
943+
bool get_pixels(ROI roi, span<T> buffer, T* buforigin,
944+
stride_t xstride = AutoStride,
945+
stride_t ystride = AutoStride,
946+
stride_t zstride = AutoStride) const
947+
{
948+
return get_pixels(roi, TypeDescFromC<T>::value(),
949+
as_writable_bytes(buffer), buforigin, xstride,
950+
ystride, zstride);
951+
}
952+
953+
/// Base case of get_pixels: read into a span of generic bytes. The
954+
/// requested data type is supplied by `format.
955+
bool get_pixels(ROI roi, TypeDesc format, span<std::byte> buffer,
956+
void* buforigin = nullptr, stride_t xstride = AutoStride,
957+
stride_t ystride = AutoStride,
958+
stride_t zstride = AutoStride) const;
959+
960+
/// Potentially unsafe get_pixels() using raw pointers. Use with catution!
961+
OIIO_IB_DEPRECATE_RAW_PTR
850962
bool get_pixels(ROI roi, TypeDesc format, void* result,
851963
stride_t xstride = AutoStride,
852964
stride_t ystride = AutoStride,
@@ -860,6 +972,37 @@ class OIIO_API ImageBuf {
860972
/// the data buffer is assumed to have the same resolution as the ImageBuf
861973
/// itself. Return true if the operation could be completed, otherwise
862974
/// return false.
975+
template<typename T, OIIO_ENABLE_IF(!std::is_same_v<T, std::byte>)>
976+
bool set_pixels(ROI roi, span<T> buffer, stride_t xstride = AutoStride,
977+
stride_t ystride = AutoStride,
978+
stride_t zstride = AutoStride)
979+
{
980+
return set_pixels(roi, TypeDescFromC<std::remove_const_t<T>>::value(),
981+
as_bytes(buffer), buffer.data(), xstride, ystride,
982+
zstride);
983+
}
984+
985+
template<typename T, OIIO_ENABLE_IF(!std::is_same_v<T, std::byte>)>
986+
bool set_pixels(ROI roi, span<T> buffer, const T* buforigin,
987+
stride_t xstride = AutoStride,
988+
stride_t ystride = AutoStride,
989+
stride_t zstride = AutoStride)
990+
{
991+
return set_pixels(roi, TypeDescFromC<std::remove_const_t<T>>::value(),
992+
as_bytes(buffer), buforigin, xstride, ystride,
993+
zstride);
994+
}
995+
996+
/// Base case of get_pixels: read into a span of generic bytes. The
997+
/// requested data type is supplied by `format.
998+
bool set_pixels(ROI roi, TypeDesc format, cspan<std::byte> buffer,
999+
const void* buforigin = nullptr,
1000+
stride_t xstride = AutoStride,
1001+
stride_t ystride = AutoStride,
1002+
stride_t zstride = AutoStride);
1003+
1004+
/// Potentially unsafe set_pixels() using raw pointers. Use with catution!
1005+
OIIO_IB_DEPRECATE_RAW_PTR
8631006
bool set_pixels(ROI roi, TypeDesc format, const void* data,
8641007
stride_t xstride = AutoStride,
8651008
stride_t ystride = AutoStride,
@@ -1125,8 +1268,8 @@ class OIIO_API ImageBuf {
11251268
/// Error reporting for ImageBuf: call this with std::format style
11261269
/// formatting specification. It is not necessary to have the error
11271270
/// message contain a trailing newline.
1128-
template<typename... Args>
1129-
void errorfmt(const char* fmt, const Args&... args) const
1271+
template<typename Str, typename... Args>
1272+
void errorfmt(const Str& fmt, Args&&... args) const
11301273
{
11311274
error(Strutil::fmt::format(fmt, args...));
11321275
}

src/include/OpenImageIO/imageio.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3370,6 +3370,19 @@ OIIO_API bool copy_image (int nchannels, int width, int height, int depth,
33703370
void *dst, stride_t dst_xstride,
33713371
stride_t dst_ystride, stride_t dst_zstride);
33723372

3373+
/// Helper: manufacture a span given an image pointer, format, size, and
3374+
/// strides. Use with caution! This is making a lot of assumptions that the
3375+
/// data pointer really does point to memory that's ok to access according to
3376+
/// the sizes and strides you give.
3377+
OIIO_API span<std::byte>
3378+
span_from_buffer(void* data, TypeDesc format, int nchannels, int width,
3379+
int height, int depth, stride_t xstride = AutoStride, stride_t ystride = AutoStride,
3380+
stride_t zstride = AutoStride);
3381+
OIIO_API cspan<std::byte>
3382+
cspan_from_buffer(const void* data, TypeDesc format, int nchannels, int width,
3383+
int height, int depth, stride_t xstride = AutoStride, stride_t ystride = AutoStride,
3384+
stride_t zstride = AutoStride);
3385+
33733386

33743387
// All the wrap_foo functions implement a wrap mode, wherein coord is
33753388
// altered to be origin <= coord < origin+width. The return value

src/include/OpenImageIO/span.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,35 @@ spanzero(span<T> dst, size_t offset = 0, size_t n = size_t(-1))
558558
}
559559

560560

561+
562+
/// Does the byte span `query` lie entirely within the safe `bounds` span?
563+
inline bool
564+
span_within(cspan<std::byte> bounds, cspan<std::byte> query)
565+
{
566+
return query.data() >= bounds.data()
567+
&& query.data() + query.size() <= bounds.data() + bounds.size();
568+
}
569+
570+
571+
572+
/// Verify the `ptr[0..len-1]` lies entirely within the given span `s`, which
573+
/// does not need to be the same data type. Return true if that is the case,
574+
/// false if it extends beyond the safe limits fo the span.
575+
template<typename SpanType, typename PtrType>
576+
inline bool
577+
check_span(span<SpanType> s, const PtrType* ptr, size_t len = 1)
578+
{
579+
return span_within(as_bytes(s), as_bytes(make_cspan(ptr, len)));
580+
}
581+
582+
583+
584+
/// OIIO_ALLOCASPAN is used to allocate smallish amount of memory on the
585+
/// stack, equivalent of C99 type var_name[size], and then return a span
586+
/// encompassing it.
587+
#define OIIO_ALLOCA_SPAN(type, size) span<type>(OIIO_ALLOCA(type, size), size)
588+
589+
561590
OIIO_NAMESPACE_END
562591

563592

src/iv/imageviewer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ class IvImage final : public ImageBuf {
116116
/// color space correction when indicated.
117117
void pixel_transform(bool srgb_to_linear, int color_mode, int channel);
118118

119-
bool get_pixels(ROI roi, TypeDesc format, void* result)
119+
bool get_pixels(ROI roi, TypeDesc format, span<std::byte> result)
120120
{
121121
if (m_corrected_image.localpixels())
122122
return m_corrected_image.get_pixels(roi, format, result);

0 commit comments

Comments
 (0)