B4J Question Rotated rectangle on B4XCanvas

max123

Well-Known Member
Licensed User
Longtime User
Hi everyone,

I'm converting my B4J application where I normally used a Canvas to use B4XCanvas.
Canvas has the DrawRectRotated method that I used but can't find this method in B4XCanvas.

I understand that I could use DrawBitmapRotated and create a runtime image (using BitmapCreator) by filling it pixel by pixels of the desired color, but is this the right way to draw the rotated rectangle as quickly as possible?

My application is time critical because it receives fast commands via WiFi (commands that draw) and I need that for performance reasons the rectangle be drawn as fast possible, before I receive next packet.

Is there any command using B4XCanvas which is the counterpart of Canvas DrawRectRotated?

I have two versions of my app, for B4J and for B4A so I need it work on both.

Many thanks
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
Many thanks Erel for your reply 😉

I will try it and then post results
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Hi,

I've tried to use B4XPath and B4XCanvas.DrawPathRotated, but comparated to Canvas DrawRectRotated seem a lot slow.

From my results B4XCanvas.DrawPathRotated is about 10-12 times slower.

Maybe something wrong in my code?

I also found a strange behavior, because in B4XCanvas the rectangle is drawn even if I comment this line ?
B4X:
xuiCvs.Invalidate  " Line 65

Can someone explaining this strange behavior?

I absolutely need as it happens on B4A that the content of the canvas is drawn only when I do Invalidate, this is the main reason why I am switching from Canvas to B4XCanvas, more than the fact of portability between the various B4X, is this possible with B4XCanvas?

I attached the code, a zip project and images.

Many thanks

B4X:
Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form

    Dim xui As XUI

    Dim cvs As Canvas
    Dim xuiCvs As B4XCanvas

    Private Pane1 As Pane
    Private Pane2 As Pane
End Sub

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

    cvs.Initialize("")
    Pane1.AddNode(cvs,0,0,Pane1.PrefWidth, Pane1.PrefHeight)
    cvs.DrawRect(0,0, cvs.Width, cvs.Height, fx.Colors.Black, True, 0)
    cvs.DrawText("Canvas", 10, 25, fx.DefaultFont(20), fx.Colors.Cyan, "LEFT")

    xuiCvs.Initialize(Pane2)
    Dim rect As B4XRect
    rect.Initialize(0, 0, xuiCvs.TargetRect.Width, xuiCvs.TargetRect.Height)
    xuiCvs.DrawRect(rect, xui.Color_Black, True, 0)
    xuiCvs.DrawText("B4XCanvas", 10, 25, xui.CreateDefaultFont(20), xui.Color_Cyan, "LEFT")

    Dim start, elapsed As Long

    start = DateTime.Now
    For i = 1 To 500
        Wait For (DrawRectRotatedCanvas) Complete()
    Next
    elapsed = DateTime.Now - start
    Log("Draw 500 rotated rectangles on Canvas required " & elapsed & " Milliseconds")

    start = DateTime.Now
    For i = 1 To 500
        Wait For (DrawRectRotatedB4XCanvas) Complete()
    Next
    elapsed = DateTime.Now - start
    Log("Draw 500 rotated rectangles on B4XCanvas required " & elapsed & " Milliseconds")
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 DrawRectRotatedCanvas() As ResumableSub
    cvs.DrawRectRotated(100, 100, 200, 300, fx.Colors.Red, True, 0, 45)
    Sleep(0)
    Return True
End Sub

Sub DrawRectRotatedB4XCanvas() As ResumableSub
    Dim path As B4XPath
    Dim rect As B4XRect
    rect.Initialize(100, 100, 300, 400)
    path.InitializeRoundedRect(rect, 0)
    xuiCvs.DrawPathRotated(path, xui.Color_Blue, True, 0, 45, rect.CenterX, rect.CenterY)

'    xuiCvs.Invalidate

    Sleep(0)
    Return True
End Sub

Screen Shot 01-28-20 at 06.34 PM.PNGScreen Shot 01-28-20 at 06.35 PM.PNG
 

Attachments

  • DrawRectRotated.zip
    2.4 KB · Views: 198
Last edited:
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
B4XCanvas = Canvas. It is just a cross platform layer.

Please, any suggestion to improve the speed?
If no I'm forced to use Canvas instead of B4XCanvas because it seem to be too much slow, but mybe my results are wrong, I will try to remove Sleep(0)

This is how the native canvas works. You can make the containing pane invisible until you want it to show.

This is a big problem. If I work on pane visibility even all other draw in my canvas make it invisible, so like a clear Canvas.

You shouldn't add Sleep(0) if you want to measure performance.

Sorry, now I'm not at my PC. But I can return resumableSub if I remove Sleep(0) ?

What my application do is to receive fast commands over WiFi from ESP8266, ESP32, Arduino Mega + WiFi or ETH Shield , I wrote a C++ library called VirtualDisplay, this library make ESP8266 to use Android and Desktop devices as WiFi TFT touch (multitouch on Android) screens, I wrapped all Canvas methods to use on ESP side, the receiver app (called VirtualDisplay) will receive fast UDP (or TCP) commands (the limit now seem to be max 2000 commands every second) and execute it, with this ESP can draw 'near' realtime on remote devices.

To explain a bit better, this line on ESP wil draw a corresponding rotated rectangle on Android or Desktop devices inside my app (note: it even works on Raspberry, but because no native JavaFX, but OpenJFX it is very slow):
C++:
VirtualDisplay.DrawRectRotated(100, 100, 200, 300, RED, True, 0, 45);

Many thanks for your precious time 😉
 
Last edited:
Upvote 0

sorex

Expert
Licensed User
Longtime User
path is clipping which makes things slower.

if you create the rectangle and path outside the sub and only pass the path it will be faster aswell as you don't need to create 1000 new objects.
if it will make much difference I don't know.

on the other hand...

17000ms / 500 = 34ms

which equals ~30fps which is retro tv refresh rate.

how many rotated rectangle will you draw in one go and do you need 30fps?

usually info screens don't refresh that much.
 
Upvote 0

sorex

Expert
Licensed User
Longtime User
Draw 500 rotated rectangles on Canvas required 31 Milliseconds
Draw 500 rotated rectangles on B4XCanvas required 12 Milliseconds

that's with normal subs and passing the path and rectangle to the sub


without passing the objects it's 4 miliseconds slower
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Many thanks to both ;) for great support. 👍

I will try your precious suggestions and post results.

Draw 500 rotated rectangles on Canvas required 31 Milliseconds
Draw 500 rotated rectangles on B4XCanvas required 12 Milliseconds
@sorex , how do you changed the code to have this very good results? As for your suggestion? I need to know if it is applicable on my app, I receive WiFi command and with a long Select I test the received command and extract values from it, then call the relative canvas method with extracted parameters.
 
Last edited:
Upvote 0

sorex

Expert
Licensed User
Longtime User
just like this but it's only suitable for your 500 equal rectangles.

B4X:
    Dim path As B4XPath
    Dim rect As B4XRect
    rect.Initialize(100, 100, 300, 400)
    path.InitializeRoundedRect(rect, 0)
    For i = 1 To 500
                    DrawRectRotatedB4XCanvas(rect,path)
    Next
    elapsed = DateTime.Now - start
    Log("Draw 500 rotated rectangles on B4XCanvas required " & elapsed & " Milliseconds")
End Sub

Sub DrawRectRotatedCanvas()
    cvs.DrawRectRotated(100, 100, 200, 300, fx.Colors.Red, True, 0, 45)
End Sub

Sub DrawRectRotatedB4XCanvas(rect As B4XRect, path As B4XPath)
    xuiCvs.DrawPathRotated(path, xui.Color_Blue, True, 0, 45, rect.CenterX, rect.CenterY)
End Sub


but with some additional code it's good for random rectangle and still the same speed as the regular one. (slightly faster)

B4X:
    Dim path As B4XPath
    Dim rect As B4XRect
    rect.Initialize(100, 100, 300, 400)
               
    For i = 1 To 500
                rect.Left=100
                rect.Top=100
                rect.Width=300
                rect.Height=400  
    path.InitializeRoundedRect(rect, 0)
'        Wait For (DrawRectRotatedB4XCanvas(rect,path)) Complete()
                    DrawRectRotatedB4XCanvas(rect,path)
    Next
    elapsed = DateTime.Now - start
    Log("Draw 500 rotated rectangles on B4XCanvas required " & elapsed & " Milliseconds")
End Sub

Sub DrawRectRotatedCanvas()
    cvs.DrawRectRotated(100, 100, 200, 300, fx.Colors.Red, True, 0, 45)
End Sub

Sub DrawRectRotatedB4XCanvas(rect As B4XRect, path As B4XPath)
    xuiCvs.DrawPathRotated(path, xui.Color_Blue, True, 0, 45, rect.CenterX, rect.CenterY)
End Sub
 
Last edited:
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
I will try it, many thanks ;)

The first code I think cannot be applied, any rectangle is different, depending on ESP8266 command, I've no collected commands for now, my original idea was to use a list to collect commands and then draw all when I receive from ESP8266 the Invalidate method. I need to study it.
 
Last edited:
Upvote 0

sorex

Expert
Licensed User
Longtime User
sorry the code in the first block was wrong, it originally had the 2 inits out of the loop (correct it)
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Sorry @Erel
Why does it need to be a resumable sub?

I need it just fo the WaitFor I've used on my sample code.

For the Invalidate command behavior, my original idea was to use a list to collect commands and then draw all when I receive from ESP8266 the Invalidate method, but if I receive a lots of commands eg 1000, 2000, 5000 + then next I've no time to draw all them if ESP send more packets with new commands.
 
Last edited:
Upvote 0

sorex

Expert
Licensed User
Longtime User
just set a global variable like isDrawing=true as first thing in your drawing sub and set it back false when the drawing is done.

then add a check in your ESP routines for it. Only if isDrawing=False then call the drawing sub.
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
@sorex

You said:
on the other hand...

17000ms / 500 = 34ms

which equals ~30fps which is retro tv refresh rate.

how many rotated rectangle will you draw in one go and do you need 30fps?

I do not refresh the screen every command the ESP will send, so the screen refresh rate is not referred any way to the command rate, it work the same way than B4X Canvas works, on B4A eg. ESP can send 1000 commands, then send the Invalidate method (VirtualDisplay.Refresh(); on ESP side) and then B4A invalidates the Canvas once.

On my C++ library as for others Arduino libraries I've created the /example folder with about 30 demo sketches to know how the library works, the Graphic part is only one it can do, it can even read all Android sensors, can draw controls like buttons or array of buttons and if these are pressed the ESP know the button was pressed and the button state, can even draw customized bitmap sliders or arrays of it and ESP know time some milliseconds the value of each one, returning to the refresh of the screen in my examples there is eg. an example on witch ESP draw a rotating wireframe cube, this code draw 12 lines (the cube edges), then finally send the Invalidate command. This make a smoth rotating cube and without flickers and it is not related to the screen refresh, it will refresh every 12 commands.

I've tested ESP libray and my app on local network and even on WAN network with very slow latency and as I said with max 2000 commands any second, but the normal average I use about 500-1000. Works bidirectional way, so ESP can send to remote and remote can send to ESP, the remote can eg. reboot ESP, or set pinMode and pin state of any pin or can read ADC values and display them.

Many thanks for your help, I still tried Erel and Your suggestions, next I will post results, so other users have same problems can read it. 😉
 
Last edited:
Upvote 0

sorex

Expert
Licensed User
Longtime User
you could also use double buffering.

use 2 canvas objects and show 1.
draw in the other one and when it's done you hide the first one and show the second one.

then you might reduce some flickering from clearing and drawing when it takes too long.
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
you could also use double buffering.

use 2 canvas objects and show 1.
draw in the other one and when it's done you hide the first one and show the second one.

then you might reduce some flickering from clearing and drawing when it takes too long.

Yes, but currently I've not flickers on B4A side, it works very smoth because high refresh rate, the problem still in Desktop Java Canvas (as Erel said) that draw even without Invalidate method, so need to collect commands without draw it on the screen and then draw one by one when Invalidate command is received, so mayte use a List, Map or maybe create some structures with Type command.

Android Canvas do it automatically.
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
@sorex I moved this code outside the function and the For loop
B4X:
    Dim path As B4XPath
    Dim rect As B4XRect
    rect.Initialize(100, 100, 300, 400)
    path.InitializeRoundedRect(rect, 0)

Draw 500 rotated rectangles on Canvas required 31 Milliseconds
Draw 500 rotated rectangles on B4XCanvas required 12 Milliseconds
but I cannot have anyway your results, mine was not changed from last I posted:
Draw 500 rotated rectangles on Canvas required 875 Milliseconds
Draw 500 rotated rectangles on B4XCanvas required 18062 Milliseconds
 
Upvote 0

sorex

Expert
Licensed User
Longtime User
with the method I mentioned above it doesn't matter.

you draw as data comes in and when the command is invalidate you swap the 2 canvas objects.

so it's drawing when hidden.
 
Upvote 0
Top