SDK Multiplataforma en C logo

SDK Multiplataforma en C

Contextos 2D

❮ Anterior
Siguiente ❯

Funciones

DCtx*dctx_bitmap (...)
Image*dctx_image (...)
voiddraw_clear (...)
voiddraw_matrix (...)
voiddraw_matrix_cartesian (...)
voiddraw_antialias (...)

Los gráficos vectoriales se componen a base de primitivas básicas tales como líneas, círculos, texto, etc, utilizando el algoritmo del pintor (Figura 1): Las operaciones entrantes se superponen o solapan a las ya existentes. El resultado se va almacenando en un búfer intermedio conocido como canvas o surface. Esta superficie de dibujo forma parte de un objeto denominado contexto que también mantiene ciertos parámetros relacionados con el aspecto de las primitivas: Colores, atributos de línea, sistema de referencia, gradientes, etc.

Diferencia en el dibujo cuando cambiamos el orden de las primitivas.
Figura 1: El orden en que las primitivas de dibujo se envían al búfer del contexto influye en el resultado final.

Una de las ventajas de trabajar con formas paramétricas, es que pueden realizarse escalados de la imagen sin perdida de calidad (Figura 2). Esto es debido a que la conversión a píxeles, proceso denominado rasterización (Figura 3), se realiza en tiempo real y se ajusta constantemente al cambio de los vectores. En Imágenes bitmap, un aumento de tamaño tiene asociada una pérdida de calidad.

Diferencia entre gráficos vectoriales y bitmap al escalar.
Figura 2: Escalado vectorial y escalado bitmap.
Círculo teórico (perfecto) y su conversión a píxeles.
Figura 3: Rasterización de un círculo.

NAppGUI permite trabajar con dos tipos de contextos 2D (Figura 4).

Como podemos observar, el dibujo en sí se realiza de la misma manera, lo único que cambia es como hemos obtenido el objeto DCtx. Esto permite que podamos escribir rutinas gráficas genéricas sin preocuparnos sobre cual será el destino del resultado final. En Dibujando en una imagen tienes un ejemplo completo del uso de contextos. Las imágenes que acompañan el resto del capítulo han sido obtenidas de esta aplicación.

Debido a que no es necesario disponer de una ventana para dibujar, draw2d puede utilizarse en aplicaciones de consola para componer o editar imágenes sin necesidad de visualizarlas.

1. Sistemas de referencia

El origen de coordenadas del dibujo se sitúa en la esquina superior izquierda (Figura 5). Las X positivas se desplazan hacia la izquierda y las Y positivas hacia abajo. Las unidades se miden en píxeles (o puntos en Pantallas retina). Por ejemplo, el comando:

1
draw_circle(ctx, ekSKFILL, 300, 200, 100);

dibujará un círculo de 100 píxeles de radio cuyo centro se encuentra a 300 píxeles a la izquierda y 200 píxeles hacia abajo del origen. A este sistema inicial se le denomina identidad ya que todavía no ha sido manipulado, como veremos a continuación.

Dibujo de varias formas geométricas, imágenes y textos sin aplicar ninguna transformación.
Figura 5: Sistema de referencia identidad en contextos 2D.
Aunque la escala inicial esté en píxeles, debemos desterrar la idea de que estamos manipulando directamente píxeles al dibujar. Los contextos de dibujo utilizan coordenadas en punto flotante. Por ejemplo, dibujar una línea entre los puntos (0.23, 1.432) y (-45.29, 12.6756) es perfectamente válido. Las transformaciones y el antialiasing pueden alterar ligeramente la posición o el grosor de ciertas líneas. Tampoco debemos esperar resultados "idénticos" a nivel de píxel al migrar las aplicaciones a diferentes plataformas, ya que cada sistema utiliza sus propios algoritmos de rasterización. Debemos pensar que estamos dibujando en el plano real. Para manipular directamente los píxeles de una imagen, consulta Pixel Buffer.

Este sistema de referencia inicial se puede manipular mediante Transformaciones 2D. Las transformaciones más comunes en gráficos son: Traslaciones (Listado 4) (Figura 6), Rotaciones (Listado 5) (Figura 7) y Escalados (Listado 6) (Figura 8).

  • draw_matrix cambiará el sistema de referencia del contexto.
  • Listado 4: Traslación del origen de coordenadas 100 unidades en ambas direcciones.
    1
    2
    3
    4
    
    T2Df t2d;
    t2d_movef(&t2d, kT2D_IDENTITYf, 100, 100);
    draw_matrix(ctx, &t2d);
    i_draw(...);
    
    Dibujo en el que se aplicado una traslación de 100, 100 píxeles.
    Figura 6: Traslación (Listado 4).
    Listado 5: Rotación del origen de coordenadas 15 grados.
    1
    2
    3
    4
    
    T2Df t2d;
    t2d_rotatef(&t2d, kT2D_IDENTITYf, 15 * kBMATH_DEG2RADf);
    draw_matrix(ctx, &t2d);
    i_draw(...);
    
    Dibujo en el que se aplicado una rotación de 15 grados.
    Figura 7: Rotación (Listado 5).
    Listado 6: Escalado, reducción del tamaño a la mitad.
    1
    2
    3
    4
    
    T2Df t2d;
    t2d_scalef(&t2d, kT2D_IDENTITYf, .5f, .5f);
    draw_matrix(ctx, &t2d);
    i_draw(...);
    
    Dibujo en el que se aplicado un escalado del 50%.
    Figura 8: Escalado (Listado 6).

Las transformaciones se pueden acumular, pero debemos tener presente que no son operaciones conmutativas, sino que el orden en que se aplican influirá en el resultado final. Por ejemplo en (Listado 7) (Figura 9) observamos que el dibujo se ha desplazado (100, 50) píxeles, en lugar de los (200, 100), debido a que la traslación está afectada por el escalado previo. Más detalles en Composición de transformaciones.

Listado 7: Composición de transformaciones.
1
2
3
4
5
6
T2Df t2d;
t2d_scalef(&t2d, kT2D_IDENTITYf, .5f, .5f);
t2d_movef(&t2d, &t2d, 200, 100);
t2d_rotatef(&t2d, &t2d, 15 * kBMATH_DEG2RADf);
draw_matrix(ctx, &t2d);
i_draw(...);
Dibujo en el que se han aplicado varias transformaciones.
Figura 9: Composición de transformaciones (Listado 7).

1.1. Sistemas cartesianos

Existe una dicotomía al dibujar en 2D: Por un lado, tradicionalmente los sistemas de escritorio y las imágenes digitales sitúan el origen de coordenadas en la esquina superior izquierda con el eje Y creciendo hacia abajo (Figura 10). Por otro lado, los sistemas cartesianos utilizados en geometría lo sitúan en la esquina inferior izquierda, con Y creciendo hacia arriba. Esto genera un dilema sobre si un sistema es mejor que otro.

Sistema de referencia del monitor y sistema de referencia cartesiano.
Figura 10: Sistema 2D en monitores (izquierda) y cartesiano (derecha).

La respuesta es claramente no. Incluso, en el mismo dibujo, es posible que necesitemos combinar ambos en función del elemento que estemos tratando. Para textos e imágenes, el sistema de pantalla es más intuitivo ya que reproduce el papel o lienzo del mundo físico. Para funciones matemáticas, gráficos de barras, planos y otros aspectos relacionados con el mundo técnico, el cartesiano resulta mucho más cómodo y natural.


2. Antialiasing

Dada la naturaleza discreta de los monitores e imágenes digitales, se produce un efecto de escalonado (dientes de sierra) al transformar las primitivas vectoriales a píxeles (Figura 12). Este efecto se hace menos perceptible a medida que aumenta la resolución de la imagen, pero aún así el "pixelado" sigue estando patente. El antialiasing, es una técnica que reduce este efecto de escalón variando ligeramente los colores de los píxeles en el entorno cercano a las líneas y contornos (Figura 13). Con esto se consigue engañar al ojo humano difuminando los bordes y generando imágenes de mayor calidad visual. Como contrapartida tenemos el coste en el rendimiento de aplicarlo, aunque hace años que los cálculos relativos al antialiasing se realizan directamente en hardware (Figura 14), por lo que el impacto es mínimo.

  • draw_antialias permite activar o desactivar los cálculos del antialiasing.
  • Ampliación de una imagen donde se aprecian los píxeles de los contornos.
    Figura 12: Antialiasing desactivado.
    Ampliación de una imagen con el antialising activado.
    Figura 13: Antialiasing activado.
    Captura de una tarjeta gráfica Orchid Fahrenheit 1280.
    Figura 14: Orchid Fahrenheit 1280 (1992). Una de las primeras tarjetas que incorporaban aceleración gráfica 2d.

3. Pantallas retina

Al final del 2014 Apple presentó su nueva línea iMac con Retina Display de alta resolución (5120x2880). Normalmente, estos monitores trabajan en modo escalado (2560x1440) lo que permite tener píxeles de doble densidad (Figura 15). Apple diferencia entre puntos en pantalla, que son los que realmente manipula la aplicación y los píxeles físicos. Por lo tanto, nuestra ventana de 600x400 realmente tendrá 1200x800 píxeles en ordenadores Retina, aunque la aplicación seguirá "viendo" solo 600x400 puntos. El sistema operativo realiza la conversión de forma transparente. De hecho, no tenemos que hacer nada para adaptar nuestro código, ya que funcionará de la misma forma tanto en iMac normales como en los equipados con monitores Retina.

Diferencia entre píxeles normales y píxeles de doble densidad.
Figura 15: Píxeles de doble densidad en Retina Display (derecha).

Esta doble densidad la aprovechará el rasterizador para generar imágenes de mayor calidad al disponer de más píxeles en el mismo área de pantalla. En (Figura 16) y (Figura 15) vemos la calidad extra que aportan estos modelos.

Ampliación de una imagen con el antialising activado.
Figura 16: Monitor normal (con antialiasing).
Ampliación de una imagen con el antialising activado en Retina Display de Apple.
Figura 17: Retina Display (con antialiasing).

dctx_bitmap ()

Crea un contexto en memoria, con el fin de generar una imagen.

DCtx*
dctx_bitmap(const uint32_t width,
            const uint32_t height,
            const pixformat_t format);
width

Ancho de la imagen en píxeles.

height

Alto de la imagen en píxeles.

format

Formato de píxel de la imagen generada.

Retorna

El contexto de dibujo.

Observaciones

Al terminar de dibujar, debemos llamar a dctx_image para obtener la imagen.


dctx_image ()

Obtiene la imagen resultado tras dibujar en el contexto creado con dctx_bitmap.

Image*
dctx_image(DCtx **ctx);
ctx

El contexto, que será destruido tras generar la imagen.

Retorna

La imagen.


draw_clear ()

Borra todo el área del contexto, utilizando un color plano.

void
draw_clear(DCtx *ctx,
           const color_t color);
ctx

Contexto de dibujo.

color

Color de fondo.


draw_matrix ()

Establece el sistema de referencia del contexto (transformación afín).

void
draw_matrix(DCtx *ctx,
            const T2Df *t2d);
ctx

Contexto de dibujo.

t2d

Transformación.

Observaciones

El origen de coordenadas está en la esquina superior izquierda. El eje Y aumenta hacia abajo.


draw_matrix_cartesian ()

Establece el sistema de referencia en coordenadas cartesianas.

void
draw_matrix_cartesian(DCtx *ctx,
                      const T2Df *t2d);
ctx

Contexto de dibujo.

t2d

Transformación.

Observaciones

El origen de coordenadas está en la esquina inferior izquierda. El eje Y aumenta hacia arriba. Ver Sistemas cartesianos.


draw_antialias ()

Activa o desactiva el antialiasing.

void
draw_antialias(DCtx *ctx,
               const bool_t on);
ctx

Contexto de dibujo.

on

TRUE activo, FALSE inactivo.

Observaciones

El antialias puede cambiar en cada primitiva. No es necesario establecer una política para todo el dibujo. Ver Antialiasing.

❮ Anterior
Siguiente ❯