B4J Question [B4X] [XUI] How to get a Canvas from a B4XCanvas?

Marcob

Member
Licensed User
Longtime User
Hello, I read about XUI lib that you can switch to the native type when needed.
How can I switch from a B4XCanvas to a Canvas object?
 

DonManfred

Expert
Licensed User
Longtime User
Probaböy like it works with all other types.

B4X:
dim canvas as Canvas = myb4xcanvasobj
 
Upvote 0

klaus

Expert
Licensed User
Longtime User
You can use B4XPath for this, from the help:

InitializeRoundedRect

Method

Initializes the path and sets the current path shape to a rectangle with rounded corners.
Rect - Rectangle.
CornersRadius - Corners radius.

Returns : B4XPath

InitializeRoundedRect(Rect As B4XRect, CornersRadius As Float)


And then:
B4XCanvas.DrawPath(...
 
Last edited:
Upvote 0

Marcob

Member
Licensed User
Longtime User
You can use P4XPath for this, from the help:

InitializeRoundedRect...

Thank you for your suggestion.
Actually, I already tried with B4xPath but, as I need to draw a gradient, it seems that this solution is much slower.

My goal would be to use XUI lib as much as possible in my program except for switching to a native object whenever I need a more efficient routine.
In the attached example you can evaluate the execution time for both methods. Canvas routine is about 30x faster on my PC!

It would be great if I could find a way to convert a B4XCanvas to a Canvas object just when I need to.
 

Attachments

  • CvsSpeedTest.zip
    8.3 KB · Views: 213
Upvote 0

klaus

Expert
Licensed User
Longtime User
Well, your code seems complicated to me.
I would do it like this:

B4X:
Private Sub DrawRoundRectGradient(cvs As B4XCanvas, rct As B4XRect, Radius As Float, Colors() As Int)
    Private bc As BitmapCreator
    Private rct1 As B4XRect
    
    rct1.Initialize(0, 0, rct.Width, rct.Height)
    bc.Initialize(rct.Width, rct.Height)
    bc.FillGradient(Colors, rct1, "TOP_BOTTOM")
    
    Private Path As B4XPath
    Path.InitializeRoundedRect(rct, Radius)
    
    cvs.ClipPath(Path)
    cvs.DrawBitmap(bc.Bitmap, rct)
    cvs.Invalidate
    cvs.RemoveClip
End Sub

Attached the modified project.
 

Attachments

  • CvsSpeedTest1.zip
    1.3 KB · Views: 205
Upvote 0

Marcob

Member
Licensed User
Longtime User
Well, your code seems complicated to me.

Yes, I agree. Your modified routine performs better.
Nevertheless B4XCanvas routine is still slower that the one using Canvas (about 4x on my PC).
If it's not possible to convert an object B4XCanvas to Canvas, I think that's the best option.
Thank you.

PS
I'm sorry, I uploaded the wrong file. Here you find the last updated project with the expected comparison.
 

Attachments

  • CvsSpeedTest2.zip
    2 KB · Views: 200
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
Several things:

1. You can get a B4J Canvas from B4XCanvas:
B4X:
Dim cvs As Canvas = Parent.GetView(0)

2. The performance numbers are not really correct. You can see that it takes a long time after the drawings are done and until the drawings appear.

3. You are not using BitmapCreator efficiently. No reason to clip the path. In this case you can do it completely with BitmapCreator:

B4X:
Sub Process_Globals
   Private fx As JFX
   Private MainForm As Form
   Private xui As XUI
   Private startref As Long
   Private iv As ImageView
End Sub

Sub AppStart (Form1 As Form, Args() As String)
   MainForm = Form1
   MainForm.Show
   iv.Initialize("")
   MainForm.RootPane.AddNode(iv, 10, 10, 30, 90)
   Dim Gradient As BitmapCreator
   Gradient.Initialize(30, 90)
   Gradient.FillGradient(Array As Int(xui.Color_Red,xui.Color_Blue), Gradient.TargetRect, "TOP_BOTTOM")
   Dim GradientBrush As BCBrush = Gradient.CreateBrushFromBitmapCreator(Gradient)
   Dim bc As BitmapCreator
   bc.Initialize(30,90)
   '**************** test with B4XCanvas
   startref = DateTime.now
   For i = 0 To 1000
       bc.DrawRectRounded2(bc.TargetRect, GradientBrush, True, 0, 15)
   Next
   bc.SetBitmapToImageView(bc.Bitmap, iv)
   Log("BitmapCreator Time: "&(DateTime.Now-startref))

This is ~200% faster than Canvas.
 
Upvote 0

Marcob

Member
Licensed User
Longtime User
Thank you all for your replies on this matter.
Here is a summary of different methods to draw a round rectangle with gradient on a canvas, in case someone else needs it:

B4X:
#Region Project Attributes
    #MainFormWidth: 600
    #MainFormHeight: 600
#End Region

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private xui As XUI
    Private startref As Long
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    'MainForm.RootPane.LoadLayout("Layout1") 'Load the layout file.
    MainForm.Show
        
    '**************** draw a rounded gradient on a B4XCanvas (with path)

    Dim cvs As B4XCanvas
    cvs.Initialize(MainForm.RootPane)
    Dim r As B4XRect
    r.Initialize(60,60,90,150)
    startref = DateTime.now
    For i = 0 To 1000
        DrawRoundRectGradient(cvs, r, 30, Array As Int(xui.Color_Red,xui.Color_Blue))
    Next
    Log("B4XCanvas Time: "&(DateTime.Now-startref))       
    
    '**************** draw a rounded gradient on a B4XCanvas (with brush)
    
    r.Initialize(120,60,150,150)
    startref = DateTime.now
    For i = 0 To 1000
        DrawRoundRectGradient2(cvs, r, 30, Array As Int(xui.Color_Red,xui.Color_Blue))
    Next
    Log("B4XCanvas(2) Time: "&(DateTime.Now-startref))   
    
    '**************** draw a rounded gradient on a native Canvas
    
    Dim g As String = "linear-gradient(from 0% 0% to 100% 100%, #ff0000 0%, 0x0000ff 100%)"
    Dim rp As B4XView = MainForm.RootPane
    Dim cv As Canvas = rp.GetView(0)
    startref = DateTime.now
    For i=0 To 1000
        DrawRectRounded(cv,180,60,30,90,30,30,GetPaint(g),True,0)
    Next
    Log("Canvas Time: "&(DateTime.Now-startref))
    
    cvs.Invalidate
    cvs.Release
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


Private Sub DrawRoundRectGradient(cvs As B4XCanvas, rct As B4XRect, Radius As Float, Colors() As Int)
    Private bc As BitmapCreator
    Private rct1 As B4XRect
    
    rct1.Initialize(0, 0, rct.Width, rct.Height)
    bc.Initialize(rct.Width, rct.Height)
    bc.FillGradient(Colors, rct1, "TOP_BOTTOM")
    
    Private Path As B4XPath
    Path.InitializeRoundedRect(rct, Radius)
    
    cvs.ClipPath(Path)
    cvs.DrawBitmap(bc.Bitmap, rct)
    cvs.RemoveClip
End Sub

Private Sub DrawRoundRectGradient2(cvs As B4XCanvas, rct As B4XRect, Radius As Float, Colors() As Int)
    Private gradient,bc As BitmapCreator
    Private rct1 As B4XRect
    
    rct1.Initialize(0, 0, rct.Width, rct.Height)   
    gradient.Initialize(rct.Width, rct.Height)
    gradient.FillGradient(Colors, rct1, "TOP_BOTTOM")
        
    Dim gradientBrush As BCBrush = gradient.CreateBrushFromBitmapCreator(gradient)
    bc.Initialize(rct.Width, rct.Height)
    bc.DrawRectRounded2(bc.TargetRect, gradientBrush, True, 0, Radius )

    cvs.DrawBitmap(bc.Bitmap, rct)
End Sub

'Returns the paint object according to the color string
Public Sub GetPaint(Color As String) As Paint
    Private joPaint As JavaObject
    joPaint = joPaint.InitializeStatic("javafx.scene.paint.Paint")
    Return joPaint.RunMethod("valueOf", Array As Object(Color))
End Sub

'Draws a rouded Rectangle
'x = left coordinate of the surrounding rectangle
'Y = top coordinate of the surrounding rectangle
'Width = width coordinate of the surrounding rectangle
'Height = height coordinate of the surrounding rectangle
'ArcWidth = corner arc width
'ArcHeight = corner arc height
'Color = fx.Colors.Color
'Filled  True Filled, False only the line
'Stroke = line width, has no effect when Filled = True
Public Sub DrawRectRounded(MyCanvas As Canvas, x As Double, y As Double, Width As Double, Height As Double, ArcWidth As Double, ArcHeight As Double, Color As Paint, Filled As Boolean, LineWidth As Double)
    Private joCanvas, joGraph As JavaObject

    joCanvas = MyCanvas
    joGraph = joCanvas.RunMethod("getGraphicsContext2D", Null)

    If Filled = False Then
        joGraph.RunMethod("setStroke", Array As Object(Color))
        joGraph.RunMethod("setLineWidth", Array As Object(LineWidth))
        joGraph.RunMethod("strokeRoundRect", Array As Object(x, y, Width, Height, ArcWidth, ArcHeight))
    Else
        joGraph.RunMethod("setFill", Array As Object(Color))
        joGraph.RunMethod("fillRoundRect", Array As Object(x, y, Width, Height, ArcWidth, ArcHeight))
    End If
End Sub
 
Upvote 0
Top