Super fast code to find the average color of an image

Hello,

It’s been a while since I’ve written code in Codea, so I thought it might be fun to test this quick concept I had to find teh average color of an image. Here’s the code:

displayMode(FULLSCREEN)

function setup()
    cameraSource(CAMERA_BACK)
end

function draw()
    local cameraImage = image(CAMERA)
    if cameraImage then
        background(averageColor(cameraImage))
        
        pushMatrix()
        translate(WIDTH/2, HEIGHT/2)
        scale(0.5)
        sprite(cameraImage, 0, 0)
        popMatrix()
    else
       print("No camera") 
    end
    --text(math.ceil(DeltaTime*60), 100, 100)
end

function averageColor(img)
    --[[
    How this works:
    OpenGL has built in smoothing capabilities. For instance, if you scale an image very large (with smoothing on), you'll see it slightly blurred, not every single pixel.
    This is very close to the graphics chip, meaning it's very, very fast. So, we can take advantage of that by drawing an image into a 1x1 pixel, making OpenGL do all the work finding the average color, as it tries to smooth the image.
    ]]
    local smallImage = image(1,1)
    setContext(smallImage)
    pushStyle()
    smooth()
    sprite(img,0,0,1,1)
    popStyle()
    setContext()
    return smallImage:get(1,1)
end

You can read a brief explenation in the comment in the code on how it works. I may not have all the terminoligy correct, so feel free to correct me, or if you want, feel free to find a way to make the code run even faster.

Thanks,
Nathan

Nice idea, but I don’t think it works very well.

If you try it on some of the built in Codea images which have transparent pixels around them, the function returns zero, and even where it doesn’t, it isn’t very close to the true arithmetic average (yep, I wrote a double loop to calculate actual average).

I’m not sure what the purpose of the average color of an image gives. A zillion different images can have the same average color or am I interpreting this wrong.

Maybe something like this can be done.

http://mkweb.bcgsc.ca/color_summarizer/

@Ignatz @dave1707 As I said, it’s simply a concept I got it from here and I wanted to try it in Codea. But thanks for the feedback, I hadn’t actually looked at the true average color and compared it. I did notice some odd behaviors.

If you just need something fast and efficient, I would say this is probably a fine solution, since I presume it’s much faster than looping through every pixel in the image and calling image:get().

Thanks,
Nathan

@Zoyt I like programs like you posted. I like to be able to prove if they really work or not. I took your code and added some parameters and other code to it. I then removed code that had no effect on it and condense it to make it easier for me to see what’s really happening. Here’s the result. Unfortunately it doesn’t seem to work. I started out with a full screen of red (255,0,0,255). The values I got from get(1,1) was (255,0,0,255). At that point I thought it was working. I then added a white rectangle that I could alter the size and also move it around the screen. I figured that as I altered the size, the values returned from get(1,1) should vary. They did, but not the way they should. Also, as I moved the rectangle around without changing the size, the values also changed. If it was a true average, the values should be the same as the same size rectangle is moved around. But that didn’t happen. The values jumped as it was moved around. I guess I’ll look at the original article and see what’s there. I hope I’m doing everything right, I hate to say something is wrong when it was really me.


function setup()
    w,h=100,100
    x,y=0,0
    parameter.integer("w",0,WIDTH,100,setup1)
    parameter.integer("h",0,HEIGHT,100,setup1)
    parameter.integer("x",0,WIDTH,0,setup1)
    parameter.integer("y",0,HEIGHT,0,setup1)
end

function setup1()
    get=true
    img=image(WIDTH,HEIGHT) -- set image for current full screen
    setContext(img)
    background(255,0,0,255) -- set the full screen color to red
    fill(255)
    rect(x,y,w,h) -- add a white rectangle
    setContext()
end

function draw()
    background(40,40,50)
    -- get the average only once each color change
    if get then
        get=false
        smallImage = image(1,1)
        setContext(smallImage)
        sprite(img,0,0,1,1)
        setContext()
        r,g,b,a=smallImage:get(1,1)
        output.clear()
        print("red",r)
        print("green",g)
        print("blue",b)
        print("alpha",a)
    end
    sprite(img,WIDTH/2,HEIGHT/2)
end

If I make the rectangle w and h a value of 100 and move it to x and y position of 500, the result is 255,255,255,255 which is just the color of the white rectangle. So it looks like it’s just getting the color from the upper right area of the screen.

@dave1707 - Yup. Pretty much. Doesn’t quite work perfectly.

@Zoyt I went and looked at the link you posted that had the original code. I read some of the comments and the last one also said he thought it wasn’t working and just getting the value from the upper left of the image. Even if it doesn’t work the way it should, I found it very interesting and gave me something to work on.