TP2
AlgoPaint
El trabajo práctico consiste en hacer una implementación propia del Paint, un programa para editar imágenes simples.
Requerimientos mínimos:
Como mínimo se espera que la aplicación tenga las siguientes características:
- Estar hecha con Gamelib.
- Permitir editar una imagen de NxM pixels, con N y M arbitrarios.
- Puede que Gamelib funcione poco eficientemente al intentar mostrar imágenes grandes; se considera suficiente con asegurar que la aplicación funciona bien con imágenes de hasta 20x20 pixels.
- Se deja a libre elección si las dimensiones de NxM de una imagen nueva son establecidas por el usuario o si se configura por constantes dentro del código.
- Permitir seleccionar un color para pintar.
- Se debe permitir ingresar un color arbitrario mediante la sintaxis
#rrggbb
(utilizando por ejemplogamelib.input
). - Mostrar un listado de al menos seis colores predefinidos, y permitir seleccionar uno de ellos a modo de "atajo".
- Debe indicarse en la pantalla de alguna manera cuál es el color seleccionado.
- Se debe permitir ingresar un color arbitrario mediante la sintaxis
- Al presionar el botón del mouse sobre un pixel, se debe pintar el mismo con el color seleccionado.
- Poder cargar una imagen de un archivo con formato PPM.
- Poder guardar la imagen en un archivo con formato PPM.
- Poder guardar la imagen en un archivo con formato PNG con color indexado.
- En caso de algún error al leer o escribir un archivo, se debe informar al
usuario (utilizando por ejemplo
gamelib.say
).
Controles
- El pintado debe poder ser realizado utilizando el mouse.
- Para el resto de las acciones (seleccionar color, cargar, guardar, etc), queda a
libre elección si se realizan mediante el mouse o el teclado.
- En el caso de elegir la opción de controles por teclado, debe indicarse por pantalla cuáles son los botones para cada una de las acciones.
Colores
En Gamelib, un color arbitrario se representa mediante la sintaxis #rrggbb
,
donde rr
, gg
y bb
son tres números expresados en formato hexadecimal,
que representan las intensidades de la componente rojo, verde y azul,
respectivamente. Alguinos ejemplos:
En algunos casos puede llegar a ser necesario convertir entre la representación hexadecimal y decimal de las componentes del color. Para ello se sugieren las siguientes funciones de Python:
int(hex, 16) -> dec
f'{dec:02x}' -> hex
Ejemplo:
>>> int('c0', 16)
192
>>> f'{192:02x}'
'c0'
El formato PPM
Para simplificar el proceso de escritura y lectura de las imágenes, utilizaremos uno de los formatos del paquete Netpbm que permite generar gráficos de forma muy sencilla. Particularmente en este trabajo utilizaremos la variante ASCII del formato PPM.
Un archivo PPM es un archivo de texto que contiene, en orden, y separados por espacios o saltos de línea:
- el encabezado
P3
- las dimensiones en ancho (
W
) y alto (H
) de la imagen - la intensidad máxima de una componente (
M
) - una secuencia de
3 x W x H
números entre0
yM
; tres números por cada pixel representando las componentes R, G, B, recorriendo los pixels de izquierda a derecha y de arriba a abajo.
Asumiremos que el máximo de intensidad M
siempre será 255.
Todos los números enteros se expresan en formato decimal.
Por ejemplo, el archivo:
P3
4 4
255
255 0 0 170 85 0 85 170 0 0 255 0
170 0 85 141 85 85 113 170 85 85 255 85
85 0 170 113 85 170 141 170 170 170 255 170
0 0 255 85 85 255 170 170 255 255 255 255
corresponde a la siguiente imagen de 4x4 pixels:
Nota: No es necesario que los números estén estructurados como en una matriz en el archivo PPM; puede haber uno por línea y el formato es igualmente válido. Queda a libre elección la estructura a utilizar para facilitar la lectura/escritura del archivo.
Nota: El programa debe permitir cargar archivos PPM de cualquier dimensión WxH
si su contenido es válido. Al cargarse, todos los píxeles del mismo deben aparecer
y ser editables en el programa.
El formato PNG
El formato PNG es mucho más conocido y utilizado que PPM, entre otras razones por ser más versátil y eficiente. Sin embargo, es bastante más complejo de implementar, y por eso proveemos el módulo png.py con la implementación ya resuelta para escribir un archivo PNG. No implementaremos la funcionalidad de leer PNG.
El módulo png.py
provee una única función: png.escribir(ruta, paleta, imagen)
.
Esta función escribe un archivo en formato PNG con color indexado. La técnica de color indexado ayuda a reducir considerablemente el tamaño del archivo resultante, en los casos en que la imagen está compuesta por una reducida cantidad de colores únicos. En lugar de almacenar el color de cada pixel por separado (que requeriría guardar tres números R, G, B por cada pixel), se calcula una paleta, que es simplemente una lista de los colores (R, G, B) disponibles, y luego por cada pixel se almacena el índice del color correspondiente a la paleta.
Por ejemplo, el siguiente código:
paleta = [
(0, 0, 0),
(255, 0, 0),
(0, 0, 255),
(0, 255, 0)
]
imagen = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0],
[0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0],
[0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0],
[0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0],
[0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
]
import png
png.escribir('archivo.png', paleta, imagen)
produce la imagen PNG:
Esqueleto
Se sugiere arrancar el código utilizando el siguiente esqueleto, que puede ser
adaptado según sea necesario. En particular, las llamadas a print
están
únicamente a modo ilustrativo:
import gamelib
def paint_nuevo(ancho, alto):
'''inicializa el estado del programa con una imagen vacía de ancho x alto pixels'''
return None
def paint_mostrar(paint):
'''dibuja la interfaz de la aplicación en la ventana'''
gamelib.draw_begin()
gamelib.draw_text('Hola mundo', 100, 100)
gamelib.draw_end()
def main():
gamelib.title("AlgoPaint")
gamelib.resize(200, 200)
paint = paint_nuevo(20, 20)
while gamelib.loop(fps=15):
paint_mostrar(paint)
for ev in gamelib.get_events():
if ev.type == gamelib.EventType.ButtonPress and ev.mouse_button == 1:
print(f'se ha presionado el botón del mouse: {ev.x} {ev.y}')
elif ev.type == gamelib.EventType.Motion:
print(f'se ha movido el puntero del mouse: {ev.x} {ev.y}')
elif ev.type == gamelib.EventType.ButtonRelease and ev.mouse_button == 1:
print(f'se ha soltado el botón del mouse: {ev.x} {ev.y}')
elif ev.type == gamelib.EventType.KeyPress:
print(f'se ha presionado la tecla: {ev.key}')
gamelib.init(main)
Recursos
Restricciones y condiciones de entrega
En general, el uso de cualquier cosa que no haya sido explicada en clase requiere previa autorización de un docente. Particularmente:
- El uso de los módulos
gamelib
ypng
está permitido.- No se permite el uso de
gamelib.draw_image
para dibujar la imagen a editar. Sí se permite dicha función para dibujar íconos y elementos similares que sean parte de la interfaz.
- No se permite el uso de
- No se permite el uso de variables globales (sí de constantes globales).
- No se permite el uso de
eval
niexec
.
Entrega
- Se deberán entregar únicamente los archivos
.py
que se utilicen. - No subir ninguna imagen producida por la aplicación.
- La entrega se hará utilizando el formulario de entregas bajo la sección
TP2
.