Merge pull request #19313 from RandomShaper/improve-image

Image trilinear scaling + Optimization
This commit is contained in:
Juan Linietsky 2018-07-23 16:21:45 -03:00 committed by GitHub
commit b66580927e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 108 additions and 12 deletions

View File

@ -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);

View File

@ -108,6 +108,8 @@ public:
INTERPOLATE_NEAREST,
INTERPOLATE_BILINEAR,
INTERPOLATE_CUBIC,
INTERPOLATE_TRILINEAR,
/* INTERPOLATE_TRICUBIC, */
/* INTERPOLATE GAUSS */
};

View File

@ -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">