Android Question Better Draw Performance

Scantech

Well-Known Member
Licensed User
Longtime User
I have 2 questions.

Which has a better performance on drawing? B4XRect or BitmapCreator DrawRect. Or are they the same?

I am really interested in faster drawing performance especially in Gradient area. My canvas draws 3 rect (using bitmapcreator) with gradient fill. 60 of them takes about 4 seconds to complete. Depending on device give or take second or two. The question for this is there anything faster than bitmapcreator in gradient area?

Thank you
 

Scantech

Well-Known Member
Licensed User
Longtime User
This is an example of a Radial Gauge with 2 borders and a Background (in BasicDrawing = False). The border orientation is different from each other and different size. The Gauges may differ in Border and Background colors from each other, otherwise i think i could of reuse the bmp. I see no other way to make it faster?

B4X:
Private Sub DrawScaleDigitalCircle
 
    'This drawing is not basic.  Requires more memory and lags performance on loading
    If cBasicDrawing = False Then
 
        If cDigitalThickBorder = False Then
            Dim cBorderWidth As Float = 0.008 * cWidth
   
            'Outter Bevel
            Dim bmp As B4XBitmap = DrawRadial(cWidth, cHeight, Array As Int(cDigitalBorderColor, cDigitalBorderColor2), "TOP_BOTTOM")
            cvsGauge.DrawBitmap(bmp, rectGauge2) 'Class_Globals
   
            'Inner Bevel
            If cDigitalInnerBevel = True Then
                'Inner Bevel
                Dim rectGauge22 As B4XRect
                rectGauge22.Initialize(cBorderWidth, cBorderWidth, cWidth - cBorderWidth, cHeight - cBorderWidth)
                Dim bmp2 As B4XBitmap = DrawRadial(cWidth - cBorderWidth * 2, cHeight - cBorderWidth * 2, Array As Int(cDigitalBorderColor, cDigitalBorderColor2), "BOTTOM_TOP")
                cvsGauge.DrawBitmap(bmp2, rectGauge22)
            End If
   
            'Background
            Dim RectGauge23 As B4XRect
            RectGauge23.Initialize(cBorderWidth * 2, cBorderWidth * 2, cWidth - (cBorderWidth * 2), cHeight - (cBorderWidth * 2))
'            Dim bmp3 As B4XBitmap = DrawRadial(cWidth - cBorderWidth * 4, cHeight - cBorderWidth * 4, Array As Int(cDigitalBackgroundColor, cDigitalBackgroundColor2), "TOP_BOTTOM")
            Dim bmp3 As B4XBitmap = DrawRadialSpecial(cWidth - cBorderWidth * 4, cHeight - cBorderWidth * 4, Array As Int(cDigitalBackgroundColor, cDigitalBackgroundColor2))
            cvsGauge.DrawBitmap(bmp3, RectGauge23)
        Else
            cBorderWidth = 0.036 * cWidth
   
            'Outer Bevel  
            Dim bmp As B4XBitmap = DrawRadial(cWidth, cHeight, Array As Int(cDigitalBorderColor, cDigitalBorderColor2), "TOP_BOTTOM")
            cvsGauge.DrawBitmap(bmp, rectGauge2)
   
            'Inner Bevel
            If cDigitalInnerBevel = True Then
                'Inner Bevel
                Dim rectGauge22 As B4XRect
                rectGauge22.Initialize(cBorderWidth + (cBorderWidth/2), cBorderWidth + (cBorderWidth/2), cWidth - (cBorderWidth + cBorderWidth/2), cHeight - (cBorderWidth + cBorderWidth/2))
                Dim bmp2 As B4XBitmap = DrawRadial(cWidth - cBorderWidth * 3, cHeight - cBorderWidth * 3, Array As Int(cDigitalBorderColor, cDigitalBorderColor2), "BOTTOM_TOP")
                cvsGauge.DrawBitmap(bmp2, rectGauge22)
            End If
   
            'Background
            Dim RectGauge23 As B4XRect
            RectGauge23.Initialize(cBorderWidth * 2.25, cBorderWidth * 2.25, cWidth - (cBorderWidth * 2.25), cHeight - (cBorderWidth * 2.25))
'            Dim bmp3 As B4XBitmap = DrawRadial(cWidth - cBorderWidth * 4.5, cHeight - cBorderWidth * 4.5, Array As Int(cDigitalBackgroundColor, cDigitalBackgroundColor2), "TOP_BOTTOM")
            'This will draw radial gradient
            Dim bmp3 As B4XBitmap = DrawRadialSpecial(cWidth - cBorderWidth * 4.5, cHeight - cBorderWidth * 4.5, Array As Int(cDigitalBackgroundColor, cDigitalBackgroundColor2))  
            cvsGauge.DrawBitmap(bmp3, RectGauge23)
        End If
    Else
        Dim cbord As Float = 0.016 * cWidth
 
        'Border
        cvsGauge.DrawCircle(cWidth/2, cHeight/2, cWidth/2, cDigitalBorderColor, True, cbord)

        'Background
        cvsGauge.DrawCircle(cWidth/2, cHeight/2, (cWidth/2) - cbord, cDigitalBackgroundColor, True, cbord)
    End If
 
    cvsGauge.DrawText(cGaugeTitle, GaugeTextX, GaugeTextY, xui.CreateDefaultBoldFont(cGaugeTitleTextSize), CDigitalTitleColor, "CENTER")
    cvsGauge.DrawText(cGaugeUnit, GaugeUnitX, GaugeUnitY, xui.CreateDefaultFont(cGaugeUnitTextSize), CDigitalUnitColor, "CENTER")

    cvsGauge.Invalidate
End Sub
B4X:
Sub DrawRadial(Width As Float, Height As Float, Clrs() As Int, Orientation As String) As B4XBitmap
    Dim Gradient As BitmapCreator
    Gradient.Initialize(10, Height)
    Gradient.FillGradient(Clrs, Gradient.TargetRect, Orientation)
    Dim Brush As BCBrush = Gradient.CreateBrushFromBitmapCreator(Gradient)
    Dim bc As BitmapCreator
    bc.Initialize(Width, Height)
    Dim xWidth As Float = Floor(Width/2)
    Dim xHeight As Float = Floor(Height/2)
    bc.DrawCircle2(xWidth, xHeight, xWidth, Brush, True, 0)
    Return bc.Bitmap
End Sub
Sub DrawRadialSpecial(Width As Float, Height As Float, Clrs() As Int) As B4XBitmap
    Dim Gradient As BitmapCreator
    Gradient.Initialize(Width, Height)
'    Gradient.FillGradient(Clrs, Gradient.TargetRect, Orientation)
    Gradient.FillRadialGradient(Clrs, Gradient.TargetRect)
    Dim Brush As BCBrush = Gradient.CreateBrushFromBitmapCreator(Gradient)
    Dim bc As BitmapCreator
    bc.Initialize(Width, Height)
    Dim xWidth As Float = Floor(Width/2)
    Dim xHeight As Float = Floor(Height/2)
    bc.DrawCircle2(xWidth, xHeight, xWidth, Brush, True, 0)
    Return bc.Bitmap
End Sub
 
Last edited:
Upvote 0

Scantech

Well-Known Member
Licensed User
Longtime User
The example above is drawn once. I get all the configurations and then draw it.

Setting filled for borders to false and adjusting the strokewidth can probably increase performance but running into some problems with it.
 
Last edited:
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
Your code is very inefficient. Many things should be reused, like the BCBrush and all BitmapCreators. It is a mistake to create new ones each call.

You will also get much better performance if you remove the Canvas completely and only use BitmapCreator. You can replace the text drawing with two labels or draw on a panel that is above the ImageView used for the BitmapCreator drawings.
 
Upvote 0

Scantech

Well-Known Member
Licensed User
Longtime User
Did as you said. Just with BCBrush and BitmapCreators i was able to reduce loading time from 5 to 7 seconds down to 2 to 3 seconds with 50 gauges. They are only reused if colors and sizes are the same. Which most of the time they will be.

I will see what i can do with the canvas.

However, I am running into Out of Memory issue at random time (over 40 gauges). Is there any tip to eliminate this issue?

Thanks Erel.
 
Upvote 0

Scantech

Well-Known Member
Licensed User
Longtime User
I suggest you try it with Samsung S6 or something equivalent to it.

Keep pressing "Click Here to add" and it might crash within 10 tries. Emulator with low resolution never crashes.

Use in portrait mode due to the gauge size
 

Attachments

  • xGaugesDemo.zip
    73.9 KB · Views: 289
Upvote 0

Scantech

Well-Known Member
Licensed User
Longtime User
How is it the apps in Google Play are able to add so many gauges without crashing and the best part when they rotate it it loads in fraction of a second. I am talking about many gauges (40+).

I added Heap Memory in Manifest. It helps to certain point then crashes.

Do you think if i use BitmapCreator and eliminate the canvas it should prevent the crashing?
 
Last edited:
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
How is it the apps in Google Play are able to add so many gauges without crashing and the best part when they rotate it it loads in fraction of a second. I am talking about many gauges (40+).
are you sure the app is using 40+ Canvases?
 
Upvote 0

Scantech

Well-Known Member
Licensed User
Longtime User
or draw on a panel that is above the ImageView used for the BitmapCreator drawings.

What is the purpose drawing on a panel that must be above ImageView? Why on top of Imageview? Does it have to be invalidated? Is the Imageview for the bmp that i created in the example above?
 
Upvote 0

emexes

Expert
Licensed User
Have you tried just using Canvas to do draw the gauges, rather than the cross-platform way (which might be constrained by having to deal with the lowest-common-denominator of various underlying OS/GPU support)?

My usual way of drawing gauges has been:

- Create a square Bitmap of the gauge background, including scale markings and numbers, fancy borders, unit and quantity labels... everything that DOESN'T change when the reading changes. Note that you can use the alpha channel of the Bitmap to create non-rectangular gauges.

- Create a rectangular Bitmap of the gauge needle, pointing upwards (can point left, right or down if you like; I like up because it matches clocks and compasses). The centre of this bitmap should be on the rotational point of the needle. Because gauges are usually round, then it is simplest to just make this bitmap as tall as the gauge itself, and as wide as the needle.

Then, to draw a new frame of the gauge, it's just:

B4X:
public Sub DrawGauge(C As Canvas, Percent As Float)
  
    C.DrawBitmap(DialBitmap, Null, DialDestRect)    'restore background
  
    Dim Angle As Float = Percent / 100 * 270 - 135

    C.DrawBitmapRotated(NeedleBitmap, Null, NeedleDestRect, Angle)
  
End Sub

and I whipped up a quick test of 15 and 60 gauges on my nothing-special-about-it Alcatel 1C running Android 7, and I get frame rates of 21 fps for 15 gauges and 12 fps for 60 gauges. I'm guessing that at least half of that time is spent copying the gauge background to clear the previous needle, and if I was running out of clock cycles, that'd be the first area to optimize, by only restoring the quadrant that the needle was previously in, should save 50-to-75% of that time.

I went to upload a screenshot of the 60 gauges, but the bbs came back and said it was too large. Which was a bit of a surprise, because I've uploaded screenshots no problem before, but then I realised: I've drawn each of the 60 gauges with random-color-line backgrounds to demonstrate that I'm not cheating by reusing the same background for all gauges, and PNG won't compress random stuff very well, so... hmm.
 
Last edited:
Upvote 0

emexes

Expert
Licensed User
The test rig was:
B4X:
Dim Z As Float
For Z = 0 To 1000 Step 0.1
    Sleep(1)
    StartTime = DateTime.Now
    For GI = 0 To NumGauges - 1
        Dim GC As Canvas
        GC.Initialize(GP(GI))
        G(GI).DrawGauge(GC, Sin(Z + GI) * 50 + 50)
    Next
    Activity.Invalidate
    Sleep(1)
    EndTime = DateTime.Now
Next
and the code to redraw a gauge is the three lines inside the GI loop. I should try getting rid of that Sin (is called for each gauge, ie 60 times per frame) and probably reuse the Canvas instead of creating it anew each time.
 
Upvote 0
Top