Merge pull request #19313 from RandomShaper/improve-image
Image trilinear scaling + Optimization
This commit is contained in:
commit
b66580927e
112
core/image.cpp
112
core/image.cpp
|
@ -33,6 +33,7 @@
|
|||
#include "core/io/image_loader.h"
|
||||
#include "core/os/copymem.h"
|
||||
#include "hash_map.h"
|
||||
#include "math_funcs.h"
|
||||
#include "print_string.h"
|
||||
|
||||
#include "thirdparty/misc/hq2x.h"
|
||||
|
@ -525,7 +526,7 @@ static double _bicubic_interp_kernel(double x) {
|
|||
}
|
||||
|
||||
template <int CC>
|
||||
static void _scale_cubic(const uint8_t *p_src, uint8_t *p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) {
|
||||
static void _scale_cubic(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) {
|
||||
|
||||
// get source image size
|
||||
int width = p_src_width;
|
||||
|
@ -555,7 +556,7 @@ static void _scale_cubic(const uint8_t *p_src, uint8_t *p_dst, uint32_t p_src_wi
|
|||
|
||||
// initial pixel value
|
||||
|
||||
uint8_t *dst = p_dst + (y * p_dst_width + x) * CC;
|
||||
uint8_t *__restrict dst = p_dst + (y * p_dst_width + x) * CC;
|
||||
|
||||
double color[CC];
|
||||
for (int i = 0; i < CC; i++) {
|
||||
|
@ -583,7 +584,7 @@ static void _scale_cubic(const uint8_t *p_src, uint8_t *p_dst, uint32_t p_src_wi
|
|||
ox2 = xmax;
|
||||
|
||||
// get pixel of original image
|
||||
const uint8_t *p = p_src + (oy2 * p_src_width + ox2) * CC;
|
||||
const uint8_t *__restrict p = p_src + (oy2 * p_src_width + ox2) * CC;
|
||||
|
||||
for (int i = 0; i < CC; i++) {
|
||||
|
||||
|
@ -600,7 +601,7 @@ static void _scale_cubic(const uint8_t *p_src, uint8_t *p_dst, uint32_t p_src_wi
|
|||
}
|
||||
|
||||
template <int CC>
|
||||
static void _scale_bilinear(const uint8_t *p_src, uint8_t *p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) {
|
||||
static void _scale_bilinear(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) {
|
||||
|
||||
enum {
|
||||
FRAC_BITS = 8,
|
||||
|
@ -655,7 +656,7 @@ static void _scale_bilinear(const uint8_t *p_src, uint8_t *p_dst, uint32_t p_src
|
|||
}
|
||||
|
||||
template <int CC>
|
||||
static void _scale_nearest(const uint8_t *p_src, uint8_t *p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) {
|
||||
static void _scale_nearest(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) {
|
||||
|
||||
for (uint32_t i = 0; i < p_dst_height; i++) {
|
||||
|
||||
|
@ -676,6 +677,16 @@ static void _scale_nearest(const uint8_t *p_src, uint8_t *p_dst, uint32_t p_src_
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
for (uint32_t i = 0; i < p_width * p_height * p_pixel_size; i++) {
|
||||
|
||||
p_dst[i] = (p_dst[i] * (256 - alpha) + p_src[i] * alpha) >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
void Image::resize_to_po2(bool p_square) {
|
||||
|
||||
if (!_can_modify(format)) {
|
||||
|
@ -707,6 +718,8 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) {
|
|||
ERR_FAIL();
|
||||
}
|
||||
|
||||
bool mipmap_aware = p_interpolation == INTERPOLATE_TRILINEAR /* || p_interpolation == INTERPOLATE_TRICUBIC */;
|
||||
|
||||
ERR_FAIL_COND(p_width <= 0);
|
||||
ERR_FAIL_COND(p_height <= 0);
|
||||
ERR_FAIL_COND(p_width > MAX_WIDTH);
|
||||
|
@ -717,6 +730,32 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) {
|
|||
|
||||
Image dst(p_width, p_height, 0, format);
|
||||
|
||||
// Setup mipmap-aware scaling
|
||||
Image dst2;
|
||||
int mip1;
|
||||
int mip2;
|
||||
float mip1_weight;
|
||||
if (mipmap_aware) {
|
||||
float avg_scale = ((float)p_width / width + (float)p_height / height) * 0.5f;
|
||||
if (avg_scale >= 1.0f) {
|
||||
mipmap_aware = false;
|
||||
} else {
|
||||
float level = Math::log(1.0f / avg_scale) / Math::log(2.0f);
|
||||
mip1 = CLAMP((int)Math::floor(level), 0, get_mipmap_count());
|
||||
mip2 = CLAMP((int)Math::ceil(level), 0, get_mipmap_count());
|
||||
mip1_weight = 1.0f - (level - mip1);
|
||||
}
|
||||
}
|
||||
bool interpolate_mipmaps = mipmap_aware && mip1 != mip2;
|
||||
if (interpolate_mipmaps) {
|
||||
dst2.create(p_width, p_height, 0, format);
|
||||
}
|
||||
bool had_mipmaps = mipmaps;
|
||||
if (interpolate_mipmaps && !had_mipmaps) {
|
||||
generate_mipmaps();
|
||||
}
|
||||
// --
|
||||
|
||||
PoolVector<uint8_t>::Read r = data.read();
|
||||
const unsigned char *r_ptr = r.ptr();
|
||||
|
||||
|
@ -734,13 +773,57 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) {
|
|||
case 4: _scale_nearest<4>(r_ptr, w_ptr, width, height, p_width, p_height); break;
|
||||
}
|
||||
} break;
|
||||
case INTERPOLATE_BILINEAR: {
|
||||
case INTERPOLATE_BILINEAR:
|
||||
case INTERPOLATE_TRILINEAR: {
|
||||
|
||||
switch (get_format_pixel_size(format)) {
|
||||
case 1: _scale_bilinear<1>(r_ptr, w_ptr, width, height, p_width, p_height); break;
|
||||
case 2: _scale_bilinear<2>(r_ptr, w_ptr, width, height, p_width, p_height); break;
|
||||
case 3: _scale_bilinear<3>(r_ptr, w_ptr, width, height, p_width, p_height); break;
|
||||
case 4: _scale_bilinear<4>(r_ptr, w_ptr, width, height, p_width, p_height); break;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
int src_width;
|
||||
int src_height;
|
||||
const unsigned char *src_ptr;
|
||||
|
||||
if (!mipmap_aware) {
|
||||
if (i == 0) {
|
||||
// Standard behavior
|
||||
src_width = width;
|
||||
src_height = height;
|
||||
src_ptr = r_ptr;
|
||||
} else {
|
||||
// No need for a second iteration
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (i == 0) {
|
||||
// Read from the first mipmap that will be interpolated
|
||||
// (if both levels are the same, we will not interpolate, but at least we'll sample from the right level)
|
||||
int offs;
|
||||
_get_mipmap_offset_and_size(mip1, offs, src_width, src_height);
|
||||
src_ptr = r_ptr + offs;
|
||||
} else if (!interpolate_mipmaps) {
|
||||
// No need generate a second image
|
||||
break;
|
||||
} else {
|
||||
// Switch to read from the second mipmap that will be interpolated
|
||||
int offs;
|
||||
_get_mipmap_offset_and_size(mip2, offs, src_width, src_height);
|
||||
src_ptr = r_ptr + offs;
|
||||
// Switch to write to the second destination image
|
||||
w = dst2.data.write();
|
||||
w_ptr = w.ptr();
|
||||
}
|
||||
}
|
||||
|
||||
switch (get_format_pixel_size(format)) {
|
||||
case 1: _scale_bilinear<1>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break;
|
||||
case 2: _scale_bilinear<2>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break;
|
||||
case 3: _scale_bilinear<3>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break;
|
||||
case 4: _scale_bilinear<4>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break;
|
||||
}
|
||||
}
|
||||
|
||||
if (interpolate_mipmaps) {
|
||||
// Switch to read again from the first scaled mipmap to overlay it over the second
|
||||
r = dst.data.read();
|
||||
_overlay(r.ptr(), w.ptr(), mip1_weight, p_width, p_height, get_format_pixel_size(format));
|
||||
}
|
||||
|
||||
} break;
|
||||
|
@ -759,7 +842,11 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) {
|
|||
r = PoolVector<uint8_t>::Read();
|
||||
w = PoolVector<uint8_t>::Write();
|
||||
|
||||
if (mipmaps > 0)
|
||||
if (interpolate_mipmaps) {
|
||||
dst._copy_internals_from(dst2);
|
||||
}
|
||||
|
||||
if (had_mipmaps)
|
||||
dst.generate_mipmaps();
|
||||
|
||||
_copy_internals_from(dst);
|
||||
|
@ -2404,6 +2491,7 @@ void Image::_bind_methods() {
|
|||
BIND_ENUM_CONSTANT(INTERPOLATE_NEAREST);
|
||||
BIND_ENUM_CONSTANT(INTERPOLATE_BILINEAR);
|
||||
BIND_ENUM_CONSTANT(INTERPOLATE_CUBIC);
|
||||
BIND_ENUM_CONSTANT(INTERPOLATE_TRILINEAR);
|
||||
|
||||
BIND_ENUM_CONSTANT(ALPHA_NONE);
|
||||
BIND_ENUM_CONSTANT(ALPHA_BIT);
|
||||
|
|
|
@ -108,6 +108,8 @@ public:
|
|||
INTERPOLATE_NEAREST,
|
||||
INTERPOLATE_BILINEAR,
|
||||
INTERPOLATE_CUBIC,
|
||||
INTERPOLATE_TRILINEAR,
|
||||
/* INTERPOLATE_TRICUBIC, */
|
||||
/* INTERPOLATE GAUSS */
|
||||
};
|
||||
|
||||
|
|
|
@ -593,6 +593,12 @@
|
|||
</constant>
|
||||
<constant name="INTERPOLATE_CUBIC" value="2" enum="Interpolation">
|
||||
</constant>
|
||||
<constant name="INTERPOLATE_TRILINEAR" value="3" enum="Interpolation">
|
||||
Performs bilinear separately on the two most suited mipmap levels, then linearly interpolates between them.
|
||||
It's slower than [code]INTERPOLATE_BILINEAR[/code], but produces higher quality results, with much less aliasing artifacts.
|
||||
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="ALPHA_NONE" value="0" enum="AlphaMode">
|
||||
</constant>
|
||||
<constant name="ALPHA_BIT" value="1" enum="AlphaMode">
|
||||
|
|
Loading…
Reference in New Issue