Dibujo en PyGobject (python3)

Estoy tratando de escribir un editor gráfico simple usando PyGObject y python 3. Necesito dibujar líneas con diferentes colores y anchos usando el mouse. Encontré muchos ejemplos como este pero nada más complejo.

¿Cómo guardo la imagen dibujada entre eventos de ‘dibujo’? ¿Hay una forma incremental de dibujar o tengo que volver a dibujar el panel en cada evento de ‘dibujo’? Descubrí que puedo guardar la ruta, pero ¿cómo puedo guardar el ancho y los colores de las líneas dibujadas? ¿Hay forma de crear una imagen fuera de la callback ‘draw’ y solo aplicarla (dibujar) dentro de la callback?

Esto es lo que tengo por ahora.

#!/usr/bin/env python # -*- coding: utf-8 -*- from gi.repository import Gtk, Gdk import os class App(object): main_ui = os.path.join(os.path.dirname(__file__), 'gui.glade') def __init__(self): self.builder = Gtk.Builder() self.builder.add_from_file(self.main_ui) self.main_window.connect('destroy', self.quit) self.mw_quit_button.connect('clicked', self.quit) self.mw_graph_editor_button.connect('clicked', self.show_window, self.graph_editor_window) self.graph_editor_window.connect('delete-event', self.hide_window_delete) self.ge_menubar_file_quit.connect('activate', self.hide_window, self.graph_editor_window) self.ge_toolbar_quit.connect('clicked', self.hide_window, self.graph_editor_window) self.ge_drawingarea.connect('motion-notify-event', self.pointer_motion) self.ge_drawingarea.connect('motion-notify-event', self.show_coordinates) self.ge_drawingarea.connect('draw', self.draw_callback) self.path = None self.coord = (0, 0) self.rgb = (0, 0, 0) def __getattr__(self, name): obj = self.builder.get_object(name) if not obj: raise AttributeError("Object {0} has no attribute {1}".format(self, name)) setattr(self, name, obj) return obj def draw_callback(self, drawingarea, cr): if self.path: cr.append_path(self.path) cr.line_to(self.coord[0], self.coord[1]) cr.set_source_rgba(*self.rgb) self.path = cr.copy_path_flat() cr.stroke() def show_coordinates(self, window, event): self.ge_mouse_coordinates.set_label('X: {0:.0f} Y: {1:.0f}'.format(event.x, event.y)) def pointer_motion(self, widget, event): if event.state & Gdk.ModifierType.BUTTON1_MASK: self.draw(widget, event.x, event.y) elif event.state & Gdk.ModifierType.BUTTON3_MASK: self.draw(widget, event.x, event.y, True) def draw(self, widget, x, y, erase=False): self.coord = (x,y) if erase: self.rgb = (256, 256, 256) else: self.rgb = (0, 0, 0) widget.queue_draw() def show_window(self, widget, data): data.show_all() def hide_window_delete(self, widget, event): widget.hide() return True def hide_window(self, widget, window): window.hide() def run(self): self.main_window.show_all() Gtk.main() def quit(self, widget=None, data=None): self.main_window.destroy() Gtk.main_quit() if __name__ == "__main__": app = App() app.run() 

Lo siento por mi inglés, no es mi idioma nativo.

Necesitas usar una técnica de doble búfer:

http://en.wikipedia.org/wiki/Multiple_buffering#Double_buffering_in_computer_graphics

Es decir, tienes una imagen y dibujas sobre esa imagen: esa imagen es el búfer “detrás de escena”. Puedes tener muchos métodos que dibujan algo en esa imagen. Luego, en la callback que responde a la señal de ‘dibujo’, es decir, el método que realmente dibuja algo en la memoria de gráficos, solo lanzas tu imagen “detrás de escena”.

Teoría en código ( test.py ):

 import cairo from gi.repository import Gtk from os.path import abspath, dirname, join WHERE_AM_I = abspath(dirname(__file__)) class MyApp(object): """Double buffer in PyGObject with cairo""" def __init__(self): # Build GUI self.builder = Gtk.Builder() self.glade_file = join(WHERE_AM_I, 'test.glade') self.builder.add_from_file(self.glade_file) # Get objects go = self.builder.get_object self.window = go('window') # Create buffer self.double_buffer = None # Connect signals self.builder.connect_signals(self) # Everything is ready self.window.show() def draw_something(self): """Draw something into the buffer""" db = self.double_buffer if db is not None: # Create cairo context with double buffer as is DESTINATION cc = cairo.Context(db) # Scale to device coordenates cc.scale(db.get_width(), db.get_height()) # Draw a white background cc.set_source_rgb(1, 1, 1) # Draw something, in this case a matrix rows = 10 columns = 10 cell_size = 1.0 / rows line_width = 1.0 line_width, notused = cc.device_to_user(line_width, 0.0) for i in range(rows): for j in range(columns): cc.rectangle(j * cell_size, i * cell_size, cell_size, cell_size) cc.set_line_width(line_width) cc.set_source_rgb(0, 0, 0) cc.stroke() # Flush drawing actions db.flush() else: print('Invalid double buffer') def main_quit(self, widget): """Quit Gtk""" Gtk.main_quit() def on_draw(self, widget, cr): """Throw double buffer into widget drawable""" if self.double_buffer is not None: cr.set_source_surface(self.double_buffer, 0.0, 0.0) cr.paint() else: print('Invalid double buffer') return False def on_configure(self, widget, event, data=None): """Configure the double buffer based on size of the widget""" # Destroy previous buffer if self.double_buffer is not None: self.double_buffer.finish() self.double_buffer = None # Create a new buffer self.double_buffer = cairo.ImageSurface(\ cairo.FORMAT_ARGB32, widget.get_allocated_width(), widget.get_allocated_height() ) # Initialize the buffer self.draw_something() return False if __name__ == '__main__': gui = MyApp() Gtk.main() 

Archivo Glade ( test.glade ):

     False center-always 800 600    True False       

Dependencias:

Python 2:

 sudo apt-get install python-cairo 

Python 3:

 sudo apt-get install python3-gi-cairo 

Ahora ejecuta con:

 python test.py 

o

 python3 test.py 

Lo que parece:

introduzca la descripción de la imagen aquí

Toda la documentación de cairo se puede encontrar en http://cairographics.org/documentation/pycairo/3/reference/index.html

Lo anterior es un puerto de un ejemplo que he hecho en C hace mucho tiempo para Gtk 2.16, también puede verificarlo, pero está en español:

http://carlos.jenkins.co.cr/gtkcairo

Saludos cordiales