| Versión 3.0 |
| 800x600 mínimo |
|
En esta lección: Dibujar líneas ----- El recuadro delimitador ----- Lápices ----- Brochas ----- Rectángulos y regiones ----- Trazados ----- Descargas ----- Otras secciones: Conceptos básicos ----- Programando en C ----- Programando en C++ ----- Programando Windows 9x. ----- Teoría electrónica ----- Circuitos electrónicos ----- Actividades adicionales ----- Hipervínculos ----- Contácteme: Dudas y comentarios ----- |
Gráficos en Windows 9x. IComo se dijo en el capítulo anterior, la interfaz de dispositivos gráficos (GDI por sus siglas en inglés) es el subsistema de Windows responsable de representar gráficos, incluyendo texto, tanto en el sistema de video como en la impresora. GDI consta de varios cientos de funciones, tipos de datos, macros y estructuras relacionadas. En forma general podemos mencionar que GDI está compuesta de: 1.- Funciones que obtienen (o crean) y liberan (o destruyen) un contexto de dispositivo. 2.- Funciones que obtienen información sobre el contexto de dispositivo. 3.- Funciones que dibujan algo. 4.- Funciones que establecen y obtienen atributos del contexto de dispositivo, y 5.- Funciones que trabajan con objetos GDI. En éste capítulo trataremos las primitivas GDI que sirven para dibujar líneas, curvas, áreas rellenas, mapas de bits y texto. Algunas de éstas funciones son fáciles de manejar y otras requieren más atención, sin embargo debo subrayar la importancia que tiene dominar el manejo del contexto de dispositivo, ésta es la clave para el éxito del manejo de gráficos en Windows 9x. Dibujar líneasDe igual manera y como se demostró en el artículo programando gráficos I el elemento básico para dibujar en un dispositivo de visualización es una función que sirve para representar un pixel, en Windows ésta función se llama SetPixel( ) y en teoría es posible dibujar cualquier cosa con ésta única función, sin embargo este enfoque no es eficiente por una parte, y por otra la función SetPixel( ) no es soportada por todos los dispositivos gráficos. En términos reales las primitivas gráficas están basadas en líneas y Windows puede dibujar líneas rectas, elípticas (o sea una línea curvada en la circunferencia de una elipse), y curvas Bézier. Son siete las funciones soportadas por Windows 95 para el dibujo de líneas: LineTo( ) (líneas rectas), Polyline( ) y PolylineTo( ) (una serie de líneas rectas conectadas), PolyPolyline( ) (múltiples polilíneas), Arc( ) (líneas elípticas), PolyBezier( ) y PolyBezierTo( ). Cinco atributos del contexto de dispositivo son los que determinan la manera en que se representarán las líneas: la posición activa del lápiz cuyo valor por defecto es la coordenada (0, 0) y afecta a las funciones LineTo( ), PolylineTo( ), PolyBezierTo( ) y MoveToEx( ); lápiz de valor por defecto igual a BLACK_PEN; modo de fondo de valor OPAQUE; color de fondo cuyo valor por defecto es blanco; y modo de dibujo de valor por defecto igual a R2_COPYPEN. Para una mayor información de los conceptos tratados en éste capítulo es recomendable consultar la información de referencia para Windows de 32 bits incluida en la mayoría de los compiladores capaces de trabajar en ésta plataforma. En la serie de artículos que tratan la programación en Windows 9x presentados en éste sitio web me concentro en los aspectos prácticos para obtener resultados en el menor tiempo posible. Para demostrar los conceptos tratados en éste capítulo utilizaremos el código básico presentado en la introducción a Windows 9x pero para evitar repetir código, en éste capítulo sólo presentaré la fracción de código correspondiente al procedimiento de ventana. El código fuente completo de todos los ejercicios lo puede descargar al final del artículo. Ya dijimos que el valor por defecto del atributo del contexto de dispositivo para la posición activa del lápiz es la coordenada (0, 0) que es la esquina superior izquierda del área cliente de la ventana del programa. Para dibujar una línea recta utilizamos la función LineTo() que traza una línea recta desde la posición activa definida en el contexto de dispositivo hasta (pero no incluyendo) el punto especificado en el segundo y tercer parámetros de la función. Veamos como se utiliza esta función:
En respuesta al mensaje WM_SIZE guardamos en dos variables llamadas cxCliente y cyCliente las dimensiones vertical y horizontal respectivamente del área cliente de la ventana. Esta información es de gran utilidad pues nos permite representar la información deseada independientemente del tamaño de la ventana del programa. Cuando el procedimiento de ventana recibe un mensaje WM_PAINT en primer lugar se obtiene un handle a un contexto de dispositivo mediante la función propia de WM_PAINT BeginPaint() y enseguida dibujamos una línea recta que parte de la posición activa del lápiz de coordenadas (0, 0) hasta, pero no incluyendo, la parte central del área cliente de la ventana. El contexto de dispositivo se destruye con la función EndPaint(). En el caso en que deseamos que nuestra línea se dibuje a partir de una posición diferente a la definida en el contexto de dispositivo necesitamos utilizar la función MoveToEx(hdc, xInicio, yInicio, &pt) a la que es necesario especificar un puntero a una estructura de tipo POINT que nos sirve para guardar las coordenadas del último punto activo, es decir, las coordenadas utilizadas por ejemplo, en el segundo y tercer parámetros de la función LineTo(). Experimente utilizando diferentes funciones LineTo() y cambiando el tamaño de la ventana como se muestra en el siguiente fragmento de código:
Poniendo en práctica el código anterior podemos verificar que la estructura de tipo POINT en efecto, almacena las coordenadas del último punto activo de tal manera que solo fué necesario especificar una vez el punto inicial y el resto de la figura se construye con sucesivas llamadas a la función LineTo(). Cuando necesitamos dibujar una cantidad grande de líneas podemos recurrir a la función Polyline() sin embargo ésta función no utiliza o cambia la posición activa, por lo tanto podemos definir el último parámetro de la función MoveToEx() a NULL como se demuestra a continuación:
En este ejemplo utilizamos una estructura de tipo POINT para almacenar 1000 coordenadas que en conjunto con la función Polyline() dibujan una onda seno en el área cliente de la ventana. Este código es equivalente a utilizar una función MoveToEx() seguida de 1000 llamadas a la función LineTo(). Lógicamente el trabajo es mucha más eficiente utilizando la función Polyline() ya que ésta función está implementada como parte del trabajo que desempeña el controlador de video. El recuadro delimitadorLas siguientes funciones GDI para dibujar líneas tienen en común el hecho de que se construyen en el interior de un rectángulo cuyas coordenadas especifican el área donde aparecerá la figura. Empezamos con la función Rectangle() que como su nombre lo indica, dibuja una figura rectangular cuyos lados son paralelos a los del área cliente, ésta función requiere de cinco parámetros, el primero es el handle a un contexto de dispositivo, el segundo y tercero especifican las coordenadas x e y de la esquina superior izquierda del rectángulo en tanto que los parámetros cuarto y quinto corresponden a la esquina inferior derecha del rectángulo:
Para demostrar que el rectángulo se dibuja en el interior de un recuadro delimitador el programa dibuja en primer lugar una línea recta en el centro del área cliente. Observe que el cuadro delimitador está completamente relleno, esto es, se utiliza la brocha blanca por defecto especificada en el contexto de dispositivo. Para que sea más evidente el efecto del cuadro delimitador puede Usted cambiar el color de fondo para el área cliente de la ventana especificando el siguiente valor dentro de WinMain():
De la misma manera que en el código anterior se dibujó un rectángulo, es posible dibujar una figura elíptica, simplemente cambiando el nombre de la función de Rectangle() a Ellipse(), los parámetros son los mismos. Por otro lado, si deseamos dibujar un rectángulo con las esquinas redondeadas podemos utilizar la función RoundRect() que utiliza además de los parámetros especificados para la función Rectangle() dos más que determinan el ancho y la altura de una pequeña elipse que Windows utiliza para redondear las esquinas del rectángulo:
Otras funciones que utilizan el cuadro delimitador son Arc(), Chord() y Pie() que dibujan figuras parciales, éstas tres funciones toman los mismos parámetros, nueve en total. Los primeros cinco son los mismos que los utilizados para la función Rectangle(), los parámetros seis y siete son para especificar las coordenadas de inicio de la figura y los parámetros ocho y nueve representan las coordenadas donde termina la figura parcial. La función Arc() dibuja dentro del cuadro delimitador un arco que inicia en el punto especificado por los parámetros seis y siete y termina en las coordenadas especificadas en los parámetros ocho y nueve como lo demuestra el siguiente código:
Para apreciar lo que hacen las funciones Chord() y Pie() simplemente sustituya en el código de arriba el nombre de la función Arc() por el de Chord() ó el de Pie() ya que utilizan los mismos parámetros. Pienso que hay cosas que se aprenden mejor experimentándolas así que lo invito a que haga Usted lo mismo. LápicesComo seguramente habrá notado, las líneas y figuras dibujadas hasta el momento, todas tienen la misma característica en cuanto a color, ancho y estilo de la línea, esto es así porque Windows utiliza para dibujar el lápiz seleccionado por defecto en el contexto de dispositivo que se llama BLACK_PEN. Este lápiz dibuja una línea sólida de color negro con un ancho de un pixel independientemente del modo de mapeado (de los modos de mapeado hablaré más adelante). BLACK_PEN es uno de los tres lápices que Windows tiene predefinidos, los otros dos son WHITE_PEN y NULL_PEN, éste último es un lápiz que no dibuja nada, fabuloso invento que aunque Usted no lo crea por el momento, sí tiene utilidad. Si bién, los lápices predefinidos en Windows son convenientes, en realidad si deseamos dibujar una línea con características diferentes tenemos que hechar mano de un lápiz lógico que no es otra cosa que un lápiz definido utilizando la función CreatePen() ó la función CreatePenIndirect(), éstas funciones devuelven un handle a un lápiz lógico. Necesitamos en primer lugar una variable de tipo HPEN para el handle de lápiz lógico, enseguida utilizamos la función CreatePen(iEstiloLapiz, iAncho, rgbColor) para obtener el handle de lápiz lógico. El primer parámetro especifica el estilo de línea que el lápiz utilizará para dibujar, el segundo parámetro es para el ancho, en pixeles de la línea y el tercer parámetro es para el color de la línea, valor obtenido generalmente utilizando la función RGB(). Una vez que contamos con el handle de lápiz lógico debemos seleccionarlo utilizando la función SelectObject(hdc, hLapiz). Después de ésto podemos utilizar nuestro lápiz para dibujar de la misma manera en que lo hemos hecho a lo largo de éste capítulo. Cuando terminemos de utilizar nuestro lápiz debemos borrarlo utilizando la función DeleteObject(hLapiz), todo el proceso lo demuestra el siguiente procedimiento de ventana
Como se mencionó, el primer parámetro de la función CreatePen() define el estilo de línea que utilizará el lápiz para dibujar, este valor puede ser uno de los siguientes tal y como se especifican en los archivos de cabecera de Windows:
Para los estilos PS_SOLID, PS_NULL y PS_INSIDEFRAME si en el segundo parámetro se especifica un valor de 0 entonces Windows utiliza un ancho de línea de 1 pixel. Si especifica un estilo de línea con puntos ó guiones y un ancho superior a 1 pixel entonces Windows utiliza un lápiz sólido, o sea que para apreciar el estilo de línea dibujado por el lápiz lógico utilice un ancho igual a 1. Nuevamente recomiendo experimentar con diferentes valores para los parámetros utilizados en la función CreatePen(). BrochasLos interiores de las figuras Rectangle, RoundRect, Ellipse, Chord, Pie, Polygon y PolyPolygon se rellenan con la brocha activa (también llamada patrón de puntos) seleccionada en el contexto de dispositivo. Windows tiene seis brochas predefinidas: WHITE_BRUSH, LTGRAY_BRUSH, GRAY_BRUSH, DKGRAY_BRUSH, BLACK_BRUSH y NULL_BRUSH también conocida como HOLLOW_BRUSH. El procedimiento para utilizar una brocha es similar al mostrado para los lápices, primero se obtiene un handle a una brocha, éste es un tipo de dato definido en los archivos de cabecera de Windows como HBRUSH. Para obtener el handle podemos utilizar la función GetStockObject(), luego seleccionamos la brocha utilizando la función SelectObject(). Una brocha es un mapa de bits de 8 por 8 pixeles que se repite horizontal y verticalmente para rellenar el área. Al igual que con los lápices, podemos crear brochas lógicas utilizando una de cuatro funciones, CreateSolidBrush(), CreateHatchBrush(), CreatePatternBrush() y CreateBrushIndirect(). en el siguiente procedimiento de ventana se demuestra el uso de la función CreateHatchBrush():
Se aprecia que el segundo parámetro de la función CreateHatchBrush() es HS_DIAGROSS, éste es uno de los seis estilos de brochas con marcas definidos en los archivos de cabecera de Windows, las brochas de estos estilos se utilizan normalmente para colorear el interior de gráficos de barras y cuando se dibuja en graficadores, los otros estilos son HS_HORIZONTAL, HS_VERTICAL, HS_FDIAGONAL, HS_BDIAGONAL y HS_CROSS. Experimente en el procedimiento de ventana de arriba con cada uno de los estilos de marca y vea los resultados. En términos generales, lo dicho para los lápices se aplica similarmente para las brochas, es buena idea, además, consultar la documentación referente a Windows que acompaña a su compilador en particular. Rectángulos y regionesUn rectángulo es una área en la ventana delimitada por las coordenadas especificadas en una estructura de tipo RECT. Una región es una área de la pantalla que es una combinación de figuras rectangulares, polígonos y elipses. Es importante no confundir una figura rectangular dibujada por ejemplo, con la función Rectangle(), con el concepto de rectángulo, éste simplemente es el área de una ventana que un programa invalida para actualizar el contenido de la misma. Las funciones FillRect(), FrameRect() e InvertRect() requieren un puntero a una estructura de tipo RECT, la estructura RECT tiene cuatro campos: left, right, top y bottom. En lenguaje C, podemos asignar valores a los miembros de la estructura utilizando el operador punto, por ejemplo: rectangulo.left = 50;, sin embargo, Windows tiene nueve funciones que nos permiten trabajar cómodamente con estructuras de tipo RECT, son las siguientes:
El siguiente procedimiento de ventana dibuja figuras rectangulares de tamaño y color aleatorio conforme se mueve el ratón por el área cliente de la ventana:
Por cada mensaje WM_MOUSEMOVE el programa establece coordenadas aleatorias y las almacena en una estructura de tipo RECT llamada rectangulo utilizando la función SetRect(), de ésta manera restringimos el dibujo dentro del área cliente de la ventana. También se demuestran las funciones CreateSolidBrush() y FillRect(). Consultando la documentación de Windows no le resultará difícil experimentar con las diferentes funciones relacionadas con el uso de la estructura de tipo RECT. De manera similar, podemos crear una región tanto para recortar como para dibujar. Creamos una región para recortar (es decir, restringir el dibujo a una área específica del área cliente de la ventana) seleccionando la región en el contexto de dispositivo, naturalmente las regiones son objetos GDI y como tal se debe borrar toda región creada llamando a la función DeleteObject(). El siguiente procedimiento de ventana crea una región elíptica y rellena la región con colores aleatorios conforme se mueve el ratón por encima del área cliente, observe que el color de la región cambia incluso si el cursor del ratón está fuera del área delimitada por la región.
TrazadosUn trazado es una colección de líneas rectas y curvas que se almacenan internamente en GDI. En referencia al procedimiento de ventana que sigue definimos un trazado llamando a la función BeginPath(), a partir de ésta función todas las líneas dibujadas se almacenan como un trazado pero aún no se muestran en el dispositivo. Generalmente un trazado consta de líneas conectadas, generadas por medio de funciones como LineTo() y PolylineTo(). Una vez que terminamos la definición del trazado indicamos el fin de éste llamando a la función EndPath(). Para desplegar en el dispositivo seleccionado la información almacenada internamente en GDI se puede llamar a una de las siguientes funciones: StrokePath(), FillPath(), StrokeAndFillPath(), PathToRegion() y SelectClipPath(). Cualquiera de éstas funciones al ser llamada elimina la información almacenada por el trazado después de realizado el trabajo. Todas las funciones excepto StrokePath() cierran cualquier trazado abierto utilizando una línea recta. La ventaja de utilizar trazados para rellenar y recortar estriba en el hecho de que no se está limitado a figuras geometricas poligonales. Es importante tener en cuenta que un trazado es una colección de definiciones de líneas y curvas, mientras que una región en términos generales, es una colección de líneas de barrido.
Al utilizar un trazado para dibujar un conjunto de líneas, Windows soporta el uso de un tipo de lápiz ampliado mediante la función ExtCreatePen(). Normalmente al llamar a la función StrokePath() se utiliza el lápiz seleccionado en el contexto de dispositivo para desplegar el conjunto de líneas almacenadas en GDI, sin embargo con la posibilidad de utilizar un estilo de lápiz ampliado queda clara la ventaja de utilizar el trazado para operaciones de dibujo/recorte. La función ExtCreatePen() tiene cinco parámetros, el primero puede ser cualquiera de los estilos de lápiz descritos en la función CreatePen(), además se puede combinar éstos estilos con PS_GEOMETRIC (en donde el segundo parámetro representa el ancho de línea en unidades lógicas) ó PS_COSMETIC en cuyo caso el segundo parámetro de la función ExtCreatePen() debe estar definido a 1. El tercer parámetro es un puntero a una estructura que define una brocha lógica que se utiliza para colorear el interior de los lápices de tipo PS_GEOMETRIC, ésta brocha puede ser incluso un mapa de bits. Al utilizar la función ExtCreatePen() para dibujar podemos también especificar la forma que tendrán los extremos de las líneas utilizando uno de los siguientes identificadores:
Similarmente podemos especificar un estilo definido para la unión de las diferentes líneas que componen el trazado:
Experimentano con el procedimiento de ventana de ejemplo y cambiando los diferentes estilos tanto para los extremos de las líneas como para sus uniones nos quedará claro cuál es la función de cada estilo aportado por la función ExtCreatePen(). Los parámetros cuarto y quinto de ésta función están por lo pronto reservados para futuras ampliaciones, por lo pronto utilizamos los valores de 0 y NULL respectivamente. Se deduce que al soportar el uso de la función StrokePath() Windows 9x. permite una amplia posibilidad de estilos de dibujo/recorte de líneas que en caso contrario contaríamos con uniones entre líneas de tipo plano, para demostrar ésta utilidad, el procedimiento de ventana anterior dibuja una línea sólida sencilla encima de la línea gruesa aportada por el trazado. DescargasEl código fuente de los ejemplos utilizados en éste capítulo, cada uno constituye un programa completo, está incluído en el archivo comprimido llamado Win03.zip (11.6 kb.), para descargarlo haga clic aquí. |