From 5ed2eea0f6fca42ee64dd4dbf436383298099575 Mon Sep 17 00:00:00 2001 From: Aras Pranckevicius Date: Thu, 25 Jan 2024 11:45:24 +0100 Subject: [PATCH] ImBuf: Refactor pixel interpolation functions There exist a bunch of "give me a (filtered) image pixel at this location" functions, some with duplicated functionality, some with almost the same but not quite, some that look similar but behave slightly differently, etc. Some of them were in BLI, some were in ImBuf. This commit tries to improve the situation by: * Adding low level interpolation functions to `BLI_math_interp.hh` - With documentation on their behavior, - And with more unit tests. * At `ImBuf` level, there are only convenience inline wrappers to the above BLI functions (split off into a separate header `IMB_interp.hh`). However, since these wrappers are inline, some things get a tiny bit faster as a side effect. E.g. VSE image strip, scaling to 4K resolution (Windows/Ryzen5950X): - Nearest filter: 2.33 -> 1.94ms - Bilinear filter: 5.83 -> 5.69ms - Subsampled3x3 filter: 28.6 -> 22.4ms Details on the functions: - All of them have `_byte` and `_fl` suffixes. - They exist in 4-channel byte (uchar4) and float (float4), as well as explicitly passed amount of channels for other float images. - New functions in BLI `blender::math` namespace: - `interpolate_nearest` - `interpolate_bilinear` - `interpolate_bilinear_wrap`. Note that unlike previous "wrap" function, this one no longer requires the caller to do their own wrapping. - `interpolate_cubic_bspline`. Previous similar function was called just "bicubic" which could mean many different things. - Same functions exist in `IMB_interp.hh`, they are just convenience that takes ImBuf and uses data pointer, width, height from that. Other bits: - Renamed `mod_f_positive` to `floored_fmod` (better matches `safe_floored_modf` and `floored_modulo` that exist elsewhere), made it branchless and added more unit tests. - `interpolate_bilinear_wrap_fl` no longer clamps result to 0..1 range. Instead, moved the clamp to be outside of the call in `paint_image_proj.cc` and `paint_utils.cc`. Though the need for clamping in there is also questionable. Pull Request: https://projects.blender.org/blender/blender/pulls/117387 --- .../blenkernel/intern/gpencil_legacy.cc | 3 +- .../blender/blenkernel/intern/studiolight.cc | 20 +- .../blenkernel/intern/tracking_stabilize.cc | 88 +++-- source/blender/blenlib/BLI_math_base.h | 16 +- source/blender/blenlib/BLI_math_interp.hh | 213 ++++++++++-- .../blender/blenlib/intern/math_base_inline.c | 9 +- source/blender/blenlib/intern/math_interp.cc | 250 ++++++++------ .../blenlib/tests/BLI_math_base_test.cc | 14 +- .../blenlib/tests/BLI_math_interp_test.cc | 270 ++++++++++----- .../compositor/intern/COM_MemoryBuffer.h | 16 +- .../operations/COM_ImageOperation.cc | 17 +- .../operations/COM_MovieClipOperation.cc | 7 +- .../COM_MultilayerImageOperation.cc | 8 +- .../operations/COM_RenderLayersProg.cc | 23 +- .../draw/engines/image/image_drawing_mode.hh | 10 +- .../editors/animation/keyframes_general.cc | 2 +- .../editors/sculpt_paint/paint_image_proj.cc | 120 +++---- .../blender/editors/sculpt_paint/paint_ops.cc | 3 +- .../editors/sculpt_paint/paint_utils.cc | 46 +-- source/blender/imbuf/CMakeLists.txt | 2 + source/blender/imbuf/IMB_imbuf.hh | 38 --- source/blender/imbuf/IMB_interp.hh | 100 ++++++ source/blender/imbuf/intern/imageprocess.cc | 309 +----------------- source/blender/imbuf/intern/interp.cc | 27 ++ source/blender/imbuf/intern/scaling.cc | 11 +- source/blender/imbuf/intern/transform.cc | 44 +-- .../blender/render/intern/texture_margin.cc | 19 +- source/blender/sequencer/intern/effects.cc | 33 +- 28 files changed, 901 insertions(+), 817 deletions(-) create mode 100644 source/blender/imbuf/IMB_interp.hh create mode 100644 source/blender/imbuf/intern/interp.cc diff --git a/source/blender/blenkernel/intern/gpencil_legacy.cc b/source/blender/blenkernel/intern/gpencil_legacy.cc index 511b791d021..70519a1f85b 100644 --- a/source/blender/blenkernel/intern/gpencil_legacy.cc +++ b/source/blender/blenkernel/intern/gpencil_legacy.cc @@ -23,8 +23,7 @@ #include "BLT_translation.h" -#include "IMB_imbuf.hh" -#include "IMB_imbuf_types.hh" +#include "IMB_interp.hh" /* Allow using deprecated functionality for .blend file I/O. */ #define DNA_DEPRECATED_ALLOW diff --git a/source/blender/blenkernel/intern/studiolight.cc b/source/blender/blenkernel/intern/studiolight.cc index 4d5470ac2d3..1b88d449fc9 100644 --- a/source/blender/blenkernel/intern/studiolight.cc +++ b/source/blender/blenkernel/intern/studiolight.cc @@ -26,7 +26,7 @@ #include "DNA_listBase.h" #include "IMB_imbuf.hh" -#include "IMB_imbuf_types.hh" +#include "IMB_interp.hh" #include "IMB_openexr.hh" #include "GPU_texture.h" @@ -35,6 +35,8 @@ #include +using blender::float4; + /* Statics */ static ListBase studiolights; static int last_studiolight_id = 0; @@ -487,11 +489,11 @@ static void studiolight_create_matcap_specular_gputexture(StudioLight *sl) sl->flag |= STUDIOLIGHT_MATCAP_SPECULAR_GPUTEXTURE; } -static void studiolight_calculate_radiance(ImBuf *ibuf, float color[4], const float direction[3]) +static float4 studiolight_calculate_radiance(ImBuf *ibuf, const float direction[3]) { float uv[2]; direction_to_equirect(uv, direction); - nearest_interpolation_color_wrap(ibuf, nullptr, color, uv[0] * ibuf->x, uv[1] * ibuf->y); + return blender::imbuf::interpolate_nearest_fl(ibuf, uv[0] * ibuf->x, uv[1] * ibuf->y); } /* @@ -692,7 +694,7 @@ static void studiolight_radiance_preview(uint *icon_buffer, StudioLight *sl) uint alphamask = alpha_circle_mask(dx, dy, 0.5f - texel_size[0], 0.5f); if (alphamask != 0) { - float normal[3], direction[3], color[4]; + float normal[3], direction[3]; const float incoming[3] = {0.0f, 0.0f, -1.0f}; sphere_normal_from_uv(normal, dx, dy); reflect_v3_v3v3(direction, incoming, normal); @@ -700,7 +702,7 @@ static void studiolight_radiance_preview(uint *icon_buffer, StudioLight *sl) SWAP(float, direction[1], direction[2]); direction[1] = -direction[1]; - studiolight_calculate_radiance(sl->equirect_radiance_buffer, color, direction); + float4 color = studiolight_calculate_radiance(sl->equirect_radiance_buffer, direction); *pixel = rgb_to_cpack(linearrgb_to_srgb(color[0]), linearrgb_to_srgb(color[1]), @@ -716,6 +718,8 @@ static void studiolight_radiance_preview(uint *icon_buffer, StudioLight *sl) static void studiolight_matcap_preview(uint *icon_buffer, StudioLight *sl, bool flipped) { + using namespace blender; + BKE_studiolight_ensure_flag(sl, STUDIOLIGHT_EXTERNAL_IMAGE_LOADED); ImBuf *diffuse_buffer = sl->matcap_diffuse.ibuf; @@ -728,14 +732,12 @@ static void studiolight_matcap_preview(uint *icon_buffer, StudioLight *sl, bool dx = 1.0f - dx; } - float color[4]; float u = dx * diffuse_buffer->x - 1.0f; float v = dy * diffuse_buffer->y - 1.0f; - nearest_interpolation_color(diffuse_buffer, nullptr, color, u, v); + float4 color = imbuf::interpolate_nearest_fl(diffuse_buffer, u, v); if (specular_buffer) { - float specular[4]; - nearest_interpolation_color(specular_buffer, nullptr, specular, u, v); + float4 specular = imbuf::interpolate_nearest_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 076710b40c9..d41d5ce9b96 100644 --- a/source/blender/blenkernel/intern/tracking_stabilize.cc +++ b/source/blender/blenkernel/intern/tracking_stabilize.cc @@ -32,6 +32,7 @@ #include "IMB_colormanagement.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" +#include "IMB_interp.hh" #include "MEM_guardedalloc.h" /* == Parameterization constants == */ @@ -1286,34 +1287,79 @@ void BKE_tracking_stabilization_data_get(MovieClip *clip, discard_stabilization_working_context(ctx); } -using interpolation_func = void (*)(const ImBuf *, ImBuf *, float, float, int, int); - struct TrackingStabilizeFrameInterpolationData { ImBuf *ibuf; ImBuf *tmpibuf; float (*mat)[4]; - - interpolation_func interpolation; + int tracking_filter; }; static void tracking_stabilize_frame_interpolation_cb(void *__restrict userdata, - const int j, + const int y, const TaskParallelTLS *__restrict /*tls*/) { + using namespace blender; + TrackingStabilizeFrameInterpolationData *data = static_cast(userdata); ImBuf *ibuf = data->ibuf; ImBuf *tmpibuf = data->tmpibuf; float(*mat)[4] = data->mat; - interpolation_func interpolation = data->interpolation; + float vec[3] = {0.0f, float(y), 0.0f}; + float rvec[3]; - for (int i = 0; i < tmpibuf->x; i++) { - float vec[3] = {float(i), float(j), 0.0f}; - - mul_v3_m4v3(vec, mat, vec); - - interpolation(ibuf, tmpibuf, vec[0], vec[1], i, j); + if (ibuf->float_buffer.data) { + /* Float image. */ + float4 *dst = reinterpret_cast(tmpibuf->float_buffer.data) + y * tmpibuf->x; + if (data->tracking_filter == TRACKING_FILTER_BILINEAR) { + for (int x = 0; x < tmpibuf->x; x++, dst++) { + vec[0] = float(x); + mul_v3_m4v3(rvec, mat, vec); + *dst = imbuf::interpolate_bilinear_fl(ibuf, rvec[0], rvec[1]); + } + } + else if (data->tracking_filter == TRACKING_FILTER_BICUBIC) { + for (int x = 0; x < tmpibuf->x; x++, dst++) { + vec[0] = float(x); + mul_v3_m4v3(rvec, mat, vec); + *dst = imbuf::interpolate_cubic_bspline_fl(ibuf, rvec[0], rvec[1]); + } + } + else { + /* Nearest or fallback to nearest. */ + 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]); + } + } + } + else if (ibuf->byte_buffer.data) { + /* Byte image. */ + uchar4 *dst = reinterpret_cast(tmpibuf->byte_buffer.data) + y * tmpibuf->x; + if (data->tracking_filter == TRACKING_FILTER_BILINEAR) { + for (int x = 0; x < tmpibuf->x; x++, dst++) { + vec[0] = float(x); + mul_v3_m4v3(rvec, mat, vec); + *dst = imbuf::interpolate_bilinear_byte(ibuf, rvec[0], rvec[1]); + } + } + else if (data->tracking_filter == TRACKING_FILTER_BICUBIC) { + for (int x = 0; x < tmpibuf->x; x++, dst++) { + vec[0] = float(x); + mul_v3_m4v3(rvec, mat, vec); + *dst = imbuf::interpolate_cubic_bspline_byte(ibuf, rvec[0], rvec[1]); + } + } + else { + /* Nearest or fallback to nearest. */ + 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]); + } + } } } @@ -1327,8 +1373,6 @@ ImBuf *BKE_tracking_stabilize_frame( int width = ibuf->x, height = ibuf->y; float pixel_aspect = tracking->camera.pixel_aspect; float mat[4][4]; - int filter = tracking->stabilization.filter; - interpolation_func interpolation = nullptr; int ibuf_flags; if (translation) { @@ -1378,25 +1422,11 @@ ImBuf *BKE_tracking_stabilize_frame( * thus we need the inverse of the transformation to apply. */ invert_m4(mat); - if (filter == TRACKING_FILTER_NEAREST) { - interpolation = nearest_interpolation; - } - else if (filter == TRACKING_FILTER_BILINEAR) { - interpolation = bilinear_interpolation; - } - else if (filter == TRACKING_FILTER_BICUBIC) { - interpolation = bicubic_interpolation; - } - else { - /* fallback to default interpolation method */ - interpolation = nearest_interpolation; - } - TrackingStabilizeFrameInterpolationData data = {}; data.ibuf = ibuf; data.tmpibuf = tmpibuf; data.mat = mat; - data.interpolation = interpolation; + data.tracking_filter = tracking->stabilization.filter; TaskParallelSettings settings; BLI_parallel_range_settings_defaults(&settings); diff --git a/source/blender/blenlib/BLI_math_base.h b/source/blender/blenlib/BLI_math_base.h index 35e9f1d6ea9..011963e7081 100644 --- a/source/blender/blenlib/BLI_math_base.h +++ b/source/blender/blenlib/BLI_math_base.h @@ -289,18 +289,24 @@ MINLINE uint ceil_to_multiple_u(uint a, uint b); MINLINE uint64_t ceil_to_multiple_ul(uint64_t a, uint64_t b); /** - * modulo that handles negative numbers, works the same as Python's. + * Floored modulo that is useful for wrapping numbers over \a n, + * including when \a i is negative. + * + * This is the same as Python % or GLSL mod(): `mod_i(-5, 3) = 1`. + * + * \return an integer in the interval [0, n), same sign as n. */ MINLINE int mod_i(int i, int n); /** - * Modulo that returns a positive result, regardless of the sign of \a f. + * Floored modulo that is useful for wrapping numbers over \a n, + * including when \a f is negative. * - * For example, mod_f_positive(-0.1, 1.0) => 0.9. + * This is the same as Python % or GLSL mod(): `floored_fmod(-0.2, 1.0) = 0.8`. * - * \returns a float in the interval [0, n). + * \return a float in the interval [0, n), same sign as n. */ -MINLINE float mod_f_positive(float f, float n); +MINLINE float floored_fmod(float f, float n); /** * Round to closest even number, halfway cases are rounded away from zero. diff --git a/source/blender/blenlib/BLI_math_interp.hh b/source/blender/blenlib/BLI_math_interp.hh index 1c1c6edc635..bb88ccba348 100644 --- a/source/blender/blenlib/BLI_math_interp.hh +++ b/source/blender/blenlib/BLI_math_interp.hh @@ -6,33 +6,204 @@ /** \file * \ingroup bli + * + * 2D image sampling with filtering functions. + * + * All functions take (u, v) texture coordinate, non-normalized (i.e. ranging + * from (0,0) to (width,height) over the image). + * + * Any filtering done on texel values just blends them without color space or + * gamma conversions. + * + * Sampling completely outside the image returns transparent black. */ -#ifdef __cplusplus -extern "C" { -#endif +#include "BLI_math_base.h" +#include "BLI_math_vector_types.hh" -void BLI_bicubic_interpolation_fl( +namespace blender::math { + +/** + * Nearest (point) sampling. + * + * 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). + */ + +inline void interpolate_nearest_byte( + const uchar *buffer, uchar *output, int width, int height, 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) { + output[0] = output[1] = output[2] = output[3] = 0; + return; + } + + 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) +{ + uchar4 res; + interpolate_nearest_byte(buffer, res, width, height, u, v); + return res; +} + +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 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_fl( + const float *buffer, int width, int height, float u, float v) +{ + float4 res; + interpolate_nearest_fl(buffer, res, width, height, 4, u, v); + return res; +} + +/** + * Wrapped nearest sampling. (u,v) is repeated to be inside the image size. + */ + +inline void interpolate_nearest_wrap_byte( + const uchar *buffer, uchar *output, int width, int height, float u, float v) +{ + BLI_assert(buffer); + u = floored_fmod(u, float(width)); + v = floored_fmod(v, float(height)); + int x = int(u); + int y = int(v); + BLI_assert(x >= 0 && y >= 0 && x < width && y < height); + + 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_wrap_byte( + const uchar *buffer, int width, int height, float u, float v) +{ + uchar4 res; + interpolate_nearest_wrap_byte(buffer, res, width, height, u, v); + return res; +} + +inline void interpolate_nearest_wrap_fl( + const float *buffer, float *output, int width, int height, int components, float u, float v) +{ + BLI_assert(buffer); + u = floored_fmod(u, float(width)); + v = floored_fmod(v, float(height)); + int x = int(u); + int y = int(v); + BLI_assert(x >= 0 && y >= 0 && x < width && y < height); + + 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_wrap_fl( + const float *buffer, int width, int height, float u, float v) +{ + float4 res; + interpolate_nearest_wrap_fl(buffer, res, width, height, 4, u, v); + return res; +} + +/** + * Bilinear sampling. + * + * Takes four image samples at floor(u,v) and floor(u,v)+1, and blends them + * based on fractional parts of u,v. Samples outside the image are turned + * into transparent black. + * + * Note that you probably want to subtract 0.5 from u,v before this function, + * to get proper filtering. + */ + +[[nodiscard]] uchar4 interpolate_bilinear_byte( + const uchar *buffer, int width, int height, float u, float v); + +[[nodiscard]] float4 interpolate_bilinear_fl( + const float *buffer, int width, int height, float u, float v); + +void interpolate_bilinear_fl( const float *buffer, float *output, int width, int height, int components, float u, float v); -void BLI_bicubic_interpolation_char( - const unsigned char *buffer, unsigned char *output, int width, int height, float u, float v); +/** + * Wrapped bilinear sampling. (u,v) is repeated to be inside the image size, + * including properly wrapping samples that are right on the edges. + */ -void BLI_bilinear_interpolation_fl( +[[nodiscard]] uchar4 interpolate_bilinear_wrap_byte( + const uchar *buffer, int width, int height, float u, float v); + +[[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); + +/** + * Cubic B-Spline sampling. + * + * Takes 4x4 image samples at floor(u,v)-1 .. floor(u,v)+2, and blends them + * based on fractional parts of u,v. Uses B-Spline variant Mitchell-Netravali + * filter (B=1, C=0), which has no ringing but introduces quite a lot of blur. + * Samples outside the image are clamped to texels at image edge. + * + * Note that you probably want to subtract 0.5 from u,v before this function, + * to get proper filtering. + */ + +[[nodiscard]] uchar4 interpolate_cubic_bspline_byte( + const uchar *buffer, int width, int height, float u, float v); + +[[nodiscard]] float4 interpolate_cubic_bspline_fl( + const float *buffer, int width, int height, float u, float v); + +void interpolate_cubic_bspline_fl( const float *buffer, float *output, int width, int height, int components, float u, float v); -void BLI_bilinear_interpolation_char( - const unsigned char *buffer, unsigned char *output, int width, int height, float u, float v); - -void BLI_bilinear_interpolation_wrap_fl(const float *buffer, - float *output, - int width, - int height, - int components, - float u, - float v, - bool wrap_x, - bool wrap_y); +} // namespace blender::math #define EWA_MAXIDX 255 extern const float EWA_WTS[EWA_MAXIDX + 1]; @@ -56,7 +227,3 @@ void BLI_ewa_filter(int width, ewa_filter_read_pixel_cb read_pixel_cb, void *userdata, float result[4]); - -#ifdef __cplusplus -} -#endif diff --git a/source/blender/blenlib/intern/math_base_inline.c b/source/blender/blenlib/intern/math_base_inline.c index 92e756f604d..723bc07c84d 100644 --- a/source/blender/blenlib/intern/math_base_inline.c +++ b/source/blender/blenlib/intern/math_base_inline.c @@ -310,14 +310,9 @@ MINLINE int mod_i(int i, int n) return (i % n + n) % n; } -MINLINE float mod_f_positive(const float f, const float n) +MINLINE float floored_fmod(const float f, const float n) { - const float modulo = fmodf(f, n); - if (modulo < 0) { - /* fmodf returns a value in the interval (-n, n). */ - return modulo + n; - } - return modulo; + return f - n * floorf(f / n); } MINLINE float fractf(float a) diff --git a/source/blender/blenlib/intern/math_interp.cc b/source/blender/blenlib/intern/math_interp.cc index a5d82d9381f..1a46fa0ce7d 100644 --- a/source/blender/blenlib/intern/math_interp.cc +++ b/source/blender/blenlib/intern/math_interp.cc @@ -117,6 +117,8 @@ static void bicubic_interpolation( { using namespace blender; + BLI_assert(src_buffer && output); + #if BLI_HAVE_SSE2 if constexpr (std::is_same_v) { if (components == 4) { @@ -203,33 +205,28 @@ static void bicubic_interpolation( } } -void BLI_bicubic_interpolation_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); -} - -void BLI_bicubic_interpolation_char( - const uchar *buffer, uchar *output, int width, int height, float u, float v) -{ - bicubic_interpolation(buffer, output, width, height, 4, u, v); -} - -/* BILINEAR INTERPOLATION */ -BLI_INLINE void bilinear_interpolation_fl(const float *float_buffer, - float *float_output, - int width, - int height, - int components, - float u, - float v, - bool wrap_x, - bool wrap_y) +BLI_INLINE void bilinear_fl_impl(const float *buffer, + float *output, + int width, + int height, + int components, + float u, + float v, + bool wrap_x = false, + bool wrap_y = false) { + BLI_assert(buffer && output); float a, b; float a_b, ma_b, a_mb, ma_mb; int y1, y2, x1, x2; + if (wrap_x) { + u = floored_fmod(u, float(width)); + } + if (wrap_y) { + v = floored_fmod(v, float(height)); + } + float uf = floorf(u); float vf = floorf(v); @@ -241,60 +238,54 @@ BLI_INLINE void bilinear_interpolation_fl(const float *float_buffer, const float *row1, *row2, *row3, *row4; const float empty[4] = {0.0f, 0.0f, 0.0f, 0.0f}; - /* pixel value must be already wrapped, however values at boundaries may flip */ + /* Check if +1 samples need wrapping, or we we don't do wrapping then if + * we are sampling completely outside the image. */ if (wrap_x) { - if (x1 < 0) { - x1 = width - 1; - } if (x2 >= width) { x2 = 0; } } else if (x2 < 0 || x1 >= width) { - copy_vn_fl(float_output, components, 0.0f); + copy_vn_fl(output, components, 0.0f); return; } - if (wrap_y) { - if (y1 < 0) { - y1 = height - 1; - } if (y2 >= height) { y2 = 0; } } else if (y2 < 0 || y1 >= height) { - copy_vn_fl(float_output, components, 0.0f); + copy_vn_fl(output, components, 0.0f); return; } - /* sample including outside of edges of image */ + /* Sample including outside of edges of image. */ if (x1 < 0 || y1 < 0) { row1 = empty; } else { - row1 = float_buffer + width * y1 * components + components * x1; + row1 = buffer + width * y1 * components + components * x1; } if (x1 < 0 || y2 > height - 1) { row2 = empty; } else { - row2 = float_buffer + width * y2 * components + components * x1; + row2 = buffer + width * y2 * components + components * x1; } if (x2 > width - 1 || y1 < 0) { row3 = empty; } else { - row3 = float_buffer + width * y1 * components + components * x2; + row3 = buffer + width * y1 * components + components * x2; } if (x2 > width - 1 || y2 > height - 1) { row4 = empty; } else { - row4 = float_buffer + width * y2 * components + components * x2; + row4 = buffer + width * y2 * components + components * x2; } a = u - uf; @@ -305,12 +296,12 @@ BLI_INLINE void bilinear_interpolation_fl(const float *float_buffer, ma_mb = (1.0f - a) * (1.0f - b); if (components == 1) { - float_output[0] = ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0]; + output[0] = ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0]; } else if (components == 3) { - float_output[0] = ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0]; - float_output[1] = ma_mb * row1[1] + a_mb * row3[1] + ma_b * row2[1] + a_b * row4[1]; - float_output[2] = ma_mb * row1[2] + a_mb * row3[2] + ma_b * row2[2] + a_b * row4[2]; + output[0] = ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0]; + output[1] = ma_mb * row1[1] + a_mb * row3[1] + ma_b * row2[1] + a_b * row4[1]; + output[2] = ma_mb * row1[2] + a_mb * row3[2] + ma_b * row2[2] + a_b * row4[2]; } else { #if BLI_HAVE_SSE2 @@ -325,19 +316,23 @@ BLI_INLINE void bilinear_interpolation_fl(const float *float_buffer, __m128 rgba13 = _mm_add_ps(rgba1, rgba3); __m128 rgba24 = _mm_add_ps(rgba2, rgba4); __m128 rgba = _mm_add_ps(rgba13, rgba24); - _mm_storeu_ps(float_output, rgba); + _mm_storeu_ps(output, rgba); #else - float_output[0] = ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0]; - float_output[1] = ma_mb * row1[1] + a_mb * row3[1] + ma_b * row2[1] + a_b * row4[1]; - float_output[2] = ma_mb * row1[2] + a_mb * row3[2] + ma_b * row2[2] + a_b * row4[2]; - float_output[3] = ma_mb * row1[3] + a_mb * row3[3] + ma_b * row2[3] + a_b * row4[3]; + output[0] = ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0]; + output[1] = ma_mb * row1[1] + a_mb * row3[1] + ma_b * row2[1] + a_b * row4[1]; + output[2] = ma_mb * row1[2] + a_mb * row3[2] + ma_b * row2[2] + a_b * row4[2]; + output[3] = ma_mb * row1[3] + a_mb * row3[3] + ma_b * row2[3] + a_b * row4[3]; #endif } } -void BLI_bilinear_interpolation_char( - const uchar *buffer, uchar *output, int width, int height, float u, float v) +namespace blender::math { + +uchar4 interpolate_bilinear_byte(const uchar *buffer, int width, int height, float u, float v) { + BLI_assert(buffer); + uchar4 res; + #if BLI_HAVE_SSE2 /* Bilinear interpolation needs to read and blend four image pixels, while * also handling conditions of sample coordinate being outside of the @@ -375,7 +370,7 @@ void BLI_bilinear_interpolation_char( int ycoord[4]; _mm_storeu_ps((float *)xcoord, _mm_castsi128_ps(x1234)); _mm_storeu_ps((float *)ycoord, _mm_castsi128_ps(y1234)); - int sample1 = ((const int *)buffer)[ycoord[0] * (int64_t)width + xcoord[0]]; + int sample1 = ((const int *)buffer)[ycoord[0] * int64_t(width) + xcoord[0]]; int sample2 = ((const int *)buffer)[ycoord[1] * int64_t(width) + xcoord[1]]; int sample3 = ((const int *)buffer)[ycoord[2] * int64_t(width) + xcoord[2]]; int sample4 = ((const int *)buffer)[ycoord[3] * int64_t(width) + xcoord[3]]; @@ -414,37 +409,26 @@ void BLI_bilinear_interpolation_char( __m128i rgba32 = _mm_cvttps_epi32(rgba); __m128i rgba16 = _mm_packs_epi32(rgba32, _mm_setzero_si128()); __m128i rgba8 = _mm_packus_epi16(rgba16, _mm_setzero_si128()); - _mm_store_ss((float *)output, _mm_castsi128_ps(rgba8)); + _mm_store_ss((float *)&res, _mm_castsi128_ps(rgba8)); #else - float a, b; - float a_b, ma_b, a_mb, ma_mb; - int y1, y2, x1, x2; - float uf = floorf(u); float vf = floorf(v); - x1 = (int)uf; - x2 = x1 + 1; - y1 = (int)vf; - y2 = y1 + 1; + int x1 = (int)uf; + int x2 = x1 + 1; + int y1 = (int)vf; + int y2 = y1 + 1; + /* Completely outside of the image? */ + if (x2 < 0 || x1 >= width || y2 < 0 || y1 >= height) { + return uchar4(0); + } + + /* Sample including outside of edges of image. */ const uchar *row1, *row2, *row3, *row4; uchar empty[4] = {0, 0, 0, 0}; - - /* completely outside of the image? */ - if (x2 < 0 || x1 >= width) { - copy_vn_uchar(output, 4, 0); - return; - } - - if (y2 < 0 || y1 >= height) { - copy_vn_uchar(output, 4, 0); - return; - } - - /* sample including outside of edges of image */ if (x1 < 0 || y1 < 0) { row1 = empty; } @@ -473,39 +457,119 @@ void BLI_bilinear_interpolation_char( row4 = buffer + width * y2 * 4 + 4 * x2; } - a = u - uf; - b = v - vf; - a_b = a * b; - ma_b = (1.0f - a) * b; - a_mb = a * (1.0f - b); - ma_mb = (1.0f - a) * (1.0f - b); + float a = u - uf; + float b = v - vf; + float a_b = a * b; + float ma_b = (1.0f - a) * b; + float a_mb = a * (1.0f - b); + float ma_mb = (1.0f - a) * (1.0f - b); - output[0] = (uchar)(ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0] + 0.5f); - output[1] = (uchar)(ma_mb * row1[1] + a_mb * row3[1] + ma_b * row2[1] + a_b * row4[1] + 0.5f); - output[2] = (uchar)(ma_mb * row1[2] + a_mb * row3[2] + ma_b * row2[2] + a_b * row4[2] + 0.5f); - output[3] = (uchar)(ma_mb * row1[3] + a_mb * row3[3] + ma_b * row2[3] + a_b * row4[3] + 0.5f); + res.x = (uchar)(ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0] + 0.5f); + res.y = (uchar)(ma_mb * row1[1] + a_mb * row3[1] + ma_b * row2[1] + a_b * row4[1] + 0.5f); + res.z = (uchar)(ma_mb * row1[2] + a_mb * row3[2] + ma_b * row2[2] + a_b * row4[2] + 0.5f); + res.w = (uchar)(ma_mb * row1[3] + a_mb * row3[3] + ma_b * row2[3] + a_b * row4[3] + 0.5f); #endif + + return res; } -void BLI_bilinear_interpolation_fl( +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); + return res; +} + +void interpolate_bilinear_fl( const float *buffer, float *output, int width, int height, int components, float u, float v) { - bilinear_interpolation_fl(buffer, output, width, height, components, u, v, false, false); + bilinear_fl_impl(buffer, output, width, height, components, u, v); } -void BLI_bilinear_interpolation_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_wrap_fl(const float *buffer, + float *output, + int width, + int height, + int components, + float u, + float v, + bool wrap_x, + bool wrap_y) { - bilinear_interpolation_fl(buffer, output, width, height, components, u, v, wrap_x, wrap_y); + bilinear_fl_impl(buffer, output, width, height, components, u, v, wrap_x, wrap_y); } +uchar4 interpolate_bilinear_wrap_byte(const uchar *buffer, int width, int height, float u, float v) +{ + u = floored_fmod(u, float(width)); + v = floored_fmod(v, float(height)); + float uf = floorf(u); + float vf = floorf(v); + + int x1 = (int)uf; + int x2 = x1 + 1; + int y1 = (int)vf; + int y2 = y1 + 1; + + /* Wrap interpolation pixels if needed. */ + BLI_assert(x1 >= 0 && x1 < width && y1 >= 0 && y1 < height); + if (x2 >= width) { + x2 = 0; + } + if (y2 >= height) { + y2 = 0; + } + + float a = u - uf; + float b = v - vf; + float a_b = a * b; + float ma_b = (1.0f - a) * b; + float a_mb = a * (1.0f - b); + float ma_mb = (1.0f - a) * (1.0f - b); + + /* Blend samples. */ + const uchar *row1 = buffer + (int64_t(width) * y1 + x1) * 4; + const uchar *row2 = buffer + (int64_t(width) * y2 + x1) * 4; + const uchar *row3 = buffer + (int64_t(width) * y1 + x2) * 4; + const uchar *row4 = buffer + (int64_t(width) * y2 + x2) * 4; + + uchar4 res; + res.x = uchar(ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0] + 0.5f); + res.y = uchar(ma_mb * row1[1] + a_mb * row3[1] + ma_b * row2[1] + a_b * row4[1] + 0.5f); + res.z = uchar(ma_mb * row1[2] + a_mb * row3[2] + ma_b * row2[2] + a_b * row4[2] + 0.5f); + res.w = uchar(ma_mb * row1[3] + a_mb * row3[3] + ma_b * row2[3] + a_b * row4[3] + 0.5f); + return res; +} + +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); + 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); + 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); + 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); +} + +} // namespace blender::math + /************************************************************************** * Filtering method based on * "Creating raster omnimax images from multiple perspective views diff --git a/source/blender/blenlib/tests/BLI_math_base_test.cc b/source/blender/blenlib/tests/BLI_math_base_test.cc index 18e7a8282e8..50db607e75a 100644 --- a/source/blender/blenlib/tests/BLI_math_base_test.cc +++ b/source/blender/blenlib/tests/BLI_math_base_test.cc @@ -183,11 +183,17 @@ TEST(math_base, InterpolateInt) EXPECT_EQ(math::interpolate(100, 200, 0.4f), 140); } -TEST(math_base, ModFPositive) +TEST(math_base, FlooredFMod) { - EXPECT_FLOAT_EQ(mod_f_positive(3.27f, 1.57f), 0.12999988f); - EXPECT_FLOAT_EQ(mod_f_positive(327.f, 47.f), 45.f); - EXPECT_FLOAT_EQ(mod_f_positive(-0.1f, 1.0f), 0.9f); + EXPECT_FLOAT_EQ(floored_fmod(3.27f, 1.57f), 0.12999988f); + EXPECT_FLOAT_EQ(floored_fmod(327.f, 47.f), 45.f); + EXPECT_FLOAT_EQ(floored_fmod(-0.1f, 1.0f), 0.9f); + EXPECT_FLOAT_EQ(floored_fmod(-0.9f, 1.0f), 0.1f); + EXPECT_FLOAT_EQ(floored_fmod(-100.1f, 1.0f), 0.90000153f); + EXPECT_FLOAT_EQ(floored_fmod(-0.1f, 12345.0f), 12344.9f); + EXPECT_FLOAT_EQ(floored_fmod(12345.1f, 12345.0f), 0.099609375f); + EXPECT_FLOAT_EQ(floored_fmod(12344.999f, 12345.0f), 12344.999f); + EXPECT_FLOAT_EQ(floored_fmod(12345.0f, 12345.0f), 0.0f); } } // namespace blender::tests diff --git a/source/blender/blenlib/tests/BLI_math_interp_test.cc b/source/blender/blenlib/tests/BLI_math_interp_test.cc index b7531f7e6f9..cfbf5558c35 100644 --- a/source/blender/blenlib/tests/BLI_math_interp_test.cc +++ b/source/blender/blenlib/tests/BLI_math_interp_test.cc @@ -8,7 +8,9 @@ #include "BLI_math_interp.hh" using namespace blender; +using namespace blender::math; +static constexpr float float_tolerance = 0.00005f; static constexpr int image_width = 3; static constexpr int image_height = 3; static constexpr unsigned char image_char[image_height][image_width][4] = { @@ -16,147 +18,241 @@ static constexpr unsigned char image_char[image_height][image_width][4] = { {{0, 1, 2, 3}, {62, 72, 82, 92}, {126, 127, 128, 129}}, {{1, 2, 3, 4}, {73, 108, 153, 251}, {128, 129, 130, 131}}, }; +static constexpr float image_fl[image_height][image_width][4] = { + {{255, 254, 217, 216}, {230, 230, 230, 230}, {240, 160, 90, 20}}, + {{0, 1, 2, 3}, {62, 72, 82, 92}, {126, 127, 128, 129}}, + {{1, 2, 3, 4}, {73, 108, 153, 251}, {128, 129, 130, 131}}, +}; TEST(math_interp, BilinearCharExactSamples) { - unsigned char res[4]; - unsigned char exp1[4] = {73, 108, 153, 251}; - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 1.0f, 2.0f); - EXPECT_EQ_ARRAY(exp1, res, 4); - unsigned char exp2[4] = {240, 160, 90, 20}; - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 2.0f, 0.0f); - EXPECT_EQ_ARRAY(exp2, res, 4); + uchar4 res; + uchar4 exp1 = {73, 108, 153, 251}; + res = interpolate_bilinear_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_bilinear_byte(image_char[0][0], image_width, image_height, 2.0f, 0.0f); + EXPECT_EQ(exp2, res); } TEST(math_interp, BilinearCharHalfwayUSamples) { - unsigned char res[4]; - unsigned char exp1[4] = {31, 37, 42, 48}; - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 0.5f, 1.0f); - EXPECT_EQ_ARRAY(exp1, res, 4); - unsigned char exp2[4] = {243, 242, 224, 223}; - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 0.5f, 0.0f); - EXPECT_EQ_ARRAY(exp2, res, 4); + uchar4 res; + uchar4 exp1 = {31, 37, 42, 48}; + res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 0.5f, 1.0f); + EXPECT_EQ(exp1, res); + uchar4 exp2 = {243, 242, 224, 223}; + res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 0.5f, 0.0f); + EXPECT_EQ(exp2, res); } TEST(math_interp, BilinearCharHalfwayVSamples) { - unsigned char res[4]; - unsigned char exp1[4] = {1, 2, 3, 4}; - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 0.0f, 1.5f); - EXPECT_EQ_ARRAY(exp1, res, 4); - unsigned char exp2[4] = {127, 128, 129, 130}; - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 2.0f, 1.5f); - EXPECT_EQ_ARRAY(exp2, res, 4); + uchar4 res; + uchar4 exp1 = {1, 2, 3, 4}; + res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 0.0f, 1.5f); + EXPECT_EQ(exp1, res); + uchar4 exp2 = {127, 128, 129, 130}; + res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 2.0f, 1.5f); + EXPECT_EQ(exp2, res); } TEST(math_interp, BilinearCharSamples) { - unsigned char res[4]; - unsigned char exp1[4] = {136, 133, 132, 130}; - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 1.25f, 0.625f); - EXPECT_EQ_ARRAY(exp1, res, 4); - unsigned char exp2[4] = {219, 191, 167, 142}; - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 1.4f, 0.1f); - EXPECT_EQ_ARRAY(exp2, res, 4); + uchar4 res; + uchar4 exp1 = {136, 133, 132, 130}; + res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 1.25f, 0.625f); + EXPECT_EQ(exp1, res); + uchar4 exp2 = {219, 191, 167, 142}; + res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 1.4f, 0.1f); + EXPECT_EQ(exp2, res); +} + +TEST(math_interp, BilinearFloatSamples) +{ + float4 res; + float4 exp1 = {135.9375f, 133.28125f, 131.5625f, 129.84375f}; + res = interpolate_bilinear_fl(image_fl[0][0], image_width, image_height, 1.25f, 0.625f); + EXPECT_V4_NEAR(exp1, res, float_tolerance); + float4 exp2 = {219.36f, 191.2f, 166.64f, 142.08f}; + res = interpolate_bilinear_fl(image_fl[0][0], image_width, image_height, 1.4f, 0.1f); + EXPECT_V4_NEAR(exp2, res, float_tolerance); } TEST(math_interp, BilinearCharPartiallyOutsideImage) { - unsigned char res[4]; - unsigned char exp1[4] = {1, 1, 2, 2}; - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, -0.5f, 2.0f); - EXPECT_EQ_ARRAY(exp1, res, 4); - unsigned char exp2[4] = {9, 11, 15, 22}; - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 1.25f, 2.9f); - EXPECT_EQ_ARRAY(exp2, res, 4); - unsigned char exp3[4] = {173, 115, 65, 14}; - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 2.2f, -0.1f); - EXPECT_EQ_ARRAY(exp3, res, 4); + uchar4 res; + uchar4 exp1 = {1, 1, 2, 2}; + res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, -0.5f, 2.0f); + EXPECT_EQ(exp1, res); + uchar4 exp2 = {9, 11, 15, 22}; + res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 1.25f, 2.9f); + EXPECT_EQ(exp2, res); + uchar4 exp3 = {173, 115, 65, 14}; + res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 2.2f, -0.1f); + EXPECT_EQ(exp3, res); +} + +TEST(math_interp, BilinearCharPartiallyOutsideImageWrap) +{ + uchar4 res; + uchar4 exp1 = {65, 66, 67, 68}; + res = interpolate_bilinear_wrap_byte(image_char[0][0], image_width, image_height, -0.5f, 2.0f); + EXPECT_EQ(exp1, res); + uchar4 exp2 = {218, 203, 190, 182}; + res = interpolate_bilinear_wrap_byte(image_char[0][0], image_width, image_height, 1.25f, 2.9f); + EXPECT_EQ(exp2, res); + uchar4 exp3 = {229, 171, 114, 64}; + res = interpolate_bilinear_wrap_byte(image_char[0][0], image_width, image_height, 2.2f, -0.1f); + EXPECT_EQ(exp3, res); +} + +TEST(math_interp, BilinearFloatPartiallyOutsideImage) +{ + float4 res; + float4 exp1 = {0.5f, 1, 1.5f, 2}; + res = interpolate_bilinear_fl(image_fl[0][0], image_width, image_height, -0.5f, 2.0f); + EXPECT_V4_NEAR(exp1, res, float_tolerance); + float4 exp2 = {8.675f, 11.325f, 14.725f, 22.1f}; + res = interpolate_bilinear_fl(image_fl[0][0], image_width, image_height, 1.25f, 2.9f); + EXPECT_V4_NEAR(exp2, res, float_tolerance); + float4 exp3 = {172.8f, 115.2f, 64.8f, 14.4f}; + res = interpolate_bilinear_fl(image_fl[0][0], image_width, image_height, 2.2f, -0.1f); + EXPECT_V4_NEAR(exp3, res, float_tolerance); +} + +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); + 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); + 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); + 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); } TEST(math_interp, BilinearCharFullyOutsideImage) { - unsigned char res[4]; - unsigned char exp[4] = {0, 0, 0, 0}; + uchar4 res; + uchar4 exp = {0, 0, 0, 0}; /* Out of range on U */ - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, -1.5f, 0); - EXPECT_EQ_ARRAY(exp, res, 4); - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, -1.1f, 0); - EXPECT_EQ_ARRAY(exp, res, 4); - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 3, 0); - EXPECT_EQ_ARRAY(exp, res, 4); - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 5, 0); - EXPECT_EQ_ARRAY(exp, res, 4); + res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, -1.5f, 0); + EXPECT_EQ(exp, res); + res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, -1.1f, 0); + EXPECT_EQ(exp, res); + res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 3, 0); + EXPECT_EQ(exp, res); + res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 5, 0); + EXPECT_EQ(exp, res); /* Out of range on V */ - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 0, -3.2f); - EXPECT_EQ_ARRAY(exp, res, 4); - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 0, -1.5f); - EXPECT_EQ_ARRAY(exp, res, 4); - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 0, 3.1f); - EXPECT_EQ_ARRAY(exp, res, 4); - BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 0, 500.0f); - EXPECT_EQ_ARRAY(exp, res, 4); + res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 0, -3.2f); + EXPECT_EQ(exp, res); + res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 0, -1.5f); + EXPECT_EQ(exp, res); + res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 0, 3.1f); + EXPECT_EQ(exp, res); + res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 0, 500.0f); + EXPECT_EQ(exp, res); } -TEST(math_interp, BicubicCharExactSamples) +TEST(math_interp, CubicBSplineCharExactSamples) { - ColorTheme4b res; - ColorTheme4b exp1 = {69, 90, 116, 172}; - BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 1.0f, 2.0f); + uchar4 res; + uchar4 exp1 = {69, 90, 116, 172}; + res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 1.0f, 2.0f); EXPECT_EQ(exp1, res); - ColorTheme4b exp2 = {218, 163, 115, 66}; - BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 2.0f, 0.0f); + uchar4 exp2 = {218, 163, 115, 66}; + res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 2.0f, 0.0f); EXPECT_EQ(exp2, res); } -TEST(math_interp, BicubicCharSamples) +TEST(math_interp, CubicBSplineCharSamples) { - ColorTheme4b res; - ColorTheme4b exp1 = {142, 136, 131, 128}; - BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 1.25f, 0.625f); + uchar4 res; + uchar4 exp1 = {142, 136, 131, 128}; + res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 1.25f, 0.625f); EXPECT_EQ(exp1, res); - ColorTheme4b exp2 = {202, 177, 154, 132}; - BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 1.4f, 0.1f); + uchar4 exp2 = {202, 177, 154, 132}; + res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 1.4f, 0.1f); EXPECT_EQ(exp2, res); } -TEST(math_interp, BicubicCharPartiallyOutsideImage) +TEST(math_interp, CubicBSplineFloatSamples) { - ColorTheme4b res; - ColorTheme4b exp1 = {2, 4, 6, 8}; - BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, -0.5f, 2.0f); + float4 res; + float4 exp1 = {142.14418f, 136.255798f, 130.87924f, 127.85243f}; + res = interpolate_cubic_bspline_fl(image_fl[0][0], image_width, image_height, 1.25f, 0.625f); + EXPECT_V4_NEAR(exp1, res, float_tolerance); + float4 exp2 = {202.36082f, 177.13397f, 154.21078f, 132.30153f}; + res = interpolate_cubic_bspline_fl(image_fl[0][0], image_width, image_height, 1.4f, 0.1f); + EXPECT_V4_NEAR(exp2, res, float_tolerance); +} + +TEST(math_interp, CubicBSplineCharPartiallyOutsideImage) +{ + uchar4 res; + uchar4 exp1 = {2, 4, 6, 8}; + res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, -0.5f, 2.0f); EXPECT_EQ(exp1, res); - ColorTheme4b exp2 = {85, 107, 135, 195}; - BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 1.25f, 2.9f); + uchar4 exp2 = {85, 107, 135, 195}; + res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 1.25f, 2.9f); EXPECT_EQ(exp2, res); - ColorTheme4b exp3 = {225, 161, 105, 49}; - BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 2.2f, -0.1f); + uchar4 exp3 = {225, 161, 105, 49}; + res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 2.2f, -0.1f); EXPECT_EQ(exp3, res); } -TEST(math_interp, BicubicCharFullyOutsideImage) +TEST(math_interp, CubicBSplineFloatPartiallyOutsideImage) { - ColorTheme4b res; - ColorTheme4b exp = {0, 0, 0, 0}; + float4 res; + 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); + 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); + 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); +} + +TEST(math_interp, CubicBSplineCharFullyOutsideImage) +{ + uchar4 res; + uchar4 exp = {0, 0, 0, 0}; /* Out of range on U */ - BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, -1.5f, 0); + res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, -1.5f, 0); EXPECT_EQ(exp, res); - BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, -1.1f, 0); + res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, -1.1f, 0); EXPECT_EQ(exp, res); - BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 3, 0); + res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 3, 0); EXPECT_EQ(exp, res); - BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 5, 0); + res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 5, 0); EXPECT_EQ(exp, res); /* Out of range on V */ - BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 0, -3.2f); + res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 0, -3.2f); EXPECT_EQ(exp, res); - BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 0, -1.5f); + res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 0, -1.5f); EXPECT_EQ(exp, res); - BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 0, 3.1f); + res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 0, 3.1f); EXPECT_EQ(exp, res); - BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 0, 500.0f); + res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 0, 500.0f); EXPECT_EQ(exp, res); } diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.h b/source/blender/compositor/intern/COM_MemoryBuffer.h index b31a9033495..e5902e84821 100644 --- a/source/blender/compositor/intern/COM_MemoryBuffer.h +++ b/source/blender/compositor/intern/COM_MemoryBuffer.h @@ -249,11 +249,11 @@ class MemoryBuffer { single_y = rel_y - last_y; } - BLI_bilinear_interpolation_fl(buffer_, out, 1, 1, num_channels_, single_x, single_y); + math::interpolate_bilinear_fl(buffer_, out, 1, 1, num_channels_, single_x, single_y); return; } - BLI_bilinear_interpolation_fl(buffer_, + math::interpolate_bilinear_fl(buffer_, out, get_width(), get_height(), @@ -451,10 +451,7 @@ class MemoryBuffer { } break; case MemoryBufferExtend::Repeat: - x = fmodf(x, w); - if (x < 0.0f) { - x += w; - } + x = floored_fmod(x, w); break; } @@ -470,10 +467,7 @@ class MemoryBuffer { } break; case MemoryBufferExtend::Repeat: - y = fmodf(y, h); - if (y < 0.0f) { - y += h; - } + y = floored_fmod(y, h); break; } @@ -547,7 +541,7 @@ class MemoryBuffer { memcpy(result, buffer_, sizeof(float) * num_channels_); } else { - BLI_bilinear_interpolation_wrap_fl(buffer_, + math::interpolate_bilinear_wrap_fl(buffer_, result, get_width(), get_height(), diff --git a/source/blender/compositor/operations/COM_ImageOperation.cc b/source/blender/compositor/operations/COM_ImageOperation.cc index 28617c897ac..331cd98b58f 100644 --- a/source/blender/compositor/operations/COM_ImageOperation.cc +++ b/source/blender/compositor/operations/COM_ImageOperation.cc @@ -7,8 +7,7 @@ #include "BKE_scene.h" #include "IMB_colormanagement.hh" -#include "IMB_imbuf.hh" -#include "IMB_imbuf_types.hh" +#include "IMB_interp.hh" namespace blender::compositor { @@ -102,27 +101,27 @@ static void sample_image_at_location(ImBuf *ibuf, if (ibuf->float_buffer.data) { switch (sampler) { case PixelSampler::Nearest: - nearest_interpolation_color(ibuf, nullptr, color, x, y); + imbuf::interpolate_nearest_fl(ibuf, color, x, y); break; case PixelSampler::Bilinear: - bilinear_interpolation_color(ibuf, nullptr, color, x, y); + imbuf::interpolate_bilinear_fl(ibuf, color, x, y); break; case PixelSampler::Bicubic: - bicubic_interpolation_color(ibuf, nullptr, color, x, y); + imbuf::interpolate_cubic_bspline_fl(ibuf, color, x, y); break; } } else { - uchar byte_color[4]; + uchar4 byte_color; switch (sampler) { case PixelSampler::Nearest: - nearest_interpolation_color(ibuf, byte_color, nullptr, x, y); + byte_color = imbuf::interpolate_nearest_byte(ibuf, x, y); break; case PixelSampler::Bilinear: - bilinear_interpolation_color(ibuf, byte_color, nullptr, x, y); + byte_color = imbuf::interpolate_bilinear_byte(ibuf, x, y); break; case PixelSampler::Bicubic: - bicubic_interpolation_color(ibuf, byte_color, nullptr, x, y); + byte_color = imbuf::interpolate_cubic_bspline_byte(ibuf, x, y); break; } rgba_uchar_to_float(color, byte_color); diff --git a/source/blender/compositor/operations/COM_MovieClipOperation.cc b/source/blender/compositor/operations/COM_MovieClipOperation.cc index 792f97e3c30..569450fe4dd 100644 --- a/source/blender/compositor/operations/COM_MovieClipOperation.cc +++ b/source/blender/compositor/operations/COM_MovieClipOperation.cc @@ -8,6 +8,7 @@ #include "BKE_movieclip.h" #include "IMB_imbuf.hh" +#include "IMB_interp.hh" namespace blender::compositor { @@ -81,13 +82,13 @@ void MovieClipBaseOperation::execute_pixel_sampled(float output[4], else { switch (sampler) { case PixelSampler::Nearest: - nearest_interpolation_color(ibuf, nullptr, output, x, y); + imbuf::interpolate_nearest_fl(ibuf, output, x, y); break; case PixelSampler::Bilinear: - bilinear_interpolation_color(ibuf, nullptr, output, x, y); + imbuf::interpolate_bilinear_fl(ibuf, output, x, y); break; case PixelSampler::Bicubic: - bicubic_interpolation_color(ibuf, nullptr, output, x, y); + imbuf::interpolate_cubic_bspline_fl(ibuf, output, x, y); break; } } diff --git a/source/blender/compositor/operations/COM_MultilayerImageOperation.cc b/source/blender/compositor/operations/COM_MultilayerImageOperation.cc index 2ba7fd3626d..9a91c44f227 100644 --- a/source/blender/compositor/operations/COM_MultilayerImageOperation.cc +++ b/source/blender/compositor/operations/COM_MultilayerImageOperation.cc @@ -6,7 +6,7 @@ #include "BLI_string.h" -#include "IMB_imbuf.hh" +#include "IMB_interp.hh" namespace blender::compositor { @@ -88,13 +88,13 @@ void MultilayerColorOperation::execute_pixel_sampled(float output[4], if (number_of_channels_ == 4) { switch (sampler) { case PixelSampler::Nearest: - nearest_interpolation_color(buffer_, nullptr, output, x, y); + imbuf::interpolate_nearest_fl(buffer_, output, x, y); break; case PixelSampler::Bilinear: - bilinear_interpolation_color(buffer_, nullptr, output, x, y); + imbuf::interpolate_bilinear_fl(buffer_, output, x, y); break; case PixelSampler::Bicubic: - bicubic_interpolation_color(buffer_, nullptr, output, x, y); + imbuf::interpolate_cubic_bspline_fl(buffer_, output, x, y); break; } } diff --git a/source/blender/compositor/operations/COM_RenderLayersProg.cc b/source/blender/compositor/operations/COM_RenderLayersProg.cc index f8027faf1d5..32458499276 100644 --- a/source/blender/compositor/operations/COM_RenderLayersProg.cc +++ b/source/blender/compositor/operations/COM_RenderLayersProg.cc @@ -4,6 +4,7 @@ #include "COM_RenderLayersProg.h" +#include "BLI_math_interp.hh" #include "BLI_string.h" #include "BKE_image.h" @@ -55,7 +56,6 @@ void RenderLayersProg::init_execution() void RenderLayersProg::do_interpolation(float output[4], float x, float y, PixelSampler sampler) { - uint offset; int width = this->get_width(), height = this->get_height(); int ix = x, iy = y; @@ -73,27 +73,14 @@ void RenderLayersProg::do_interpolation(float output[4], float x, float y, Pixel } switch (sampler) { - case PixelSampler::Nearest: { - offset = (iy * width + ix) * elementsize_; - - if (elementsize_ == 1) { - output[0] = input_buffer_[offset]; - } - else if (elementsize_ == 3) { - copy_v3_v3(output, &input_buffer_[offset]); - } - else { - copy_v4_v4(output, &input_buffer_[offset]); - } + case PixelSampler::Nearest: + math::interpolate_nearest_fl(input_buffer_, output, width, height, elementsize_, x, y); break; - } - case PixelSampler::Bilinear: - BLI_bilinear_interpolation_fl(input_buffer_, output, width, height, elementsize_, x, y); + math::interpolate_bilinear_fl(input_buffer_, output, width, height, elementsize_, x, y); break; - case PixelSampler::Bicubic: - BLI_bicubic_interpolation_fl(input_buffer_, output, width, height, elementsize_, x, y); + math::interpolate_cubic_bspline_fl(input_buffer_, output, width, height, elementsize_, x, y); break; } } diff --git a/source/blender/draw/engines/image/image_drawing_mode.hh b/source/blender/draw/engines/image/image_drawing_mode.hh index 833a2885430..91663d05c9a 100644 --- a/source/blender/draw/engines/image/image_drawing_mode.hh +++ b/source/blender/draw/engines/image/image_drawing_mode.hh @@ -11,6 +11,7 @@ #include "BKE_image_partial_update.hh" #include "IMB_imbuf_types.hh" +#include "IMB_interp.hh" #include "BLI_math_matrix_types.hh" #include "BLI_math_vector_types.hh" @@ -503,11 +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; - nearest_interpolation_color(tile_buffer, - nullptr, - &extracted_buffer.float_buffer.data[offset * 4], - u * tile_buffer->x, - v * tile_buffer->y); + imbuf::interpolate_nearest_fl(tile_buffer, + &extracted_buffer.float_buffer.data[offset * 4], + u * tile_buffer->x, + v * tile_buffer->y); offset++; } } diff --git a/source/blender/editors/animation/keyframes_general.cc b/source/blender/editors/animation/keyframes_general.cc index a8cca182a55..afebe744b3e 100644 --- a/source/blender/editors/animation/keyframes_general.cc +++ b/source/blender/editors/animation/keyframes_general.cc @@ -936,7 +936,7 @@ void time_offset_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float /* This simulates the fcu curve moving in time. */ const float time = fcu->bezt[segment->start_index + i].vec[1][0] + frame_offset; /* Need to normalize time to first_key to specify that as the wrapping point. */ - const float wrapped_time = mod_f_positive(time - first_key_x, fcu_x_range) + first_key_x; + const float wrapped_time = floored_fmod(time - first_key_x, fcu_x_range) + first_key_x; const float delta_y = fcu_y_range * floorf((time - first_key_x) / fcu_x_range); const float key_y_value = evaluate_fcurve(fcu, wrapped_time) + delta_y; diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.cc b/source/blender/editors/sculpt_paint/paint_image_proj.cc index bf1cceb683c..a9daae21a4a 100644 --- a/source/blender/editors/sculpt_paint/paint_image_proj.cc +++ b/source/blender/editors/sculpt_paint/paint_image_proj.cc @@ -27,6 +27,7 @@ #include "BLI_math_bits.h" #include "BLI_math_color_blend.h" #include "BLI_math_geom.h" +#include "BLI_math_vector.hh" #include "BLI_memarena.h" #include "BLI_task.h" #include "BLI_threads.h" @@ -37,7 +38,7 @@ #include "BLT_translation.h" #include "IMB_imbuf.hh" -#include "IMB_imbuf_types.hh" +#include "IMB_interp.hh" #include "DNA_brush_types.h" #include "DNA_customdata_types.h" @@ -715,35 +716,17 @@ static int project_paint_PickFace(const ProjPaintState *ps, const float pt[2], f return best_tri_index; } -/* Converts a uv coord into a pixel location wrapping if the uv is outside 0-1 range */ -static void uvco_to_wrapped_pxco(const float uv[2], int ibuf_x, int ibuf_y, float *x, float *y) -{ - /* use */ - *x = fmodf(uv[0], 1.0f); - *y = fmodf(uv[1], 1.0f); - - if (*x < 0.0f) { - *x += 1.0f; - } - if (*y < 0.0f) { - *y += 1.0f; - } - - *x = *x * ibuf_x - 0.5f; - *y = *y * ibuf_y - 0.5f; -} - /* Set the top-most face color that the screen space coord 'pt' touches * (or return 0 if none touch) */ static bool project_paint_PickColor( const ProjPaintState *ps, const float pt[2], float *rgba_fp, uchar *rgba, const bool interp) { + using namespace blender; const float *tri_uv[3]; float w[3], uv[2]; int tri_index; Image *ima; ImBuf *ibuf; - int xi, yi; tri_index = project_paint_PickFace(ps, pt, w); @@ -770,58 +753,32 @@ static bool project_paint_PickColor( return false; } + float x = uv[0] * ibuf->x; + float y = uv[1] * ibuf->y; if (interp) { - float x, y; - uvco_to_wrapped_pxco(uv, ibuf->x, ibuf->y, &x, &y); + x -= 0.5f; + y -= 0.5f; + } - if (ibuf->float_buffer.data) { - if (rgba_fp) { - bilinear_interpolation_color_wrap(ibuf, nullptr, rgba_fp, x, y); - } - else { - float rgba_tmp_f[4]; - bilinear_interpolation_color_wrap(ibuf, nullptr, rgba_tmp_f, x, y); - premul_float_to_straight_uchar(rgba, rgba_tmp_f); - } + if (ibuf->float_buffer.data) { + float4 col = interp ? imbuf::interpolate_bilinear_wrap_fl(ibuf, x, y) : + imbuf::interpolate_nearest_wrap_fl(ibuf, x, y); + col = math::clamp(col, 0.0f, 1.0f); + if (rgba_fp) { + memcpy(rgba_fp, &col, sizeof(col)); } else { - if (rgba) { - bilinear_interpolation_color_wrap(ibuf, rgba, nullptr, x, y); - } - else { - uchar rgba_tmp[4]; - bilinear_interpolation_color_wrap(ibuf, rgba_tmp, nullptr, x, y); - straight_uchar_to_premul_float(rgba_fp, rgba_tmp); - } + premul_float_to_straight_uchar(rgba, col); } } else { - // xi = int((uv[0]*ibuf->x) + 0.5f); - // yi = int((uv[1]*ibuf->y) + 0.5f); - // if (xi < 0 || xi >= ibuf->x || yi < 0 || yi >= ibuf->y) return false; - - /* wrap */ - xi = mod_i(int(uv[0] * ibuf->x), ibuf->x); - yi = mod_i(int(uv[1] * ibuf->y), ibuf->y); - + uchar4 col = interp ? imbuf::interpolate_bilinear_wrap_byte(ibuf, x, y) : + imbuf::interpolate_nearest_wrap_byte(ibuf, x, y); if (rgba) { - if (ibuf->float_buffer.data) { - const float *rgba_tmp_fp = ibuf->float_buffer.data + (xi + yi * ibuf->x * 4); - premul_float_to_straight_uchar(rgba, rgba_tmp_fp); - } - else { - *((uint *)rgba) = *(uint *)(((char *)ibuf->byte_buffer.data) + ((xi + yi * ibuf->x) * 4)); - } + memcpy(rgba, &col, sizeof(col)); } - - if (rgba_fp) { - if (ibuf->float_buffer.data) { - copy_v4_v4(rgba_fp, (ibuf->float_buffer.data + ((xi + yi * ibuf->x) * 4))); - } - else { - uchar *tmp_ch = ibuf->byte_buffer.data + ((xi + yi * ibuf->x) * 4); - straight_uchar_to_premul_float(rgba_fp, tmp_ch); - } + else { + straight_uchar_to_premul_float(rgba_fp, col); } } BKE_image_release_ibuf(ima, ibuf, nullptr); @@ -1657,18 +1614,22 @@ static float screen_px_line_point_factor_v2_persp(const ProjPaintState *ps, static void project_face_pixel( const float *tri_uv[3], ImBuf *ibuf_other, const float w[3], uchar rgba_ub[4], float rgba_f[4]) { - float uv_other[2], x, y; + using namespace blender; + float uv_other[2]; interp_v2_v2v2v2(uv_other, UNPACK3(tri_uv), w); - /* use */ - uvco_to_wrapped_pxco(uv_other, ibuf_other->x, ibuf_other->y, &x, &y); + float x = uv_other[0] * ibuf_other->x - 0.5f; + float y = uv_other[1] * ibuf_other->y - 0.5f; - if (ibuf_other->float_buffer.data) { /* from float to float */ - bilinear_interpolation_color_wrap(ibuf_other, nullptr, rgba_f, x, y); + if (ibuf_other->float_buffer.data) { + float4 col = imbuf::interpolate_bilinear_wrap_fl(ibuf_other, x, y); + col = math::clamp(col, 0.0f, 1.0f); + memcpy(rgba_f, &col, sizeof(col)); } - else { /* from char to float */ - bilinear_interpolation_color_wrap(ibuf_other, rgba_ub, nullptr, x, y); + else { + uchar4 col = imbuf::interpolate_bilinear_wrap_byte(ibuf_other, x, y); + memcpy(rgba_ub, &col, sizeof(col)); } } @@ -5388,11 +5349,10 @@ static void do_projectpaint_thread(TaskPool *__restrict /*pool*/, void *ph_v) if (is_floatbuf) { BLI_assert(ps->reproject_ibuf->float_buffer.data != nullptr); - bicubic_interpolation_color(ps->reproject_ibuf, - nullptr, - projPixel->newColor.f, - projPixel->projCoSS[0], - projPixel->projCoSS[1]); + blender::imbuf::interpolate_cubic_bspline_fl(ps->reproject_ibuf, + projPixel->newColor.f, + projPixel->projCoSS[0], + projPixel->projCoSS[1]); if (projPixel->newColor.f[3]) { float mask = float(projPixel->mask) * (1.0f / 65535.0f); @@ -5404,12 +5364,10 @@ static void do_projectpaint_thread(TaskPool *__restrict /*pool*/, void *ph_v) } else { BLI_assert(ps->reproject_ibuf->byte_buffer.data != nullptr); - - bicubic_interpolation_color(ps->reproject_ibuf, - projPixel->newColor.ch, - nullptr, - projPixel->projCoSS[0], - projPixel->projCoSS[1]); + blender::imbuf::interpolate_cubic_bspline_byte(ps->reproject_ibuf, + projPixel->newColor.ch, + projPixel->projCoSS[0], + projPixel->projCoSS[1]); if (projPixel->newColor.ch[3]) { float mask = float(projPixel->mask) * (1.0f / 65535.0f); projPixel->newColor.ch[3] *= mask; diff --git a/source/blender/editors/sculpt_paint/paint_ops.cc b/source/blender/editors/sculpt_paint/paint_ops.cc index a834e4134e6..755649d4881 100644 --- a/source/blender/editors/sculpt_paint/paint_ops.cc +++ b/source/blender/editors/sculpt_paint/paint_ops.cc @@ -19,8 +19,7 @@ #include "BLT_translation.h" -#include "IMB_imbuf.hh" -#include "IMB_imbuf_types.hh" +#include "IMB_interp.hh" #include "DNA_brush_types.h" #include "DNA_customdata_types.h" diff --git a/source/blender/editors/sculpt_paint/paint_utils.cc b/source/blender/editors/sculpt_paint/paint_utils.cc index ed7b34bc3f0..e7980ea4a14 100644 --- a/source/blender/editors/sculpt_paint/paint_utils.cc +++ b/source/blender/editors/sculpt_paint/paint_utils.cc @@ -19,6 +19,7 @@ #include "BLI_listbase.h" #include "BLI_math_color.h" #include "BLI_math_matrix.h" +#include "BLI_math_vector.hh" #include "BLI_rect.h" #include "BLI_utildefines.h" @@ -51,6 +52,7 @@ #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" +#include "IMB_interp.hh" #include "RE_texture.h" @@ -372,6 +374,7 @@ static int imapaint_pick_face(ViewContext *vc, const int mval[2], uint *r_index, void paint_sample_color( bContext *C, ARegion *region, int x, int y, bool texpaint_proj, bool use_palette) { + using namespace blender; Scene *scene = CTX_data_scene(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Paint *paint = BKE_paint_get_active_from_context(C); @@ -445,46 +448,31 @@ void paint_sample_color( } if (image) { - float uv[2]; - float u, v; /* XXX get appropriate ImageUser instead */ ImageUser iuser; BKE_imageuser_default(&iuser); iuser.framenr = image->lastframe; + float uv[2]; imapaint_pick_uv(me_eval, scene, ob_eval, faceindex, mval, uv); if (image->source == IMA_SRC_TILED) { float new_uv[2]; iuser.tile = BKE_image_get_tile_from_pos(image, uv, new_uv, nullptr); - u = new_uv[0]; - v = new_uv[1]; - } - else { - u = fmodf(uv[0], 1.0f); - v = fmodf(uv[1], 1.0f); - - if (u < 0.0f) { - u += 1.0f; - } - if (v < 0.0f) { - v += 1.0f; - } + uv[0] = new_uv[0]; + uv[1] = new_uv[1]; } ImBuf *ibuf = BKE_image_acquire_ibuf(image, &iuser, nullptr); if (ibuf && (ibuf->byte_buffer.data || ibuf->float_buffer.data)) { - u = u * ibuf->x; - v = v * ibuf->y; + float u = uv[0] * ibuf->x; + float v = uv[1] * ibuf->y; if (ibuf->float_buffer.data) { - float rgba_f[4]; - if (interp == SHD_INTERP_CLOSEST) { - nearest_interpolation_color_wrap(ibuf, nullptr, rgba_f, u, v); - } - else { - bilinear_interpolation_color_wrap(ibuf, nullptr, rgba_f, u, v); - } + float4 rgba_f = interp == SHD_INTERP_CLOSEST ? + imbuf::interpolate_nearest_wrap_fl(ibuf, u, v) : + imbuf::interpolate_bilinear_wrap_fl(ibuf, u, v); + rgba_f = math::clamp(rgba_f, 0.0f, 1.0f); straight_to_premul_v4(rgba_f); if (use_palette) { linearrgb_to_srgb_v3_v3(color->rgb, rgba_f); @@ -495,13 +483,9 @@ void paint_sample_color( } } else { - uchar rgba[4]; - if (interp == SHD_INTERP_CLOSEST) { - nearest_interpolation_color_wrap(ibuf, rgba, nullptr, u, v); - } - else { - bilinear_interpolation_color_wrap(ibuf, rgba, nullptr, u, v); - } + uchar4 rgba = interp == SHD_INTERP_CLOSEST ? + imbuf::interpolate_nearest_wrap_byte(ibuf, u, v) : + imbuf::interpolate_bilinear_wrap_byte(ibuf, u, v); if (use_palette) { rgb_uchar_to_float(color->rgb, rgba); } diff --git a/source/blender/imbuf/CMakeLists.txt b/source/blender/imbuf/CMakeLists.txt index 82094cfcaf7..602fdae530b 100644 --- a/source/blender/imbuf/CMakeLists.txt +++ b/source/blender/imbuf/CMakeLists.txt @@ -36,6 +36,7 @@ set(SRC intern/format_tiff.cc intern/imageprocess.cc intern/indexer.cc + intern/interp.cc intern/iris.cc intern/jpeg.cc intern/metadata.cc @@ -58,6 +59,7 @@ set(SRC IMB_imbuf.hh IMB_imbuf_enums.h IMB_imbuf_types.hh + IMB_interp.hh IMB_metadata.hh IMB_moviecache.hh IMB_openexr.hh diff --git a/source/blender/imbuf/IMB_imbuf.hh b/source/blender/imbuf/IMB_imbuf.hh index ea5bb91326b..34f8c39796f 100644 --- a/source/blender/imbuf/IMB_imbuf.hh +++ b/source/blender/imbuf/IMB_imbuf.hh @@ -512,47 +512,9 @@ void IMB_buffer_byte_from_byte(unsigned char *rect_to, */ void IMB_convert_rgba_to_abgr(ImBuf *ibuf); -void bicubic_interpolation(const ImBuf *in, ImBuf *out, float u, float v, int xout, int yout); -void nearest_interpolation(const ImBuf *in, ImBuf *out, float u, float v, int xout, int yout); -void bilinear_interpolation(const ImBuf *in, ImBuf *out, float u, float v, int xout, int yout); - -typedef void (*InterpolationColorFunction)( - const ImBuf *in, unsigned char outI[4], float outF[4], float u, float v); -void bicubic_interpolation_color( - const ImBuf *in, unsigned char outI[4], float outF[4], float u, float v); - -/* Functions assumes out to be zeroed, only does RGBA. */ - -void nearest_interpolation_color_char( - const ImBuf *in, unsigned char outI[4], float outF[4], float u, float v); -void nearest_interpolation_color_fl( - const ImBuf *in, unsigned char outI[4], float outF[4], float u, float v); -void nearest_interpolation_color( - const ImBuf *in, unsigned char outI[4], float outF[4], float u, float v); -void nearest_interpolation_color_wrap( - const ImBuf *in, unsigned char outI[4], float outF[4], float u, float v); -void bilinear_interpolation_color( - const ImBuf *in, unsigned char outI[4], float outF[4], float u, float v); -void bilinear_interpolation_color_char(const ImBuf *in, unsigned char outI[4], float u, float v); -void bilinear_interpolation_color_fl(const ImBuf *in, float outF[4], float u, float v); -/** - * Note about wrapping, the u/v still needs to be within the image bounds, - * just the interpolation is wrapped. - * This the same as bilinear_interpolation_color except it wraps - * rather than using empty and emptyI. - */ -void bilinear_interpolation_color_wrap( - const ImBuf *in, unsigned char outI[4], float outF[4], float u, float v); - void IMB_alpha_under_color_float(float *rect_float, int x, int y, float backcol[3]); void IMB_alpha_under_color_byte(unsigned char *rect, int x, int y, const float backcol[3]); -/** - * Sample pixel of image using NEAREST method. - */ -void IMB_sampleImageAtLocation( - ImBuf *ibuf, float x, float y, bool make_linear_rgb, float color[4]); - ImBuf *IMB_loadifffile(int file, int flags, char colorspace[IM_MAX_SPACE], const char *descr); ImBuf *IMB_half_x(ImBuf *ibuf1); diff --git a/source/blender/imbuf/IMB_interp.hh b/source/blender/imbuf/IMB_interp.hh new file mode 100644 index 00000000000..781da8af08b --- /dev/null +++ b/source/blender/imbuf/IMB_interp.hh @@ -0,0 +1,100 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup imbuf + * + * Image buffer pixel sampling functions. + * Mostly convenience wrappers around lower level `BLI_math_interp.hh`. + */ + +#pragma once + +#include "BLI_math_interp.hh" +#include "IMB_imbuf_types.hh" + +#include + +namespace blender::imbuf { + +[[nodiscard]] inline uchar4 interpolate_nearest_byte(const ImBuf *in, float u, float v) +{ + return math::interpolate_nearest_byte(in->byte_buffer.data, in->x, in->y, u, v); +} +[[nodiscard]] inline float4 interpolate_nearest_fl(const ImBuf *in, float u, float v) +{ + return math::interpolate_nearest_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) +{ + math::interpolate_nearest_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) +{ + math::interpolate_nearest_fl(in->float_buffer.data, output, in->x, in->y, 4, u, v); +} + +[[nodiscard]] inline uchar4 interpolate_nearest_wrap_byte(const ImBuf *in, float u, float v) +{ + return math::interpolate_nearest_wrap_byte(in->byte_buffer.data, in->x, in->y, u, v); +} +[[nodiscard]] inline float4 interpolate_nearest_wrap_fl(const ImBuf *in, float u, float v) +{ + return math::interpolate_nearest_wrap_fl(in->float_buffer.data, in->x, in->y, u, v); +} + +[[nodiscard]] inline uchar4 interpolate_bilinear_byte(const ImBuf *in, float u, float v) +{ + return math::interpolate_bilinear_byte(in->byte_buffer.data, in->x, in->y, u, v); +} +[[nodiscard]] inline float4 interpolate_bilinear_fl(const ImBuf *in, float u, float v) +{ + return math::interpolate_bilinear_fl(in->float_buffer.data, in->x, in->y, u, v); +} +inline void interpolate_bilinear_byte(const ImBuf *in, uchar output[4], float u, float v) +{ + uchar4 col = math::interpolate_bilinear_byte(in->byte_buffer.data, in->x, in->y, u, v); + memcpy(output, &col, sizeof(col)); +} +inline void interpolate_bilinear_fl(const ImBuf *in, float output[4], float u, float v) +{ + float4 col = math::interpolate_bilinear_fl(in->float_buffer.data, in->x, in->y, u, v); + memcpy(output, &col, sizeof(col)); +} + +[[nodiscard]] inline uchar4 interpolate_bilinear_wrap_byte(const ImBuf *in, float u, float v) +{ + return math::interpolate_bilinear_wrap_byte(in->byte_buffer.data, in->x, in->y, u, v); +} +[[nodiscard]] inline float4 interpolate_bilinear_wrap_fl(const ImBuf *in, float u, float v) +{ + return math::interpolate_bilinear_wrap_fl(in->float_buffer.data, in->x, in->y, u, v); +} + +[[nodiscard]] inline uchar4 interpolate_cubic_bspline_byte(const ImBuf *in, float u, float v) +{ + return math::interpolate_cubic_bspline_byte(in->byte_buffer.data, in->x, in->y, u, v); +} +[[nodiscard]] inline float4 interpolate_cubic_bspline_fl(const ImBuf *in, float u, float v) +{ + return math::interpolate_cubic_bspline_fl(in->float_buffer.data, in->x, in->y, u, v); +} +inline void interpolate_cubic_bspline_byte(const ImBuf *in, uchar output[4], float u, float v) +{ + uchar4 col = math::interpolate_cubic_bspline_byte(in->byte_buffer.data, in->x, in->y, u, v); + memcpy(output, &col, sizeof(col)); +} +inline void interpolate_cubic_bspline_fl(const ImBuf *in, float output[4], float u, float v) +{ + float4 col = math::interpolate_cubic_bspline_fl(in->float_buffer.data, in->x, in->y, u, v); + memcpy(output, &col, sizeof(col)); +} + +} // namespace blender::imbuf + +/** + * Sample pixel of image using NEAREST method. + */ +void IMB_sampleImageAtLocation( + ImBuf *ibuf, float x, float y, bool make_linear_rgb, float color[4]); diff --git a/source/blender/imbuf/intern/imageprocess.cc b/source/blender/imbuf/intern/imageprocess.cc index 3eda62a7e6a..5c0e19dcfb5 100644 --- a/source/blender/imbuf/intern/imageprocess.cc +++ b/source/blender/imbuf/intern/imageprocess.cc @@ -1,14 +1,10 @@ /* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved. + * SPDX-FileCopyrightText: 2024 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup imbuf - * - * This file was moved here from the `src/` directory. - * It is meant to deal with endianness. It resided in a general blending lib. - * The other functions were only used during rendering. This single function remained. - * It should probably move to `imbuf/intern/util.cc`, but we'll keep it here for the time being. */ #include @@ -16,7 +12,6 @@ #include "MEM_guardedalloc.h" -#include "BLI_math_interp.hh" #include "BLI_task.h" #include "BLI_utildefines.h" @@ -59,286 +54,6 @@ void IMB_convert_rgba_to_abgr(ImBuf *ibuf) } } -static void pixel_from_buffer(const ImBuf *ibuf, uchar **outI, float **outF, int x, int y) - -{ - size_t offset = size_t(ibuf->x) * y * 4 + 4 * x; - - if (ibuf->byte_buffer.data) { - *outI = ibuf->byte_buffer.data + offset; - } - - if (ibuf->float_buffer.data) { - *outF = ibuf->float_buffer.data + offset; - } -} - -/* -------------------------------------------------------------------- */ -/** \name Bi-Cubic Interpolation - * \{ */ - -void bicubic_interpolation_color(const ImBuf *in, uchar outI[4], float outF[4], float u, float v) -{ - if (outF) { - BLI_bicubic_interpolation_fl(in->float_buffer.data, outF, in->x, in->y, 4, u, v); - } - else { - BLI_bicubic_interpolation_char(in->byte_buffer.data, outI, in->x, in->y, u, v); - } -} - -void bicubic_interpolation(const ImBuf *in, ImBuf *out, float u, float v, int xout, int yout) -{ - uchar *outI = nullptr; - float *outF = nullptr; - - if (in == nullptr || (in->byte_buffer.data == nullptr && in->float_buffer.data == nullptr)) { - return; - } - - /* GCC warns these could be uninitialized, but its ok. */ - pixel_from_buffer(out, &outI, &outF, xout, yout); - - bicubic_interpolation_color(in, outI, outF, u, v); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Bi-Linear Interpolation - * \{ */ - -void bilinear_interpolation_color_fl(const ImBuf *in, float outF[4], float u, float v) -{ - BLI_assert(outF); - BLI_assert(in->float_buffer.data); - BLI_bilinear_interpolation_fl(in->float_buffer.data, outF, in->x, in->y, 4, u, v); -} - -void bilinear_interpolation_color_char(const ImBuf *in, uchar outI[4], float u, float v) -{ - BLI_assert(outI); - BLI_assert(in->byte_buffer.data); - BLI_bilinear_interpolation_char(in->byte_buffer.data, outI, in->x, in->y, u, v); -} - -void bilinear_interpolation_color(const ImBuf *in, uchar outI[4], float outF[4], float u, float v) -{ - if (outF) { - BLI_bilinear_interpolation_fl(in->float_buffer.data, outF, in->x, in->y, 4, u, v); - } - else { - BLI_bilinear_interpolation_char(in->byte_buffer.data, outI, in->x, in->y, u, v); - } -} - -/* Function assumes out to be zeroed, only does RGBA. */ -/* BILINEAR INTERPOLATION */ - -void bilinear_interpolation_color_wrap( - const ImBuf *in, uchar outI[4], float outF[4], float u, float v) -{ - float *row1, *row2, *row3, *row4, a, b; - uchar *row1I, *row2I, *row3I, *row4I; - float a_b, ma_b, a_mb, ma_mb; - int y1, y2, x1, x2; - - /* ImBuf in must have a valid rect or rect_float, assume this is already checked */ - - x1 = int(floor(u)); - x2 = int(ceil(u)); - y1 = int(floor(v)); - y2 = int(ceil(v)); - - /* sample area entirely outside image? */ - if (x2 < 0 || x1 > in->x - 1 || y2 < 0 || y1 > in->y - 1) { - return; - } - - /* Wrap interpolation pixels - main difference from #bilinear_interpolation_color. */ - if (x1 < 0) { - x1 = in->x + x1; - } - if (y1 < 0) { - y1 = in->y + y1; - } - - if (x2 >= in->x) { - x2 = x2 - in->x; - } - if (y2 >= in->y) { - y2 = y2 - in->y; - } - - a = u - floorf(u); - b = v - floorf(v); - a_b = a * b; - ma_b = (1.0f - a) * b; - a_mb = a * (1.0f - b); - ma_mb = (1.0f - a) * (1.0f - b); - - if (outF) { - float *in_rect_float = in->float_buffer.data; - /* sample including outside of edges of image */ - row1 = in_rect_float + size_t(in->x) * y1 * 4 + 4 * x1; - row2 = in_rect_float + size_t(in->x) * y2 * 4 + 4 * x1; - row3 = in_rect_float + size_t(in->x) * y1 * 4 + 4 * x2; - row4 = in_rect_float + size_t(in->x) * y2 * 4 + 4 * x2; - - outF[0] = ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0]; - outF[1] = ma_mb * row1[1] + a_mb * row3[1] + ma_b * row2[1] + a_b * row4[1]; - outF[2] = ma_mb * row1[2] + a_mb * row3[2] + ma_b * row2[2] + a_b * row4[2]; - outF[3] = ma_mb * row1[3] + a_mb * row3[3] + ma_b * row2[3] + a_b * row4[3]; - - /* clamp here or else we can easily get off-range */ - clamp_v4(outF, 0.0f, 1.0f); - } - if (outI) { - uchar *in_rect = in->byte_buffer.data; - /* sample including outside of edges of image */ - row1I = in_rect + size_t(in->x) * y1 * 4 + 4 * x1; - row2I = in_rect + size_t(in->x) * y2 * 4 + 4 * x1; - row3I = in_rect + size_t(in->x) * y1 * 4 + 4 * x2; - row4I = in_rect + size_t(in->x) * y2 * 4 + 4 * x2; - - /* Tested with white images and this should not wrap back to zero. */ - outI[0] = roundf(ma_mb * row1I[0] + a_mb * row3I[0] + ma_b * row2I[0] + a_b * row4I[0]); - outI[1] = roundf(ma_mb * row1I[1] + a_mb * row3I[1] + ma_b * row2I[1] + a_b * row4I[1]); - outI[2] = roundf(ma_mb * row1I[2] + a_mb * row3I[2] + ma_b * row2I[2] + a_b * row4I[2]); - outI[3] = roundf(ma_mb * row1I[3] + a_mb * row3I[3] + ma_b * row2I[3] + a_b * row4I[3]); - } -} - -void bilinear_interpolation(const ImBuf *in, ImBuf *out, float u, float v, int xout, int yout) -{ - uchar *outI = nullptr; - float *outF = nullptr; - - if (in == nullptr || (in->byte_buffer.data == nullptr && in->float_buffer.data == nullptr)) { - return; - } - - /* GCC warns these could be uninitialized, but its ok. */ - pixel_from_buffer(out, &outI, &outF, xout, yout); - - bilinear_interpolation_color(in, outI, outF, u, v); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Nearest Interpolation - * \{ */ - -void nearest_interpolation_color_char( - const ImBuf *in, uchar outI[4], float /*outF*/[4], float u, float v) -{ - BLI_assert(outI); - BLI_assert(in->byte_buffer.data); - /* ImBuf in must have a valid rect or rect_float, assume this is already checked */ - int x1 = int(u); - int y1 = int(v); - - /* sample area entirely outside image? */ - if (x1 < 0 || x1 >= in->x || y1 < 0 || y1 >= in->y) { - outI[0] = outI[1] = outI[2] = outI[3] = 0; - return; - } - - const size_t offset = (size_t(in->x) * y1 + x1) * 4; - const uchar *dataI = in->byte_buffer.data + offset; - outI[0] = dataI[0]; - outI[1] = dataI[1]; - outI[2] = dataI[2]; - outI[3] = dataI[3]; -} - -void nearest_interpolation_color_fl( - const ImBuf *in, uchar /*outI*/[4], float outF[4], float u, float v) -{ - BLI_assert(outF); - BLI_assert(in->float_buffer.data); - /* ImBuf in must have a valid rect or rect_float, assume this is already checked */ - int x1 = int(u); - int y1 = int(v); - - /* sample area entirely outside image? */ - if (x1 < 0 || x1 >= in->x || y1 < 0 || y1 >= in->y) { - zero_v4(outF); - return; - } - - const size_t offset = (size_t(in->x) * y1 + x1) * 4; - const float *dataF = in->float_buffer.data + offset; - copy_v4_v4(outF, dataF); -} - -void nearest_interpolation_color(const ImBuf *in, uchar outI[4], float outF[4], float u, float v) -{ - if (outF) { - nearest_interpolation_color_fl(in, outI, outF, u, v); - } - else { - nearest_interpolation_color_char(in, outI, outF, u, v); - } -} - -void nearest_interpolation_color_wrap( - const ImBuf *in, uchar outI[4], float outF[4], float u, float v) -{ - const float *dataF; - uchar *dataI; - int y, x; - - /* ImBuf in must have a valid rect or rect_float, assume this is already checked */ - - x = int(floor(u)); - y = int(floor(v)); - - x = x % in->x; - y = y % in->y; - - /* Wrap interpolation pixels - main difference from #nearest_interpolation_color. */ - if (x < 0) { - x += in->x; - } - if (y < 0) { - y += in->y; - } - - dataI = in->byte_buffer.data + size_t(in->x) * y * 4 + 4 * x; - if (outI) { - outI[0] = dataI[0]; - outI[1] = dataI[1]; - outI[2] = dataI[2]; - outI[3] = dataI[3]; - } - dataF = in->float_buffer.data + size_t(in->x) * y * 4 + 4 * x; - if (outF) { - outF[0] = dataF[0]; - outF[1] = dataF[1]; - outF[2] = dataF[2]; - outF[3] = dataF[3]; - } -} - -void nearest_interpolation(const ImBuf *in, ImBuf *out, float u, float v, int xout, int yout) -{ - uchar *outI = nullptr; - float *outF = nullptr; - - if (in == nullptr || (in->byte_buffer.data == nullptr && in->float_buffer.data == nullptr)) { - return; - } - - /* gcc warns these could be uninitialized, but its ok. */ - pixel_from_buffer(out, &outI, &outF, xout, yout); - - nearest_interpolation_color(in, outI, outF, u, v); -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Threaded Image Processing * \{ */ @@ -472,25 +187,3 @@ void IMB_alpha_under_color_byte(uchar *rect, int x, int y, const float backcol[3 } /** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sample Pixel - * \{ */ - -void IMB_sampleImageAtLocation(ImBuf *ibuf, float x, float y, bool make_linear_rgb, float color[4]) -{ - if (ibuf->float_buffer.data) { - nearest_interpolation_color(ibuf, nullptr, color, x, y); - } - else { - uchar byte_color[4]; - nearest_interpolation_color(ibuf, byte_color, nullptr, x, y); - rgba_uchar_to_float(color, byte_color); - if (make_linear_rgb) { - IMB_colormanagement_colorspace_to_scene_linear_v4( - color, false, ibuf->byte_buffer.colorspace); - } - } -} - -/** \} */ diff --git a/source/blender/imbuf/intern/interp.cc b/source/blender/imbuf/intern/interp.cc new file mode 100644 index 00000000000..7e1ae484722 --- /dev/null +++ b/source/blender/imbuf/intern/interp.cc @@ -0,0 +1,27 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup imbuf + */ + +#include "BLI_math_vector.h" +#include "IMB_colormanagement.hh" +#include "IMB_interp.hh" + +void IMB_sampleImageAtLocation(ImBuf *ibuf, float x, float y, bool make_linear_rgb, float color[4]) +{ + using namespace blender; + if (ibuf->float_buffer.data) { + imbuf::interpolate_nearest_fl(ibuf, color, x, y); + } + else { + uchar4 byte_color = imbuf::interpolate_nearest_byte(ibuf, x, y); + rgba_uchar_to_float(color, byte_color); + if (make_linear_rgb) { + IMB_colormanagement_colorspace_to_scene_linear_v4( + color, false, ibuf->byte_buffer.colorspace); + } + } +} diff --git a/source/blender/imbuf/intern/scaling.cc b/source/blender/imbuf/intern/scaling.cc index a222f6b0286..bf99aaca942 100644 --- a/source/blender/imbuf/intern/scaling.cc +++ b/source/blender/imbuf/intern/scaling.cc @@ -9,16 +9,15 @@ #include #include "BLI_math_color.h" -#include "BLI_math_interp.hh" #include "BLI_utildefines.h" #include "MEM_guardedalloc.h" +#include "IMB_filter.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" +#include "IMB_interp.hh" #include "imbuf.hh" -#include "IMB_filter.hh" - #include "BLI_sys_types.h" /* for intptr_t support */ static void imb_half_x_no_alloc(ImBuf *ibuf2, ImBuf *ibuf1) @@ -1745,6 +1744,7 @@ static void scale_thread_init(void *data_v, int start_line, int tot_line, void * static void *do_scale_thread(void *data_v) { + using namespace blender::imbuf; ScaleThreadData *data = (ScaleThreadData *)data_v; ImBuf *ibuf = data->ibuf; int i; @@ -1761,13 +1761,12 @@ static void *do_scale_thread(void *data_v) int offset = y * data->newx + x; if (data->byte_buffer) { - uchar *pixel = data->byte_buffer + 4 * offset; - BLI_bilinear_interpolation_char(ibuf->byte_buffer.data, pixel, ibuf->x, ibuf->y, u, v); + interpolate_bilinear_byte(ibuf, data->byte_buffer + 4 * offset, u, v); } if (data->float_buffer) { float *pixel = data->float_buffer + ibuf->channels * offset; - BLI_bilinear_interpolation_fl( + blender::math::interpolate_bilinear_fl( ibuf->float_buffer.data, pixel, ibuf->x, ibuf->y, ibuf->channels, u, v); } } diff --git a/source/blender/imbuf/intern/transform.cc b/source/blender/imbuf/intern/transform.cc index 5d4cebbcdf7..1ba0aa70787 100644 --- a/source/blender/imbuf/intern/transform.cc +++ b/source/blender/imbuf/intern/transform.cc @@ -1,4 +1,5 @@ /* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved. + * SPDX-FileCopyrightText: 2024 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ @@ -17,7 +18,10 @@ #include "BLI_vector.hh" #include "IMB_imbuf.hh" -#include "IMB_imbuf_types.hh" +#include "IMB_interp.hh" + +using blender::float4; +using blender::uchar4; namespace blender::imbuf::transform { @@ -134,27 +138,6 @@ static float wrap_uv(float value, int size) return x; } -template -static void sample_nearest_float(const ImBuf *source, float u, float v, float *r_sample) -{ - int x1 = int(u); - int y1 = int(v); - - /* Break when sample outside image is requested. */ - if (x1 < 0 || x1 >= source->x || y1 < 0 || y1 >= source->y) { - for (int i = 0; i < NumChannels; i++) { - r_sample[i] = 0.0f; - } - return; - } - - size_t offset = (size_t(source->x) * y1 + x1) * NumChannels; - const float *dataF = source->float_buffer.data + offset; - for (int i = 0; i < NumChannels; i++) { - r_sample[i] = dataF[i]; - } -} - /* Read a pixel from an image buffer, with filtering/wrapping parameters. */ template static void sample_image(const ImBuf *source, float u, float v, T *r_sample) @@ -171,19 +154,19 @@ static void sample_image(const ImBuf *source, float u, float v, T *r_sample) v -= 0.5f; } if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v && NumChannels == 4) { - bilinear_interpolation_color_fl(source, r_sample, u, v); + interpolate_bilinear_fl(source, r_sample, u, v); } else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v && NumChannels == 4) { - nearest_interpolation_color_char(source, r_sample, nullptr, u, v); + interpolate_nearest_byte(source, r_sample, u, v); } else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v && NumChannels == 4) { - bilinear_interpolation_color_char(source, r_sample, u, v); + interpolate_bilinear_byte(source, r_sample, u, v); } else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v) { if constexpr (WrapUV) { - BLI_bilinear_interpolation_wrap_fl(source->float_buffer.data, + math::interpolate_bilinear_wrap_fl(source->float_buffer.data, r_sample, source->x, source->y, @@ -194,20 +177,21 @@ static void sample_image(const ImBuf *source, float u, float v, T *r_sample) true); } else { - BLI_bilinear_interpolation_fl( + math::interpolate_bilinear_fl( source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v); } } else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v) { - sample_nearest_float(source, u, v, r_sample); + math::interpolate_nearest_fl( + source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v); } else if constexpr (Filter == IMB_FILTER_BICUBIC && std::is_same_v) { - BLI_bicubic_interpolation_fl( + math::interpolate_cubic_bspline_fl( source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v); } else if constexpr (Filter == IMB_FILTER_BICUBIC && std::is_same_v && NumChannels == 4) { - BLI_bicubic_interpolation_char(source->byte_buffer.data, r_sample, source->x, source->y, u, v); + interpolate_cubic_bspline_byte(source, r_sample, u, v); } else { /* Unsupported sampler. */ diff --git a/source/blender/render/intern/texture_margin.cc b/source/blender/render/intern/texture_margin.cc index 73873b7cb18..51d30ab0726 100644 --- a/source/blender/render/intern/texture_margin.cc +++ b/source/blender/render/intern/texture_margin.cc @@ -19,7 +19,7 @@ #include "BKE_mesh_mapping.hh" #include "IMB_imbuf.hh" -#include "IMB_imbuf_types.hh" +#include "IMB_interp.hh" #include "MEM_guardedalloc.h" @@ -221,9 +221,12 @@ class TextureMarginMap { */ void lookup_pixels(ImBuf *ibuf, char *mask, int maxPolygonSteps) { + float4 *ibuf_ptr_fl = reinterpret_cast(ibuf->float_buffer.data); + uchar4 *ibuf_ptr_ch = reinterpret_cast(ibuf->byte_buffer.data); + size_t pixel_index = 0; for (int y = 0; y < h_; y++) { for (int x = 0; x < w_; x++) { - uint32_t dp = get_pixel(x, y); + uint32_t dp = pixel_data_[pixel_index]; if (IsDijkstraPixel(dp) && !DijkstraPixelIsUnset(dp)) { int dist = DijkstraPixelGetDistance(dp); int direction = DijkstraPixelGetDirection(dp); @@ -270,9 +273,14 @@ class TextureMarginMap { } if (found_pixel_in_polygon) { - bilinear_interpolation(ibuf, ibuf, destX, destY, x, y); + if (ibuf_ptr_fl) { + ibuf_ptr_fl[pixel_index] = imbuf::interpolate_bilinear_fl(ibuf, destX, destY); + } + if (ibuf_ptr_ch) { + ibuf_ptr_ch[pixel_index] = imbuf::interpolate_bilinear_byte(ibuf, destX, destY); + } /* Add our new pixels to the assigned pixel map. */ - mask[y * w_ + x] = 1; + mask[pixel_index] = 1; } } } @@ -280,8 +288,9 @@ class TextureMarginMap { /* These are not margin pixels, make sure the extend filter which is run after this step * leaves them alone. */ - mask[y * w_ + x] = 1; + mask[pixel_index] = 1; } + pixel_index++; } } } diff --git a/source/blender/sequencer/intern/effects.cc b/source/blender/sequencer/intern/effects.cc index 822d5d4660f..1ffd6754426 100644 --- a/source/blender/sequencer/intern/effects.cc +++ b/source/blender/sequencer/intern/effects.cc @@ -1,5 +1,5 @@ /* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved. - * SPDX-FileCopyrightText: 2003-2009 Blender Authors + * SPDX-FileCopyrightText: 2003-2024 Blender Authors * SPDX-FileCopyrightText: 2005-2006 Peter Schlaile * * SPDX-License-Identifier: GPL-2.0-or-later */ @@ -40,6 +40,7 @@ #include "IMB_colormanagement.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" +#include "IMB_interp.hh" #include "IMB_metadata.hh" #include "BLI_math_color_blend.h" @@ -64,7 +65,7 @@ #include "strip_time.hh" #include "utils.hh" -using blender::float4; +using namespace blender; static SeqEffectHandle get_sequence_effect_impl(int seq_type); @@ -1536,7 +1537,7 @@ static void transform_image(int x, int y, int start_line, int total_lines, - ImBuf *ibuf1, + ImBuf *ibuf, ImBuf *out, float scale_x, float scale_y, @@ -1549,6 +1550,10 @@ static void transform_image(int x, float s = sinf(rotate); float c = cosf(rotate); + float4 *dst_fl = reinterpret_cast(out->float_buffer.data); + uchar4 *dst_ch = reinterpret_cast(out->byte_buffer.data); + + size_t offset = size_t(x) * start_line; for (int yi = start_line; yi < start_line + total_lines; yi++) { for (int xi = 0; xi < x; xi++) { /* Translate point. */ @@ -1570,15 +1575,31 @@ static void transform_image(int x, /* interpolate */ switch (interpolation) { case 0: - nearest_interpolation(ibuf1, out, xt, yt, xi, yi); + if (dst_fl) { + dst_fl[offset] = imbuf::interpolate_nearest_fl(ibuf, xt, yt); + } + else { + dst_ch[offset] = imbuf::interpolate_nearest_byte(ibuf, xt, yt); + } break; case 1: - bilinear_interpolation(ibuf1, out, xt, yt, xi, yi); + if (dst_fl) { + dst_fl[offset] = imbuf::interpolate_bilinear_fl(ibuf, xt, yt); + } + else { + dst_ch[offset] = imbuf::interpolate_bilinear_byte(ibuf, xt, yt); + } break; case 2: - bicubic_interpolation(ibuf1, out, xt, yt, xi, yi); + if (dst_fl) { + dst_fl[offset] = imbuf::interpolate_cubic_bspline_fl(ibuf, xt, yt); + } + else { + dst_ch[offset] = imbuf::interpolate_cubic_bspline_byte(ibuf, xt, yt); + } break; } + offset++; } } }