Emulating Photoshop's blending modes

Just starting out? Need help? Post your questions and find answers here.
Seymour Clufley
Addict
Addict
Posts: 1264
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Emulating Photoshop's blending modes

Post by Seymour Clufley »

I'm in the middle of making a rather fancy video processor with FFMpeg and ImageMagick. The latter has some impressive drawing functions but where I need full control I'm using PB's internal commands.

One job involves overlaying one graphic onto another using - hopefully - an emulation of Photoshop's Color Dodge. Luckily I've found this page with algorithms for emulating Photoshop's mixing modes. It's missing a few that require HSL<>RGB conversion but here's Color Dodge, for the Green channel:

Code: Select all

GNew = GTop >= 255? 255 : min(GBottom * 255 / (255 - GTop), 255)
Can anyone tell me what is signified by the question mark, the colon, and "min"? This would be a great help.

And what does "GTop >= 255" mean?

Thanks for reading,
Seymour.
Last edited by Seymour Clufley on Sun Feb 08, 2009 8:44 pm, edited 1 time in total.
User avatar
IceSoft
Addict
Addict
Posts: 1682
Joined: Thu Jun 24, 2004 8:51 am
Location: Germany

Post by IceSoft »

Code: Select all

If  GTop >= 255
GNew = 255
else
 GNew =min(GBottom * 255 / (255 - GTop), 255)
endif
Belive! C++ version of Puzzle of Mystralia
<Wrapper>4PB, PB<game>, =QONK=, PetriDish, Movie2Image, PictureManager,...
Seymour Clufley
Addict
Addict
Posts: 1264
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Post by Seymour Clufley »

Thanks very much, IceSoft! I'll go and test it right now!

By the way, what language is the original code in? Is it C?
User avatar
IceSoft
Addict
Addict
Posts: 1682
Joined: Thu Jun 24, 2004 8:51 am
Location: Germany

Post by IceSoft »

Seymour Clufley wrote:Thanks very much, IceSoft! I'll go and test it right now!

By the way, what language is the original code in? Is it C?
Seems to be C /C++

BTW:
This will not work on PB (min(...,...) is not a PB function

Code: Select all

min(GBottom * 255 / (255 - GTop), 255) 
Belive! C++ version of Puzzle of Mystralia
<Wrapper>4PB, PB<game>, =QONK=, PetriDish, Movie2Image, PictureManager,...
User avatar
Psychophanta
Always Here
Always Here
Posts: 5153
Joined: Wed Jun 11, 2003 9:33 pm
Location: Anare
Contact:

Post by Psychophanta »

Can be used something like:

Code: Select all

Procedure.l min(a.l,b.l)
  If a>b:ProcedureReturn b:EndIf
  ProcedureReturn a
EndProcedure
http://www.zeitgeistmovie.com

while (world==business) world+=mafia;
Seymour Clufley
Addict
Addict
Posts: 1264
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Post by Seymour Clufley »

Yes, I guessed it stood for "minimum".

Thanks to both of you for your help here.

Now I'll see if I can translate all the other mixing modes by myself!
Seymour Clufley
Addict
Addict
Posts: 1264
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Post by Seymour Clufley »

Below is code that successfully emulates most of Photoshop's blending modes.

First the good news...

The following Photoshop modes seem to work:
  • Add (Linear Dodge)
  • Multiply
  • Screen
  • Overlay
  • Hard Light
  • Color Burn
  • Color Dodge
  • Linear Burn
  • Darken
  • Lighten
  • Difference
  • Exclusion
  • Vivid Light
  • Pin Light
  • Hard Mix
There are some non-Photoshop modes included which all seem to work as the original author (Jens Gruschel) demonstrates on his site.
  • Reflect
  • Glow
  • Phoenix
  • Negation
  • Average
  • Subtract
  • Freeze
  • Stamp
Now the bad news... The following Photoshop modes don't seem to work:
  • Hue
  • Saturation
  • Color
  • Luminosity
  • Linear Light
  • Soft Light
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.

And here's the code:

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
Seymour Clufley
Addict
Addict
Posts: 1264
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Post by Seymour Clufley »

I've updated the above code.

The BlendPixel procedure now has an opacity parameter (0-100) so you can overlay "by degree", as in Photoshop.

Soft Light now works a bit better than it did before. Linear Light is still a disaster.

I've implemented Froggerprogger's conversion code which works much better, allowing the remaining Photoshop modes to be added.

However they're far from perfect...
  • Saturation suffers from too much blue and too little red.
  • Color suffers from too little green.
  • Hue. The reds seem to be bang on. Greens are too bright. Blues are too dark.
  • Luminosity is the worst. There is too much green and far too much red. The blues seem to be pretty much correct.
I will continue to work on this and hopefully will deliver a final version tomorrow. I hope someone finds the code useful.

If I can find an algorithm for the Dissolve mode, I'll add that too. As far as I know that'll be all of Photoshop's modes.

Does anyone know any other blending algorithms that we could add to this?
Seymour Clufley
Addict
Addict
Posts: 1264
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Post by Seymour Clufley »

I've updated these to work with the new CustomFilter drawing mode. Much faster, but opacity isn't included (1-100%, as in Photoshop, is included in the previous code).

Again, modes are included here that are not in Photoshop. I found them on a programmer's website back when I started this thread.

The modes Luminosity, Hue, Saturation and Color are not included here because, in their current state, they're unusable.

Code: Select all

Procedure.d Channel_ColorBurn(t.d,b.d)
  
  n.d
  If t<0
      n = 0
  Else
      n = Beat(#RGBFull - ((#RGBFull - b) * #RGBFull / t), 0)
  EndIf
  
  ProcedureReturn n
  
EndProcedure
Procedure.i Blend_ColorBurn(x.i,y.i,topcol.i,bottomcol.i)
  
  CheckAlphaZero
  
  BlendRGB(Channel_ColorBurn,topcol,bottomcol)
  
  ProcedureReturn AlphaBlend(RGBA(nred,ngreen,nblue,alpha),bottomcol)
  
EndProcedure

Procedure.d Channel_Add(t.d,b.d)
  
  ProcedureReturn Defeat(#RGBFull, (b+t))
  
EndProcedure
Procedure.i Blend_Add(x.i,y.i,topcol.i,bottomcol.i)
  
  CheckAlphaZero
  
  BlendRGB(Channel_Add,topcol,bottomcol)
  
  ProcedureReturn AlphaBlend(RGBA(nred,ngreen,nblue,alpha),bottomcol)
  
EndProcedure

Procedure.d Channel_Multiply(t.d,b.d)
  
  ProcedureReturn (b*t)/#RGBFull
  
EndProcedure
Procedure.i Blend_Multiply(x.i,y.i,topcol.i,bottomcol.i)
  
  CheckAlphaZero
  
  BlendRGB(Channel_Multiply,topcol,bottomcol)
  
  ProcedureReturn AlphaBlend(RGBA(nred,ngreen,nblue,alpha),bottomcol)
  
EndProcedure

Procedure.d Channel_Screen(t.d,b.d)
  
  ProcedureReturn #RGBFull - ( ( #RGBFull - b ) * ( #RGBFull - t ) ) / #RGBFull
  
EndProcedure
Procedure.i Blend_Screen(x.i,y.i,topcol.i,bottomcol.i)
  
  CheckAlphaZero
  
  BlendRGB(Channel_Screen,topcol,bottomcol)
  
  ProcedureReturn AlphaBlend(RGBA(nred,ngreen,nblue,alpha),bottomcol)
  
EndProcedure

Procedure.d Channel_Exclusion(t.d,b.d)
  
  ProcedureReturn #RGBFull - ( ( ( #RGBFull - b ) * ( #RGBFull - t ) / #RGBFull ) + ( b * t / #RGBFull ) )
  
EndProcedure
Procedure.i Blend_Exclusion(x.i,y.i,topcol.i,bottomcol.i)
  
  CheckAlphaZero
  
  BlendRGB(Channel_Exclusion,topcol,bottomcol)
  
  ProcedureReturn AlphaBlend(RGBA(nred,ngreen,nblue,alpha),bottomcol)
  
EndProcedure

Procedure.d Channel_Difference(t.d,b.d)
  
  ProcedureReturn Difference(t,b)
  
EndProcedure
Procedure.i Blend_Difference(x.i,y.i,topcol.i,bottomcol.i)
  
  CheckAlphaZero
  
  BlendRGB(Channel_Difference,topcol,bottomcol)
  
  ProcedureReturn AlphaBlend(RGBA(nred,ngreen,nblue,alpha),bottomcol)
  
EndProcedure

Procedure.d Channel_Lighten(t.d,b.d)
  
  ProcedureReturn Beat(t,b)
  
EndProcedure
Procedure.i Blend_Lighten(x.i,y.i,topcol.i,bottomcol.i)
  
  CheckAlphaZero
  
  BlendRGB(Channel_Lighten,topcol,bottomcol)
  
  ProcedureReturn AlphaBlend(RGBA(nred,ngreen,nblue,alpha),bottomcol)
  
EndProcedure

Procedure.d Channel_Darken(t.d,b.d)
  
  ProcedureReturn Defeat(t,b)
  
EndProcedure
Procedure.i Blend_Darken(x.i,y.i,topcol.i,bottomcol.i)
  
  CheckAlphaZero
  
  BlendRGB(Channel_Darken,topcol,bottomcol)
  
  ProcedureReturn AlphaBlend(RGBA(nred,ngreen,nblue,alpha),bottomcol)
  
EndProcedure

Procedure.d Channel_ColorDodge(t.d,b.d)
  
  n.d = #RGBFull
  If t<#RGBFull
      n = Defeat(b*#RGBFull/(#RGBFull-t),#RGBFull)
  EndIf
  
  ProcedureReturn n
  
EndProcedure
Procedure.i Blend_ColorDodge(x.i,y.i,topcol.i,bottomcol.i)
  
  CheckAlphaZero
  
  BlendRGB(Channel_ColorDodge,topcol,bottomcol)
  
  ProcedureReturn AlphaBlend(RGBA(nred,ngreen,nblue,alpha),bottomcol)
  
EndProcedure

Procedure.d Channel_HardLight(t.d,b.d)
  
  n.d
  If t < 128
      n = ( 2 * b * t ) / #RGBFull
  Else
      n = #RGBFull - ( ( 2 * ( #RGBFull - b ) * ( #RGBFull - t ) ) / #RGBFull )
  EndIf
  
  ProcedureReturn n
  
EndProcedure
Procedure.i Blend_HardLight(x.i,y.i,topcol.i,bottomcol.i)
  
  CheckAlphaZero
  
  BlendRGB(Channel_HardLight,topcol,bottomcol)
  
  ProcedureReturn AlphaBlend(RGBA(nred,ngreen,nblue,alpha),bottomcol)
  
EndProcedure

Procedure.d Channel_PinLight(t.d,b.d)
  
  n.d
  If t > 128
      n = Beat(b,2*(t-128))
  Else
      n = Defeat(b,2*t)
  EndIf
  
  ProcedureReturn n
  
EndProcedure
Procedure.i Blend_PinLight(x.i,y.i,topcol.i,bottomcol.i)
  
  CheckAlphaZero
  
  BlendRGB(Channel_PinLight,topcol,bottomcol)
  
  ProcedureReturn AlphaBlend(RGBA(nred,ngreen,nblue,alpha),bottomcol)
  
EndProcedure

Procedure.d Channel_HardMix(t.d,b.d)
  
  n.d = Channel_VividLight(t,b)
  If n<128
      n = 0
  Else
      n = #RGBFull
  EndIf
  
  ProcedureReturn n
  
EndProcedure
Procedure.i Blend_HardMix(x.i,y.i,topcol.i,bottomcol.i)
  
  CheckAlphaZero
  
  BlendRGB(Channel_HardMix,topcol,bottomcol)
  
  ProcedureReturn AlphaBlend(RGBA(nred,ngreen,nblue,alpha),bottomcol)
  
EndProcedure
Here are some more, but these ones don't work perfectly:

Code: Select all

Procedure.d Channel_LinearBurn(t.d,b.d)
  
  n.d
  If t<0
      n = 0
  Else
      n = Beat(#RGBFull - ((#RGBFull - b) * #RGBFull / t), 0)
  EndIf
  
  ProcedureReturn n
  
EndProcedure
Procedure.i Blend_LinearBurn(x.i,y.i,topcol.i,bottomcol.i)
  
  CheckAlphaZero
  
  BlendRGB(Channel_LinearBurn,topcol,bottomcol)
  
  ProcedureReturn AlphaBlend(RGBA(nred,ngreen,nblue,alpha),bottomcol)
  
EndProcedure

Procedure.d Channel_VividLight(t.d,b.d)
  
  n.d
  If b<128
      n = Channel_ColorBurn(2*b,t)
  Else
      n = Channel_ColorDodge(2*(b-128),t)
  EndIf
  
  ProcedureReturn n
  
EndProcedure
Procedure.i Blend_VividLight(x.i,y.i,topcol.i,bottomcol.i)
  
  CheckAlphaZero
  
  BlendRGB(Channel_VividLight,topcol,bottomcol)
  
  ProcedureReturn AlphaBlend(RGBA(nred,ngreen,nblue,alpha),bottomcol)
  
EndProcedure
You'll need these helper procedures:

Code: Select all

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
Enjoy!
JACK WEBB: "Coding in C is like sculpting a statue using only sandpaper. You can do it, but the result wouldn't be any better. So why bother? Just use the right tools and get the job done."
dige
Addict
Addict
Posts: 1391
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Re: Emulating Photoshop's blending modes

Post by dige »

Could'nt test the customfilter. What is CheckAlphaZero?
"Daddy, I'll run faster, then it is not so far..."
Seymour Clufley
Addict
Addict
Posts: 1264
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Re: Emulating Photoshop's blending modes

Post by Seymour Clufley »

That's just a macro to check whether the colour being overlaid (the top colour) has zero alpha. If it has, the procedure just returns the bottom colour (since nothing is being painted over it).

I'm sorry that I didn't include it.
JACK WEBB: "Coding in C is like sculpting a statue using only sandpaper. You can do it, but the result wouldn't be any better. So why bother? Just use the right tools and get the job done."
User avatar
[blendman]
Enthusiast
Enthusiast
Posts: 297
Joined: Thu Apr 07, 2011 1:14 pm
Location: 3 arks
Contact:

Re: Emulating Photoshop's blending modes

Post by [blendman] »

Hi

Your exemple works fine.

But how can I use this with some alpha chanel, and png image ?

Thank you very much.
Seymour Clufley
Addict
Addict
Posts: 1264
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Re: Emulating Photoshop's blending modes

Post by Seymour Clufley »

I don't know if you can use these blending modes over a transparent image, even in Photoshop.
JACK WEBB: "Coding in C is like sculpting a statue using only sandpaper. You can do it, but the result wouldn't be any better. So why bother? Just use the right tools and get the job done."
User avatar
[blendman]
Enthusiast
Enthusiast
Posts: 297
Joined: Thu Apr 07, 2011 1:14 pm
Location: 3 arks
Contact:

Re: Emulating Photoshop's blending modes

Post by [blendman] »

Seymour Clufley wrote:I don't know if you can use these blending modes over a transparent image, even in Photoshop.
Hi

In photoshop, you can have some layers with transparents pixels, and blend them :

layer1 (the background) :
Image

layer 2 (with transparent pixels):
Image

blend mode in photoshop.
- normal :
Image

- multiply
Image

-overlay
Image

-add :
Image

I have tried to have the normal mode, and with a gadgetimage, it's easy.
So for the normal mode, can we use this simple technic ?

The code :

Code: Select all

Enumeration
  #Kbd_Add
  #Kbd_Subtract
  #kbd_up
  #kbd_down
EndEnumeration

;{ init
If UseJPEGImageDecoder() = 0 Or UsePNGImageDecoder() = 0 Or UseJPEGImageEncoder() = 0 Or UsePNGImageEncoder()=0 Or UseTGAImageDecoder()=0 Or UseTIFFImageDecoder() =0
  MessageRequester("Erreur", "Erreur de chargement des decoder images")
  End
EndIf
;}

;{ variables
Global quit.b, Event, size = 200, alpha_layer2.a=255
;}

Declare resizepicture()

Procedure event_()
  Event = WaitWindowEvent() ; This line waits until an event is received from Windows  
  WindowID = EventWindow() ; The Window where the event is generated, can be used in the gadget procedures  
  GadgetID = EventGadget() ; Is it a gadget event?  
  EventType = EventType() ; The event type
  
  Select Event
    Case #PB_Event_CloseWindow
      quit=1        
      
    Case #PB_Event_Menu
      Select EventMenu()
          
        Case #Kbd_Add          
          size + 50
          resizepicture()
          
        Case #Kbd_Subtract          
          If size > 50
            size - 50
            resizepicture()  
          EndIf
          
        Case #Kbd_up         
          If alpha_layer2<255
            alpha_layer2 +1
            resizepicture()
          EndIf
          SetWindowTitle(0,"Layer test - Alpha = "+Str(alpha_layer2))
          
        Case #kbd_down        
          If alpha_layer2>1
            alpha_layer2 -1
            resizepicture()
          EndIf   
          SetWindowTitle(0,"Layer test - Alpha = "+Str(alpha_layer2))
      EndSelect      
  EndSelect  
EndProcedure

Procedure open_win()
  If OpenWindow(0, 0,0, 400,400, "Layer test",  #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget | #PB_Window_SizeGadget | #PB_Window_TitleBar | #PB_Window_ScreenCentered | #PB_Window_WindowCentered)
    SmartWindowRefresh(0,1)   
    
    LoadImage(0,"1.jpg")
    LoadImage(1,"2.png")
    CreateImage(2,800,800)
    CopyImage(1,3)
      
    AddKeyboardShortcut(0, #PB_Shortcut_Add, #Kbd_Add)
    AddKeyboardShortcut(0, #PB_Shortcut_Subtract, #Kbd_Subtract)
    AddKeyboardShortcut(0, #PB_Shortcut_Up, #Kbd_up)
    AddKeyboardShortcut(0, #PB_Shortcut_Down, #Kbd_down)
    
    ImageGadget(0, 0,0,size,size, ImageID(2),#PB_Image_Border|#PB_Image_Smooth) 
    resizepicture()  
  EndIf  
EndProcedure

Procedure resizepicture()
  CopyImage(1,3)
  ResizeImage(3,size,size)
  CopyImage(0,4)
  ResizeImage(4,size,size)
  CopyImage(2,5)
  ResizeImage(5,size,size)
  StartDrawing(ImageOutput(5))
  DrawingMode(#PB_2DDrawing_AlphaChannel)
  Box(0,0,size,size,RGBA(15,15,15,255))
  DrawingMode(#PB_2DDrawing_Default)
  Box(0,0,size,size,RGB(255,255,255))
  DrawingMode(#PB_2DDrawing_AlphaBlend)    
  DrawImage(ImageID(4),0,0)
  DrawAlphaImage(ImageID(3),0,0,alpha_layer2)
  StopDrawing()  
  SetGadgetState(0,ImageID(5))
EndProcedure

open_win()
SaveImage(5,"save_newlayer.jpg")
Repeat
  event_()  
Until quit = 1 Or Event = #PB_Event_CloseWindow

End
thank you ;)
Seymour Clufley
Addict
Addict
Posts: 1264
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Re: Emulating Photoshop's blending modes

Post by Seymour Clufley »

Yes I know PS layers can be transparent. I was talking about what happens when you have a layer superimposed over a transparent area... for example, a black layer with "screen" mode superimposed over a transparent layer. I think that results in a black image.

As for your code, I've never used the canvas gadget so can't comment. You try it and see what works! :)
JACK WEBB: "Coding in C is like sculpting a statue using only sandpaper. You can do it, but the result wouldn't be any better. So why bother? Just use the right tools and get the job done."
Post Reply