summaryrefslogtreecommitdiff
path: root/core/image.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'core/image.cpp')
-rw-r--r--core/image.cpp200
1 files changed, 122 insertions, 78 deletions
diff --git a/core/image.cpp b/core/image.cpp
index 29be2ff5eb..b8a443eed2 100644
--- a/core/image.cpp
+++ b/core/image.cpp
@@ -30,6 +30,7 @@
#include "image.h"
+#include "core/error_macros.h"
#include "core/hash_map.h"
#include "core/io/image_loader.h"
#include "core/io/resource_loader.h"
@@ -37,8 +38,6 @@
#include "core/os/copymem.h"
#include "core/print_string.h"
-#include "thirdparty/misc/hq2x.h"
-
#include <stdio.h>
const char *Image::format_names[Image::FORMAT_MAX] = {
@@ -364,6 +363,82 @@ void Image::get_mipmap_offset_size_and_dimensions(int p_mipmap, int &r_ofs, int
r_size = ofs2 - ofs;
}
+Image::Image3DValidateError Image::validate_3d_image(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_images) {
+ int w = p_width;
+ int h = p_height;
+ int d = p_depth;
+
+ int arr_ofs = 0;
+
+ while (true) {
+ for (int i = 0; i < d; i++) {
+ int idx = i + arr_ofs;
+ if (idx >= p_images.size()) {
+ return VALIDATE_3D_ERR_MISSING_IMAGES;
+ }
+ if (p_images[idx].is_null() || p_images[idx]->empty()) {
+ return VALIDATE_3D_ERR_IMAGE_EMPTY;
+ }
+ if (p_images[idx]->get_format() != p_format) {
+ return VALIDATE_3D_ERR_IMAGE_FORMAT_MISMATCH;
+ }
+ if (p_images[idx]->get_width() != w || p_images[idx]->get_height() != h) {
+ return VALIDATE_3D_ERR_IMAGE_SIZE_MISMATCH;
+ }
+ if (p_images[idx]->has_mipmaps()) {
+ return VALIDATE_3D_ERR_IMAGE_HAS_MIPMAPS;
+ }
+ }
+
+ arr_ofs += d;
+
+ if (!p_mipmaps) {
+ break;
+ }
+
+ if (w == 1 && h == 1 && d == 1) {
+ break;
+ }
+
+ w = MAX(1, w >> 1);
+ h = MAX(1, h >> 1);
+ d = MAX(1, d >> 1);
+ }
+
+ if (arr_ofs != p_images.size()) {
+ return VALIDATE_3D_ERR_EXTRA_IMAGES;
+ }
+
+ return VALIDATE_3D_OK;
+}
+
+String Image::get_3d_image_validation_error_text(Image3DValidateError p_error) {
+ switch (p_error) {
+ case VALIDATE_3D_OK: {
+ return TTR("Ok");
+ } break;
+ case VALIDATE_3D_ERR_IMAGE_EMPTY: {
+ return TTR("Empty Image found");
+ } break;
+ case VALIDATE_3D_ERR_MISSING_IMAGES: {
+ return TTR("Missing Images");
+ } break;
+ case VALIDATE_3D_ERR_EXTRA_IMAGES: {
+ return TTR("Too many Images");
+ } break;
+ case VALIDATE_3D_ERR_IMAGE_SIZE_MISMATCH: {
+ return TTR("Image size mismatch");
+ } break;
+ case VALIDATE_3D_ERR_IMAGE_FORMAT_MISMATCH: {
+ return TTR("Image format mismatch");
+ } break;
+ case VALIDATE_3D_ERR_IMAGE_HAS_MIPMAPS: {
+ return TTR("Image has included mipmaps");
+ } break;
+ }
+ return String();
+}
+
int Image::get_width() const {
return width;
}
@@ -680,34 +755,35 @@ static void _scale_bilinear(const uint8_t *__restrict p_src, uint8_t *__restrict
enum {
FRAC_BITS = 8,
FRAC_LEN = (1 << FRAC_BITS),
+ FRAC_HALF = (FRAC_LEN >> 1),
FRAC_MASK = FRAC_LEN - 1
-
};
for (uint32_t i = 0; i < p_dst_height; i++) {
- uint32_t src_yofs_up_fp = (i * p_src_height * FRAC_LEN / p_dst_height);
- uint32_t src_yofs_frac = src_yofs_up_fp & FRAC_MASK;
- uint32_t src_yofs_up = src_yofs_up_fp >> FRAC_BITS;
-
- uint32_t src_yofs_down = (i + 1) * p_src_height / p_dst_height;
+ // Add 0.5 in order to interpolate based on pixel center
+ uint32_t src_yofs_up_fp = (i + 0.5) * p_src_height * FRAC_LEN / p_dst_height;
+ // Calculate nearest src pixel center above current, and truncate to get y index
+ uint32_t src_yofs_up = src_yofs_up_fp >= FRAC_HALF ? (src_yofs_up_fp - FRAC_HALF) >> FRAC_BITS : 0;
+ uint32_t src_yofs_down = (src_yofs_up_fp + FRAC_HALF) >> FRAC_BITS;
if (src_yofs_down >= p_src_height) {
src_yofs_down = p_src_height - 1;
}
-
- //src_yofs_up*=CC;
- //src_yofs_down*=CC;
+ // Calculate distance to pixel center of src_yofs_up
+ uint32_t src_yofs_frac = src_yofs_up_fp & FRAC_MASK;
+ src_yofs_frac = src_yofs_frac >= FRAC_HALF ? src_yofs_frac - FRAC_HALF : src_yofs_frac + FRAC_HALF;
uint32_t y_ofs_up = src_yofs_up * p_src_width * CC;
uint32_t y_ofs_down = src_yofs_down * p_src_width * CC;
for (uint32_t j = 0; j < p_dst_width; j++) {
- uint32_t src_xofs_left_fp = (j * p_src_width * FRAC_LEN / p_dst_width);
- uint32_t src_xofs_frac = src_xofs_left_fp & FRAC_MASK;
- uint32_t src_xofs_left = src_xofs_left_fp >> FRAC_BITS;
- uint32_t src_xofs_right = (j + 1) * p_src_width / p_dst_width;
+ uint32_t src_xofs_left_fp = (j + 0.5) * p_src_width * FRAC_LEN / p_dst_width;
+ uint32_t src_xofs_left = src_xofs_left_fp >= FRAC_HALF ? (src_xofs_left_fp - FRAC_HALF) >> FRAC_BITS : 0;
+ uint32_t src_xofs_right = (src_xofs_left_fp + FRAC_HALF) >> FRAC_BITS;
if (src_xofs_right >= p_src_width) {
src_xofs_right = p_src_width - 1;
}
+ uint32_t src_xofs_frac = src_xofs_left_fp & FRAC_MASK;
+ src_xofs_frac = src_xofs_frac >= FRAC_HALF ? src_xofs_frac - FRAC_HALF : src_xofs_frac + FRAC_HALF;
src_xofs_left *= CC;
src_xofs_right *= CC;
@@ -1401,7 +1477,7 @@ int Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int &
h = MAX(minh, h >> 1);
}
mm++;
- };
+ }
r_mipmaps = mm;
return size;
@@ -1445,47 +1521,6 @@ static void _generate_po2_mipmap(const Component *p_src, Component *p_dst, uint3
}
}
-void Image::expand_x2_hq2x() {
- ERR_FAIL_COND(!_can_modify(format));
-
- bool used_mipmaps = has_mipmaps();
- if (used_mipmaps) {
- clear_mipmaps();
- }
-
- Format current = format;
-
- if (current != FORMAT_RGBA8) {
- convert(FORMAT_RGBA8);
- }
-
- Vector<uint8_t> dest;
- dest.resize(width * 2 * height * 2 * 4);
-
- {
- const uint8_t *r = data.ptr();
- uint8_t *w = dest.ptrw();
-
- ERR_FAIL_COND(!r);
-
- hq2x_resize((const uint32_t *)r, width, height, (uint32_t *)w);
- }
-
- width *= 2;
- height *= 2;
- data = dest;
-
- 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();
- }
-}
-
void Image::shrink_x2() {
ERR_FAIL_COND(data.size() == 0);
@@ -1936,8 +1971,10 @@ Vector<uint8_t> Image::get_data() const {
}
void Image::create(int p_width, int p_height, bool p_use_mipmaps, Format p_format) {
- ERR_FAIL_INDEX(p_width - 1, MAX_WIDTH);
- ERR_FAIL_INDEX(p_height - 1, MAX_HEIGHT);
+ ERR_FAIL_COND_MSG(p_width <= 0, "Image width must be greater than 0.");
+ ERR_FAIL_COND_MSG(p_height <= 0, "Image height must 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) + ".");
ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS, "Too many pixels for image, maximum is " + itos(MAX_PIXELS));
int mm = 0;
@@ -1956,8 +1993,10 @@ void Image::create(int p_width, int p_height, bool p_use_mipmaps, Format p_forma
}
void Image::create(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector<uint8_t> &p_data) {
- ERR_FAIL_INDEX(p_width - 1, MAX_WIDTH);
- ERR_FAIL_INDEX(p_height - 1, MAX_HEIGHT);
+ ERR_FAIL_COND_MSG(p_width <= 0, "Image width must be greater than 0.");
+ ERR_FAIL_COND_MSG(p_height <= 0, "Image height must 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) + ".");
ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS, "Too many pixels for image, maximum is " + itos(MAX_PIXELS));
int mm;
@@ -1993,7 +2032,7 @@ void Image::create(const char **p_xpm) {
HashMap<String, Color> colormap;
int colormap_size = 0;
uint32_t pixel_size = 0;
- uint8_t *w;
+ uint8_t *data_write = nullptr;
while (status != DONE) {
const char *line_ptr = p_xpm[line];
@@ -2074,7 +2113,7 @@ void Image::create(const char **p_xpm) {
case 5:
col_b |= v;
break;
- };
+ }
}
// magenta mask
@@ -2089,7 +2128,7 @@ void Image::create(const char **p_xpm) {
if (line == colormap_size) {
status = READING_PIXELS;
create(size_width, size_height, false, has_alpha ? FORMAT_RGBA8 : FORMAT_RGB8);
- w = data.ptrw();
+ data_write = data.ptrw();
pixel_size = has_alpha ? 4 : 3;
}
} break;
@@ -2107,7 +2146,7 @@ void Image::create(const char **p_xpm) {
for (uint32_t i = 0; i < pixel_size; i++) {
pixel[i] = CLAMP((*colorptr)[i] * 255, 0, 255);
}
- _put_pixelb(x, y, pixel_size, w, pixel);
+ _put_pixelb(x, y, pixel_size, data_write, pixel);
}
if (y == (size_height - 1)) {
@@ -2582,12 +2621,11 @@ void Image::blend_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const P
int dst_y = dest_rect.position.y + i;
Color sc = img->get_pixel(src_x, src_y);
- Color dc = get_pixel(dst_x, dst_y);
- dc.r = (double)(sc.a * sc.r + dc.a * (1.0 - sc.a) * dc.r);
- dc.g = (double)(sc.a * sc.g + dc.a * (1.0 - sc.a) * dc.g);
- dc.b = (double)(sc.a * sc.b + dc.a * (1.0 - sc.a) * dc.b);
- dc.a = (double)(sc.a + dc.a * (1.0 - sc.a));
- set_pixel(dst_x, dst_y, dc);
+ if (sc.a != 0) {
+ Color dc = get_pixel(dst_x, dst_y);
+ dc = dc.blend(sc);
+ set_pixel(dst_x, dst_y, dc);
+ }
}
}
}
@@ -2637,12 +2675,11 @@ void Image::blend_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, c
int dst_y = dest_rect.position.y + i;
Color sc = img->get_pixel(src_x, src_y);
- Color dc = get_pixel(dst_x, dst_y);
- dc.r = (double)(sc.a * sc.r + dc.a * (1.0 - sc.a) * dc.r);
- dc.g = (double)(sc.a * sc.g + dc.a * (1.0 - sc.a) * dc.g);
- dc.b = (double)(sc.a * sc.b + dc.a * (1.0 - sc.a) * dc.b);
- dc.a = (double)(sc.a + dc.a * (1.0 - sc.a));
- set_pixel(dst_x, dst_y, dc);
+ if (sc.a != 0) {
+ Color dc = get_pixel(dst_x, dst_y);
+ dc = dc.blend(sc);
+ set_pixel(dst_x, dst_y, dc);
+ }
}
}
}
@@ -2673,6 +2710,7 @@ void Image::fill(const Color &c) {
ImageMemLoadFunc Image::_png_mem_loader_func = nullptr;
ImageMemLoadFunc Image::_jpg_mem_loader_func = nullptr;
ImageMemLoadFunc Image::_webp_mem_loader_func = nullptr;
+ImageMemLoadFunc Image::_tga_mem_loader_func = nullptr;
void (*Image::_image_compress_bc_func)(Image *, float, Image::UsedChannels) = nullptr;
void (*Image::_image_compress_bptc_func)(Image *, float, Image::UsedChannels) = nullptr;
@@ -3047,7 +3085,6 @@ void Image::_bind_methods() {
ClassDB::bind_method(D_METHOD("resize_to_po2", "square"), &Image::resize_to_po2, DEFVAL(false));
ClassDB::bind_method(D_METHOD("resize", "width", "height", "interpolation"), &Image::resize, DEFVAL(INTERPOLATE_BILINEAR));
ClassDB::bind_method(D_METHOD("shrink_x2"), &Image::shrink_x2);
- ClassDB::bind_method(D_METHOD("expand_x2_hq2x"), &Image::expand_x2_hq2x);
ClassDB::bind_method(D_METHOD("crop", "width", "height"), &Image::crop);
ClassDB::bind_method(D_METHOD("flip_x"), &Image::flip_x);
@@ -3062,6 +3099,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_png_to_buffer"), &Image::save_png_to_buffer);
ClassDB::bind_method(D_METHOD("save_exr", "path", "grayscale"), &Image::save_exr, DEFVAL(false));
ClassDB::bind_method(D_METHOD("detect_alpha"), &Image::detect_alpha);
@@ -3102,6 +3140,7 @@ void Image::_bind_methods() {
ClassDB::bind_method(D_METHOD("load_png_from_buffer", "buffer"), &Image::load_png_from_buffer);
ClassDB::bind_method(D_METHOD("load_jpg_from_buffer", "buffer"), &Image::load_jpg_from_buffer);
ClassDB::bind_method(D_METHOD("load_webp_from_buffer", "buffer"), &Image::load_webp_from_buffer);
+ ClassDB::bind_method(D_METHOD("load_tga_from_buffer", "buffer"), &Image::load_tga_from_buffer);
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "_set_data", "_get_data");
@@ -3433,6 +3472,11 @@ Error Image::load_webp_from_buffer(const Vector<uint8_t> &p_array) {
return _load_from_buffer(p_array, _webp_mem_loader_func);
}
+Error Image::load_tga_from_buffer(const Vector<uint8_t> &p_array) {
+ ERR_FAIL_NULL_V_MSG(_tga_mem_loader_func, ERR_UNAVAILABLE, "TGA module was not installed.");
+ return _load_from_buffer(p_array, _tga_mem_loader_func);
+}
+
void Image::convert_rg_to_ra_rgba8() {
ERR_FAIL_COND(format != FORMAT_RGBA8);
ERR_FAIL_COND(!data.size());