BLI: Add nearest interpolation with clamped boundary

This patch adds clamped boundaries variants of the nearest interpolation
functions in the BLI module. The naming convention used by the bilinear
functions were followed.

Needed by #119414.

Pull Request: https://projects.blender.org/blender/blender/pulls/119732
This commit is contained in:
Omar Emara 2024-03-21 13:22:10 +01:00
parent e2bdaf8ec7
commit 2906ea9785
10 changed files with 140 additions and 40 deletions

View File

@ -491,7 +491,7 @@ static float4 studiolight_calculate_radiance(ImBuf *ibuf, const float direction[
{
float uv[2];
direction_to_equirect(uv, direction);
return blender::imbuf::interpolate_nearest_fl(ibuf, uv[0] * ibuf->x, uv[1] * ibuf->y);
return blender::imbuf::interpolate_nearest_border_fl(ibuf, uv[0] * ibuf->x, uv[1] * ibuf->y);
}
/*
@ -732,10 +732,10 @@ static void studiolight_matcap_preview(uint *icon_buffer, StudioLight *sl, bool
float u = dx * diffuse_buffer->x - 1.0f;
float v = dy * diffuse_buffer->y - 1.0f;
float4 color = imbuf::interpolate_nearest_fl(diffuse_buffer, u, v);
float4 color = imbuf::interpolate_nearest_border_fl(diffuse_buffer, u, v);
if (specular_buffer) {
float4 specular = imbuf::interpolate_nearest_fl(specular_buffer, u, v);
float4 specular = imbuf::interpolate_nearest_border_fl(specular_buffer, u, v);
add_v3_v3(color, specular);
}

View File

@ -1329,7 +1329,7 @@ static void tracking_stabilize_frame_interpolation_cb(void *__restrict userdata,
for (int x = 0; x < tmpibuf->x; x++, dst++) {
vec[0] = float(x);
mul_v3_m4v3(rvec, mat, vec);
*dst = imbuf::interpolate_nearest_fl(ibuf, rvec[0], rvec[1]);
*dst = imbuf::interpolate_nearest_border_fl(ibuf, rvec[0], rvec[1]);
}
}
}
@ -1355,7 +1355,7 @@ static void tracking_stabilize_frame_interpolation_cb(void *__restrict userdata,
for (int x = 0; x < tmpibuf->x; x++, dst++) {
vec[0] = float(x);
mul_v3_m4v3(rvec, mat, vec);
*dst = imbuf::interpolate_nearest_byte(ibuf, rvec[0], rvec[1]);
*dst = imbuf::interpolate_nearest_border_byte(ibuf, rvec[0], rvec[1]);
}
}
}

View File

@ -15,23 +15,25 @@
* Any filtering done on texel values just blends them without color space or
* gamma conversions.
*
* Sampling completely outside the image returns transparent black.
*/
#include "BLI_math_base.h"
#include "BLI_math_base.hh"
#include "BLI_math_vector_types.hh"
namespace blender::math {
/**
* Nearest (point) sampling.
* Nearest (point) sampling (with black border).
*
* Returns texel at floor(u,v) integer index -- note that it is not "nearest
* to u,v coordinate", but rather with fractional part truncated (it would be
* "nearest" if subtracting 0.5 from input u,v).
* Returns texel at floor(u,v) integer index. Samples outside the image are turned into transparent
* black.
*
* Note that it is not "nearest to u,v coordinate", but rather with fractional part truncated (it
* would be "nearest" if subtracting 0.5 from input u,v).
*/
inline void interpolate_nearest_byte(
inline void interpolate_nearest_border_byte(
const uchar *buffer, uchar *output, int width, int height, float u, float v)
{
BLI_assert(buffer);
@ -51,6 +53,67 @@ inline void interpolate_nearest_byte(
output[3] = data[3];
}
[[nodiscard]] inline uchar4 interpolate_nearest_border_byte(
const uchar *buffer, int width, int height, float u, float v)
{
uchar4 res;
interpolate_nearest_border_byte(buffer, res, width, height, u, v);
return res;
}
inline void interpolate_nearest_border_fl(
const float *buffer, float *output, int width, int height, int components, float u, float v)
{
BLI_assert(buffer);
int x = int(u);
int y = int(v);
/* Outside image? */
if (x < 0 || x >= width || y < 0 || y >= height) {
for (int i = 0; i < components; i++) {
output[i] = 0.0f;
}
return;
}
const float *data = buffer + (int64_t(width) * y + x) * components;
for (int i = 0; i < components; i++) {
output[i] = data[i];
}
}
[[nodiscard]] inline float4 interpolate_nearest_border_fl(
const float *buffer, int width, int height, float u, float v)
{
float4 res;
interpolate_nearest_border_fl(buffer, res, width, height, 4, u, v);
return res;
}
/**
* Nearest (point) sampling.
*
* Returns texel at floor(u,v) integer index. Samples outside the image are clamped to texels at
* image edge.
*
* Note that it is not "nearest to u,v coordinate", but rather with fractional part truncated (it
* would be "nearest" if subtracting 0.5 from input u,v).
*/
inline void interpolate_nearest_byte(
const uchar *buffer, uchar *output, int width, int height, float u, float v)
{
BLI_assert(buffer);
const int x = math::clamp(int(u), 0, width - 1);
const int y = math::clamp(int(v), 0, height - 1);
const uchar *data = buffer + (int64_t(width) * y + x) * 4;
output[0] = data[0];
output[1] = data[1];
output[2] = data[2];
output[3] = data[3];
}
[[nodiscard]] inline uchar4 interpolate_nearest_byte(
const uchar *buffer, int width, int height, float u, float v)
{
@ -63,16 +126,8 @@ inline void interpolate_nearest_fl(
const float *buffer, float *output, int width, int height, int components, float u, float v)
{
BLI_assert(buffer);
int x = int(u);
int y = int(v);
/* Outside image? */
if (x < 0 || x >= width || y < 0 || y >= height) {
for (int i = 0; i < components; i++) {
output[i] = 0.0f;
}
return;
}
const int x = math::clamp(int(u), 0, width - 1);
const int y = math::clamp(int(v), 0, height - 1);
const float *data = buffer + (int64_t(width) * y + x) * components;
for (int i = 0; i < components; i++) {

View File

@ -24,6 +24,50 @@ static constexpr float image_fl[image_height][image_width][4] = {
{{1, 2, 3, 4}, {73, 108, 153, 251}, {128, 129, 130, 131}},
};
TEST(math_interp, NearestCharExactSamples)
{
uchar4 res;
uchar4 exp1 = {73, 108, 153, 251};
res = interpolate_nearest_border_byte(image_char[0][0], image_width, image_height, 1.0f, 2.0f);
EXPECT_EQ(exp1, res);
uchar4 exp2 = {240, 160, 90, 20};
res = interpolate_nearest_border_byte(image_char[0][0], image_width, image_height, 2.0f, 0.0f);
EXPECT_EQ(exp2, res);
}
TEST(math_interp, NearestCharHalfwaySamples)
{
uchar4 res;
uchar4 exp1 = {0, 1, 2, 3};
res = interpolate_nearest_border_byte(image_char[0][0], image_width, image_height, 0.5f, 1.5f);
EXPECT_EQ(exp1, res);
uchar4 exp2 = {255, 254, 217, 216};
res = interpolate_nearest_border_byte(image_char[0][0], image_width, image_height, 0.5f, 0.5f);
EXPECT_EQ(exp2, res);
}
TEST(math_interp, NearestFloatExactSamples)
{
float4 res;
float4 exp1 = {73.0f, 108.0f, 153.0f, 251.0f};
res = interpolate_nearest_border_fl(image_fl[0][0], image_width, image_height, 1.0f, 2.0f);
EXPECT_EQ(exp1, res);
float4 exp2 = {240.0f, 160.0f, 90.0f, 20.0f};
res = interpolate_nearest_border_fl(image_fl[0][0], image_width, image_height, 2.0f, 0.0f);
EXPECT_EQ(exp2, res);
}
TEST(math_interp, NearestFloatHalfwaySamples)
{
float4 res;
float4 exp1 = {0.0f, 1.0f, 2.0f, 3.0f};
res = interpolate_nearest_border_fl(image_fl[0][0], image_width, image_height, 0.5f, 1.5f);
EXPECT_EQ(exp1, res);
float4 exp2 = {255.0f, 254.0f, 217.0f, 216.0f};
res = interpolate_nearest_border_fl(image_fl[0][0], image_width, image_height, 0.5f, 0.5f);
EXPECT_EQ(exp2, res);
}
TEST(math_interp, BilinearCharExactSamples)
{
uchar4 res;

View File

@ -74,7 +74,8 @@ void RenderLayersProg::do_interpolation(float output[4], float x, float y, Pixel
switch (sampler) {
case PixelSampler::Nearest:
math::interpolate_nearest_fl(input_buffer_, output, width, height, elementsize_, x, y);
math::interpolate_nearest_border_fl(
input_buffer_, output, width, height, elementsize_, x, y);
break;
case PixelSampler::Bilinear:
math::interpolate_bilinear_border_fl(

View File

@ -504,10 +504,10 @@ template<typename TextureMethod> class ScreenSpaceDrawingMode : public AbstractD
float xf = x / (float)texture_width;
float u = info.clipping_uv_bounds.xmax * xf +
info.clipping_uv_bounds.xmin * (1.0 - xf) - tile_offset_x;
imbuf::interpolate_nearest_fl(tile_buffer,
&extracted_buffer.float_buffer.data[offset * 4],
u * tile_buffer->x,
v * tile_buffer->y);
imbuf::interpolate_nearest_border_fl(tile_buffer,
&extracted_buffer.float_buffer.data[offset * 4],
u * tile_buffer->x,
v * tile_buffer->y);
offset++;
}
}

View File

@ -20,21 +20,21 @@ namespace blender::imbuf {
/* Nearest sampling. */
[[nodiscard]] inline uchar4 interpolate_nearest_byte(const ImBuf *in, float u, float v)
[[nodiscard]] inline uchar4 interpolate_nearest_border_byte(const ImBuf *in, float u, float v)
{
return math::interpolate_nearest_byte(in->byte_buffer.data, in->x, in->y, u, v);
return math::interpolate_nearest_border_byte(in->byte_buffer.data, in->x, in->y, u, v);
}
[[nodiscard]] inline float4 interpolate_nearest_fl(const ImBuf *in, float u, float v)
[[nodiscard]] inline float4 interpolate_nearest_border_fl(const ImBuf *in, float u, float v)
{
return math::interpolate_nearest_fl(in->float_buffer.data, in->x, in->y, u, v);
return math::interpolate_nearest_border_fl(in->float_buffer.data, in->x, in->y, u, v);
}
inline void interpolate_nearest_byte(const ImBuf *in, uchar output[4], float u, float v)
inline void interpolate_nearest_border_byte(const ImBuf *in, uchar output[4], float u, float v)
{
math::interpolate_nearest_byte(in->byte_buffer.data, output, in->x, in->y, u, v);
math::interpolate_nearest_border_byte(in->byte_buffer.data, output, in->x, in->y, u, v);
}
inline void interpolate_nearest_fl(const ImBuf *in, float output[4], float u, float v)
inline void interpolate_nearest_border_fl(const ImBuf *in, float output[4], float u, float v)
{
math::interpolate_nearest_fl(in->float_buffer.data, output, in->x, in->y, 4, u, v);
math::interpolate_nearest_border_fl(in->float_buffer.data, output, in->x, in->y, 4, u, v);
}
/* Nearest sampling with UV wrapping. */

View File

@ -14,10 +14,10 @@ void IMB_sampleImageAtLocation(ImBuf *ibuf, float x, float y, bool make_linear_r
{
using namespace blender;
if (ibuf->float_buffer.data) {
imbuf::interpolate_nearest_fl(ibuf, color, x, y);
imbuf::interpolate_nearest_border_fl(ibuf, color, x, y);
}
else {
uchar4 byte_color = imbuf::interpolate_nearest_byte(ibuf, x, y);
uchar4 byte_color = imbuf::interpolate_nearest_border_byte(ibuf, x, y);
rgba_uchar_to_float(color, byte_color);
if (make_linear_rgb) {
IMB_colormanagement_colorspace_to_scene_linear_v4(

View File

@ -140,7 +140,7 @@ static void sample_image(const ImBuf *source, float u, float v, T *r_sample)
}
else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v<T, uchar> && NumChannels == 4)
{
interpolate_nearest_byte(source, r_sample, u, v);
interpolate_nearest_border_byte(source, r_sample, u, v);
}
else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<T, uchar> && NumChannels == 4)
{
@ -164,7 +164,7 @@ static void sample_image(const ImBuf *source, float u, float v, T *r_sample)
}
}
else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v<T, float>) {
math::interpolate_nearest_fl(
math::interpolate_nearest_border_fl(
source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v);
}
else if constexpr (Filter == IMB_FILTER_CUBIC_BSPLINE && std::is_same_v<T, float>) {

View File

@ -1573,10 +1573,10 @@ static void transform_image(int x,
switch (interpolation) {
case 0:
if (dst_fl) {
dst_fl[offset] = imbuf::interpolate_nearest_fl(ibuf, xt, yt);
dst_fl[offset] = imbuf::interpolate_nearest_border_fl(ibuf, xt, yt);
}
else {
dst_ch[offset] = imbuf::interpolate_nearest_byte(ibuf, xt, yt);
dst_ch[offset] = imbuf::interpolate_nearest_border_byte(ibuf, xt, yt);
}
break;
case 1: