Ver código fonte

Merge pull request #64198 from Geometror/add-bitmap-tests

Rémi Verschelde 3 anos atrás
pai
commit
5263fd5343

+ 26 - 9
doc/classes/BitMap.xml

@@ -17,7 +17,7 @@
 		</method>
 		<method name="create">
 			<return type="void" />
-			<param index="0" name="size" type="Vector2" />
+			<param index="0" name="size" type="Vector2i" />
 			<description>
 				Creates a bitmap with the specified size, filled with [code]false[/code].
 			</description>
@@ -32,13 +32,21 @@
 		</method>
 		<method name="get_bit" qualifiers="const">
 			<return type="bool" />
-			<param index="0" name="position" type="Vector2" />
+			<param index="0" name="x" type="int" />
+			<param index="1" name="y" type="int" />
+			<description>
+				Returns bitmap's value at the specified position.
+			</description>
+		</method>
+		<method name="get_bitv" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="position" type="Vector2i" />
 			<description>
 				Returns bitmap's value at the specified position.
 			</description>
 		</method>
 		<method name="get_size" qualifiers="const">
-			<return type="Vector2" />
+			<return type="Vector2i" />
 			<description>
 				Returns bitmap's dimensions.
 			</description>
@@ -52,14 +60,14 @@
 		<method name="grow_mask">
 			<return type="void" />
 			<param index="0" name="pixels" type="int" />
-			<param index="1" name="rect" type="Rect2" />
+			<param index="1" name="rect" type="Rect2i" />
 			<description>
 				Applies morphological dilation or erosion to the bitmap. If [param pixels] is positive, dilation is applied to the bitmap. If [param pixels] is negative, erosion is applied to the bitmap. [param rect] defines the area where the morphological operation is applied. Pixels located outside the [param rect] are unaffected by [method grow_mask].
 			</description>
 		</method>
 		<method name="opaque_to_polygons" qualifiers="const">
 			<return type="PackedVector2Array[]" />
-			<param index="0" name="rect" type="Rect2" />
+			<param index="0" name="rect" type="Rect2i" />
 			<param index="1" name="epsilon" type="float" default="2.0" />
 			<description>
 				Creates an [Array] of polygons covering a rectangular portion of the bitmap. It uses a marching squares algorithm, followed by Ramer-Douglas-Peucker (RDP) reduction of the number of vertices. Each polygon is described as a [PackedVector2Array] of its vertices.
@@ -72,26 +80,35 @@
 		</method>
 		<method name="resize">
 			<return type="void" />
-			<param index="0" name="new_size" type="Vector2" />
+			<param index="0" name="new_size" type="Vector2i" />
 			<description>
 				Resizes the image to [param new_size].
 			</description>
 		</method>
 		<method name="set_bit">
 			<return type="void" />
-			<param index="0" name="position" type="Vector2" />
-			<param index="1" name="bit" type="bool" />
+			<param index="0" name="x" type="int" />
+			<param index="1" name="y" type="int" />
+			<param index="2" name="bit" type="bool" />
 			<description>
 				Sets the bitmap's element at the specified position, to the specified value.
 			</description>
 		</method>
 		<method name="set_bit_rect">
 			<return type="void" />
-			<param index="0" name="rect" type="Rect2" />
+			<param index="0" name="rect" type="Rect2i" />
 			<param index="1" name="bit" type="bool" />
 			<description>
 				Sets a rectangular portion of the bitmap to the specified value.
 			</description>
 		</method>
+		<method name="set_bitv">
+			<return type="void" />
+			<param index="0" name="position" type="Vector2i" />
+			<param index="1" name="bit" type="bool" />
+			<description>
+				Sets the bitmap's element at the specified position, to the specified value.
+			</description>
+		</method>
 	</methods>
 </class>

+ 2 - 2
editor/editor_atlas_packer.cpp

@@ -81,7 +81,7 @@ void EditorAtlasPacker::chart_pack(Vector<Chart> &charts, int &r_width, int &r_h
 				int l = k == 0 ? 2 : k - 1;
 				Vector<Point2i> points = Geometry2D::bresenham_line(v[k], v[l]);
 				for (Point2i point : points) {
-					src_bitmap->set_bit(point, true);
+					src_bitmap->set_bitv(point, true);
 				}
 			}
 		}
@@ -128,7 +128,7 @@ void EditorAtlasPacker::chart_pack(Vector<Chart> &charts, int &r_width, int &r_h
 							continue;
 						}
 
-						if (src_bitmap->get_bit(Vector2(px, py))) {
+						if (src_bitmap->get_bit(px, py)) {
 							found_pixel = true;
 						}
 					}

+ 1 - 1
editor/import/resource_importer_bitmask.cpp

@@ -99,7 +99,7 @@ Error ResourceImporterBitMap::import(const String &p_source_file, const String &
 				bit = c.a > threshold;
 			}
 
-			bitmap->set_bit(Vector2(j, i), bit);
+			bitmap->set_bit(j, i, bit);
 		}
 	}
 

+ 1 - 1
editor/plugins/editor_preview_plugins.cpp

@@ -201,7 +201,7 @@ Ref<Texture2D> EditorBitmapPreviewPlugin::generate(const Ref<Resource> &p_from,
 
 		for (int i = 0; i < bm->get_size().width; i++) {
 			for (int j = 0; j < bm->get_size().height; j++) {
-				if (bm->get_bit(Point2i(i, j))) {
+				if (bm->get_bit(i, j)) {
 					w[j * (int)bm->get_size().width + i] = 255;
 				} else {
 					w[j * (int)bm->get_size().width + i] = 0;

+ 1 - 1
scene/2d/touch_screen_button.cpp

@@ -264,7 +264,7 @@ bool TouchScreenButton::_is_point_inside(const Point2 &p_point) {
 	if (bitmask.is_valid()) {
 		check_rect = false;
 		if (!touched && Rect2(Point2(), bitmask->get_size()).has_point(coord)) {
-			if (bitmask->get_bit(coord)) {
+			if (bitmask->get_bitv(coord)) {
 				touched = true;
 			}
 		}

+ 1 - 1
scene/gui/texture_button.cpp

@@ -112,7 +112,7 @@ bool TextureButton::has_point(const Point2 &p_point) const {
 		}
 
 		Point2i p = point;
-		return click_mask->get_bit(p);
+		return click_mask->get_bitv(p);
 	}
 
 	return Control::has_point(p_point);

+ 85 - 65
scene/resources/bit_map.cpp

@@ -33,13 +33,18 @@
 #include "core/io/image_loader.h"
 #include "core/variant/typed_array.h"
 
-void BitMap::create(const Size2 &p_size) {
+void BitMap::create(const Size2i &p_size) {
 	ERR_FAIL_COND(p_size.width < 1);
 	ERR_FAIL_COND(p_size.height < 1);
 
+	ERR_FAIL_COND(static_cast<int64_t>(p_size.width) * static_cast<int64_t>(p_size.height) > INT32_MAX);
+
+	Error err = bitmask.resize((((p_size.width * p_size.height) - 1) / 8) + 1);
+	ERR_FAIL_COND(err != OK);
+
 	width = p_size.width;
 	height = p_size.height;
-	bitmask.resize((((width * height) - 1) / 8) + 1);
+
 	memset(bitmask.ptrw(), 0, bitmask.size());
 }
 
@@ -49,7 +54,7 @@ void BitMap::create_from_image_alpha(const Ref<Image> &p_image, float p_threshol
 	img->convert(Image::FORMAT_LA8);
 	ERR_FAIL_COND(img->get_format() != Image::FORMAT_LA8);
 
-	create(img->get_size());
+	create(Size2i(img->get_width(), img->get_height()));
 
 	const uint8_t *r = img->get_data().ptr();
 	uint8_t *w = bitmask.ptrw();
@@ -63,7 +68,7 @@ void BitMap::create_from_image_alpha(const Ref<Image> &p_image, float p_threshol
 	}
 }
 
-void BitMap::set_bit_rect(const Rect2 &p_rect, bool p_value) {
+void BitMap::set_bit_rect(const Rect2i &p_rect, bool p_value) {
 	Rect2i current = Rect2i(0, 0, width, height).intersection(p_rect);
 	uint8_t *data = bitmask.ptrw();
 
@@ -91,7 +96,7 @@ int BitMap::get_true_bit_count() const {
 	const uint8_t *d = bitmask.ptr();
 	int c = 0;
 
-	//fast, almost branchless version
+	// Fast, almost branchless version.
 
 	for (int i = 0; i < ds; i++) {
 		c += (d[i] & (1 << 7)) >> 7;
@@ -107,14 +112,15 @@ int BitMap::get_true_bit_count() const {
 	return c;
 }
 
-void BitMap::set_bit(const Point2 &p_pos, bool p_value) {
-	int x = p_pos.x;
-	int y = p_pos.y;
+void BitMap::set_bitv(const Point2i &p_pos, bool p_value) {
+	set_bit(p_pos.x, p_pos.y, p_value);
+}
 
-	ERR_FAIL_INDEX(x, width);
-	ERR_FAIL_INDEX(y, height);
+void BitMap::set_bit(int p_x, int p_y, bool p_value) {
+	ERR_FAIL_INDEX(p_x, width);
+	ERR_FAIL_INDEX(p_y, height);
 
-	int ofs = width * y + x;
+	int ofs = width * p_y + p_x;
 	int bbyte = ofs / 8;
 	int bbit = ofs % 8;
 
@@ -129,21 +135,23 @@ void BitMap::set_bit(const Point2 &p_pos, bool p_value) {
 	bitmask.write[bbyte] = b;
 }
 
-bool BitMap::get_bit(const Point2 &p_pos) const {
-	int x = Math::fast_ftoi(p_pos.x);
-	int y = Math::fast_ftoi(p_pos.y);
-	ERR_FAIL_INDEX_V(x, width, false);
-	ERR_FAIL_INDEX_V(y, height, false);
+bool BitMap::get_bitv(const Point2i &p_pos) const {
+	return get_bit(p_pos.x, p_pos.y);
+}
+
+bool BitMap::get_bit(int p_x, int p_y) const {
+	ERR_FAIL_INDEX_V(p_x, width, false);
+	ERR_FAIL_INDEX_V(p_y, height, false);
 
-	int ofs = width * y + x;
+	int ofs = width * p_y + p_x;
 	int bbyte = ofs / 8;
 	int bbit = ofs % 8;
 
 	return (bitmask[bbyte] & (1 << bbit)) != 0;
 }
 
-Size2 BitMap::get_size() const {
-	return Size2(width, height);
+Size2i BitMap::get_size() const {
+	return Size2i(width, height);
 }
 
 void BitMap::_set_data(const Dictionary &p_d) {
@@ -161,13 +169,13 @@ Dictionary BitMap::_get_data() const {
 	return d;
 }
 
-Vector<Vector2> BitMap::_march_square(const Rect2i &rect, const Point2i &start) const {
+Vector<Vector2> BitMap::_march_square(const Rect2i &p_rect, const Point2i &p_start) const {
 	int stepx = 0;
 	int stepy = 0;
 	int prevx = 0;
 	int prevy = 0;
-	int startx = start.x;
-	int starty = start.y;
+	int startx = p_start.x;
+	int starty = p_start.y;
 	int curx = startx;
 	int cury = starty;
 	unsigned int count = 0;
@@ -176,7 +184,7 @@ Vector<Vector2> BitMap::_march_square(const Rect2i &rect, const Point2i &start)
 	Vector<Vector2> _points;
 	do {
 		int sv = 0;
-		{ //square value
+		{ // Square value
 
 			/*
 			checking the 2x2 pixel grid, assigning these values to each pixel, if not transparent
@@ -187,13 +195,13 @@ Vector<Vector2> BitMap::_march_square(const Rect2i &rect, const Point2i &start)
 			+---+---+
 			*/
 			Point2i tl = Point2i(curx - 1, cury - 1);
-			sv += (rect.has_point(tl) && get_bit(tl)) ? 1 : 0;
+			sv += (p_rect.has_point(tl) && get_bitv(tl)) ? 1 : 0;
 			Point2i tr = Point2i(curx, cury - 1);
-			sv += (rect.has_point(tr) && get_bit(tr)) ? 2 : 0;
+			sv += (p_rect.has_point(tr) && get_bitv(tr)) ? 2 : 0;
 			Point2i bl = Point2i(curx - 1, cury);
-			sv += (rect.has_point(bl) && get_bit(bl)) ? 4 : 0;
+			sv += (p_rect.has_point(bl) && get_bitv(bl)) ? 4 : 0;
 			Point2i br = Point2i(curx, cury);
-			sv += (rect.has_point(br) && get_bit(br)) ? 8 : 0;
+			sv += (p_rect.has_point(br) && get_bitv(br)) ? 8 : 0;
 			ERR_FAIL_COND_V(sv == 0 || sv == 15, Vector<Vector2>());
 		}
 
@@ -303,16 +311,16 @@ Vector<Vector2> BitMap::_march_square(const Rect2i &rect, const Point2i &start)
 			default:
 				ERR_PRINT("this shouldn't happen.");
 		}
-		//little optimization
-		// if previous direction is same as current direction,
-		// then we should modify the last vec to current
+		// Small optimization:
+		// If the previous direction is same as the current direction,
+		// then we should modify the last vector to current.
 		curx += stepx;
 		cury += stepy;
 		if (stepx == prevx && stepy == prevy) {
-			_points.write[_points.size() - 1].x = (float)(curx - rect.position.x);
-			_points.write[_points.size() - 1].y = (float)(cury + rect.position.y);
+			_points.write[_points.size() - 1].x = (float)(curx - p_rect.position.x);
+			_points.write[_points.size() - 1].y = (float)(cury + p_rect.position.y);
 		} else {
-			_points.push_back(Vector2((float)(curx - rect.position.x), (float)(cury + rect.position.y)));
+			_points.push_back(Vector2((float)(curx - p_rect.position.x), (float)(cury + p_rect.position.y)));
 		}
 
 		count++;
@@ -348,7 +356,7 @@ static Vector<Vector2> rdp(const Vector<Vector2> &v, float optimization) {
 
 	int index = -1;
 	float dist = 0.0;
-	//not looping first and last point
+	// Not looping first and last point.
 	for (size_t i = 1, size = v.size(); i < size - 1; ++i) {
 		float cdist = perpendicular_distance(v[i], v[0], v[v.size() - 1]);
 		if (cdist > dist) {
@@ -385,9 +393,9 @@ static Vector<Vector2> rdp(const Vector<Vector2> &v, float optimization) {
 
 static Vector<Vector2> reduce(const Vector<Vector2> &points, const Rect2i &rect, float epsilon) {
 	int size = points.size();
-	// if there are less than 3 points, then we have nothing
+	// If there are less than 3 points, then we have nothing.
 	ERR_FAIL_COND_V(size < 3, Vector<Vector2>());
-	// if there are less than 9 points (but more than 3), then we don't need to reduce it
+	// If there are less than 9 points (but more than 3), then we don't need to reduce it.
 	if (size < 9) {
 		return points;
 	}
@@ -412,9 +420,9 @@ struct FillBitsStackEntry {
 };
 
 static void fill_bits(const BitMap *p_src, Ref<BitMap> &p_map, const Point2i &p_pos, const Rect2i &rect) {
-	// Using a custom stack to work iteratively to avoid stack overflow on big bitmaps
+	// Using a custom stack to work iteratively to avoid stack overflow on big bitmaps.
 	Vector<FillBitsStackEntry> stack;
-	// Tracking size since we won't be shrinking the stack vector
+	// Tracking size since we won't be shrinking the stack vector.
 	int stack_size = 0;
 
 	Point2i pos = p_pos;
@@ -433,10 +441,10 @@ static void fill_bits(const BitMap *p_src, Ref<BitMap> &p_map, const Point2i &p_
 		for (int i = next_i; i <= pos.x + 1; i++) {
 			for (int j = next_j; j <= pos.y + 1; j++) {
 				if (popped) {
-					// The next loop over j must start normally
+					// The next loop over j must start normally.
 					next_j = pos.y;
 					popped = false;
-					// Skip because an iteration was already executed with current counter values
+					// Skip because an iteration was already executed with current counter values.
 					continue;
 				}
 
@@ -447,11 +455,11 @@ static void fill_bits(const BitMap *p_src, Ref<BitMap> &p_map, const Point2i &p_
 					continue;
 				}
 
-				if (p_map->get_bit(Vector2(i, j))) {
+				if (p_map->get_bit(i, j)) {
 					continue;
 
-				} else if (p_src->get_bit(Vector2(i, j))) {
-					p_map->set_bit(Vector2(i, j), true);
+				} else if (p_src->get_bit(i, j)) {
+					p_map->set_bit(i, j, true);
 
 					FillBitsStackEntry se = { pos, i, j };
 					stack.resize(MAX(stack_size + 1, stack.size()));
@@ -482,7 +490,7 @@ static void fill_bits(const BitMap *p_src, Ref<BitMap> &p_map, const Point2i &p_
 	print_verbose("BitMap: Max stack size: " + itos(stack.size()));
 }
 
-Vector<Vector<Vector2>> BitMap::clip_opaque_to_polygons(const Rect2 &p_rect, float p_epsilon) const {
+Vector<Vector<Vector2>> BitMap::clip_opaque_to_polygons(const Rect2i &p_rect, float p_epsilon) const {
 	Rect2i r = Rect2i(0, 0, width, height).intersection(p_rect);
 	print_verbose("BitMap: Rect: " + r);
 
@@ -494,7 +502,7 @@ Vector<Vector<Vector2>> BitMap::clip_opaque_to_polygons(const Rect2 &p_rect, flo
 	Vector<Vector<Vector2>> polygons;
 	for (int i = r.position.y; i < r.position.y + r.size.height; i++) {
 		for (int j = r.position.x; j < r.position.x + r.size.width; j++) {
-			if (!fill->get_bit(Point2(j, i)) && get_bit(Point2(j, i))) {
+			if (!fill->get_bit(j, i) && get_bit(j, i)) {
 				fill_bits(this, fill, Point2i(j, i), r);
 
 				Vector<Vector2> polygon = _march_square(r, Point2i(j, i));
@@ -515,7 +523,7 @@ Vector<Vector<Vector2>> BitMap::clip_opaque_to_polygons(const Rect2 &p_rect, flo
 	return polygons;
 }
 
-void BitMap::grow_mask(int p_pixels, const Rect2 &p_rect) {
+void BitMap::grow_mask(int p_pixels, const Rect2i &p_rect) {
 	if (p_pixels == 0) {
 		return;
 	}
@@ -532,7 +540,7 @@ void BitMap::grow_mask(int p_pixels, const Rect2 &p_rect) {
 
 	for (int i = r.position.y; i < r.position.y + r.size.height; i++) {
 		for (int j = r.position.x; j < r.position.x + r.size.width; j++) {
-			if (bit_value == get_bit(Point2(j, i))) {
+			if (bit_value == get_bit(j, i)) {
 				continue;
 			}
 
@@ -543,7 +551,7 @@ void BitMap::grow_mask(int p_pixels, const Rect2 &p_rect) {
 					bool outside = false;
 
 					if ((x < p_rect.position.x) || (x >= p_rect.position.x + p_rect.size.x) || (y < p_rect.position.y) || (y >= p_rect.position.y + p_rect.size.y)) {
-						// outside of rectangle counts as bit not set
+						// Outside of rectangle counts as bit not set.
 						if (!bit_value) {
 							outside = true;
 						} else {
@@ -556,7 +564,7 @@ void BitMap::grow_mask(int p_pixels, const Rect2 &p_rect) {
 						continue;
 					}
 
-					if (outside || (bit_value == copy->get_bit(Point2(x, y)))) {
+					if (outside || (bit_value == copy->get_bit(x, y))) {
 						found = true;
 						break;
 					}
@@ -567,20 +575,20 @@ void BitMap::grow_mask(int p_pixels, const Rect2 &p_rect) {
 			}
 
 			if (found) {
-				set_bit(Point2(j, i), bit_value);
+				set_bit(j, i, bit_value);
 			}
 		}
 	}
 }
 
-void BitMap::shrink_mask(int p_pixels, const Rect2 &p_rect) {
+void BitMap::shrink_mask(int p_pixels, const Rect2i &p_rect) {
 	grow_mask(-p_pixels, p_rect);
 }
 
-TypedArray<PackedVector2Array> BitMap::_opaque_to_polygons_bind(const Rect2 &p_rect, float p_epsilon) const {
+TypedArray<PackedVector2Array> BitMap::_opaque_to_polygons_bind(const Rect2i &p_rect, float p_epsilon) const {
 	Vector<Vector<Vector2>> result = clip_opaque_to_polygons(p_rect, p_epsilon);
 
-	// Convert result to bindable types
+	// Convert result to bindable types.
 
 	TypedArray<PackedVector2Array> result_array;
 	result_array.resize(result.size());
@@ -603,15 +611,25 @@ TypedArray<PackedVector2Array> BitMap::_opaque_to_polygons_bind(const Rect2 &p_r
 	return result_array;
 }
 
-void BitMap::resize(const Size2 &p_new_size) {
+void BitMap::resize(const Size2i &p_new_size) {
+	ERR_FAIL_COND(p_new_size.width < 0 || p_new_size.height < 0);
+	if (p_new_size == get_size()) {
+		return;
+	}
+
 	Ref<BitMap> new_bitmap;
 	new_bitmap.instantiate();
 	new_bitmap->create(p_new_size);
-	int lw = MIN(width, p_new_size.width);
-	int lh = MIN(height, p_new_size.height);
+	// also allow for upscaling
+	int lw = (width == 0) ? 0 : p_new_size.width;
+	int lh = (height == 0) ? 0 : p_new_size.height;
+
+	float scale_x = ((float)width / p_new_size.width);
+	float scale_y = ((float)height / p_new_size.height);
 	for (int x = 0; x < lw; x++) {
 		for (int y = 0; y < lh; y++) {
-			new_bitmap->set_bit(Vector2(x, y), get_bit(Vector2(x, y)));
+			bool new_bit = get_bit(x * scale_x, y * scale_y);
+			new_bitmap->set_bit(x, y, new_bit);
 		}
 	}
 
@@ -627,14 +645,16 @@ Ref<Image> BitMap::convert_to_image() const {
 
 	for (int i = 0; i < width; i++) {
 		for (int j = 0; j < height; j++) {
-			image->set_pixel(i, j, get_bit(Point2(i, j)) ? Color(1, 1, 1) : Color(0, 0, 0));
+			image->set_pixel(i, j, get_bit(i, j) ? Color(1, 1, 1) : Color(0, 0, 0));
 		}
 	}
 
 	return image;
 }
 
-void BitMap::blit(const Vector2 &p_pos, const Ref<BitMap> &p_bitmap) {
+void BitMap::blit(const Vector2i &p_pos, const Ref<BitMap> &p_bitmap) {
+	ERR_FAIL_COND_MSG(p_bitmap.is_null(), "It's not a reference to a valid BitMap object.");
+
 	int x = p_pos.x;
 	int y = p_pos.y;
 	int w = p_bitmap->get_size().width;
@@ -650,8 +670,8 @@ void BitMap::blit(const Vector2 &p_pos, const Ref<BitMap> &p_bitmap) {
 			if (py < 0 || py >= height) {
 				continue;
 			}
-			if (p_bitmap->get_bit(Vector2(i, j))) {
-				set_bit(Vector2(x, y), true);
+			if (p_bitmap->get_bit(i, j)) {
+				set_bit(px, py, true);
 			}
 		}
 	}
@@ -661,8 +681,10 @@ void BitMap::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("create", "size"), &BitMap::create);
 	ClassDB::bind_method(D_METHOD("create_from_image_alpha", "image", "threshold"), &BitMap::create_from_image_alpha, DEFVAL(0.1));
 
-	ClassDB::bind_method(D_METHOD("set_bit", "position", "bit"), &BitMap::set_bit);
-	ClassDB::bind_method(D_METHOD("get_bit", "position"), &BitMap::get_bit);
+	ClassDB::bind_method(D_METHOD("set_bitv", "position", "bit"), &BitMap::set_bitv);
+	ClassDB::bind_method(D_METHOD("set_bit", "x", "y", "bit"), &BitMap::set_bit);
+	ClassDB::bind_method(D_METHOD("get_bitv", "position"), &BitMap::get_bitv);
+	ClassDB::bind_method(D_METHOD("get_bit", "x", "y"), &BitMap::get_bit);
 
 	ClassDB::bind_method(D_METHOD("set_bit_rect", "rect", "bit"), &BitMap::set_bit_rect);
 	ClassDB::bind_method(D_METHOD("get_true_bit_count"), &BitMap::get_true_bit_count);
@@ -681,5 +703,3 @@ void BitMap::_bind_methods() {
 }
 
 BitMap::BitMap() {}
-
-//////////////////////////////////////

+ 15 - 12
scene/resources/bit_map.h

@@ -46,9 +46,9 @@ class BitMap : public Resource {
 	int width = 0;
 	int height = 0;
 
-	Vector<Vector2> _march_square(const Rect2i &rect, const Point2i &start) const;
+	Vector<Vector2> _march_square(const Rect2i &p_rect, const Point2i &p_start) const;
 
-	TypedArray<PackedVector2Array> _opaque_to_polygons_bind(const Rect2 &p_rect, float p_epsilon) const;
+	TypedArray<PackedVector2Array> _opaque_to_polygons_bind(const Rect2i &p_rect, float p_epsilon) const;
 
 protected:
 	void _set_data(const Dictionary &p_d);
@@ -57,24 +57,27 @@ protected:
 	static void _bind_methods();
 
 public:
-	void create(const Size2 &p_size);
+	void create(const Size2i &p_size);
 	void create_from_image_alpha(const Ref<Image> &p_image, float p_threshold = 0.1);
 
-	void set_bit(const Point2 &p_pos, bool p_value);
-	bool get_bit(const Point2 &p_pos) const;
-	void set_bit_rect(const Rect2 &p_rect, bool p_value);
+	void set_bitv(const Point2i &p_pos, bool p_value);
+	void set_bit(int p_x, int p_y, bool p_value);
+	void set_bit_rect(const Rect2i &p_rect, bool p_value);
+	bool get_bitv(const Point2i &p_pos) const;
+	bool get_bit(int p_x, int p_y) const;
+
 	int get_true_bit_count() const;
 
-	Size2 get_size() const;
-	void resize(const Size2 &p_new_size);
+	Size2i get_size() const;
+	void resize(const Size2i &p_new_size);
 
-	void grow_mask(int p_pixels, const Rect2 &p_rect);
-	void shrink_mask(int p_pixels, const Rect2 &p_rect);
+	void grow_mask(int p_pixels, const Rect2i &p_rect);
+	void shrink_mask(int p_pixels, const Rect2i &p_rect);
 
-	void blit(const Vector2 &p_pos, const Ref<BitMap> &p_bitmap);
+	void blit(const Vector2i &p_pos, const Ref<BitMap> &p_bitmap);
 	Ref<Image> convert_to_image() const;
 
-	Vector<Vector<Vector2>> clip_opaque_to_polygons(const Rect2 &p_rect, float p_epsilon = 2.0) const;
+	Vector<Vector<Vector2>> clip_opaque_to_polygons(const Rect2i &p_rect, float p_epsilon = 2.0) const;
 
 	BitMap();
 };

+ 3 - 3
scene/resources/texture.cpp

@@ -294,7 +294,7 @@ bool ImageTexture::is_pixel_opaque(int p_x, int p_y) const {
 		x = CLAMP(x, 0, aw);
 		y = CLAMP(y, 0, ah);
 
-		return alpha_cache->get_bit(Point2(x, y));
+		return alpha_cache->get_bit(x, y);
 	}
 
 	return true;
@@ -561,7 +561,7 @@ bool PortableCompressedTexture2D::is_pixel_opaque(int p_x, int p_y) const {
 		x = CLAMP(x, 0, aw);
 		y = CLAMP(y, 0, ah);
 
-		return alpha_cache->get_bit(Point2(x, y));
+		return alpha_cache->get_bit(x, y);
 	}
 
 	return true;
@@ -1017,7 +1017,7 @@ bool CompressedTexture2D::is_pixel_opaque(int p_x, int p_y) const {
 		x = CLAMP(x, 0, aw);
 		y = CLAMP(y, 0, ah);
 
-		return alpha_cache->get_bit(Point2(x, y));
+		return alpha_cache->get_bit(x, y);
 	}
 
 	return true;

+ 445 - 0
tests/scene/test_bit_map.h

@@ -0,0 +1,445 @@
+/*************************************************************************/
+/*  test_bit_map.h                                                       */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 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       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef TEST_BIT_MAP_H
+#define TEST_BIT_MAP_H
+
+#include "core/os/memory.h"
+#include "scene/resources/bit_map.h"
+#include "tests/test_macros.h"
+
+namespace TestBitmap {
+
+void reset_bit_map(BitMap &p_bm) {
+	Size2i size = p_bm.get_size();
+	p_bm.set_bit_rect(Rect2i(0, 0, size.width, size.height), false);
+}
+
+TEST_CASE("[BitMap] Create bit map") {
+	Size2i dim{ 256, 512 };
+	BitMap bit_map{};
+	bit_map.create(dim);
+	CHECK(bit_map.get_size() == Size2i(256, 512));
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "This will go through the entire bitmask inside of bitmap, thus hopefully checking if the bitmask was correctly set up.");
+
+	dim = Size2i(0, 256);
+	bit_map.create(dim);
+	CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 512), "We should still have the same dimensions as before, because the new dimension is invalid.");
+
+	dim = Size2i(512, 0);
+	bit_map.create(dim);
+	CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 512), "We should still have the same dimensions as before, because the new dimension is invalid.");
+
+	dim = Size2i(46341, 46341);
+	bit_map.create(dim);
+	CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 512), "We should still have the same dimensions as before, because the new dimension is too large (46341*46341=2147488281).");
+}
+
+TEST_CASE("[BitMap] Create bit map from image alpha") {
+	const Size2i dim{ 256, 256 };
+	BitMap bit_map{};
+	bit_map.create(dim);
+
+	const Ref<Image> null_img = nullptr;
+	bit_map.create_from_image_alpha(null_img);
+	CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 256), "Bitmap should have its old values because bitmap creation from a nullptr should fail.");
+
+	Ref<Image> empty_img;
+	empty_img.instantiate();
+	bit_map.create_from_image_alpha(empty_img);
+	CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 256), "Bitmap should have its old values because bitmap creation from an empty image should fail.");
+
+	Ref<Image> wrong_format_img;
+	wrong_format_img.instantiate();
+	wrong_format_img->create(3, 3, false, Image::Format::FORMAT_DXT1);
+	bit_map.create_from_image_alpha(wrong_format_img);
+	CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 256), "Bitmap should have its old values because converting from a compressed image should fail.");
+
+	Ref<Image> img;
+	img.instantiate();
+	img->create(3, 3, false, Image::Format::FORMAT_RGBA8);
+	img->set_pixel(0, 0, Color(0, 0, 0, 0));
+	img->set_pixel(0, 1, Color(0, 0, 0, 0.09f));
+	img->set_pixel(0, 2, Color(0, 0, 0, 0.25f));
+	img->set_pixel(1, 0, Color(0, 0, 0, 0.5f));
+	img->set_pixel(1, 1, Color(0, 0, 0, 0.75f));
+	img->set_pixel(1, 2, Color(0, 0, 0, 0.99f));
+	img->set_pixel(2, 0, Color(0, 0, 0, 1.f));
+
+	// Check different threshold values.
+	bit_map.create_from_image_alpha(img);
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 5, "There are 5 values in the image that are smaller than the default threshold of 0.1.");
+
+	bit_map.create_from_image_alpha(img, 0.08f);
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 6, "There are 6 values in the image that are smaller than the threshold of 0.08.");
+
+	bit_map.create_from_image_alpha(img, 1);
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "There are no values in the image that are smaller than the threshold of 1, there is one value equal to 1, but we check for inequality only.");
+}
+
+TEST_CASE("[BitMap] Set bit") {
+	Size2i dim{ 256, 256 };
+	BitMap bit_map{};
+
+	// Setting a point before a bit map is created should not crash, because there are checks to see if we are out of bounds.
+	bit_map.set_bitv(Point2i(128, 128), true);
+
+	bit_map.create(dim);
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "All values should be initialized to false.");
+	bit_map.set_bitv(Point2i(128, 128), true);
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 1, "One bit should be set to true.");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 128)) == true, "The bit at (128,128) should be set to true");
+
+	bit_map.set_bitv(Point2i(128, 128), false);
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "The bit should now be set to false again");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 128)) == false, "The bit at (128,128) should now be set to false again");
+
+	bit_map.create(dim);
+	bit_map.set_bitv(Point2i(512, 512), true);
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "Nothing should change as we were trying to edit a bit outside of the correct range.");
+}
+
+TEST_CASE("[BitMap] Get bit") {
+	const Size2i dim{ 256, 256 };
+	BitMap bit_map{};
+
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 128)) == false, "Trying to access a bit outside of the BitMap's range should always return false");
+
+	bit_map.create(dim);
+	CHECK(bit_map.get_bitv(Point2i(128, 128)) == false);
+
+	bit_map.set_bit_rect(Rect2i(-1, -1, 257, 257), true);
+
+	// Checking that range is [0, 256).
+	CHECK(bit_map.get_bitv(Point2i(-1, 0)) == false);
+	CHECK(bit_map.get_bitv(Point2i(0, 0)) == true);
+	CHECK(bit_map.get_bitv(Point2i(128, 128)) == true);
+	CHECK(bit_map.get_bitv(Point2i(255, 255)) == true);
+	CHECK(bit_map.get_bitv(Point2i(256, 256)) == false);
+	CHECK(bit_map.get_bitv(Point2i(257, 257)) == false);
+}
+
+TEST_CASE("[BitMap] Set bit rect") {
+	const Size2i dim{ 256, 256 };
+	BitMap bit_map{};
+
+	// Although we have not setup the BitMap yet, this should not crash because we get an empty intersection inside of the method.
+	bit_map.set_bit_rect(Rect2i{ 0, 0, 128, 128 }, true);
+
+	bit_map.create(dim);
+	CHECK(bit_map.get_true_bit_count() == 0);
+
+	bit_map.set_bit_rect(Rect2i{ 0, 0, 256, 256 }, true);
+	CHECK(bit_map.get_true_bit_count() == 65536);
+
+	reset_bit_map(bit_map);
+
+	// Checking out of bounds handling.
+	bit_map.set_bit_rect(Rect2i{ 128, 128, 256, 256 }, true);
+	CHECK(bit_map.get_true_bit_count() == 16384);
+
+	reset_bit_map(bit_map);
+
+	bit_map.set_bit_rect(Rect2i{ -128, -128, 256, 256 }, true);
+	CHECK(bit_map.get_true_bit_count() == 16384);
+
+	reset_bit_map(bit_map);
+
+	bit_map.set_bit_rect(Rect2i{ -128, -128, 512, 512 }, true);
+	CHECK(bit_map.get_true_bit_count() == 65536);
+}
+
+TEST_CASE("[BitMap] Get true bit count") {
+	const Size2i dim{ 256, 256 };
+	BitMap bit_map{};
+
+	CHECK(bit_map.get_true_bit_count() == 0);
+
+	bit_map.create(dim);
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "Unitialized bit map should have no true bits");
+	bit_map.set_bit_rect(Rect2i{ 0, 0, 256, 256 }, true);
+	CHECK(bit_map.get_true_bit_count() == 65536);
+	bit_map.set_bitv(Point2i{ 0, 0 }, false);
+	CHECK(bit_map.get_true_bit_count() == 65535);
+	bit_map.set_bit_rect(Rect2i{ 0, 0, 256, 256 }, false);
+	CHECK(bit_map.get_true_bit_count() == 0);
+}
+
+TEST_CASE("[BitMap] Get size") {
+	const Size2i dim{ 256, 256 };
+	BitMap bit_map{};
+
+	CHECK_MESSAGE(bit_map.get_size() == Size2i(0, 0), "Unitialized bit map should have a size of 0x0");
+
+	bit_map.create(dim);
+	CHECK(bit_map.get_size() == Size2i(256, 256));
+
+	bit_map.create(Size2i(-1, 0));
+	CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 256), "Invalid size should not be accepted by create");
+
+	bit_map.create(Size2i(256, 128));
+	CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 128), "Bitmap should have updated size");
+}
+
+TEST_CASE("[BitMap] Resize") {
+	const Size2i dim{ 128, 128 };
+	BitMap bit_map{};
+
+	bit_map.resize(dim);
+	CHECK(bit_map.get_size() == dim);
+
+	bit_map.create(dim);
+	bit_map.set_bit_rect(Rect2i(0, 0, 10, 10), true);
+	bit_map.set_bit_rect(Rect2i(118, 118, 10, 10), true);
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 200, "There should be 100 bits in the top left corner, and 100 bits in the bottom right corner");
+	bit_map.resize(Size2i(64, 64));
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 50, "There should be 25 bits in the top left corner, and 25 bits in the bottom right corner");
+
+	bit_map.create(dim);
+	bit_map.resize(Size2i(-1, 128));
+	CHECK_MESSAGE(bit_map.get_size() == Size2i(128, 128), "When an invalid size is given the bit map will keep its size");
+
+	bit_map.create(dim);
+	bit_map.set_bit_rect(Rect2i(0, 0, 10, 10), true);
+	bit_map.set_bit_rect(Rect2i(118, 118, 10, 10), true);
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 200, "There should be 100 bits in the top left corner, and 100 bits in the bottom right corner");
+	bit_map.resize(Size2i(256, 256));
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 800, "There should still be 100 bits in the bottom right corner, and all new bits should be initialized to false");
+	CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 256), "The bitmap should now be 256x256");
+}
+
+TEST_CASE("[BitMap] Grow and shrink mask") {
+	const Size2i dim{ 256, 256 };
+	BitMap bit_map{};
+	bit_map.grow_mask(100, Rect2i(0, 0, 128, 128)); // Check if method does not crash when working with an uninitialised bit map.
+	CHECK_MESSAGE(bit_map.get_size() == Size2i(0, 0), "Size should still be equal to 0x0");
+
+	bit_map.create(dim);
+
+	bit_map.set_bit_rect(Rect2i(96, 96, 64, 64), true);
+
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 4096, "Creating a square of 64x64 should be 4096 bits");
+	bit_map.grow_mask(0, Rect2i(0, 0, 256, 256));
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 4096, "Growing with size of 0 should not change any bits");
+
+	reset_bit_map(bit_map);
+
+	bit_map.set_bit_rect(Rect2i(96, 96, 64, 64), true);
+
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(95, 128)) == false, "Bits just outside of the square should not be set");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(160, 128)) == false, "Bits just outside of the square should not be set");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 95)) == false, "Bits just outside of the square should not be set");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 160)) == false, "Bits just outside of the square should not be set");
+	bit_map.grow_mask(1, Rect2i(0, 0, 256, 256));
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 4352, "We should have 4*64 (perimeter of square) more bits set to true");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(95, 128)) == true, "Bits that were just outside of the square should now be set to true");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(160, 128)) == true, "Bits that were just outside of the square should now be set to true");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 95)) == true, "Bits that were just outside of the square should now be set to true");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 160)) == true, "Bits that were just outside of the square should now be set to true");
+
+	reset_bit_map(bit_map);
+
+	bit_map.set_bit_rect(Rect2i(127, 127, 1, 1), true);
+
+	CHECK(bit_map.get_true_bit_count() == 1);
+	bit_map.grow_mask(32, Rect2i(0, 0, 256, 256));
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 3209, "Creates a circle around the initial bit with a radius of 32 bits. Any bit that has a distance within this radius will be set to true");
+
+	reset_bit_map(bit_map);
+
+	bit_map.set_bit_rect(Rect2i(127, 127, 1, 1), true);
+	for (int i = 0; i < 32; i++) {
+		bit_map.grow_mask(1, Rect2i(0, 0, 256, 256));
+	}
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 2113, "Creates a diamond around the initial bit with diagonals that are 65 bits long.");
+
+	reset_bit_map(bit_map);
+
+	bit_map.set_bit_rect(Rect2i(123, 123, 10, 10), true);
+
+	CHECK(bit_map.get_true_bit_count() == 100);
+	bit_map.grow_mask(-11, Rect2i(0, 0, 256, 256));
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "Shrinking by more than the width of the square should totally remove it.");
+
+	reset_bit_map(bit_map);
+	bit_map.set_bit_rect(Rect2i(96, 96, 64, 64), true);
+
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(96, 129)) == true, "Bits on the edge of the square should be true");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(159, 129)) == true, "Bits on the edge of the square should be true");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(129, 96)) == true, "Bits on the edge of the square should be true");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(129, 159)) == true, "Bits on the edge of the square should be true");
+	bit_map.grow_mask(-1, Rect2i(0, 0, 256, 256));
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 3844, "Shrinking by 1 should set 4*63=252 bits to false");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(96, 129)) == false, "Bits that were on the edge of the square should now be set to false");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(159, 129)) == false, "Bits that were on the edge of the square should now be set to false");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(129, 96)) == false, "Bits that were on the edge of the square should now be set to false");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(129, 159)) == false, "Bits that were on the edge of the square should now be set to false");
+
+	reset_bit_map(bit_map);
+
+	bit_map.set_bit_rect(Rect2i(125, 125, 1, 6), true);
+	bit_map.set_bit_rect(Rect2i(130, 125, 1, 6), true);
+	bit_map.set_bit_rect(Rect2i(125, 130, 6, 1), true);
+
+	CHECK(bit_map.get_true_bit_count() == 16);
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(125, 131)) == false, "Bits that are on the edge of the shape should be set to false");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(131, 131)) == false, "Bits that are on the edge of the shape should be set to false");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(125, 124)) == false, "Bits that are on the edge of the shape should be set to false");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(130, 124)) == false, "Bits that are on the edge of the shape should be set to false");
+	bit_map.grow_mask(1, Rect2i(0, 0, 256, 256));
+	CHECK(bit_map.get_true_bit_count() == 48);
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(125, 131)) == true, "Bits that were on the edge of the shape should now be set to true");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(131, 130)) == true, "Bits that were on the edge of the shape should now be set to true");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(125, 124)) == true, "Bits that were on the edge of the shape should now be set to true");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(130, 124)) == true, "Bits that were on the edge of the shape should now be set to true");
+
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(124, 124)) == false, "Bits that are on the edge of the shape should be set to false");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(126, 124)) == false, "Bits that are on the edge of the shape should be set to false");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(124, 131)) == false, "Bits that are on the edge of the shape should be set to false");
+	CHECK_MESSAGE(bit_map.get_bitv(Point2i(131, 131)) == false, "Bits that are on the edge of the shape should be set to false");
+}
+
+TEST_CASE("[BitMap] Blit") {
+	Point2i blit_pos{ 128, 128 };
+	Point2i bit_map_size{ 256, 256 };
+	Point2i blit_size{ 32, 32 };
+
+	BitMap bit_map{};
+	Ref<BitMap> blit_bit_map{};
+
+	// Testing null reference to blit bit map.
+	bit_map.blit(blit_pos, blit_bit_map);
+
+	blit_bit_map.instantiate();
+
+	// Testing if uninitialised blit bit map and uninitialised bit map does not crash
+	bit_map.blit(blit_pos, blit_bit_map);
+
+	// Testing if uninitialised bit map does not crash
+	blit_bit_map->create(blit_size);
+	bit_map.blit(blit_pos, blit_bit_map);
+
+	// Testing if uninitialised bit map does not crash
+	blit_bit_map.unref();
+	blit_bit_map.instantiate();
+	CHECK_MESSAGE(blit_bit_map->get_size() == Point2i(0, 0), "Size should be cleared by unref and instance calls.");
+	bit_map.create(bit_map_size);
+	bit_map.blit(Point2i(128, 128), blit_bit_map);
+
+	// Testing if both initialised does not crash.
+	blit_bit_map->create(blit_size);
+	bit_map.blit(blit_pos, blit_bit_map);
+
+	bit_map.set_bit_rect(Rect2i{ 127, 127, 3, 3 }, true);
+	CHECK(bit_map.get_true_bit_count() == 9);
+	bit_map.blit(Point2i(112, 112), blit_bit_map);
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 9, "No bits should have been changed, as the blit bit map only contains falses");
+
+	bit_map.create(bit_map_size);
+	blit_bit_map->create(blit_size);
+	blit_bit_map->set_bit_rect(Rect2i(15, 15, 3, 3), true);
+	CHECK(blit_bit_map->get_true_bit_count() == 9);
+
+	CHECK(bit_map.get_true_bit_count() == 0);
+	bit_map.blit(Point2i(112, 112), blit_bit_map);
+	CHECK_MESSAGE(bit_map.get_true_bit_count() == 9, "All true bits should have been moved to the bit map");
+	for (int x = 127; x < 129; ++x) {
+		for (int y = 127; y < 129; ++y) {
+			CHECK_MESSAGE(bit_map.get_bitv(Point2i(x, y)) == true, "All true bits should have been moved to the bit map");
+		}
+	}
+}
+
+TEST_CASE("[BitMap] Convert to image") {
+	const Size2i dim{ 256, 256 };
+	BitMap bit_map{};
+	Ref<Image> img;
+
+	img = bit_map.convert_to_image();
+	CHECK_MESSAGE(img.is_valid(), "We should receive a valid Image Object even if BitMap is not created yet");
+	CHECK_MESSAGE(img->get_format() == Image::FORMAT_L8, "We should receive a valid Image Object even if BitMap is not created yet");
+	CHECK_MESSAGE(img->get_size() == (Size2i(0, 0)), "Image should have no width or height, because BitMap has not yet been created");
+
+	bit_map.create(dim);
+	img = bit_map.convert_to_image();
+	CHECK_MESSAGE(img->get_size() == dim, "Image should have the same dimensions as the BitMap");
+	CHECK_MESSAGE(img->get_pixel(0, 0).is_equal_approx(Color(0, 0, 0)), "BitMap is intialized to all 0's, so Image should be all black");
+
+	reset_bit_map(bit_map);
+	bit_map.set_bit_rect(Rect2i(0, 0, 128, 128), true);
+	img = bit_map.convert_to_image();
+	CHECK_MESSAGE(img->get_pixel(0, 0).is_equal_approx(Color(1, 1, 1)), "BitMap's top-left quadrant is all 1's, so Image should be white");
+	CHECK_MESSAGE(img->get_pixel(256, 256).is_equal_approx(Color(0, 0, 0)), "All other quadrants were 0's, so these should be black");
+}
+
+TEST_CASE("[BitMap] Clip to polygon") {
+	const Size2i dim{ 256, 256 };
+	BitMap bit_map{};
+	Vector<Vector<Vector2>> polygons;
+
+	polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 128, 128));
+	CHECK_MESSAGE(polygons.size() == 0, "We should have no polygons, because the BitMap was not initialized");
+
+	bit_map.create(dim);
+	polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 128, 128));
+	CHECK_MESSAGE(polygons.size() == 0, "We should have no polygons, because the BitMap was all 0's");
+
+	reset_bit_map(bit_map);
+	bit_map.set_bit_rect(Rect2i(0, 0, 64, 64), true);
+	polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 128, 128));
+	CHECK_MESSAGE(polygons.size() == 1, "We should have exactly 1 polygon");
+	CHECK_MESSAGE(polygons[0].size() == 4, "The polygon should have exactly 4 points");
+
+	reset_bit_map(bit_map);
+	bit_map.set_bit_rect(Rect2i(0, 0, 32, 32), true);
+	bit_map.set_bit_rect(Rect2i(64, 64, 32, 32), true);
+	polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 128, 128));
+	CHECK_MESSAGE(polygons.size() == 2, "We should have exactly 2 polygons");
+	CHECK_MESSAGE(polygons[0].size() == 4, "The polygon should have exactly 4 points");
+	CHECK_MESSAGE(polygons[1].size() == 4, "The polygon should have exactly 4 points");
+
+	reset_bit_map(bit_map);
+	bit_map.set_bit_rect(Rect2i(124, 112, 8, 32), true);
+	bit_map.set_bit_rect(Rect2i(112, 124, 32, 8), true);
+	polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 256, 256));
+	CHECK_MESSAGE(polygons.size() == 1, "We should have exactly 1 polygon");
+	CHECK_MESSAGE(polygons[0].size() == 12, "The polygon should have exactly 12 points");
+
+	reset_bit_map(bit_map);
+	bit_map.set_bit_rect(Rect2i(124, 112, 8, 32), true);
+	bit_map.set_bit_rect(Rect2i(112, 124, 32, 8), true);
+	polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 128, 128));
+	CHECK_MESSAGE(polygons.size() == 1, "We should have exactly 1 polygon");
+	CHECK_MESSAGE(polygons[0].size() == 6, "The polygon should have exactly 6 points");
+}
+
+} // namespace TestBitmap
+
+#endif // TEST_BIT_MAP_H

+ 1 - 0
tests/test_main.cpp

@@ -85,6 +85,7 @@
 #include "tests/core/variant/test_variant.h"
 #include "tests/scene/test_animation.h"
 #include "tests/scene/test_audio_stream_wav.h"
+#include "tests/scene/test_bit_map.h"
 #include "tests/scene/test_code_edit.h"
 #include "tests/scene/test_curve.h"
 #include "tests/scene/test_gradient.h"