Escolar Documentos
Profissional Documentos
Cultura Documentos
PAINT ME A TRIANGLE
TUTOR:
WILSON SARMIENTO
INGENIERÍA MULTIMEDIA
BOGOTÁ D.C.
29-Nov-2017
1. Introducción
Como muestra del progreso realizado a lo largo del semestre, así como de los múltiples
conocimientos adquiridos en el área de la computación gráfica, al menos en lo que se podría
considerar su parte “básica” se propuso realizar un proyecto final en código que implementara en
lo posible los temas más relevantes vistos durante el curso si no es por decir todos. A manera de
resumen corto, la propuesta solicitaba un programa en el cual, al hacer click en la pantalla 3 veces
se pudiera pintar un triángulo definido por esos 3 puntos. El triángulo debía poder ser cambiado de
color y adicionalmente debía poder ser rellenado con una imagen externa. Del mismo modo, una
vez tenido el triángulo, haciendo click 2 veces más dentro del mismo (por fuera no debía pasar nada)
se debía de poder pintar una línea entre esos dos puntos, cuyo punto medio no solo debía ser
resaltado, sino que, además, se le debían calcular sus perfiles de color tanto en el modelo RGB como
en el HSV.
Paint Me a Triangle surgió entonces como la respuesta a esta propuesta comprendiendo dentro de
sí la implementación y visualización de múltiples tópicos de la computación grafica con el fin de
poder llegar a dar solución al resultado final que se buscaba. Cumpliendo el objetivo de demostrar
lo que se había aprendido a lo largo del curso, Paint Me a Triangle cubrió en forma los siguientes
temas de la materia dentro de su estructuración: Manejo de puertos de vista y coordenadas de
puerto de vista/mundo, el algoritmo de Bressenham para graficación de líneas rectas, los algoritmos
de relleno tipo scan line o por línea de rastreo, los algoritmos para pruebas dentro fuera, los
algoritmos para lectura de imágenes, los algoritmos de conversión de modelos de color, los
algoritmos de lectura de color para un pixel especifico y finalmente pero no menos importante los
algoritmos de manejo de estructuras de datos. Este último, cumpliendo el único fin de organizar la
información gráfica pertinente (aunque bien se sabe que este aspecto no hace parte como tal del
área de la computación gráfica, es importante aclarar que sin él no hubiera sido posible realizar el
programa.
Por consiguiente, en el presente informe se procederá a dar registro, así como explicar y relatar
que planteamientos, procedimientos y dificultades se llegaron a presentar a la hora de hacer la
esquematización y posteriormente la implementación del programa Paint Me a Triangle tal que
este cumpliese los requisitos que solicitaba la propuesta de proyecto final y sirviera como solución
a la misma.
2. Metodología, contratiempos e implementación
Para dar solución a esto, y aplicando el primer concepto teórico de computación grafica en
el programa, se procedido a construir e implementar el algoritmo de cambio de
coordenadas de puerto de vista a ventana. Se planteó la necesidad de este algoritmo ya que
su funcionalidad permite tomar una coordenada (x, y) cualquiera dentro del puerto de vista
(tenga este el tamaño que tenga y este donde este) y transformarla de tal forma que toda
la pantalla o ventana haga parte del mismo puerto de vista mas solo se grafiquen cosas
dentro de este. Esta lógica permite al usuario dar click en cualquier parte de la pantalla y
que estos entren directamente al puerto de vista dando en algunos casos la sensación de
que se está graficando “por detrás” (ver Figura 1.). pero siempre trabajando en el puerto
de vista como tal dando la escala correcta de posición de los clicks en la pantalla.
Figura No.1 Triangulo graficado con clicks que han pasado por transformación de coordenadas de puerto de vista a
ventana. Los dos primeros clicks han sido dados dentro del puerto de vista (zona negra) mientras que el tercero ha sido
dado por fuera de este, lo cual es leído mas no graficado, dando la sensación de que el triángulo se graficó por “detrás”
Esto permitió leer los clicks directamente sobre la pantalla sin restricción alguna de
ubicación o tamaño del puerto de vista más allá de que solo los clicks que se hicieran por
dentro del puerto serian graficados (aunque los que se hicieran por fuera si existirían como
tal mas no se verían). Por lo que al hacer cada click, estos serían guardados en la primera
lista estructurada que se estaba manejando: la lista de vértices. Guardando así el número
de clicks con un máximo de 3 (por los vértices de un triángulo) y las coordenadas (x, y) de
cada click transformadas a la escala del puerto de vista actual para así trabajar las
graficaciones posteriores dentro del mismo.
Esta implementación del algoritmo de Bressenham dentro del algoritmo de rastreo de línea
que se diseñó permitiría entonces conocer los valores enteros exactos de cruce en “x” de la
línea de rastreo con las diferentes aristas (trabajando estas últimas como si fuesen líneas
graficadas con el algoritmo de Bressenham). Tal que luego de tener tales valores, estos se
organizaban de menor a mayor para luego proceder a pintar todos los puntos de la línea de
rastreo que compartieran la coordenada en “y” actual de esta de arriba hacia abajo y las
coordenadas en “x” de cruce desde la menor (la más hacia la izquierda) hasta la mayor (la
más hacia la derecha).
Finalmente, esta lógica se corroboro graficando ahora si netamente con el algoritmo de
Bressenham para trazado de líneas rectas, el cual se construyó previamente durante el
curso de introducción a la computación gráfica por medio de análisis de punto medio
realizados a papel y lápiz. Tal que, con los resultados visibles que este algoritmo proveía se
graficaron las líneas que unían a los diferentes vértices del triángulo en otro color, bajo la
hipótesis de que, si el planteamiento realizado en el algoritmo de línea de rastreo era
correcto, estas líneas debían superponerse a la perfección con el relleno que se encontraba
por debajo. Lo cual, si sucedió, (ver figura No.2)
Figura No.2 Triangulo coloreado con el algoritmo de relleno por línea de rastreo con líneas rectas uniendo sus vértices
graficadas encima y con Bressenham. La forma de corroborar que la lógica había sido hecha correctamente fue que al
graficar líneas rectas con el algoritmo de Bressenham que uniesen a los vértices estas debían de encajar perfecto con el
relleno que yacía por debajo el cual había usado ese mismo algoritmo para calcular los cortes en “x”
2.3. Primer contratiempo ¿Qué hacer cuando nos falta una función?
Implementación de función de lectura del color de un pixel
Bien pudo ser por ignorancia propia o por algún fallo en el llamado de la función (más la
primera que la segunda) pero la función que en un principio se pensaba utilizar para la
lectura del color de un pixel en específico dentro de la pantalla no presentó los resultados
esperados. Bien era una función necesaria para poder hallar los valores de color del punto
medio de la línea que se pretendía pintar dentro del triángulo como lo solicitaba la
propuesta, bien era una función que proveía por defecto OpenGl (por lo que técnicamente
no debía presentar fallos), pero sin embargo los presento. A manera de resumen, ReadPixels
(el nombre de la función), solo calculaba para todos los pixeles en la pantalla un color
unánime siendo este el del fondo que se le había asignado por defecto al iniciar el programa
más ningún otro color. Por lo que surgió la necesidad de generar una función “propia” que
permitiese obtener correctamente el color actual del pixel que se le solicitara si se deseaba
continuar con el proyecto.
Se pensó entonces que al diseño que se había utilizado en el algoritmo de relleno por línea
de rastreo se le podía implementar un factor adicional que no cambiaría en nada su función
de rellenar triángulos por medio de líneas rectas, más si colaboraría con el problema del
cómo saber el color de un pixel. Se consideró que realmente tal color de píxel solo se
necesitaba conocer si este se encontraba dentro del triángulo, pues por fuera realmente no
se graficaría nada (la línea por fuera del triángulo no sería leída) por lo que afirmo que si el
algoritmo de relleno por línea de rastreo permitía pintar cada pixel del triángulo (es decir
dentro de este) con un color en particular (el que se le asignara) fuese este uno general para
todos (monocromático) o uno específico por pixel (como en el caso de la imagen o en un
caso de degrade de colores) entonces debía poderse guardar justo antes de ser pintado el
pixel, tanto su color en el modelo RGB así como su posición (x,y) en coordenadas de puerto
de vista.
Aquello concluyo en la generación de una nueva lista enlazada la cual guardaría toda la
información correspondiente de que pixeles en la pantalla (y cuantos) eran los que
conformaban al triangulo y que color tenia cada uno en sus valores R, G & B. Como
complemento de lo anterior, se optó por una lista enlazada en vez de por ejemplo una
matriz de datos pues se consideró que al no saber exactamente cuántos pixeles tendría el
triángulo a pintarse ( depende de los clicks, puede ser grande o pequeño) entonces la lista
enlazada generaría un nodo por pixel consumiendo exactamente la cantidad de memoria
que se necesitara para solo los pixeles del triángulo (por fuera, nuevamente, no era
relevante saber el color) haciendo optimo el algoritmo más allá de por ejemplo haber
declarado una matriz muy grande a la cual bien le podrían sobrar o faltar espacios de
información .
Con la lista terminada, el problema de la lectura del color del punto medio de la línea recta
se resumía a comparar la posiciona actual en x & y de ese punto con las posiciones de los
pixeles que conformaban al triangulo dentro de la lista de “colores” y buscar la respectiva
correspondencia. Lo que permitía conocer sin problema alguno los perfiles en RGB y HSV de
ese pixel en particular. Obviamente, en el momento en el que el usuario deseara trabajar
con otro triangulo de dimensiones y colores diferentes se hacían los respectivos llamados a
las funciones de liberación espacio de memoria, borrando toda la lista de colores y dejando
campo para nuevos triángulos con colores diferentes en posiciones diferentes.
Para la siguiente parte de la construcción del código: la lectura de la línea recta dentro del
triángulo, se “copio” la lógica de listas que se había diseñado previamente para la lectura
de los vértices del triángulo (de hacer click 3 veces y guardar los 3 puntos). Salvo que esta
vez, dos factores harían un valor agregado adicional a ese diseño. Número uno, la limitante
esta vez fue de 2 vértices (para una línea) y no 3 (para un triángulo) y número dos, se incluyó
la necesidad y presencia de un algoritmo que permitiese saber si aquellos clicks se
encontraban por dentro o por fuera del triángulo. Es decir, un algoritmo de pruebas dentro
fuera.
Se consideró que la lógica de las pruebas dentro fuera tipo par/impar sería la más adecuada
de trabajar debido a la facilidad de información que el algoritmo de relleno por línea de
rastreo proveía con sus múltiples listas (información de color por pixel, información de los
cruces con el eje x para una posición particular en y, información de los vértices en orden
de arriba abajo y de izquierda a derecha, etc.). Pues con toda esta información a la mano
resultaba sencillo hacer comparaciones y contarlas para deducir sencillamente si el número
de estas era un numero par o impar.
Básicamente, se esquematizo la idea de que al hacer click en cualquier punto (fuera dentro
o fuera del triángulo), se tomarían las coordenadas en (x, y) de este punto y se recorrería
horizontalmente desde allí hasta la coordenada en “x” del vértice más externo, contando la
cantidad de cruces teóricos en “x” que este recorrido presentaría al toparse con las aristas
del triángulo para una coordenada “y” constante. Donde, si el número de cruces
comprendía un numero par, entonces significaría que la línea había entrado al triangulo y
luego salido de este, por lo que el punto se encontraría fuera del mismo y por lo tanto no
se pintaba. Mientras que, si el número de cruces era impar, significaba que la línea había
entrado, pero no había salido, y por lo tanto el punto se encontraría dentro del triángulo y
por tanto se pintaría. (existía adicionalmente el caso donde el número de cortes fuese cero
y significaba que sencillamente el punto jamás toco al triangulo, es decir estaba por fuera y
además lejos de este).
Aplicando esta lógica se logró configurar adecuadamente el trazado de una línea por medio
de clicks (dos clicks y luego un bloqueo) que estuvieran por dentro del triángulo para luego
poder, haciendo uso de la ecuación paramétrica, tomar al punto inicial como el primer click
y respectivamente al punto final como el segundo click, y calcularles su punto medio. Donde
tales coordenadas, eran automáticamente guardadas y comparadas en la lista de colores
para conocer el perfil del color de ese punto. Graficando posteriormente una línea recta con
Bressenham que uniera los clicks y resaltando el punto medio en cuestión.
Aunque bien viene siendo cierto que OpenGl, en su forma básica, trabaja con el modelo de
color RGB y por tanto obtener y trabajar con esta información es relativamente “Sencillo”,
el hecho de que la propuesta solicitara un perfil en el modelo de color HSV complicaba un
poco las cosas. Sin embargo, y tal vez de forma obvia, la utilización del algoritmo de
conversión de modelo RGB a HSV se presentaba como la solución al problema. Pero no todo
podía ser tan fácil, pues se encontró un aspecto particular en este algoritmo que, aunque
se logró corregir, inicialmente nunca se tuvo en cuenta y por lo tanto generaba errores. El
algoritmo de conversión de RGB a HSV funciona, en su forma más básica, tomando el
máximo de los valores entre rojo, azul y verde y calculando con estos por medio de algunas
conversiones matemáticas específicas, las equivalencias en valor (V) y saturación (S) entre
0 y 1 (igual que funciona la escala en RGB). Hasta allí todo bien. La parte particular venia
cuando se trataba de calcular el tono actual (H) del color, pues este ya no funcionaba de 0
a 1 como la saturación (S) o el valor (V) sino que funcionaba en una escala de grados de 0 a
360.
Figura No.3 Modelo de color HSV. Nótese como el Hue (tono) arranca en
rojo para 0 grados y da la vuelta completa hacia la izquierda volviendo a
llegar a rojo luego de haber pasado el ultimo color siendo este magenta.
Figura tomada de:
https://upload.wikimedia.org/wikipedia/commons/thumb/6/6c/Cono
_de_la_coloración_HSV.png/250px-Cono_de_la_coloración_HSV.png
El problema radicaba en que, en el momento en que, para un tono rojo máximo, el valor en
azul fuese mayor que el valor en verde, matemáticamente se interpretaba como que el
circulo de colores había logrado dar una vuelta completa y, por tanto, los grados se
reiniciaban a arrancar desde 0 nuevamente. Sin embargo, en su comportamiento teórico
esto no sucedía, pues la vuelta completa se realiza al completar el ciclo de color desde rojo
hasta nuevamente rojo (según el modelo HSV, ver Figura No.3) por lo que, para esta
condición particular, la solución que se pensó fue la de restarle el excedente en grados para
así mantener la equivalencia teórica. Cabe aclarar que, aunque se investigó sobre este
algoritmo con anterioridad para poder aplicarlo en el proyecto presente, tal particularidad
jamás se mencionó por ningún lado, por lo que adicional a la implementación del algoritmo,
el ajuste de ese desperfecto fue bastante por cuenta propia por medio de varias pruebas de
escritorio y comprobándolo con algoritmos reales en páginas de conversión de modelos de
color. (la gracia no era copiar algo ya hecho sino diseñar uno propio y entender lo que se
estaba haciendo).
Para la graficación de la imagen dentro del triángulo se trabajó con un formato particular
de imágenes poco usual pero bastante útil si se buscan resultados rápidos mas no
necesariamente óptimos, las imágenes tipo PNM o Portable AnyMap.
La espada de doble filo que es el formato PNM el cual softwares libres de graficación en 2D
como GIMP pueden ofrecer en encriptado tipo ASCII (no en formato crudo que es diferente)
se encarga de guardar toda la información del color RGB en la escala de 0 a 255 por pixel en
un archivo de texto que podrá ser leído fácilmente por una máquina. Sin embargo, el hecho
de que contengan absolutamente toda la información de todos los pixeles de una imagen
cualquiera hacen de este tipo de archivos unos bastante pesados de leer si se está
trabajando con imágenes grandes (que poseen muchos más pixeles) por, evidentemente, la
cantidad de información que tienen. Del mismo modo, la organización de esta información
en RGB pixel por pixel se vuelve mucho más complicada de separar si se tiene en cuenta que
esta va de largo y sin interrupciones (ver Figura No.4).
Figura No.4 Imagen usada en el programa Paint Me a Triangle en formato PNM. La imagen fue transformada a este
formato usando el software libre GIMP. Nótese como posee un header de identificación (P3#) y como los primeros datos
numéricos corresponden al tamaño de la imagen (para el caso 600 x 600) para luego ser seguido por los valores de color de
0 a 255 de cada pixel. Nótese lo pequeña que es la barra de desplazamiento lateral, pues se trata de mucha información.
Sin embargo, el uso de estas imágenes se implementó gracias las librerías de lectura de las
mismas que el Dr. Wilson Sarmiento facilitó para poder realizar el programa con éxito (al
cual se le da el respectivo crédito y autoría de las mismas. Es decir que toda la parte de
lectura de la imagen en formato PNM, así como su posterior guardado y obtención del color
NO es implementación propia sino utilizada con previa autorización del autor y para uso
educativo solamente). Aclarado esto, se prosigue con la explicación.
Aunque ya se recalcó que estas librerías de lectura y sus funciones no son de autoría propia
sino externas, si se logró comprender su funcionamiento básico, pues la estrategia que el
Dr. Sarmiento ideó para la lectura de la información de la imagen era una bastante
coherente y lógica realmente. En palabras simples, se recorría toda la imagen en ancho y
largo y se iban guardando todos los datos del formato de texto en una matriz triple cuyas
primeras dos componentes referenciarían las pociones (x, y) del pixel de la imagen más su
tercera componente referenciaría el color del mismo, contando esta con 3 sub espacios para
así guardar los valores en RGB en la escala de 0 a 255 de ese pixel en específico. Resulta
curioso que, si esta lógica se compara con la lógica utilizada en Paint Me a Triangle, ese si
de autoría propia, para conocer los colores de un pixel particular dentro del triángulo, la
idea es casi la misma. Recorrer dentro de unos límites definidos una serie de píxeles y para
cada uno ir guardando su pareja ordenada de posición, así como su color. Una lógica muy
útil y funcional, cabe resaltar, para que al final solo sea indicarle al algoritmo de relleno que
valores de color deberá usar y en que posiciones los deberá usar.
Sin embargo, como no podría faltar, en este punto de la lectura y graficación de la imagen
se llegó a presentar un último contratiempo adicional que generaba que, aunque se tuviese
ya leída la imagen, esta no pudiese ser graficada en la pantalla. Y es que incluso con la
información de los colores de la imagen por pixel almacenada en una matriz de fácil acceso,
se debía tener en cuenta que, estos pixeles correspondían como tal a los pixeles de la
imagen y no a los del triángulo. Una afirmación obvia, pero de vital importancia a la hora de
graficar la imagen, pues la información de los colores de los pixeles de la misma se pasaba
de forma directa (es decir en escala uno a uno) el algoritmo de relleno por línea de rastreo
presentaría errores al quedarse sin información a graficar si el triángulo resultaba ser más
grande que la imagen. Por lo que para solucionar este inconveniente se planteó y luego
procedió a realizar una transformación de coordenadas.
Figura No.5 ejemplificación de la lógica de cambio de coordenadas de ventana a puerto de vista para graficar la imagen
dentro del triángulo. Si se toman los vértices más externos del triángulo y con él se arma un “rectángulo” o bounding box,
entonces partiendo de ese como ventana se podrán realizar transformaciones para saber qué punto del triángulo
corresponde a que pixel de la imagen siendo el puerto vista y así conocer el color correcto a graficar.
2.7. Detalles adicionales: Funciones de Reset e interfaz gráfica de colores
3. Resultados:
Figura No.2 Triangulo pintado en degrade luego de haber hecho click 3 veces. Se resalta la parte
derecha de la interfaz donde aún no se modifican los valores pues solo tomaran al punto medio de
la línea dentro del triángulo.
Figura No.3 Triangulo pintado en degrade con línea dentro de él. Se resalta la parte derecha de la
interfaz donde se pueden ver los valores RGB, así como HSV del punto medio entre los dos puntos
de la línea que se tomaron con dos clicks.
Figura No.4 Triangulo pintado con imagen y línea dentro de él. Los valores RGB y HSV del punto
medio corresponden al brillo azul que se encuentra en la cara en la imagen
Figura No.5 Triangulo pintado con imagen y línea dentro de él. Los valores RGB y HSV del punto
medio corresponden al brillo semi rosado del casco en la imagen
Figura No.6 Triangulo pintado en degrade con línea horizontal dentro de él.
Figura No.7 Triangulo pintado con un solo color con línea dentro de él.
Figura No.8 Triangulo pintado en degradé con línea sin completar dentro de él. Se resalta como
el punto rojo al encontrarse por fuera del triángulo no es leído para realizar la línea y por tanto
esta aún no se gráfica.
4. Conclusiones:
Del proyecto final realizado junto con todo lo que conllevó esquematizarlo, implementarlo
y testearlo para verlo funcionado se pueden resaltar las siguientes conclusiones al
respecto:
Una forma bastante útil de poder saber el color de un pixel particular en un área
definida dentro de la pantalla (como lo es por ejemplo los colores de una imagen o
el color actual de un pixel dentro de un triángulo coloreado para el caso) en
ausencia de una función que realice este trabajo, será el recorrer las dimensiones
del área en una lista o matriz triple, la cual guarde en sus primer y segundo campo
las coordenadas x & y del pixel y en su tercer campo los valores del color del pixel
en cuestión (donde sí se está trabajando en el modelo RGB, que usualmente es así,
este tercer campo deberá poseer 3 subcampos mas). Esta lógica puede resultar
bastante optima si el área trabajada es bastante más pequeña en relación con la
pantalla (como lo era el caso de los triángulos) pues no se necesitará guardar los
colores de toda la pantalla sino solo de esa área pequeña en cuestión.
Adicionalmente si se hace uso de una lista enlazada, se podrá utilizar exactamente
la cantidad de memoria que se necesite en proporcionalidad directa con el número
de pixeles de la figura, lo cual es bastante óptimo. Cabe aclarar que este método es
consistente únicamente si al dejar de utilizarlo se libera la memoria utilizada para
guardar la información de posición y color de los pixeles.
Las imágenes en formato PNM se presentan como una espada de doble filo a la hora
de trabajar con imágenes de una forma dígase así sencillas en comparación con el
uso de otros formatos. Por el lado positivo, si su formateado de datos se encuentra
en ASCII entonces se podrá acceder casi que de forma libre y directa a la
información del color en RGB para cada pixel sin ningún tipo de conversión adicional
ni proceso externo más allá de organizar la información recibida. Por lo que
básicamente, al utilizar softwares que trabajen con este tipo de imágenes como
GIMP es posible convertir cualquier imagen que se desee a este formato y cargar su
información de color para luego graficarla. Sin embargo, por el lado negativo, el
proceso de organizar la información es complicado de realizar por la forma en que
esta esta presentada (toda de largo por lo que es difícil distinguir entre el fin de un
pixel y el comienzo de otro) sin contar que a mayor tamaño tenga la imagen, mayor
información se trabajará y por tanto resultara volviéndose un proceso poco optimo
en el que la maquina deberá cargar cantidades absurdas de información para
imágenes grandes y por tanto se arriesga a quedarse colgada. Adicionalmente, si su
formateo de datos no se realiza en ASCII sino en crudo entonces no se podrá
trabajar de la forma que se hizo en este proyecto.