lunes, octubre 22, 2012

Pequeñas lecciones aprendidas usando Cairo (1)

Portando actividades de gtk2 a gtk3 aprendimos algunos trucos relacionados con Cairo.
Voy a compartir algunos básicos en este post, y luego algunas cosas que hemos aprendido con respecto a la pérformance, que no son del todo obvias o no son muy sencillas de encontrar.

En primer lugar un ejemplo bien sencillo de la mínima aplicacion para mostrar algo usando cairo en gtk2 y gtk3:

#!/usr/bin/python

import gtk

class MinimalCairoTest(gtk.Window):

    def __init__(self):
        super(MinimalCairoTest, self).__init__()
        self.set_size_request(400, 400)
        self.connect("destroy", gtk.main_quit)
        darea = gtk.DrawingArea()
        darea.connect("expose-event", self.__expose_cb)
        self.add(darea)
        self.show_all()

    def __expose_cb(self, widget, event):
        cr = widget.window.cairo_create()
        cr.set_source_rgb(1.0, 0.0, 0.0)
        cr.rectangle(20, 20, 120, 80)
        cr.fill()


MinimalCairoTest()
gtk.main()


El mismo código usando Gtk3:

#!/usr/bin/python

from gi.repository import Gtk

class MinimalCairoTest(Gtk.Window):

    def __init__(self):
        super(MinimalCairoTest, self).__init__()
        self.set_size_request(400, 400)
        self.connect("destroy", Gtk.main_quit)
        darea = Gtk.DrawingArea()
        darea.connect("draw", self.__draw_cb)
        self.add(darea)
        self.show_all()

    def __draw_cb(self, widget, cr):
        cr.set_source_rgb(1.0, 0.0, 0.0)
        cr.rectangle(20, 20, 120, 80)
        cr.fill()


MinimalCairoTest()
Gtk.main()

Como podemos ver, el cambio más importante es que el "expose-event" es reemplazado por un evento "draw" que ya nos provee un contexto de cairo, en este caso, es el parámetro cr.

Lo que veremos en pantalla es simplemente:

No muy interesante, pero nos sirve de base para seguir avanzando.

Si queremos mostrar una imagen centrada en la pantalla podemos hacer:

#!/usr/bin/python

from gi.repository import Gtk
import cairo

png_test_file = '/usr/share/icons/gnome/256x256/emotes/face-cool.png'

class MinimalCairoTest(Gtk.Window):

    def __init__(self):
        super(MinimalCairoTest, self).__init__()
        self.set_size_request(400, 400)
        self.connect("destroy", Gtk.main_quit)
        # read the png file and create a surface
        self._im_surface = cairo.ImageSurface.create_from_png(png_test_file)
        
        darea = Gtk.DrawingArea()
        darea.connect("draw", self.__draw_cb)
        self.add(darea)
        self.show_all()

    def __draw_cb(self, widget, cr):
        # get window width & heigth
        win_width = self.get_allocation().width
        win_height = self.get_allocation().height
        # show the image centered
        cr.translate((win_width - self._im_surface.get_width()) / 2,
                (win_height - self._im_surface.get_height()) / 2)
        cr.set_source_surface(self._im_surface)
        cr.paint()


MinimalCairoTest()
Gtk.main()

Y veremos:


Lo primero a tener en cuenta, es que aun usando los bindings dinámicos para Gtk3, los bindings dinámicos para Cairo no estan listos por eso hacemos "import cairo" y no "from gi.repository import Cairo"

En el último ejemplo de este post, vamos a aplicar una transformación simple, para mostrar la imagen a la mitad de su tamaño (copio solo el metodo __draw_cb, ya que el resto es igual)

     def __draw_cb(self, widget, cr):
        # get window width & heigth
        win_width = self.get_allocation().width
        win_height = self.get_allocation().height
        # show the image centered
        scale = 0.5
        image_width = self._im_surface.get_width() * scale
        image_height = self._im_surface.get_height() * scale
        cr.translate((win_width - image_width) / 2,
                (win_height - image_height) / 2)
        cr.scale(scale, scale)
        cr.set_source_surface(self._im_surface)
        cr.paint()

En este caso veremos:

Lo interesante es ver que pasa si cambiamos el orden en las operaciones de cairo efectuadas (translate, scale, set_surface, paint).

Si hacemos el scale despues del set_surface:

        cr.translate((win_width - image_width) / 2,
                (win_height - image_height) / 2)
        cr.set_source_surface(self._im_surface)
        cr.scale(scale, scale)
        cr.paint()

La imagen es transladada correctamente, pero no se escala:

Y si hacemos el scale antes del translate:

        cr.scale(scale, scale)
        cr.translate((win_width - image_width) / 2,
                (win_height - image_height) / 2)
        cr.set_source_surface(self._im_surface)
        cr.paint()

La escala afecta al translate, por lo que se translada menos de lo que debería:

Este caso es sencillo, porque hay una sola transformación efectuada, pero por ejemplo cuando hay escalados y rotaciones, se complica un poco más, y hay que tener bien en cuenta el orden en el que se aplican las transformaciones.
En un próximo post voy a escribir acerca de algunos puntos para lograr buena performance usando Cairo.
Publicar un comentario