How can I colorize a texture without losing white?

If I have the following texture:
black & white arrow example

How can I colorize this but not completely tint the texture so that the white in it becomes purple or red?

desired output as purple:
purple arrow example
desired output as red:
red arrow example

Just a bit of background, this is for an iOS game I am making with Cocos2d which uses OpenGL ES 2.0 under the hood. Originally, I was using 7 different colored texture files, which were identical other than their color, which was very wasteful… So I have been trying to explore ways to programmatically change the colors… My first attempt was to use Apple’s CoreImage filters prior to loading the textures into openGL, and it works— but there is a horrific performance lag when loading my textures now, so I was wondering how easy/difficult of a task this would be with openGL?

The method at line 119 is how the game library loads textures:
https://searchcode.com/codesearch/view/73755093/

I found this question which is exactly what I am wanting:
http://stackoverflow.com/a/5370471/594763

However, I do not understand the answer at all-- not to mention the code example in the accepted answer involving multi-texturing is for OpenGL ES 1.0.

Anyway, after you premultiply, just bind the byte data as an RGBA OpenGL texture. Using glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD); with a single texture unit should be all you need after that. You should get exactly (or pretty damn close) to what you want.

How do you apply a color to this texture? How would I turn it green, yellow, purple, red, etc?

Use a vec4 uniform for the color you want to set to your arrow. Each time the color change, update the uniform and send it to your shader.
In your fragment shader multiply the result of your arrow texture with this uniform.

assuming you have a “fragmentshader” which samples from that texture, you can easily multiply the sampled texel with your desired color

fragmentshader example:


in vec2 sometexcoords;
uniform vec4 desiredcolor;
uniform sampler2D arrow;
out vec4 outcolor;

void main() {
vec4 texel = texture(arrow, sometexcoords);
outcolor = texel * desiredcolor;
}

if the texel at that texture location (described by sometexcoords) is black, then the result wil be black
if the texel at that texture location (described by sometexcoords) is white, then the result wil be desiredcolor

to make black disappear, enable blending and add the result with the color already written in te framebuffer
https://www.opengl.org/wiki/Blending#Blend_Equations

[QUOTE=john_connor;1284535]assuming you have a “fragmentshader” which samples from that texture, you can easily multiply the sampled texel with your desired color
[/QUOTE]

Hmmm, well I just tried what you suggested but that removes all luminance…


#ifdef GL_ES
precision lowp float;
#endif

varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
uniform vec4 u_desiredColor;
uniform sampler2D u_texture;

void main()
{
    gl_FragColor = texture2D(u_texture, v_texCoord) * u_desiredColor;
}

This outputs:
My results of fragment shader with red tint

Again what I want is something that looks as close to this as possible:
My desired results

To explain further what I want: The darker shades of gray should have the richest color, but as the sampled grayscale pixel approaches white, the colorized pixels lose saturation, where ultimately a sampled pixel of white has 0% saturation-- but a sampled pixel of dark gray would be 100% saturation.

A really simple starting point would just be to have the output pixel color’s saturation be something like 1.0 - samplePixelIntensity… So, if the sample pixel’s intensity was 1.0, that would mean 0% saturation in the output, if the sample pixel’s intensity was 0.5, it would have 50% saturation… if the sample pixel’s intensity was 0.2, then it would have 80% saturation…

Can I achieve this with a fragment shader?

If I understand you correctly, you want to map the black->grey->white progression in the source texture to a black->(colour)->white progression, for a colour chosen at run time.

One way to do this is with a palette stored e.g. as a 1D texture. Read a pixel from the source texture, use one of the components (all three should have the same value) as the texture coordinate for the 1D texture. I.e.


in vec2 texcoord;
uniform sampler2D tex;
uniform sampler1D palette;

void main()
{
    float intensity = texture(tex, texcoord).r;
    vec3 color = texture(palette, intensity);
    gl_FragColor = vec4(color, 1);
}

For a palette which is a smooth gradient, you can use a function instead of a 1D texture. E.g.


vec3 blend(vec3 color, float t)
{
    float a = clamp(2*t,0,1);
    float c = clamp(2*(1-t),0,1);
    float b = 1-a-c;
    return b*color+c; // = a*vec3(0,0,0)+b*color+c*vec3(1,1,1)
}

or


vec3 blend(vec3 color, float t)
{
    float a = smoothstep(0.0,0.5,t);
    float c = smoothstep(0.5,1.0,t);
    float b = 1-a-c;
    return b*color+c; // = a*vec3(0,0,0)+b*color+c*vec3(1,1,1)
}

These will vary from black at t=0 to the specified colour at t=0.5 to white at t=1.

But a palette has the advantage of being easier to design in an external program then load from a file. There are some semi-standard formats for palettes, which can be created using paint programs such as GIMP.

Thank you so much for your reply! I tried your suggestion involving clamp, and it seems that all of the colors are somehow getting inversed?


#ifdef GL_ES							                                                         
precision highp float;					                                                         
#endif									                                                         

varying vec4 v_fragmentColor;			                                                         
varying vec2 v_texCoord;				                                                         
uniform vec3 u_desiredColor;                                                                     
uniform sampler2D u_texture;			                                                         

float a;                                                                                         
float b;                                                                                         
float c;                                                                                         
vec4 source;                                                                                 

vec3 blend(vec3 color, float t)                                                                  
{                                                                                                
	a = clamp(2.0 * t, 0.0, 1.0);                                                                    
	c = clamp(2.0 * (1.0 - t), 0.0, 1.0);                                                                
	b = 1.0 - a - c;                                                                             
	return b * color + c;                                   
}																								 

void main()									                                                     
{									                                                             
	source = texture2D(u_texture, v_texCoord);                                                      
	gl_FragColor = vec4(blend(u_desiredColor, source.r), source.a);    									 
}                                               

This gives me:
Output of shader using clamp

I am not sure if I did something wrong or not? I am passing in vec3(1.0, 0.0, 0.0) for u_desiredColor… Any idea?

I also tried the smoothstep version and it gives me:
Output of shader using smoothstep

Oops. Try:


	a = clamp(1.0 - 2.0 * t, 0.0, 1.0);
	c = clamp(2.0 * t - 1.0, 0.0, 1.0);

or:


	a = 1.0-clamp(2.0 * t, 0.0, 1.0);
	c = 1.0-clamp(2.0 * (1.0 - t), 0.0, 1.0);

The latter approach (1.0-…) can also be applied to the smoothstep() version.

So with this approach, I could use the file that looks how I want it to look, and use that as the palette?
My desired results

I am a little confused with how that works exactly, if its a 1D texture, that would mean it’s a very wide image that is 1px high with all the various colors? They are ordered from lowest intensity to highest?


    float intensity = texture(tex, texcoord).r;
    vec3 color = texture(palette, intensity);
    gl_FragColor = vec4(color, 1);

I’m a bit confused how intensity would map to the correct x coordinate for the correct color + intensity… How does that work exactly?

[QUOTE=GClements;1284546]Oops. Try:


	a = clamp(1.0 - 2.0 * t, 0.0, 1.0);
	c = clamp(2.0 * t - 1.0, 0.0, 1.0);

or:


	a = 1.0-clamp(2.0 * t, 0.0, 1.0);
	c = 1.0-clamp(2.0 * (1.0 - t), 0.0, 1.0);

The latter approach (1.0-…) can also be applied to the smoothstep() version.[/QUOTE]

Yeah, that looks a LOT better…

Results and comparison

The left is the shader, and the right is my desired output…

The glowy arrow is a little over saturated and blobby, and the non-glowy arrow is a little too far on the white side…

Is this where a palette consisting of the exact colors in my desired result version, would solve all the problems?

Hmm, I assume you mean:


    float a = 1.0 - smoothstep(0.0,0.5,t);
    float c = 1.0 - smoothstep(0.5,1.0,t);

I tried that but the coloring was all wrong-- in any event, even though the colors were wrong, I could still tell the result of the glowy arrow was still a little too blobby and over saturated around the edges…

Is there an easy way I can say add a slight bit more color to the output and smooth out the edges of the glowy version so that it matches more closely my desired results version?

[QUOTE=patrick99e99;1284548]
Is this where a palette consisting of the exact colors in my desired result version, would solve all the problems?[/QUOTE]
Probably. It’s easier to modify a palette by eye than a function.

Also, what neither approach can change is the fact that pixels which have the same colour in the original image will have the same colour in the colourised image.

You can change the rate of transition (e.g. the smoothstep version will tend to have more black, red and white, and less of the in-between colours), but the overall shape is set by the original image.

[QUOTE=GClements;1284550]Probably. It’s easier to modify a palette by eye than a function.
[/QUOTE]
Incidentally, the easiest way to do that is to load the grey-scale image into a paint program, convert it to a palette-based (256-colour) image, then use the paint program’s palette editor. That will give you immediate feedback.

Then you can either figure out how to read the paint program’s palette files directly, or read the palette from the modified image, or read the colours from the original and modified images and generate the palette from that.

Well, I was able to get gimp to just output my color palette for one of the original files. It actually will export it out as a text file, so I have a list of hex color codes (not so useful, I know). So, assuming I convert these hex values to 0-1 ranges, how exactly do I do the code for this?

say somewhere I have a 2 dimensional float array consisting of rgb values.


    float intensity = texture(tex, texcoord).r;
    vec3 color = texture(palette, intensity);  // i assume I wouldn't need to do this now?  it would be more like:
    vec3 color = palleteArray[intensity]; // or something, right?
    gl_FragColor = vec4(color, 1);

Except for the fact that when I look at this palette, gimp did not sort it by intensity-- so I’d need to do that too?

A texture would probably be preferable to an array, as you can index it using the 0-1 float value you get from the texture. Also, for a smooth gradient, you don’t need to use 256 colours; you can use a smaller texture and use GL_LINEAR texture filtering to interpolate it.

If you want to use an array, you’d use something like:


vec3 color = paletteArray[int(round(255*intensity))];

Hmm; GIMP’s palette editing is rather primitive. You could just create the palette as an image which you load as a texture, although then you don’t get the live preview. Or you could see if you can find a program with better palette-editing facilities.

The thing that I am still hung up on is this code:


    float intensity = texture(tex, texcoord).r;
    vec3 color = texture(palette, intensity);
    gl_FragColor = vec4(color, 1);

This would require the color palette image to have the colors ordered from lowest to highest intensity, right? I don’t understand how that can work otherwise…

The palette gimp created from my original image has 875 colors. So I would imagine this means my 1D palette texture is 875x1 ?

Also, from all my googling, I can’t seem to find anything about programs that will export a palette as an image… They all do it as .csv, .txt, etc…

Well, I just figured out, I can tell gimp to convert the palette to a gradient and then fill a new image (875 X 1px) with that gradient…

Well I have been trying to get this palette approach going, but it doesn’t seem to be working, and I don’t know why… I made a .png of the palette, and I have loaded it as a 2D texture and binded it to the variable u_palette:


    CCTexture2D *bluePalette = [[CCTextureCache sharedTextureCache] addImage:@"blue_palette.png"];
    [bluePalette setAliasTexParameters];  // turn off anti-aliasing

    GLint paletteUniformLocation = glGetUniformLocation(shaderProgram->program_, "u_palette");
    glUniform1i(paletteUniformLocation, bluePalette.name); // name property is the GLuint id that comes from --> glGenTextures(1, &name_);

and then the fragment shader code is:


ifdef GL_ES							                                    
precision highp float;					                                    
#endif									                                    

varying vec4 v_fragmentColor;			                                    
varying vec2 v_texCoord;				                                    
uniform sampler2D u_texture;			                                    
uniform sampler2D u_palette;			                                    
vec3 color;        
float intensity;    

void main()									                                
{									                                        
    intensity = texture2D(u_texture, v_texCoord).r; 
    color = texture2D(u_palette, vec2(intensity, 0.0)).rgb; 
    gl_FragColor = vec4(color, 1.0); 
}                  

I don’t get any OpenGL errors… but nothing appears on the screen…

Note, I am loading the palette as a 2D texture because I am using the game engine Cocos2D which does not support 1D textures, I figured it should be ok to load it as a 2D texture and just do vec2(intesity, 0.0)… That should be fine right?

Do you see any reason why this code wouldn’t be working?