diff --git a/source/blender/blenlib/BLI_math_interp.hh b/source/blender/blenlib/BLI_math_interp.hh index a1836912237..76f8f5925dc 100644 --- a/source/blender/blenlib/BLI_math_interp.hh +++ b/source/blender/blenlib/BLI_math_interp.hh @@ -15,6 +15,11 @@ * Any filtering done on texel values just blends them without color space or * gamma conversions. * + * For sampling float images, there are "fully generic" functions that + * take arbitrary image channel counts, and arbitrary texture coordinate wrapping + * modes. However if you do not need full flexibility, use less generic functions, + * they will be faster (e.g. #interpolate_nearest_border_fl is faster than + * #interpolate_nearest_wrapmode_fl). */ #include "BLI_math_base.h" @@ -23,6 +28,22 @@ namespace blender::math { +/** + * Texture coordinate wrapping mode. + */ +enum class InterpWrapMode { + /** Image edges are extended outside the image, i.e. sample coordinates are clamped to the edge. + */ + Extend, + /** Image repeats, i.e. sample coordinates are wrapped around. */ + Repeat, + /** Samples outside the image return transparent black. */ + Border +}; + +/* -------------------------------------------------------------------- */ +/* Nearest (point) sampling. */ + /** * Nearest (point) sampling (with black border). * @@ -196,6 +217,19 @@ inline void interpolate_nearest_wrap_fl( return res; } +void interpolate_nearest_wrapmode_fl(const float *buffer, + float *output, + int width, + int height, + int components, + float u, + float v, + InterpWrapMode wrap_u, + InterpWrapMode wrap_v); + +/* -------------------------------------------------------------------- */ +/* Bilinear sampling. */ + /** * Bilinear sampling (with black border). * @@ -247,15 +281,18 @@ void interpolate_bilinear_fl( [[nodiscard]] float4 interpolate_bilinear_wrap_fl( const float *buffer, int width, int height, float u, float v); -void interpolate_bilinear_wrap_fl(const float *buffer, - float *output, - int width, - int height, - int components, - float u, - float v, - bool wrap_x, - bool wrap_y); +void interpolate_bilinear_wrapmode_fl(const float *buffer, + float *output, + int width, + int height, + int components, + float u, + float v, + InterpWrapMode wrap_u, + InterpWrapMode wrap_v); + +/* -------------------------------------------------------------------- */ +/* Cubic sampling. */ /** * Cubic B-Spline sampling. @@ -278,6 +315,16 @@ void interpolate_bilinear_wrap_fl(const float *buffer, void interpolate_cubic_bspline_fl( const float *buffer, float *output, int width, int height, int components, float u, float v); +void interpolate_cubic_bspline_wrapmode_fl(const float *buffer, + float *output, + int width, + int height, + int components, + float u, + float v, + InterpWrapMode wrap_u, + InterpWrapMode wrap_v); + /** * Cubic Mitchell sampling. * @@ -301,6 +348,9 @@ void interpolate_cubic_mitchell_fl( } // namespace blender::math +/* -------------------------------------------------------------------- */ +/* EWA sampling. */ + #define EWA_MAXIDX 255 extern const float EWA_WTS[EWA_MAXIDX + 1]; diff --git a/source/blender/blenlib/intern/math_interp.cc b/source/blender/blenlib/intern/math_interp.cc index 1668933012f..47a25b57a7e 100644 --- a/source/blender/blenlib/intern/math_interp.cc +++ b/source/blender/blenlib/intern/math_interp.cc @@ -20,6 +20,52 @@ namespace blender::math { +BLI_INLINE int wrap_coord(float u, int size, InterpWrapMode wrap) +{ + int x = 0; + switch (wrap) { + case InterpWrapMode::Extend: + x = math::clamp(int(u), 0, size - 1); + break; + case InterpWrapMode::Repeat: + x = int(floored_fmod(u, float(size))); + break; + case InterpWrapMode::Border: + x = int(u); + if (x < 0 || x >= size) { + x = -1; + } + break; + } + return x; +} + +void interpolate_nearest_wrapmode_fl(const float *buffer, + float *output, + int width, + int height, + int components, + float u, + float v, + InterpWrapMode wrap_u, + InterpWrapMode wrap_v) +{ + BLI_assert(buffer); + int x = wrap_coord(u, width, wrap_u); + int y = wrap_coord(v, height, wrap_v); + if (x < 0 || y < 0) { + 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]; + } +} + enum class eCubicFilter { BSpline, Mitchell, @@ -59,17 +105,6 @@ BLI_INLINE void bicubic_interpolation_uchar_simd( __m128 uv_floor = _mm_floor_ps(uv); __m128i i_uv = _mm_cvttps_epi32(uv_floor); - /* Sample area entirely outside image? - * We check if any of (iu+1, iv+1, width, height) < (0, 0, iu+1, iv+1). */ - __m128i i_uv_1 = _mm_add_epi32(i_uv, _mm_set_epi32(0, 0, 1, 1)); - __m128i cmp_a = _mm_or_si128(i_uv_1, _mm_set_epi32(height, width, 0, 0)); - __m128i cmp_b = _mm_shuffle_epi32(i_uv_1, _MM_SHUFFLE(1, 0, 3, 2)); - __m128i invalid = _mm_cmplt_epi32(cmp_a, cmp_b); - if (_mm_movemask_ps(_mm_castsi128_ps(invalid)) != 0) { - memset(output, 0, 4); - return; - } - __m128 frac_uv = _mm_sub_ps(uv, uv_floor); /* Calculate pixel weights. */ @@ -114,14 +149,21 @@ BLI_INLINE void bicubic_interpolation_uchar_simd( #endif /* BLI_HAVE_SSE4 */ template -static void bicubic_interpolation( - const T *src_buffer, T *output, int width, int height, int components, float u, float v) +BLI_INLINE void bicubic_interpolation(const T *src_buffer, + T *output, + int width, + int height, + int components, + float u, + float v, + InterpWrapMode wrap_u, + InterpWrapMode wrap_v) { BLI_assert(src_buffer && output); #if BLI_HAVE_SSE4 if constexpr (std::is_same_v) { - if (components == 4) { + if (components == 4 && wrap_u == InterpWrapMode::Extend && wrap_v == InterpWrapMode::Extend) { bicubic_interpolation_uchar_simd(src_buffer, output, width, height, u, v); return; } @@ -131,8 +173,12 @@ static void bicubic_interpolation( int iu = int(floor(u)); int iv = int(floor(v)); - /* Sample area entirely outside image? */ - if (iu + 1 < 0 || iu > width - 1 || iv + 1 < 0 || iv > height - 1) { + /* Sample area entirely outside image in border mode? */ + if (wrap_u == InterpWrapMode::Border && (iu + 1 < 0 || iu > width - 1)) { + memset(output, 0, size_t(components) * sizeof(T)); + return; + } + if (wrap_v == InterpWrapMode::Border && (iv + 1 < 0 || iv > height - 1)) { memset(output, 0, size_t(components) * sizeof(T)); return; } @@ -149,11 +195,17 @@ static void bicubic_interpolation( /* Read 4x4 source pixels and blend them. */ for (int n = 0; n < 4; n++) { int y1 = iv + n - 1; - CLAMP(y1, 0, height - 1); - for (int m = 0; m < 4; m++) { + y1 = wrap_coord(float(y1), height, wrap_v); + if (wrap_v == InterpWrapMode::Border && y1 < 0) { + continue; + } + for (int m = 0; m < 4; m++) { int x1 = iu + m - 1; - CLAMP(x1, 0, width - 1); + x1 = wrap_coord(float(x1), width, wrap_u); + if (wrap_u == InterpWrapMode::Border && x1 < 0) { + continue; + } float w = wx[m] * wy[n]; const T *data = src_buffer + (width * y1 + x1) * components; @@ -215,7 +267,6 @@ static void bicubic_interpolation( } } -template BLI_INLINE void bilinear_fl_impl(const float *buffer, float *output, int width, @@ -223,18 +274,18 @@ BLI_INLINE void bilinear_fl_impl(const float *buffer, int components, float u, float v, - bool wrap_x = false, - bool wrap_y = false) + InterpWrapMode wrap_x, + InterpWrapMode wrap_y) { BLI_assert(buffer && output); float a, b; float a_b, ma_b, a_mb, ma_mb; int y1, y2, x1, x2; - if (wrap_x) { + if (wrap_x == InterpWrapMode::Repeat) { u = floored_fmod(u, float(width)); } - if (wrap_y) { + if (wrap_y == InterpWrapMode::Repeat) { v = floored_fmod(v, float(height)); } @@ -251,44 +302,57 @@ BLI_INLINE void bilinear_fl_impl(const float *buffer, /* Check if +1 samples need wrapping, or we don't do wrapping then if * we are sampling completely outside the image. */ - if (wrap_x) { + if (wrap_x == InterpWrapMode::Repeat) { if (x2 >= width) { x2 = 0; } } - else if (border && (x2 < 0 || x1 >= width)) { + else if (wrap_x == InterpWrapMode::Border && (x2 < 0 || x1 >= width)) { copy_vn_fl(output, components, 0.0f); return; } - if (wrap_y) { + if (wrap_y == InterpWrapMode::Repeat) { if (y2 >= height) { y2 = 0; } } - else if (border && (y2 < 0 || y1 >= height)) { + else if (wrap_y == InterpWrapMode::Border && (y2 < 0 || y1 >= height)) { copy_vn_fl(output, components, 0.0f); return; } /* Sample locations. */ - if constexpr (border) { - row1 = (x1 < 0 || y1 < 0) ? empty : buffer + (int64_t(width) * y1 + x1) * components; - row2 = (x1 < 0 || y2 > height - 1) ? empty : buffer + (int64_t(width) * y2 + x1) * components; - row3 = (x2 > width - 1 || y1 < 0) ? empty : buffer + (int64_t(width) * y1 + x2) * components; - row4 = (x2 > width - 1 || y2 > height - 1) ? empty : - buffer + (int64_t(width) * y2 + x2) * components; + int x1c = blender::math::clamp(x1, 0, width - 1); + int x2c = blender::math::clamp(x2, 0, width - 1); + int y1c = blender::math::clamp(y1, 0, height - 1); + int y2c = blender::math::clamp(y2, 0, height - 1); + row1 = buffer + (int64_t(width) * y1c + x1c) * components; + row2 = buffer + (int64_t(width) * y2c + x1c) * components; + row3 = buffer + (int64_t(width) * y1c + x2c) * components; + row4 = buffer + (int64_t(width) * y2c + x2c) * components; + + if (wrap_x == InterpWrapMode::Border) { + if (x1 < 0) { + row1 = empty; + row2 = empty; + } + if (x2 > width - 1) { + row3 = empty; + row4 = empty; + } } - else { - x1 = blender::math::clamp(x1, 0, width - 1); - x2 = blender::math::clamp(x2, 0, width - 1); - y1 = blender::math::clamp(y1, 0, height - 1); - y2 = blender::math::clamp(y2, 0, height - 1); - row1 = buffer + (int64_t(width) * y1 + x1) * components; - row2 = buffer + (int64_t(width) * y2 + x1) * components; - row3 = buffer + (int64_t(width) * y1 + x2) * components; - row4 = buffer + (int64_t(width) * y2 + x2) * components; + if (wrap_y == InterpWrapMode::Border) { + if (y1 < 0) { + row1 = empty; + row3 = empty; + } + if (y2 > height - 1) { + row2 = empty; + row4 = empty; + } } + /* Finally, do interpolation. */ a = u - uf; b = v - vf; a_b = a * b; @@ -485,40 +549,58 @@ uchar4 interpolate_bilinear_byte(const uchar *buffer, int width, int height, flo float4 interpolate_bilinear_border_fl(const float *buffer, int width, int height, float u, float v) { float4 res; - bilinear_fl_impl(buffer, res, width, height, 4, u, v); + bilinear_fl_impl( + buffer, res, width, height, 4, u, v, InterpWrapMode::Border, InterpWrapMode::Border); return res; } void interpolate_bilinear_border_fl( const float *buffer, float *output, int width, int height, int components, float u, float v) { - bilinear_fl_impl(buffer, output, width, height, components, u, v); + bilinear_fl_impl(buffer, + output, + width, + height, + components, + u, + v, + InterpWrapMode::Border, + InterpWrapMode::Border); } float4 interpolate_bilinear_fl(const float *buffer, int width, int height, float u, float v) { float4 res; - bilinear_fl_impl(buffer, res, width, height, 4, u, v); + bilinear_fl_impl( + buffer, res, width, height, 4, u, v, InterpWrapMode::Extend, InterpWrapMode::Extend); return res; } void interpolate_bilinear_fl( const float *buffer, float *output, int width, int height, int components, float u, float v) { - bilinear_fl_impl(buffer, output, width, height, components, u, v); + bilinear_fl_impl(buffer, + output, + width, + height, + components, + u, + v, + InterpWrapMode::Extend, + InterpWrapMode::Extend); } -void interpolate_bilinear_wrap_fl(const float *buffer, - float *output, - int width, - int height, - int components, - float u, - float v, - bool wrap_x, - bool wrap_y) +void interpolate_bilinear_wrapmode_fl(const float *buffer, + float *output, + int width, + int height, + int components, + float u, + float v, + InterpWrapMode wrap_u, + InterpWrapMode wrap_v) { - bilinear_fl_impl(buffer, output, width, height, components, u, v, wrap_x, wrap_y); + bilinear_fl_impl(buffer, output, width, height, components, u, v, wrap_u, wrap_v); } uchar4 interpolate_bilinear_wrap_byte(const uchar *buffer, int width, int height, float u, float v) @@ -566,51 +648,84 @@ uchar4 interpolate_bilinear_wrap_byte(const uchar *buffer, int width, int height float4 interpolate_bilinear_wrap_fl(const float *buffer, int width, int height, float u, float v) { float4 res; - bilinear_fl_impl(buffer, res, width, height, 4, u, v, true, true); + bilinear_fl_impl( + buffer, res, width, height, 4, u, v, InterpWrapMode::Repeat, InterpWrapMode::Repeat); return res; } uchar4 interpolate_cubic_bspline_byte(const uchar *buffer, int width, int height, float u, float v) { uchar4 res; - bicubic_interpolation(buffer, res, width, height, 4, u, v); + bicubic_interpolation( + buffer, res, width, height, 4, u, v, InterpWrapMode::Extend, InterpWrapMode::Extend); return res; } float4 interpolate_cubic_bspline_fl(const float *buffer, int width, int height, float u, float v) { float4 res; - bicubic_interpolation(buffer, res, width, height, 4, u, v); + bicubic_interpolation( + buffer, res, width, height, 4, u, v, InterpWrapMode::Extend, InterpWrapMode::Extend); return res; } void interpolate_cubic_bspline_fl( const float *buffer, float *output, int width, int height, int components, float u, float v) +{ + bicubic_interpolation(buffer, + output, + width, + height, + components, + u, + v, + InterpWrapMode::Extend, + InterpWrapMode::Extend); +} + +void interpolate_cubic_bspline_wrapmode_fl(const float *buffer, + float *output, + int width, + int height, + int components, + float u, + float v, + math::InterpWrapMode wrap_u, + math::InterpWrapMode wrap_v) { bicubic_interpolation( - buffer, output, width, height, components, u, v); + buffer, output, width, height, components, u, v, wrap_u, wrap_v); } uchar4 interpolate_cubic_mitchell_byte( const uchar *buffer, int width, int height, float u, float v) { uchar4 res; - bicubic_interpolation(buffer, res, width, height, 4, u, v); + bicubic_interpolation( + buffer, res, width, height, 4, u, v, InterpWrapMode::Extend, InterpWrapMode::Extend); return res; } float4 interpolate_cubic_mitchell_fl(const float *buffer, int width, int height, float u, float v) { float4 res; - bicubic_interpolation(buffer, res, width, height, 4, u, v); + bicubic_interpolation( + buffer, res, width, height, 4, u, v, InterpWrapMode::Extend, InterpWrapMode::Extend); return res; } void interpolate_cubic_mitchell_fl( const float *buffer, float *output, int width, int height, int components, float u, float v) { - bicubic_interpolation( - buffer, output, width, height, components, u, v); + bicubic_interpolation(buffer, + output, + width, + height, + components, + u, + v, + InterpWrapMode::Extend, + InterpWrapMode::Extend); } } // namespace blender::math diff --git a/source/blender/blenlib/tests/BLI_math_interp_test.cc b/source/blender/blenlib/tests/BLI_math_interp_test.cc index 0ecf447803e..5d7a6488f85 100644 --- a/source/blender/blenlib/tests/BLI_math_interp_test.cc +++ b/source/blender/blenlib/tests/BLI_math_interp_test.cc @@ -198,25 +198,69 @@ TEST(math_interp, BilinearFloatPartiallyOutsideImageWrap) { float4 res; float4 exp1 = {64.5f, 65.5f, 66.5f, 67.5f}; - interpolate_bilinear_wrap_fl( - image_fl[0][0], res, image_width, image_height, 4, -0.5f, 2.0f, true, true); + interpolate_bilinear_wrapmode_fl(image_fl[0][0], + res, + image_width, + image_height, + 4, + -0.5f, + 2.0f, + InterpWrapMode::Repeat, + InterpWrapMode::Repeat); EXPECT_V4_NEAR(exp1, res, float_tolerance); res = interpolate_bilinear_wrap_fl(image_fl[0][0], image_width, image_height, -0.5f, 2.0f); EXPECT_V4_NEAR(exp1, res, float_tolerance); float4 exp2 = {217.92502f, 202.57501f, 190.22501f, 181.85f}; - interpolate_bilinear_wrap_fl( - image_fl[0][0], res, image_width, image_height, 4, 1.25f, 2.9f, true, true); + interpolate_bilinear_wrapmode_fl(image_fl[0][0], + res, + image_width, + image_height, + 4, + 1.25f, + 2.9f, + InterpWrapMode::Repeat, + InterpWrapMode::Repeat); EXPECT_V4_NEAR(exp2, res, float_tolerance); res = interpolate_bilinear_wrap_fl(image_fl[0][0], image_width, image_height, 1.25f, 2.9f); EXPECT_V4_NEAR(exp2, res, float_tolerance); float4 exp3 = {228.96f, 171.27998f, 114.32f, 63.84f}; - interpolate_bilinear_wrap_fl( - image_fl[0][0], res, image_width, image_height, 4, 2.2f, -0.1f, true, true); + interpolate_bilinear_wrapmode_fl(image_fl[0][0], + res, + image_width, + image_height, + 4, + 2.2f, + -0.1f, + InterpWrapMode::Repeat, + InterpWrapMode::Repeat); EXPECT_V4_NEAR(exp3, res, float_tolerance); res = interpolate_bilinear_wrap_fl(image_fl[0][0], image_width, image_height, 2.2f, -0.1f); EXPECT_V4_NEAR(exp3, res, float_tolerance); + + /* Wrap only V axis. */ + float4 exp4 = {191.5f, 191.0f, 163.5f, 163.0f}; + interpolate_bilinear_wrapmode_fl(image_fl[0][0], + res, + image_width, + image_height, + 4, + -0.5f, + 2.75f, + InterpWrapMode::Extend, + InterpWrapMode::Repeat); + EXPECT_V4_NEAR(exp4, res, float_tolerance); + interpolate_bilinear_wrapmode_fl(image_fl[0][0], + res, + image_width, + image_height, + 4, + -0.5f, + 5.75f, + InterpWrapMode::Extend, + InterpWrapMode::Repeat); + EXPECT_V4_NEAR(exp4, res, float_tolerance); } TEST(math_interp, BilinearCharFullyOutsideImage) @@ -244,6 +288,31 @@ TEST(math_interp, BilinearCharFullyOutsideImage) EXPECT_EQ(exp, res); } +TEST(math_interp, BilinearFloatFullyOutsideImage) +{ + float4 res; + float4 exp = {0, 0, 0, 0}; + /* Out of range on U */ + res = interpolate_bilinear_border_fl(image_fl[0][0], image_width, image_height, -1.5f, 0); + EXPECT_EQ(exp, res); + res = interpolate_bilinear_border_fl(image_fl[0][0], image_width, image_height, -1.1f, 0); + EXPECT_EQ(exp, res); + res = interpolate_bilinear_border_fl(image_fl[0][0], image_width, image_height, 3, 0); + EXPECT_EQ(exp, res); + res = interpolate_bilinear_border_fl(image_fl[0][0], image_width, image_height, 5, 0); + EXPECT_EQ(exp, res); + + /* Out of range on V */ + res = interpolate_bilinear_border_fl(image_fl[0][0], image_width, image_height, 0, -3.2f); + EXPECT_EQ(exp, res); + res = interpolate_bilinear_border_fl(image_fl[0][0], image_width, image_height, 0, -1.5f); + EXPECT_EQ(exp, res); + res = interpolate_bilinear_border_fl(image_fl[0][0], image_width, image_height, 0, 3.1f); + EXPECT_EQ(exp, res); + res = interpolate_bilinear_border_fl(image_fl[0][0], image_width, image_height, 0, 500.0f); + EXPECT_EQ(exp, res); +} + TEST(math_interp, CubicBSplineCharExactSamples) { uchar4 res; @@ -297,37 +366,69 @@ TEST(math_interp, CubicBSplineFloatPartiallyOutsideImage) float4 exp1 = {2.29861f, 3.92014f, 5.71528f, 8.430554f}; res = interpolate_cubic_bspline_fl(image_fl[0][0], image_width, image_height, -0.5f, 2.0f); EXPECT_V4_NEAR(exp1, res, float_tolerance); + interpolate_cubic_bspline_wrapmode_fl(image_fl[0][0], + res, + image_width, + image_height, + 4, + -0.5f, + 2.0f, + InterpWrapMode::Extend, + InterpWrapMode::Extend); + EXPECT_V4_NEAR(exp1, res, float_tolerance); + float4 exp2 = {85.41022f, 107.21497f, 135.13849f, 195.49146f}; res = interpolate_cubic_bspline_fl(image_fl[0][0], image_width, image_height, 1.25f, 2.9f); EXPECT_V4_NEAR(exp2, res, float_tolerance); + interpolate_cubic_bspline_wrapmode_fl(image_fl[0][0], + res, + image_width, + image_height, + 4, + 1.25f, + 2.9f, + InterpWrapMode::Extend, + InterpWrapMode::Extend); + EXPECT_V4_NEAR(exp2, res, float_tolerance); + float4 exp3 = {224.73579f, 160.66783f, 104.63521f, 48.60260f}; res = interpolate_cubic_bspline_fl(image_fl[0][0], image_width, image_height, 2.2f, -0.1f); EXPECT_V4_NEAR(exp3, res, float_tolerance); -} + interpolate_cubic_bspline_wrapmode_fl(image_fl[0][0], + res, + image_width, + image_height, + 4, + 2.2f, + -0.1f, + InterpWrapMode::Extend, + InterpWrapMode::Extend); + EXPECT_V4_NEAR(exp3, res, float_tolerance); -TEST(math_interp, CubicBSplineCharFullyOutsideImage) -{ - uchar4 res; - uchar4 exp = {0, 0, 0, 0}; - /* Out of range on U */ - res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, -1.5f, 0); - EXPECT_EQ(exp, res); - res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, -1.1f, 0); - EXPECT_EQ(exp, res); - res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 3, 0); - EXPECT_EQ(exp, res); - res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 5, 0); - EXPECT_EQ(exp, res); + /* Different wrap modes. */ + float4 exp4 = {122.66441f, 89.68848f, 60.85706f, 32.02566f}; + interpolate_cubic_bspline_wrapmode_fl(image_fl[0][0], + res, + image_width, + image_height, + 4, + 2.2f, + -0.1f, + InterpWrapMode::Border, + InterpWrapMode::Border); + EXPECT_V4_NEAR(exp4, res, float_tolerance); - /* Out of range on V */ - res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 0, -3.2f); - EXPECT_EQ(exp, res); - res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 0, -1.5f); - EXPECT_EQ(exp, res); - res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 0, 3.1f); - EXPECT_EQ(exp, res); - res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 0, 500.0f); - EXPECT_EQ(exp, res); + float4 exp5 = {189.57422f, 157.32167f, 122.71796f, 95.81750f}; + interpolate_cubic_bspline_wrapmode_fl(image_fl[0][0], + res, + image_width, + image_height, + 4, + 2.2f, + -0.1f, + InterpWrapMode::Repeat, + InterpWrapMode::Repeat); + EXPECT_V4_NEAR(exp5, res, float_tolerance); } TEST(math_interp, CubicMitchellCharExactSamples) diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.h b/source/blender/compositor/intern/COM_MemoryBuffer.h index 3039bd73378..80f22bdf529 100644 --- a/source/blender/compositor/intern/COM_MemoryBuffer.h +++ b/source/blender/compositor/intern/COM_MemoryBuffer.h @@ -461,15 +461,18 @@ class MemoryBuffer { if (sampler == PixelSampler::Bilinear) { /* Sample using Extend or Repeat. */ - math::interpolate_bilinear_wrap_fl(buffer_, - result, - w, - h, - num_channels_, - x, - y, - extend_x == MemoryBufferExtend::Repeat, - extend_y == MemoryBufferExtend::Repeat); + math::interpolate_bilinear_wrapmode_fl( + buffer_, + result, + w, + h, + num_channels_, + x, + y, + extend_x == MemoryBufferExtend::Repeat ? math::InterpWrapMode::Repeat : + math::InterpWrapMode::Extend, + extend_y == MemoryBufferExtend::Repeat ? math::InterpWrapMode::Repeat : + math::InterpWrapMode::Extend); } else { /* #PixelSampler::Bicubic */ /* Sample using Extend (Repeat is not implemented by `interpolate_cubic_bspline`). */ @@ -504,15 +507,18 @@ class MemoryBuffer { memcpy(result, buffer_, sizeof(float) * num_channels_); } else { - math::interpolate_bilinear_wrap_fl(buffer_, - result, - get_width(), - get_height(), - num_channels_, - u, - v, - extend_x == MemoryBufferExtend::Repeat, - extend_y == MemoryBufferExtend::Repeat); + math::interpolate_bilinear_wrapmode_fl( + buffer_, + result, + get_width(), + get_height(), + num_channels_, + u, + v, + extend_x == MemoryBufferExtend::Repeat ? math::InterpWrapMode::Repeat : + math::InterpWrapMode::Extend, + extend_y == MemoryBufferExtend::Repeat ? math::InterpWrapMode::Repeat : + math::InterpWrapMode::Extend); } } diff --git a/source/blender/compositor/realtime_compositor/COM_result.hh b/source/blender/compositor/realtime_compositor/COM_result.hh index e29e5ee3c26..d05b5c965d6 100644 --- a/source/blender/compositor/realtime_compositor/COM_result.hh +++ b/source/blender/compositor/realtime_compositor/COM_result.hh @@ -429,6 +429,10 @@ class Result { /* Identical to sample_nearest_extended but with bilinear interpolation. */ float4 sample_bilinear_extended(const float2 &coordinates) const; + float4 sample_nearest_wrap(const float2 &coordinates, bool wrap_x, bool wrap_y) const; + float4 sample_bilinear_wrap(const float2 &coordinates, bool wrap_x, bool wrap_y) const; + float4 sample_cubic_wrap(const float2 &coordinates, bool wrap_x, bool wrap_y) const; + /* Equivalent to the GLSL textureGrad() function with EWA filtering and extended boundary * condition. Note that extended boundaries only cover areas touched by the ellipses whose * center is inside the image, other areas will be zero. The coordinates are thus expected to @@ -614,6 +618,82 @@ inline float4 Result::sample_nearest_zero(const float2 &coordinates) const return pixel_value; } +inline float4 Result::sample_nearest_wrap(const float2 &coordinates, + bool wrap_x, + bool wrap_y) const +{ + float4 pixel_value = float4(0.0f, 0.0f, 0.0f, 1.0f); + if (is_single_value_) { + this->copy_pixel(pixel_value, float_texture_); + return pixel_value; + } + + const int2 size = domain_.size; + const float2 texel_coordinates = coordinates * float2(size); + + math::interpolate_nearest_wrapmode_fl( + this->float_texture(), + pixel_value, + size.x, + size.y, + this->channels_count(), + texel_coordinates.x, + texel_coordinates.y, + wrap_x ? math::InterpWrapMode::Repeat : math::InterpWrapMode::Border, + wrap_y ? math::InterpWrapMode::Repeat : math::InterpWrapMode::Border); + return pixel_value; +} + +inline float4 Result::sample_bilinear_wrap(const float2 &coordinates, + bool wrap_x, + bool wrap_y) const +{ + float4 pixel_value = float4(0.0f, 0.0f, 0.0f, 1.0f); + if (is_single_value_) { + this->copy_pixel(pixel_value, float_texture_); + return pixel_value; + } + + const int2 size = domain_.size; + const float2 texel_coordinates = coordinates * float2(size) - 0.5f; + + math::interpolate_bilinear_wrapmode_fl( + this->float_texture(), + pixel_value, + size.x, + size.y, + this->channels_count(), + texel_coordinates.x, + texel_coordinates.y, + wrap_x ? math::InterpWrapMode::Repeat : math::InterpWrapMode::Border, + wrap_y ? math::InterpWrapMode::Repeat : math::InterpWrapMode::Border); + return pixel_value; +} + +inline float4 Result::sample_cubic_wrap(const float2 &coordinates, bool wrap_x, bool wrap_y) const +{ + float4 pixel_value = float4(0.0f, 0.0f, 0.0f, 1.0f); + if (is_single_value_) { + this->copy_pixel(pixel_value, float_texture_); + return pixel_value; + } + + const int2 size = domain_.size; + const float2 texel_coordinates = coordinates * float2(size) - 0.5f; + + math::interpolate_cubic_bspline_wrapmode_fl( + this->float_texture(), + pixel_value, + size.x, + size.y, + this->channels_count(), + texel_coordinates.x, + texel_coordinates.y, + wrap_x ? math::InterpWrapMode::Repeat : math::InterpWrapMode::Border, + wrap_y ? math::InterpWrapMode::Repeat : math::InterpWrapMode::Border); + return pixel_value; +} + inline float4 Result::sample_bilinear_zero(const float2 &coordinates) const { float4 pixel_value = float4(0.0f, 0.0f, 0.0f, 1.0f); diff --git a/source/blender/compositor/realtime_compositor/algorithms/intern/realize_on_domain.cc b/source/blender/compositor/realtime_compositor/algorithms/intern/realize_on_domain.cc index f3e1fe72253..87d9aa1a6fa 100644 --- a/source/blender/compositor/realtime_compositor/algorithms/intern/realize_on_domain.cc +++ b/source/blender/compositor/realtime_compositor/algorithms/intern/realize_on_domain.cc @@ -104,7 +104,8 @@ static void realize_on_domain_gpu(Context &context, static void realize_on_domain_cpu(Result &input, Result &output, const Domain &domain, - const float3x3 &inverse_transformation) + const float3x3 &inverse_transformation, + const RealizationOptions &realization_options) { output.allocate_texture(domain); @@ -122,8 +123,22 @@ static void realize_on_domain_cpu(Result &input, const int2 input_size = input.domain().size; float2 normalized_coordinates = coordinates / float2(input_size); - /* TODO: Support other interpolations and wrapping modes. */ - output.store_pixel(texel, input.sample_nearest_zero(normalized_coordinates)); + float4 sample; + switch (realization_options.interpolation) { + case Interpolation::Nearest: + sample = input.sample_nearest_wrap( + normalized_coordinates, realization_options.wrap_x, realization_options.wrap_y); + break; + case Interpolation::Bilinear: + sample = input.sample_bilinear_wrap( + normalized_coordinates, realization_options.wrap_x, realization_options.wrap_y); + break; + case Interpolation::Bicubic: + sample = input.sample_cubic_wrap( + normalized_coordinates, realization_options.wrap_x, realization_options.wrap_y); + break; + } + output.store_pixel(texel, sample); }); } @@ -165,7 +180,7 @@ void realize_on_domain(Context &context, context, input, output, domain, inverse_transformation, realization_options); } else { - realize_on_domain_cpu(input, output, domain, inverse_transformation); + realize_on_domain_cpu(input, output, domain, inverse_transformation, realization_options); } } diff --git a/source/blender/imbuf/intern/transform.cc b/source/blender/imbuf/intern/transform.cc index 302914bbd23..87270a6e207 100644 --- a/source/blender/imbuf/intern/transform.cc +++ b/source/blender/imbuf/intern/transform.cc @@ -148,15 +148,15 @@ static void sample_image(const ImBuf *source, float u, float v, T *r_sample) } else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v) { if constexpr (WrapUV) { - math::interpolate_bilinear_wrap_fl(source->float_buffer.data, - r_sample, - source->x, - source->y, - NumChannels, - u, - v, - true, - true); + math::interpolate_bilinear_wrapmode_fl(source->float_buffer.data, + r_sample, + source->x, + source->y, + NumChannels, + u, + v, + math::InterpWrapMode::Repeat, + math::InterpWrapMode::Repeat); } else { math::interpolate_bilinear_fl(