diff options
Diffstat (limited to 'core/image.cpp')
| -rw-r--r-- | core/image.cpp | 485 |
1 files changed, 361 insertions, 124 deletions
diff --git a/core/image.cpp b/core/image.cpp index 19440d1718..dca8aedb8f 100644 --- a/core/image.cpp +++ b/core/image.cpp @@ -30,11 +30,12 @@ #include "image.h" +#include "core/hash_map.h" #include "core/io/image_loader.h" +#include "core/io/resource_loader.h" +#include "core/math/math_funcs.h" #include "core/os/copymem.h" -#include "hash_map.h" -#include "math_funcs.h" -#include "print_string.h" +#include "core/print_string.h" #include "thirdparty/misc/hq2x.h" @@ -306,6 +307,7 @@ void Image::_get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_widt r_width = w; r_height = h; } + int Image::get_mipmap_offset(int p_mipmap) const { ERR_FAIL_INDEX_V(p_mipmap, get_mipmap_count() + 1, -1); @@ -498,8 +500,6 @@ void Image::convert(Format p_new_format) { bool gen_mipmaps = mipmaps; - //mipmaps=false; - _copy_internals_from(new_img); if (gen_mipmaps) @@ -525,7 +525,7 @@ static double _bicubic_interp_kernel(double x) { return bc; } -template <int CC> +template <int CC, class T> 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 @@ -556,7 +556,7 @@ static void _scale_cubic(const uint8_t *__restrict p_src, uint8_t *__restrict p_ // initial pixel value - uint8_t *__restrict dst = p_dst + (y * p_dst_width + x) * CC; + T *__restrict dst = ((T *)p_dst) + (y * p_dst_width + x) * CC; double color[CC]; for (int i = 0; i < CC; i++) { @@ -584,23 +584,32 @@ static void _scale_cubic(const uint8_t *__restrict p_src, uint8_t *__restrict p_ ox2 = xmax; // get pixel of original image - const uint8_t *__restrict p = p_src + (oy2 * p_src_width + ox2) * CC; + const T *__restrict p = ((T *)p_src) + (oy2 * p_src_width + ox2) * CC; for (int i = 0; i < CC; i++) { - - color[i] += p[i] * k2; + if (sizeof(T) == 2) { //half float + color[i] = Math::half_to_float(p[i]); + } else { + color[i] += p[i] * k2; + } } } } for (int i = 0; i < CC; i++) { - dst[i] = CLAMP(Math::fast_ftoi(color[i]), 0, 255); + if (sizeof(T) == 1) { //byte + dst[i] = CLAMP(Math::fast_ftoi(color[i]), 0, 255); + } else if (sizeof(T) == 2) { //half float + dst[i] = Math::make_half_float(color[i]); + } else { + dst[i] = color[i]; + } } } } } -template <int CC> +template <int CC, class T> 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 { @@ -640,22 +649,58 @@ static void _scale_bilinear(const uint8_t *__restrict p_src, uint8_t *__restrict for (uint32_t l = 0; l < CC; l++) { - uint32_t p00 = p_src[y_ofs_up + src_xofs_left + l] << FRAC_BITS; - uint32_t p10 = p_src[y_ofs_up + src_xofs_right + l] << FRAC_BITS; - uint32_t p01 = p_src[y_ofs_down + src_xofs_left + l] << FRAC_BITS; - uint32_t p11 = p_src[y_ofs_down + src_xofs_right + l] << FRAC_BITS; - - uint32_t interp_up = p00 + (((p10 - p00) * src_xofs_frac) >> FRAC_BITS); - uint32_t interp_down = p01 + (((p11 - p01) * src_xofs_frac) >> FRAC_BITS); - uint32_t interp = interp_up + (((interp_down - interp_up) * src_yofs_frac) >> FRAC_BITS); - interp >>= FRAC_BITS; - p_dst[i * p_dst_width * CC + j * CC + l] = interp; + if (sizeof(T) == 1) { //uint8 + uint32_t p00 = p_src[y_ofs_up + src_xofs_left + l] << FRAC_BITS; + uint32_t p10 = p_src[y_ofs_up + src_xofs_right + l] << FRAC_BITS; + uint32_t p01 = p_src[y_ofs_down + src_xofs_left + l] << FRAC_BITS; + uint32_t p11 = p_src[y_ofs_down + src_xofs_right + l] << FRAC_BITS; + + uint32_t interp_up = p00 + (((p10 - p00) * src_xofs_frac) >> FRAC_BITS); + uint32_t interp_down = p01 + (((p11 - p01) * src_xofs_frac) >> FRAC_BITS); + uint32_t interp = interp_up + (((interp_down - interp_up) * src_yofs_frac) >> FRAC_BITS); + interp >>= FRAC_BITS; + p_dst[i * p_dst_width * CC + j * CC + l] = interp; + } else if (sizeof(T) == 2) { //half float + + float xofs_frac = float(src_xofs_frac) / (1 << FRAC_BITS); + float yofs_frac = float(src_yofs_frac) / (1 << FRAC_BITS); + const T *src = ((const T *)p_src); + T *dst = ((T *)p_dst); + + float p00 = Math::half_to_float(src[y_ofs_up + src_xofs_left + l]); + float p10 = Math::half_to_float(src[y_ofs_up + src_xofs_right + l]); + float p01 = Math::half_to_float(src[y_ofs_down + src_xofs_left + l]); + float p11 = Math::half_to_float(src[y_ofs_down + src_xofs_right + l]); + + float interp_up = p00 + (p10 - p00) * xofs_frac; + float interp_down = p01 + (p11 - p01) * xofs_frac; + float interp = interp_up + ((interp_down - interp_up) * yofs_frac); + + dst[i * p_dst_width * CC + j * CC + l] = Math::make_half_float(interp); + } else if (sizeof(T) == 4) { //float + + float xofs_frac = float(src_xofs_frac) / (1 << FRAC_BITS); + float yofs_frac = float(src_yofs_frac) / (1 << FRAC_BITS); + const T *src = ((const T *)p_src); + T *dst = ((T *)p_dst); + + float p00 = src[y_ofs_up + src_xofs_left + l]; + float p10 = src[y_ofs_up + src_xofs_right + l]; + float p01 = src[y_ofs_down + src_xofs_left + l]; + float p11 = src[y_ofs_down + src_xofs_right + l]; + + float interp_up = p00 + (p10 - p00) * xofs_frac; + float interp_down = p01 + (p11 - p01) * xofs_frac; + float interp = interp_up + ((interp_down - interp_up) * yofs_frac); + + dst[i * p_dst_width * CC + j * CC + l] = interp; + } } } } } -template <int CC> +template <int CC, class T> 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++) { @@ -670,8 +715,11 @@ static void _scale_nearest(const uint8_t *__restrict p_src, uint8_t *__restrict for (uint32_t l = 0; l < CC; l++) { - uint32_t p = p_src[y_ofs + src_xofs + l]; - p_dst[i * p_dst_width * CC + j * CC + l] = p; + const T *src = ((const T *)p_src); + T *dst = ((T *)p_dst); + + T p = src[y_ofs + src_xofs + l]; + dst[i * p_dst_width * CC + j * CC + l] = p; } } } @@ -732,9 +780,9 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { // Setup mipmap-aware scaling Image dst2; - int mip1; - int mip2; - float mip1_weight; + int mip1 = 0; + int mip2 = 0; + float mip1_weight = 0; if (mipmap_aware) { float avg_scale = ((float)p_width / width + (float)p_height / height) * 0.5f; if (avg_scale >= 1.0f) { @@ -750,6 +798,7 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { if (interpolate_mipmaps) { dst2.create(p_width, p_height, 0, format); } + bool had_mipmaps = mipmaps; if (interpolate_mipmaps && !had_mipmaps) { generate_mipmaps(); @@ -766,12 +815,30 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { case INTERPOLATE_NEAREST: { - switch (get_format_pixel_size(format)) { - case 1: _scale_nearest<1>(r_ptr, w_ptr, width, height, p_width, p_height); break; - case 2: _scale_nearest<2>(r_ptr, w_ptr, width, height, p_width, p_height); break; - case 3: _scale_nearest<3>(r_ptr, w_ptr, width, height, p_width, p_height); break; - case 4: _scale_nearest<4>(r_ptr, w_ptr, width, height, p_width, p_height); break; + if (format >= FORMAT_L8 && format <= FORMAT_RGBA8) { + switch (get_format_pixel_size(format)) { + case 1: _scale_nearest<1, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); break; + case 2: _scale_nearest<2, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); break; + case 3: _scale_nearest<3, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); break; + case 4: _scale_nearest<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_nearest<1, float>(r_ptr, w_ptr, width, height, p_width, p_height); break; + case 8: _scale_nearest<2, float>(r_ptr, w_ptr, width, height, p_width, p_height); break; + case 12: _scale_nearest<3, float>(r_ptr, w_ptr, width, height, p_width, p_height); break; + case 16: _scale_nearest<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_nearest<1, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); break; + case 4: _scale_nearest<2, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); break; + case 6: _scale_nearest<3, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); break; + case 8: _scale_nearest<4, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); break; + } } + } break; case INTERPOLATE_BILINEAR: case INTERPOLATE_TRILINEAR: { @@ -812,11 +879,27 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { } } - 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 (format >= FORMAT_L8 && format <= FORMAT_RGBA8) { + switch (get_format_pixel_size(format)) { + case 1: _scale_bilinear<1, uint8_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break; + case 2: _scale_bilinear<2, uint8_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break; + case 3: _scale_bilinear<3, uint8_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break; + case 4: _scale_bilinear<4, uint8_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break; + } + } else if (format >= FORMAT_RF && format <= FORMAT_RGBAF) { + switch (get_format_pixel_size(format)) { + case 4: _scale_bilinear<1, float>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break; + case 8: _scale_bilinear<2, float>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break; + case 12: _scale_bilinear<3, float>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break; + case 16: _scale_bilinear<4, float>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break; + } + } else if (format >= FORMAT_RH && format <= FORMAT_RGBAH) { + switch (get_format_pixel_size(format)) { + case 2: _scale_bilinear<1, uint16_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break; + case 4: _scale_bilinear<2, uint16_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break; + case 6: _scale_bilinear<3, uint16_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break; + case 8: _scale_bilinear<4, uint16_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); break; + } } } @@ -829,13 +912,28 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { } break; case INTERPOLATE_CUBIC: { - switch (get_format_pixel_size(format)) { - case 1: _scale_cubic<1>(r_ptr, w_ptr, width, height, p_width, p_height); break; - case 2: _scale_cubic<2>(r_ptr, w_ptr, width, height, p_width, p_height); break; - case 3: _scale_cubic<3>(r_ptr, w_ptr, width, height, p_width, p_height); break; - case 4: _scale_cubic<4>(r_ptr, w_ptr, width, height, p_width, p_height); break; + if (format >= FORMAT_L8 && format <= FORMAT_RGBA8) { + switch (get_format_pixel_size(format)) { + case 1: _scale_cubic<1, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); break; + case 2: _scale_cubic<2, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); break; + case 3: _scale_cubic<3, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); break; + case 4: _scale_cubic<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_cubic<1, float>(r_ptr, w_ptr, width, height, p_width, p_height); break; + case 8: _scale_cubic<2, float>(r_ptr, w_ptr, width, height, p_width, p_height); break; + case 12: _scale_cubic<3, float>(r_ptr, w_ptr, width, height, p_width, p_height); break; + case 16: _scale_cubic<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_cubic<1, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); break; + case 4: _scale_cubic<2, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); break; + case 6: _scale_cubic<3, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); break; + case 8: _scale_cubic<4, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); break; + } } - } break; } @@ -853,6 +951,7 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { } void Image::crop_from_point(int p_x, int p_y, int p_width, int p_height) { + if (!_can_modify(format)) { ERR_EXPLAIN("Cannot crop in indexed, compressed or custom image formats."); ERR_FAIL(); @@ -898,7 +997,7 @@ void Image::crop_from_point(int p_x, int p_y, int p_width, int p_height) { } } - if (mipmaps > 0) + if (has_mipmaps()) dst.generate_mipmaps(); _copy_internals_from(dst); } @@ -915,10 +1014,10 @@ void Image::flip_y() { ERR_FAIL(); } - bool gm = mipmaps; - - if (gm) + bool used_mipmaps = has_mipmaps(); + if (used_mipmaps) { clear_mipmaps(); + } { PoolVector<uint8_t>::Write w = data.write(); @@ -939,8 +1038,9 @@ void Image::flip_y() { } } - if (gm) + if (used_mipmaps) { generate_mipmaps(); + } } void Image::flip_x() { @@ -950,9 +1050,10 @@ void Image::flip_x() { ERR_FAIL(); } - bool gm = mipmaps; - if (gm) + bool used_mipmaps = has_mipmaps(); + if (used_mipmaps) { clear_mipmaps(); + } { PoolVector<uint8_t>::Write w = data.write(); @@ -973,8 +1074,9 @@ void Image::flip_x() { } } - if (gm) + if (used_mipmaps) { generate_mipmaps(); + } } int Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps) { @@ -987,8 +1089,10 @@ int Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int & int pixsize = get_format_pixel_size(p_format); int pixshift = get_format_pixel_rshift(p_format); int block = get_format_block_size(p_format); - int minw, minh; - get_format_min_pixel_size(p_format, minw, minh); + //technically, you can still compress up to 1 px no matter the format, so commenting this + //int minw, minh; + //get_format_min_pixel_size(p_format, minw, minh); + int minw = 1, minh = 1; while (true) { @@ -1027,48 +1131,38 @@ bool Image::_can_modify(Format p_format) const { return p_format <= FORMAT_RGBE9995; } -template <int CC, bool renormalize> -static void _generate_po2_mipmap(const uint8_t *p_src, uint8_t *p_dst, uint32_t p_width, uint32_t p_height) { +template <class Component, int CC, bool renormalize, + void (*average_func)(Component &, const Component &, const Component &, const Component &, const Component &), + void (*renormalize_func)(Component *)> +static void _generate_po2_mipmap(const Component *p_src, Component *p_dst, uint32_t p_width, uint32_t p_height) { //fast power of 2 mipmap generation - uint32_t dst_w = p_width >> 1; - uint32_t dst_h = p_height >> 1; + uint32_t dst_w = MAX(p_width >> 1, 1); + uint32_t dst_h = MAX(p_height >> 1, 1); + + int right_step = (p_width == 1) ? 0 : CC; + int down_step = (p_height == 1) ? 0 : (p_width * CC); for (uint32_t i = 0; i < dst_h; i++) { - const uint8_t *rup_ptr = &p_src[i * 2 * p_width * CC]; - const uint8_t *rdown_ptr = rup_ptr + p_width * CC; - uint8_t *dst_ptr = &p_dst[i * dst_w * CC]; + const Component *rup_ptr = &p_src[i * 2 * down_step]; + const Component *rdown_ptr = rup_ptr + down_step; + Component *dst_ptr = &p_dst[i * dst_w * CC]; uint32_t count = dst_w; while (count--) { for (int j = 0; j < CC; j++) { - - uint16_t val = 0; - val += rup_ptr[j]; - val += rup_ptr[j + CC]; - val += rdown_ptr[j]; - val += rdown_ptr[j + CC]; - dst_ptr[j] = val >> 2; + average_func(dst_ptr[j], rup_ptr[j], rup_ptr[j + right_step], rdown_ptr[j], rdown_ptr[j + right_step]); } if (renormalize) { - Vector3 n(dst_ptr[0] / 255.0, dst_ptr[1] / 255.0, dst_ptr[2] / 255.0); - n *= 2.0; - n -= Vector3(1, 1, 1); - n.normalize(); - n += Vector3(1, 1, 1); - n *= 0.5; - n *= 255; - dst_ptr[0] = CLAMP(int(n.x), 0, 255); - dst_ptr[1] = CLAMP(int(n.y), 0, 255); - dst_ptr[2] = CLAMP(int(n.z), 0, 255); + renormalize_func(dst_ptr); } dst_ptr += CC; - rup_ptr += CC * 2; - rdown_ptr += CC * 2; + rup_ptr += right_step * 2; + rdown_ptr += right_step * 2; } } } @@ -1077,12 +1171,13 @@ void Image::expand_x2_hq2x() { ERR_FAIL_COND(!_can_modify(format)); - Format current = format; - bool mm = has_mipmaps(); - if (mm) { + bool used_mipmaps = has_mipmaps(); + if (used_mipmaps) { clear_mipmaps(); } + Format current = format; + if (current != FORMAT_RGBA8) convert(FORMAT_RGBA8); @@ -1103,6 +1198,8 @@ void Image::expand_x2_hq2x() { if (current != FORMAT_RGBA8) convert(current); + // FIXME: This is likely meant to use "used_mipmaps" as defined above, but if we do, + // we end up with a regression: GH-22747 if (mipmaps) { generate_mipmaps(); } @@ -1148,11 +1245,23 @@ void Image::shrink_x2() { switch (format) { case FORMAT_L8: - case FORMAT_R8: _generate_po2_mipmap<1, false>(r.ptr(), w.ptr(), width, height); break; - case FORMAT_LA8: _generate_po2_mipmap<2, false>(r.ptr(), w.ptr(), width, height); break; - case FORMAT_RG8: _generate_po2_mipmap<2, false>(r.ptr(), w.ptr(), width, height); break; - case FORMAT_RGB8: _generate_po2_mipmap<3, false>(r.ptr(), w.ptr(), width, height); break; - case FORMAT_RGBA8: _generate_po2_mipmap<4, false>(r.ptr(), w.ptr(), width, height); break; + case FORMAT_R8: _generate_po2_mipmap<uint8_t, 1, false, Image::average_4_uint8, Image::renormalize_uint8>(r.ptr(), w.ptr(), width, height); break; + case FORMAT_LA8: _generate_po2_mipmap<uint8_t, 2, false, Image::average_4_uint8, Image::renormalize_uint8>(r.ptr(), w.ptr(), width, height); break; + case FORMAT_RG8: _generate_po2_mipmap<uint8_t, 2, false, Image::average_4_uint8, Image::renormalize_uint8>(r.ptr(), w.ptr(), width, height); break; + case FORMAT_RGB8: _generate_po2_mipmap<uint8_t, 3, false, Image::average_4_uint8, Image::renormalize_uint8>(r.ptr(), w.ptr(), width, height); break; + case FORMAT_RGBA8: _generate_po2_mipmap<uint8_t, 4, false, Image::average_4_uint8, Image::renormalize_uint8>(r.ptr(), w.ptr(), width, height); break; + + case FORMAT_RF: _generate_po2_mipmap<float, 1, false, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(r.ptr()), reinterpret_cast<float *>(w.ptr()), width, height); break; + case FORMAT_RGF: _generate_po2_mipmap<float, 2, false, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(r.ptr()), reinterpret_cast<float *>(w.ptr()), width, height); break; + case FORMAT_RGBF: _generate_po2_mipmap<float, 3, false, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(r.ptr()), reinterpret_cast<float *>(w.ptr()), width, height); break; + case FORMAT_RGBAF: _generate_po2_mipmap<float, 4, false, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(r.ptr()), reinterpret_cast<float *>(w.ptr()), width, height); break; + + case FORMAT_RH: _generate_po2_mipmap<uint16_t, 1, false, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(r.ptr()), reinterpret_cast<uint16_t *>(w.ptr()), width, height); break; + case FORMAT_RGH: _generate_po2_mipmap<uint16_t, 2, false, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(r.ptr()), reinterpret_cast<uint16_t *>(w.ptr()), width, height); break; + case FORMAT_RGBH: _generate_po2_mipmap<uint16_t, 3, false, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(r.ptr()), reinterpret_cast<uint16_t *>(w.ptr()), width, height); break; + case FORMAT_RGBAH: _generate_po2_mipmap<uint16_t, 4, false, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(r.ptr()), reinterpret_cast<uint16_t *>(w.ptr()), width, height); break; + + case FORMAT_RGBE9995: _generate_po2_mipmap<uint32_t, 1, false, Image::average_4_rgbe9995, Image::renormalize_rgbe9995>(reinterpret_cast<const uint32_t *>(r.ptr()), reinterpret_cast<uint32_t *>(w.ptr()), width, height); break; default: {} } } @@ -1214,7 +1323,7 @@ Error Image::generate_mipmaps(bool p_renormalize) { int prev_h = height; int prev_w = width; - for (int i = 1; i < mmcount; i++) { + for (int i = 1; i <= mmcount; i++) { int ofs, w, h; _get_mipmap_offset_and_size(i, ofs, w, h); @@ -1222,21 +1331,68 @@ Error Image::generate_mipmaps(bool p_renormalize) { switch (format) { case FORMAT_L8: - case FORMAT_R8: _generate_po2_mipmap<1, false>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); break; + case FORMAT_R8: _generate_po2_mipmap<uint8_t, 1, false, Image::average_4_uint8, Image::renormalize_uint8>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); break; case FORMAT_LA8: - case FORMAT_RG8: _generate_po2_mipmap<2, false>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); break; + case FORMAT_RG8: _generate_po2_mipmap<uint8_t, 2, false, Image::average_4_uint8, Image::renormalize_uint8>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); break; case FORMAT_RGB8: if (p_renormalize) - _generate_po2_mipmap<3, true>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + _generate_po2_mipmap<uint8_t, 3, true, Image::average_4_uint8, Image::renormalize_uint8>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); else - _generate_po2_mipmap<3, false>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + _generate_po2_mipmap<uint8_t, 3, false, Image::average_4_uint8, Image::renormalize_uint8>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); break; case FORMAT_RGBA8: if (p_renormalize) - _generate_po2_mipmap<4, true>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + _generate_po2_mipmap<uint8_t, 4, true, Image::average_4_uint8, Image::renormalize_uint8>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + else + _generate_po2_mipmap<uint8_t, 4, false, Image::average_4_uint8, Image::renormalize_uint8>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + break; + case FORMAT_RF: + _generate_po2_mipmap<float, 1, false, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(&wp[prev_ofs]), reinterpret_cast<float *>(&wp[ofs]), prev_w, prev_h); + break; + case FORMAT_RGF: + _generate_po2_mipmap<float, 2, false, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(&wp[prev_ofs]), reinterpret_cast<float *>(&wp[ofs]), prev_w, prev_h); + break; + case FORMAT_RGBF: + if (p_renormalize) + _generate_po2_mipmap<float, 3, true, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(&wp[prev_ofs]), reinterpret_cast<float *>(&wp[ofs]), prev_w, prev_h); + else + _generate_po2_mipmap<float, 3, false, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(&wp[prev_ofs]), reinterpret_cast<float *>(&wp[ofs]), prev_w, prev_h); + + break; + case FORMAT_RGBAF: + if (p_renormalize) + _generate_po2_mipmap<float, 4, true, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(&wp[prev_ofs]), reinterpret_cast<float *>(&wp[ofs]), prev_w, prev_h); + else + _generate_po2_mipmap<float, 4, false, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(&wp[prev_ofs]), reinterpret_cast<float *>(&wp[ofs]), prev_w, prev_h); + + break; + case FORMAT_RH: + _generate_po2_mipmap<uint16_t, 1, false, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(&wp[prev_ofs]), reinterpret_cast<uint16_t *>(&wp[ofs]), prev_w, prev_h); + break; + case FORMAT_RGH: + _generate_po2_mipmap<uint16_t, 2, false, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(&wp[prev_ofs]), reinterpret_cast<uint16_t *>(&wp[ofs]), prev_w, prev_h); + break; + case FORMAT_RGBH: + if (p_renormalize) + _generate_po2_mipmap<uint16_t, 3, true, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(&wp[prev_ofs]), reinterpret_cast<uint16_t *>(&wp[ofs]), prev_w, prev_h); else - _generate_po2_mipmap<4, false>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + _generate_po2_mipmap<uint16_t, 3, false, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(&wp[prev_ofs]), reinterpret_cast<uint16_t *>(&wp[ofs]), prev_w, prev_h); + + break; + case FORMAT_RGBAH: + if (p_renormalize) + _generate_po2_mipmap<uint16_t, 4, true, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(&wp[prev_ofs]), reinterpret_cast<uint16_t *>(&wp[ofs]), prev_w, prev_h); + else + _generate_po2_mipmap<uint16_t, 4, false, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(&wp[prev_ofs]), reinterpret_cast<uint16_t *>(&wp[ofs]), prev_w, prev_h); + + break; + case FORMAT_RGBE9995: + if (p_renormalize) + _generate_po2_mipmap<uint32_t, 1, true, Image::average_4_rgbe9995, Image::renormalize_rgbe9995>(reinterpret_cast<const uint32_t *>(&wp[prev_ofs]), reinterpret_cast<uint32_t *>(&wp[ofs]), prev_w, prev_h); + else + _generate_po2_mipmap<uint32_t, 1, false, Image::average_4_rgbe9995, Image::renormalize_rgbe9995>(reinterpret_cast<const uint32_t *>(&wp[prev_ofs]), reinterpret_cast<uint32_t *>(&wp[ofs]), prev_w, prev_h); + break; default: {} } @@ -1304,7 +1460,7 @@ void Image::create(int p_width, int p_height, bool p_use_mipmaps, Format p_forma int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); if (size != p_data.size()) { - ERR_EXPLAIN("Expected data size of " + itos(size) + " in Image::create()"); + ERR_EXPLAIN("Expected data size of " + itos(size) + " bytes in Image::create(), got instead " + itos(p_data.size()) + " bytes."); ERR_FAIL_COND(p_data.size() != size); } @@ -1317,7 +1473,8 @@ void Image::create(int p_width, int p_height, bool p_use_mipmaps, Format p_forma void Image::create(const char **p_xpm) { - int size_width, size_height; + int size_width = 0; + int size_height = 0; int pixelchars = 0; mipmaps = false; bool has_alpha = false; @@ -1333,8 +1490,8 @@ void Image::create(const char **p_xpm) { int line = 0; HashMap<String, Color> colormap; - int colormap_size; - uint32_t pixel_size; + int colormap_size = 0; + uint32_t pixel_size = 0; PoolVector<uint8_t>::Write w; while (status != DONE) { @@ -1580,7 +1737,11 @@ Image::AlphaMode Image::detect_alpha() const { } Error Image::load(const String &p_path) { - +#ifdef DEBUG_ENABLED + if (p_path.begins_with("res://") && ResourceLoader::exists(p_path)) { + WARN_PRINTS("Loaded resource as image file, this will not work on export: '" + p_path + "'. Instead, import the image file as an Image resource and load it normally as a resource."); + } +#endif return ImageLoader::load_image(p_path, this); } @@ -1592,10 +1753,10 @@ Error Image::save_png(const String &p_path) const { return save_png_func(p_path, Ref<Image>((Image *)this)); } -int Image::get_image_data_size(int p_width, int p_height, Format p_format, int p_mipmaps) { +int Image::get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps) { int mm; - return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmaps); + return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmaps ? -1 : 0); } int Image::get_image_required_mipmaps(int p_width, int p_height, Format p_format) { @@ -1605,14 +1766,25 @@ int Image::get_image_required_mipmaps(int p_width, int p_height, Format p_format return mm; } +int Image::get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap) { + + if (p_mipmap <= 0) { + return 0; + } + int mm; + return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmap - 1); +} + bool Image::is_compressed() const { return format > FORMAT_RGBE9995; } Error Image::decompress() { - if (format >= FORMAT_DXT1 && format <= FORMAT_BPTC_RGBFU && _image_decompress_bc) + if (format >= FORMAT_DXT1 && format <= FORMAT_RGTC_RG && _image_decompress_bc) _image_decompress_bc(this); + else if (format >= FORMAT_BPTC_RGBA && format <= FORMAT_BPTC_RGBFU && _image_decompress_bptc) + _image_decompress_bptc(this); else if (format >= FORMAT_PVRTC2 && format <= FORMAT_PVRTC4A && _image_decompress_pvrtc) _image_decompress_pvrtc(this); else if (format == FORMAT_ETC && _image_decompress_etc1) @@ -1631,7 +1803,7 @@ Error Image::compress(CompressMode p_mode, CompressSource p_source, float p_loss case COMPRESS_S3TC: { ERR_FAIL_COND_V(!_image_compress_bc_func, ERR_UNAVAILABLE); - _image_compress_bc_func(this, p_source); + _image_compress_bc_func(this, p_lossy_quality, p_source); } break; case COMPRESS_PVRTC2: { @@ -1653,6 +1825,11 @@ Error Image::compress(CompressMode p_mode, CompressSource p_source, float p_loss ERR_FAIL_COND_V(!_image_compress_etc2_func, ERR_UNAVAILABLE); _image_compress_etc2_func(this, p_lossy_quality, p_source); } break; + case COMPRESS_BPTC: { + + ERR_FAIL_COND_V(!_image_compress_bptc_func, ERR_UNAVAILABLE); + _image_compress_bptc_func(this, p_lossy_quality, p_source); + } break; } return OK; @@ -1754,7 +1931,8 @@ void Image::blit_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Po if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) return; - Rect2i dest_rect = Rect2i(0, 0, width, height).clip(Rect2i(p_dest, clipped_src_rect.size)); + Point2 src_underscan = Point2(MIN(0, p_src_rect.position.x), MIN(0, p_src_rect.position.y)); + Rect2i dest_rect = Rect2i(0, 0, width, height).clip(Rect2i(p_dest - src_underscan, clipped_src_rect.size)); PoolVector<uint8_t>::Write wp = data.write(); uint8_t *dst_data_ptr = wp.ptr(); @@ -1808,7 +1986,8 @@ void Image::blit_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, co if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) return; - Rect2i dest_rect = Rect2i(0, 0, width, height).clip(Rect2i(p_dest, clipped_src_rect.size)); + Point2 src_underscan = Point2(MIN(0, p_src_rect.position.x), MIN(0, p_src_rect.position.y)); + Rect2i dest_rect = Rect2i(0, 0, width, height).clip(Rect2i(p_dest - src_underscan, clipped_src_rect.size)); PoolVector<uint8_t>::Write wp = data.write(); uint8_t *dst_data_ptr = wp.ptr(); @@ -1865,7 +2044,8 @@ void Image::blend_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const P if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) return; - Rect2i dest_rect = Rect2i(0, 0, width, height).clip(Rect2i(p_dest, clipped_src_rect.size)); + Point2 src_underscan = Point2(MIN(0, p_src_rect.position.x), MIN(0, p_src_rect.position.y)); + Rect2i dest_rect = Rect2i(0, 0, width, height).clip(Rect2i(p_dest - src_underscan, clipped_src_rect.size)); lock(); Ref<Image> img = p_src; @@ -1919,7 +2099,8 @@ void Image::blend_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, c if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) return; - Rect2i dest_rect = Rect2i(0, 0, width, height).clip(Rect2i(p_dest, clipped_src_rect.size)); + Point2 src_underscan = Point2(MIN(0, p_src_rect.position.x), MIN(0, p_src_rect.position.y)); + Rect2i dest_rect = Rect2i(0, 0, width, height).clip(Rect2i(p_dest - src_underscan, clipped_src_rect.size)); lock(); Ref<Image> img = p_src; @@ -1989,13 +2170,15 @@ ImageMemLoadFunc Image::_png_mem_loader_func = NULL; ImageMemLoadFunc Image::_jpg_mem_loader_func = NULL; ImageMemLoadFunc Image::_webp_mem_loader_func = NULL; -void (*Image::_image_compress_bc_func)(Image *, Image::CompressSource) = NULL; +void (*Image::_image_compress_bc_func)(Image *, float, Image::CompressSource) = NULL; +void (*Image::_image_compress_bptc_func)(Image *, float, Image::CompressSource) = NULL; void (*Image::_image_compress_pvrtc2_func)(Image *) = NULL; void (*Image::_image_compress_pvrtc4_func)(Image *) = NULL; void (*Image::_image_compress_etc1_func)(Image *, float) = NULL; void (*Image::_image_compress_etc2_func)(Image *, float, Image::CompressSource) = NULL; void (*Image::_image_decompress_pvrtc)(Image *) = NULL; void (*Image::_image_decompress_bc)(Image *) = NULL; +void (*Image::_image_decompress_bptc)(Image *) = NULL; void (*Image::_image_decompress_etc1)(Image *) = NULL; void (*Image::_image_decompress_etc2)(Image *) = NULL; @@ -2179,18 +2362,7 @@ Color Image::get_pixel(int p_x, int p_y) const { return Color(Math::half_to_float(r), Math::half_to_float(g), Math::half_to_float(b), Math::half_to_float(a)); } break; case FORMAT_RGBE9995: { - uint32_t rgbe = ((uint32_t *)ptr)[ofs]; - float r = rgbe & 0x1ff; - float g = (rgbe >> 9) & 0x1ff; - float b = (rgbe >> 18) & 0x1ff; - float e = (rgbe >> 27); - float m = Math::pow(2, e - 15.0 - 9.0); - ; - float rd = r * m; - float gd = g * m; - float bd = b * m; - - return Color(rd, gd, bd, 1.0); + return Color::from_rgbe9995(((uint32_t *)ptr)[ofs]); } break; default: { @@ -2224,10 +2396,10 @@ void Image::set_pixel(int p_x, int p_y, const Color &p_color) { switch (format) { case FORMAT_L8: { - ptr[ofs] = uint8_t(CLAMP(p_color.gray() * 255.0, 0, 255)); + ptr[ofs] = uint8_t(CLAMP(p_color.get_v() * 255.0, 0, 255)); } break; case FORMAT_LA8: { - ptr[ofs * 2 + 0] = uint8_t(CLAMP(p_color.gray() * 255.0, 0, 255)); + ptr[ofs * 2 + 0] = uint8_t(CLAMP(p_color.get_v() * 255.0, 0, 255)); ptr[ofs * 2 + 1] = uint8_t(CLAMP(p_color.a * 255.0, 0, 255)); } break; case FORMAT_R8: { @@ -2376,6 +2548,17 @@ Image::DetectChannels Image::get_detected_channels() { return DETECTED_RGBA; } +void Image::optimize_channels() { + switch (get_detected_channels()) { + case DETECTED_L: convert(FORMAT_L8); break; + case DETECTED_LA: convert(FORMAT_LA8); break; + case DETECTED_R: convert(FORMAT_R8); break; + case DETECTED_RG: convert(FORMAT_RG8); break; + case DETECTED_RGB: convert(FORMAT_RGB8); break; + case DETECTED_RGBA: convert(FORMAT_RGBA8); break; + } +} + void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("get_width"), &Image::get_width); @@ -2508,11 +2691,16 @@ void Image::_bind_methods() { BIND_ENUM_CONSTANT(COMPRESS_SOURCE_NORMAL); } -void Image::set_compress_bc_func(void (*p_compress_func)(Image *, CompressSource)) { +void Image::set_compress_bc_func(void (*p_compress_func)(Image *, float, CompressSource)) { _image_compress_bc_func = p_compress_func; } +void Image::set_compress_bptc_func(void (*p_compress_func)(Image *, float, CompressSource)) { + + _image_compress_bptc_func = p_compress_func; +} + void Image::normalmap_to_xy() { convert(Image::FORMAT_RGBA8); @@ -2766,6 +2954,55 @@ Error Image::_load_from_buffer(const PoolVector<uint8_t> &p_array, ImageMemLoadF return OK; } +void Image::average_4_uint8(uint8_t &p_out, const uint8_t &p_a, const uint8_t &p_b, const uint8_t &p_c, const uint8_t &p_d) { + p_out = static_cast<uint8_t>((p_a + p_b + p_c + p_d + 2) >> 2); +} + +void Image::average_4_float(float &p_out, const float &p_a, const float &p_b, const float &p_c, const float &p_d) { + p_out = (p_a + p_b + p_c + p_d) * 0.25f; +} + +void Image::average_4_half(uint16_t &p_out, const uint16_t &p_a, const uint16_t &p_b, const uint16_t &p_c, const uint16_t &p_d) { + p_out = Math::make_half_float((Math::half_to_float(p_a) + Math::half_to_float(p_b) + Math::half_to_float(p_c) + Math::half_to_float(p_d)) * 0.25f); +} + +void Image::average_4_rgbe9995(uint32_t &p_out, const uint32_t &p_a, const uint32_t &p_b, const uint32_t &p_c, const uint32_t &p_d) { + p_out = ((Color::from_rgbe9995(p_a) + Color::from_rgbe9995(p_b) + Color::from_rgbe9995(p_c) + Color::from_rgbe9995(p_d)) * 0.25f).to_rgbe9995(); +} + +void Image::renormalize_uint8(uint8_t *p_rgb) { + Vector3 n(p_rgb[0] / 255.0, p_rgb[1] / 255.0, p_rgb[2] / 255.0); + n *= 2.0; + n -= Vector3(1, 1, 1); + n.normalize(); + n += Vector3(1, 1, 1); + n *= 0.5; + n *= 255; + p_rgb[0] = CLAMP(int(n.x), 0, 255); + p_rgb[1] = CLAMP(int(n.y), 0, 255); + p_rgb[2] = CLAMP(int(n.z), 0, 255); +} + +void Image::renormalize_float(float *p_rgb) { + Vector3 n(p_rgb[0], p_rgb[1], p_rgb[2]); + n.normalize(); + p_rgb[0] = n.x; + p_rgb[1] = n.y; + p_rgb[2] = n.z; +} + +void Image::renormalize_half(uint16_t *p_rgb) { + Vector3 n(Math::half_to_float(p_rgb[0]), Math::half_to_float(p_rgb[1]), Math::half_to_float(p_rgb[2])); + n.normalize(); + p_rgb[0] = Math::make_half_float(n.x); + p_rgb[1] = Math::make_half_float(n.y); + p_rgb[2] = Math::make_half_float(n.z); +} + +void Image::renormalize_rgbe9995(uint32_t *p_rgb) { + // Never used +} + Image::Image(const uint8_t *p_mem_png_jpg, int p_len) { width = 0; |