[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
-
+
+
+
+
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. +
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:
+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:
+Aquí hay otro ejemplo de dibujo, en este caso obtenido del +Tutorial de Cairo
+É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
.
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 + +