Kivy - Framebuffer



Kivy library provides an "Fbo" class which stands for Framebuffer offscreen. It is an off-screen window on which you can draw any graphics instructions and then use it as a texture for the canvas of a certain Kivy widget.

The Fbo class is defined in kivy.graphics,fbo module. The first step is to create the fbo and use the fbo texture on other rectangle.

from kivy.graphics import Fbo, Color, Rectangle
with self.canvas:
   self.fbo = Fbo(size=self.size)

Next, add graphics instructions such as Rectangle to the Fbo object. For example −

with self.fbo:
   Color(1, 0, 0, .8)
   Rectangle(size=(256, 64))
   Color(0, 1, 0, .8)
   Rectangle(size=(64, 256))

Finally, apply the Fbo texture to the canvas.

self.texture = self.fbo.texture

Note that the FBO is lost if the OpenGL context is lost. In such a case, you need to reupload data using the Fbo.add_reload_observer() method..

add_reload_observer(callback) − Add a callback to be called after the whole graphics context has been reloaded. The callback parameter will be the context itself.

The bind() method binds the FBO object to the current opengl context. This way all the drawing operations will act inside the Framebuffer, until release() is called. The release() method releases or unbinds the Framebuffer.

self.fbo = FBO()
self.fbo.bind()

# do any drawing command
self.fbo.release()

self.canvas = self.fbo.texture

There is also a remove_reload_observer(callback) method that removes a callback from the observer list, previously added by add_reload_observer().

The clear_buffer() and clear_color methods clear the framebuffer and clear color in (red, green, blue, alpha) format.

Example

The following code demonstrates the use of Framebuffer in a Kivy application. The important part of the code is a class named as FboFloatLayout which inherits Kivy's FloatLayout class.

The constructor (__init__() method) creates a Fbo object on the canvase of float layout, draws a rectangle on it and sets its texture as the texture of the canvas.

def __init__(self, **kwargs):
   self.canvas = Canvas()
   with self.canvas:
      self.fbo = Fbo(size=self.size)
      self.fbo_color = Color(1, 1, 1, 1)
      self.fbo_rect = Rectangle()
   
   with self.fbo:
      ClearColor(0, 0, 0, 0)
      ClearBuffers()
   
   self.texture = self.fbo.texture
   super(FboFloatLayout, self).__init__(**kwargs)

we are going to add a button to this FloatLayout class, but before that the add_widget() method is overridden so as to add the graphics instruction to the fbo and then add it to the canvas.

def add_widget(self, *args, **kwargs):
   canvas = self.canvas
   self.canvas = self.fbo
   ret = super(FboFloatLayout, self).add_widget(*args, **kwargs)
   self.canvas = canvas
   return ret

The FboFloatLayout class also has the callbacks that respond to the changes in the size, pos and texture.

def on_size(self, instance, value):
   self.fbo.size = value
   self.texture = self.fbo.texture
   self.fbo_rect.size = value

def on_pos(self, instance, value):
   self.fbo_rect.pos = value

def on_texture(self, instance, value):
   self.fbo_rect.texture = value

Now to the App class. The build() method adds a button and applies a series of animation effects that change the button position up to down and from right to left, repeatedly.

def anim_btn(*args):
   animate = Animation(pos=(b.pos[0], Window.height - 50))
   animate += Animation(pos=(b.pos[0], 0))
   animate += Animation(pos_hint={'center_x': 1})
   animate += Animation(pos_hint={'center_x': 0})
   animate += Animation(pos_hint={'center_x': .5})
   animate.start(b)
   animate.repeat = True
   
b.bind(on_press=anim_btn)

These code fragments are put together in the complete code listing for the sake of convenience −

from kivy.graphics import Color, Rectangle, Canvas,
ClearBuffers, ClearColor
from kivy.graphics.fbo import Fbo
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty, NumericProperty
from kivy.app import App
from kivy.animation import Animation
from kivy.core.window import Window

Window.size = (720, 400)

class FboFloatLayout(FloatLayout):
   texture = ObjectProperty(None, allownone=True)

   def __init__(self, **kwargs):
      self.canvas = Canvas()
      with self.canvas:
         self.fbo = Fbo(size=self.size)
         self.fbo_color = Color(1, 1, 1, 1)
         self.fbo_rect = Rectangle()

      with self.fbo:
         ClearColor(0, 0, 0, 0)
         ClearBuffers()
         
      self.texture = self.fbo.texture
      super(FboFloatLayout, self).__init__(**kwargs)

   def add_widget(self, *args, **kwargs):
      canvas = self.canvas
      self.canvas = self.fbo
      ret = super(FboFloatLayout, self).add_widget(*args, **kwargs)
      self.canvas = canvas
      return ret
      
   def on_size(self, instance, value):
      self.fbo.size = value
      self.texture = self.fbo.texture
      self.fbo_rect.size = value

   def on_pos(self, instance, value):
      self.fbo_rect.pos = value

   def on_texture(self, instance, value):
      self.fbo_rect.texture = value

class FBOdemoApp(App):
   def build(self):
      f = FboFloatLayout()
      b = Button(text="FBO", size_hint=(None, None), pos_hint={'center_x': .5})
      f.add_widget(b)

      def anim_btn(*args):
         animate = Animation(pos=(b.pos[0], Window.height - 50))
         animate += Animation(pos=(b.pos[0], 0))
         animate += Animation(pos_hint={'center_x': 1})
         animate += Animation(pos_hint={'center_x': 0})
         animate += Animation(pos_hint={'center_x': .5})
         animate.start(b)
         animate.repeat = True
      b.bind(on_press=anim_btn)
      return f
      
FBOdemoApp().run()

Output

The application starts with a button captioned FBO at the bottom. When clicked, it starts its animation effects as defined.

kivy framebuffer.jpg
Advertisements