• Resources
  • Fix for Pixel bleed in Imagetextures with multiple frames

This function is to help with the sort of Pixel bleed issues as reported in various places such as Here. The usual effect is lines of pixels from the adjacent frames in a texture grid "bleeding" into a frame which produces ugly lines at the edges of the frames. The effect is most visible in tilemaps.

According to the final post in the above link the "Clip UV" option on tilemaps should fix this but I have foundthe effect still occurs in my project, possibly because I am creating my Images dynamically at run time.

The function below adds one or more extra pixels around each frame in an image with a copy of the edge pixels of the frame, any bleed will therefore match the edge pixels so not be noticable.

It is called with an image and the width and height of the frame(s), optionally you can set the number of pixels to add.

func image_add_boundary_padding(source: Image, cell_width: int, cell_height: int, pixels: int = 1):
	# Repeats the boundary of each cell in the "source" image grid for "pixels" pixels.
	var cells_x := ceil(source.get_width() / cell_width)
	var cells_y := ceil(source.get_height() / cell_height)
	var result:= Image.new()
	result.create(source.get_width() + (2 * cells_x * pixels), source.get_height(), 0, source.get_format())
	for x in range(0, cells_x + 1):
		var source_x : int = x * cell_width
		for pix in range(0, pixels):
			result.blit_rect(source, Rect2(source_x, 0, cell_width, source.get_height()), 
				Vector2(source_x + (x * pixels * 2) + pix, 0))
			result.blit_rect(source, Rect2(source_x, 0, cell_width, source.get_height()), 
				Vector2(source_x + (x * pixels * 2) + (pixels * 2 - pix), 0))
		result.blit_rect(source, Rect2(source_x, 0, cell_width, source.get_height()), 
			Vector2(source_x + (x * pixels * 2) + pixels, 0))
	
	source.copy_from(result)
	result.create(source.get_width(), source.get_height() + (2 * cells_y * pixels), 0, source.get_format())
	for y in range(0, cells_y + 1):
		var source_y : int = y * cell_height
		for pix in range(0, pixels):
			result.blit_rect(source, Rect2(0, source_y, source.get_width(), cell_height), 
				Vector2(0, source_y + (y * pixels * 2) + pix))
			result.blit_rect(source, Rect2(0, source_y, source.get_width(), cell_height), 
				Vector2(0, source_y + (y * pixels * 2) + (pixels * 2 - pix)))
		result.blit_rect(source, Rect2(0, source_y, source.get_width(), cell_height), 
			Vector2(0, source_y + (y * pixels * 2) + pixels))
		
	return result

Example call (pseudo code):

	var img := Image.new()
	var imgtex := ImageTexture.new()
	# Adjust the image
	img = image_add_boundary_padding(tile_set.tile_get_texture(tile_number).get_data(), 
		cell_size.x, cell_size.y, border_size)
	imgtex.create_from_image(img)
	tile_set.tile_set_texture(tile_number, imgtex)

If you checked the post in issue 8084 you'll see that the image has been modified to account for the Clip UV feature too. So the solution is two-fold. So yes, if you are creating the textures during run-time and loading images that haven't been specifically created for this use-case then this is the correct way to handle it.

Great contribution, thanks. :)

Ah, thanks, I think I mis-read it. Cheers :)