You are on page 1of 5

DESARROLLO Python

NO SLO LAS TORTUGAS PINTAN


Aade funcionalidad a Inkscape usando pequeos scripts de Python.
Hace poco creamos un programa Python que interactuaba con OpenOffice, ahora vamos a crear scripts que nos permiten realizar acciones dentro de Inkscape, un gran programa de diseo vectorial. POR JOS MARA RUIZ

nkscape es un programa para la creacin de grficos vectoriales. En el mundo de Linux hasta el momento slo existan dos programas en este mbito: Inkscape y Sodipodi. Pero puede que la balanza se incline hacia el que ofrezca ms funcionalidades. Y qu mejor manera de hacerlo que soportar extensiones en un lenguaje de alto nivel. Los programadores de Inkscape han desarrollado un sistema de extensiones que permite usar cualquier lenguaje para realizar los plugins. En la Figura 1 podemos ver un dibujo generado por una de las extensiones. Aaron Spike ha estado desarrollando plugins experimentales en Python y espera que en un futuro prximo el soporte para Python venga integrado en Inkscape. Para ver sus ejemplos y documentacin puede pasar por el sitio web que aparece en Recurso [1].

Grficos vectoriales y binarios


Los grficos vectoriales son una de las tecnologas que ms rpidamente se estn extendiendo. El objetivo de todos los entornos grficos parecen ser el uso de grficos vectoriales en lugar de grficos binarios. Por qu son mejores los grficos vectoriales? Qu ventajas tienen? El problema con los grficos binarios se

debe a que son como fotografas de los objetos que representan. Imagina un tamiz de 0 y 1 que representa colores pues eso es un fichero JPEG o PNG. Si queremos hacer ese grfico dos veces mayor tenemos que usar trucos que nos permiten inventarnos colores con los que rellenar el nuevo espacio. Por ejemplo, si un punto tiene el color 53-39-200 (Rojo-Verde-Azul) entonces los nuevos puntos que lo rodean debern ser del mismo color o de uno parecido. El resultado nunca es satisfactorio, y conforme aumentamos el tamao de la imagen vamos perdiendo informacin, al final aparecen manchas borrosas. Observaremos el mismo comportamiento al rotar la imagen. The Gimp es el programa ms usado para trabajar con ficheros grficos binarios. Existe otra manera de almacenar la informacin grfica, como ecuaciones. En lugar de guardar fsicamente todos los puntos entre el punto A y el punto B guardamos una ecuacin que representa esa recta, as como los puntos de inicio y final. Si queremos hacer la lnea ms grande slo tenemos que alejar el punto final. As de simple. De esta manera no se pierde informacin. Pero como todo en la vida, esta tcnica tambin posee desventajas. Cada vez que queremos ver el grfico vectorial, el ordenador tiene que aplicar tcnicas para pintar la lnea entre los

puntos A y B, lo cual resulta muy costoso. Al contrario que lo que ocurre con los grficos binarios, que guardan ya los puntos pintados. Imagina el trabajo de pintar un dibujo vectorial de cientos o miles de lneas. A pesar de ello, la flexibilidad que proporcionan los grficos vectoriales es muy apreciada por los desarrolladores y diseadores grficos. Un problema subyacente a los grficos vectoriales es cmo representarlos cuando los guardemos en disco. Esto ha supuesto durante mucho tiempo un gran dilema, ya que cada fabricante empleaba un tipo de fichero distinto, de manera que no era posible importar los grficos de unos programas a otros. La solucin lleg con el formato SVG (ver Recurso [2]) que paulatinamente han ido aceptando todos los

Figura 1: Dibujo generado por una de las extensiones.

52

Nmero 18

WWW.LINUX- MAGAZINE.ES

Python DESARROLLO

Listado 1: patito.svg
01 <?xml version=1.0 standalone=no?> 02 <!DOCTYPE svg PUBLIC -//W3C//DTD SVG 1.0//EN http://www.w3.org/TR/2001/REC-SVG03 20010904/DTD/svg10.dtd> 04 <svg width=320 height=220> 05 <rect width=320 height=220 fill=white stroke=black /> 06 <g transform=translate(10 10)> 07 <g stroke=none fill=lime> 08 <path d=M 0 112 09 L 20 124 L 40 129 L 60 126 L 80 120 L 100 111 L 120 104 10 L 140 101 L 164 106 L 170 103 L 173 80 L 178 60 L 185 39 11 L 200 30 L 220 30 L 240 40 L 260 61 L 280 69 L 290 68 12 L 288 77 L 272 85 L 250 85 L 230 85 L 215 88 L 211 95 13 L 215 110 L 228 120 L 241 130 L 251 149 L 252 164 L 242 181 14 L 221 189 L 200 191 L 180 193 L 160 192 L 140 190 L 120 190 15 L 100 188 L 80 182 L 61 179 L 42 171 L 30 159 L 13 140Z/> 16 </g> 17 </g> 18 </svg>

Antes de comenzar
Los script de efectos son algo experimentales an en Inkscape. Por ello no veremos el men Efectos, a menos que le indiquemos a Inkscape que lo muestre. Para ello se accede a las preferencias de Inkscape, se hace click en la pestaa de Misc, y en el panel que se muestre se vuelve a hacer click sobre habilitar efectos (normalmente pondr: Experimental). Una vez hecho, hay que reiniciar Inkscape. Podremos ver en la barra de men una nueva entrada: Efectos.

Primer paso, fichero borra.inx


El fichero INX le va a decir a Inkscape dnde debe colocar en el men de efectos nuestro script. Tambin proporciona informacin sobre el objetivo del mismo. Su formato es tal y como se muestra en el Listado 2. Se proporciona el nombre y el tipo de script que vamos a emplear. La etiqueta <id> debe contener un nombre nico. Se puede usar una URL, por ejemplo, para proporcionarlo. En el fichero se indica que nuestro script va a borrar los objetos que seleccionemos en Inkscape. Tambin indicamos qu script se llamar borra.py.

fabricantes. Es un formato abierto basado en XML, de forma que todo el que est interesado puede no slo leerlo sino tambin modificarlo. Y aqu es donde entra en juego Python. La comunidad Python ha creado un paquete de libreras llamado PyXML que permite manipular ficheros XML sin demasiados problemas.

Scalable Vector Graphics


SVG (Scalable Vector Graphics) ha sido la solucin propuesta por el W3C y utilizada por todos los grandes fabricantes y los programas de software libre!. SVG es un formato XML muy sencillo, por ejemplo:
<text x=20 y=20>MeU encanta SVG</text>

mostrar el texto Me encanta SVG en la posicin (20,20), y:


<rect x=10 y=10U width=50 height=30>U </rect>

pintar un rectngulo que tiene su punto de arranque en (10,10), 50 de ancho y 30 de alto. El funcionamiento de este formato es bastante evidente. Desgraciadamente tanta simplicidad pesa, hace que los ficheros comiencen a ganar tamao de manera desmesurada. Por ello SVG puede funcionar tambin de forma abreviada.

El Listado 1 muestra el cdigo necesario (y completo) para dibujar un patito de goma como el que aparece en la Figura 4. Las distintas reas del fichero definen el tipo de documento, xml, el tamao del lienzo, 320x220, se prepara el pincel con la instruccin <g>, y se realiza el dibujo de manera abreviada con la instruccin <path>. Esas letras seguidas de nmeros, la L o la M, son abreviaturas de funciones matemticas. L es la abreviatura de lnea y debe ir seguida de dos coordenadas. Se pintar una lnea hasta las coordenadas de la siguiente L y as hasta el final. El comando M mueve el pincel a la posicin que le indiquemos, y el comando Z cierra un PATH, una ruta o un dibujo. Nosotros usaremos la versin extendida, con un elemento XML para cada trazo. Es ms simple y fcil de comprender. No parece especialmente complicado, incluso podramos realizar un dibujo SVG usando un editor de textos y mucha paciencia. Pues este es el formato que la industria ha estandarizado para la representacin de grficos vectoriales. Al ser un fichero XML podemos hacer uso de parsers y navegadores que nos permiten trabajar con ellos de manera ms o menos cmoda. Usaremos la librera PyXML que se puede encontrar en el Recurso [3] o en su sistema de paquetes.

Listado 2: borra.inx
01 <inkscape-extension> 02 <name>Borra los Objetos seleccionados</name> 03 <id>es.linux-magazine.borra</i d> 04 <dependency type=executable location=extensions>borra.py </dependency> 05 <effect> 06 <object-type>all</object-type> 07 </effect> 08 <script> 09 <command reldir=extensions>borra.py</ command> 10 </script> 11 </inkscape-extension>

WWW.LINUX- MAGAZINE.ES

Nmero 18

53

DESARROLLO Python

Listado 3: borra.py
01 02 03 04 05 06 07 08 #!/usr/bin/env python import import import import sys, getopt xml.dom.ext xml.dom.ext.reader.Sax2 xml.xpath open(sys.argv[-1:][0],r) 17 18 #create DOM object 19 doc = reader.fromStream(stream) 20 stream.close() 21 22 #Code to modify the DOM object goes here 23 for id in ids: 24 path = //*[@id=%s] % id 25 for node in xml.xpath.Evaluate(path,doc): 26 node.parentNode.removeChild(no de) 27 28 #serialize DOM object into XML on stdout 29 xml.dom.ext.Print(doc)

Listado 4: bosque.inx
01 <inkscape-extension> 02 <name>Generar bosque</name> 03 <id>es.linux-magazine.borra</i d> 04 <dependency type=executable location=extensions>bosque.p y</dependency> 05 <effect> 06 <object-type>all</object-type> 07 </effect> 08 <script> 09 <command reldir=extensions>bosque.py< /command> 10 </script> 11 </inkscape-extension>

#collect ids from the arguments 09 opts = getopt.getopt(sys.argv[1:],, [id=]) 10 ids = [opt[1] for opt in opts[0] if opt[0]==id] 11 12 #create xml parser 13 reader = xml.dom.ext.reader.Sax2.Reader () 14 15 #open SVG tempfile 16 stream =

Segundo paso, fichero borra.py


Este fichero realiza el trabajo duro. En realidad es un simple programa Python puro y duro. El protocolo usado por Inkscape es el de crear un fichero en /tmp SVG con el contenido del fichero actual. Inkscape pasa la ruta de este fichero as como los identificadores de los objetos seleccionados a Python a travs de los parmetros de ejecucin del script. Python debe recogerlos y procesarlos; para ello empleamos getopt. Esta librera nos permite procesar los parmetros que acompaan al nombre del programa cuando se ejecuta desde un terminal. En nuestro caso:
opts = getopt.getoptU (sys.argv[1:],,[id=])

Recogemos del primer parmetro en adelante y que busque parmetros del tipo id=.
ids = [opt[1] for opt inU opts[0] if opt[0]==id]

doc = reader.fromStream(stream) stream.close()

En opts tenemos las distintas opciones, usamos una estructura especial de Python, una lista por comprensin, para generar una lista donde se cumpla que la primera entrada, la que identifica el tipo de parmetro recogido, sea id. La segunda entrada contendr el valor asociado. Si tenemos la siguiente entrada:
borra.py id=3 id=1U id=232

Acabaremos con la lista:


[3,1,232]

Creamos un parser, un analizador, de XML de tipo DOM. ste es un estndar implementado por casi todos los lenguajes de programacin y desarrollado por el W3C. Abrimos el fichero que nos pasan entre los argumentos en modo lectura y usamos nuestro analizador DOM para interpretarlo, y el resultado lo guardamos en doc. Como ya no necesitamos el fichero, cerramos el stream que usamos para leerlo. Bueno, ya tenemos los ids de los puntos a borrar y el fichero SVG dentro de un analizador en memoria. Ahora deberemos recorrer el fichero SVG buscando todas aquellas etiquetas que tengan un id como algunas de las que nos han pasado y borrarlas.
for id in ids: path = //*[@id=%s] % id for node in xml.xpath.U Evaluate(path,doc): node.parentNode.U removeChild(node)

As que ya tenemos los IDs de los puntos a borrar. Ahora necesitamos analizar el fichero del que nos pasan la ruta y que contiene el dibujo en s. Nuestros pasos sern:
reader = xml.dom.ext.U reader.Sax2.Reader() stream = open(sys.argv[-1:][0],U r)

Figura 2: Antes de aplicar nuestra extensin vemos el elemento escogido para su eliminacin.

Recorremos nuestras ids, generamos una ruta XPath (veremos ms sobre ellas en la siguiente seccin) y la usamos para generar una lista de nodos que se corresponden a nuestra id.

54

Nmero 18

WWW.LINUX- MAGAZINE.ES

Python DESARROLLO

reiniciar Inkscape para que surta efecto.

XPath
Si echamos un vistazo al cdigo del bucle for veremos una linea algo extraa:
path = //*[@id=%s] % id

Figura 3: Despus de aplicar nuestra extensin, el elemento escogido ha sido eliminado por nuestro plugin en Python.

Qu significa esto? Para comenzar, el operador % normalmente se emplea para sacar el resto de una divisin, pero tambin nos permite realizar sustituciones dentro de una cadena de texto. Veamos un ejemplo:
>>> Hola mundo %s %U cruel Hola mundo cruel

Pero sigue sin estar claro para qu sirve la cadena //*[@id=3]. Es una ruta XPath (ver Recurso [4]). El W3C ha desarrollado un lenguaje de rutas que nos permite expresar la posicin de un conjunto de elementos dentro de un fichero XML. Este lenguaje es simple, un fichero XML tiene etiquetas y las etiquetas tienen propiedades. Toda las etiquetas tienen un padre y estn conectadas unas con otras. Las rutas son parecidas a las de los sistemas de ficheros. Imaginemos el siguiente fichero XML:
01 <ciudad nombre=Mlaga> 02 <calle nombre=Ancha del Crmen> 03 <casa numero=1/> 04 <casa numero=2/> 05 <casa numero=3/> 06 <casa numero=4/> 07 <casa numero=5/> 08 </calle> 09 <calle nombre=Angosta del Crmen>

Procedemos a recorrer esta segunda lista y a borrar los nodos uno a uno. Podemos ver el antes y el despus de aplicar nuestra extensin sobre un dibujo en las Figuras 2 y 3. Debemos copiar los dos ficheros (normalmente usando el usuario root) en /usr/share/gnome/inkscape/extensions. Los ficheros con efectos se cargan cada vez que Inkscape arranca, cuando hagamos un cambio debemos

La palabra de control %s se sustituye por la palabra cruel. Si tuvisemos ms palabras de control, stas se iran sustituyendo una por una, en orden de izquierda a derecha, con los parmetros que pasemos despus del %.

Listado 5: bosque.py
01 02 03 04 05 06 07 08 09 #!/usr/bin/env python import sys, getopt import xml.dom.ext import xml.dom.ext.reader.Sax2 import xml.xpath import random 28 triangulo.setAttribute(fill, green) 29 30 return triangulo 31 32 def tronco (doc,x,y): 33 tronco = doc.createElement (rect) 34 tronco.setAttribute(x,%d % (x-20)) 35 tronco.setAttribute(y, %d % (y+90)) 36 tronco.setAttribute(height,50) 37 tronco.setAttribute(width,20) 38 tronco.setAttribute(fill,brown) 39 return tronco 40 41 def arbol (x,y): 42 nodo = xml.xpath.Evaluate(path,doc) 43 nodo[0].parentNode.appendChild(triangulo(doc,x, y)) 44 nodo[0].parentNode.appendChild(triangulo(doc,x, (80+y))) 45 nodo[0].parentNode.appendChild(tronco(doc,x,y))) 46 47 for i in range(0,5): 48 x = random.randint(0,500) 49 y = random.randint(0,1000) 50 arbol(x,y) 51 52 xml.dom.ext.Print(doc)

def recogeDatos (): opts = getopt.getopt(sys.argv[1:],,[id=]) 10 ids = [opt[1] for opt in opts[0] if opt[0]==id] 11 12 reader = xml.dom.ext.reader.Sax2.Reader() 13 stream = open(sys.argv[-1:][0],r) 14 doc = reader.fromStream(stream) 15 stream.close() 16 return doc 17 18 doc = recogeDatos() 19 20 path = //g[1] 21 22 def triangulo (doc,x,y): 23 triangulo = doc.createElement(polyline) 24 coords = %d %d %d %d %d %d %d %d % (x, y, (50+x), (50+y),(x-50), (y+50), x, y) 25 triangulo.setAttribute(points,coords) 26 triangulo.setAttribute(stroke,black) 27 triangulo.setAttribute(stroke-witdh, 2)

WWW.LINUX- MAGAZINE.ES

Nmero 18

55

DESARROLLO Python

10 11 12 13 14

<casa numero=1/> <casa numero=2/> <casa numero=3/> </calle> </ciudad>

Pintando un bosque
Ya somos capaces de borrar cosas, vamos ahora a dibujarlas. Realmente todo el problema est en saber dnde colocar las lineas de texto dentro del fichero XML y qu poner en ellas. Vamos a crear un bosque muy simple a base de dibujar pequeos rboles (ver Figura 5): se compondrn de dos tringulos verdes y un tronco marrn. Hemos dividido el cdigo en funciones que dibujan los tringulos y el tronco dadas unas coordenadas. Para los tringulos emplearemos un elemento llamado polyline que dibuja un polgono, una figura cerrada, a base de lneas. Para el tronco empleamos el elemento rect que dibuja rectngulos. Para crear los elementos empleamos el mtodo doc.createElement() que DOM nos proporciona. Este mtodo devuelve un nodo que contiene el elemento que le especifiquemos. Por ejemplo, podramos invocar doc.createElement(casa), y el resultado sera un nodo con el elemento <casa></casa> en su interior. Una vez que tengamos el elemento almacenado en una variable podemos aadirle atributos usando el mtodo doc.setAttribute que acepta el nombre del atributo y el valor. Muy importante: los atributos slo aceptan cadenas de texto. Si no tenemos una cadena habr que convertir el dato a una. As ya podemos crear un elemento casa de la siguiente manera:
>>> casa = doc.U createElement(casa) >>> casa.setAttributeU (numero,3)

Queremos toda la informacin de las etiquetas ciudad, as que usamos la ruta XPath /ciudad. Si queremos la informacin de todas las casas la ruta sera /ciudad/calle/casa. No es muy complicado no? Pero an no se parece a la ruta de nuestro ejemplo. Imaginemos que necesitamos todos los elementos que estn en la segunda posicin de la jerarqua, para ello empleamos //, que nos devuelve todos los elementos que estn en la posicin dos de la jerarqua (las calles). Esto ya se va acercando a lo que hicimos, pero el * sigue sin tener sentido. Como en las rutas de los sistemas operativos * significa cualquier cosa, es por lo que //* viene a decir cualquier cosa que est en el segundo nivel de XML. Pero no nos interesa cualquier cosa, sino aquellas etiquetas que tienen una propiedad id y que adems estn dentro de las que nos han pasado. Para ello se usa //*[@id=3], que restringe los elementos a aquellos con id igual a 3. La ruta que hemos generado se la pasamos a xml.xpath.Evaluate(path,doc), y esta funcin devuelve una lista con los nodos que contienen las etiquetas que corresponden con la misma. Entonces usamos el mtodo:
node.parentNode.U removeChild(node)

Figura 5: Nuestro programticamente.

bosque

generado

bosque parezca de verdad, en realidad es un bosque de juguete, hacemos uso de la funcin random.randint(), que nos genera nmeros aleatorios. De esa manera los rboles se distribuirn por la pgina al azar. Ahora slo tenemos que copiar los ficheros del Listado 4 y 5 al directorio de recursos de Inkscape en /usr/share/inkscape/extensions/.

Conclusin
En la prxima versin de Inkscape, la 0.44, las extensiones aparecern por defecto. Esto nos puede llevar a una explosin de scripts que realizarn trucos muy interesantes con nuestras imgenes. Ya existen scripts que aaden sombras a nuestros diseos o generan imgenes muy decorativas (como por ejemplo un copo de nieve). Aaron Spike y otra/os desarrolladores/ as trabajan duro para que Inkscape se convierta en una opcin libre a las grandes suites de diseo vectorial. Colaboremos todos con ellos si es posible, The Gimp ya es rival para su alternativa comercial. Inkscape puede serlo en unos aos con el apoyo de la comunidad. I

que cada nodo posee para borrarse a s mismo, y por tanto eliminando esos puntos de la imagen SVG. Para ms informacin sobre cmo usar XPath vea el Recurso [5].

Figura 4: El patito de goma dibujado por el Listado 1.

Este cdigo generara el elemento <casa numero=3></casa>. Pero tener el elemento no nos vale de nada si no lo insertamos dentro del fichero XML. Para ello debemos obtener un nodo de la jerarqua, por ejemplo mediante XPath, e insertar en l nuestro nuevo elemento. El mtodo nodo.appendChild() se encarga de ello. El cdigo del Listado 5 recoge los parmetros de la lnea de comandos, de los que slo nos interesa la ruta del fichero SVG. Entonces usamos un bucle que ejecuta 5 veces la funcin rbol() que ensambla la imagen usando dos tringulos y un rectngulo. Para que el

RECURSOS
[1] Plugins experimentales en Python de Aaron Spike: http://www.ekips.org/ comp/inkscape/extending.php [2] El formato SVG: http://www.w3.org/ 2002/Talks/www2002-svgtut-ih/hwtut. pdf [3] La librera PyXML: http://pyxml. sourceforge.net/ [4] Descripcin de las rutas XPath: http:// www.w3.org/TR/xpath [5] Tutorial de las rutas XPath: http:// www.zvon.org/xxl/XPathTutorial/ Output_spa/examples.html

56

Nmero 18

WWW.LINUX- MAGAZINE.ES

You might also like