Browse Source

Merge pull request #52456 from kleonc/image-fill-rect

Rémi Verschelde 3 năm trước cách đây
mục cha
commit
b1bf82d2b4
4 tập tin đã thay đổi với 111 bổ sung30 xóa
  1. 49 23
      core/io/image.cpp
  2. 6 3
      core/io/image.h
  3. 9 1
      doc/classes/Image.xml
  4. 47 3
      tests/core/io/test_image.h

+ 49 - 23
core/io/image.cpp

@@ -86,20 +86,14 @@ SaveEXRFunc Image::save_exr_func = nullptr;
 
 SavePNGBufferFunc Image::save_png_buffer_func = nullptr;
 
-void Image::_put_pixelb(int p_x, int p_y, uint32_t p_pixelsize, uint8_t *p_data, const uint8_t *p_pixel) {
-	uint32_t ofs = (p_y * width + p_x) * p_pixelsize;
-
-	for (uint32_t i = 0; i < p_pixelsize; i++) {
-		p_data[ofs + i] = p_pixel[i];
-	}
+void Image::_put_pixelb(int p_x, int p_y, uint32_t p_pixel_size, uint8_t *p_data, const uint8_t *p_pixel) {
+	uint32_t ofs = (p_y * width + p_x) * p_pixel_size;
+	memcpy(p_data + ofs, p_pixel, p_pixel_size);
 }
 
-void Image::_get_pixelb(int p_x, int p_y, uint32_t p_pixelsize, const uint8_t *p_data, uint8_t *p_pixel) {
-	uint32_t ofs = (p_y * width + p_x) * p_pixelsize;
-
-	for (uint32_t i = 0; i < p_pixelsize; i++) {
-		p_pixel[i] = p_data[ofs + i];
-	}
+void Image::_get_pixelb(int p_x, int p_y, uint32_t p_pixel_size, const uint8_t *p_data, uint8_t *p_pixel) {
+	uint32_t ofs = (p_y * width + p_x) * p_pixel_size;
+	memcpy(p_pixel, p_data + ofs, p_pixel_size);
 }
 
 int Image::get_format_pixel_size(Format p_format) {
@@ -2697,24 +2691,55 @@ void Image::blend_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, c
 	}
 }
 
-void Image::fill(const Color &c) {
+// Repeats `p_pixel` `p_count` times in consecutive memory.
+// Results in the original pixel and `p_count - 1` subsequent copies of it.
+void Image::_repeat_pixel_over_subsequent_memory(uint8_t *p_pixel, int p_pixel_size, int p_count) {
+	int offset = 1;
+	for (int stride = 1; offset + stride <= p_count; stride *= 2) {
+		memcpy(p_pixel + offset * p_pixel_size, p_pixel, stride * p_pixel_size);
+		offset += stride;
+	}
+	if (offset < p_count) {
+		memcpy(p_pixel + offset * p_pixel_size, p_pixel, (p_count - offset) * p_pixel_size);
+	}
+}
+
+void Image::fill(const Color &p_color) {
 	ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot fill in compressed or custom image formats.");
 
-	uint8_t *wp = data.ptrw();
-	uint8_t *dst_data_ptr = wp;
+	uint8_t *dst_data_ptr = data.ptrw();
 
 	int pixel_size = get_format_pixel_size(format);
 
-	// put first pixel with the format-aware API
-	set_pixel(0, 0, c);
+	// Put first pixel with the format-aware API.
+	_set_color_at_ofs(dst_data_ptr, 0, p_color);
 
-	for (int y = 0; y < height; y++) {
-		for (int x = 0; x < width; x++) {
-			uint8_t *dst = &dst_data_ptr[(y * width + x) * pixel_size];
+	_repeat_pixel_over_subsequent_memory(dst_data_ptr, pixel_size, width * height);
+}
 
-			for (int k = 0; k < pixel_size; k++) {
-				dst[k] = dst_data_ptr[k];
-			}
+void Image::fill_rect(const Rect2 &p_rect, const Color &p_color) {
+	ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot fill rect in compressed or custom image formats.");
+
+	Rect2i r = Rect2i(0, 0, width, height).intersection(p_rect.abs());
+	if (r.has_no_area()) {
+		return;
+	}
+
+	uint8_t *dst_data_ptr = data.ptrw();
+
+	int pixel_size = get_format_pixel_size(format);
+
+	// Put first pixel with the format-aware API.
+	uint8_t *rect_first_pixel_ptr = &dst_data_ptr[(r.position.y * width + r.position.x) * pixel_size];
+	_set_color_at_ofs(rect_first_pixel_ptr, 0, p_color);
+
+	if (r.size.x == width) {
+		// No need to fill rows separately.
+		_repeat_pixel_over_subsequent_memory(rect_first_pixel_ptr, pixel_size, width * r.size.y);
+	} else {
+		_repeat_pixel_over_subsequent_memory(rect_first_pixel_ptr, pixel_size, r.size.x);
+		for (int y = 1; y < r.size.y; y++) {
+			memcpy(rect_first_pixel_ptr + y * width * pixel_size, rect_first_pixel_ptr, r.size.x * pixel_size);
 		}
 	}
 }
@@ -3160,6 +3185,7 @@ void Image::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("blend_rect", "src", "src_rect", "dst"), &Image::blend_rect);
 	ClassDB::bind_method(D_METHOD("blend_rect_mask", "src", "mask", "src_rect", "dst"), &Image::blend_rect_mask);
 	ClassDB::bind_method(D_METHOD("fill", "color"), &Image::fill);
+	ClassDB::bind_method(D_METHOD("fill_rect", "rect", "color"), &Image::fill_rect);
 
 	ClassDB::bind_method(D_METHOD("get_used_rect"), &Image::get_used_rect);
 	ClassDB::bind_method(D_METHOD("get_rect", "rect"), &Image::get_rect);

+ 6 - 3
core/io/image.h

@@ -190,8 +190,10 @@ private:
 	static int _get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps = -1, int *r_mm_width = nullptr, int *r_mm_height = nullptr);
 	bool _can_modify(Format p_format) const;
 
-	_FORCE_INLINE_ void _put_pixelb(int p_x, int p_y, uint32_t p_pixelsize, uint8_t *p_data, const uint8_t *p_pixel);
-	_FORCE_INLINE_ void _get_pixelb(int p_x, int p_y, uint32_t p_pixelsize, const uint8_t *p_data, uint8_t *p_pixel);
+	_FORCE_INLINE_ void _put_pixelb(int p_x, int p_y, uint32_t p_pixel_size, uint8_t *p_data, const uint8_t *p_pixel);
+	_FORCE_INLINE_ void _get_pixelb(int p_x, int p_y, uint32_t p_pixel_size, const uint8_t *p_data, uint8_t *p_pixel);
+
+	_FORCE_INLINE_ void _repeat_pixel_over_subsequent_memory(uint8_t *p_pixel, int p_pixel_size, int p_count);
 
 	void _set_data(const Dictionary &p_data);
 	Dictionary _get_data() const;
@@ -362,7 +364,8 @@ public:
 	void blit_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, const Rect2 &p_src_rect, const Point2 &p_dest);
 	void blend_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Point2 &p_dest);
 	void blend_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, const Rect2 &p_src_rect, const Point2 &p_dest);
-	void fill(const Color &c);
+	void fill(const Color &p_color);
+	void fill_rect(const Rect2 &p_rect, const Color &p_color);
 
 	Rect2 get_used_rect() const;
 	Ref<Image> get_rect(const Rect2 &p_area) const;

+ 9 - 1
doc/classes/Image.xml

@@ -154,7 +154,15 @@
 			<return type="void" />
 			<argument index="0" name="color" type="Color" />
 			<description>
-				Fills the image with a given [Color].
+				Fills the image with [code]color[/code].
+			</description>
+		</method>
+		<method name="fill_rect">
+			<return type="void" />
+			<argument index="0" name="rect" type="Rect2" />
+			<argument index="1" name="color" type="Color" />
+			<description>
+				Fills [code]rect[/code] with [code]color[/code].
 			</description>
 		</method>
 		<method name="fix_alpha_edges">

+ 47 - 3
tests/core/io/test_image.h

@@ -52,6 +52,13 @@ TEST_CASE("[Image] Instantiation") {
 			"A newly created image should not be compressed.");
 	CHECK(!image->has_mipmaps());
 
+	PackedByteArray image_data = image->get_data();
+	for (int i = 0; i < image_data.size(); i++) {
+		CHECK_MESSAGE(
+				image_data[i] == 0,
+				"An image created without data specified should have its data zeroed out.");
+	}
+
 	Ref<Image> image_copy = memnew(Image());
 	CHECK_MESSAGE(
 			image_copy->is_empty(),
@@ -62,7 +69,7 @@ TEST_CASE("[Image] Instantiation") {
 			image->get_data() == image_copy->get_data(),
 			"Duplicated images should have the same data.");
 
-	PackedByteArray image_data = image->get_data();
+	image_data = image->get_data();
 	Ref<Image> image_from_data = memnew(Image(8, 4, false, Image::FORMAT_RGBA8, image_data));
 	CHECK_MESSAGE(
 			image->get_data() == image_from_data->get_data(),
@@ -214,14 +221,51 @@ TEST_CASE("[Image] Modifying pixels of an image") {
 
 	// Fill image with color
 	image2->fill(Color(0.5, 0.5, 0.5, 0.5));
-	for (int x = 0; x < image2->get_width(); x++) {
-		for (int y = 0; y < image2->get_height(); y++) {
+	for (int y = 0; y < image2->get_height(); y++) {
+		for (int x = 0; x < image2->get_width(); x++) {
 			CHECK_MESSAGE(
 					image2->get_pixel(x, y).r > 0.49,
 					"fill() should colorize all pixels of the image.");
 		}
 	}
 
+	// Fill rect with color
+	{
+		const int img_width = 3;
+		const int img_height = 3;
+		Vector<Rect2> rects;
+		rects.push_back(Rect2());
+		rects.push_back(Rect2(-5, -5, 3, 3));
+		rects.push_back(Rect2(img_width, 0, 12, 12));
+		rects.push_back(Rect2(0, img_height, 12, 12));
+		rects.push_back(Rect2(img_width + 1, img_height + 2, 12, 12));
+		rects.push_back(Rect2(1, 1, 1, 1));
+		rects.push_back(Rect2(0, 1, 2, 3));
+		rects.push_back(Rect2(-5, 0, img_width + 10, 2));
+		rects.push_back(Rect2(0, -5, 2, img_height + 10));
+		rects.push_back(Rect2(-1, -1, img_width + 1, img_height + 1));
+
+		for (const Rect2 &rect : rects) {
+			Ref<Image> img = memnew(Image(img_width, img_height, false, Image::FORMAT_RGBA8));
+			CHECK_NOTHROW_MESSAGE(
+					img->fill_rect(rect, Color(1, 1, 1, 1)),
+					"fill_rect() shouldn't throw for any rect.");
+			for (int y = 0; y < img->get_height(); y++) {
+				for (int x = 0; x < img->get_width(); x++) {
+					if (rect.abs().has_point(Point2(x, y))) {
+						CHECK_MESSAGE(
+								img->get_pixel(x, y).is_equal_approx(Color(1, 1, 1, 1)),
+								"fill_rect() should colorize all image pixels within rect bounds.");
+					} else {
+						CHECK_MESSAGE(
+								!img->get_pixel(x, y).is_equal_approx(Color(1, 1, 1, 1)),
+								"fill_rect() shouldn't colorize any image pixel out of rect bounds.");
+					}
+				}
+			}
+		}
+	}
+
 	// Blend two images together
 	image->blend_rect(image2, Rect2(Vector2(0, 0), image2->get_size()), Vector2(0, 0));
 	CHECK_MESSAGE(