Skip to content

Commit 9f668c7

Browse files
committed
api(fmath.h): Add span version of convert_type
api(imageio.h): Add span/image_span functions * span convert_pixel_values * image_span convert_image and parallel_convert_image Signed-off-by: Larry Gritz <lg@larrygritz.com>
1 parent aefe764 commit 9f668c7

5 files changed

Lines changed: 233 additions & 12 deletions

File tree

src/include/OpenImageIO/fmath.h

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,7 @@ scaled_conversion(const S& src, F scale, F min, F max)
757757
template<typename S, typename D>
758758
void convert_type (const S *src, D *dst, size_t n, D _min, D _max)
759759
{
760-
if (std::is_same<S,D>::value) {
760+
if constexpr (std::is_same<S,D>::value) {
761761
// They must be the same type. Just memcpy.
762762
memcpy (dst, src, n*sizeof(D));
763763
return;
@@ -968,6 +968,25 @@ inline void convert_type (const S *src, D *dst, size_t n)
968968

969969

970970

971+
/// Copy (type convert) consecutive values from the cspan `src` holding data
972+
/// of type S into the span `dst` holding the same number of elements of data
973+
/// of type D.
974+
///
975+
/// The conversion is not a simple cast, but correctly remaps the 0.0->1.0
976+
/// range from and to the full positive range of integral types. It's just a
977+
/// straight copy if both types are identical. Optional arguments `min` and
978+
/// `max` can give nonstandard quantizations.
979+
template<typename S, typename D>
980+
void convert_type (cspan<S> src, span<D> dst,
981+
D min = std::numeric_limits<D>::min(),
982+
D max = std::numeric_limits<D>::min())
983+
{
984+
OIIO_DASSERT(src.size() == dst.size());
985+
convert_type(src.data(), dst.data(), std::min(src.size(), dst.size()),
986+
min, max);
987+
}
988+
989+
971990

972991
/// Convert a single value from the type of S to the type of D.
973992
/// The conversion is not a simple cast, but correctly remaps the
@@ -978,7 +997,7 @@ template<typename S, typename D>
978997
inline D
979998
convert_type (const S &src)
980999
{
981-
if (std::is_same<S,D>::value) {
1000+
if constexpr (std::is_same<S,D>::value) {
9821001
// They must be the same type. Just return it.
9831002
return (D)src;
9841003
}

src/include/OpenImageIO/imageio.h

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3418,6 +3418,25 @@ get_extension_map()
34183418
OIIO_API bool convert_pixel_values (TypeDesc src_type, const void *src,
34193419
TypeDesc dst_type, void *dst, int n = 1);
34203420

3421+
/// Helper function: copy values from spans `src` to `dst`, converting between
3422+
/// types as appropriate. Return true if ok, false if it didn't know how to do
3423+
/// the conversion.
3424+
///
3425+
/// The conversion is of normalized (pixel-like) values -- for example 'UINT8'
3426+
/// 255 will convert to float 1.0 and vice versa, not float 255.0. If you want
3427+
/// a straight C-like data cast conversion (e.g., uint8 255 -> float 255.0),
3428+
/// then you should prefer the un-normalized convert_type() utility function
3429+
/// found in typedesc.h.
3430+
template<typename SrcType, typename DstType>
3431+
bool
3432+
convert_pixel_values(cspan<SrcType> src, span<DstType> dst)
3433+
{
3434+
OIIO_DASSERT(dst.size() >= src.size());
3435+
return convert_pixel_values(TypeDescFromC_v<SrcType>, src.data(),
3436+
TypeDescFromC_v<DstType>, dst.data(),
3437+
std::min(dst.size(), src.size()));
3438+
}
3439+
34213440

34223441
/// Helper routine for data conversion: Convert an image of nchannels x
34233442
/// width x height x depth from src to dst. The src and dst may have
@@ -3436,6 +3455,50 @@ OIIO_API bool convert_image (int nchannels, int width, int height, int depth,
34363455
stride_t dst_xstride, stride_t dst_ystride,
34373456
stride_t dst_zstride);
34383457

3458+
/// Helper routine for data conversion: Convert an image described by
3459+
/// image_span `src` into image_span `dst`, which must be the same dimensions
3460+
/// but possibly differing data type and strides. Clever use of this function
3461+
/// can not only exchange data among different formats (e.g., half to 8-bit
3462+
/// unsigned), but also can copy selective channels, copy subimages, etc.
3463+
/// Return true if ok, false if it didn't know how to do the conversion.
3464+
template<typename SrcType, typename DstType>
3465+
bool
3466+
convert_image(image_span<SrcType> src, image_span<DstType> dst)
3467+
{
3468+
// For now, just implement by wrapping the pointer-based version.
3469+
OIIO_DASSERT(src.nchannels() == dst.nchannels()
3470+
&& src.width() == dst.width() && src.height() == dst.height()
3471+
&& src.depth() == dst.depth());
3472+
return convert_image(src.nchannels(), src.width(), src.height(),
3473+
src.depth(), src.data(), TypeDescFromC_v<SrcType>,
3474+
src.xstride(), src.ystride(), src.zstride(),
3475+
dst.data(), TypeDescFromC_v<DstType>, dst.xstride(),
3476+
dst.ystride(), dst.zstride());
3477+
}
3478+
3479+
3480+
/// Helper routine for data conversion: Convert an image described by
3481+
/// image_span `src` into image_span `dst`, which must be the same dimensions
3482+
/// but possibly differing data type and strides. The data types are passed as
3483+
/// `TypeDesc`, and the spans are untyped bytes that provide the dimensions
3484+
/// and memory layout.
3485+
inline bool
3486+
convert_image(image_span<const std::byte> src, TypeDesc src_type,
3487+
image_span<std::byte> dst, TypeDesc dst_type)
3488+
{
3489+
// For now, just implement by wrapping the pointer-based version.
3490+
OIIO_DASSERT(src.nchannels() == dst.nchannels()
3491+
&& src.width() == dst.width() && src.height() == dst.height()
3492+
&& src.depth() == dst.depth());
3493+
OIIO_DASSERT(src_type.size() == src.chansize()
3494+
&& dst_type.size() == dst.chansize());
3495+
return convert_image(src.nchannels(), src.width(), src.height(),
3496+
src.depth(), src.data(), src_type, src.xstride(),
3497+
src.ystride(), src.zstride(), dst.data(), dst_type,
3498+
dst.xstride(), dst.ystride(), dst.zstride());
3499+
}
3500+
3501+
34393502

34403503
/// A version of convert_image that will break up big jobs into multiple
34413504
/// threads.
@@ -3448,6 +3511,48 @@ OIIO_API bool parallel_convert_image (
34483511
stride_t dst_xstride, stride_t dst_ystride,
34493512
stride_t dst_zstride, int nthreads=0);
34503513

3514+
/// A version of convert_image that will break up big jobs into multiple
3515+
/// threads. The data types are taken from the spans.
3516+
template<typename SrcType, typename DstType>
3517+
bool
3518+
parallel_convert_image(image_span<SrcType> src, image_span<DstType> dst,
3519+
int nthreads = 0)
3520+
{
3521+
// For now, just implement by wrapping the pointer-based version.
3522+
OIIO_DASSERT(src.nchannels() == dst.nchannels()
3523+
&& src.width() == dst.width() && src.height() == dst.height()
3524+
&& src.depth() == dst.depth());
3525+
return parallel_convert_image(src.nchannels(), src.width(), src.height(),
3526+
src.depth(), src.data(),
3527+
TypeDescFromC_v<SrcType>, src.xstride(),
3528+
src.ystride(), src.zstride(), dst.data(),
3529+
TypeDescFromC_v<SrcType>, dst.xstride(),
3530+
dst.ystride(), dst.zstride(), nthreads);
3531+
}
3532+
3533+
/// A version of convert_image that will break up big jobs into multiple
3534+
/// threads. The data types are passed as `TypeDesc`, and the spans are
3535+
/// untyped bytes that provide the dimensions and memory layout.
3536+
inline bool
3537+
parallel_convert_image(image_span<const std::byte> src, TypeDesc src_type,
3538+
image_span<std::byte> dst, TypeDesc dst_type,
3539+
int nthreads = 0)
3540+
{
3541+
// For now, just implement by wrapping the pointer-based version.
3542+
OIIO_DASSERT(src.nchannels() == dst.nchannels()
3543+
&& src.width() == dst.width() && src.height() == dst.height()
3544+
&& src.depth() == dst.depth());
3545+
OIIO_DASSERT(src_type.size() == src.chansize()
3546+
&& dst_type.size() == dst.chansize());
3547+
return parallel_convert_image(src.nchannels(), src.width(), src.height(),
3548+
src.depth(), src.data(),
3549+
src_type, src.xstride(),
3550+
src.ystride(), src.zstride(), dst.data(),
3551+
dst_type, dst.xstride(),
3552+
dst.ystride(), dst.zstride(), nthreads);
3553+
}
3554+
3555+
34513556

34523557
/// Add random [-ditheramplitude,ditheramplitude] dither to the color channels
34533558
/// of the image. Dither will not be added to the alpha or z channel. The

src/libOpenImageIO/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ if (OIIO_BUILD_TESTS AND BUILD_TESTING)
253253
add_test (unit_color ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/color_test)
254254

255255
fancy_add_executable (NAME image_span_test SRC image_span_test.cpp
256-
LINK_LIBRARIES OpenImageIO
256+
LINK_LIBRARIES OpenImageIO Imath::Imath
257257
FOLDER "Unit Tests" NO_INSTALL)
258258
add_test (unit_image_span ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/image_span_test)
259259

src/libOpenImageIO/image_span_test.cpp

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
// https://github.com/AcademySoftwareFoundation/OpenImageIO
44

55

6+
#include <OpenImageIO/half.h>
7+
68
#include <OpenImageIO/benchmark.h>
9+
#include <OpenImageIO/fmath.h>
710
#include <OpenImageIO/imageio.h>
811
#include <OpenImageIO/parallel.h>
912
#include <OpenImageIO/unittest.h>
@@ -120,7 +123,7 @@ fill_image_span(image_span<T> img)
120123

121124

122125
// Check that an image span in the characteristic way
123-
template<typename T>
126+
template<typename T, typename S = T>
124127
bool
125128
check_image_span(image_span<T> img, int xoff = 0, int yoff = 0, int zoff = 0)
126129
{
@@ -129,7 +132,8 @@ check_image_span(image_span<T> img, int xoff = 0, int yoff = 0, int zoff = 0)
129132
for (uint32_t y = 0; y < img.height(); ++y) {
130133
for (uint32_t x = 0; x < img.width(); ++x) {
131134
for (uint32_t c = 0; c < img.nchannels(); ++c) {
132-
auto v = testvalue<T>(x + xoff, y + yoff, z + zoff, c);
135+
auto v = convert_type<S, T>(
136+
testvalue<S>(x + xoff, y + yoff, z + zoff, c));
133137
OIIO_CHECK_EQUAL(img(x, y, z)[c], v);
134138
if (img(x, y, z)[c] != v) {
135139
print("\tError at ({}, {}, {})[{}]\n", x, y, z, c);
@@ -279,6 +283,72 @@ test_image_span_contiguize()
279283

280284

281285

286+
template<typename Stype = float, typename Dtype = Stype>
287+
void
288+
test_image_span_convert_image()
289+
{
290+
// Benchmark old (ptr) versus new (span) convert_image functions
291+
const int xres = 2048, yres = 1536, nchans = 4;
292+
const size_t schansize = sizeof(Stype);
293+
const size_t dchansize = sizeof(Dtype);
294+
print("\nTesting convert_image {} -> {} (total {}M values):\n",
295+
TypeDescFromC_v<Stype>, TypeDescFromC_v<Dtype>,
296+
xres * yres * nchans * 3 / 4 / 1024 / 1024);
297+
298+
// We test different amounts of contiguity. Each test copies 3/4 of the
299+
// total image, to keep the total number of bytes copied identical.
300+
const stride_t src_xstride(schansize * nchans);
301+
const stride_t src_ystride(src_xstride * xres);
302+
const stride_t dst_xstride(dchansize * nchans);
303+
const stride_t dst_ystride(dst_xstride * xres);
304+
for (int i = 0; i < 3; ++i) {
305+
size_t nc(nchans), w(xres), h(yres);
306+
std::string label;
307+
if (i == 0) {
308+
// Fully contiguous region -- copy 3/4 of the image.
309+
label = "contig buffer";
310+
h = h * 3 / 4;
311+
} else if (i == 1) {
312+
// Contiguous scanlines -- copy 3/4 of the width of each scanline.
313+
label = "contig scanlines";
314+
w = w * 3 / 4;
315+
} else if (i == 2) {
316+
// Contiguous pixels -- copy 3 of 4 channels of each pixel.
317+
label = "contig pixels";
318+
nc = nc * 3 / 4;
319+
}
320+
321+
print(" test convert_image {}\n", label);
322+
std::vector<Stype> sbuf(xres * yres * nchans, Stype(10));
323+
std::vector<Dtype> dbuf(xres * yres * nchans, Dtype(20));
324+
325+
// Spans for src and dst
326+
image_span sispan(sbuf.data(), nc, w, h, 1, schansize, src_xstride,
327+
src_ystride, AutoStride);
328+
image_span dispan(dbuf.data(), nc, w, h, 1, dchansize, dst_xstride,
329+
dst_ystride, AutoStride);
330+
331+
fill_image_span(sispan);
332+
333+
// Benchmark old (ptr) versus new (span) contiguize functions
334+
Benchmarker bench;
335+
bench.units(Benchmarker::Unit::ms);
336+
337+
bench(Strutil::format(" convert_image image_span {}", label),
338+
[&]() { convert_image(sispan, dispan); });
339+
// Test correctness
340+
bench(Strutil::format(" convert_image raw ptrs {}", label), [&]() {
341+
convert_image(nc, w, h, 1, sbuf.data(), TypeDescFromC_v<Stype>,
342+
src_xstride, src_ystride, AutoStride, dbuf.data(),
343+
TypeDescFromC_v<Dtype>, dst_xstride, dst_ystride,
344+
AutoStride);
345+
});
346+
OIIO_CHECK_ASSERT((check_image_span<Dtype, Stype>(dispan)));
347+
}
348+
}
349+
350+
351+
282352
int
283353
main(int /*argc*/, char* /*argv*/[])
284354
{
@@ -297,5 +367,16 @@ main(int /*argc*/, char* /*argv*/[])
297367
test_image_span_contiguize<uint16_t>();
298368
test_image_span_contiguize<uint8_t>();
299369

370+
test_image_span_convert_image<float, half>();
371+
test_image_span_convert_image<float, uint16_t>();
372+
test_image_span_convert_image<float, uint8_t>();
373+
test_image_span_convert_image<half, float>();
374+
test_image_span_convert_image<uint16_t, float>();
375+
test_image_span_convert_image<uint8_t, float>();
376+
test_image_span_convert_image<uint16_t, uint8_t>();
377+
test_image_span_convert_image<uint8_t, uint16_t>();
378+
test_image_span_convert_image<uint16_t, half>();
379+
test_image_span_convert_image<half, uint16_t>();
380+
300381
return unit_test_failures;
301382
}

src/libutil/fmath_test.cpp

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -334,13 +334,22 @@ test_convert_type(double tolerance = 1e-6)
334334

335335
template<typename S, typename D>
336336
void
337-
do_convert_type(const std::vector<S>& svec, std::vector<D>& dvec)
337+
do_convert_type_ptr(const std::vector<S>& svec, std::vector<D>& dvec)
338338
{
339339
convert_type(&svec[0], &dvec[0], svec.size());
340340
DoNotOptimize(dvec[0]); // Be sure nothing is optimized away
341341
}
342342

343343

344+
template<typename S, typename D>
345+
void
346+
do_convert_type_span(const std::vector<S>& svec, std::vector<D>& dvec)
347+
{
348+
convert_type(svec, dvec);
349+
DoNotOptimize(dvec[0]); // Be sure nothing is optimized away
350+
}
351+
352+
344353
template<typename S, typename D>
345354
void
346355
benchmark_convert_type()
@@ -349,17 +358,24 @@ benchmark_convert_type()
349358
const size_t size = iterations;
350359
const S testval(1.0);
351360
std::vector<S> svec(size, testval);
352-
std::vector<D> dvec(size);
353-
Strutil::print("Benchmark conversion of {:6} -> {:6} : ",
354-
TypeDesc(BaseTypeFromC<S>::value).c_str(),
355-
TypeDesc(BaseTypeFromC<D>::value).c_str());
356-
float time = time_trial(bind(do_convert_type<S, D>, std::cref(svec),
361+
std::vector<D> dvec(size), dvec2(size);
362+
print("Benchmark conversion of {:6} -> {:6} (ptr) : ",
363+
TypeDesc(BaseTypeFromC<S>::value), TypeDesc(BaseTypeFromC<D>::value));
364+
float time = time_trial(bind(do_convert_type_ptr<S, D>, std::cref(svec),
357365
std::ref(dvec)),
358366
ntrials, repeats)
359367
/ repeats;
360-
Strutil::print("{:7.1f} Mvals/sec\n", (size / 1.0e6) / time);
368+
print("{:7.1f} Mvals/sec\n", (size / 1.0e6) / time);
369+
print("Benchmark conversion of {:6} -> {:6} (span): ",
370+
TypeDesc(BaseTypeFromC<S>::value), TypeDesc(BaseTypeFromC<D>::value));
371+
float time2 = time_trial(bind(do_convert_type_ptr<S, D>, std::cref(svec),
372+
std::ref(dvec2)),
373+
ntrials, repeats)
374+
/ repeats;
375+
print("{:7.1f} Mvals/sec\n", (size / 1.0e6) / time2);
361376
D r = convert_type<S, D>(testval);
362377
OIIO_CHECK_EQUAL(dvec[size - 1], r);
378+
OIIO_CHECK_EQUAL(dvec2[size - 1], r);
363379
}
364380

365381

0 commit comments

Comments
 (0)