Revista Tecnología

Generando imágenes en C, sólo por diversión, empezando desde cero (Parte I)

Publicado el 06 mayo 2016 por Gaspar Fernández Moreno @gaspar_fm

Cómo generar las imágenes

Para generar las imágenes, vamos a pensar en un buffer sencillo, lineal de tipo unsigned char, o uint8_t, para imágenes en blanco y negro... vamos a empezar con imágenes sencillas, ya meteremos color... y más cosas.

Utilizaremos un array unidimensional, en lugar de uno bidimensional porque nos va a hacer la vida más fácil a la hora de recorrer todos los pixels de la imagen y manipularlos. Así, en muchas ocasiones nos ahorraremos tener dos variables, una para indicar coordenada x (o columna) y otra para indicar coordenada y (o filas). Aunque el ahorro de memoria no es significativo: da igual tener un array bidimensional de 8×8 que uno unidimensional de 64, sólo que tendremos una variable menos en el bucle principal; y el tiempo de procesamiento tampoco: actualmente los compiladores son capaces de optimizar muchísimo el ejecutable y los procesadores son cada vez mejores, capaces de realizar los dos recorridos prácticamente en el mismo tiempo; hay ocasiones en las que esto nos beneficia, por ejemplo, cuando utilizamos un código complicado para generar una imagen, el código con el array unidimensional puede tardar menos (el compilador intenta utilizar registros de CPU para las variables que más usamos (los contadores del bucle, y los registros son limitados y puede que tengamos que escribir memoria->registro->memoria muchas veces, si son dos contadores, habrá más operaciones del estilo), también tendremos un bucle menos, un indentado menos en el código que siempre se agradece, y, bueno, muchas bibliotecas gráficas utilizan este formato, así que de esta forma nos será fácil, por ejemplo crear plugins de frei0r, por ejemplo.

Generando imágenes sólo diversión, empezando desde cero (ParteGenerando imágenes en C, sólo por diversión, empezando desde cero (Parte I)Al final, en memoria tendremos algo así. Nosotros sabemos que es una matriz (con dos dimensiones), pero en realidad para acceder a cada casilla tendremos que utilizar un número consecutivo a la anterior. Y aunque nosotros, para acceder a una posición digamos fila 3, columna 4, en realidad le estamos pasando un 14, sólo que el compilador aplica de forma transparente una transformación.
Todo esto nos sirve para poder calcular la posición del array en función de las coordenadas y viceversa, porque en algunos casos suele ser útil (digamos que la fila es y, y la columna es x, el ancho de la imagen será el número de columnas por fila):

casilla = y * ancho + x
y = casilla / ancho
x = casilla % ancho

Vemos un pequeño ejemplo de todo esto en C, con un array bidimensional (al que podremos acceder como si fuera un array unidimensional, viendo que en memoria está almacenado así):

Cómo guardar las imágenes

Almacenar una imagen en disco es complicado, muy complicado, se utilizan complejos algoritmos y fórmulas matemáticas para realizar la compresión de la información, y esto puede terminar en varios miles de líneas de código. En el pasado, he escrito sobre varios formatos de imagen, como BMP, JPEG (con libjpeg) y más, en este último utilizamos MagickCore, que nos ayudará a pelearnos con los formatos gráficos de forma indolora, y es que, nos proporciona una API muy sencilla para interactuar con efectos gráficos, lectura y salvado de múltiples formatos de imagen.

Es la que vamos a utilizar aquí, para salvar archivos PNG, por detrás utilizará libpng, aunque con MagickCore, podremos utilizar cualquier formato gráfico de la misma manera, sin necesidad de cambiar mucho nuestro código, y eso es bueno para experimentar).

Por ejemplo, tendremos este sencillo código para salvar nuestras imágenes:

Imágenes en blanco y negro

En principio, vamos a crear algunos patrones ( no pondré el código por completo, puesto que la función salva_imagen(), ANCHO y ALTO, ya los tenemos arriba, pondré sólo la función main() o alguna función extra que vea necesaria).

Un color gris

Empezamos desde el principio, en estos ejemplos, para una imagen gris, supongo que tenemos 256 niveles de gris y, por eso la variable donde almaceno los pixels es de tipo unsigned char (con rango desde 0 a 255). Por lo tanto si queremos un color intermedio (nivel 128, debemos rellenar nuestro array de pixels de este valor):

Gradiente lineal de grises

Y, por supuesto un gradiente circular:
Generando imágenes sólo diversión, empezando desde cero (ParteGenerando imágenes en C, sólo por diversión, empezando desde cero (Parte I)

Donde podemos cambiar el valor de min y max para que haya más o menos negro o más o menos blanco en la imagen. Así como modificar focox y focoy en función de dónde queremos que esté el centro.

Cuadrícula

Puntos aleatorios, o ruido

Fondo de estrellas

Estrellas de diferentes tamaños

Generando imágenes sólo diversión, empezando desde cero (ParteGenerando imágenes en C, sólo por diversión, empezando desde cero (Parte I)Otra posibilidad que tenemos es dibujar estrellas de diferentes tamaños, con diferentes brillos, en realidad, antes, una estrella era un punto, ahora será un punto rodeado de más puntos, en los que veremos que el brillo se va degradando hasta llegar a negro.
En este caso puede haber transparencias, en este caso, se ha cogido siempre el pixel de mayor valor (ya hablaremos del blending un día de estos). Este será uno de los más simples que podamos hacer. En este ejemplo, podremos variar el tamaño de las estrellas, el número y el brillo.

Dibujar líneas

Generando imágenes sólo diversión, empezando desde cero (ParteGenerando imágenes en C, sólo por diversión, empezando desde cero (Parte I)
Se pueden dibujar líneas con varios algoritmos diferentes. Emepzaremos por el más sencillo, el DDA (Digital Differential Analyzer). El problema de dibujar líneas es que las coordenadas de cada pixel son números enteros, y en ocasiones, una línea, no pasa exactamente por un punto. Como vemos, una línea recta sí tiene un trazo puro, una línea con inclinación de 45º también, pero casi todas las demás, si aumentamos la imagen serán una sucesión de pequeñas líneas rectas puesto que no tenemos nada intermedio.
Aquí tenemos un ejemplo,

Algoritmo de Bresenham

Lo que nos queda...

Aunque con algoritmos de Bresenham podemos dibujar líneas de varias formas (reservadas para la próxima entrega). Veremos muchos algoritmos para dibujar en C, con ejemplos y mucho más... formas simples y complejas, superposición, colores, antialias...


Volver a la Portada de Logo Paperblog