h1

Photoshop math with GLSL shaders

January 5, 2009

I usualy play with Photoshop to try post-processing effects on photos or game screenshots, it’s a lot faster than coding directly anything in shaders, but at the end I wanted to see my effects running in real-time. So I adapted a big part of the C-like code from this famous Photoshop blending mode math page + missing blending modes to GLSL (and now HLSL!) code and I added a few other useful things from Photoshop, such as Hue/Saturation/Luminance conversion, desaturation, contrast.

For example, I tried combining a few things in my Editor:

photoshopmath_tn

photoshopmath_editor_tn

Translating Photoshop operations on layers gives this kind of code:

uniform sampler2D Tex;
uniform sampler1D GradientMap;
uniform sampler1D GradientGround;

varying vec2 uv;

void main()
{

vec3 color = texture2D(Tex, uv).xyz;

// Split-tone
vec4 colorDesat = Desaturate(color, 1.0);
vec3 splitColor = texture1D(GradientMap, colorDesat.r).rgb;
vec3 pass1 = BlendColor(color, splitColor);

// Vertical gradient
vec4 verticalGradientColor = texture1D(GradientGround, uv.y);
vec3 pass2 = mix(pass1, BlendColor(pass1, verticalGradientColor.rgb), verticalGradientColor.a);

// Luminosity
vec3 pass3 = mix(pass2, BlendLuminosity(pass2, color + vec3(0.08)), 0.5);

// Linear light at 40%
vec3 pass4 = mix(pass3, BlendLinearLight(pass3, color), 0.4);

// Final
gl_FragColor = vec4(pass4, 1.0);

}

Here is the list of blending modes and functions I got:

Blending modes:

  • Normal
  • Lighten
  • Darken
  • Multiply
  • Average
  • Add
  • Substract
  • Difference
  • Negation
  • Exclusion
  • Screen
  • Overlay
  • SoftLight
  • HardLight
  • ColorDodge
  • ColorBurn
  • LinearDodge
  • LinearBurn
  • LinearLight
  • VividLight
  • PinLight
  • HardMix
  • Reflect
  • Glow
  • Phoenix
  • Hue
  • Saturation
  • Color
  • Luminosity

Functions:

  • Desaturation
  • RGBToHSL (RGB to Hue/Saturation/Luminance)
  • HSLToRGB (Hue/Saturation/Luminance to RGB)
  • Contrast

Here is my GLSL code, almost all the blending modes are macros and some do per-channel operation so it could run faster using vector operations with masks (to take into account the values per component), but still I guess it could help :)

Download PhotoshopMath.glsl

Update:

Oh and by the way you noticed the Split-Tone pass in my example:

// Split-tone
vec4 colorDesat = Desaturate(color, 1.0);
vec3 splitColor = texture1D(GradientMap, colorDesat.r).rgb;
vec3 result = BlendColor(color, splitColor);

It’s just the same thing than the Gradient Map… of the Create new fill or adjustment layer in Photoshop but blended in Color mode, which reminds me Color Temperature and Cross-Processing effects :)

Update:

I updated the .glsl file, because I forgot a line in the ContrastSaturationBrightness() function and I had some issues on specific hardware due to conditional returns, so now it’s fixed.

And now, here is the HLSL version :)

Download PhotoshopMath.hlsl

8 comments

  1. Great post! Implemented many of the photoshop blend modes in HLSL myself a while ago, but not as many as you’ve done here.


  2. [...] I added the code in my Photoshop Math (GLSL/HLSL) shaders. [...]


  3. Hello, and thanks for your webside and sorry my bad english.

    I will like to know how I change in the fragment shader the HUE of a texture . and not modify the saturation and brithness

    Thanks for all


  4. You can use my code and do:
    vec3 hsl = RGBToHSL(texture2d(yourTexture, yourUV).rgb);
    hsl.x = whateverYouWant; // .x is hue, .y is saturation, .z is brightness
    vec3 color = HSLToRGB(hsl);


  5. Thanks very much.Works well.
    I will see you web page to knews comments.

    Thanks for all.


  6. Hi, I’ve been beating my brains out trying to blend two layers in case both are semitransparent. My results slightly differ from photoshop.

    I write this shader (for ex., for difference):

    // Performs “over”-type blending (colors must be premultiplied by alpha)
    float4 blend(float4 overlying, float4 underlying)
    {
    float3 blended = overlying.rgb + ((1-overlying.a)*underlying.rgb);
    float alpha = underlying.a + (1-underlying.a)*overlying.a;
    return float4(blended, alpha);
    }

    // Performs advanced “over”-type blending
    // (newcolor is the result of advanced blending (for ex, overlying.rgb * underlying.rgb))
    float4 blend(float3 newcolor, float4 overlying, float4 underlying)
    {
    // Here the problem! (I think…)
    float alpha = overlying.a * underlying.a;
    float3 blended = newcolor*alpha + ((1-alpha)*overlying.rgb);

    return blend(float4(blended, overlying.a), underlying);
    }

    // Input images
    sampler2D underlyingSampler : register(s0);
    sampler2D overlyingSampler : register(s1);
    // Opacity of source image
    float opacity : register(c0);

    float4 main(float2 uv : TEXCOORD) : COLOR
    {
    float4 overlying = tex2D(overlyingSampler, uv);
    float4 underlying = tex2D(underlyingSampler, uv);
    overlying.a *= opacity;
    overlying.rgb *= overlying.a;
    underlying.rgb *= underlying.a;

    float4 result = blend(abs(overlying.rgb – underlying.rgb), overlying, underlying);
    return float4(result.rgb/result.a, result.a);
    }


  7. [...] wasn’t working. I tried to figure out what was wrong with it, but eventually I ran into another post that had a different way of expressing the math … it was in HLSL! Plugging in this HLSL worked! [...]


  8. I don’t know If I said it already but …This blog rocks! I gotta say, that I read a lot of blogs on a daily basis and for the most part, people lack substance but, I just wanted to make a quick comment to say I’m glad I found your blog. Thanks, :)

    A definite great read….



Leave a Comment