[soft 2D] blendmode (mode de fusion)

Partagez votre expérience de PureBasic avec les autres utilisateurs.
Avatar de l’utilisateur
blendman
Messages : 2017
Inscription : sam. 19/févr./2011 12:46

[soft 2D] blendmode (mode de fusion)

Message par blendman »

Salut

Voici un petit essai sur les modes de fusions (comme photoshop).

Infos
Je "gère" le canal alpha des images, donc ça marche avec des circle() ou box(), et aussi avec des drawalphaimage().
Pour ça, voici la technique :
- "composer" d'abord l'image et le fond en blendmode, uniquement en fonction d el'alpha opaque
- puis superposer l'image obtenue avec le fond (calque du dessous).

Modes de fusion testés
- normal
- multiply ( produit)
- screen (superposition)
- add ou color dodge (densité lumière -)
- overlay (incrustation)

Code

Code : Tout sélectionner

;{ infos
; test blendmode by blendman 07/2011
; pb : 4.60
;}  

;{ variables & globales
Global mode.b = 1
;}

;{ init
If UseJPEGImageDecoder() =0 Or UsePNGImageDecoder() =0
  End
EndIf
;}

;{ declare
Declare affiche_img()
Declare bm_add(x, y, SourceColor, TargetColor)
Declare bm_multiply(x, y, SourceColor, TargetColor)
Declare bm_overlay(x, y, SourceColor, TargetColor)
Declare bm_screen(x, y, SourceColor, TargetColor)
Declare Changemode()
;}

;{ open window & create image
If OpenWindow(0, 0, 0, 400, 200, "2DDrawing Example - Blendmode test", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  
  ;{ menu
  If CreateMenu(0,WindowID(0))
    MenuTitle("BlendMode")
    MenuItem(1,"BlendMode suivant"+Chr(9)+"+")
    MenuItem(2,"BlendMode précédent"+Chr(9)+"-")
  EndIf  
  ;}
  
  ;{ images
  LoadImage(1, #PB_Compiler_Home + "examples/sources/data/clouds.jpg")
  
  ; test avec une image, mais pour le moment, ne fonctionne pas complètement (manque le canal alpha avant le blendmode)
  CreateImage(2,400,200,32)
  StartDrawing(ImageOutput(2))
  DrawingMode(#PB_2DDrawing_AlphaChannel)    
  Box(0,0,400,200,RGBA(0,0,0,0))
  DrawingMode(#PB_2DDrawing_AlphaBlend)    
  Circle(300, 100, 100,RGBA(255,0,0,255))  
  StopDrawing()
    
  If CreateImage(0, 400, 200,32)     
    affiche_Img()    
  EndIf
  ;}
  
  ;{ shortcut
  AddKeyboardShortcut(0,#PB_Shortcut_Add,1)
  AddKeyboardShortcut(0,#PB_Shortcut_Subtract,2)  
  ;}
Else
  MessageRequester("Erreur", "impossible d'ouvrir une fenêtre")
  End
EndIf
;}

Repeat    
  Changemode()
Until Event = #PB_Event_CloseWindow

;{ procedure
Procedure  affiche_img()  
  If StartDrawing(ImageOutput(0))      
    DrawingMode(#PB_2DDrawing_AlphaChannel)    ; pour la transparence
    Box(0,0,400,200,RGBA(0,0,0,0))
    DrawingMode(#PB_2DDrawing_AlphaBlend)      
    DrawImage(ImageID(1), 0, 0, 400, 200) ; image de fond, on peut utiliser une image avec transparence
    ; puis on affiche l'image du dessus en fonction du mode de fusion choisi
    If mode =0 ; normal
      DrawingMode(#PB_2DDrawing_Default) 
      Circle(300, 100, 80, #Red)  
    ElseIf mode <>0      
      DrawingMode(#PB_2DDrawing_CustomFilter) 
      If mode = 1 ; add
        CustomFilterCallback(@bm_add())
        Circle(300, 100, 80, RGBA(125,125,125,255))  
      ElseIf mode = 2 ; multiply
        CustomFilterCallback(@bm_multiply())
        Circle(300, 100, 80, #Red)  
      ElseIf mode = 3 ; overlay  
        CustomFilterCallback(@bm_overlay()) 
        Circle(300, 100, 80, #Red)  
      ElseIf mode = 4 ; screen
        CustomFilterCallback(@bm_screen()) 
        Circle(300, 100, 80, RGBA(125,125,125,255))  
      EndIf
    EndIf    
    ;DrawAlphaImage(ImageID(2),0,0)  ;>>>>>>>>>>>>>>> bug actuellement dans certains cas, à revoir, car il manque la gestion du canal alpha. 
    StopDrawing() 
    ImageGadget(0, 0, 0, 400, 200, ImageID(0))
  EndIf
  
  Select mode
    Case 0
      SetWindowTitle(0,"Mode : Normal - cercle rouge")
    Case 1
      SetWindowTitle(0,"Mode : Add - cercle blanc")
    Case 2
      SetWindowTitle(0,"Mode : Multiply - cercle rouge")
    Case 3
      SetWindowTitle(0,"Mode : Overlay - cercle rouge")
    Case 4
      SetWindowTitle(0,"Mode : Screen - cercle blanc")
  EndSelect    
EndProcedure

Procedure.l min(a.l,b.l)
  If a>b
    ProcedureReturn b
  EndIf
  ProcedureReturn a
EndProcedure

Procedure bm_screen(x, y, SourceColor, TargetColor)
  ; color = 255 - ( ( 255 - bottom ) * ( 255 - top ) ) / 255  
  red = 255 -((255-Red(SourceColor))*(255-Red(TargetColor)))/255
  green = 255 -((255-Green(SourceColor))*(255-Green(TargetColor)))/255
  blue = 255 -((255-Blue(SourceColor))*(255-Blue(TargetColor)))/255   
  ProcedureReturn RGBA(red,green,blue, Alpha(TargetColor))
EndProcedure

Procedure bm_add(x, y, SourceColor, TargetColor)
  ; color = top >= 255? 255 : min(bottom * 255 / (255 - top), 255)
  If  Red(TargetColor) >= 255
    Red.l= 255
  Else
    result.l= 255 - Red(SourceColor)
    If result >0      
      Red =min(Red(TargetColor) * 255 / (result), 255)
    ElseIf result =0
      red=min(Red(TargetColor) * 255, 255)
    EndIf    
  EndIf 
  
  If  Blue(TargetColor) >= 255
    blue = 255
  Else
    result= 255 - Blue(SourceColor)
    If result >0
      blue=min(Blue(TargetColor) * 255 / (result), 255)
    ElseIf result =0
      blue = min(Blue(TargetColor) * 255, 255)     
    EndIf    
  EndIf 
  
  If  Green(TargetColor) >= 255
    Green = 255
  Else
    result= 255 - Green(SourceColor)
    If result >0
      Green =min(Green(TargetColor) * 255 / (result), 255)
    ElseIf result =0
      green =min(Green(TargetColor) * 255, 255)
    EndIf    
  EndIf 
    
  ProcedureReturn RGBA(red, green, blue, Alpha(TargetColor))
EndProcedure

Procedure bm_multiply(x, y, SourceColor, TargetColor)  
  ProcedureReturn RGBA((Red(SourceColor)*Red(TargetColor))/255,(Green(SourceColor)*Green(TargetColor))/255,(Blue(SourceColor)*Blue(TargetColor))/255, Alpha(TargetColor)*Alpha(TargetColor)/255)
EndProcedure

Macro overlay(color_source,color_targ, colo)
  If  color_source <128
    colo = ( 2 * color_source*color_targ ) / 255
  Else
    colo =255 - ( 2 * ( 255 - color_source ) * ( 255 - color_targ  ) / 255 )
  EndIf
EndMacro

Procedure bm_overlay(x, y, SourceColor, TargetColor)
  ; color = bottom < 128 ? ( 2 * bottom * top ) / 255 : 255 - ( 2 * ( 255 - bottom ) * ( 255 - top ) / 255 )
  overlay(Red(TargetColor),Red(SourceColor), red)
  overlay(Blue(TargetColor),Blue(SourceColor), blue)
  overlay(Green(TargetColor),Green(SourceColor), green)
  
  ProcedureReturn RGBA(red, green, blue, Alpha(TargetColor))
EndProcedure

Procedure Changemode()
  Event = WaitWindowEvent()
  Select event
    Case #PB_Event_CloseWindow
      End
    Case #PB_Event_Menu
      Select EventMenu()
        Case 1
          If mode<4
            mode +1
          Else 
            mode = 0
          EndIf 
          affiche_img()
          
        Case 2
          If mode>0
            mode -1
          Else 
            mode = 4
          EndIf 
          affiche_img()          
      EndSelect      
  EndSelect  
EndProcedure
;}
Les autres modes, selon le même principe
http://www.vanderlee.com/tut_fm_mixingmodes.html
http://download.java.net/javadesktop/sc ... .Mode.html
http://www.pegtop.net/delphi/articles/blendmodes/

N'hésitez pas à poster si vous avez des remarques ou si vous modifier/ajouter quelque chose au code.

ce n'est pas nécessairement la meilleure technique, mais elle me semble simple et intéressante :).

EDIT : et voici le code qui fonctionne avec une image en tant que layer, cte classe :)

Code : Tout sélectionner

;{ infos
; test blendmode by blendman 07/2011
; pb : 4.60
;} 

;{ variables & globales
Enumeration
  #fond
  #layer1
  #alpha_layer1
  #tempo
  #result
  #imagefinal  
EndEnumeration

Global mode.b = 1
;}

;{ init
If UseJPEGImageDecoder() =0 Or UsePNGImageDecoder() =0
  End
EndIf
;}

;{ declare
Declare affiche_img()
Declare bm_add(x, y, SourceColor, TargetColor)
Declare bm_multiply(x, y, SourceColor, TargetColor)
Declare bm_overlay(x, y, SourceColor, TargetColor)
Declare bm_screen(x, y, SourceColor, TargetColor)
Declare Changemode()
;}

;{ open window & create image
If OpenWindow(0, 0, 0, 400, 200, "2DDrawing Example - Blendmode test", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
 
  ;{ menu
  If CreateMenu(0,WindowID(0))
    MenuTitle("BlendMode")
    MenuItem(1,"BlendMode suivant"+Chr(9)+"+")
    MenuItem(2,"BlendMode précédent"+Chr(9)+"-")
  EndIf 
  ;}
 
  ;{ images
  LoadImage(#fond, #PB_Compiler_Home + "examples/sources/data/clouds.jpg") ; le fond "nuages"
    
  ; l'image (layer1 : ici un simple cercle rouge
  CreateImage(#layer1,400,200,32)
  StartDrawing(ImageOutput(#layer1))
  DrawingMode(#PB_2DDrawing_AlphaChannel)   
  Box(0,0,400,200,RGBA(0,0,0,0))
  DrawingMode(#PB_2DDrawing_AlphaBlend)   
  Circle(300, 100, 100,RGBA(255,0,0,255)) 
  StopDrawing()
  
  CopyImage(#layer1,#alpha_layer1)
  
  ; copy pour l'image temporaire pour le canal alpha
  CreateImage(#tempo,400,200,32)
  StartDrawing(ImageOutput(#tempo))
  DrawingMode(#PB_2DDrawing_AlphaChannel)   
  Box(0,0,400,200,RGBA(0,0,0,0))
  StopDrawing()
    
  If CreateImage(#imagefinal, 400, 200,32)     
    affiche_Img()   
  EndIf
  ;}
 
  ;{ shortcut
  AddKeyboardShortcut(0,#PB_Shortcut_Add,1)
  AddKeyboardShortcut(0,#PB_Shortcut_Subtract,2) 
  ;}
Else
  MessageRequester("Erreur", "impossible d'ouvrir une fenêtre")
  End
EndIf
;}

Repeat   
  Changemode()
Until Event = #PB_Event_CloseWindow

;{ procedure
Procedure  affiche_img() 
  ; on calcule le blendmode, uniquement sur l'alpha opaque lié au layer1
  If StartDrawing(ImageOutput(#tempo))    
    ; image de fond, on peut utiliser une image avec transparence
    DrawingMode(#PB_2DDrawing_AlphaBlend)     
    DrawImage(ImageID(#fond), 0, 0, 400, 200) 
    ; puis on affiche l'image du dessus en fonction du mode de fusion choisi
    If mode =0 ; normal
      DrawingMode(#PB_2DDrawing_Default)
    ElseIf mode <>0     
      DrawingMode(#PB_2DDrawing_CustomFilter)
      If mode = 1 ; add
        CustomFilterCallback(@bm_add())
      ElseIf mode = 2 ; multiply
        CustomFilterCallback(@bm_multiply())
      ElseIf mode = 3 ; overlay 
        CustomFilterCallback(@bm_overlay())
      ElseIf mode = 4 ; screen
        CustomFilterCallback(@bm_screen())        
      EndIf
    EndIf   
    DrawAlphaImage(ImageID(#layer1),0,0)  
    DrawingMode(#PB_2DDrawing_AlphaChannel)   
    DrawAlphaImage(ImageID(#alpha_layer1),0,0)
    StopDrawing()
  EndIf
  
  ; on calcule le resultat, l'image finale
  If StartDrawing(ImageOutput(#imagefinal))   
    DrawImage(ImageID(#fond), 0, 0, 400, 200) ; image de fond, on peut utiliser une image avec transparence   
    DrawingMode(#PB_2DDrawing_AlphaBlend) 
    DrawAlphaImage(ImageID(#tempo),0,0) 
    StopDrawing()
  EndIf
  ImageGadget(0, 0, 0, 400, 200, ImageID(#imagefinal))
     
  Select mode
    Case 0
      SetWindowTitle(0,"Mode : Normal - cercle rouge")
    Case 1
      SetWindowTitle(0,"Mode : Add - cercle blanc")
    Case 2
      SetWindowTitle(0,"Mode : Multiply - cercle rouge")
    Case 3
      SetWindowTitle(0,"Mode : Overlay - cercle rouge")
    Case 4
      SetWindowTitle(0,"Mode : Screen - cercle blanc")
  EndSelect   
EndProcedure

Procedure.l min(a.l,b.l)
  If a>b
    ProcedureReturn b
  EndIf
  ProcedureReturn a
EndProcedure

Procedure bm_screen(x, y, SourceColor, TargetColor)
  ; color = 255 - ( ( 255 - bottom ) * ( 255 - top ) ) / 255 
  red = 255 -((255-Red(SourceColor))*(255-Red(TargetColor)))/255
  green = 255 -((255-Green(SourceColor))*(255-Green(TargetColor)))/255
  blue = 255 -((255-Blue(SourceColor))*(255-Blue(TargetColor)))/255   
  ProcedureReturn RGBA(red,green,blue, Alpha(TargetColor))
EndProcedure

Procedure bm_add(x, y, SourceColor, TargetColor)
  ; color = top >= 255? 255 : min(bottom * 255 / (255 - top), 255)
  If  Red(TargetColor) >= 255
    Red.l= 255
  Else
    result.l= 255 - Red(SourceColor)
    If result >0     
      Red =min(Red(TargetColor) * 255 / (result), 255)
    ElseIf result =0
      red=min(Red(TargetColor) * 255, 255)
    EndIf   
  EndIf
 
  If  Blue(TargetColor) >= 255
    blue = 255
  Else
    result= 255 - Blue(SourceColor)
    If result >0
      blue=min(Blue(TargetColor) * 255 / (result), 255)
    ElseIf result =0
      blue = min(Blue(TargetColor) * 255, 255)     
    EndIf   
  EndIf
 
  If  Green(TargetColor) >= 255
    Green = 255
  Else
    result= 255 - Green(SourceColor)
    If result >0
      Green =min(Green(TargetColor) * 255 / (result), 255)
    ElseIf result =0
      green =min(Green(TargetColor) * 255, 255)
    EndIf   
  EndIf
   
  ProcedureReturn RGBA(red, green, blue, Alpha(TargetColor))
EndProcedure

Procedure bm_multiply(x, y, SourceColor, TargetColor) 
  ProcedureReturn RGBA((Red(SourceColor)*Red(TargetColor))/255,(Green(SourceColor)*Green(TargetColor))/255,(Blue(SourceColor)*Blue(TargetColor))/255, Alpha(TargetColor)*Alpha(TargetColor)/255)
EndProcedure

Macro overlay(color_source,color_targ, colo)
  If  color_source <128
    colo = ( 2 * color_source*color_targ ) / 255
  Else
    colo =255 - ( 2 * ( 255 - color_source ) * ( 255 - color_targ  ) / 255 )
  EndIf
EndMacro

Procedure bm_overlay(x, y, SourceColor, TargetColor)
  ; color = bottom < 128 ? ( 2 * bottom * top ) / 255 : 255 - ( 2 * ( 255 - bottom ) * ( 255 - top ) / 255 )
  overlay(Red(TargetColor),Red(SourceColor), red)
  overlay(Blue(TargetColor),Blue(SourceColor), blue)
  overlay(Green(TargetColor),Green(SourceColor), green)
 
  ProcedureReturn RGBA(red, green, blue, Alpha(TargetColor))
EndProcedure

Procedure Changemode()
  Event = WaitWindowEvent()
  Select event
    Case #PB_Event_CloseWindow
      End
    Case #PB_Event_Menu
      Select EventMenu()
        Case 1
          If mode<4
            mode +1
          Else
            mode = 0
          EndIf
          affiche_img()
         
        Case 2
          If mode>0
            mode -1
          Else
            mode = 4
          EndIf
          affiche_img()         
      EndSelect     
  EndSelect 
EndProcedure
;}