SdA A.5. Procesamiento de Imágenes, Gráficos Vectoriales y Diseño Generativo con Processing
CDYPC – 1º de Bachillerato
¡Seguimos ampliando nuestro lienzo de código!
La sesión pasada aprendiste el alfabeto visual de Processing: puntos, líneas, polígonos y curvas. Hoy ese alfabeto cobra vida: vamos a cargar imágenes reales, hacer que el programa reaccione al ratón y al teclado, mover, girar y escalar lo que dibujamos, y usar todo eso para construir arte generativo y patrones que el propio algoritmo genera por ti.
1. Procesamiento de imágenes: cargar y mostrar una foto
Processing puede trabajar con imágenes reales, no solo con figuras dibujadas por código. El tipo de dato que las representa se llama PImage.
PImage foto;
void setup() {
size(600, 400);
foto = loadImage("paisaje.jpg");
}
void draw() {
image(foto, 0, 0, width, height); // se escala al tamaño del lienzo
}
Importante: el archivo de imagen debe estar dentro de una carpeta llamada
data, en la misma carpeta que tu sketch (Sketch → Añadir archivo…lo hace automáticamente). Si no está ahí, Processing no la encuentra.
Puedes también modificar la imagen ya cargada:
tint(255, 100, 100); // le da un tono rojizo antes de dibujarla
image(foto, 0, 0);
filter(GRAY); // convierte todo el lienzo a escala de grises
Y leer el color de un píxel concreto con get(x, y), que devuelve un color que luego puedes usar en fill() o stroke():
color c = foto.get(100, 50); // color del píxel (100, 50) de la imagen
fill(c);
2. Vectorial vs. mapa de bits: dos formas de “ser una imagen”
En TIC ya viste que una fotografía es un mapa de bits (una rejilla de píxeles con su color). Cuando dibujas con rect(), ellipse() o line(), en cambio, estás creando gráficos vectoriales: el programa no guarda píxeles, guarda instrucciones matemáticas (“un círculo de centro tal y radio cual”).
Mapa de bits (loadImage) | Vectorial (formas de código) | |
|---|---|---|
| Qué guarda | Un color por cada píxel | Fórmulas (posición, tamaño, ángulo) |
| Al escalar mucho | Se pixela, pierde calidad | Se recalcula, sigue nítido |
| Ejemplo típico | .jpg, .png | rect(), ellipse(), logotipos .svg |
3. Eventos: el programa reacciona al ratón
Hasta ahora draw() se repetía sola, sin que tú intervinieras. Con los eventos, Processing te avisa cuando ocurre algo:
| Variable / función | Qué da |
|---|---|
mouseX, mouseY | Posición actual del ratón |
pmouseX, pmouseY | Posición del ratón en el fotograma anterior |
mousePressed | true mientras el botón esté pulsado |
mousePressed() | Función que se ejecuta una vez, justo al pulsar |
mouseDragged() | Función que se ejecuta en cada fotograma mientras arrastras |
void draw() {
if (mousePressed) {
fill(200, 0, 0);
ellipse(mouseX, mouseY, 20, 20);
}
}
4. Eventos: el programa reacciona al teclado
void keyPressed() {
println("Has pulsado: " + key);
if (key == 'r') {
background(255, 0, 0);
}
}
key guarda el último carácter pulsado, y keyPressed (sin paréntesis) es una variable booleana como mousePressed, útil dentro de draw() si quieres comprobarlo en cada fotograma.
5. Dibujar a mano alzada: el punto y la línea al servicio del trazo
Con pmouseX/pmouseY y mouseX/mouseY puedes construir un lápiz digital: en cada fotograma dibujas una línea corta entre la posición anterior y la actual del ratón, y como no borras el fondo, el trazo se va acumulando.
void setup() {
size(600, 400);
background(255);
}
void draw() {
// sin background() aquí: así el trazo no se borra en cada fotograma
}
void mouseDragged() {
stroke(0);
strokeWeight(4);
line(pmouseX, pmouseY, mouseX, mouseY);
}
Una variante con point() da un efecto de aerógrafo: en vez de una línea continua, salpicas puntos alrededor del ratón.
void mouseDragged() {
for (int i = 0; i < 15; i++) {
float dx = random(-8, 8);
float dy = random(-8, 8);
point(mouseX + dx, mouseY + dy);
}
}
6. Operaciones en el espacio: trasladar, rotar y escalar
Hasta ahora dibujabas cada forma indicando sus coordenadas directamente. Processing tiene otra forma de trabajar: mover el propio sistema de coordenadas antes de dibujar.
translate(x, y);→ mueve el origen (0,0) al punto que indiques.rotate(angulo);→ gira los ejes ese ángulo, en radianes (usaradians(grados)para convertir).scale(factor);→ agranda o reduce lo que dibujes a partir de ahora.
void draw() {
background(255);
translate(width/2, height/2); // el origen pasa a estar en el centro
rotate(radians(frameCount)); // gira un poco más en cada fotograma
rectMode(CENTER);
fill(0, 150, 200);
rect(0, 0, 100, 100);
}
El problema: una vez que trasladas o giras, todo lo que dibujes después queda afectado, incluso formas que no querías mover. Para dibujar varias figuras con transformaciones distintas e independientes, usa pushMatrix() / popMatrix(), que guardan y restauran el sistema de coordenadas:
void draw() {
background(255);
pushMatrix();
translate(150, 200);
rotate(radians(30));
rect(-40, -40, 80, 80);
popMatrix(); // aquí se deshacen translate y rotate
pushMatrix();
translate(450, 200);
scale(1.5);
ellipse(0, 0, 60, 60);
popMatrix();
}
Idea clave:
pushMatrix()guarda una “foto” del sistema de coordenadas actual;popMatrix()lo devuelve exactamente a como estaba. Todo lo que hagas entre los dos solo afecta a lo que dibujes en ese bloque.
7. Diseño digital generativo basado en algoritmos
El arte generativo no dibuja una obra fija: usa reglas (bucles, aleatoriedad, condiciones) para que el propio código construya una composición distinta cada vez que se ejecuta.
void setup() {
size(600, 600);
background(255);
noStroke();
for (int i = 0; i < 200; i++) {
float x = random(width);
float y = random(height);
float d = random(10, 40);
fill(random(255), random(255), random(255), 150); // el 4º valor es transparencia
ellipse(x, y, d, d);
}
}
Cada vez que pulses Ejecutar, obtendrás un resultado distinto: las reglas son las mismas, pero random() cambia. Si quieres que sea reproducible (el mismo resultado cada vez, útil para depurar), fija la semilla con randomSeed(numero); al principio de setup().
8. Diseño de patrones: repetición con transformación
Un patrón combina un bucle for (que repite) con translate() y rotate() (que cambian dónde y cómo se dibuja cada copia):
void setup() {
size(600, 600);
noLoop(); // dibuja una sola vez, no hace falta repetir cada fotograma
}
void draw() {
background(255);
int filas = 6, columnas = 6;
float tam = width / (float) columnas;
for (int fila = 0; fila < filas; fila++) {
for (int col = 0; col < columnas; col++) {
pushMatrix();
translate(col * tam + tam/2, fila * tam + tam/2);
rotate(radians((fila + col) * 15));
rectMode(CENTER);
fill(200, 100, 50);
rect(0, 0, tam * 0.7, tam * 0.7);
popMatrix();
}
}
}
Fíjate en el patrón de diseño: entra en el bucle → guarda coordenadas (pushMatrix) → transforma (translate+rotate) → dibuja → restaura (popMatrix) → siguiente vuelta. Esta estructura es la base de casi cualquier composición generativa con repetición.
Parte 2: Ejercicios para Moodle (Tarea del Alumnado)
Estos ejercicios combinan varios de los bloques de hoy. Cada uno tiene su solución debajo: intenta resolverlo tú primero y usa la solución solo para comprobar o para desatascarte.
Ejercicio 1: Pincel con grosor variable
Enunciado: modifica el pincel a mano alzada del apartado 5 para que el grosor del trazo dependa de lo rápido que muevas el ratón: cuanto más deprisa arrastres, más grueso el trazo. Pista: la distancia entre (pmouseX, pmouseY) y (mouseX, mouseY) en un mismo fotograma es una buena medida de velocidad, y la función dist(x1,y1,x2,y2) te la da directamente.
Solución:
void setup() {
size(600, 400);
background(255);
}
void draw() {
}
void mouseDragged() {
float velocidad = dist(pmouseX, pmouseY, mouseX, mouseY);
float grosor = constrain(velocidad, 1, 30); // limita el grosor entre 1 y 30
stroke(0);
strokeWeight(grosor);
line(pmouseX, pmouseY, mouseX, mouseY);
}
Ejercicio 2: Selector de color con teclado
Enunciado: crea un pincel a mano alzada donde el color del trazo cambie según la tecla pulsada: 1 rojo, 2 verde, 3 azul. Debe empezar en negro por defecto.
Solución:
color colorActual;
void setup() {
size(600, 400);
background(255);
colorActual = color(0);
}
void draw() {
}
void keyPressed() {
if (key == '1') colorActual = color(255, 0, 0);
if (key == '2') colorActual = color(0, 200, 0);
if (key == '3') colorActual = color(0, 0, 255);
}
void mouseDragged() {
stroke(colorActual);
strokeWeight(4);
line(pmouseX, pmouseY, mouseX, mouseY);
}
Ejercicio 3: Manecilla generativa
Enunciado: dibuja una manecilla de reloj (una línea larga desde el centro del lienzo) que gire continuamente, usando translate() y rotate(). La velocidad de giro debe depender de frameCount.
Solución:
void setup() {
size(400, 400);
}
void draw() {
background(255);
translate(width/2, height/2);
rotate(radians(frameCount * 2)); // 2 grados más en cada fotograma
strokeWeight(6);
stroke(200, 0, 0);
line(0, 0, 0, -150); // la manecilla apunta "hacia arriba" antes de girar
}
Ejercicio 4: Patrón reproducible con color aleatorio
Enunciado: modifica el patrón en rejilla del apartado 8 para que cada celda tenga un color de relleno aleatorio, pero de forma que el patrón sea idéntico cada vez que ejecutes el programa (usa randomSeed).
Solución:
void setup() {
size(600, 600);
noLoop();
}
void draw() {
randomSeed(42); // misma semilla = misma secuencia de "aleatorios"
background(255);
int filas = 6, columnas = 6;
float tam = width / (float) columnas;
for (int fila = 0; fila < filas; fila++) {
for (int col = 0; col < columnas; col++) {
pushMatrix();
translate(col * tam + tam/2, fila * tam + tam/2);
rotate(radians((fila + col) * 15));
rectMode(CENTER);
fill(random(255), random(255), random(255));
rect(0, 0, tam * 0.7, tam * 0.7);
popMatrix();
}
}
}
Ejercicio 5: Lienzo sobre una fotografía
Enunciado: carga una imagen de fondo con loadImage() y permite que el usuario dibuje libremente encima con el ratón (línea a mano alzada), sin que el dibujo borre la foto de fondo entre fotograma y fotograma.
Solución:
PImage fondo;
void setup() {
size(600, 400);
fondo = loadImage("paisaje.jpg");
image(fondo, 0, 0, width, height); // se dibuja una sola vez, al principio
}
void draw() {
// no volvemos a llamar a image() aquí: así el trazo no se borra
}
void mouseDragged() {
stroke(255, 255, 0);
strokeWeight(5);
line(pmouseX, pmouseY, mouseX, mouseY);
}
