Alternative for Label when drawing text on a canvas

3,384 views
Skip to first unread message

João Ventura

unread,
Mar 20, 2014, 7:50:54 AM3/20/14
to kivy-...@googlegroups.com
Hi there,

I've been profiling my app, and I've noticed that Label.__init__(..) is such a bottleneck for a widget that I have which draws some figures and text in a canvas. Since my app is also to run on Android devices, where are many slow devices, I need to address this issue.

I've tried to create an empty label and shallow copy to reuse it, but without success (I think because the Label listeners, etc, do not get copied when shallow copying).

Does anyone have a suggestion for a faster alternative to draw text on a canvas? The text is fairly static, since I only need to change the font/size and draw it once, unless redrawing it for scaling up/down.


Thanks,
João Ventura

João Ventura

unread,
Mar 20, 2014, 2:56:28 PM3/20/14
to kivy-...@googlegroups.com
After searching through the Kivy source code, I've found that kivy.uix.Label uses kivy.core.text.Label to get the the text provider. We can use directly the text provider to generate a texture for us to append directly on our canvas.

Something like this can be done:

from kivy.core.text import Label as CoreLabel

mylabel
= CoreLabel(text="Hi there!", font_size=25, color=(0, 0, 0, 1))
# Force refresh to compute things and generate the texture
mylabel
.refresh()
# Get the texture and the texture size
texture
= mylabel.texture
texture_size
= list(texture.size)
# Draw the texture on any widget canvas
myWidget
= Widget()
myWidget
.canvas.add(Rectangle(texture=texture, size=texture_size))

With this, I reduced to half the drawing time of my widget. Label __init__ method is too heavy as is...

I just wished something like this was better documented, but it may be of use to someone.


João Ventura

João Ventura

unread,
Mar 20, 2014, 8:15:40 PM3/20/14
to kivy-...@googlegroups.com
By the way, if someone stomps across this, check if the position where you are drawing the texture is an integer tuple, or else the text may come out a little bit blurred. I just wasted 5 hours to find out that little detail.. :)

João Ventura

Mathieu Virbel

unread,
Mar 25, 2014, 3:44:09 PM3/25/14
to kivy-...@googlegroups.com
And this little details is something that Label take care of it.
Would you mind to give us a test case for reproducing?

I guess when you say you reduced to half the drawing time, i wonder how you really checked. Also, your approach doesn't help as when you move your widget, the rectangle will not follow the widget position. Also, because you don't set color, the Label color will use the previous set Color. (if it's white, you're lucky.)

Mathieu


--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

João Ventura

unread,
Mar 25, 2014, 4:38:52 PM3/25/14
to kivy-...@googlegroups.com
Hi Mathieu,

in the mentioned widget that I've created, I'm creating and drawing 93 labels, and these are the profiling statistics:
- Using kivy.uix.label.Label: 29731 function calls in 0.160 seconds
- Using kivy.core.text.Label: 16171 function calls in 0.066 seconds

The function calls are not exclusive for calls to the uix.Label or CoreLabel, but for my widget's draw method. But the only difference between both statistics is that in one I add uix.label to the canvas and in another, I use a text.label texture to add a rectangle to my widget's canvas.

As you said, the second approach has the problem for when moving/resizing the widget. But for my quick prototyping, I did prefer to have faster initial drawings than (faster) subsequent redrawings. Also, I don't know if relying on kv lang to redraw my labels would be much faster, since any change in a uix.label property triggers the redraw method of CoreLabel, and that is the only thing that I do.

Probably in the near future I'll rely on kivy lang, but it would be great if one could speed up the __init__ of uix.labels.

As should be obvious now, since uix.label eventually also invokes CoreLabel, it is faster to call CoreLabel. But here is a working example:

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.widget import Widget

from kivy.core.text import Label as CoreLabel

import cProfile
   
class TestApp(App):
   
def build(self):
        widget
= Widget()
        cProfile
.runctx('self.labels(100)', globals(), locals())
        cProfile
.runctx('self.corelabels(100)', globals(), locals())
       
return widget
   
   
def labels(self, n):
       
for i in xrange(n):
           
Label(text="%s" % i)
           
   
def corelabels(self, n):
       
for i in xrange(n):
           
CoreLabel(text="%s" % i)
       
if __name__ == "__main__":
   
TestApp().run()

In my computer, these are the statistics:
- self.labels: 18330 function calls (18329 primitive calls) in 0.073 seconds
- self.corelabels: 503 function calls in 0.002 seconds

The creation of uix.Label is too much expensive for some simple drawing cases, since it does binds to listeners, etc. I guess this is not something new, but the difference in magnitude seems to be large.. This is specially relevant in my low-spec Android tablet.

I Hope this is of some assistance.. Regards,
João Ventura

ngoone...@gmail.com

unread,
Oct 15, 2015, 5:46:51 PM10/15/15
to Kivy users support
Thank you, from the future.

In my use case, I'm duplicating the texture of a Label with a fixed set of offsets. Initially I did that by creating many copies of the Label, but that slowed down really fast (5 Labels, all referencing 1 'main' Label, resulted in a noticeable slow-down on my laptop, wouldn't have wanted to even try that on an Android device).

However, due to the nature of what I intend to do, I've simply subclassed Label to always re-draw on canvas my set of offsets. No noticable difference in rendering time compared to a single Label, which is good.

Roger Johnson

unread,
Mar 25, 2018, 6:11:04 AM3/25/18
to kivy-...@googlegroups.com
Please tell me there's still support for this.  I need the text but none of the other stuff and I'm dealing with large amounts of data so the significant processor savings is critical.... however the default text color is black I need to be able to change this on runtime.  Please say this is still being monitored!

my code

class BigDataDraw(Widget):

    def __init__(self, **kwargs):
        super(BigDataDraw, self).__init__(**kwargs)
        self.bind(pos=self.update_canvas)
        self.bind(size=self.update_canvas)
        self.update_canvas()
       
    def update_canvas(self, *args):
    #need to reset everything
        self.canvas.clear()
        with self.canvas:
            Color(0,0,1,0.25)
            Rectangle(pos=self.pos, size=self.size)
        #context instruction
            Color(1,1,1,1)
        #vertex instruction
            Ellipse (pos = (self.width/2,self.height/2), size=(10,10))
            mylabel = CoreLabel(text="Hi there!", font_size=10, color=(0, 0, 0, 1))

# Force refresh to compute things and generate the texture
            mylabel.refresh()
# Get the texture and the texture size
            texture = mylabel.texture
            texture_size = list(texture.size)
           
# Draw the texture on any widget canvas
            myWidget = Widget()
            Color(1,1,0,1)  #NOT WORKING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            Rectangle(texture=texture, size=texture_size q)

Roger Johnson

unread,
Mar 25, 2018, 6:14:27 AM3/25/18
to Kivy users support
Cancel that just saw the code thoroughly I see the color code my bad
Reply all
Reply to author
Forward
0 new messages