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