TP2

AlgoPaint

El trabajo práctico consiste en hacer una implementación propia del Paint, un programa para editar imágenes simples.

AlgoPaint

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 ejemplo gamelib.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.
  • 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:

colores

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 entre 0 y M; 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:

ppm

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:

ppm

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 y png 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 variables globales (sí de constantes globales).
  • No se permite el uso de eval ni exec.

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.