diff options
Diffstat (limited to 'core/image.cpp')
-rw-r--r-- | core/image.cpp | 445 |
1 files changed, 299 insertions, 146 deletions
diff --git a/core/image.cpp b/core/image.cpp index c0002e0cd6..e0b0a1f8be 100644 --- a/core/image.cpp +++ b/core/image.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -83,6 +83,7 @@ const char *Image::format_names[Image::FORMAT_MAX] = { }; SavePNGFunc Image::save_png_func = NULL; +SaveEXRFunc Image::save_exr_func = NULL; void Image::_put_pixelb(int p_x, int p_y, uint32_t p_pixelsize, uint8_t *p_data, const uint8_t *p_pixel) { @@ -240,27 +241,27 @@ int Image::get_format_block_size(Format p_format) { case FORMAT_RGTC_RG: { //bc5 case case FORMAT_DXT1: return 4; - } break; + } case FORMAT_PVRTC2: case FORMAT_PVRTC2A: { return 4; - } break; + } case FORMAT_PVRTC4A: case FORMAT_PVRTC4: { return 4; - } break; + } case FORMAT_ETC: { return 4; - } break; + } case FORMAT_BPTC_RGBA: case FORMAT_BPTC_RGBF: case FORMAT_BPTC_RGBFU: { return 4; - } break; + } case FORMAT_ETC2_R11: //etc2 case FORMAT_ETC2_R11S: //signed: NOT srgb. case FORMAT_ETC2_RG11: @@ -270,7 +271,7 @@ int Image::get_format_block_size(Format p_format) { case FORMAT_ETC2_RGB8A1: { return 4; - } break; + } default: { } } @@ -307,6 +308,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); @@ -421,8 +423,7 @@ void Image::convert(Format p_new_format) { if (format > FORMAT_RGBE9995 || p_new_format > FORMAT_RGBE9995) { - ERR_EXPLAIN("Cannot convert to <-> from compressed formats. Use compress() and decompress() instead."); - ERR_FAIL(); + ERR_FAIL_MSG("Cannot convert to <-> from compressed formats. Use compress() and decompress() instead."); } else if (format > FORMAT_RGBA8 || p_new_format > FORMAT_RGBA8) { @@ -494,13 +495,11 @@ void Image::convert(Format p_new_format) { case FORMAT_RGBA8 | (FORMAT_RGB8 << 8): _convert<3, true, 3, false, false, false>(width, height, rptr, wptr); break; } - r = PoolVector<uint8_t>::Read(); - w = PoolVector<uint8_t>::Write(); + r.release(); + w.release(); bool gen_mipmaps = mipmaps; - //mipmaps=false; - _copy_internals_from(new_img); if (gen_mipmaps) @@ -726,9 +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); + + for (int32_t buffer_x = 0; buffer_x < dst_width; buffer_x++) { + + // The corresponding point on the source image + float src_x = (buffer_x + 0.5f) * x_scale; // Offset by 0.5 so it uses the pixel's center + int32_t start_x = MAX(0, int32_t(src_x) - half_kernel + 1); + int32_t end_x = MIN(src_width - 1, int32_t(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((target_x + 0.5f - src_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); + + for (int32_t dst_y = 0; dst_y < dst_height; dst_y++) { + + float buffer_y = (dst_y + 0.5f) * y_scale; + int32_t start_y = MAX(0, int32_t(buffer_y) - half_kernel + 1); + int32_t end_y = MIN(src_height - 1, int32_t(buffer_y) + half_kernel); + + for (int32_t target_y = start_y; target_y <= end_y; target_y++) + kernel[target_y - start_y] = _lanczos((target_y + 0.5f - buffer_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); + uint16_t alpha = MIN((uint16_t)(p_alpha * 256.0f), 256); for (uint32_t i = 0; i < p_width * p_height * p_pixel_size; i++) { @@ -736,12 +857,13 @@ static void _overlay(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, } } +bool Image::is_size_po2() const { + return uint32_t(width) == next_power_of_2(width) && uint32_t(height) == next_power_of_2(height); +} + void Image::resize_to_po2(bool p_square) { - if (!_can_modify(format)) { - ERR_EXPLAIN("Cannot resize in indexed, compressed or custom image formats."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot resize in compressed or custom image formats."); int w = next_power_of_2(width); int h = next_power_of_2(height); @@ -757,22 +879,16 @@ void Image::resize_to_po2(bool p_square) { void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { - if (data.size() == 0) { - ERR_EXPLAIN("Cannot resize image before creating it, use create() or create_from_data() first."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(data.size() == 0, "Cannot resize image before creating it, use create() or create_from_data() first."); - if (!_can_modify(format)) { - ERR_EXPLAIN("Cannot resize in indexed, compressed or custom image formats."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot resize in compressed or custom image formats."); 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); - ERR_FAIL_COND(p_height > MAX_HEIGHT); + ERR_FAIL_COND_MSG(p_width <= 0, "Image width cannot be greater than 0."); + ERR_FAIL_COND_MSG(p_height <= 0, "Image height cannot be greater than 0."); + ERR_FAIL_COND_MSG(p_width > MAX_WIDTH, "Image width cannot be greater than " + itos(MAX_WIDTH) + "."); + ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT, "Image height cannot be greater than " + itos(MAX_HEIGHT) + "."); if (p_width == width && p_height == height) return; @@ -781,9 +897,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) { @@ -799,6 +915,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(); @@ -935,10 +1052,35 @@ 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(); - w = PoolVector<uint8_t>::Write(); + r.release(); + w.release(); if (interpolate_mipmaps) { dst._copy_internals_from(dst2); @@ -951,16 +1093,15 @@ 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(); - } - ERR_FAIL_COND(p_x < 0); - ERR_FAIL_COND(p_y < 0); - ERR_FAIL_COND(p_width <= 0); - ERR_FAIL_COND(p_height <= 0); - ERR_FAIL_COND(p_x + p_width > MAX_WIDTH); - ERR_FAIL_COND(p_y + p_height > MAX_HEIGHT); + + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot crop in compressed or custom image formats."); + + ERR_FAIL_COND_MSG(p_x < 0, "Start x position cannot be smaller than 0."); + ERR_FAIL_COND_MSG(p_y < 0, "Start y position cannot be smaller than 0."); + ERR_FAIL_COND_MSG(p_width <= 0, "Width of image must be greater than 0."); + ERR_FAIL_COND_MSG(p_height <= 0, "Height of image must be greater than 0."); + ERR_FAIL_COND_MSG(p_x + p_width > MAX_WIDTH, "End x position cannot be greater than " + itos(MAX_WIDTH) + "."); + ERR_FAIL_COND_MSG(p_y + p_height > MAX_HEIGHT, "End y position cannot be greater than " + itos(MAX_HEIGHT) + "."); /* to save memory, cropping should be done in-place, however, since this function will most likely either not be used much, or in critical areas, for now it won't, because @@ -996,7 +1137,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); } @@ -1008,15 +1149,12 @@ void Image::crop(int p_width, int p_height) { void Image::flip_y() { - if (!_can_modify(format)) { - ERR_EXPLAIN("Cannot flip_y in indexed, compressed or custom image formats."); - ERR_FAIL(); - } - - bool gm = mipmaps; + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot flip_y in compressed or custom image formats."); - if (gm) + bool used_mipmaps = has_mipmaps(); + if (used_mipmaps) { clear_mipmaps(); + } { PoolVector<uint8_t>::Write w = data.write(); @@ -1037,20 +1175,19 @@ void Image::flip_y() { } } - if (gm) + if (used_mipmaps) { generate_mipmaps(); + } } void Image::flip_x() { - if (!_can_modify(format)) { - ERR_EXPLAIN("Cannot flip_x in indexed, compressed or custom image formats."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot flip_x in compressed or custom image formats."); - bool gm = mipmaps; - if (gm) + bool used_mipmaps = has_mipmaps(); + if (used_mipmaps) { clear_mipmaps(); + } { PoolVector<uint8_t>::Write w = data.write(); @@ -1071,8 +1208,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) { @@ -1167,12 +1305,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); @@ -1193,6 +1332,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(); } @@ -1211,6 +1352,7 @@ void Image::shrink_x2() { int new_size = data.size() - ofs; new_img.resize(new_size); + ERR_FAIL_COND(new_img.size() == 0); { PoolVector<uint8_t>::Write w = new_img.write(); @@ -1230,6 +1372,7 @@ void Image::shrink_x2() { ERR_FAIL_COND(!_can_modify(format)); int ps = get_format_pixel_size(format); new_img.resize((width / 2) * (height / 2) * ps); + ERR_FAIL_COND(new_img.size() == 0); { PoolVector<uint8_t>::Write w = new_img.write(); @@ -1255,7 +1398,8 @@ void Image::shrink_x2() { 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: {} + default: { + } } } @@ -1297,12 +1441,9 @@ void Image::normalize() { Error Image::generate_mipmaps(bool p_renormalize) { - if (!_can_modify(format)) { - ERR_EXPLAIN("Cannot generate mipmaps in indexed, compressed or custom image formats."); - ERR_FAIL_V(ERR_UNAVAILABLE); - } + ERR_FAIL_COND_V_MSG(!_can_modify(format), ERR_UNAVAILABLE, "Cannot generate mipmaps in compressed or custom image formats."); - ERR_FAIL_COND_V(width == 0 || height == 0, ERR_UNCONFIGURED); + ERR_FAIL_COND_V_MSG(width == 0 || height == 0, ERR_UNCONFIGURED, "Cannot generate mipmaps with width or height equal to 0."); int mmcount; @@ -1387,7 +1528,8 @@ Error Image::generate_mipmaps(bool p_renormalize) { _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: {} + default: { + } } prev_ofs = ofs; @@ -1452,10 +1594,7 @@ void Image::create(int p_width, int p_height, bool p_use_mipmaps, Format p_forma int mm; 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) + " bytes in Image::create(), got instead " + itos(p_data.size()) + " bytes."); - ERR_FAIL_COND(p_data.size() != size); - } + ERR_FAIL_COND_MSG(p_data.size() != size, "Expected data size of " + itos(size) + " bytes in Image::create(), got instead " + itos(p_data.size()) + " bytes."); height = p_height; width = p_width; @@ -1466,7 +1605,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; @@ -1482,8 +1622,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) { @@ -1600,7 +1740,8 @@ void Image::create(const char **p_xpm) { if (y == (size_height - 1)) status = DONE; } break; - default: {} + default: { + } } line++; @@ -1673,7 +1814,8 @@ bool Image::is_invisible() const { case FORMAT_DXT5: { detected = true; } break; - default: {} + default: { + } } return !detected; @@ -1717,7 +1859,8 @@ Image::AlphaMode Image::detect_alpha() const { case FORMAT_DXT5: { detected = true; } break; - default: {} + default: { + } } if (detected) @@ -1745,6 +1888,14 @@ Error Image::save_png(const String &p_path) const { return save_png_func(p_path, Ref<Image>((Image *)this)); } +Error Image::save_exr(const String &p_path, bool p_grayscale) const { + + if (save_exr_func == NULL) + return ERR_UNAVAILABLE; + + return save_exr_func(p_path, Ref<Image>((Image *)this), p_grayscale); +} + int Image::get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps) { int mm; @@ -1758,6 +1909,15 @@ 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; } @@ -1772,7 +1932,7 @@ Error Image::decompress() { _image_decompress_pvrtc(this); else if (format == FORMAT_ETC && _image_decompress_etc1) _image_decompress_etc1(this); - else if (format >= FORMAT_ETC2_R11 && format <= FORMAT_ETC2_RGB8A1 && _image_decompress_etc1) + else if (format >= FORMAT_ETC2_R11 && format <= FORMAT_ETC2_RGB8A1 && _image_decompress_etc2) _image_decompress_etc2(this); else return ERR_UNAVAILABLE; @@ -1850,7 +2010,7 @@ Image::Image(int p_width, int p_height, bool p_mipmaps, Format p_format, const P Rect2 Image::get_used_rect() const { - if (format != FORMAT_LA8 && format != FORMAT_RGBA8) + if (format != FORMAT_LA8 && format != FORMAT_RGBA8 && format != FORMAT_RGBAF && format != FORMAT_RGBAH && format != FORMAT_RGBA4444 && format != FORMAT_RGBA5551) return Rect2(Point2(), Size2(width, height)); int len = data.size(); @@ -1858,17 +2018,13 @@ Rect2 Image::get_used_rect() const { if (len == 0) return Rect2(); - //int data_size = len; - PoolVector<uint8_t>::Read r = data.read(); - const unsigned char *rptr = r.ptr(); - - int ps = format == FORMAT_LA8 ? 2 : 4; + const_cast<Image *>(this)->lock(); int minx = 0xFFFFFF, miny = 0xFFFFFFF; int maxx = -1, maxy = -1; for (int j = 0; j < height; j++) { for (int i = 0; i < width; i++) { - bool opaque = rptr[(j * width + i) * ps + (ps - 1)] > 2; + bool opaque = get_pixel(i, j).a > 0.99; if (!opaque) continue; if (i > maxx) @@ -1882,6 +2038,8 @@ Rect2 Image::get_used_rect() const { } } + const_cast<Image *>(this)->unlock(); + if (maxx == -1) return Rect2(); else @@ -1897,7 +2055,7 @@ Ref<Image> Image::get_rect(const Rect2 &p_area) const { void Image::blit_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Point2 &p_dest) { - ERR_FAIL_COND(p_src.is_null()); + ERR_FAIL_COND_MSG(p_src.is_null(), "It's not a reference to a valid Image object."); int dsize = data.size(); int srcdsize = p_src->data.size(); ERR_FAIL_COND(dsize == 0); @@ -1914,7 +2072,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(); @@ -1946,16 +2105,16 @@ void Image::blit_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Po void Image::blit_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, const Rect2 &p_src_rect, const Point2 &p_dest) { - ERR_FAIL_COND(p_src.is_null()); - ERR_FAIL_COND(p_mask.is_null()); + ERR_FAIL_COND_MSG(p_src.is_null(), "It's not a reference to a valid Image object."); + ERR_FAIL_COND_MSG(p_mask.is_null(), "It's not a reference to a valid Image object."); int dsize = data.size(); int srcdsize = p_src->data.size(); int maskdsize = p_mask->data.size(); ERR_FAIL_COND(dsize == 0); ERR_FAIL_COND(srcdsize == 0); ERR_FAIL_COND(maskdsize == 0); - ERR_FAIL_COND(p_src->width != p_mask->width); - ERR_FAIL_COND(p_src->height != p_mask->height); + ERR_FAIL_COND_MSG(p_src->width != p_mask->width, "Source image width is different from mask width."); + ERR_FAIL_COND_MSG(p_src->height != p_mask->height, "Source image height is different from mask height."); ERR_FAIL_COND(format != p_src->format); Rect2i clipped_src_rect = Rect2i(0, 0, p_src->width, p_src->height).clip(p_src_rect); @@ -1968,7 +2127,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(); @@ -2008,7 +2168,7 @@ void Image::blit_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, co void Image::blend_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Point2 &p_dest) { - ERR_FAIL_COND(p_src.is_null()); + ERR_FAIL_COND_MSG(p_src.is_null(), "It's not a reference to a valid Image object."); int dsize = data.size(); int srcdsize = p_src->data.size(); ERR_FAIL_COND(dsize == 0); @@ -2025,7 +2185,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; @@ -2057,16 +2218,16 @@ void Image::blend_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const P void Image::blend_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, const Rect2 &p_src_rect, const Point2 &p_dest) { - ERR_FAIL_COND(p_src.is_null()); - ERR_FAIL_COND(p_mask.is_null()); + ERR_FAIL_COND_MSG(p_src.is_null(), "It's not a reference to a valid Image object."); + ERR_FAIL_COND_MSG(p_mask.is_null(), "It's not a reference to a valid Image object."); int dsize = data.size(); int srcdsize = p_src->data.size(); int maskdsize = p_mask->data.size(); ERR_FAIL_COND(dsize == 0); ERR_FAIL_COND(srcdsize == 0); ERR_FAIL_COND(maskdsize == 0); - ERR_FAIL_COND(p_src->width != p_mask->width); - ERR_FAIL_COND(p_src->height != p_mask->height); + ERR_FAIL_COND_MSG(p_src->width != p_mask->width, "Source image width is different from mask width."); + ERR_FAIL_COND_MSG(p_src->height != p_mask->height, "Source image height is different from mask height."); ERR_FAIL_COND(format != p_src->format); Rect2i clipped_src_rect = Rect2i(0, 0, p_src->width, p_src->height).clip(p_src_rect); @@ -2079,7 +2240,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; @@ -2211,7 +2373,7 @@ void Image::lock() { void Image::unlock() { - write_lock = PoolVector<uint8_t>::Write(); + write_lock.release(); } Color Image::get_pixelv(const Point2 &p_src) const { @@ -2222,10 +2384,7 @@ Color Image::get_pixel(int p_x, int p_y) const { uint8_t *ptr = write_lock.ptr(); #ifdef DEBUG_ENABLED - if (!ptr) { - ERR_EXPLAIN("Image must be locked with 'lock()' before using get_pixel()"); - ERR_FAIL_COND_V(!ptr, Color()); - } + ERR_FAIL_COND_V_MSG(!ptr, Color(), "Image must be locked with 'lock()' before using get_pixel()."); ERR_FAIL_INDEX_V(p_x, width, Color()); ERR_FAIL_INDEX_V(p_y, height, Color()); @@ -2238,38 +2397,36 @@ Color Image::get_pixel(int p_x, int p_y) const { case FORMAT_L8: { float l = ptr[ofs] / 255.0; return Color(l, l, l, 1); - } break; + } case FORMAT_LA8: { float l = ptr[ofs * 2 + 0] / 255.0; float a = ptr[ofs * 2 + 1] / 255.0; return Color(l, l, l, a); - } break; + } case FORMAT_R8: { float r = ptr[ofs] / 255.0; return Color(r, 0, 0, 1); - } break; + } case FORMAT_RG8: { float r = ptr[ofs * 2 + 0] / 255.0; float g = ptr[ofs * 2 + 1] / 255.0; return Color(r, g, 0, 1); - } break; + } case FORMAT_RGB8: { float r = ptr[ofs * 3 + 0] / 255.0; float g = ptr[ofs * 3 + 1] / 255.0; float b = ptr[ofs * 3 + 2] / 255.0; return Color(r, g, b, 1); - - } break; + } case FORMAT_RGBA8: { float r = ptr[ofs * 4 + 0] / 255.0; float g = ptr[ofs * 4 + 1] / 255.0; float b = ptr[ofs * 4 + 2] / 255.0; float a = ptr[ofs * 4 + 3] / 255.0; return Color(r, g, b, a); - - } break; + } case FORMAT_RGBA4444: { uint16_t u = ((uint16_t *)ptr)[ofs]; float r = (u & 0xF) / 15.0; @@ -2277,8 +2434,7 @@ Color Image::get_pixel(int p_x, int p_y) const { float b = ((u >> 8) & 0xF) / 15.0; float a = ((u >> 12) & 0xF) / 15.0; return Color(r, g, b, a); - - } break; + } case FORMAT_RGBA5551: { uint16_t u = ((uint16_t *)ptr)[ofs]; @@ -2287,25 +2443,25 @@ Color Image::get_pixel(int p_x, int p_y) const { float b = ((u >> 10) & 0x1F) / 15.0; float a = ((u >> 15) & 0x1) / 1.0; return Color(r, g, b, a); - } break; + } case FORMAT_RF: { float r = ((float *)ptr)[ofs]; return Color(r, 0, 0, 1); - } break; + } case FORMAT_RGF: { float r = ((float *)ptr)[ofs * 2 + 0]; float g = ((float *)ptr)[ofs * 2 + 1]; return Color(r, g, 0, 1); - } break; + } case FORMAT_RGBF: { float r = ((float *)ptr)[ofs * 3 + 0]; float g = ((float *)ptr)[ofs * 3 + 1]; float b = ((float *)ptr)[ofs * 3 + 2]; return Color(r, g, b, 1); - } break; + } case FORMAT_RGBAF: { float r = ((float *)ptr)[ofs * 4 + 0]; @@ -2313,25 +2469,25 @@ Color Image::get_pixel(int p_x, int p_y) const { float b = ((float *)ptr)[ofs * 4 + 2]; float a = ((float *)ptr)[ofs * 4 + 3]; return Color(r, g, b, a); - } break; + } case FORMAT_RH: { uint16_t r = ((uint16_t *)ptr)[ofs]; return Color(Math::half_to_float(r), 0, 0, 1); - } break; + } case FORMAT_RGH: { uint16_t r = ((uint16_t *)ptr)[ofs * 2 + 0]; uint16_t g = ((uint16_t *)ptr)[ofs * 2 + 1]; return Color(Math::half_to_float(r), Math::half_to_float(g), 0, 1); - } break; + } case FORMAT_RGBH: { uint16_t r = ((uint16_t *)ptr)[ofs * 3 + 0]; uint16_t g = ((uint16_t *)ptr)[ofs * 3 + 1]; uint16_t b = ((uint16_t *)ptr)[ofs * 3 + 2]; return Color(Math::half_to_float(r), Math::half_to_float(g), Math::half_to_float(b), 1); - } break; + } case FORMAT_RGBAH: { uint16_t r = ((uint16_t *)ptr)[ofs * 4 + 0]; @@ -2339,32 +2495,25 @@ Color Image::get_pixel(int p_x, int p_y) const { uint16_t b = ((uint16_t *)ptr)[ofs * 4 + 2]; uint16_t a = ((uint16_t *)ptr)[ofs * 4 + 3]; 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: { return Color::from_rgbe9995(((uint32_t *)ptr)[ofs]); - - } break; + } default: { - ERR_EXPLAIN("Can't get_pixel() on compressed image, sorry."); - ERR_FAIL_V(Color()); + ERR_FAIL_V_MSG(Color(), "Can't get_pixel() on compressed image, sorry."); } } - - return Color(); } void Image::set_pixelv(const Point2 &p_dst, const Color &p_color) { - return set_pixel(p_dst.x, p_dst.y, p_color); + set_pixel(p_dst.x, p_dst.y, p_color); } void Image::set_pixel(int p_x, int p_y, const Color &p_color) { uint8_t *ptr = write_lock.ptr(); #ifdef DEBUG_ENABLED - if (!ptr) { - ERR_EXPLAIN("Image must be locked with 'lock()' before using set_pixel()"); - ERR_FAIL_COND(!ptr); - } + ERR_FAIL_COND_MSG(!ptr, "Image must be locked with 'lock()' before using set_pixel()."); ERR_FAIL_INDEX(p_x, width); ERR_FAIL_INDEX(p_y, height); @@ -2476,8 +2625,7 @@ void Image::set_pixel(int p_x, int p_y, const Color &p_color) { } break; default: { - ERR_EXPLAIN("Can't set_pixel() on compressed image, sorry."); - ERR_FAIL(); + ERR_FAIL_MSG("Can't set_pixel() on compressed image, sorry."); } } } @@ -2569,6 +2717,7 @@ void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("load", "path"), &Image::load); ClassDB::bind_method(D_METHOD("save_png", "path"), &Image::save_png); + ClassDB::bind_method(D_METHOD("save_exr", "path", "grayscale"), &Image::save_exr, DEFVAL(false)); ClassDB::bind_method(D_METHOD("detect_alpha"), &Image::detect_alpha); ClassDB::bind_method(D_METHOD("is_invisible"), &Image::is_invisible); @@ -2611,6 +2760,9 @@ void Image::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "_set_data", "_get_data"); + BIND_CONSTANT(MAX_WIDTH); + BIND_CONSTANT(MAX_HEIGHT); + BIND_ENUM_CONSTANT(FORMAT_L8); //luminance BIND_ENUM_CONSTANT(FORMAT_LA8); //luminance-alpha BIND_ENUM_CONSTANT(FORMAT_R8); @@ -2654,6 +2806,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); @@ -2877,15 +3030,15 @@ void Image::fix_alpha_edges() { if (dist >= closest_dist) continue; - const uint8_t *rp = &srcptr[(k * width + l) << 2]; + const uint8_t *rp2 = &srcptr[(k * width + l) << 2]; - if (rp[3] < alpha_threshold) + if (rp2[3] < alpha_threshold) continue; closest_dist = dist; - closest_color[0] = rp[0]; - closest_color[1] = rp[1]; - closest_color[2] = rp[2]; + closest_color[0] = rp2[0]; + closest_color[1] = rp2[1]; + closest_color[2] = rp2[2]; } } |