[Tutorial-Port Spanish Cairo (Appendix) hthiel.char@zonnet.nl**20080128144137 Spanish Translation of Drawing with Cairo: Getting Started Added links English-Spanish and vice versa in the index files Corrected typo and improved unclear formulation in English appendix corrected .next' in Spanish index ] hunk ./docs/tutorial/Tutorial_Port/app1.xhtml 198 -setSourceRGBA which sets not just the color but also the transparancy, as a +setSourceRGBA which sets not just the color but also the transparency, as a hunk ./docs/tutorial/Tutorial_Port/app1.xhtml 241 -

Note the showPage function, without with the program will compile, but -a .pdf reader will complain there is no page. The API documentation states the width and height +

Note the showPage function. Without it the program will compile, and even produce a .pdf +file, but this cannot be read correctly by a pdf reader. The API documentation states the width and height addfile ./docs/tutorial/Tutorial_Port/es-app1.xhtml hunk ./docs/tutorial/Tutorial_Port/es-app1.xhtml 1 - + + + + + + Tutorial de Gtk2Hs: Apéndice + + + + + +

+ +

Dibujando con Cairo: Empezando...

+ +

Es posible dibujar figuras complejas en Gtk2Hs, tanto en la propia pantalla como en diferentes formatos de +fichero, mediante la librería de dibujo de Cairo. Dibujar en Gtk2Hs no es muy diferente de dibujar +en Cairo, pero no está de más desarrollar un tutorial específico para ello.

+

Puedes consultar el centro de documentación de Cairo.

+

Para dibujar usando los formatos de Cairo con Gtk2Hs necesitamos una sintaxis especial, ya sea pare realizar +dibujos en pantalla, o en diferentes formatos de fichero, portable network graphics (png), portable document format (pdf), +postscript (ps) o scaleable vector graphics (svg). +El objetivo de este apéndice es explicar las funciones básicas que necesitarás. +

1. Dibujar

+ +En primer lugar debemos importar el módulo de dibujo de Cairo con import Graphics.Rendering.Cairo. +La siguiente función define el dibujo de un triángulo:

+
myDraw :: Render ()
+myDraw = do
+    setSourceRGB 1 1 0
+    setLineWidth 5
+
+    moveTo 120 60
+    lineTo 60 110
+    lineTo 180 110
+    closePath
+    
+    stroke
+
+

Esta función es de tipo Render () y a partir de la instrucción "do" puedes inferir que +Render es una mónada. Fíjate que no se trata de una mónada IO, que es la usada +en Graphics.UI.Gtk . El triángulo queda definido por las funciones moveTo, +lineTo and closePath , que hacen lo que su nombre sugiere. (moveTo - mueve a, lineTo - línea a y +closePath - cierraCamino). Sin embargo no dibujan nada, sino que definen el camino a seguir. El dibujo se +realiza cuando ejecutamos la función stroke (golpe).

+

Al principio, como puedes ver, +debe especificarse el color de las líneas setSourceRGB 1 1 0 y su ancho setLineWidth 5 .

+

Con todo esto ya se podría dibujar la figura. Para ello necesitamos un widget vacío, de tipo DrawingArea +(área de dibujo). Sin embargo no se dibuja en el propio widget, el canvas en el ejemplo siguiente, +sino en su DrawWindow (ventana de dibujo). Para lograrlo puedes usar:

+
widgetGetDrawWindow :: WidgetClass widget => widget -> IO DrawWindow
+o, también, este más símple: +
drawingAreaGetDrawWindow :: DrawingArea -> IO DrawWindow
+

Ahora puedes usar:

+
renderWithDrawable :: DrawableClass drawable => drawable -> Render a -> IO a
+

El dibujo debe realizarse en respuesta a un evento. Una posibilidad es un evento asociado a un botón, +onButtonPress como el que vimos en el capítulo 6.2. !Esto ya funciona! +Cada vez que se modifica el tamaño de la ventana, el dibujo desaparece y se vuelve a dibujar cuando +vuelves a pulsar el botón. Sin embargo dispones de otro evento, el Expose (mostrar), que envía una señal +cada vez que la ventana cambia de tamaño o se redibuja en la pantalla. Esto quizá se aproxime +más a lo que buscas, así que usa:

+
onExpose canvas (\x -> do renderWithDrawable drawin myDraw
+                          return (eventSent x))
+
+

Esto es lo que hemos desarrollado hasta ahora:

+

Appendix 1 Example a

+

Se ha incluído también un marco, para que se vea más bonito, aunque es innecesario. +Fíjate en que la función widgetShowAll aparece antes de la función widgetGetDrawWindow +en el código de abajo. Esto es necesario debido a que sólo se puede dibujar en una ventana visible! +

+
import Graphics.UI.Gtk
+import Graphics.Rendering.Cairo
+
+main :: IO ()
+main= do
+     initGUI
+     window <- windowNew
+     set window [windowTitle := "Hello Cairo",
+                 windowDefaultWidth := 300, windowDefaultHeight := 200,
+                 containerBorderWidth := 30 ]
+
+     frame <- frameNew
+     containerAdd window frame
+     canvas <- drawingAreaNew
+     containerAdd frame canvas
+     widgetModifyBg canvas StateNormal (Color 65535 65535 65535)
+
+     widgetShowAll window 
+     drawin <- widgetGetDrawWindow canvas
+     onExpose canvas (\x -> do renderWithDrawable drawin myDraw
+                               return (eventSent x))
+    
+     onDestroy window mainQuit
+     mainGUI
+
+myDraw :: Render ()
+myDraw = do
+    setSourceRGB 1 1 0
+    setLineWidth 5
+
+    moveTo 120 60
+    lineTo 60 110
+    lineTo 180 110
+    closePath
+
+    stroke
+
+

Quizá este ejemplo no sea realmente lo que buscas, ya que, aunque la figura es redibujada, no se ajusta al nuevo +tamaño de la ventana principal. Para conseguir ese efecto necesitaríamos:

+
myDraw :: Double -> Double -> Render ()
+myDraw w h = do
+    setSourceRGB 1 1 1
+    paint
+
+    setSourceRGB 1 1 0
+    setLineWidth 5
+
+    moveTo (0.5 * w) (0.43 * h)
+    lineTo (0.33 * w) (0.71 * h)
+    lineTo (0.66 * w) (0.71 * h)
+    closePath
+    stroke
+
+

Ahora el dibujo siempre se ajustará a los bordes definidos por los parámetros. Además hemos +fijado el color de fondo con la función paint, en vez de con widgetModify . +La función paint pinta con el color actual todo lo que esté dentro del área de dibujo (clip region). +Fíjate en que setSourceRGB no sólo emplea un valor de tipo Double +con un rango entre 0 y 1, en vez de un valor de tipo Int entre 0 y 65535, +sino que además usa una mónada de tipo Render en vez de una mónada de tipo IO .

+

Para dibujar la figura adaptable, necesitamos obtener el tamaño del +área de dibujo cada vez que cambie.

+
widgetGetSize :: WidgetClass widget => widget -> IO (Int, Int)
+

Así que el código para dibujar se convierte en:

+
onExpose canvas (\x -> do (w,h) <- widgetGetSize canvas
+                          drw <- widgetGetDrawWindow canvas
+                          renderWithDrawable drw (myDraw (fromIntegral w) (fromIntegral h))
+                          return (eventSent x))
+

Como el resto del código del ejemplo permanece como antes, no lo listamos. Este sería el +resultado de modificar el ancho de la ventana:

+

Appendix 1 Example b

+

Aquí hay otro ejemplo de dibujo, en este caso obtenido del +Tutorial de Cairo

+

Appendix 1 Example 3

+

Éste es el listado:

+
import Graphics.UI.Gtk  hiding (fill)
+import Graphics.Rendering.Cairo
+
+main :: IO ()
+main= do
+     initGUI
+     window <- windowNew
+     set window [windowTitle := "Hello Cairo 4",
+                 windowDefaultWidth := 300, windowDefaultHeight := 200,
+                 containerBorderWidth := 15 ]
+
+     frame <- frameNew
+     containerAdd window frame
+     canvas <- drawingAreaNew
+     containerAdd frame canvas
+
+     widgetShowAll window 
+     onExpose canvas (\x ->  do (w,h) <- widgetGetSize canvas
+                                drawin <- widgetGetDrawWindow canvas
+                                renderWithDrawable drawin 
+                                    (myDraw (fromIntegral w)(fromIntegral h))
+                                return (eventSent x))
+    
+     onDestroy window mainQuit
+     mainGUI
+
+myDraw :: Double -> Double -> Render ()
+myDraw w h = do
+           setSourceRGB 1 1 1
+           paint
+
+           setSourceRGB 0 0 0
+           moveTo 0 0
+           lineTo w h
+           moveTo w 0
+           lineTo 0 h
+           setLineWidth (0.1 * (h + w))
+           stroke
+
+           rectangle 0 0 (0.5 * w) (0.5 * h)
+           setSourceRGBA 1 0 0 0.8
+           fill
+
+           rectangle 0 (0.5 * h) (0.5 * w) (0.5 * h)
+           setSourceRGBA 0 1 0 0.6
+           fill
+
+           rectangle (0.5 * w) 0 (0.5 * w) (0.5 * h)
+           setSourceRGBA 0 0 1 0.4
+           fill
+
+

Comprueba que es igual que el ejemplo anterior, excepto por el dibujo. Introduce la función +setSourceRGBA que no sólo establece el color sino que también establece la transparencia, +como un valor entre 0 and 1. El ejemplo también emplea la función rectangle (rectángulo) y un método +fill (llenar) que "llena" las figuras cerradas con el color y la transparencia especificadas.

+

Nota: Debido a que existe un conflicto de nombres con una antigua librería +de Gtk2Hs, debes o ocultar fill en la importación de Graphics.UI.Gtk, o usar el nombre completo +Graphics.Rendering.Cairo.fill .

+

2. Escribir en ficheros

+

Es muy sencillo guardar un dibujo en un formato png, pdf, ps o svg. Más que guardar deberíamos hablar de +renderizar, ya que cada formato necesita su propia renderización. La función es:

+
renderWith:: MonadIO m => Surface -> Render a -> m a
+La Surface (superficie) es el lugar en quye aparecerá el dibujo. Es estos casos no se trata +de la pantalla sino de algo que debes proporcionar. Hay cuatro funciones diferentes, una por cada tipo de fichero. +
withImageSurface
+:: Format	                -- formato de píxeles en la superficie a crear
+-> Int	                        -- ancho de la superficie, en píxeles
+-> Int	                        -- alto de la superficie, en píxeles
+-> (Surface -> IO a)	        -- una acción que podría usar la superficie. La superficie es sólo válida para esta acción.
+-> IO a	
+ +

Para los ficheros en formato "portable network graphics (png)" usamos esta función. El tipo de datos Format +tiene cuatro constructores posibles, FormatARGB32, FormatRGB24, FormatA8, FormatA1 . +En el ejemplo inferior usamos el primero. La acción que emplea una Surface como su argumento suele +ser la función renderWith seguida de una función para escribir en el fichero. Así para el formato png +esta podría ser la función:

+
surfaceWriteToPNG
+:: Surface	                    -- una Superficie
+-> FilePath	                    -- el nombre del fichero en el que vamos a escribir
+-> IO ()
+ +

Así, la receta para escribir en dibujo en un fichero en formato png podría ser:

+
withImageSurface  FormatARGB32 pnw pnh (\srf -> do renderWith srf (myDraw (fromIntegral pnw) (fromIntegral pnh))
+                                                   surfaceWriteToPNG srf "myDraw.png")
+

donde pnw y pnh son el ancho y el alto (tipos Int) .

+

Para guardar el dibujo en un fichero en formato pdf, puedes usar:

+
withPDFSurface
+:: FilePath	               -- un nombre de fichero para la salida PDF (debes poder escribir)
+-> Double	               -- ancho de la superficie, en puntos (1 punto == 1/72.0 inch)
+-> Double	               -- alto de la superficie, en puntos (1 punto == 1/72.0 inch)
+-> (Surface -> IO a)	       -- una acción que pueda usar la superficie. La superficie sólo vale para esta acción.
+-> IO a
+

Esta función emplea diferentes parámetros que la anterior, aunque es muy parecida. La receta para guardar el fichero +es ahora:

+
withPDFSurface "myDraw.pdf" pdw pdh (\s ->  renderWith s $ do myDraw pdw pdh
+                                                              showPage )
+

Fíjate en que la función showPage . GHC y GHCi dará el código como válido +pero el lector de pdf no será capaz de leer el resultado, indicando que que no hay ninguna página. +La documentación de la API indica que el ancho y el largo +se establecen en puntos (y tipo Double ), así que debes comprobar si esto funciona en la práctica.

+

Para guardar un archivo Postscript:

+
withPSSurface
+:: FilePath	                 -- un nombre de fichero para la salida PS (debes poder escribir)
+-> Double	                 -- ancho de la superficie, en puntos (1 punto == 1/72.0 inch)
+-> Double	                 -- alto de la superficie, en puntos (1 punto == 1/72.0 inch)
+-> (Surface -> IO a)	         -- una acción que pueda usar la superficie. La superficie sólo vale para esta acción.
+-> IO a
+

Para salvarlo puedes usar la misma receta que antes o la versión más reducida:

+
withPSSurface "myDraw.ps" psw psh (flip renderWith (myDraw psw psh >> showPage))
+

Por último, para salvarlo en el formato "scaleable vector graphics", debes usar la misma sintaxis, pero con +withSVGSurface . Así que esto sería:

+
withSVGSurface "myDraw.svg" pgw pgh (flip renderWith $ myDraw pgw pgh >> showPage)
+

A continuación he puesto un ejemplo que guarda el último dibujo mostrado arriba en los cuatro formatos (con diferentes +tamaños):

+
import Graphics.UI.Gtk  hiding (fill)
+import Graphics.Rendering.Cairo
+
+main :: IO ()
+main= do
+     initGUI
+     window <- windowNew
+     set window [windowTitle := "Save as...",
+                 windowDefaultWidth := 300, windowDefaultHeight := 200] 
+
+     let pnw = 300
+         pnh = 200
+     withImageSurface 
+       FormatARGB32 pnw pnh (\srf -> do renderWith srf (myDraw (fromIntegral pnw) (fromIntegral pnh))
+                                        surfaceWriteToPNG srf "myDraw.png")
+
+
+     let pdw = 720
+         pdh = 720
+     withPDFSurface "myDraw.pdf" pdw pdh (\s ->  renderWith s $ do myDraw pdw pdh
+                                                                   showPage )
+     
+     let psw = 360
+         psh = 540
+     withPSSurface 
+        "myDraw.ps" psw psh (flip renderWith (myDraw psw psh >> showPage))
+
+     let pgw = 180
+         pgh = 360
+     withSVGSurface 
+        "myDraw.svg" pgw pgh (flip renderWith $ myDraw pgw pgh >> showPage)
+         
+     putStrLn "Press any key to quit..."
+     onKeyPress window (\x -> do widgetDestroy window
+                                 return (eventSent x))
+
+     widgetShowAll window
+     onDestroy window mainQuit
+     mainGUI
+
+myDraw :: Double -> Double -> Render ()
+myDraw w h = do
+           setSourceRGB 1 1 1
+           paint
+
+           setSourceRGB 0 0 0
+           moveTo 0 0
+           lineTo w h
+           moveTo w 0
+           lineTo 0 h
+           setLineWidth (0.1 * (h + w))
+           stroke
+
+           rectangle 0 0 (0.5 * w) (0.5 * h)
+           setSourceRGBA 1 0 0 0.8
+           fill
+
+           rectangle 0 (0.5 * h) (0.5 * w) (0.5 * h)
+           setSourceRGBA 0 1 0 0.6
+           fill
+
+           rectangle (0.5 * w) 0 (0.5 * w) (0.5 * h)
+           setSourceRGBA 0 0 1 0.4
+           fill
+
+

Nota: Por favor, consulta la documentación de la API del Graphics.Rendering.Cairo +y los tutoriales y ejemplos de Cairo para usos más avanzados. La distribución de Gtk2Hs también viene con varios +ejemplos interesantes.

+ + + + hunk ./docs/tutorial/Tutorial_Port/es-index.xhtml 26 + English Version hunk ./docs/tutorial/Tutorial_Port/es-index.xhtml 109 + +
  • Apéndice +
  • +
      +
    1. + Dibujando con Cairo: Empezando... +
    2. hunk ./docs/tutorial/Tutorial_Port/es-index.xhtml 117 - + + + hunk ./docs/tutorial/Tutorial_Port/es-index.xhtml 157 - Next + Siguiente hunk ./docs/tutorial/Tutorial_Port/index.xhtml 26 + Versión en español