Below is code that successfully emulates most of Photoshop's blending modes.
First the good news...
There are some non-Photoshop modes included which all seem to work as the original author (Jens Gruschel) demonstrates on
.
Now the bad news... The following Photoshop modes don't seem to work:
The first four rely on working in the HSL space - perhaps the error is in the conversion in or out of that.
LinearLight and SoftLight don't use HSL. I just can't get the calculations right. Three SoftLight procedures are included (each using a different algorithm) along with a LinearLight procedure. If anyone wants to work on these or the modes using HSL, please do.
Code: Select all
;- START
;{
UseJPEGImageDecoder()
Procedure R(str.s)
MessageRequester("Report",str,0)
EndProcedure
Procedure.d Beat(a.d,b.d)
If a>b
ProcedureReturn a
Else
ProcedureReturn b
EndIf
EndProcedure
Procedure.d Defeat(a.d,b.d)
If a<b
ProcedureReturn a
Else
ProcedureReturn b
EndIf
EndProcedure
Procedure.d Difference(a.d,b.d)
If a=b
ProcedureReturn 0
EndIf
ProcedureReturn Beat(a,b)-Defeat(a,b)
EndProcedure
Procedure ReportImage(img.i,free.b)
If Not IsImage(img)
ProcedureReturn R("img is not an image!")
EndIf
iw = ImageWidth(img)
ih = ImageHeight(img)
win = OpenWindow(#PB_Any,0,0,iw,ih,"Report Image",#PB_Window_BorderLess|#PB_Window_ScreenCentered)
imgad = ImageGadget(#PB_Any,0,0,iw,ih,ImageID(img))
escapekey = 1
spacekey = 2
returnkey = 3
AddKeyboardShortcut(win,#PB_Shortcut_Escape,escapekey)
AddKeyboardShortcut(win,#PB_Shortcut_Space,spacekey)
AddKeyboardShortcut(win,#PB_Shortcut_Return,returnkey)
Repeat
we = WindowEvent()
If we
If we=#PB_Event_Menu
Break
EndIf
Else
Delay(10)
EndIf
ForEver
CloseWindow(win)
If free
FreeImage(img)
EndIf
EndProcedure
#RGBFull = 255
#IWMax = 2000
#IHMax = 2000
Enumeration ; mixing modes
#Normal
#Add
#Multiply
#Screen
#Overlay
#SoftLight
#HardLight
#ColorBurn
#LinearBurn
#ColorDodge
#LinearDodge
#Darken
#Lighten
#Difference
#Exclusion
#LinearLight
#PinLight
#VividLight
#HardMix
#Hue
#Saturation
#Color
#Luminosity
#Average
#Negation
#Subtract
#Reflect
#Glow
#Phoenix
#Stamp
#Freeze
EndEnumeration
;}
Structure xArray
yPoint.i[#IHMax]
EndStructure
Structure imageryinfo
img.i
pixeltable.xArray[#IWMax]
w.f
h.f
EndStructure
Macro px(x,y)
pixeltable[x]\yPoint[y]
EndMacro
Procedure MixTwoColors(color1.l, color2.i, percent.f)
r.f = ((Red(color1)*percent)/100) + ((Red(color2)*(100-percent)) / 100)
g.f = ((Green(color1)*percent)/100) + ((Green(color2)*(100-percent)) / 100)
b.f = ((Blue(color1)*percent)/100) + ((Blue(color2)*(100-percent)) / 100)
ProcedureReturn RGB(r,g,b)
EndProcedure
Procedure.b LearnImage(file.s,*info.imageryinfo)
*info\img = LoadImage(#PB_Any,file)
If Not IsImage(*info\img) : ProcedureReturn #False : EndIf
*info\w = ImageWidth(*info\img)
*info\h = ImageHeight(*info\img)
StartDrawing(ImageOutput(*info\img))
For x = 0 To *info\w
For y = 0 To *info\h
*info\px(x,y) = Point(x,y)
Next y
Next x
StopDrawing()
ProcedureReturn #True
EndProcedure
;- PHOTOSHOP BLENDING MODES
Procedure.i Blend_Add(top.f,bottom.f)
new.f = Defeat(#RGBFull, (bottom+top))
ProcedureReturn new
EndProcedure
Procedure.i Blend_Multiply(top.f,bottom.f)
new.f = (bottom*top )/#RGBFull
ProcedureReturn new
EndProcedure
Procedure.i Blend_Screen(top.f,bottom.f)
new.f = #RGBFull - ( ( #RGBFull - bottom ) * ( #RGBFull - top ) ) / #RGBFull
ProcedureReturn new
EndProcedure
Procedure.i Blend_Overlay(top.f,bottom.f)
new.f
If bottom < 128
new = ( 2 * bottom * top ) / #RGBFull
Else
new = #RGBFull - ( 2 * ( #RGBFull - bottom ) * ( #RGBFull - top ) / #RGBFull )
EndIf
ProcedureReturn new
EndProcedure
;- PROBLEM WITH "SOFT LIGHT"
Procedure.i Blend_SoftLight1(top.f,bottom.f)
; ((L < 128) ? (2 * ((B >> 1) + 64)) * (L / 255): \
; (255 - (2 * (255 - ((B >> 1) + 64)) * (255 - L) / 255))))
; ((top < 128) ? (2 * ((bottom >> 1) + 64)) * (top / #RGBFull): \
; (#RGBFull - (2 * (#RGBFull - ((bottom >> 1) + 64)) * (#RGBFull - top) / #RGBFull))))
; ((top < 128) ? (2 * ((bottom >> 1) + 64)) * (top / #RGBFull): \
; (#RGBFull - (2 * (#RGBFull - ((bottom >> 1) + 64)) * (#RGBFull - top) / #RGBFull))))
new.f
If top < 128
new = ( 2 * bottom * top ) / #RGBFull
Else
new = #RGBFull - ( ( 2 * ( #RGBFull - bottom ) * ( #RGBFull - top ) ) / #RGBFull )
EndIf
ProcedureReturn new
EndProcedure
Procedure.i Blend_SoftLight2(top.f,bottom.f)
a.f = bottom
b.f = top
c.f = a * b * Sqr(8)
new.f = c + a * (#RGBFull-((#RGBFull-a)*(#RGBFull-b) * Sqr(8))-c) * Sqr(8)
ProcedureReturn new
EndProcedure
Procedure.i Blend_SoftLight3(top.f,bottom.f)
new.f
If top > 128
new = 255 - ((255-bottom) * (255-(top-128)) )
Else
new = bottom * (top+128)
EndIf
ProcedureReturn new
EndProcedure
Procedure.i Blend_SoftLight(top.f,bottom.i)
; ((L < 128) ? (2 * ((B >> 1) + 64)) * (L / 255): \
; (255 - (2 * (255 - ((B >> 1) + 64)) * (255 - L) / 255))))
; ((top < 128) ? (2 * ((bottom >> 1) + 64)) * (top / #RGBFull): \
; (#RGBFull - (2 * (#RGBFull - ((bottom >> 1) + 64)) * (#RGBFull - top) / #RGBFull))))
; ((top < 128) ? (2 * ((bottom >> 1) + 64)) * (top / #RGBFull): \
; (#RGBFull - (2 * (#RGBFull - ((bottom >> 1) + 64)) * (#RGBFull - top) / #RGBFull))))
new.i
If top < 128
new = (2 * ((bottom>>1) + 64 )) * (top/#RGBFull)
Else
new = #RGBFull - (2 * (#RGBFull - ((bottom>>1) + 64)) * (#RGBFull - top) / #RGBFull)
EndIf
ProcedureReturn new
EndProcedure
Procedure.i Blend_HardLight(top.f,bottom.f)
new.f
If top < 128
new = ( 2 * bottom * top ) / #RGBFull
Else
new = #RGBFull - ( ( 2 * ( #RGBFull - bottom ) * ( #RGBFull - top ) ) / #RGBFull )
EndIf
ProcedureReturn new
EndProcedure
Procedure.i Blend_ColorBurn(top.f,bottom.f)
new.f
If top<0
new = 0
Else
new = Beat(#RGBFull - ((#RGBFull - bottom) * #RGBFull / top), 0)
EndIf
ProcedureReturn new
EndProcedure
Procedure.i Blend_LinearBurn(top.f,bottom.f)
new.f
If top<0
new = 0
Else
new = Beat(#RGBFull - ((#RGBFull - bottom) * #RGBFull / top), 0)
EndIf
ProcedureReturn new
EndProcedure
Procedure.i Blend_ColorDodge(top.f,bottom.f)
new.f = #RGBFull
If top<#RGBFull
new = Defeat(bottom*#RGBFull/(#RGBFull-top),#RGBFull)
EndIf
ProcedureReturn new
EndProcedure
Procedure.i Blend_Darken(top.f,bottom.f)
new.f = Defeat(bottom,top)
ProcedureReturn new
EndProcedure
Procedure.i Blend_Lighten(top.f,bottom.f)
new.f = Beat(bottom,top)
ProcedureReturn new
EndProcedure
Procedure.i Blend_Difference(top.f,bottom.f)
new.f =Difference(bottom,top)
ProcedureReturn new
EndProcedure
Procedure.i Blend_Exclusion(top.f,bottom.f)
new.f = #RGBFull - ( ( ( #RGBFull - bottom ) * ( #RGBFull - top ) / #RGBFull ) + ( bottom * top / #RGBFull ) )
ProcedureReturn new
EndProcedure
Procedure.i Blend_LinearDodge(top.f,bottom.f)
ProcedureReturn Blend_Add(top,bottom)
EndProcedure
;- PROBLEM WITH "LINEAR LIGHT"
Procedure.i Blend_LinearLight(top.f,bottom.f)
new.f
; ATTEMPT 1
If bottom < 128
new = Blend_LinearBurn((2*bottom),top)
Else
new = Blend_LinearDodge((2*(bottom-128)),top)
EndIf
; ATTEMPT 2
If top>128
new = bottom + (2*(top-128))
Else
new = bottom + (2*top) - 255
EndIf
ProcedureReturn new
EndProcedure
Procedure.i Blend_VividLight(top.f,bottom.f)
; (B < 128)? Blend_ColorBurn((2*B),L):ChannelBlend_ColorDodge((2*(B - 128)),L))
new.f
If bottom<128
new = Blend_ColorBurn(2*bottom,top)
Else
new = Blend_ColorDodge(2*(bottom-128),top)
EndIf
ProcedureReturn new
EndProcedure
Procedure.i Blend_PinLight(top.f,bottom.f)
new.f
If top > 128
new = Beat(bottom,2*(top-128))
Else
new = Defeat(bottom,2*top)
EndIf
ProcedureReturn new
EndProcedure
Procedure.i Blend_HardMix(top.f,bottom.f)
; ((Blend_VividLight(B,L) < 128) ? 0:255)
new.f = Blend_VividLight(bottom,top)
If new<128
new = 0
Else
new = #RGBFull
EndIf
ProcedureReturn new
EndProcedure
Enumeration
; do not change their order!
#COLOR_RGB
#COLOR_CMY
#COLOR_HSV
#COLOR_HSL
EndEnumeration
Structure Color
StructureUnion
r.f ; Red in RGB [0.0, 1.0]
c.f ; Cyan in CMY [0.0, 1.0]
h.f ; Hue in HSV/HSL [0.0, 360.0[
EndStructureUnion
StructureUnion
g.f ; Green in RGB [0.0, 1.0]
m.f ; Magenta in CMY [0.0, 1.0]
s.f ; Saturation in HSV/HSL [0.0, 1.0]
EndStructureUnion
StructureUnion
b.f ; Blue in RGB [0.0, 1.0]
y.f ; Yellow in CMY [0.0, 1.0]
v.f ; Value in HSV [0.0, 1.0]
l.f ; Lightness in HSL [0.0, 1.0]
EndStructureUnion
type.l ; gives the type. One of #COLOR_RGB, #COLOR_CMY, #COLOR_HSV, #COLOR_HSL
EndStructure
ProcedureDLL.i ColorSetRGB(*c.Color, r.f, g.f, b.f) ; sets *c to the RGB-color given by r,g,b, each in range [0.0, 1.0] (no check is made) and returns *c
*c\r = r
*c\g = g
*c\b = b
*c\type = #COLOR_RGB
ProcedureReturn *c
EndProcedure
ProcedureDLL.l ColorComplement(*c.Color) ; complements *c and returns *c
If *c\type <= #COLOR_CMY
; if #COLOR_RGB or #COLOR_CMY
*c\r = 1.0 - *c\r
*c\g = 1.0 - *c\g
*c\b = 1.0 - *c\b
Else
; if #COLOR_HSV or #COLOR_HSL
If *c\h >= 180.0
*c\h - 180.0
Else
*c\h + 180.0
EndIf
EndIf
EndProcedure
;- some helper-proceudures
Procedure.f Max3F(a.f, b.f, c.f)
If a > b
If a > c
ProcedureReturn a
Else
ProcedureReturn c
EndIf
Else
If b > c
ProcedureReturn b
Else
ProcedureReturn c
EndIf
EndIf
EndProcedure
Procedure.f Min3F(a.f, b.f, c.f)
If a < b
If a < c
ProcedureReturn a
Else
ProcedureReturn c
EndIf
Else
If b < c
ProcedureReturn b
Else
ProcedureReturn c
EndIf
EndIf
EndProcedure
Procedure.f HSL2RGBHelper(q1.f, q2.f, h.f)
If h >= 360.0
h - 360.0
ElseIf h < 0.0
h + 360.0
EndIf
If h < 60.0
ProcedureReturn q1+(q2-q1)*h/60.0
ElseIf h < 180.0
ProcedureReturn q2
ElseIf h < 240.0
ProcedureReturn q1+(q2-q1)*(240.0-h)/60.0
Else
ProcedureReturn q1
EndIf
EndProcedure
ProcedureDLL.i HSL2RGB(*c.Color) ; converts HSL-color *c to RGB and returns *c. No check if HSL is made!
Protected h.f, l.f, s.f
Protected f.f, p1.f, p2.f, t.f
Protected i.l
h = *c\h
l = *c\l
s = *c\s
If l<=0.5
p2 = l*(1.0+s)
Else
p2 = l+s-l*s
EndIf
p1 = 2.0*l-p2
If s=0.0
; it's a gray-tone
*c\r = l
*c\g = l
*c\b = l
Else
*c\r = HSL2RGBHelper(p1, p2, h+120.0)
*c\g = HSL2RGBHelper(p1, p2, h)
*c\b = HSL2RGBHelper(p1, p2, h-120.0)
EndIf
*c\type = #COLOR_RGB
ProcedureReturn *c
EndProcedure
ProcedureDLL.l ColorCopy(*c.Color) ; copies *c and returns *copy
Protected *out = AllocateMemory(SizeOf(Color))
CopyMemory(*c, *out, SizeOf(Color))
ProcedureReturn *out
EndProcedure
ProcedureDLL.l Color2RGB(*c.Color) ; converts *c from any color-type to RGB and returns *c
Select *c\type
Case #COLOR_RGB : ProcedureReturn *c
;Case #COLOR_CMY : ProcedureReturn CMY2RGB(*c)
;Case #COLOR_HSV : ProcedureReturn HSV2RGB(*c)
Case #COLOR_HSL : ProcedureReturn HSL2RGB(*c)
EndSelect
EndProcedure
ProcedureDLL.l ColorRGB(*c.Color) ; converts an internal copy of *c to RGB and returns a Purebasic-RGB-colorcode same as returned by PB's RGB()
Protected *cc.Color, rgb.l
*cc = ColorCopy(*c)
Color2RGB(*cc)
rgb = RGB(255 * *cc\r, 255 * *cc\g, 255 * *cc\b)
FreeMemory(*cc)
ProcedureReturn rgb
EndProcedure
;- change color to HSL
ProcedureDLL.i RGB2HSL(*c.Color) ; converts RGB-color *c to HSL and returns *c. No check if RGB is made!
Protected r.f, g.f, b.f, max.f, min.f, delta.f
r = *c\r
g = *c\g
b = *c\b
max = Max3F(r,g,b)
min = Min3F(r,g,b)
delta = max - min
If delta <> 0.0
; get lightness
*c\l = (max + min) / 2.0
; get saturation
If *c\l <= 0.5
*c\s = delta/(max+min)
Else
*c\s = delta/(2-max-min)
EndIf
; get hue
If r = max
*c\h = (g-b)/delta
ElseIf g = max
*c\h = 2.0 + (b-r)/delta
ElseIf b = max
*c\h = 4.0 + (r-g)/delta
EndIf
*c\h * 60.0
If *c\h<0.0
*c\h + 360.0
EndIf
Else
; it's black
*c\s = 0
*c\h = 0 ; *c\h is even undefined
EndIf
*c\type = #COLOR_HSL
ProcedureReturn *c
EndProcedure
Procedure.b PB2Color(pb.l,*c.Color)
ColorSetRGB(*c,1/#RGBFull*Red(pb),1/#RGBFull*Green(pb),1/#RGBFull*Blue(pb))
EndProcedure
;- PHOTOSHOP MODES USING HSL CONVERSION
Procedure.i Blend_Hue(*top.Color,*bottom.Color)
composite.Color
composite\type = #COLOR_HSL
composite\h = *top\h
composite\s = *bottom\s
composite\l = *bottom\l
ProcedureReturn ColorRGB(@composite)
EndProcedure
Procedure.i Blend_Saturation(*top.Color,*bottom.Color)
composite.Color
composite\type = #COLOR_HSL
composite\h = *bottom\h
composite\s = *top\s
composite\l = *bottom\l
ProcedureReturn ColorRGB(@composite)
EndProcedure
Procedure.i Blend_Color(*top.Color,*bottom.Color)
composite.Color
composite\type = #COLOR_HSL
composite\h = *top\h
composite\s = *top\s
composite\l = *bottom\l
ProcedureReturn ColorRGB(@composite)
EndProcedure
;- PROBLEM WITH LUMINOSITY
Procedure.i Blend_Luminosity(*top.Color,*bottom.Color)
composite.Color
composite\type = #COLOR_HSL
composite\h = *bottom\h
composite\s = *bottom\s
composite\l = *top\l
ProcedureReturn ColorRGB(@composite)
EndProcedure
;- NON-PHOTOSHOP BLENDING MODES
Procedure.i Blend_Negation(top.f,bottom.f)
ProcedureReturn #RGBFull-Abs(#RGBFull - bottom - top)
EndProcedure
Procedure.i Blend_Average(top.f,bottom.f)
ProcedureReturn (bottom+top)/2
EndProcedure
Procedure.i Blend_Subtract(top.f,bottom.f)
new.f = bottom+top-#RGBFull
new = Beat(new,0)
new = Defeat(new,#RGBFull)
ProcedureReturn new
EndProcedure
Procedure.i Blend_Reflect(top.f,bottom.f)
new.f
If bottom = #RGBFull
new = bottom
Else
new = Defeat(#RGBFull, (top * top / (#RGBFull - bottom)))
EndIf
ProcedureReturn new
EndProcedure
Procedure.i Blend_Glow(top.f,bottom.f)
new.f
If top = #RGBFull
new = top
Else
new = Defeat(#RGBFull, (bottom * bottom / (#RGBFull - top)))
EndIf
ProcedureReturn new
EndProcedure
Procedure.i Blend_Phoenix(top.f,bottom.f)
new.f
new = Defeat(bottom,top) - Beat(bottom,top) + #RGBFull
ProcedureReturn new
EndProcedure
Procedure.i Blend_Stamp(top.f,bottom.f)
new.f
c.f
c = top + (2*top) - #RGBFull
If c < 0
new = 0
ElseIf c > #RGBFull
new = #RGBFull
Else
new = c
EndIf
ProcedureReturn new
EndProcedure
Procedure.i Blend_Freeze(top.f,bottom.f)
If bottom = 0
ProcedureReturn 0
Else
new.f = (#RGBFull - (Sqr(#RGBFull-top))) / bottom
ProcedureReturn Beat(0,new)
EndIf
EndProcedure
Macro BlendRGB(ProcName)
r = ProcName(rt,rb)
g = ProcName(gt,gb)
b = ProcName(bt,bb)
EndMacro
;-
;- THE PROCEDURE
Procedure.i BlendPixel(pixeltop.i,pixelbottom.i,mode.b,opacity.f)
If opacity<1
ProcedureReturn pixelbottom
EndIf
rt.f = Red(pixeltop)
gt.f = Green(pixeltop)
bt.f = Blue(pixeltop)
rb.f = Red(pixelbottom)
gb.f = Green(pixelbottom)
bb.f = Blue(pixelbottom)
Select mode
Case #Normal
pixelblend = pixeltop
Case #Hue, #Saturation, #Color, #Luminosity
PB2Color(pixeltop,@tsplit.Color)
PB2Color(pixelbottom,@bsplit.Color)
;ColorSetRGB(@bsplit.Color,1/#RGBFull*rb,1/#RGBFull*gb,1/#RGBFull*bb)
RGB2HSL(@tsplit)
RGB2HSL(@bsplit)
;Color_RgbToHls(pixelbottom,@bsplit.ColorAnalysis)
Select mode
Case #Hue
pixelblend = Blend_Hue(@tsplit,@bsplit)
Case #Saturation
pixelblend = Blend_Saturation(@tsplit,@bsplit)
Case #Color
pixelblend = Blend_Color(@tsplit,@bsplit)
Case #Luminosity
pixelblend = Blend_Luminosity(@tsplit,@bsplit)
EndSelect
Default
Select mode
Case #Add, #LinearDodge
BlendRGB(Blend_Add)
Case #Multiply
BlendRGB(Blend_Multiply)
Case #Screen
BlendRGB(Blend_Screen)
Case #Overlay
BlendRGB(Blend_Overlay)
Case #SoftLight
BlendRGB(Blend_SoftLight)
Case #HardLight
BlendRGB(Blend_HardLight)
Case #VividLight
BlendRGB(Blend_VividLight)
Case #ColorBurn
BlendRGB(Blend_ColorBurn)
Case #LinearBurn
BlendRGB(Blend_LinearBurn)
Case #ColorDodge
BlendRGB(Blend_ColorDodge)
Case #Darken
BlendRGB(Blend_Darken)
Case #Lighten
BlendRGB(Blend_Lighten)
Case #Difference
BlendRGB(Blend_Difference)
Case #Exclusion
BlendRGB(Blend_Exclusion)
Case #HardMix
BlendRGB(Blend_HardMix)
Case #PinLight
BlendRGB(Blend_PinLight)
Case #LinearLight
BlendRGB(Blend_LinearLight)
Case #Hue
BlendRGB(Blend_Hue)
; non-photoshop blend modes
Case #Negation
BlendRGB(Blend_Negation)
Case #Average
BlendRGB(Blend_Average)
Case #Subtract
BlendRGB(Blend_Subtract)
Case #Reflect
BlendRGB(Blend_Reflect)
Case #Glow
BlendRGB(Blend_Glow)
Case #Phoenix
BlendRGB(Blend_Phoenix)
Case #Stamp
BlendRGB(Blend_Stamp)
Case #Freeze
BlendRGB(Blend_Freeze)
EndSelect
pixelblend = RGB(r,g,b)
EndSelect
If opacity<100
pixelblend = MixTwoColors(pixelblend,pixelbottom,opacity)
EndIf
ProcedureReturn pixelblend
EndProcedure
LearnImage(folder+"purple black.bmp",@img1.imageryinfo) ;- FILE FOR BACKGROUND IMAGE
LearnImage(folder+"85607236_o.jpg",@img2.imageryinfo) ;- FILE FOR TOP IMAGE (TO BE OVERLAID)
mixmode = #Hue
opacity = 100
StartDrawing(ImageOutput(img1\img))
bx = 0
by = 0;ih1/2
For x = 0 To img2\w-1
dx = Beat(0,bx+x)
For y = 0 To img2\h-1
dy = Beat(0,by+y)
If dx=<img1\w And dy=<img1\h
blend = BlendPixel(img2\px(x,y),img1\px(dx,dy),mixmode,opacity)
Plot(dx,dy,blend)
EndIf
Next y
Next x
StopDrawing()
;SetClipboardImage(img1\img)
ReportImage(img1\img,#True)
;-END
End