Capítulo 19 Funciones para el trazado de líneas

El GDI dispone de un repertorio de funciones para el trazado de líneas bastante completo

Función Tipo de línea
MoveToEx Actualiza la posición actual del cursor gráfico, opcionalmente obtiene la posición anterior.
LineTo Traza una línea desde la posición actual del cursor al punto indicado.
ArcTo Traza un arco de elipse.
PolylineTo Traza uno o más trazos de líneas rectas.
PolyBezierTo Traza una o más curvas Bézier.
AngleArc Traza un segmento de arco de circunferencia.
Arc Traza un arco de elipse.
Polyline Traza una serie de segmentos de recta que conectan los puntos de un array.
PolyBezier Traza una o más curvas Bézier.
GetArcDirection Devuelve la dirección de arco del DC actual.
SetArcDirection Cambia la dirección del ángulo para el trazado de arcos y rectángulos.
LineDDA Traza una línea, pero permite a una función de usuario decidir qué pixels se mostrarán.
LineDDAProc Función callback de la aplicación que procesa las coordenadas recibidas de LineDDA.
PolyDraw Traza una o varias series de líneas y curvas Bézier.
PolyPolyLine Traza una o varias series de segmentos de recta conectados.

La mayoría de éstas funciones no requieren mayor explicación, sobre todo las que trazan arcos y líneas rectas.

Trazado de arcos, función Arc

Para trazar un arco mediante la función Arc, se parte de una circunferencia inscrita en el rectángulo de borde. Los puntos de inicio y final del arco se obtienen de los cortes de los radios definidos por los puntos de radio de inicio y final:

Arc

La función ArcTo es idéntica, salvo que se traza una línea desde la posición actual del cursor gráfico hasta el punto de inicio del arco.

Curvas Bézier

Las curvas Bézier son una forma de definir curvas irregulares mediante métodos matemáticos. Para definir una curva Bézier sólo se necesitan cuatro puntos: los puntos de inicio y final, y dos puntos de control.

En el gráfico se representa una curva Bézier y las líneas auxiliares, que sólo se muestran como ayuda. El punto de inicio y el punto de control 1 refinen una recta que es tangente a la curva en el punto de inicio. La curva tiende a aproximarse al punto de control 1.

Análogamente, el punto de final y el punto de control 2 refinen otra recta, que es tangente a la curva en el punto de final. La curva también tiende a acercarse al punto de control 2.

Las ecuaciones que definen la curva no son demasiado complejas, pero escapan al objetivo de este curso, se estudiaran las curvas de Bézier con detalle en un artículo separado.

Curva Bézier

Funciones Poly<tipo>

Todas las funciones con el prefijo Poly trabajan de un modo parecido. Se basan en un array de puntos para definir un conjunto de líneas que se trazan una a continuación de otra.

Polyline o PolylineTo trazan un conjunto de segmentos rectos, PolyBezier y PolyBezierTo, un conjunto de curvas Bézier. PolyDraw, varios conjuntos de líneas y curvas Bézier, y PolyPolyline, varios conjuntos de líneas rectas.

Función LineDDA y funciones callback LineDDAProc

LineDDA nos permite personalizar el trazado de segmentos de líneas rectas. Mediante la definición de funciones propias del tipo LineDDAProc, podemos procesar cada punto de la línea y decidir cómo visualizarlo. Podemos cambiar el color, ignorar ciertos puntos, y en general, aplicar la modificación que queramos. Esto nos permite trazar líneas de varios colores o con distintas tramas, etc.

Veamos un ejemplo sencillo, definiremos una función LineDDAProc para trazar líneas que alternen 10 pixels rojos y 10 verdes.

struct DatosDDA1 {
   int cuenta;
   HDC hdc;
};
 
VOID CALLBACK FuncionDDA1(int X, int Y, LPARAM datos)
{
   struct DatosDDA1 *dato = (struct DatosDDA1 *)datos;
 
   // Función que pinta líneas con 10 pixels alternados rojos y verdes
   dato->cuenta++;
   if(dato->cuenta >= 20) dato->cuenta = 0; // Mantenemos cuenta entre 0 y 19
   if(dato->cuenta < 10) 
     SetPixel(dato->hdc, X, Y, RGB(0,255,0));
   else
     SetPixel(dato->hdc, X, Y, RGB(255,0,0));
}

Hemos definido una función callback para que muestre cada punto en función de los datos almacenados en una estructura DatosDDA1, diseñada por nosotros. En ella almacenamos un valor "cuenta" que nos ayuda a decidir el color de cada pixel, y un manipulador de DC para poder activar pixels, usando SetPixel, en la ventana:

...
   struct DatosDDA1 datos = {0, hDC};
   
   LineDDA(10,10, 240,180, FuncionDDA1, (LPARAM)&datos);
... 

Ahora podemos llamar a la función LineDDA, indicando los puntos de inicio y final de la línea, la función que usaremos para decidir el color de cada punto, y un puntero a la estructura de datos que esa función necesita.

Línea trazada con LineDDA

Ejemplo 17

Basándonos en el último programa de ejemplo, vamos a hacer otro que muestre cada una de éstas funciones y cómo se usan.

Añadiremos un menú para poder ver cada función por separado.

Nota: Algunas de las funciones del API usadas en el ejemplo no están disponibles en Windows 95 y Win32s, por ejemplo: PolyDraw, ArcTo y AngleArc. Esto no impide que el programa se pueda compilar y ejecutar, pero algunas funciones no funcionarán.


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 17 win017.zip 2004-01-18 3532 bytes 524

Comentarios de los usuarios (4)

JDC
2012-10-13 00:28:38

Hola estoy intentando dibujar sobre la ventana una linea recta desde el origen hasta la posicion donde uno hace click, en visual c++ 6.0 era supremamente sencillo solo habia que agregar la funcion On_WM_LButtonDown y escribir este codigo:

void CProyecto01::OnLButtonDown(UINT nFlags, CPoint point)

{

CClientDC dc(this);

dc.LineTo(point.x,point.y);

CDialog::OnLButtonDown(nFlags, point);

}

en cambio usando directamente la WIN API esta bastante complicado, he probado combinando varios ejemplos donde usan lineas y eventos del raton pero aun no consigo hacerlo, ojala me puedan orientar, para que vean como queda el programa final dejo el ejecutable

www.4shared.com/file/WrXeJ151/DibujarLineas.html

Steven R. Davidson
2012-10-13 03:07:22

Hola JDC,

Hablamos del ratón y de sus mensajes en el capítulo 33 ( http://winapi.conclase.net/curso/index.php?cap=033#MEN_CLIENTE ).

El código que presentas es casi igual a la versión usando directamente el API de MS-Windows. Esto sería, en el procedimiento de ventana de la aplicación (o ventana) principal:

case WM_LBUTTONDOWN:
{
  POINTS pt = MAKEPOINTS( lParam );
  HDC hdc = GetDC( hwnd );
    LineTo( hdc, pt.x, pt.y );
  ReleaseDC( hwnd, hdc );
}
return 0;

Básicamente, capturamos el DC del área del cliente de esta ventana (principal) para poder usar las funciones gráficas de la GDI. En este mensaje, 'lParam' contiene las coordenadas del cursor del ratón en el momento que se pulsó el botón izquierdo. Como obtuvimos el DC mediante una captura, debemos soltar tal DC para que otras partes de la aplicación funcionen correctamente.

Espero que esto te oriente.

Steven

JDC
2012-10-13 04:28:45

Si ese metodo ya lo conocia, no soy experto pero me parece que con ese metodo se captura la ventana actual, se hace el dibujo respectivo y despues se devuelve la nueva ventana, con lo cual se redibuja toda la ventana consumiendo muchos recursos, puse la comparacion del visual 6 porque ahi parece que solo se agregan los pixeles respectivos y no se tiene que redibujar toda la ventana, como dije no estoy seguro si eso sucede internamente en el programa.

Seria interesante poder usar BeginPaint para tener un codigo masomenos asi

hdc=BeginPaint(hwnd,&ps);
MoveToEx(hdc,punto1.x,punto1.y,NULL);
LineTo(hdc,punto2.x,punto2.y);              
EndPaint(hwnd, &ps);

pero parece que solo se puede usar en el mensaje WM_PAINT

por cierto gracias por tu rapida respuesta

Steven R. Davidson
2012-10-13 05:15:43

Hola JDC,

La verdad es que no consumimos tantos recursos al usar estas funciones. Esto es porque dibujamos directamente al DC, sin enviar un mensaje WM_PAINT. La desventaja es que tal dibujo no permanecerá si la aplicación tiene que volver a pintar el área del cliente de la ventana, ya que entonces se envía un mensaje WM_ERASEBKGND al igual que el WM_PAINT.

Ciertamente, usamos 'BeginPaint()/EndPaint()' exclusivamente al procesar el mensaje WM_PAINT. Esto es porque preparan el proceso de pintar el área inválida. Podríamos hacer algo parecido con tu ejemplo del ratón. Esto sería,

case WM_LBUTTONDOWN:
{
  punto2 = MAKEPOINTS( lParam );
  InvalidateRect( hwnd, NULL, TRUE );
}
break;
...
case WM_PAINT:
...
hdc = BeginPaint( hwnd, &ps );
  MoveToEx( hdc, punto1.x,punto1.y, NULL );
  LineTo( hdc, punto2.x,punto2.y );
EndPaint( hwnd, &ps );
break;

La función 'InvalidateRect()' forzaría la generación de un mensaje WM_PAINT, y en el ejemplo, también provocaría que se enviase el mensaje WM_ERASEBKGND.

Espero que esto te sirva.

Steven