B4J Code Snippet [B4X] Canvas - ensure free mouse drawing without gaps (smooth continuous drawing)

EDIT: see posts #2 and #4 for better solutions thanks to Erel.

There might be better ways but I have used DrawRect() method of the Canvas in the past when I want to do free mouse/hand drawing on the screen.
However, very often DrawRect() skips some points, i.e., leaves gaps if one tries to draw a continuous line. The gapping intensifies as pointer speed increases.

I found this thread on SO which suggested the following code to resolve it and indeed it seems to work very well.

B4X:
Dim jo As JavaObject = Canvas1
Dim jocan As JavaObject = jo.RunMethod("getGraphicsContext2D",Null)
jocan.RunMethod("setStroke",Array(fx.Colors.Blue))
jocan.RunMethod("setLineWidth",Array(5.0))
jocan.RunMethod("lineTo",Array(EventData.X,EventData.Y))
jocan.RunMethod("stroke",Null)
jocan.RunMethod("closePath",Null)
jocan.RunMethod("beginPath",Null)
jocan.RunMethod("moveTo",Array(EventData.X,EventData.Y))

This image shows the difference.
The first canvas at the top with blue drawings is using the above-posted code while the canvas below with red drawings shows the unsatisfying result when using DrawRect():

capt1.jpg


I am attaching a sample project so you can try it out for yourself.

I hope someone might find it useful. Happy coding.
 

Attachments

  • Canvasdrawing.zip
    2.4 KB · Views: 423
Last edited:

Erel

B4X founder
Staff member
Licensed User
Longtime User
Why is this code needed?

Much simpler code, though I wouldn't use it as it is simpler to use B4XCanvas and write cross platform code:
B4X:
Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private Canvas1 As Canvas
    Private Canvas2 As Canvas
    Type Point (x As Double, y As Double)
    Private PrevPoint As Point
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("1") 'Load the layout file.
    MainForm.Show
End Sub

Sub CreatePoint (ED As MouseEvent) As Point
    Dim thepoints As Point
    thepoints.Initialize
    thepoints.x = ED.X
    thepoints.y = ED.y
    Return thepoints
End Sub

Sub Canvas1_MouseDragged (EventData As MouseEvent)
    If EventData.PrimaryButtonDown Then
        Dim NewPoint As Point = CreatePoint(EventData)
        If PrevPoint <> Null And PrevPoint.IsInitialized Then
            Canvas1.DrawLine(PrevPoint.x, PrevPoint.y, NewPoint.x, NewPoint.y, fx.Colors.Red, 5dip)
        End If
        PrevPoint = NewPoint
    End If
End Sub

Sub Canvas1_MouseReleased (EventData As MouseEvent)
    PrevPoint = Null
End Sub
 

moster67

Expert
Licensed User
Longtime User
Why is this code needed?
As I said, there would probably be better ways and I stood corrected.
Indeed, your code is much simpler, without the need of JavaObject. :)
I will use such code going forward.

I don't need cross platform code for the current project but if anyone would like to add the corresponding code using B4XCanvas, I will gladly change the title of the thread.

Happy coding.
 

klaus

Expert
Licensed User
Longtime User
I would have done it that way, with a B4XView and a B4XCanvas, as Erel suggested.

B4X:
Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private xui As XUI
    
    Private pnlDrawing As B4XView
    Private cvsDrawing As B4XCanvas
    
    Public x0, y0 As Int
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("Main") 'Load the layout file.
    MainForm.Show
    
    cvsDrawing.Initialize(pnlDrawing)
End Sub

Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
    Return True
End Sub

Private Sub pnlDrawing_Touch(Action As Int, X As Float, Y As Float)
    Select Action
        Case pnlDrawing.TOUCH_ACTION_DOWN
            x0 = X
            y0 = Y
            cvsDrawing.DrawCircle(x0, y0, 3, xui.Color_Red, True, 1)
            'cvsDrawing.Invalidate    'needed for B4A and B4i, uncomment it for cross-platform
        Case pnlDrawing.TOUCH_ACTION_MOVE
            cvsDrawing.DrawLine(x0, y0, X, Y, xui.Color_Red, 5dip)
            cvsDrawing.DrawCircle(x0, y0, 3, xui.Color_Red, True, 1)
            'cvsDrawing.Invalidate    'needed for B4A and B4i, uncomment it for cross-platform
            x0 = X
            y0 = Y
        Case pnlDrawing.TOUCH_ACTION_UP
    End Select
End Sub

Private Sub btnClear_Click
    cvsDrawing.ClearRect(cvsDrawing.TargetRect)
End Sub
 

Attachments

  • B4X_FreeHandDrawing.zip
    2.3 KB · Views: 459

cosmobug

New Member
Licensed User
Longtime User
Why is this code needed?

Much simpler code, though I wouldn't use it as it is simpler to use B4XCanvas and write cross platform code:
B4X:
Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private Canvas1 As Canvas
    Private Canvas2 As Canvas
    Type Point (x As Double, y As Double)
    Private PrevPoint As Point
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("1") 'Load the layout file.
    MainForm.Show
End Sub

Sub CreatePoint (ED As MouseEvent) As Point
    Dim thepoints As Point
    thepoints.Initialize
    thepoints.x = ED.X
    thepoints.y = ED.y
    Return thepoints
End Sub

Sub Canvas1_MouseDragged (EventData As MouseEvent)
    If EventData.PrimaryButtonDown Then
        Dim NewPoint As Point = CreatePoint(EventData)
        If PrevPoint <> Null And PrevPoint.IsInitialized Then
            Canvas1.DrawLine(PrevPoint.x, PrevPoint.y, NewPoint.x, NewPoint.y, fx.Colors.Red, 5dip)
        End If
        PrevPoint = NewPoint
    End If
End Sub

Sub Canvas1_MouseReleased (EventData As MouseEvent)
    PrevPoint = Null
End Sub
How would you draw the line as dotted or dash using DrawLine without using "stroke" method?
Trying to use something like: Stroke dotted = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] {1,2}, 0);
 
Top