Programming magic, glory, and juices.

Photoshop Blend Mode Math

January 7th, 2008


Been working on programmatically blending bitmaps. Below are the macros that I’ve come up, borrowing the some of the formulas from other websites (see sources at the bottom of this post).

Channel blending

#define ChannelBlend_Normal(B,L)     ((uint8)(B))
#define ChannelBlend_Lighten(B,L)    ((uint8)((L > B) ? L:B))
#define ChannelBlend_Darken(B,L)     ((uint8)((L > B) ? B:L))
#define ChannelBlend_Multiply(B,L)   ((uint8)((B * L) / 255))
#define ChannelBlend_Average(B,L)    ((uint8)((B + L) / 2))
#define ChannelBlend_Add(B,L)        ((uint8)(min(255, (B + L))))
#define ChannelBlend_Subtract(B,L)   ((uint8)((B + L < 255) ? 0:(B + L - 255)))
#define ChannelBlend_Difference(B,L) ((uint8)(abs(B - L)))
#define ChannelBlend_Negation(B,L)   ((uint8)(255 - abs(255 - B - L)))
#define ChannelBlend_Screen(B,L)     ((uint8)(255 - (((255 - B) * (255 - L)) >> 8)))
#define ChannelBlend_Exclusion(B,L)  ((uint8)(B + L - 2 * B * L / 255))
#define ChannelBlend_Overlay(B,L)    ((uint8)((L < 128) ? (2 * B * L / 255):(255 - 2 * (255 - B) * (255 - L) / 255)))
#define ChannelBlend_SoftLight(B,L)  ((uint8)((L < 128)?(2*((B>>1)+64))*((float)L/255):(255-(2*(255-((B>>1)+64))*(float)(255-L)/255))))
#define ChannelBlend_HardLight(B,L)  (ChannelBlend_Overlay(L,B))
#define ChannelBlend_ColorDodge(B,L) ((uint8)((L == 255) ? L:min(255, ((B << 8 ) / (255 - L)))))
#define ChannelBlend_ColorBurn(B,L)  ((uint8)((L == 0) ? L:max(0, (255 - ((255 - B) << 8 ) / L))))
#define ChannelBlend_LinearDodge(B,L)(ChannelBlend_Add(B,L))
#define ChannelBlend_LinearBurn(B,L) (ChannelBlend_Subtract(B,L))
#define ChannelBlend_LinearLight(B,L)((uint8)(L < 128)?ChannelBlend_LinearBurn(B,(2 * L)):ChannelBlend_LinearDodge(B,(2 * (L - 128))))
#define ChannelBlend_VividLight(B,L) ((uint8)(L < 128)?ChannelBlend_ColorBurn(B,(2 * L)):ChannelBlend_ColorDodge(B,(2 * (L - 128))))
#define ChannelBlend_PinLight(B,L)   ((uint8)(L < 128)?ChannelBlend_Darken(B,(2 * L)):ChannelBlend_Lighten(B,(2 * (L - 128))))
#define ChannelBlend_HardMix(B,L)    ((uint8)((ChannelBlend_VividLight(B,L) < 128) ? 0:255))
#define ChannelBlend_Reflect(B,L)    ((uint8)((L == 255) ? L:min(255, (B * B / (255 - L)))))
#define ChannelBlend_Glow(B,L)       (ChannelBlend_Reflect(L,B))
#define ChannelBlend_Phoenix(B,L)    ((uint8)(min(B,L) - max(B,L) + 255))
#define ChannelBlend_Alpha(B,L,O)    ((uint8)(O * B + (1 - O) * L))
#define ChannelBlend_AlphaF(B,L,F,O) (ChannelBlend_Alpha(F(B,L),B,O))

B is the base channel and L is the blend channel. For example you to blend using Glow you simply call:

Target[i] = ChannelBlend_Glow(Base[i], Blend[i]);

The great thing about these macros is that you can apply the blending effect simply by passing in the channel value, without regard to which channel is red, green, or blue.

To use the blending along with opacity you can use the following.

Target[i] = ChannelBlend_AlphaF(Base[i], Blend[i], Blend_Subtract, 0.5F)

Color blending

To add certain blend modes that utilize hue, luminosity, and saturation we have to construct a per-color interface instead of per-channel interface. For these macros we assume that A and B are buffer pointers and they point to bytes with channels red, green, and blue in that order.

#define ColorBlend_Buffer(T,B,L,M)      (T)[0] = ChannelBlend_##M((B)[0], (L)[0]), \
                                        (T)[1] = ChannelBlend_##M((B)[1], (L)[1]), \
                                        (T)[2] = ChannelBlend_##M((B)[2], (L)[2])
#define ColorBlend_Normal(T,B,L)        (ColorBlend_Buffer(T,B,L,Normal))
#define ColorBlend_Lighten(T,B,L)       (ColorBlend_Buffer(T,B,L,Lighten))
#define ColorBlend_Darken(T,B,L)        (ColorBlend_Buffer(T,B,L,Darken))
#define ColorBlend_Multiply(T,B,L)      (ColorBlend_Buffer(T,B,L,Multiply))
#define ColorBlend_Average(T,B,L)       (ColorBlend_Buffer(T,B,L,Average))
#define ColorBlend_Add(T,B,L)           (ColorBlend_Buffer(T,B,L,Add))
#define ColorBlend_Subtract(T,B,L)      (ColorBlend_Buffer(T,B,L,Subtract))
#define ColorBlend_Difference(T,B,L)    (ColorBlend_Buffer(T,B,L,Difference))
#define ColorBlend_Negation(T,B,L)      (ColorBlend_Buffer(T,B,L,Negation))
#define ColorBlend_Screen(T,B,L)        (ColorBlend_Buffer(T,B,L,Screen))
#define ColorBlend_Exclusion(T,B,L)     (ColorBlend_Buffer(T,B,L,Exclusion))
#define ColorBlend_Overlay(T,B,L)       (ColorBlend_Buffer(T,B,L,Overlay))
#define ColorBlend_SoftLight(T,B,L)     (ColorBlend_Buffer(T,B,L,SoftLight))
#define ColorBlend_HardLight(T,B,L)     (ColorBlend_Buffer(T,B,L,HardLight))
#define ColorBlend_ColorDodge(T,B,L)    (ColorBlend_Buffer(T,B,L,ColorDodge))
#define ColorBlend_ColorBurn(T,B,L)     (ColorBlend_Buffer(T,B,L,ColorBurn))
#define ColorBlend_LinearDodge(T,B,L)   (ColorBlend_Buffer(T,B,L,LinearDodge))
#define ColorBlend_LinearBurn(T,B,L)    (ColorBlend_Buffer(T,B,L,LinearBurn))
#define ColorBlend_LinearLight(T,B,L)   (ColorBlend_Buffer(T,B,L,LinearLight))
#define ColorBlend_VividLight(T,B,L)    (ColorBlend_Buffer(T,B,L,VividLight))
#define ColorBlend_PinLight(T,B,L)      (ColorBlend_Buffer(T,B,L,PinLight))
#define ColorBlend_HardMix(T,B,L)       (ColorBlend_Buffer(T,B,L,HardMix))
#define ColorBlend_Reflect(T,B,L)       (ColorBlend_Buffer(T,B,L,Reflect))
#define ColorBlend_Glow(T,B,L)          (ColorBlend_Buffer(T,B,L,Glow))
#define ColorBlend_Phoenix(T,B,L)       (ColorBlend_Buffer(T,B,L,Phoenix))
#define ColorBlend_Hue(T,B,L)            ColorBlend_Hls(T,B,L,HueL,LuminationB,SaturationB)
#define ColorBlend_Saturation(T,B,L)     ColorBlend_Hls(T,B,L,HueB,LuminationB,SaturationL)
#define ColorBlend_Color(T,B,L)          ColorBlend_Hls(T,B,L,HueL,LuminationB,SaturationL)
#define ColorBlend_Luminosity(T,B,L)     ColorBlend_Hls(T,B,L,HueB,LuminationL,SaturationB)

#define ColorBlend_Hls(T,B,L,O1,O2,O3) {                                        \
    float64 HueB, LuminationB, SaturationB;                                     \
    float64 HueL, LuminationL, SaturationL;                                     \
    Color_RgbToHls((B)[2],(B)[1],(B)[0], &HueB, &LuminationB, &SaturationB);    \
    Color_RgbToHls((L)[2],(L)[1],(L)[0], &HueL, &LuminationL, &SaturationL);    \
    Color_HlsToRgb(O1,O2,O3,&(T)[2],&(T)[1],&(T)[0]);                           \
    }

int32 Color_HueToRgb(float64 M1, float64 M2, float64 Hue, float64 *Channel)
{
    if (Hue < 0.0)
        Hue += 1.0;
    else if (Hue > 1.0)
        Hue -= 1.0;

    if ((6.0 * Hue) < 1.0)
        *Channel = (M1 + (M2 - M1) * Hue * 6.0);
    else if ((2.0 * Hue) < 1.0)
        *Channel = (M2);
    else if ((3.0 * Hue) < 2.0)
        *Channel = (M1 + (M2 - M1) * ((2.0F / 3.0F) - Hue) * 6.0);
    else
        *Channel = (M1);

    return TRUE;
}

int32 Color_RgbToHls(uint8 Red, uint8 Green, uint8 Blue, float64 *Hue, float64 *Lumination, float64 *Saturation)
{
    float64 Delta;
    float64 Max, Min;
    float64 Redf, Greenf, Bluef;

    Redf    = ((float64)Red   / 255.0F);
    Greenf  = ((float64)Green / 255.0F);
    Bluef   = ((float64)Blue  / 255.0F); 

    Max     = max(max(Redf, Greenf), Bluef);
    Min     = min(min(Redf, Greenf), Bluef);

    *Hue        = 0;
    *Lumination = (Max + Min) / 2.0F;
    *Saturation = 0;

    if (Max == Min)
        return TRUE;

    Delta = (Max - Min);

    if (*Lumination < 0.5)
        *Saturation = Delta / (Max + Min);
    else
        *Saturation = Delta / (2.0 - Max - Min);

    if (Redf == Max)
        *Hue = (Greenf - Bluef) / Delta;
    else if (Greenf == Max)
        *Hue = 2.0 + (Bluef - Redf) / Delta;
    else
        *Hue = 4.0 + (Redf - Greenf) / Delta;

    *Hue /= 6.0; 

    if (*Hue < 0.0)
        *Hue += 1.0;       

    return TRUE;
}

int32 Color_HlsToRgb(float64 Hue, float64 Lumination, float64 Saturation, uint8 *Red, uint8 *Green, uint8 *Blue)
{
    float64 M1, M2;
    float64 Redf, Greenf, Bluef;

    if (Saturation == 0)
        {
        Redf    = Lumination;
        Greenf  = Lumination;
        Bluef   = Lumination;
        }
    else
        {
        if (Lumination <= 0.5)
            M2 = Lumination * (1.0 + Saturation);
        else
            M2 = Lumination + Saturation - Lumination * Saturation;

        M1 = (2.0 * Lumination - M2);

        Color_HueToRgb(M1, M2, Hue + (1.0F / 3.0F), &Redf);
        Color_HueToRgb(M1, M2, Hue, &Greenf);
        Color_HueToRgb(M1, M2, Hue - (1.0F / 3.0F), &Bluef);
        }

    *Red    = (uint8)(Redf * 255);
    *Blue   = (uint8)(Bluef * 255);
    *Green  = (uint8)(Greenf * 255);

    return TRUE;
}

So now we can use the hue, saturation, color, and luminosity blend modes along with the rest of the blend modes. To use the ColorBlend macros we just position our bitmap's pointers to the next RGB iteration and call,

ColorBlend_Glow(Target + iTarget, Base + iBase, Blend + iBlend);

Color macros

Here are some color macros for combining and extracting channels colors out of an integer. One macro that I find a lot of use for is HexToRgb. With it I can copy and paste a hex color value out of the Photoshop color dialog and use the macro to do all the conversion for me to an rgb value.

#define COLOR_OPAQUE                (0)
#define COLOR_TRANSPARENT           (127)

#define RGB_SIZE                    (3)
#define RGB_BPP                     (24)
#define RGB_MAXRED                  (255)
#define RGB_MAXGREEN                (255)
#define RGB_MAXBLUE                 (255)

#define ARGB_SIZE                   (4)
#define ARGB_BPP                    (32)
#define ARGB_MAXALPHA               (127)
#define ARGB_MAXRED                 (RGB_MAXRED)
#define ARGB_MAXGREEN               (RGB_MAXGREEN)
#define ARGB_MAXBLUE                (RGB_MAXBLUE)

/*********************************************************************/

#define Color_GetChannel(c,shift)   ((uint8)((c) >> (shift)))
#define Color_Reverse(c,bpp)        ((((uint8)(c) << 24) | ((uint8)((c) >> 8 ) << 16) | ((uint8)((c) >> 16) << 8 ) | \
                                    ((uint8)((c) >> 24))) >> (32 - (bpp)))

#define Rgb_ByteWidth(width)        ((width) * RGB_SIZE)
#define Rgb_PixelWidth(width)       ((width) / RGB_SIZE)

#define Rgb_GetRed(rgb)             (Color_GetChannel(rgb, 0))
#define Rgb_GetGreen(rgb)           (Color_GetChannel(rgb, 8))
#define Rgb_GetBlue(rgb)            (Color_GetChannel(rgb, 16))

#define Rgba_GetRed(rgba)           (Color_GetChannel(rgba, 24))
#define Rgba_GetGreen(rgba)         (Color_GetChannel(rgba, 16))
#define Rgba_GetBlue(rgba)          (Color_GetChannel(rgba, 8))
#define Rgba_GetAlpha(rgba)         (Color_GetChannel(rgba, 0))

#define Argb_GetAlpha(argb)         (Color_GetChannel(argb, 24))
#define Argb_GetRed(argb)           (Color_GetChannel(argb, 16))
#define Argb_GetGreen(argb)         (Color_GetChannel(argb, 8))
#define Argb_GetBlue(argb)          (Color_GetChannel(argb, 0))

#define MakeRgb(r,g,b)              (((uint32)(uint8)(b) << 16) | ((uint16)(uint8)(g) << 8 ) | (uint8)(r))
#define MakeRgba(r,g,b,a)           (((uint32)(uint8)(r) << 24) | ((uint16)(uint8)(g) << 16) | ((uint16)(uint8)(b) << 8 ) | (uint8)(a))
#define MakeArgb(a,r,g,b)           (((uint32)(uint8)(a) << 24) | ((uint32)(uint8)(r) << 16) | ((uint16)(uint8)(g) << 8 ) | (uint8)(b))

#define HexToRgb(hex)               (MakeRgb(((hex & 0xFF0000) >> 16), ((hex & 0x00FF00) >> 8 ), (hex & 0xFF)))

Image blending

To show you what each effect does I've blended the following two photos.


Photoshop Blending - Base Layer
Base Layer
Source
Photoshop Blending - Upper Layer
Blend Layer
Source

And the results..

Photoshop Blending - Lighten
Blend_Lighten
Photoshop Blending - Darken
Blend_Darken
Photoshop Blending - Multiply
Blend_Multiply
Photoshop Blending - Average
Blend_Average
Photoshop Blending - Add
Blend_Add
Photoshop Blending - Subtract
Blend_Subtract
Photoshop Blending - Difference
Blend_Difference
Photoshop Blending - Negation
Blend_Negation
Photoshop Blending - Screen
Blend_Screen
Photoshop Blending - Exclusion
Blend_Exclusion
Photoshop Blending - Overlay
Blend_Overlay
Photoshop Blending - Softlight
Blend_SoftLight
Photoshop Blending - Hardlight
Blend_HardLight
Photoshop Blending - ColorDodge
Blend_ColorDodge
Photoshop Blending - ColorBurn
Blend_ColorBurn
Photoshop Blending - LinearDodge
Blend_LinearDodge
Photoshop Blending - LinearBurn
Blend_LinearBurn
Photoshop Blending - LinearLight
Blend_LinearLight
Photoshop Blending - VividLight
Blend_VividLight
Photoshop Blending - PinLight
Blend_PinLight
Photoshop Blending - HardMix
Blend_HardMix
Photoshop Blending - Reflect
Blend_Reflect
Photoshop Blending - Glow
Blend_Glow
Photoshop Blending - Phoenix
Blend_Phoenix
Photoshop Blending - Hue
Blend_Hue
Photoshop Blending - Saturation
Blend_Saturation
Photoshop Blending - Color
Blend_Color
Photoshop Blending - Luminosity
Blend_Luminosity

And the gradients..

Photoshop Gradient Blending - Base Layer
Base Layer
Photoshop Gradient Blending - Blend Layer
Blend Layer

And the results..

Photoshop Gradient Blending - Lighten
Blend_Lighten
Photoshop Gradient Blending - Darken
Blend_Darken
Photoshop Gradient Blending - Multiply
Blend_Multiply
Photoshop Gradient Blending - Average
Blend_Average
Photoshop Gradient Blending - Add
Blend_Add
Photoshop Gradient Blending - Subtract
Blend_Subtract
Photoshop Gradient Blending - Difference
Blend_Difference
Photoshop Gradient Blending - Negation
Blend_Negation
Photoshop Gradient Blending - Screen
Blend_Screen
Photoshop Gradient Blending - Exclusion
Blend_Exclusion
Photoshop Gradient Blending - Overlay
Blend_Overlay
Photoshop Gradient Blending - Softlight
Blend_SoftLight
Photoshop Gradient Blending - Hardlight
Blend_HardLight
Photoshop Gradient Blending - ColorDodge
Blend_ColorDodge
Photoshop Gradient Blending - ColorBurn
Blend_ColorBurn
Photoshop Gradient Blending - LinearDodge
Blend_LinearDodge
Photoshop Gradient Blending - LinearBurn
Blend_LinearBurn
Photoshop Gradient Blending - LinearLight
Blend_LinearLight
Photoshop Gradient Blending - VividLight
Blend_VividLight
Photoshop Gradient Blending - PinLight
Blend_PinLight
Photoshop Gradient Blending - HardMix
Blend_HardMix
Photoshop Gradient Blending - Reflect
Blend_Reflect
Photoshop Gradient Blending - Glow
Blend_Glow
Photoshop Gradient Blending - Phoenix
Blend_Phoenix
Photoshop Gradient Blending - Hue
Blend_Hue
Photoshop Gradient Blending - Saturation
Blend_Saturation
Photoshop Gradient Blending - Color
Blend_Color
Photoshop Gradient Blending - Luminosity
Blend_Luminosity

More blending resources

PegTop blend modes
Forensic Photoshop
Insight into Photoshop 7.0 Blending Modes
SF - Basics - Blending Modes
finish the blend() modes
Romz blog
ReactOS RGB/HLS conversion fucntions

26 Responses to “Photoshop Blend Mode Math”
  1. Photoshop Blend Modes Says:

    [...] previously wrote about Photoshop Blend Mode Math and provided all the macros that are needed in order to do image blending using ALL of the [...]

  2. Jim Hoerricks Says:

    Nice use of the math for some interesting effects. Glad to be of help.

    Jim Hoerricks
    Author – Forensic Photoshop blog

  3. Math Art | Heart Broken Says:

    [...] basically the absolute difference (i.e. |difference|) between the pixels of two layers. Nathan has a very good writeup on the math behind photoshop (and why Subtract is different from Difference) Share this page! : [...]

  4. CrossProcessing shader « Romz blog Says:

    [...] and by the way [...]

  5. Brad Stiritz Says:

    Nathan, this is a really great resource, thanks so much for publishing it :)

  6. Nathan Says:

    No problemo. I couldn’t find any site out there that had EVERY photoshop blend mode. So I worked on getting the macros right and then put them all up here for everyone to use.

  7. purshoth Says:

    nice

  8. nagacharan Says:

    thx for the links

  9. drwitt Says:

    Thankyou very much, Nathan, this exactly answers what I’ve been wondering about these blend-modes… Great :-)

  10. PatchBox r284 | Quadratura Says:

    [...] Found an excellent page this morning with a good range of succinctly described blending modes. [...]

  11. impress your friends: perform an isaac-edit at parties! « isaac bowen Says:

    [...] mixes with the colors in the layers below.” For the programmers and/or math majors among you, this definition should [...]

  12. Bart Says:

    It seems you made a small error in the definition of Blend_Add(A,B): “max” should be “min”, so it reads “((uint8)(min(255, (A + B))))”. Great list by the way!

  13. Nathan Says:

    Thanks I fixed that. I made changes to it so it used min and max where needed and I guess that one got screwed up.

  14. Photoshop math with GLSL shaders « Romz blog Says:

    [...] 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 code and I added a few other useful things from Photoshop, [...]

  15. Nathan Says:

    If anybody else has fixes or other blending code they would like to contribute, just add a comment with the code or fix.

  16. Romz Says:

    Hey Nathan, you can check on my blog: http://blog.mouaif.org, I’ve posted GLSL and now also HLSL code of all the Photoshop blending modes based on your code + other blending modes (like hue, saturation, luminosity, color) + other stuff. Thanks!

  17. Nathan Says:

    @Romz: Cool! Nice blog. I noticed in your code that you said you fixed HardMix, what was wrong with it? I updated my definition of HardMix so that it is the same as yours. I’ve also updated the my blog post with the per-color code and copied your blend definitions for hue, luminosity, saturation, and color. I like how you used vectors in your code. Very nice.

  18. Romz Says:

    Thank you for the link :)
    Concerning hard mix I don’t remember where I saw the exact definition but after a quick search I found the same description (with less details):

    Hard Mix: This blend mode is a combination of the Vivid Light mode and a posterization effect (i.e., where the image appears more pixelated). It will posterize the bottom layer pixels through the blend layer and recolor the image using the specifications of the Vivid Light mode. A higher Fill Opacity on the top layer will increase the posterization effect on the image. http://www.uwec.edu/help/PhotoshopCS2/blendmodes.htm

  19. Photoshop Blend Mode Math « François Tarlier’s Blog Says:

    [...] Photoshop Blend Mode Math. [...]

  20. Joseph Francis Says:

    Here’s a good use for hard mix -

    http://www.digitalartform.com/archives/2009/06/custom_halftone.html

  21. Cory Plotts’ Blog » Blend Modes, Part I Says:

    [...] quickly started off with a simple Google search and it led me to Nathan Moinvaziri’s post (http://www.nathanm.com/photoshop-blending-math/) which is all the blend mode math in C. So, I decided to buckle down and see if I could do Linear [...]

  22. Cory Plotts’ Blog » Blend Modes, Part II Says:

    [...] Nathan’s post had a visual way of verifying things as well (near the bottom) and so I included in my test harness [...]

  23. Nathan Says:

    Today I updated a few of the blend modes and added gradient images.

  24. Nathan Says:

    If anybody finds any problems with the blend modes or has a more accurate representation of the photoshop blend modes please let me know so I can update this post.

  25. Joseph Francis Says:

    I think each blend mode is best demonstrated by at least one ‘killer app.’ I’m trying to collect them here:

    http://www.digitalartform.com/archives/2009/06/photoshop_blend.html

  26. Nathan Says:

    Cool!

Leave a Reply