Godot Alters PNG Colors – How to Preserve Exact Pixel Colors?

Godot Version

4.3

Question

I’m encountering an issue with how Godot processes my PNG image. I have a map where each country is assigned a specific, unique color (e.g., France = #00bbd4, Italy = #ff3131, etc.). However, when I load the scenery, the colors seem to change slightly(the color of a country which was first set to one specific color, is now set to round about 30 similar colors), which then interferes with how i like to display the map and the countries.
I have a Sprite2D where i set the “Texture” to my png. I have a regions.txt file which is a dictionary where the colors are described:
{
#00bbd4” : “France”
,“#ff3131” : “Italy”
,“#f1c30f” : “Spain”
,“#00cc00” : “Berber”
}

my main.gd file processes the image:

extends Node2D

@onready var mapImage = $Sprite2D
func _ready():
	load_regions()

func _process(delta):
	pass

func load_regions():
	var image = mapImage.get_texture().get_image()
	var pixel_color_dict = get_pixel_color_dict(image)
	var regions_dict = import_file("res://Map_data/regions.txt")
	
	for region_color in regions_dict:
		var region = load("res://Scenes/Region_Area.tscn").instantiate()
		region.region_name = regions_dict[region_color]
		region.set_name(region_color)
		get_node("Regions").add_child(region)
		
		var polygons = get_polygons(image, region_color, pixel_color_dict)
	
		for polygon in polygons:
			var region_collision = CollisionPolygon2D.new()
			var region_polygon = Polygon2D.new()
			
			region_collision.polygon = polygon
			region_polygon.polygon = polygon
			
			region.add_child(region_collision)
			region.add_child(region_polygon)
	mapImage.queue_free()

func get_pixel_color_dict(image):
	var pixel_color_dict = {}
	for y in range(image.get_height()):
		for x in range(image.get_width()):
			var pixel_color = "#" + str(image.get_pixel(int(x), int(y)).to_html(false))
			if pixel_color not in pixel_color_dict:
				pixel_color_dict[pixel_color] = []
			pixel_color_dict[pixel_color].append(Vector2(x,y))
	return pixel_color_dict

func get_polygons(image, region_color, pixel_color_dict):
	var targetImage = Image.create(image.get_size().x,image.get_size().y, false, Image.FORMAT_RGBA8)
	for value in pixel_color_dict[region_color]:
		targetImage.set_pixel(value.x,value.y, "#ffffff")
		
	var bitmap = BitMap.new()
	bitmap.create_from_image_alpha(targetImage)
	var polygons = bitmap.opaque_to_polygons(Rect2(Vector2(0,0), bitmap.get_size()), 0.1)
	return polygons

#Import JSON files and converts to lists or dictionary
func import_file(filepath):
	var file = FileAccess.open(filepath, FileAccess.READ)
	if file != null:
		return JSON.parse_string(file.get_as_text().replace("_", " "))
	else:
		print("Failed to open file:", filepath)
		return null

My question:
How can I ensure that Godot displays and processes my PNG exactly as it is from the Original, without altering colors in any way? Is there a specific import setting or workaround that guarantees the image remains unchanged?

Any help or insights would be greatly appreciated!
Thanks in advance.

1 Like

Which renderer are you using? I initially used the mobile renderer because I was hoping it would run on lower spec machines, but I eventually found I had to switch to forward+ for a variety of reasons, including color reproduction and alpha quantization.

Make sure that the image is saved in a loseless format (png) and imported with Loseless compression.

i did but it does not do the trick.

yes i am using forward+ as renderer but it still wont work. any other settings that could affect the import and rendering i could change?

By any chance, does your PNG have color space management data in it?

sRGB is the current color space. Not sure if thats what you meant.

It is what I mean, and if you’re applying a color space it’s going to try to alter the image colors to fit that color space. You might want to try stripping that out of the PNG and see if that fixes your problem. The color space stuff is absolutely going to change your source image colors to try to match the profile of your output device; that’s it’s job.

excuse for my lack of knowledge. but stripping the png from the color space, that would completely delete the colors from my image, correct or do i need to change it to a different color space that Godot can handle? because what i want is to keep the colors from my png and display them in Godot as they are. Is there a way for Godot to preprocess the image to handle those colors? Or how must i best proceed now?

Color spaces are optional, and for games they can be detrimental.

Your PNG has per-pixel colors in it. How those colors are stored depends on how the PNG was created, but roughly speaking, it has zlib-compressed scan lines of pixels, and each of those pixels has a color.

Pixel color in PNG files these days are mostly direct (IIRC, usually 8 bits per channel RGB or RGBA). Each of those channels is a value from 0 to 255 (for the 8 bit channels, anyways), or logically an intensity value from 0.0 to 1.0. So, if you have a pixel that’s (128, 192, 0, 255) in an RGBA pixel, that’ll be half intensity red, full intensity green, no blue, and full alpha, mixed. So, kind of a lime green, ish?

The problem that color spaces are meant to solve is, what does 0.5 red mean?

You’ve got three values for colors and possibly a fourth for transparency. Are they linear? Logarithmic? Some other curve? Do your monitor, your video card and your color printer agree about that? How about your camera?

Color spaces take the channel values in pixels and try to map them onto hardware so that if you display the same color on two different things (like, say, your phone and your PC) it looks like the same color.

So, the color space is explicitly about taking the raw pixel colors and remapping them for whatever device you have. If you get rid of the color space mapping, it just means your image feeds its raw color values into the video card, and the video card puts out whatever color it thinks those represent. Which I think is what you want in this case.

Stripping the color space out of the PNG is just removing the remapping info, not the core image or its pixel colors.

2 Likes

yeah turned out Godot couldnt deal with the used color space. So i changed the color space in my map to a color space that i knew Godot could accept.

Btw is there some documentation in Godot about what color spaces it can deal with?

I’m not sure if/where color space support for Godot is documented; you might try a bug report and see what the Godot devs say.