Screenlet a kreslení s buffery

Michal Hořejšek 29. 9. 2009

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.

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í.

Našli jste v článku chybu?
120na80.cz: Tipy pro odvodnění organismu

Tipy pro odvodnění organismu

Měšec.cz: Banky umí platby na kartu, jen to neříkají

Banky umí platby na kartu, jen to neříkají

Měšec.cz: Se stavebkem k soudu už (většinou) nemusíte

Se stavebkem k soudu už (většinou) nemusíte

Měšec.cz: TEST: Vyzkoušeli jsme pražské taxikáře

TEST: Vyzkoušeli jsme pražské taxikáře

Podnikatel.cz: Karuselové podvody. Jak se nepřiplést?

Karuselové podvody. Jak se nepřiplést?

Měšec.cz: Kurzy platebních karet: vyplatí se platit? (TEST)

Kurzy platebních karet: vyplatí se platit? (TEST)

DigiZone.cz: Skylink o půlnoci vypnul 12 525

Skylink o půlnoci vypnul 12 525

Lupa.cz: Vodafone umí volání přes Wi-Fi. Z ciziny jako v ČR

Vodafone umí volání přes Wi-Fi. Z ciziny jako v ČR

Měšec.cz: Investice do drahých kovů - znáte základní chyby?

Investice do drahých kovů - znáte základní chyby?

Měšec.cz: Test: Výběry z bankomatů v cizině a kurzy

Test: Výběry z bankomatů v cizině a kurzy

Vitalia.cz: Tohle je Břicháč Tom, co zhubnul 27 kg

Tohle je Břicháč Tom, co zhubnul 27 kg

DigiZone.cz: Sázka na e-sporty stanici Prima vychází

Sázka na e-sporty stanici Prima vychází

Vitalia.cz: Je kočka riziko pro těhotnou ženu?

Je kočka riziko pro těhotnou ženu?

Vitalia.cz: Bio vejce nepoznají ani veterináři

Bio vejce nepoznají ani veterináři

Lupa.cz: Největší torrentový web KickassTorrents padl

Největší torrentový web KickassTorrents padl

Podnikatel.cz: Výsledek akce Bez Heureky? Terno se nekoná

Výsledek akce Bez Heureky? Terno se nekoná

Měšec.cz: Co s reklamací, když e-shop krachuje?

Co s reklamací, když e-shop krachuje?

Vitalia.cz: Za zánět močových cest mohou plavky

Za zánět močových cest mohou plavky

Měšec.cz: Platíme NFC mobilem. Konečně to funguje!

Platíme NFC mobilem. Konečně to funguje!

Podnikatel.cz: Fotogalerie: Jesenka už má skoro 50 let

Fotogalerie: Jesenka už má skoro 50 let