Implement Lanczos image filter
This commit is contained in:
parent
d8617f237a
commit
28bff3d1ad
151
core/image.cpp
151
core/image.cpp
|
@ -725,6 +725,131 @@ static void _scale_nearest(const uint8_t *__restrict p_src, uint8_t *__restrict
|
|||
}
|
||||
}
|
||||
|
||||
#define LANCZOS_TYPE 3
|
||||
|
||||
static float _lanczos(float p_x) {
|
||||
return Math::abs(p_x) >= LANCZOS_TYPE ? 0 : Math::sincn(p_x) * Math::sincn(p_x / LANCZOS_TYPE);
|
||||
}
|
||||
|
||||
template <int CC, class T>
|
||||
static void _scale_lanczos(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) {
|
||||
|
||||
int32_t src_width = p_src_width;
|
||||
int32_t src_height = p_src_height;
|
||||
int32_t dst_height = p_dst_height;
|
||||
int32_t dst_width = p_dst_width;
|
||||
|
||||
uint32_t buffer_size = src_height * dst_width * CC;
|
||||
float *buffer = memnew_arr(float, buffer_size); // Store the first pass in a buffer
|
||||
|
||||
{ // FIRST PASS (horizontal)
|
||||
|
||||
float x_scale = float(src_width) / float(dst_width);
|
||||
|
||||
float scale_factor = MAX(x_scale, 1); // A larger kernel is required only when downscaling
|
||||
int32_t half_kernel = LANCZOS_TYPE * scale_factor;
|
||||
|
||||
float *kernel = memnew_arr(float, half_kernel * 2 - 1);
|
||||
|
||||
for (int32_t buffer_x = 0; buffer_x < dst_width; buffer_x++) {
|
||||
|
||||
float src_real_x = buffer_x * x_scale;
|
||||
int32_t src_x = src_real_x;
|
||||
|
||||
int32_t start_x = MAX(0, src_x - half_kernel + 1);
|
||||
int32_t end_x = MIN(src_width - 1, src_x + half_kernel);
|
||||
|
||||
// Create the kernel used by all the pixels of the column
|
||||
for (int32_t target_x = start_x; target_x <= end_x; target_x++)
|
||||
kernel[target_x - start_x] = _lanczos((src_real_x - target_x) / scale_factor);
|
||||
|
||||
for (int32_t buffer_y = 0; buffer_y < src_height; buffer_y++) {
|
||||
|
||||
float pixel[CC] = { 0 };
|
||||
float weight = 0;
|
||||
|
||||
for (int32_t target_x = start_x; target_x <= end_x; target_x++) {
|
||||
|
||||
float lanczos_val = kernel[target_x - start_x];
|
||||
weight += lanczos_val;
|
||||
|
||||
const T *__restrict src_data = ((const T *)p_src) + (buffer_y * src_width + target_x) * CC;
|
||||
|
||||
for (uint32_t i = 0; i < CC; i++) {
|
||||
if (sizeof(T) == 2) //half float
|
||||
pixel[i] += Math::half_to_float(src_data[i]) * lanczos_val;
|
||||
else
|
||||
pixel[i] += src_data[i] * lanczos_val;
|
||||
}
|
||||
}
|
||||
|
||||
float *dst_data = ((float *)buffer) + (buffer_y * dst_width + buffer_x) * CC;
|
||||
|
||||
for (uint32_t i = 0; i < CC; i++)
|
||||
dst_data[i] = pixel[i] / weight; // Normalize the sum of all the samples
|
||||
}
|
||||
}
|
||||
|
||||
memdelete_arr(kernel);
|
||||
} // End of first pass
|
||||
|
||||
{ // SECOND PASS (vertical + result)
|
||||
|
||||
float y_scale = float(src_height) / float(dst_height);
|
||||
|
||||
float scale_factor = MAX(y_scale, 1);
|
||||
int32_t half_kernel = LANCZOS_TYPE * scale_factor;
|
||||
|
||||
float *kernel = memnew_arr(float, half_kernel * 2 - 1);
|
||||
|
||||
for (int32_t dst_y = 0; dst_y < dst_height; dst_y++) {
|
||||
|
||||
float buffer_real_y = dst_y * y_scale;
|
||||
int32_t buffer_y = buffer_real_y;
|
||||
|
||||
int32_t start_y = MAX(0, buffer_y - half_kernel + 1);
|
||||
int32_t end_y = MIN(src_height - 1, buffer_y + half_kernel);
|
||||
|
||||
for (int32_t target_y = start_y; target_y <= end_y; target_y++)
|
||||
kernel[target_y - start_y] = _lanczos((buffer_real_y - target_y) / scale_factor);
|
||||
|
||||
for (int32_t dst_x = 0; dst_x < dst_width; dst_x++) {
|
||||
|
||||
float pixel[CC] = { 0 };
|
||||
float weight = 0;
|
||||
|
||||
for (int32_t target_y = start_y; target_y <= end_y; target_y++) {
|
||||
|
||||
float lanczos_val = kernel[target_y - start_y];
|
||||
weight += lanczos_val;
|
||||
|
||||
float *buffer_data = ((float *)buffer) + (target_y * dst_width + dst_x) * CC;
|
||||
|
||||
for (uint32_t i = 0; i < CC; i++)
|
||||
pixel[i] += buffer_data[i] * lanczos_val;
|
||||
}
|
||||
|
||||
T *dst_data = ((T *)p_dst) + (dst_y * dst_width + dst_x) * CC;
|
||||
|
||||
for (uint32_t i = 0; i < CC; i++) {
|
||||
pixel[i] /= weight;
|
||||
|
||||
if (sizeof(T) == 1) //byte
|
||||
dst_data[i] = CLAMP(Math::fast_ftoi(pixel[i]), 0, 255);
|
||||
else if (sizeof(T) == 2) //half float
|
||||
dst_data[i] = Math::make_half_float(pixel[i]);
|
||||
else // float
|
||||
dst_data[i] = pixel[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memdelete_arr(kernel);
|
||||
} // End of second pass
|
||||
|
||||
memdelete_arr(buffer);
|
||||
}
|
||||
|
||||
static void _overlay(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, float p_alpha, uint32_t p_width, uint32_t p_height, uint32_t p_pixel_size) {
|
||||
|
||||
uint16_t alpha = CLAMP((uint16_t)(p_alpha * 256.0f), 0, 256);
|
||||
|
@ -939,6 +1064,31 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) {
|
|||
}
|
||||
}
|
||||
} break;
|
||||
case INTERPOLATE_LANCZOS: {
|
||||
|
||||
if (format >= FORMAT_L8 && format <= FORMAT_RGBA8) {
|
||||
switch (get_format_pixel_size(format)) {
|
||||
case 1: _scale_lanczos<1, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); break;
|
||||
case 2: _scale_lanczos<2, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); break;
|
||||
case 3: _scale_lanczos<3, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); break;
|
||||
case 4: _scale_lanczos<4, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); break;
|
||||
}
|
||||
} else if (format >= FORMAT_RF && format <= FORMAT_RGBAF) {
|
||||
switch (get_format_pixel_size(format)) {
|
||||
case 4: _scale_lanczos<1, float>(r_ptr, w_ptr, width, height, p_width, p_height); break;
|
||||
case 8: _scale_lanczos<2, float>(r_ptr, w_ptr, width, height, p_width, p_height); break;
|
||||
case 12: _scale_lanczos<3, float>(r_ptr, w_ptr, width, height, p_width, p_height); break;
|
||||
case 16: _scale_lanczos<4, float>(r_ptr, w_ptr, width, height, p_width, p_height); break;
|
||||
}
|
||||
} else if (format >= FORMAT_RH && format <= FORMAT_RGBAH) {
|
||||
switch (get_format_pixel_size(format)) {
|
||||
case 2: _scale_lanczos<1, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); break;
|
||||
case 4: _scale_lanczos<2, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); break;
|
||||
case 6: _scale_lanczos<3, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); break;
|
||||
case 8: _scale_lanczos<4, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); break;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
r = PoolVector<uint8_t>::Read();
|
||||
|
@ -2685,6 +2835,7 @@ void Image::_bind_methods() {
|
|||
BIND_ENUM_CONSTANT(INTERPOLATE_BILINEAR);
|
||||
BIND_ENUM_CONSTANT(INTERPOLATE_CUBIC);
|
||||
BIND_ENUM_CONSTANT(INTERPOLATE_TRILINEAR);
|
||||
BIND_ENUM_CONSTANT(INTERPOLATE_LANCZOS);
|
||||
|
||||
BIND_ENUM_CONSTANT(ALPHA_NONE);
|
||||
BIND_ENUM_CONSTANT(ALPHA_BIT);
|
||||
|
|
|
@ -109,6 +109,7 @@ public:
|
|||
INTERPOLATE_BILINEAR,
|
||||
INTERPOLATE_CUBIC,
|
||||
INTERPOLATE_TRILINEAR,
|
||||
INTERPOLATE_LANCZOS,
|
||||
/* INTERPOLATE_TRICUBIC, */
|
||||
/* INTERPOLATE GAUSS */
|
||||
};
|
||||
|
|
|
@ -61,6 +61,12 @@ public:
|
|||
static _ALWAYS_INLINE_ double sinh(double p_x) { return ::sinh(p_x); }
|
||||
static _ALWAYS_INLINE_ float sinh(float p_x) { return ::sinhf(p_x); }
|
||||
|
||||
static _ALWAYS_INLINE_ float sinc(float p_x) { return p_x == 0 ? 1 : ::sin(p_x) / p_x; }
|
||||
static _ALWAYS_INLINE_ double sinc(double p_x) { return p_x == 0 ? 1 : ::sin(p_x) / p_x; }
|
||||
|
||||
static _ALWAYS_INLINE_ float sincn(float p_x) { return sinc(Math_PI * p_x); }
|
||||
static _ALWAYS_INLINE_ double sincn(double p_x) { return sinc(Math_PI * p_x); }
|
||||
|
||||
static _ALWAYS_INLINE_ double cosh(double p_x) { return ::cosh(p_x); }
|
||||
static _ALWAYS_INLINE_ float cosh(float p_x) { return ::coshf(p_x); }
|
||||
|
||||
|
|
|
@ -603,6 +603,8 @@
|
|||
If the image does not have mipmaps, they will be generated and used internally, but no mipmaps will be generated on the resulting image. (Note that if you intend to scale multiple copies of the original image, it's better to call [code]generate_mipmaps[/code] on it in advance, to avoid wasting processing power in generating them again and again.)
|
||||
On the other hand, if the image already has mipmaps, they will be used, and a new set will be generated for the resulting image.
|
||||
</constant>
|
||||
<constant name="INTERPOLATE_LANCZOS" value="4" enum="Interpolation">
|
||||
</constant>
|
||||
<constant name="ALPHA_NONE" value="0" enum="AlphaMode">
|
||||
</constant>
|
||||
<constant name="ALPHA_BIT" value="1" enum="AlphaMode">
|
||||
|
|
|
@ -1015,8 +1015,7 @@ void EditorNode::_save_scene_with_preview(String p_file, int p_idx) {
|
|||
y = (img->get_height() - size) / 2;
|
||||
|
||||
img->crop_from_point(x, y, size, size);
|
||||
// We could get better pictures with better filters
|
||||
img->resize(preview_size, preview_size, Image::INTERPOLATE_CUBIC);
|
||||
img->resize(preview_size, preview_size, Image::INTERPOLATE_LANCZOS);
|
||||
}
|
||||
img->convert(Image::FORMAT_RGB8);
|
||||
|
||||
|
|
Loading…
Reference in New Issue