Convert to grayscale?

Any way to make an overlay that changes the screen from color to grayscale? Or some shader that does the same? Or, if neither of those, how do you convert color(r,g,b,a) to grayscale? I tried testing things, but can’t seem to get it

So could i then setContext() to a mesh? I dont have my ipad, so i cant test it right now.

@JakAttak - it doesn’t matter, an image variable is just a number (memory address of the image), not a copy of the actual image.

@Ignatz, I know this (its why changes to img are applied to the mesh) I just think it’s cleaner to say directly to the mesh texture (also helps with Code readability at a glance) But that’s all personal preference.

And your little mini convo just taught me something, thank you! Off topic, but whats your profile image @JakAttak

There are a number of methods - but the simplest is simply ‘Averaging’ the values: (r + g +b ) / 3 and then copying the result to each r,g,b channel. :smiley:

Here are three ways to do it. Taken from a google search.

The lightness method averages the most prominent and least prominent colors: (max(R, G, B) + min(R, G, B)) / 2.

The average method simply averages the values: (R + G + B) / 3.

The luminosity method is a more sophisticated version of the average method. It also averages the values, but it forms a weighted average to account for human perception. We’re more sensitive to green than other colors, so green is weighted most heavily. The formula for luminosity is 0.21 R + 0.72 G + 0.07 B.

Thank you, @andymac3d! Not sure if you know, but you can, instead of copying the average into all three just do color(average) and it’ll come out as a grayscale.
Also thank you to @dave1707! I looked up that luminosity thing but I my search came up with different values (0.2989, 0.5870, 0.1140). How would I factor alpha into that? And is there a way to make that into a shader that lays over the screen and turns it from color to grayscale?

No worries @Monkeyman32123 - alpha doesn’t influence these calculations as it purely should deal with the colour components only.

@Monkeyman32123 I’m sure a shader can be used, but I don’t have an example for that.

@Monkeyman32123 - it would be simple to write a shader for this, but a shader only works on meshes, so you would have to set up a mesh for the whole screen and include everything you draw in there, or draw everything as separate meshes.

Or you could draw everything to an image in memory with setContext, and draw that as a mesh - but using setContext to draw everything is likely to be very slow.

EDIT - however, you could use setContext and a shader to make (and store) grayscale copies of the images you are using, then you could draw these instead of the original images. This would avoid slowing down your program.

@Monkeyman32123 Here’s a shader example. I don’t know what I’m doing, so this works but may not be totally correct. Just run the slider from 0 to 1 to go from color to grey.


function setup() 
    parameter.integer("grey",0,1,0)
    m=mesh()
    img=readImage("Cargo Bot:Startup Screen")
    m:addRect(WIDTH/2,HEIGHT/2,img.width/2,img.height)
    m:setColors(color(255))
    m.texture=img 
    m.shader=shader(DefaultShader.vertexShader,DefaultShader.fragmentShader)
end

function draw() 
    background(40, 40, 50) 
    m.shader.grey=grey 
    m:draw()
end

DefaultShader = 
    {   vertexShader = [[
        uniform mat4 modelViewProjection;
        attribute vec4 position; 
        attribute vec4 color; 
        attribute vec2 texCoord;
        varying lowp vec4 vColor; 
        varying highp vec2 vTexCoord;
    
        void main() 
        { vColor=color;
            vTexCoord = texCoord;
            gl_Position = modelViewProjection * position;
        }    ]],
   
        fragmentShader = [[
        precision highp float;
        uniform lowp sampler2D texture;
        varying lowp vec4 vColor; 
        varying highp vec2 vTexCoord;
        uniform float grey;
        
        void main() 
        {   lowp vec4 col = texture2D( texture, vTexCoord) * vColor;
            float avg=0.0;
            float th=3.0;
            if (grey == 1.0)
            { avg=(col.r+col.g+col.b)/th;
              col.r=avg;
              col.g=avg;
              col.b=avg;
            }
            gl_FragColor = col; 
        }    ]]
    }

@Monkeyman32123 - out of interest, here is a shader that uses the luminosity method. You tell it which numbers to use.

function setup()
    --choose a colour image
    a=readImage("Planet Cute:Character Princess Girl")  
    -- now we'll make a grayscale version
    b=mesh() 
    b:addRect(0,0,a.width,a.height) --size of image
    b.shader=shader(GrayScaleShader.v,GrayScaleShader.f)
    b.shader.texture=a --the image to draw
    b.shader.luminance=vec3(0.21,0.72,0.07) -- you can change these, they must add to 1.0
end

function draw()
    background(220)
    sprite(a,300,300)
    --now the grayscale
    pushMatrix()
    translate(400,300)
    b:draw()
    popMatrix()
end

GrayScaleShader={
v=[[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
    vColor = color;
    vTexCoord = texCoord;
    gl_Position = modelViewProjection * position;
}
]],
f=[[
precision highp float;
uniform lowp sampler2D texture;
uniform vec3 luminance;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
    lowp vec4 col = texture2D( texture, vTexCoord ) * vColor;
    float c = col.r*luminance.x + col.g*luminance.y + col.b*luminance.z;
    gl_FragColor = vec4(c, c, c, col.a );
}      
]]}








Ah, thank you all, you’re life savers!
I think ill go with drawing the screen to a setContext image then grayscaling that (mostly because I’ve no clue how to draw directly onto a mesh)

That is likely to be pretty slow because Codea has to draw everything twice

Drawing to a mesh isn’t hard if you use setRect to add images to it, but I guess it takes getting used to. You should learn how to do it, though.

I’m not sure if is is correct, i dont have much experience with meshes, but i think setContext() draws to images, so you would then use that image and add it to a rectangle which the shader would act on, I’m not sure if this i correct though.

You would do it like this (the FPS lines are just there to measure speed)

function setup()
    --test images
    a=readImage("Planet Cute:Character Princess Girl")  
    b=readImage("Planet Cute:Character Boy")
    x=0
    FPS=60
    parameter.integer("FPS",0,60,60) --measure speed
    --you can replace everything above with your own code, but you need this...
    --set up mesh
    m=mesh() 
    m:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    img=image(WIDTH,HEIGHT)
    m.texture=img
    m.shader=shader(GrayScaleShader.v,GrayScaleShader.f)
    m.shader.luminance=vec3(0.21,0.72,0.07)
end

function draw()
    FPS=FPS*0.9+.1/DeltaTime
    --draw to image in memory
    setContext(img)
        --put your drawing code in here
        background(220)
        x=x+1
        sprite(a,x-100,300)
        sprite(b,x-100,420)
        sprite(a,x,300)
        sprite(b,x,420)
        sprite(a,100+x,300)
        sprite(b,100+x,420)
        -- end of your drawing code
    setContext()
    m:draw()
end

GrayScaleShader={
v=[[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
    vColor = color;
    vTexCoord = texCoord;
    gl_Position = modelViewProjection * position;
}
]],
f=[[
precision highp float;
uniform lowp sampler2D texture;
uniform vec3 luminance;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
    lowp vec4 col = texture2D( texture, vTexCoord ) * vColor;
    float c = col.r*luminance.x + col.g*luminance.y + col.b*luminance.z;
    gl_FragColor = vec4(c, c, c, col.a );
}      
]]}

I would have never gotten that, thank you so much @Ignatz; and even better, I actually understand your code!

could you also just do setContext(m.texture) so you don’t need to have the other image variable?

@Monkeyman32123, my avatar is a screenshot of something I made in Codea that I thought was cool :slight_smile:

EDIT: after loading your cool line thing project, it is actually similar to what I was doing. Really fun stuff (nice work btw)