|
Saturation blender |
Winfield
Member #15,244
July 2013
|
Hey, guys- I'd really like being able to blend two bitmaps in such a way that all the recipient bitmap receives from the donor is its saturation. I'm relatively certain it can't be done with built-in filters, but I'd like to ask the experts to weigh in before I burn a chunk of time writing a per-pixel filter. Also, WOO FIRST POST, celebrations, so forth. W. |
Elias
Member #358
May 2000
|
I'd say it's something that can't be done with blending, and yes, a very trivial shader to draw the bitmap will work... instead of outputting r/g/b from each texture pixel output s/s/s. -- |
ph03nix
Member #15,028
April 2013
|
Can you write the equation of what you want? |
Elias
Member #358
May 2000
|
Yes, the whole GLSL fragment shader would be something like this: uniform sampler2D tex; varying vec2 uv; void main() { vec4 c = texture2D(tex, uv); float s = (c.r + c.g c.b) / 3; gl_FragColor = vec4(s, s, s, c.a); } With probably a more sophisticated formula to get the saturation -- |
Winfield
Member #15,244
July 2013
|
The cheap method of returning saturation for an RGB value looks something like: function SaturationFromRGB(unsigned char r, unsigned char g, unsigned char b){ int Minimum = min(b,min(r,g)); int Maximum = max(b,max(r,g)); int Saturation = Minimum-Maximum; return Saturation; } And the cheap(ish) way of applying a saturation value to something in the RGB colorspace is: void SaturationToRGB(unsigned char *r, unsigned char *g, unsigned char *b, char Saturation){ double Pr = .299; double Pg = .587; double Pb = .114; double P=sqrt( (*r)*(*r)*Pr+ (*g)*(*g)*Pg+ (*b)*(*b)*Pb ) ; *r=P+((*r)-P)*change; *g=P+((*g)-P)*change; *b=P+((*b)-P)*change; } So ultimately, the algo I'll be using when I scan through on a per-pixel basis will be something like: unsigned char sr, sg, sb, ar, ag, ab; for (i = 0; i < sizeof(SCREEN_W); i++) { //only rendering pixels in the viewport for (j = 0; j < sizeof(SCREEN_H); j++) { PixelToBuffer(*SourceCanvas, ViewportOffsetX+i, ViewportOffsetY+j, &sr, &sg, &sb); PixelToBuffer(*AdjustCanvas, ViewportOffsetX+i, ViewportOffsetY+j, &ar, &ag, &ab); SaturationToRGB(&ar, &ag, &ab, SaturationFromRGB(sr, sg, sb)); } } For now the project's strictly 2D, but I'll look into making GLSL do what I want - it'd be nice to tilt the base layer of the tilemap away from the camera slightly like Diablo 2: Lord of Destruction did. We'll see. In any case, thanks for thinking about the problem. And please forgive any mistakes, I've had to write PHP all morning and it takes a few hours for my brain to recover.;D |
Vanneto
Member #8,643
May 2007
|
Just use <code></code> tags next time. <code> Becomes: void heyThere () { }
In capitalist America bank robs you. |
Winfield
Member #15,244
July 2013
|
Next time? I totally did that this time. Yep. ;] (Thanks for the tip!) |
Mark Oates
Member #1,146
March 2001
|
al_color_rgb_to_hsl() already been done for you. -- |
Winfield
Member #15,244
July 2013
|
Thanks. I'm aware of the function, but chose to reimplement it anyway: an authentic HSL conversion may not ultimately be the effect I need, because it doesn't stand out very much. I'm not making the decision until I've implemented the effect, but it's likely I will need to tweak the formula to make a gray more distinct from the source colors. UPDATE: I've written a shader to do this - several, in fact. I've attached the simplest, most accurate one - it masks the stencil (al_tex) against the texture (tex2), using luminance as opacity. Basically, the brighter the pixel on the mask, the more desaturated the composite result will be. Anyone who wants it, feel free to use it under the terms of the zLib license. Note that the shader used in the attached screenshot is not exactly the same - it's a less accurate, stylized version which is far less useful as learner code. If you want to desaturate the entire texture, this is easily done by replacing tmp.r, tmp.b, and tmp.g by clamping Opacity to the level of desaturation you want (between 0 and 1.) 1#version 120
2#ifdef GL_ES
3precision mediump float;
4#endif
5uniform sampler2D al_tex;
6uniform sampler2D tex2;
7varying vec4 varying_color;
8varying vec2 varying_texcoord;
9void main()
10{
11 vec4 tmp = varying_color * texture2D(al_tex, varying_texcoord);
12 vec4 art = texture2D(tex2, varying_texcoord);
13
14 float des = (0.299*art.r + 0.587*art.g + 0.114*art.b); //desaturated grey value from the base RGB color, weighted for human perception
15
16 float bump = 0.03; // Brighten or darken results by tweaking this - recommend .03 to .1 to make desaturated area "pop"
17
18 float opacity = (tmp.r + tmp.g + tmp.b) / 3;
19 des = des + bump;
20 tmp.r = (
21 art.r + (
22 (des - art.r)
23 * opacity
24 )
25 );
26 tmp.g = (
27 art.g + (
28 (des - art.g)
29 * opacity
30 )
31 );
32 tmp.b = (
33 art.b + (
34 (des - art.b)
35 * opacity
36 )
37 );
38
39 gl_FragColor = tmp;
40}
A video of the shader is here: http://www.youtube.com/watch?v=JefzZPhZd0Q (It's the first brush shader demonstrated) |
Mark Oates
Member #1,146
March 2001
|
Winfield said: Anyone who wants it, feel free to use it under the terms of the zLib license. [opensource.org] Thank you sir, I will add this to my arsenal. I've been using a shadow layer (simple multiply blending) but I also want to desaturate the darker areas as well. This will help. -- |
Winfield
Member #15,244
July 2013
|
Sure thing! The other part of the magic is passing a second texture to the shader - the function you want is al_set_shader_sampler(). (In the case of using the shader I pasted, unit should be 1 if the stencil bitmap is the first texture.) examples/ex_shader_multitex.c contains a good implementation, though it's kind of hard to follow - it took me a while before I realized that the regions it was creating existed directly on the backbuffer and thus don't need to be drawn explicitly as long as you're flipping the display. |
|