Can somebody please explain how Photoshop blends two images together so that I may reproduce the same effects in my application.

- 9,649
- 7
- 44
- 75

- 5,506
- 4
- 29
- 30
-
3This is definitely **not** a question about general computing hardware or software. It's about algorithms, C++ and image processing. [This type of question is explicitly allowed](https://stackoverflow.com/help/self-answer) and [actively encouraged](https://stackoverflow.blog/2012/05/22/encyclopedia-stack-exchange/) – Panagiotis Kanavos Jul 04 '19 at 15:18
-
1After 8 years of driving clicks to this site it is considered off topic only now? – Nathan Moinvaziri Jul 05 '19 at 19:47
-
All it needs is 5 close votes from people with at least 3000 rep. It's rather obvious this question is *not* considered off-topic by the SO community – Panagiotis Kanavos Jul 08 '19 at 07:47
-
PS: the 4 downvotes could be 8 years old. Someone may have encountered the question in the close queue last week and voted to close. SO has so *many* questions right now that it's hard to pay proper attention to questions any more. The edit history shows that the original question needed a bit of editing too – Panagiotis Kanavos Jul 08 '19 at 07:55
-
Perhaps reverting to revision 3, or something similar, would make this more likely to be reopened by reviewers. In its current state, it's easy to mistake the question as a "how do I use Photoshop" question, and also to dismiss it as not showing any research effort. – Adam Millerchip Jul 27 '19 at 05:28
4 Answers
Photoshop blends two images together by performing a blend operation on each pixel in image A against its corresponding pixel in image B. Each pixel is a color consisting of multiple channels. Assuming we are working with RGB pixels, the channels in each pixel would be red, green and blue. To blend two pixels we blend their respective channels.
The blend operation that occurs for each blend mode in Photoshop can be summed up in the following macros:
#define ChannelBlend_Normal(A,B) ((uint8)(A))
#define ChannelBlend_Lighten(A,B) ((uint8)((B > A) ? B:A))
#define ChannelBlend_Darken(A,B) ((uint8)((B > A) ? A:B))
#define ChannelBlend_Multiply(A,B) ((uint8)((A * B) / 255))
#define ChannelBlend_Average(A,B) ((uint8)((A + B) / 2))
#define ChannelBlend_Add(A,B) ((uint8)(min(255, (A + B))))
#define ChannelBlend_Subtract(A,B) ((uint8)((A + B < 255) ? 0:(A + B - 255)))
#define ChannelBlend_Difference(A,B) ((uint8)(abs(A - B)))
#define ChannelBlend_Negation(A,B) ((uint8)(255 - abs(255 - A - B)))
#define ChannelBlend_Screen(A,B) ((uint8)(255 - (((255 - A) * (255 - B)) >> 8)))
#define ChannelBlend_Exclusion(A,B) ((uint8)(A + B - 2 * A * B / 255))
#define ChannelBlend_Overlay(A,B) ((uint8)((B < 128) ? (2 * A * B / 255):(255 - 2 * (255 - A) * (255 - B) / 255)))
#define ChannelBlend_SoftLight(A,B) ((uint8)((B < 128)?(2*((A>>1)+64))*((float)B/255):(255-(2*(255-((A>>1)+64))*(float)(255-B)/255))))
#define ChannelBlend_HardLight(A,B) (ChannelBlend_Overlay(B,A))
#define ChannelBlend_ColorDodge(A,B) ((uint8)((B == 255) ? B:min(255, ((A << 8 ) / (255 - B)))))
#define ChannelBlend_ColorBurn(A,B) ((uint8)((B == 0) ? B:max(0, (255 - ((255 - A) << 8 ) / B))))
#define ChannelBlend_LinearDodge(A,B)(ChannelBlend_Add(A,B))
#define ChannelBlend_LinearBurn(A,B) (ChannelBlend_Subtract(A,B))
#define ChannelBlend_LinearLight(A,B)((uint8)(B < 128)?ChannelBlend_LinearBurn(A,(2 * B)):ChannelBlend_LinearDodge(A,(2 * (B - 128))))
#define ChannelBlend_VividLight(A,B) ((uint8)(B < 128)?ChannelBlend_ColorBurn(A,(2 * B)):ChannelBlend_ColorDodge(A,(2 * (B - 128))))
#define ChannelBlend_PinLight(A,B) ((uint8)(B < 128)?ChannelBlend_Darken(A,(2 * B)):ChannelBlend_Lighten(A,(2 * (B - 128))))
#define ChannelBlend_HardMix(A,B) ((uint8)((ChannelBlend_VividLight(A,B) < 128) ? 0:255))
#define ChannelBlend_Reflect(A,B) ((uint8)((B == 255) ? B:min(255, (A * A / (255 - B)))))
#define ChannelBlend_Glow(A,B) (ChannelBlend_Reflect(B,A))
#define ChannelBlend_Phoenix(A,B) ((uint8)(min(A,B) - max(A,B) + 255))
#define ChannelBlend_Alpha(A,B,O) ((uint8)(O * A + (1 - O) * B))
#define ChannelBlend_AlphaF(A,B,F,O) (ChannelBlend_Alpha(F(A,B),A,O))
To blend a single RGB pixel you would do the following:
ImageTColorR = ChannelBlend_Glow(ImageAColorR, ImageBColorR);
ImageTColorB = ChannelBlend_Glow(ImageAColorB, ImageBColorB);
ImageTColorG = ChannelBlend_Glow(ImageAColorG, ImageBColorG);
ImageTColor = RGB(ImageTColorR, ImageTColorB, ImageTColorG);
If we wanted to perform a blend operation with a particular opacity, say 50%:
ImageTColorR = ChannelBlend_AlphaF(ImageAColorR, ImageBColorR, Blend_Subtract, 0.5F);
If you have pointers to the image data for images A, B, and T (our target), we can simplify the blending of all three channels using this macro:
#define ColorBlend_Buffer(T,A,B,M) (T)[0] = ChannelBlend_##M((A)[0], (B)[0]),
(T)[1] = ChannelBlend_##M((A)[1], (B)[1]),
(T)[2] = ChannelBlend_##M((A)[2], (B)[2])
And can derive the following RGB color blend macros:
#define ColorBlend_Normal(T,A,B) (ColorBlend_Buffer(T,A,B,Normal))
#define ColorBlend_Lighten(T,A,B) (ColorBlend_Buffer(T,A,B,Lighten))
#define ColorBlend_Darken(T,A,B) (ColorBlend_Buffer(T,A,B,Darken))
#define ColorBlend_Multiply(T,A,B) (ColorBlend_Buffer(T,A,B,Multiply))
#define ColorBlend_Average(T,A,B) (ColorBlend_Buffer(T,A,B,Average))
#define ColorBlend_Add(T,A,B) (ColorBlend_Buffer(T,A,B,Add))
#define ColorBlend_Subtract(T,A,B) (ColorBlend_Buffer(T,A,B,Subtract))
#define ColorBlend_Difference(T,A,B) (ColorBlend_Buffer(T,A,B,Difference))
#define ColorBlend_Negation(T,A,B) (ColorBlend_Buffer(T,A,B,Negation))
#define ColorBlend_Screen(T,A,B) (ColorBlend_Buffer(T,A,B,Screen))
#define ColorBlend_Exclusion(T,A,B) (ColorBlend_Buffer(T,A,B,Exclusion))
#define ColorBlend_Overlay(T,A,B) (ColorBlend_Buffer(T,A,B,Overlay))
#define ColorBlend_SoftLight(T,A,B) (ColorBlend_Buffer(T,A,B,SoftLight))
#define ColorBlend_HardLight(T,A,B) (ColorBlend_Buffer(T,A,B,HardLight))
#define ColorBlend_ColorDodge(T,A,B) (ColorBlend_Buffer(T,A,B,ColorDodge))
#define ColorBlend_ColorBurn(T,A,B) (ColorBlend_Buffer(T,A,B,ColorBurn))
#define ColorBlend_LinearDodge(T,A,B) (ColorBlend_Buffer(T,A,B,LinearDodge))
#define ColorBlend_LinearBurn(T,A,B) (ColorBlend_Buffer(T,A,B,LinearBurn))
#define ColorBlend_LinearLight(T,A,B) (ColorBlend_Buffer(T,A,B,LinearLight))
#define ColorBlend_VividLight(T,A,B) (ColorBlend_Buffer(T,A,B,VividLight))
#define ColorBlend_PinLight(T,A,B) (ColorBlend_Buffer(T,A,B,PinLight))
#define ColorBlend_HardMix(T,A,B) (ColorBlend_Buffer(T,A,B,HardMix))
#define ColorBlend_Reflect(T,A,B) (ColorBlend_Buffer(T,A,B,Reflect))
#define ColorBlend_Glow(T,A,B) (ColorBlend_Buffer(T,A,B,Glow))
#define ColorBlend_Phoenix(T,A,B) (ColorBlend_Buffer(T,A,B,Phoenix))
And example would be:
ColorBlend_Glow(TargetPtr, ImageAPtr, ImageBPtr);
The remainder of the photoshop blend modes involve converting RGB to HLS and back again.
#define ColorBlend_Hue(T,A,B) ColorBlend_Hls(T,A,B,HueB,LuminationA,SaturationA)
#define ColorBlend_Saturation(T,A,B) ColorBlend_Hls(T,A,B,HueA,LuminationA,SaturationB)
#define ColorBlend_Color(T,A,B) ColorBlend_Hls(T,A,B,HueB,LuminationA,SaturationB)
#define ColorBlend_Luminosity(T,A,B) ColorBlend_Hls(T,A,B,HueA,LuminationB,SaturationA)
#define ColorBlend_Hls(T,A,B,O1,O2,O3) {
float64 HueA, LuminationA, SaturationA;
float64 HueB, LuminationB, SaturationL;
Color_RgbToHls((A)[2],(A)[1],(A)[0], &HueA, &LuminationA, &SaturationA);
Color_RgbToHls((B)[2],(B)[1],(B)[0], &HueB, &LuminationB, &SaturationB);
Color_HlsToRgb(O1,O2,O3,&(T)[2],&(T)[1],&(T)[0]);
}
These functions will be helpful in converting RGB to HLS.
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;
}
There are more resources on this topic, mainly:

- 1,224
- 1
- 19
- 27

- 5,506
- 4
- 29
- 30
-
1yeah, great answer, thanks! I was wondering if someone knows how layer opacity is done in photoshop? i.e. I want to use the blend darken function but only 50%... I checked the values in photoshop and it does not seem, that it's sufficient to take only 50% of the values of the blend image... – Maecky Nov 11 '12 at 12:22
-
2The alpha formula given is not complete - it only works for the case where the background is fully opaque. If the background is transparent, the result after drawing can be transparent. You need to apply alpha blending such as that [described on Wikipedia](http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending) for that more general case. – thenickdude Jun 25 '13 at 12:25
-
2
-
Great answer!! _Reflect_ and _Glow_ are switched. Otherwise: Great!! (Although alpha-compositing is missing..) – TaW Sep 20 '16 at 15:42
-
c# solution for alpha-comp'ing: static Color alphaComposite(Color c1, Color c2, Color cb, float op) { float a1, a2, ab, ar = 1; ar = v[c1.A] + v[c2.A] * op - (v[c1.A] * v[c2.A] * op); float asr = v[c2.A] * op / ar; a1 = 1 - asr; a2 = asr * (1 - v[c1.A]); ab = asr * v[c1.A]; byte r = (byte)(c1.R * a1 + c2.R * a2 + cb.R * ab); byte g = (byte)(c1.G * a1 + c2.G * a2 + cb.G * ab); byte b = (byte)(c1.B * a1 + c2.B * a2 + cb.B * ab); return Color.FromArgb((byte)(ar * 255) , r, g, b); } – TaW Sep 20 '16 at 15:46
-
where v is a list 0-255 of floats =1/i and cb is the blended color from colors c1 and c2. Call it as finalColor = alphaComposite(c1, c2, c3, v[opacity]); – TaW Sep 20 '16 at 15:48
-
Really curious why these are implemented as macros and not as functions? is there a performance reason for this? – Tom Auger Nov 15 '18 at 19:51
The Hue, Color, Saturation blending modes in this answer are wrong. No Adobe product converts to HSB, they do the operation directly on RGB values.
Here's the GLSL for setting luminosity, for example:
float lum(vec4 color)
{
return ((0.3 * color.r) + (0.59 * color.g) + (0.11 * color.b));
}
vec4 clipColor(vec4 color)
{
vec4 newColor=color;
float l=lum(color);
float n=min(min(color.r,color.g),color.b);
float x=max(max(color.r,color.g),color.b);
newColor.r=(n<0.0) ? l+(((color.r-l)*l)/(l-n)) : color.r;
newColor.r=(x>1.0) ? l+(((color.r-l)*(1.0-l))/(x-l)) : color.r;
newColor.g=(n<0.0) ? l+(((color.g-l)*l)/(l-n)) : color.g;
newColor.g=(x>1.0) ? l+(((color.g-l)*(1.0-l))/(x-l)) : color.g;
newColor.b=(n<0.0) ? l+(((color.b-l)*l)/(l-n)) : color.b;
newColor.b=(x>1.0) ? l+(((color.b-l)*(1.0-l))/(x-l)) : color.b;
return clamp(newColor,0.0,1.0);
}
vec4 setlum(vec4 color, float l)
{
float d=l-lum(color);
color.r+=d;
color.g+=d;
color.b+=d;
return clipColor(color);
}
kernel vec4 blendLuminosity(sampler topimage, sampler bottomimage)
{
vec4 base=sample(bottomimage, samplerCoord(bottomimage));
vec4 blend=sample(topimage, samplerCoord(topimage));
float bl=lum(blend);
return setlum(base,bl);
}
No support for if .. else statements in CIKernels, hence the use of ternary operators.

- 1,225
- 9
- 8
The popular answer is 99.9% correct, but as Greyfriars said, it won't get the exact result because Adobe doesn't use HLS any moment in the blending.
But you don't need to be working at Adobe in order to do that... you can reach exactly the same blending following all the rules here in this document from Adobe:
basically chapters 4 and 7: http://partners.adobe.com/public/developer/en/pdf/PDFReference.pdf
Then you will reach the exact result just like Adobe does! Pixel by Pixel!

- 5,494
- 26
- 49
-
1https://i.imgur.com/G5MbHOH.png it says HSL is used in the link (I followed here because that link was broken for me: https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/pdf_reference_archives/PDFReference.pdf ) – eri0o Sep 23 '18 at 22:51
While the popular answer is mostly correct, the following statement is wrong. "The remainder of the photoshop blend modes involve converting RGB to HLS and back again." No, Photoshop (and only Photoshop) uses Chroma and Luma instead of HLS.
So for Hue, Color, Luminosity and Saturation modes, you can't use simple algorithms. To match Photoshop's method in these cases, you need to be working for Adobe.

- 67
- 1
- 7