Brush painting blendmode

Share your advanced PureBasic knowledge/code with the community.
User avatar
[blendman]
Enthusiast
Enthusiast
Posts: 297
Joined: Thu Apr 07, 2011 1:14 pm
Location: 3 arks
Contact:

Brush painting blendmode

Post 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 :)
User avatar
StarBootics
Addict
Addict
Posts: 984
Joined: Sun Jul 07, 2013 11:35 am
Location: Canada

Re: Brush painting blendmode

Post 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
The Stone Age did not end due to a shortage of stones !
User avatar
[blendman]
Enthusiast
Enthusiast
Posts: 297
Joined: Thu Apr 07, 2011 1:14 pm
Location: 3 arks
Contact:

Re: Brush painting blendmode

Post by [blendman] »

Hi

I'm glad you like it ;)
Post Reply