I have search how to paint stroke with "constant" space between dots.
And here is the result (I have tried to find that in 2015, but it's in 2021 that I have found how to do ).
Some image of the result (it's for my tool animatoon) :
The code :
Code: Select all
; Code by falsam 2015
; modified a lot by blendman in 03.2021
; EnableExplicit
Enumeration
; Window
#MainForm
; Gadgets
#Canvas= 0
; brush parameters
#G_BrushColor
#G_BrushPreview ; image for the brush preview
#G_TB_Size
#G_SG_Size
#G_TB_Alpha
#G_SG_Alpha
#G_TB_Pas
#G_SG_Pas
#G_Combobox_Brush
#G_CB_BrushFadeOn ; checkbox
#G_CB_BrushFadeAlpha ; checkbox
; image
#Imagelayer = 0
#imagelayer1
#ImageBrushColor
#ImageBrushPreview
#ImageBrushFinal
#ImagecopyBrushFinal
; menu
#menu_Clear = 0
EndEnumeration
;{ structure
Structure Canvas
LeftButtonDown.i
RightButtonDown.i
EndStructure
Global Draw, This.Canvas, fadeOn, fadeOff, fadeAlpha, FadeSizeMAx
FadeSizeMAx = 20
; fade = to get a fade
Structure VPoint
x.f
y.f
EndStructure
Global PreviousPoint.VPoint
Global CurrentPoint.VPoint, LastPoint.VPoint
; Global Dim Dot.Vpoint(0), NbDot = -1 ; not used, it's an array to store the dots, for example, to create a fade ON/off
Structure sBrush
size.w
Alpha.a
Pas.w ; space between two dots /100 : 100% -> space = brush\size
Brush.a ; the id of the brush used
Color.i
FadeSize.a ; to kwow if we use the fadeOn (fade for size) or not
FadeSizeOn.w
FadeSizeOff.w
; the fade for alpha
FadeAlpha.a
FadeAlphaOff.w
FadeAlphaOn.w
EndStructure
Global brush.sbrush
brush\size = 20
brush\pas = 100
brush\alpha = 100
;}
UsePNGImageDecoder()
Macro distance_(x1,y1,x2,y2)
Int(Sqr((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)) )
EndMacro
Macro direction(x1,y1,x2,y2) ; angle
ATan2((y2- y1),(x2- x1))
EndMacro
Declare EventDraw()
Procedure.d Distance(*p.VPoint, *q.VPoint)
Protected Distance.d, dx.d, dy.d
dx = *p\x - *q\x
dy = *p\y - *q\y
Distance = Sqr(dx*dx + dy*dy )
ProcedureReturn Distance
EndProcedure
Procedure.l RotateImageEx2(ImageID, Angle.f, Mode.a=2) ; Rotation d'une image d'un angle en ° - rotation of an image, in degres.
Shared ImageBrushW.w, ImageBrushH.w
; Procedure par Le Soldat inconnu - Merci
; By LSI, but windows Only
Protected bmi.BITMAPINFO, bmi2.BITMAPINFO, hdc.l, NewImageID, Mem, n, nn, bm.BITMAP
; If IsImage(NewImageID)
; FreeImage(NewImageID)
; EndIf
Angle = Angle * #PI / 180 ; On convertit en radian
Cos.f = Cos(Angle)
Sin.f = Sin(Angle)
; CouleurFond = Brush\Color ; 0
GetObject_(ImageID, SizeOf(BITMAP), @bm.BITMAP) ; windows only; is there a version for linux, mac os ?
bmi\bmiHeader\biSize = SizeOf(BITMAPINFOHEADER)
bmi\bmiHeader\biWidth = bm\bmWidth
bmi\bmiHeader\biHeight = bm\bmHeight
bmi\bmiHeader\biPlanes = 1
bmi\bmiHeader\biBitCount = 32
bmi2\bmiHeader\biSize = SizeOf(BITMAPINFOHEADER)
; Select Mode
; Case 1
; bmi2\bmiHeader\biWidth = bm\bmWidth
; bmi2\bmiHeader\biHeight = bm\bmHeight
; Case 2
bmi2\bmiHeader\biWidth = Round(Sqr(bm\bmWidth * bm\bmWidth + bm\bmHeight * bm\bmHeight), 1)
bmi2\bmiHeader\biHeight = bmi2\bmiHeader\biWidth
; Default
; bmi2\bmiHeader\biWidth = Round(bm\bmWidth * Abs(Cos) + bm\bmHeight * Abs(Sin), 1)
; bmi2\bmiHeader\biHeight = Round(bm\bmHeight * Abs(Cos) + bm\bmWidth * Abs(Sin), 1)
; EndSelect
bmi2\bmiHeader\biPlanes = 1
bmi2\bmiHeader\biBitCount = 32
Mem = AllocateMemory(bm\bmWidth * bm\bmHeight * 4)
If Mem
Mem2 = AllocateMemory(bmi2\bmiHeader\biWidth * bmi2\bmiHeader\biHeight * 4)
If Mem2
hdc = CreateCompatibleDC_(GetDC_(ImageID)) ; windows only
If hdc
GetDIBits_(hdc, ImageID, 0, bm\bmHeight, Mem, @bmi, #DIB_RGB_COLORS) ; on envoie la liste dans l'image / windows only
DeleteDC_(hdc) ; windows only
EndIf
CX1 = bm\bmWidth - 1
CY1 = bm\bmHeight - 1
CX2 = bmi2\bmiHeader\biWidth - 1
CY2 = bmi2\bmiHeader\biHeight - 1
Mem01 = Mem + bm\bmWidth * 4
Mem10 = Mem + 4
Mem11 = Mem01 + 4
Mem2Temp = Mem2
For nn = 0 To CY2
y1b.l = nn * 2 - CY2
Temp1.f = CX1 - y1b * Sin
Temp2.f = CY1 + y1b * Cos
For n = 0 To CX2
x1b.l = n * 2 - CX2
x1.f = (Temp1 + x1b * Cos) / 2
y1.f = (Temp2 + x1b * Sin) / 2
x2.l = x1
y2.l = y1
If x1 < x2
x2 - 1
EndIf
If y1 < y2
y2 - 1
EndIf
x2b = x2 + 1
y2b = y2 + 1
If x2b >= 0 And x2 <= CX1 And y2b >= 0 And y2 <= CY1 ; On filtre si on est completement en dehors de l'image
fx.f = x1 - x2
fy.f = y1 - y2
f00.f = (1 - fx) * (1 - fy)
f01.f = (1 - fx) * fy
f10.f = fx * (1 - fy)
f11.f = fx * fy
MemTemp = (x2 + y2 * bm\bmWidth) * 4
If x2 >= 0 And x2 <= CX1
If y2 >= 0 And y2 <= CY1
c00 = PeekL(Mem + MemTemp)
Else
c00 = 0
EndIf
If y2b >= 0 And y2b <= CY1
c01 = PeekL(Mem01 + MemTemp)
Else
c01 = 0
EndIf
Else
c00 = 0
c01 = 0
EndIf
If x2b >= 0 And x2b <= CX1
If y2 >= 0 And y2 <= CY1
c10 = PeekL(Mem10 + MemTemp)
Else
c10 = 0
EndIf
If y2b >= 0 And y2b <= CY1
c11 = PeekL(Mem11 + MemTemp)
Else
c11 = 0
EndIf
Else
c10 = 0
c11 = 0
EndIf
Channel00 = c00 >> 24 & $FF
Channel01 = c01 >> 24 & $FF
Channel10 = c10 >> 24 & $FF
Channel11 = c11 >> 24 & $FF
Alpha = Channel00 * f00 + Channel01 * f01 + Channel10 * f10 + Channel11 * f11
Channel00 = c00 >> 16 & $FF
Channel01 = c01 >> 16 & $FF
Channel10 = c10 >> 16 & $FF
Channel11 = c11 >> 16 & $FF
Bleu = Channel00 * f00 + Channel01 * f01 + Channel10 * f10 + Channel11 * f11
Channel00 = c00 >> 8 & $FF
Channel01 = c01 >> 8 & $FF
Channel10 = c10 >> 8 & $FF
Channel11 = c11 >> 8 & $FF
Vert = Channel00 * f00 + Channel01 * f01 + Channel10 * f10 + Channel11 * f11
Channel00 = c00 & $FF
Channel01 = c01 & $FF
Channel10 = c10 & $FF
Channel11 = c11 & $FF
Rouge = Channel00 * f00 + Channel01 * f01 + Channel10 * f10 + Channel11 * f11
PokeL(Mem2Temp, Rouge | Vert << 8 | Bleu << 16 | Alpha << 24)
Else
PokeL(Mem2Temp, 0)
EndIf
Mem2Temp + 4
Next
Next
; On crée la nouvelle image
NewImageID = CreateImage(#PB_Any, bmi2\bmiHeader\biWidth, bmi2\bmiHeader\biHeight, 32)
ImageBrushW = bmi2\bmiHeader\biWidth
ImageBrushH = bmi2\bmiHeader\biHeight
hdc = CreateCompatibleDC_(GetDC_(ImageID(NewImageID)))
If hdc
SetDIBits_(hdc, ImageID(NewImageID), 0, bmi2\bmiHeader\biHeight, Mem2, @bmi2, #DIB_RGB_COLORS) ; on envoie la liste dans l'image
DeleteDC_(hdc)
EndIf
FreeMemory(Mem2)
EndIf
FreeMemory(Mem)
; NewImageID = UnPreMultiplyAlpha(NewImageID)
EndIf
ProcedureReturn NewImageID
EndProcedure
Procedure UpdateBrush()
Define color.i
If IsImage(#ImagecopyBrushFinal)
FreeImage(#ImagecopyBrushFinal)
EndIf
CopyImage(#ImageBrushFinal, #ImagecopyBrushFinal)
If StartDrawing(ImageOutput(#ImagecopyBrushFinal))
DrawingMode(#PB_2DDrawing_AlphaClip)
color = brush\color
Box(0,0,OutputWidth(), OutputHeight(), RGBA(Red(color),Green(color),Blue(color),255))
StopDrawing()
EndIf
EndProcedure
Procedure Addtrackbar(id, x, y, w, h, min, max, val, tip$)
Define w1 = 45
TrackBarGadget(id, x, y, w-w1-10, h, min, max)
SetGadgetState(id, val)
GadgetToolTip(id, tip$)
StringGadget(id+1, x+w-w1-10, y, w1,h,Str(val))
GadgetToolTip(id+1, tip$)
EndProcedure
Procedure StateGadgetBrush()
Define eventgadget.i, color.i
eventgadget = EventGadget()
Select EventGadget
Case #G_Combobox_Brush
brush\Brush = GetGadgetState(EventGadget)
Case #G_BrushColor
If EventType() = #PB_EventType_LeftClick
color = ColorRequester(brush\color)
If color >=0
brush\color = COlor
If StartDrawing(ImageOutput(#ImageBrushColor))
Box(0,0,OutputWidth(), OutputHeight(), color)
StopDrawing()
EndIf
SetGadgetState(#G_BrushColor, ImageID(#ImageBrushColor))
UpdateBrush()
EndIf
EndIf
Case #G_CB_BrushFadeAlpha
brush\FadeAlpha = GetGadgetState(EventGadget)
Case #G_CB_BrushFadeOn
brush\FadeSize = GetGadgetState(EventGadget)
Case #G_SG_Size
brush\size = Val(GetGadgetText(#G_SG_Size))
SetGadgetState(#G_TB_Size, brush\size)
UpdateBrush()
Case #G_SG_Alpha
brush\Alpha = Val(GetGadgetText(#G_SG_Alpha))
SetGadgetState(#G_TB_Alpha, brush\Alpha)
Case #G_SG_Pas
brush\pas = Val(GetGadgetText(#G_SG_Pas))
SetGadgetState(#G_TB_Pas, brush\Pas)
Case #G_TB_Size
brush\size = GetGadgetState(EventGadget)
SetGadgetText(#G_SG_Size, Str( brush\size))
UpdateBrush()
Case #G_TB_Alpha
brush\Alpha = GetGadgetState(EventGadget)
SetGadgetText(#G_sg_Alpha, Str(brush\Alpha))
Case #G_TB_Pas
brush\Pas = GetGadgetState(EventGadget)
SetGadgetText(#G_sg_Pas, Str( brush\Pas))
EndSelect
EndProcedure
Procedure UpdateCanvas()
If StartDrawing(CanvasOutput(#Canvas))
DrawingMode(#PB_2DDrawing_AllChannels)
Box(0,0, OutputWidth(), OutputHeight(), RGBA(255,255,255,255))
; DrawAlphaImage(ImageID(#imagelayer1), 0, 0) ; uncomment it if you use 2 layers
DrawAlphaImage(ImageID(#imagelayer), 0, 0)
StopDrawing()
EndIf
EndProcedure
Procedure Event_Menu()
Select EventMenu()
Case #menu_Clear
If StartDrawing(ImageOutput(#Imagelayer))
DrawingMode(#PB_2DDrawing_AllChannels)
Box(0, 0, OutputWidth(), OutputHeight(), RGBA(0,0,0,0))
StopDrawing()
EndIf
UpdateCanvas()
EndSelect
EndProcedure
If OpenWindow(#MainForm, 0, 0, 1024, 768, "Canvas Draw", #PB_Window_SystemMenu|#PB_Window_SizeGadget|#PB_Window_MaximizeGadget|#PB_Window_ScreenCentered)
CreateMenu(0, WindowID(#MainForm))
MenuTitle("File")
MenuTitle("Edit")
MenuItem(#menu_Clear, "Clear the layer")
AddKeyboardShortcut(0,#PB_Shortcut_Control|#PB_Shortcut_X, #menu_Clear)
Define y.w = 10, w.a = 200, i=0
AddTrackbar(#G_TB_Size, 10, y, w, 20, 1, 500, brush\size, "size of the brush") : y+30
AddTrackbar(#G_TB_Alpha, 10, y, w, 20, 0, 255, brush\Alpha, "Alpha of the brush") : y+30
AddTrackbar(#G_TB_Pas, 10, y, w, 20, 1, 500, brush\Pas, "Pas (space between two dots for the brush (in % of the current size)") : y+30
CheckBoxGadget(#G_CB_BrushFadeOn, 10, Y, w, 20, "Fade the size at start") : y+30
CheckBoxGadget(#G_CB_BrushFadeAlpha, 10, Y, w, 20, "Fade the alpha") : y+30
If CreateImage(#ImageBrushColor, 60, 60) : EndIf
ImageGadget(#G_BrushColor, w/2 - 30, y, 60, 60, ImageID(#ImageBrushColor)) : y+70
; If LoadImage(#ImageBrushFinal, "brush7.png") : EndIf ; use your own brush image if you want to test ;)
If Not IsImage(#ImageBrushFinal)
If CreateImage(#ImageBrushFinal, 60, 60, 32, #PB_Image_Transparent)
If StartDrawing(ImageOutput(#ImageBrushFinal))
DrawingMode(#PB_2DDrawing_AllChannels)
For i = 0 To 20
x1 = Random(30)-Random(30)
y1 = Random(30)-Random(30)
a = 100+Random(155)
Circle(30+x1, 30+y1, 2+Random(10), RGBA(255,255,255,a))
Next
StopDrawing()
EndIf
EndIf
EndIf
updatebrush()
If CreateImage(#ImageBrushPreview, 60,60)
If StartDrawing(ImageOutput(#ImageBrushPreview))
Box(0,0,OutputWidth(), OutputHeight(), RGB(255,255,255))
DrawingMode(#PB_2DDrawing_AlphaBlend)
DrawAlphaImage(ImageID(#ImagecopyBrushFinal), 0,0)
StopDrawing()
EndIf
ImageGadget(#G_BrushPreview, w/2 - 30, y, 60,60, ImageID(#ImageBrushPreview), #PB_Image_Border ) : y+ImageHeight(#ImageBrushPreview)+10
EndIf
ComboBoxGadget(#G_Combobox_Brush, 10, y, 180, 20)
AddGadgetItem(#G_Combobox_Brush, 0, "Circle")
AddGadgetItem(#G_Combobox_Brush, 1, "Brush")
SetGadgetState(#G_Combobox_Brush, 0)
If CanvasGadget(#Canvas, w+10, 0, 1024-w-10, 768) : EndIf
; The image for layer
; If CreateImage(#Imagelayer, 1024-w-10, 768, 32, #PB_Image_Transparent) : EndIf
docW = 1500
If CreateImage(#Imagelayer,docW, docW, 32, #PB_Image_Transparent) : EndIf
; test for 2 layers
; If CreateImage(#Imagelayer1,docW, docW, 32, #PB_Image_Transparent)
; temp = LoadImage(#PB_Any, "layer1.png")
; If StartDrawing(ImageOutput(#Imagelayer1))
; DrawingMode(#PB_2DDrawing_AlphaBlend)
; DrawAlphaImage(ImageID(temp), 0,0)
; StopDrawing()
;
; EndIf
; FreeImage(temp)
; EndIf
UpdateCanvas()
; bind event
BindGadgetEvent(#Canvas, @EventDraw())
BindEvent(#PB_Event_Gadget, @StateGadgetBrush())
BindEvent(#PB_Event_Menu, @Event_Menu())
Repeat : Until WaitWindowEvent(0) = #PB_Event_CloseWindow
EndIf
Procedure EventDraw()
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.d, Thealpha.a
Define x1.d, y1.d, x2.f, y2.f, X3.f, y3.f, nextDot.i, color.i
With This
Select EventType()
Case #PB_EventType_LeftButtonDown
\LeftButtonDown = #True
PreviousPoint\x = x
PreviousPoint\Y = y
If first = 0
First = 1
Draw = #True
If brush\FadeSize = 0
fadeOn = FadeSizeMAx
EndIf
EndIf
Case #PB_EventType_LeftButtonUp
\LeftButtonDown = #False
CurrentPoint\x = x
CurrentPoint\Y = y
fadeOn = 0
Draw = #False
First = 0
brush\FadeAlphaOff = 0
Case #PB_EventType_MouseMove
Draw = #False
If \LeftButtonDown
If Brush\alpha-brush\FadeAlphaOff <=255 And Brush\alpha-brush\FadeAlphaOff >0 ; Or \RightButtonDown)
Draw = #True
If brush\FadeSize = 0
fadeOn = FadeSizeMAx
Else
If fadeOn <= FadeSizeMAx
fadeOn + 1
Thesize = (brush\size*fadeOn)/FadeSizeMAx
EndIf
EndIf
CurrentPoint\x = x
CurrentPoint\Y = y
; nbdot +1
; ReDim dot(nbdot)
; dot(nbdot)\x = x
; dot(nbdot)\y = y
EndIf
EndIf
EndSelect
; Drawing
If Draw
x1 = PreviousPoint\x
y1 = PreviousPoint\y
x2 = CurrentPoint\x
y2 = CurrentPoint\y
Color= brush\color
; pas = to calculate the distance between two dots -> brush\Pas = 100 ->pas = 1 (100%) -> dist = brush\size, brush\Pas = 50 -> dist = brush\size/2...
Pas = brush\Pas/100
; alpha should be drawn on another image and this would be : drawalphaimage(image, 0,0, brush\alpha)
Thealpha = Brush\alpha- brush\FadeAlphaOff ; Thealpha = Brush\alpha *0.9+ Brush\alpha * Pas*0.1
If Thealpha < 0
thealpha = 0
EndIf
; calcul the size, with the fade-size On
Thesize = (brush\size * fadeOn)/FadeSizeMAx
If thesize >0 And Thealpha > 0
; update the brush image
UpdateBrush()
ResizeImage(#ImagecopyBrushFinal, thesize, thesize,#PB_Image_Raw )
; the distancebetween Two dots
distBetween2dot = Thesize*Pas
;distance between two vectors
Distance = Distance_ (x1,y1, x2,y2)
; Distance = Distance(PreviousPoint, CurrentPoint)
; number of dots to draw
CountPoint = Distance/(Pas*Thesize)
; Debug CountPoint
If StartDrawing(ImageOutput(#Imagelayer))
DrawingMode(#PB_2DDrawing_AlphaBlend)
If distBetween2dot <= distance And CountPoint > 0 And first = 0
direction = direction(x1, y1, x2, y2)
For N = 1 To CountPoint
If brush\FadeAlpha = 1
brush\FadeAlphaOff+1
EndIf
x3 = x1 + n * distBetween2dot * Sin(direction)
y3 = y1 + n * distBetween2dot * Cos(direction)
If brush\Brush = 0
Circle(x3, y3, Thesize/2, RGBA(Red(color), Green(color), Blue(color), Thealpha))
Else
temp = RotateImageEx2(ImageID(#ImagecopyBrushFinal), Random(360))
DrawAlphaImage(ImageID(temp), x3-Thesize/2, y3-Thesize/2, Thealpha)
FreeImage(temp)
EndIf
Next
PreviousPoint\x = x3
PreviousPoint\y = y3
Else
If first = 1
If brush\FadeSize = 0
If brush\Brush = 0
Circle(x1, y1, Thesize/2, RGBA(Red(color), Green(color), Blue(color), Thealpha))
Else
temp = RotateImageEx2(ImageID(#ImagecopyBrushFinal), Random(360))
DrawAlphaImage(ImageID(temp), x1-Thesize/2, y1-Thesize/2, Thealpha)
FreeImage(temp)
EndIf
EndIf
EndIf
EndIf
StopDrawing()
EndIf
UpdateCanvas()
EndIf
EndIf
EndWith
EndProcedure
- a kind of FadeIn (size) for the first dots
- a kind of Fadeout (alpha)
- The rotation of image when we use an image as brush
- PAS : space between 2 dots
- brush : Size/alpha/Color
Cheers