Screenlet a kreslení s buffery

29. 9. 2009
Doba čtení: 5 minut

Sdílet

V minulém díle našeho seriálu o tvorbě takzvaných Screenletů jsme se naučili nakreslit různé tvary a dnes si povíme, jak zařídit, aby se nám některé prvky nevykreslovaly zbytečně často. Tím dokážeme náš Screenlet poměrně dobře optimalizovat a on potom není zbytečně náročný na systémové zdroje.

Něco takového uděláme uděláme jen tak, že použijeme buffery (vyrovnávací paměť). Vyrovnávací paměť pro kreslení ve Screenletech se tvoří tak, že nejprve vytvoříme samotnou proměnnou, ve které budeme kreslení uschovávat. Dále si po inicializaci Screenletu (tj. v metodě on_init) do této proměnné vytvoříme kreslící plochu pomocí metody Pixmap z modulu GTK. Poté si při potřebě vytvoříme novou cairo vrstvu a s tou pracujeme běžně jako s hlavní cairo vrstvou (jako s proměnnou ctx). A nakonec nesmíme zapomenout pokaždé buffer zapsat do hlavní cairo vrstvy, aby se nám obsah zobrazil.

Teď si třeba někteří říkáte, proč dělat něco tak složitého, když můžu mít jednoduše stejný výsledek. Odpověď je taková, že pokud máme naši ukázku s časem a chtěli bychom přidat datum, tak se bude každou vteřinu zbytečně zjišťovat, jaké je datum a ještě k tomu se bude každou vteřinu vykreslovat. Pokud použijeme buffery, tak datum zjistíme jen jednou, vykreslíme ho do vytvořené vrstvy a poté jen tuto vrstvu vykreslujeme do té hlavní (ctx) vrstvy. Tím se náročnost velice sníží. Je pravda, že v tomto případě žádnou změnu defacto nezporozujeme, ale při větších projektech je to už znát.

Ukázka

Vše si předvedeme na názorné ukázce.

#!/usr/bin/env python

import screenlets
from screenlets import DefaultMenuItem
import gobject
import time
import gtk
import cairo

class MyFirstScreenlet( screenlets.Screenlet ):

  __name__ = 'My First Screenlet'
  __version__ = '1.0'
  __author__ = 'Michal Horejsek'
  __desc__ = __doc__

  __timer = None
  __buffers = {}

  firstDrawing = True

  width = 220
  height = 80

  def __init__( self, **keyword_args ):
    screenlets.Screenlet.__init__( self, width=self.width, height=self.height, **keyword_args )

  def on_init( self ):
    self.add_default_menuitems()
    self.initBuffers()

  def on_map( self ):
    if not self.__timer:
      self.__timer = gobject.timeout_add( 1000, self.update )
    self.update()

  def on_unmap( self ):
    if self.__timer:
      gobject.source_remove( self.__timer )
      self.__timer = None

  def initBuffers(self):
    self.__buffers['time2'] = gtk.gdk.Pixmap( self.window.window, self.width, gtk.gdk.screen_height(), -1 )

  def update( self ):
    self.redraw_canvas()
    return True

  def on_draw( self, ctx ):
    if self.scale > 5:
      self.scale = 5
    ctx.save()
    ctx.scale( self.scale, self.scale )

    # pozadi
    ctx.set_source_rgba( 1, 1, 1, 0.1 )
    self.draw_rounded_rectangle( ctx, 0, 0, 20, self.width, self.height, fill=True ) # ohraniceni

    # ziskame cas
    foo = str( time.localtime()[3] ) + ':' + str( time.localtime()[4] ) + ':' + str( time.localtime()[5] )
    ctx.set_source_rgba( 1, 1, 1 )
    # vykreslime cas do hlavni vrstvy
    self.draw_text( ctx, 'Prave je '+foo, 45, 10, 'FreeSans', 12, 200 )

    # druhy cas aktualizujeme pouze, kdyz je vterina suda nebo probiha prvni vykresleni
    if time.localtime()[5] % 2 == 0 or self.firstDrawing:
      # vytvoreni nove cairo vrstvy
      ctxLayer = self.__buffers['time2'].cairo_create()
      # vyprazdneni (priprava) vrstvy
      self.clear_cairo_context( ctxLayer )

      ctxLayer.set_source_rgba( 1, 1, 1 )
      # vykreslime cas do nove vytvorene vrstvy (do bufferu)
      self.draw_text( ctxLayer, 'Prave je '+foo, 45, 50, 'FreeSans', 12, 200 )

    # vzdy vypise buffer time2
    ctx.set_source_pixmap( self.__buffers['time2'], 0, 0 )
    ctx.paint()

    ctx.restore()
    self.firstDrawing = False

  def on_draw_shape(self, ctx):
    self.draw_rectangle(ctx, 0, 0, self.width, self.height)

if __name__ == "__main__":
  import screenlets.session
  screenlets.session.create_session( MyFirstScreenlet )

Na začátek bych chtěl upozornit, že pro tento příklad je nutné importovat moduly gtk a cairo, které využíváme pro vytváření bufferů.

Když se podíváte do ukázky, základem bufferů je slovník zde nazvaný jako __buffers. Název si však můžete zvolit sami a vůbec nemusíte používat slovník, ale pokud budete používat více než jeden buffer (jakože to je velmi pravděpodobné), tak se toto bude nejspíše hodit více.

Dále si v metodě on_init (volána po úplné inicializaci Screnletů) voláme námi vytvořenou funkci initBuffers, kde si připravíme kreslící plochu. Jak je vidět, tak v příkladu používám rozměry bufferu jako náš samotný Screenlet a poté je vkládám do hlavní vrstvy na souřadnice [0,0]. Můžete také využívat možnosti vytváření menší buffery a vkládat je přímo na určené místo v hlavní vrstvě.

TIP: Tím, že použijete menší buffery a budete je vkládat přímo na určité místo v hlavní vrstvě, ušetříte paměť.

Další, co následuje po vytvoření bufferu, je potřeba ho nějak vyplnit. To v ukázce děláme v podmínce, kde zjišťujeme, jestli je vteřina sudá a pokud ano, tak buffer překreslíme novou hodnotou – tím nám vznikne, že druhý zobrazovaný čas bude „skákat“ po dvou sekundách. Buffer vyplníme cairo vrstvou, kterou následně vyprázdníme od předchozích hodnot (nemusejí to být zrovna předchozí hodnoty, které jsme my používali, ale nějaké „bláboly“, a mohou vznikat opravdová grafická díla, které opravdu nechceme).

ctxLayer = self.__buffers['time2'].cairo_create()
self.clear_cairo_context( ctxLayer ) 

A po usilovné práci máme vytvořenou novou vrstvu (v příkladu označená jako ctxLayer), s kterou již pracujeme stejně jako s hlavní vrstvou (v příkladu označená jako ctx).

Upozornění: Každá vrstva má vlastní nastavení. Pokud jsme v příkladu v hlavní vrstvě (ctx) nastavili nějakou barvu, pro novou vrstvu (ctxLayer) budeme muset opět barvu nastavit.

Nesmíme nakonec zapomenout buffer pokaždé přidat do hlavní cairo vrstvy.

ctx.set_source_pixmap( self.__buffers['time2'], 0, 0 )
ctx.paint() 

Výsledek

A tím je hotovo. Nyní už jede druhý výpis času přes buffer a na výsledek se můžete podívat. Zobrazení 2× času jsem použil proto, aby bylo vidět, že buffer opravdu funguje a můžete zkusit vytvořit cokoli podle vaší fantazie.

bitcoin školení listopad 24

Screenlet 3

Tipy, kde buffery použít

Buffery se dají použít u Screenletů, kde je potřeba provést refresh jen části Screenletu, což je vlastně skoro všude. Můžete si vytvořit pozadí a různé grafické prvky a poté už jen dělat refresh částí, které se mění. Například v minulém příkladu jsme vždy vykreslovali složité pozadí a do toho čas, nyní to můžete předělat tak, že pozadí se vykreslí pouze při startu Screenletu do bufferu a poté už jen vykreslovat ten čas. Buffery se také hodí u projektů, kde se provádí nějaká změna při přejíždění myši. Například mějme Screenlet, který zobrazuje seznam připojených oddílů a při přejetí myší nad určitým oddílem se pozadí toho oddílu změní – to by bylo bez bufferu pro procesor náročné, s buffery to je mnohem lepší.

Závěr

Buffery jsou opravdu dobrá věc, mně snížily až 3× zatížení procesoru u projektu InfoPanel a opravdu je doporučuju používat, pokud kódujete trošku vetší Screenlet, který už něco „umí“. Příště se podíváme, jak na nastavení.

Autor článku