How To Get Average Colour Of An Image?

Godot Version

4.3

Question

I am working on something which involves getting a file at run-time, and I want to display that average colour of that image on a background somewhere, so I went and looked up how to do that and ended up writing this code;

var image : Image = load("...").get_image()
var colour : Vector3 = Vector3(0, 0, 0)
	for i in range(0, image.get_size().x):
		for i2 in range(0, image.get_size().y):
			var pixel : Color = image.get_pixelv(Vector2i(i, i2))
			colour.x += pixel.r
			colour.y += pixel.g
			colour.z += pixel.b
	colour[0] /= (image.get_size()[0] + image.get_size()[1])
	colour[1] /= (image.get_size()[0] + image.get_size()[1])
	colour[2] /= (image.get_size()[0] + image.get_size()[1])
	%Background.color = Color(colour[0], colour[1], colour[2])

And I don’t even know if this works because it is so slow that after multiple seconds it had only gotten to an X of around 5 (the image I was using to test was 316x316).

So is there either a built-in way of doing this or a way faster method to get the average colour of an image? As this function is supposed to run semi-frequently.

Thanks.

It’s probably faster to get the pixels of the Image via get_data() and use the PackedByteArray directly. By the way, instead of

var pixel : Color = image.get_pixelv(Vector2i(i, i2))

you could write

var pixel := image.get_pixel(i, i2)

so that would remove one indirection already.

Not sure if applicable to your usecase, but I think to get the average color of a picture you could resize it to 1*1 and only use that single pixel.

1 Like

I tried it as well, but I reversed the for loops. This might make a difference here, since the cpu can cache the rows easier than the columns.
I also tried both c# and gdscript, both are roughly equal in speed and dont take too long. I even tried the following big image from wallhaven, this took less than 2 seconds for me, most of the time was starting the scene

What are your components for your computer/ laptop?

here is my code:
The scene is a sprite2d with this script, with a colorRect child to test the color

@export var colorRect: ColorRect

func _ready() -> void:
	var color := Vector3.ZERO
	var texture_size := texture.get_size()
	var image := texture.get_image()
	
	for y in range(0, texture_size.y):
		for x in range(0, texture_size.x):
			var pixel := image.get_pixel(x, y)
			color += Vector3(pixel.r, pixel.g, pixel.b)
			
	color /= texture_size.x * texture_size.y

	colorRect.color = Color(color.x, color.y, color.z)


1 Like

Thank you so much!

This worked wonders, being near instant when I run it, also I never knew you could do math using vectors directly, I always assumed you had to access and re-write every value individually which is very helpful.

Also I tested with doing the for-loop forwards and backwards and couldn’t notice any significant time difference, it is interesting what you said about being able to cache rows easier then columns; why is that? since they are both an array of numbers I would think they would be the same speed?

Using the texture modulo function should work faster than put pixel, and get pixel. It should be material function.

I know this has been closed, but a few ideas to think about if you want any more speed doing this, and some things popped in my head, and may be handy if on a lower spec release like mobile.

One is that if these images are already in the build or project, I’d create a simple resource that includes both the image reference and a single color value that you can calculate before hand (like in photoshop). This would bypass any computation at runtime all together.

If the textures are in the build/project and you have control over their import, you could also have the image source in godot create mipmaps then when you access the packed byte array through the get data you get the mipmap data instead an loop through that. If you know what the lowest mipmap index is, and it’s resolution, you can loop through those really fast. The existing mipmaps should do a lot of the color averaging for you. I am not sure though that mipmaps in godot support non-power of 2 textures though.

Another idea is to compare resizing the texture before looping through the pixels to average using Image.resize() using interpolation, or even Image.shrink_x2() if that function is way faster. Maybe the cost of the resize offsets the cost of looping through all of the colors.

You could also skip pixels to a degree to loop through a lot less, like if you took every 4th pixel you’d probably get a color value that is really close to what you’d get if you sampled them all.

The difference lies in memory layout and cache utilization.
In most programming languages (e.g., C, Python, C#), 2D arrays are stored in row-major order, meaning:

  • Elements of a row (e.g., image[0][x]) are stored contiguously in memory.
  • Rows are stored sequentially.

Modern CPUs use cache lines (typically 64 bytes) to fetch chunks of memory:

  • Accessing consecutive memory locations is fast since they fit within the same cache line.
  • Accessing non-contiguous locations (e.g., across columns in row-major order) leads to cache misses, slowing execution.

Row-first (y outer loop , x inner loop):
Accessing image[x][y] traverses contiguous memory, maximizing cache efficiency.

While Column-first (x outer loop, y inner loop):
Accessing image[x][y] jumps across rows, disrupting spatial locality and causing more cache misses.

Hope that explains why you should do it like so.

I know you said there is no significant time difference. But I think there is a difference, but you would have to time that to see it.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.