From 2906ea978586de6e5177f5ece85d9d6dc058e0ac Mon Sep 17 00:00:00 2001 From: Omar Emara Date: Thu, 21 Mar 2024 13:22:10 +0100 Subject: [PATCH] BLI: Add nearest interpolation with clamped boundary This patch adds clamped boundaries variants of the nearest interpolation functions in the BLI module. The naming convention used by the bilinear functions were followed. Needed by #119414. Pull Request: https://projects.blender.org/blender/blender/pulls/119732 --- .../blender/blenkernel/intern/studiolight.cc | 6 +- .../blenkernel/intern/tracking_stabilize.cc | 4 +- source/blender/blenlib/BLI_math_interp.hh | 87 +++++++++++++++---- .../blenlib/tests/BLI_math_interp_test.cc | 44 ++++++++++ .../operations/COM_RenderLayersProg.cc | 3 +- .../draw/engines/image/image_drawing_mode.hh | 8 +- source/blender/imbuf/IMB_interp.hh | 16 ++-- source/blender/imbuf/intern/interp.cc | 4 +- source/blender/imbuf/intern/transform.cc | 4 +- source/blender/sequencer/intern/effects.cc | 4 +- 10 files changed, 140 insertions(+), 40 deletions(-) diff --git a/source/blender/blenkernel/intern/studiolight.cc b/source/blender/blenkernel/intern/studiolight.cc index dea89c741a2..da8f5b383d7 100644 --- a/source/blender/blenkernel/intern/studiolight.cc +++ b/source/blender/blenkernel/intern/studiolight.cc @@ -491,7 +491,7 @@ static float4 studiolight_calculate_radiance(ImBuf *ibuf, const float direction[ { float uv[2]; direction_to_equirect(uv, direction); - return blender::imbuf::interpolate_nearest_fl(ibuf, uv[0] * ibuf->x, uv[1] * ibuf->y); + return blender::imbuf::interpolate_nearest_border_fl(ibuf, uv[0] * ibuf->x, uv[1] * ibuf->y); } /* @@ -732,10 +732,10 @@ static void studiolight_matcap_preview(uint *icon_buffer, StudioLight *sl, bool float u = dx * diffuse_buffer->x - 1.0f; float v = dy * diffuse_buffer->y - 1.0f; - float4 color = imbuf::interpolate_nearest_fl(diffuse_buffer, u, v); + float4 color = imbuf::interpolate_nearest_border_fl(diffuse_buffer, u, v); if (specular_buffer) { - float4 specular = imbuf::interpolate_nearest_fl(specular_buffer, u, v); + float4 specular = imbuf::interpolate_nearest_border_fl(specular_buffer, u, v); add_v3_v3(color, specular); } diff --git a/source/blender/blenkernel/intern/tracking_stabilize.cc b/source/blender/blenkernel/intern/tracking_stabilize.cc index 9b5b03cc67a..886dcd61793 100644 --- a/source/blender/blenkernel/intern/tracking_stabilize.cc +++ b/source/blender/blenkernel/intern/tracking_stabilize.cc @@ -1329,7 +1329,7 @@ static void tracking_stabilize_frame_interpolation_cb(void *__restrict userdata, for (int x = 0; x < tmpibuf->x; x++, dst++) { vec[0] = float(x); mul_v3_m4v3(rvec, mat, vec); - *dst = imbuf::interpolate_nearest_fl(ibuf, rvec[0], rvec[1]); + *dst = imbuf::interpolate_nearest_border_fl(ibuf, rvec[0], rvec[1]); } } } @@ -1355,7 +1355,7 @@ static void tracking_stabilize_frame_interpolation_cb(void *__restrict userdata, for (int x = 0; x < tmpibuf->x; x++, dst++) { vec[0] = float(x); mul_v3_m4v3(rvec, mat, vec); - *dst = imbuf::interpolate_nearest_byte(ibuf, rvec[0], rvec[1]); + *dst = imbuf::interpolate_nearest_border_byte(ibuf, rvec[0], rvec[1]); } } } diff --git a/source/blender/blenlib/BLI_math_interp.hh b/source/blender/blenlib/BLI_math_interp.hh index b000b5301dc..a1836912237 100644 --- a/source/blender/blenlib/BLI_math_interp.hh +++ b/source/blender/blenlib/BLI_math_interp.hh @@ -15,23 +15,25 @@ * Any filtering done on texel values just blends them without color space or * gamma conversions. * - * Sampling completely outside the image returns transparent black. */ #include "BLI_math_base.h" +#include "BLI_math_base.hh" #include "BLI_math_vector_types.hh" namespace blender::math { /** - * Nearest (point) sampling. + * Nearest (point) sampling (with black border). * - * Returns texel at floor(u,v) integer index -- note that it is not "nearest - * to u,v coordinate", but rather with fractional part truncated (it would be - * "nearest" if subtracting 0.5 from input u,v). + * Returns texel at floor(u,v) integer index. Samples outside the image are turned into transparent + * black. + * + * Note that it is not "nearest to u,v coordinate", but rather with fractional part truncated (it + * would be "nearest" if subtracting 0.5 from input u,v). */ -inline void interpolate_nearest_byte( +inline void interpolate_nearest_border_byte( const uchar *buffer, uchar *output, int width, int height, float u, float v) { BLI_assert(buffer); @@ -51,6 +53,67 @@ inline void interpolate_nearest_byte( output[3] = data[3]; } +[[nodiscard]] inline uchar4 interpolate_nearest_border_byte( + const uchar *buffer, int width, int height, float u, float v) +{ + uchar4 res; + interpolate_nearest_border_byte(buffer, res, width, height, u, v); + return res; +} + +inline void interpolate_nearest_border_fl( + const float *buffer, float *output, int width, int height, int components, float u, float v) +{ + BLI_assert(buffer); + int x = int(u); + int y = int(v); + + /* Outside image? */ + if (x < 0 || x >= width || y < 0 || y >= height) { + for (int i = 0; i < components; i++) { + output[i] = 0.0f; + } + return; + } + + const float *data = buffer + (int64_t(width) * y + x) * components; + for (int i = 0; i < components; i++) { + output[i] = data[i]; + } +} + +[[nodiscard]] inline float4 interpolate_nearest_border_fl( + const float *buffer, int width, int height, float u, float v) +{ + float4 res; + interpolate_nearest_border_fl(buffer, res, width, height, 4, u, v); + return res; +} + +/** + * Nearest (point) sampling. + * + * Returns texel at floor(u,v) integer index. Samples outside the image are clamped to texels at + * image edge. + * + * Note that it is not "nearest to u,v coordinate", but rather with fractional part truncated (it + * would be "nearest" if subtracting 0.5 from input u,v). + */ + +inline void interpolate_nearest_byte( + const uchar *buffer, uchar *output, int width, int height, float u, float v) +{ + BLI_assert(buffer); + const int x = math::clamp(int(u), 0, width - 1); + const int y = math::clamp(int(v), 0, height - 1); + + const uchar *data = buffer + (int64_t(width) * y + x) * 4; + output[0] = data[0]; + output[1] = data[1]; + output[2] = data[2]; + output[3] = data[3]; +} + [[nodiscard]] inline uchar4 interpolate_nearest_byte( const uchar *buffer, int width, int height, float u, float v) { @@ -63,16 +126,8 @@ inline void interpolate_nearest_fl( const float *buffer, float *output, int width, int height, int components, float u, float v) { BLI_assert(buffer); - int x = int(u); - int y = int(v); - - /* Outside image? */ - if (x < 0 || x >= width || y < 0 || y >= height) { - for (int i = 0; i < components; i++) { - output[i] = 0.0f; - } - return; - } + const int x = math::clamp(int(u), 0, width - 1); + const int y = math::clamp(int(v), 0, height - 1); const float *data = buffer + (int64_t(width) * y + x) * components; for (int i = 0; i < components; i++) { diff --git a/source/blender/blenlib/tests/BLI_math_interp_test.cc b/source/blender/blenlib/tests/BLI_math_interp_test.cc index fa09dcdc884..0ecf447803e 100644 --- a/source/blender/blenlib/tests/BLI_math_interp_test.cc +++ b/source/blender/blenlib/tests/BLI_math_interp_test.cc @@ -24,6 +24,50 @@ static constexpr float image_fl[image_height][image_width][4] = { {{1, 2, 3, 4}, {73, 108, 153, 251}, {128, 129, 130, 131}}, }; +TEST(math_interp, NearestCharExactSamples) +{ + uchar4 res; + uchar4 exp1 = {73, 108, 153, 251}; + res = interpolate_nearest_border_byte(image_char[0][0], image_width, image_height, 1.0f, 2.0f); + EXPECT_EQ(exp1, res); + uchar4 exp2 = {240, 160, 90, 20}; + res = interpolate_nearest_border_byte(image_char[0][0], image_width, image_height, 2.0f, 0.0f); + EXPECT_EQ(exp2, res); +} + +TEST(math_interp, NearestCharHalfwaySamples) +{ + uchar4 res; + uchar4 exp1 = {0, 1, 2, 3}; + res = interpolate_nearest_border_byte(image_char[0][0], image_width, image_height, 0.5f, 1.5f); + EXPECT_EQ(exp1, res); + uchar4 exp2 = {255, 254, 217, 216}; + res = interpolate_nearest_border_byte(image_char[0][0], image_width, image_height, 0.5f, 0.5f); + EXPECT_EQ(exp2, res); +} + +TEST(math_interp, NearestFloatExactSamples) +{ + float4 res; + float4 exp1 = {73.0f, 108.0f, 153.0f, 251.0f}; + res = interpolate_nearest_border_fl(image_fl[0][0], image_width, image_height, 1.0f, 2.0f); + EXPECT_EQ(exp1, res); + float4 exp2 = {240.0f, 160.0f, 90.0f, 20.0f}; + res = interpolate_nearest_border_fl(image_fl[0][0], image_width, image_height, 2.0f, 0.0f); + EXPECT_EQ(exp2, res); +} + +TEST(math_interp, NearestFloatHalfwaySamples) +{ + float4 res; + float4 exp1 = {0.0f, 1.0f, 2.0f, 3.0f}; + res = interpolate_nearest_border_fl(image_fl[0][0], image_width, image_height, 0.5f, 1.5f); + EXPECT_EQ(exp1, res); + float4 exp2 = {255.0f, 254.0f, 217.0f, 216.0f}; + res = interpolate_nearest_border_fl(image_fl[0][0], image_width, image_height, 0.5f, 0.5f); + EXPECT_EQ(exp2, res); +} + TEST(math_interp, BilinearCharExactSamples) { uchar4 res; diff --git a/source/blender/compositor/operations/COM_RenderLayersProg.cc b/source/blender/compositor/operations/COM_RenderLayersProg.cc index abdb2174f08..88e51cf0f0c 100644 --- a/source/blender/compositor/operations/COM_RenderLayersProg.cc +++ b/source/blender/compositor/operations/COM_RenderLayersProg.cc @@ -74,7 +74,8 @@ void RenderLayersProg::do_interpolation(float output[4], float x, float y, Pixel switch (sampler) { case PixelSampler::Nearest: - math::interpolate_nearest_fl(input_buffer_, output, width, height, elementsize_, x, y); + math::interpolate_nearest_border_fl( + input_buffer_, output, width, height, elementsize_, x, y); break; case PixelSampler::Bilinear: math::interpolate_bilinear_border_fl( diff --git a/source/blender/draw/engines/image/image_drawing_mode.hh b/source/blender/draw/engines/image/image_drawing_mode.hh index e56302fbed4..9475f22c58d 100644 --- a/source/blender/draw/engines/image/image_drawing_mode.hh +++ b/source/blender/draw/engines/image/image_drawing_mode.hh @@ -504,10 +504,10 @@ template class ScreenSpaceDrawingMode : public AbstractD float xf = x / (float)texture_width; float u = info.clipping_uv_bounds.xmax * xf + info.clipping_uv_bounds.xmin * (1.0 - xf) - tile_offset_x; - imbuf::interpolate_nearest_fl(tile_buffer, - &extracted_buffer.float_buffer.data[offset * 4], - u * tile_buffer->x, - v * tile_buffer->y); + imbuf::interpolate_nearest_border_fl(tile_buffer, + &extracted_buffer.float_buffer.data[offset * 4], + u * tile_buffer->x, + v * tile_buffer->y); offset++; } } diff --git a/source/blender/imbuf/IMB_interp.hh b/source/blender/imbuf/IMB_interp.hh index 43c2602707b..560c8b0bc12 100644 --- a/source/blender/imbuf/IMB_interp.hh +++ b/source/blender/imbuf/IMB_interp.hh @@ -20,21 +20,21 @@ namespace blender::imbuf { /* Nearest sampling. */ -[[nodiscard]] inline uchar4 interpolate_nearest_byte(const ImBuf *in, float u, float v) +[[nodiscard]] inline uchar4 interpolate_nearest_border_byte(const ImBuf *in, float u, float v) { - return math::interpolate_nearest_byte(in->byte_buffer.data, in->x, in->y, u, v); + return math::interpolate_nearest_border_byte(in->byte_buffer.data, in->x, in->y, u, v); } -[[nodiscard]] inline float4 interpolate_nearest_fl(const ImBuf *in, float u, float v) +[[nodiscard]] inline float4 interpolate_nearest_border_fl(const ImBuf *in, float u, float v) { - return math::interpolate_nearest_fl(in->float_buffer.data, in->x, in->y, u, v); + return math::interpolate_nearest_border_fl(in->float_buffer.data, in->x, in->y, u, v); } -inline void interpolate_nearest_byte(const ImBuf *in, uchar output[4], float u, float v) +inline void interpolate_nearest_border_byte(const ImBuf *in, uchar output[4], float u, float v) { - math::interpolate_nearest_byte(in->byte_buffer.data, output, in->x, in->y, u, v); + math::interpolate_nearest_border_byte(in->byte_buffer.data, output, in->x, in->y, u, v); } -inline void interpolate_nearest_fl(const ImBuf *in, float output[4], float u, float v) +inline void interpolate_nearest_border_fl(const ImBuf *in, float output[4], float u, float v) { - math::interpolate_nearest_fl(in->float_buffer.data, output, in->x, in->y, 4, u, v); + math::interpolate_nearest_border_fl(in->float_buffer.data, output, in->x, in->y, 4, u, v); } /* Nearest sampling with UV wrapping. */ diff --git a/source/blender/imbuf/intern/interp.cc b/source/blender/imbuf/intern/interp.cc index 7e1ae484722..e4fb1432e35 100644 --- a/source/blender/imbuf/intern/interp.cc +++ b/source/blender/imbuf/intern/interp.cc @@ -14,10 +14,10 @@ void IMB_sampleImageAtLocation(ImBuf *ibuf, float x, float y, bool make_linear_r { using namespace blender; if (ibuf->float_buffer.data) { - imbuf::interpolate_nearest_fl(ibuf, color, x, y); + imbuf::interpolate_nearest_border_fl(ibuf, color, x, y); } else { - uchar4 byte_color = imbuf::interpolate_nearest_byte(ibuf, x, y); + uchar4 byte_color = imbuf::interpolate_nearest_border_byte(ibuf, x, y); rgba_uchar_to_float(color, byte_color); if (make_linear_rgb) { IMB_colormanagement_colorspace_to_scene_linear_v4( diff --git a/source/blender/imbuf/intern/transform.cc b/source/blender/imbuf/intern/transform.cc index 03e6373b88b..1611ade84f5 100644 --- a/source/blender/imbuf/intern/transform.cc +++ b/source/blender/imbuf/intern/transform.cc @@ -140,7 +140,7 @@ static void sample_image(const ImBuf *source, float u, float v, T *r_sample) } else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v && NumChannels == 4) { - interpolate_nearest_byte(source, r_sample, u, v); + interpolate_nearest_border_byte(source, r_sample, u, v); } else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v && NumChannels == 4) { @@ -164,7 +164,7 @@ static void sample_image(const ImBuf *source, float u, float v, T *r_sample) } } else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v) { - math::interpolate_nearest_fl( + math::interpolate_nearest_border_fl( source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v); } else if constexpr (Filter == IMB_FILTER_CUBIC_BSPLINE && std::is_same_v) { diff --git a/source/blender/sequencer/intern/effects.cc b/source/blender/sequencer/intern/effects.cc index 2c6757ac2e9..f0f95eeafc9 100644 --- a/source/blender/sequencer/intern/effects.cc +++ b/source/blender/sequencer/intern/effects.cc @@ -1573,10 +1573,10 @@ static void transform_image(int x, switch (interpolation) { case 0: if (dst_fl) { - dst_fl[offset] = imbuf::interpolate_nearest_fl(ibuf, xt, yt); + dst_fl[offset] = imbuf::interpolate_nearest_border_fl(ibuf, xt, yt); } else { - dst_ch[offset] = imbuf::interpolate_nearest_byte(ibuf, xt, yt); + dst_ch[offset] = imbuf::interpolate_nearest_border_byte(ibuf, xt, yt); } break; case 1: