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.
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)
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?
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.