Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Saturation blender

Credits go to Elias for helping out!
This thread is locked; no one can reply to it. rss feed Print
Saturation blender
Winfield
Member #15,244
July 2013
avatar

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.

--
"Either help out or stop whining" - Evert

ph03nix
Member #15,028
April 2013
avatar

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 :)

--
"Either help out or stop whining" - Evert

Winfield
Member #15,244
July 2013
avatar

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>
void heyThere ()
{
}
</code>

Becomes:

void heyThere ()
{
}

In capitalist America bank robs you.

Winfield
Member #15,244
July 2013
avatar

Next time? I totally did that this time. Yep. ;]

(Thanks for the tip!)

Mark Oates
Member #1,146
March 2001
avatar

al_color_rgb_to_hsl() already been done for you.

--
Visit CLUBCATT.com for cat shirts, cat mugs, puzzles, art and more <-- coupon code ALLEGRO4LIFE at checkout and get $3 off any order of 3 or more items!

AllegroFlareAllegroFlare DocsAllegroFlare GitHub

Winfield
Member #15,244
July 2013
avatar

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

#SelectExpand
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 screenshot of the shader

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
avatar

Do you have some pictures of how that turns out? Oh, right, you attached. Hey that's cool.

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.

--
Visit CLUBCATT.com for cat shirts, cat mugs, puzzles, art and more <-- coupon code ALLEGRO4LIFE at checkout and get $3 off any order of 3 or more items!

AllegroFlareAllegroFlare DocsAllegroFlare GitHub

Winfield
Member #15,244
July 2013
avatar

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.

Go to: