B4J Question Canvas Problem. Leaving Artifacts.

keirS

Well-Known Member
Licensed User
Longtime User
canvas.PNG


I am trying to implement a simple line draw using the mouse on a canvas. The below code leaves artifacts on the canvas. I was expecting Canvas1.DrawLine(StartX,Starty,Endx,Endy,fx.Colors.White,10) in the MouseDragged to remove the existing line. It almost does but leaves artifacts.

Draw Line With Mouse On Canvas:
Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private DrawLine As Boolean
    Private Canvas1 As Canvas
    Private ToggleButton1 As ToggleButton
    Private StartX As Double
    Private Starty As Double
    Private Endx As Double
    Private Endy As Double
   
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("testdraw") 'Load the layout file.
   
    MainForm.Show
    Canvas1.DrawRect(0,0,Canvas1.Width,Canvas1.Height,fx.Colors.White,True,10)
   
   
   
End Sub

'Return true to allow the default exceptions handler to handle the uncaught exception.
Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
    Return True
End Sub

Sub Canvas1_MouseClicked (EventData As MouseEvent)
   
   
End Sub

Sub Canvas1_MouseMoved (EventData As MouseEvent)
     
End Sub

Sub Canvas1_MouseDragged (EventData As MouseEvent)
    If ToggleButton1.Selected And DrawLine  = True Then
        Canvas1.DrawLine(StartX,Starty,Endx,Endy,fx.Colors.White,10)
        Endx = EventData.X
        Endy = EventData.Y
        Canvas1.DrawLine(StartX,Starty,Endx,Endy,fx.Colors.Blue,10)
   
    End If
   
End Sub

Sub Canvas1_MousePressed (EventData As MouseEvent)
   
    If EventData.PrimaryButtonPressed Then
      If ToggleButton1.Selected And DrawLine  = False Then
        DrawLine = True
        StartX = EventData.X
        Starty = EventData.Y
        Endy = EventData.Y
        Endx = EventData.X
      Else
        
       
      End If
    End If
   
End Sub

Sub Canvas1_MouseReleased (EventData As MouseEvent)
    If DrawLine = True Then
        Canvas1.DrawLine(StartX,Starty,Endx,Endy,fx.Colors.White,10)
        Endx = EventData.X
        Endy = EventData.Y
        Canvas1.DrawLine(StartX,Starty,Endx,Endy,fx.Colors.Blue,10)
        DrawLine = False
       
    End If
   
End Sub

Sub Canvas1_MouseEntered (EventData As MouseEvent)
   
End Sub

Sub Canvas1_MouseExited (EventData As MouseEvent)
   
End Sub

Sub Canvas1_FocusChanged (HasFocus As Boolean)
   
End Sub

Sub ToggleButton1_SelectedChange(Selected As Boolean)
   
End Sub

Project attached,
 

Attachments

  • canvastestl.zip
    2.5 KB · Views: 210

agraham

Expert
Licensed User
Longtime User
Perhaps I am missing something but I can't explain why two different DrawLine commands with the same parameters render differently. It looks like an integer rounding problem somewhere. You can see this in the following
B4X:
Sub Canvas1_MouseDragged (EventData As MouseEvent)
    If ToggleButton1.Selected And DrawLine  = True Then
        Canvas1.DrawLine(StartX,Starty,Endx,Endy,fx.Colors.Blue,10)
        Canvas1.DrawLine(StartX,Starty,Endx,Endy,fx.Colors.White,10)
        Endx = EventData.X
        Endy = EventData.Y   
    End If   
End Sub
A not very satisfactory solution is to increase the stroke width from 10 to 11 when drawing the White line.

Note that you only need to draw in the Canvas1_MouseDragged event.
 
Upvote 0

keirS

Well-Known Member
Licensed User
Longtime User
That does indeed work. But like you I cannot explain why it happens in the first place.
 
Upvote 0

JordiCP

Expert
Licensed User
Longtime User
Isn't it because of antialiasing?

When you draw a line with a given thickness, some of the pixels fall totally inside the line, so they are drawn 100% with the given color. But edge pixels, those that somehow are "cut" by the line if it was ideal, only have a percentage of its area inside it , so they are drawn with a X% of its original color ( by means of alpha channel).
In this case, drawing again with white to erase leaves these artifacts since it performs the same operation for those edge pixels (draws a given percentage of white onto the existing color)

In this case, drawing a thicker line or managing to draw as solid (alpha=1) all pixels, including the edge ones will make the erase function work. I think there are methods to achieve the later with B4XCanvas
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
Isn't it because of antialiasing?
Spot on. šŸ‘ I kick myself that I didn't realise this. You can see this if you change the line colour to Cyan and the background to Yellow, the artifacts are then Green. I don't know enough about JavaFX to know if it can be disabled - perhaps someone else does.
 
Upvote 0

keirS

Well-Known Member
Licensed User
Longtime User
Tip: never use Canvas. Use B4XCanvas. Same features but cross platform.

Antialiasing cannot be disabled. You will need to clear a larger region.

Not sure B4XCanvas will work for me. If I need to clear a larger region then I will need 2 canvases on top of each other. The top one to show the line whilst drawing is in progress and the bottom one to draw the the line when the drawing operation is finished. Then clear the top canvas ready for the next drawing operation. Declaring two B4XCanvases and setting them to the same pane would just get me one drawing surface wouldn't it?
 
Upvote 0

keirS

Well-Known Member
Licensed User
Longtime User
Ok I am now confused. I have added a second canvas (Canvas2) on top of Canvas1 to do the drawing operation which when finished draws to Canvas1. But it doesn't show a line when drawing (on Canvas2). Looking at the JavaFx docs this should be possible: https://docs.oracle.com/javafx/2/canvas/jfxpub-canvas.htm

You can also instantiate multiple Canvas objects, and use them to define a simple layer system. Switching layers therefore becomes a matter of selecting the desired Canvas and writing to it. (A Canvas object is completely transparent, and shows through until you draw on parts of it.)

The background of Canvas2 is set to transparent.

B4X:
Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private DrawLine As Boolean
    Private Canvas1 As Canvas
    Private ToggleButton1 As ToggleButton
    Private StartX As Double
    Private Starty As Double
    Private Endx As Double
    Private Endy As Double
    
    Private Canvas2 As Canvas
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("testdraw") 'Load the layout file.
    
    MainForm.Show
    
    Canvas1.DrawRect(0,0,Canvas1.Width,Canvas1.Height,fx.Colors.White,True,10)
    'Canvas2.DrawRect(0,0,Canvas2.Width,Canvas2.Height,fx.Colors.Transparent,True,10)
    Canvas2.DrawLine(100,100,200,200,fx.Colors.Blue,10)
    Log(GetType(Canvas2))
End Sub

'Return true to allow the default exceptions handler to handle the uncaught exception.
Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
    Return True
End Sub

Sub Canvas1_MouseClicked (EventData As MouseEvent)
    
    
End Sub

Sub Canvas1_MouseMoved (EventData As MouseEvent)
    
End Sub

Sub Canvas1_MouseDragged (EventData As MouseEvent)
    
End Sub

Sub Canvas1_MousePressed (EventData As MouseEvent)
    
    
    
End Sub

Sub Canvas1_MouseReleased (EventData As MouseEvent)
    
    
End Sub

Sub Canvas1_MouseEntered (EventData As MouseEvent)
    
End Sub

Sub Canvas1_MouseExited (EventData As MouseEvent)
    
End Sub

Sub Canvas1_FocusChanged (HasFocus As Boolean)
    
End Sub

Sub ToggleButton1_SelectedChange(Selected As Boolean)
    
End Sub

Sub Canvas2_MouseClicked (EventData As MouseEvent)
    
End Sub

Sub Canvas2_MouseMoved (EventData As MouseEvent)
    
End Sub

Sub Canvas2_MouseDragged (EventData As MouseEvent)
    If ToggleButton1.Selected And DrawLine  = True Then
        Canvas2.DrawLine(StartX,Starty,Endx,Endy,fx.Colors.Transparent,12)
        Endx = EventData.X
        Endy = EventData.Y
        Canvas2.DrawLine(StartX,Starty,Endx,Endy,fx.Colors.Blue,10)
    
    End If
    
    
    
    
    
    
End Sub

Sub Canvas2_MousePressed (EventData As MouseEvent)

    If EventData.PrimaryButtonPressed Then
        If ToggleButton1.Selected And DrawLine  = False Then
            DrawLine = True
            StartX = EventData.X
            Starty = EventData.Y
            Endy = EventData.Y
            Endx = EventData.X
        Else
        
        
        End If
    End If
    
End Sub

Sub Canvas2_MouseReleased (EventData As MouseEvent)
    If DrawLine = True Then
        Canvas2.DrawLine(StartX,Starty,Endx,Endy,fx.Colors.Transparent,12)
        Endx = EventData.X
        Endy = EventData.Y
        Canvas1.DrawLine(StartX,Starty,Endx,Endy,fx.Colors.Blue,10)
        DrawLine = False
        
    End If
    
End Sub

Sub Canvas2_MouseEntered (EventData As MouseEvent)
    
End Sub

Sub Canvas2_MouseExited (EventData As MouseEvent)
    
End Sub

Sub Canvas2_FocusChanged (HasFocus As Boolean)
    
End Sub

Sub Canvas2_AnimationCompleted
    
End Sub
 

Attachments

  • canvastestl.zip
    2.6 KB · Views: 179
Upvote 0

klaus

Expert
Licensed User
Longtime User
Declaring two B4XCanvases and setting them to the same pane would just get me one drawing surface wouldn't it?
Yes. But you need 2 Panes.
Anyway you need two Canvases, because when you have finished a line and you draw a new line it will erase parts of the previous drawings.
You may have a look at the B4X Booklets and more specifically at the B4X Graphics Booklet.
 
Last edited:
Upvote 0

klaus

Expert
Licensed User
Longtime User
The problem was that you set Alpha Level to 0 in the Designer!
This has as a result that nothing id drawn onto the Canvas, it must be 1.0.

Then, to make Canvas2 transparent you must use Canvas2.ClearRect.(0, 0, Canvas2.Width, Canvas2.Height).

Attached 2 test projects:
canvastest2.zip, your project modified with 2 Canvases
canvastestXUI.zip new project with 2 B4XViews (Panes) and 2 B4XCanvases
 

Attachments

  • canvastest2.zip
    2.6 KB · Views: 199
  • canvastestXUI.zip
    2.5 KB · Views: 184
Upvote 0

Peter Meares

Member
Licensed User
Longtime User
I noticed a similar ghosting when drawing and erasing square ares. I had to account for the line thickness in the erase rectangle.
 
Upvote 0

keirS

Well-Known Member
Licensed User
Longtime User
Thanks Klaus. The Alpha value is listed as a background property. I had it in my head that the drawing was in the foreground when it obviously it isn't.
 
Upvote 0
Top