Page 1 of 1

Brush painting blendmode

Posted: Fri Jun 18, 2021 12:21 pm
by [blendman]
Hi

If you use gimp/photoshop or other kind of 2D software, you probably know that we can paint with a brush on our layer, with a selected blendmode.
It's almost the same as "layer blendmode", but for brush when we paint on the layer :).

Here is a code (thanks to stargate, #Null, and Rashad who help me) :

Code: Select all

; blendmode for brush paint, by blendman, help by stargate, #NULL
Enumeration
  ; painting blendmode
  #BM_PaintingDefault
  #BM_PaintingNormal
  #BM_PaintingNormalBorder
  #BM_PaintingMultiply
  #BM_PaintingADD
  #BM_PaintingScreen
  #BM_PaintingLast
  ; gadgets
  #Canvas = 0
  #G_BrushBlendmode
  #G_brushSize
  #G_brushAlpha
  #G_brushPas
  ; images
  #Img_brush = 0
  #Img_Layer 
EndEnumeration

;{ structures
Structure Canvas
  LeftButtonDown.i
  RightButtonDown.i
EndStructure
Global This.Canvas

Structure VPoint
  x.f
  y.f
EndStructure
Global PreviousPoint.VPoint
Global CurrentPoint.VPoint, LastPoint.VPoint

Structure sBrush
  size.w
  alpha.a
  blendmode.a
  Pas.w ; space betwen two dot
EndStructure
Global Brush.sBrush
Brush\alpha = 140
Brush\blendmode = 0
Brush\pas = 20
Brush\Size = 50
;}

Global draw

;{ procedures
; the blendmode for painting (not for the layer ;))
Procedure PaintingBMNormalBorder(X, Y, SourceColor, TargetColor)
  ; be carefull with this mode, it can give some weird result ^^
  Protected resultColor
  Protected r, g, b
  Protected a
  r = (  Red(sourceColor) * (Alpha(sourceColor) / 255.0) +   Red(targetColor) * (1 - (Alpha(sourceColor) / 255.0)))
  g = (Green(sourceColor) * (Alpha(sourceColor) / 255.0) + Green(targetColor) * (1 - (Alpha(sourceColor) / 255.0)))
  b = ( Blue(sourceColor) * (Alpha(sourceColor) / 255.0) +  Blue(targetColor) * (1 - (Alpha(sourceColor) / 255.0)))
  If Alpha(sourceColor)=255
    a = Alpha(sourceColor)
  Else
    a = (Alpha(sourceColor) + Alpha(targetColor)) * (1.0 - Alpha(sourceColor))
  EndIf
  
  resultColor = RGBA(r, g, b, a)
  ProcedureReturn resultColor
  
EndProcedure  
Procedure PaintingBMNormal(X, Y, SourceColor, TargetColor)
  
  ; This mode is a few better than the default #pb_2Ddrawing_alphablend, with brtush\alpha<40
  Protected resultColor
  Protected r, g, b
  Protected a
  
  u.d =255.0
  If Alpha(targetColor)<=2
    r = Red(sourceColor) 
    g = Green(sourceColor) 
    b = Blue(sourceColor) 
  Else
    r = (  Red(sourceColor) * (Alpha(sourceColor) / u) +   Red(targetColor) * (1 - (Alpha(sourceColor) / u)))
    g = (Green(sourceColor) * (Alpha(sourceColor) / u) + Green(targetColor) * (1 - (Alpha(sourceColor) / u)))
    b = ( Blue(sourceColor) * (Alpha(sourceColor) / u) +  Blue(targetColor) * (1 - (Alpha(sourceColor) / u)))
  EndIf
  a = Alpha(sourceColor) + Alpha(targetColor) * (1.0 - Alpha(sourceColor)/u)
  resultColor = RGBA(r, g, b, a)
  ProcedureReturn resultColor
EndProcedure
Procedure PaintingBMMultiply(X,Y,SourceColor, TargetColor)
  
  Protected resultColor
  Protected.f r, g, b, a
  u.d =255
  c.d = (brush\alpha)/255

  If Alpha(TargetColor) <5 ; And Alpha(sourceColor)<>0
    r = Red(SourceColor)
    g = Green(SourceColor)
    b = Blue(SourceColor)
  Else
    r = (  Red(sourceColor) * Red(targetColor)  )/ u
    If r>255
      r=255
    EndIf
    g = (Green(sourceColor) * Green(targetColor) )/ u
    If g>255
      g=255
    EndIf
    b = ( Blue(sourceColor) * Blue(targetColor)  )/ u
    If b>255
      b= 255
    EndIf
  EndIf
  
  If Alpha(targetColor)=0
    a = Alpha(sourceColor)
  Else
    a =( Alpha(sourceColor) + Alpha(targetColor) * (1.0 - Alpha(sourceColor)/255.0))
  EndIf
  If a>255
    a=255
  EndIf
  
  resultColor = RGBA(r, g, b, a)
  ProcedureReturn resultColor
EndProcedure
Procedure PaintingBMAdd(X,Y,SourceColor, TargetColor)
  
  Protected resultColor
  Protected.f r, g, b, a
  u.d = (0.1*brush\alpha)/255
  If Alpha(TargetColor) <5 ;And Alpha(sourceColor)<>0
    r = Red(SourceColor)
    g = Green(SourceColor)
    b = Blue(SourceColor)
  Else
    r = (  Red(sourceColor)*u + Red(targetColor)  )
    If r>255
      r=255
    EndIf
    g = (Green(sourceColor)*u + Green(targetColor))
    If g>255
      g=255
    EndIf
    b = ( Blue(sourceColor)*u + Blue(targetColor))
    If b>255
      b=255
    EndIf
  EndIf
  
  If Alpha(targetColor)=0
    a = Alpha(sourceColor)
  Else
    a =( Alpha(sourceColor) + Alpha(targetColor)  * (1.0 - Alpha(sourceColor)/255.0))
  EndIf
  If a>255
    a=255
  EndIf
  
  resultColor = RGBA(r, g, b, a)
  ProcedureReturn resultColor
EndProcedure
Procedure PaintingBMScreen(X,Y,SourceColor,TargetColor)
  
  Protected resultColor
  Protected.f r, g, b, a
  u.d =(0.1*brush\alpha)/255
  If Alpha(TargetColor) <5 ;And Alpha(sourceColor)<>0
    r = Red(SourceColor)
    g = Green(SourceColor)
    b = Blue(SourceColor)
  Else
    r = 255 -((255-Red(SourceColor)*u)*(255-Red(TargetColor)))/255
    g = 255 -((255-Green(SourceColor)*u)*(255-Green(TargetColor)))/255
    b = 255 -((255-Blue(SourceColor)*u)*(255-Blue(TargetColor)))/255   
  EndIf

  If Alpha(targetColor)=0
    a = Alpha(sourceColor)
  Else
    a =( Alpha(sourceColor) + Alpha(targetColor)  * (1.0 - Alpha(sourceColor)/255.0))
  EndIf
  If a>255
    a=255
  EndIf
  
  resultColor = RGBA(r, g, b, a)
  ProcedureReturn resultColor
EndProcedure

; for drawing
Macro distance(x1,y1,x2,y2)   
  Int(Sqr((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)) )       
EndMacro
Macro direction(x1,y1,x2,y2) ; get angle
  ATan2((y2- y1),(x2- x1))
EndMacro
Procedure EventDraw()
  ; help by falsam
  Protected Distance, first
  Protected CountPoint, N
  
  Protected x = GetGadgetAttribute(#Canvas, #PB_Canvas_MouseX)
  Protected y = GetGadgetAttribute(#Canvas, #PB_Canvas_MouseY)
  
  Protected NextPointX.f, NextPointY.f
  Protected DeltaX.f, DeltaY.f
  
  Define direction.d, interval.d, pas.d, distBetween2dot.d, thesize.w
  Define x1.d, y1.d, x2.f, y2.f, X3.f, y3.f, nextDot.i, color.q
  
  ; define the alpha of the brush
  Thealpha = Brush\Alpha
  
  With This
    
    Select EventType()
        
      Case #PB_EventType_LeftButtonDown
        \LeftButtonDown = #True
        PreviousPoint\x = x
        PreviousPoint\Y = y
        If first = 0
          First = 1
          Draw = #True
        EndIf
        
      Case #PB_EventType_LeftButtonUp
        \LeftButtonDown = #False
        CurrentPoint\x = x
        CurrentPoint\Y = y
        Draw = #False
        First = 0
        
      Case #PB_EventType_MouseMove   
        Draw = #False
        If \LeftButtonDown 
          If Thealpha >0  And Thealpha <=255
            Draw = #True
          EndIf
          CurrentPoint\x = x
          CurrentPoint\Y = y
        EndIf
    EndSelect
    
    ; Drawing
    If Draw
      
      ; pas = to calculate the distance between two dots 100/100 => distance=TheSize
      Pas = Brush\pas/100
      
      ; calcul the size
      Thesize = Brush\size
      
      If thesize >=1 And Thealpha > 0 And thealpha <=255
        x1 = PreviousPoint\x
        y1 = PreviousPoint\y   
        x2 = CurrentPoint\x
        y2 = CurrentPoint\y
        
        ; the distancebetween Two dots
        distBetween2dot = Thesize*Pas
        
        ;distance between two vectors
        Distance = Distance(x1,y1, x2,y2)
        
        ; number of dots to draw
        CountPoint = Distance/(Pas*Thesize)
        
        ; Draw !
        If StartDrawing(ImageOutput(#Img_layer))
          
          Select Brush\blendmode 
            Case #BM_paintingNormal ; bm painting normal
              DrawingMode(#PB_2DDrawing_AlphaBlend)
            Case #BM_PaintingNormalBorder ; bm normal with a strange behavior ^^
              DrawingMode(#PB_2DDrawing_CustomFilter)
              CustomFilterCallback(@PaintingBMNormalBorder())
            Case #BM_PaintingNormal ; bm normal, but a few better with alpha <50 :)
              DrawingMode(#PB_2DDrawing_CustomFilter)
              CustomFilterCallback(@PaintingBMNormal())
            Case #BM_PaintingMultiply
              DrawingMode(#PB_2DDrawing_CustomFilter)
              CustomFilterCallback(@PaintingBMMultiply())
            Case #BM_PaintingScreen
              DrawingMode(#PB_2DDrawing_CustomFilter)
              CustomFilterCallback(@PaintingBMScreen())
            Case #BM_PaintingADD
              DrawingMode(#PB_2DDrawing_CustomFilter)
              CustomFilterCallback(@PaintingBMAdd())
          EndSelect
          
          If distBetween2dot <= distance And CountPoint > 0 And first = 0
            
            direction = direction(x1, y1, x2, y2)
            For N = 1 To CountPoint
              ; then draw the dots
              x3 = x1 + n * distBetween2dot * Sin(direction)
              y3 = y1 + n * distBetween2dot * Cos(direction)
              DrawAlphaImage(ImageID(#Img_brush), x3-Thesize/2, y3-Thesize/2, Thealpha)
            Next
            PreviousPoint\x = x3
            PreviousPoint\y = y3
          Else
            If first = 1
              DrawAlphaImage(ImageID(#Img_brush), x1-Thesize/2, y1-Thesize/2) ; , Thealpha)
            EndIf
          EndIf
          StopDrawing()
        EndIf
      EndIf
    EndIf
  EndWith
EndProcedure

Procedure UpdateBrush(create=0)
  
  s = brush\size
  
  ; we have to recreate the brush if needed
  If create = 1
    If IsImage(#Img_brush)
      FreeImage(#Img_brush)
    EndIf
    If CreateImage(#Img_brush,s,s,32,#PB_Image_Transparent)
    EndIf
  EndIf
  
  ; Draw the brush
  If StartDrawing(ImageOutput(#Img_brush))
    DrawingMode(#PB_2DDrawing_AllChannels)
    Select brush\blendmode
      Case #BM_PaintingMultiply
        Box(0,0,s,s,RGBA(255,255,255,255-brush\alpha))
        DrawingMode(#PB_2DDrawing_AlphaBlend)
      Case #BM_PaintingADD, #BM_PaintingScreen
        Box(0,0,s,s,RGBA(0,0,0,0))
      Default
        Box(0,0,s,s,RGBA(0,0,0,0))
    EndSelect
    Circle(s/2, s/2, s/2-2, RGBA(200,150,150,brush\alpha))
    StopDrawing()
  EndIf
  
EndProcedure
Procedure UpdateCanvas(clear=0)
  If StartDrawing(CanvasOutput(#canvas))
    DrawingMode(#PB_2DDrawing_AllChannels)
    Box(0,0,OutputWidth(), OutputHeight(),RGBA(200,200,200,255))
    ; draw the layers
    If clear=0
      DrawingMode(#PB_2DDrawing_AlphaBlend)
      DrawAlphaImage(ImageID(#Img_Layer),0,0)
    EndIf
    StopDrawing()
  EndIf 
EndProcedure
Procedure Addtrackbar(gadget,x,y,w,h,min,max,tip$,state)
  TrackBarGadget(gadget,x,y,w,h,min,max)
  SetGadgetState(gadget,state)
  GadgetToolTip(gadget,tip$)
EndProcedure
;}


w=800
h=400
If OpenWindow(0, 0, 0, w, h, "Blendmode painting.", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  
  CreateImage(#Img_Layer,w-200,h,32,#PB_Image_Transparent)
  updateBrush(1)
  
  blendmode$="default,normal,normalBorder,multiply,add,screen,"
  nb = CountString(blendmode$, ",")-1
  ; gadget
  Addtrackbar(#G_brushSize,10,10,150,20,1,150,"Brush size",brush\size)
  Addtrackbar(#G_brushAlpha,10,40,150,20,0,255,"Brush Alpha",brush\alpha)
  Addtrackbar(#G_brushPas,10,70,150,20,1,500,"Brush Pas (space between dots)",brush\pas)
  ComboBoxGadget(#G_BrushBlendmode,10,100,150,20)
  For i=0 To nb
    AddGadgetItem(#G_BrushBlendmode,i,StringField(blendmode$,i+1,","))
  Next
  SetGadgetState(#G_BrushBlendmode,0)
  GadgetToolTip(#G_BrushBlendmode, "painting blendmode")
  
  CanvasGadget(#canvas,200,0,w-200,h)
  updatecanvas()
  
  Repeat
    
    Event = WaitWindowEvent()
    EventGadget =EventGadget() 
    
    If Event = #PB_Event_Gadget 
      Select EventGadget 
        Case #canvas
          If EventType() = #PB_EventType_LeftButtonDown Or (EventType() = #PB_EventType_MouseMove And GetGadgetAttribute(0, #PB_Canvas_Buttons) & #PB_Canvas_LeftButton)
            ; draw on the layer with a brush image
            Eventdraw()
            ; update the canvas
            updatecanvas()
          EndIf
        Case #G_brushSize
          brush\size= GetGadgetState(EventGadget)
          UpdateBrush(1)
        Case #G_brushAlpha
          brush\alpha= GetGadgetState(EventGadget)
          UpdateBrush()
        Case #G_brushPas
          brush\Pas = GetGadgetState(EventGadget)
        Case #G_BrushBlendmode
          brush\blendmode = GetGadgetState(EventGadget)
          UpdateBrush()
      EndSelect
      
    EndIf    
    
  Until Event = #PB_Event_CloseWindow
EndIf
Image

Cheers :)

Re: Brush painting blendmode

Posted: Fri Jun 18, 2021 1:41 pm
by StarBootics
Hello [blendman],

Thanks for sharing this code thinking about creating a Blackboard application to sketch in real time on the Canvas gadget but I was not able to make it reactive enough to be practical. Your code with little adaptation will make it happen.

Best regards
StarBootics

Re: Brush painting blendmode

Posted: Sat Jun 19, 2021 2:18 pm
by [blendman]
Hi

I'm glad you like it ;)