Existen varias estrategias para aprovechar estructuras repetitivas con el fin de cambiar gradualmente de color.
Partiremos del siguiente ejemplo de código que dibuja una serie de rectángulos alineados verticalmente.
Todas estas técnicas pueden extrapolarse a cualquier dibujo basado en repetición.
// repite 10 veces
for(int i=0; i<10; i=i+1){
// asigna color
fill(0);
// dibuja rectángulo y traslada
rect(0,0, width, 20);
translate(0, 40);
}
Un gradiente de escala de grises consiste en cambiar el valor numérico del canal de luminosidad para cada figura dibujada.
Podemos utilizar una variable para el canal de luminosidad, que se incrementa con cada iteración del ciclo:
float tono = 0; // valor inicial del tono
// repite 10 veces
for(int i=0; i<10; i=i+1){
// asigna tono
fill(tono);
// dibuja rectángulo y traslada
rect(0,0, width, 20);
translate(0, 40);
// incrementa el canal
25;
tono = tono + }
La magnitud del incremento, en combinación con la cantidad de repeticiones y el valor inicial del tono, nos indicará cuál será el rango que tendremos.
En este ejemplo, el tono tomará los siguientes valores: 0, 25, 50, 75, 100, 125, 150, 175, 200.
Entre más repeticiones haya, menos hay que incrementar para que el gradiente abarque toda la composición.
¿Qué habría que cambiar de la estructura para que el gradiente fuera en la dirección opuesta?
Podemos aprovechar la variable que cuenta las repeticiones del ciclo, escalándola / convirtiéndola a valores que nos funcionen para el tono.
Tomando en cuenta que el contador del ciclo se incrementa de uno en uno (0, 1, 2, 3, etc), podemos multiplicar esa variable por una cantidad constante para obtener un resultado que se incrementa proporcionalmente:
// repite 10 veces
for(int i=0; i<10; i=i+1){
// asigna tono en función del contador
fill( i*25 );
// dibuja rectángulo y traslada
rect(0,0, width, 20);
translate(0, 40);
}
En este ejemplo, el tono tomará los siguientes valores: 0, 25, 50, 75, 100, 125, 150, 175, 200.
¿Qué tendríamos que hacer para que el valor inicial no fuera 0?
¿Y qué tendríamos que hacer para que el gradiente fuera en la dirección opuesta?
La función map( )
, si bien un poco más compleja, nos puede facilitar la creación de un gradiente en específico
// repite 10 veces
for(int i=0; i<10; i=i+1){
// convierte a i, que va de 0 a 9
// a un valor entre 0 y 225
float tono = map( i, 0, 9, 0, 225);
// asigna el tono
fill( tono );
// dibuja rectángulo y traslada
rect(0,0, width, 20);
translate(0, 40);
}
Lo ideal es tener claro el significado de los 5 parámetros de map()
Cambiando los últimos dos parámetros alteramos el comportamiento del gradiente:
// convierte a i, que en este caso va de 0 a 9
// a un valor entre 100 y 225
float tono = map( i, 0, 9, 100, 225);
Incluyendo la posibilidad de invertir su dirección:
// convierte a i, que en este caso va de 0 a 9
// a un valor entre 200 y 50
float tono = map( i, 0, 9, 200, 50);
Las estrategias para gradientes de color son similares a las previas, pero ahora actuando en tres canales independientes en lugar de solo uno.
El mismo principio visto arriba lo podemos utilizar para cambiar un canal de color mientras los otros dos se mantienen constantes.
El rango de valores en el gradiente va a depender de nuevo de:
Este ejemplo modifica al canal rojo y deja constantes a verde y azul.
float rojo = 0; // valor inicial del canal rojo
// repite 10 veces
for(int i=0; i<10; i=i+1){
// asigna color
fill(rojo, 100, 250);
// dibuja rectángulo y traslada
rect(0,0, width, 20);
translate(0, 40);
// incrementa el canal rojo
25;
rojo = rojo + }
Este ejemplo modifica al tono (hue) y deja constantes a la saturación y luminosidad.
// modo de color HSB
colorMode(HSB, 360, 100, 100);
float tono = 0; // valor inicial del tono
// repite 10 veces
for(int i=0; i<10; i=i+1){
// asigna tono
fill(tono, 50, 100);
// dibuja rectángulo y traslada
rect(0,0, width, 20);
translate(0, 40);
// incrementa el canal
30;
tono = tono + }
Podemos extrapolar lo anterior para tener un acumulador por cada canal de color, cada uno con un comportamiento distinto: ya sea con distinto valor inicial, y/o con distinto valor de incremento.
float rojo = 0; // valor inicial del canal rojo
float verde = 50; // valor inicial del canal verde
float azul = 100; // valor inicial del canal azul
// repite 10 veces
for(int i=0; i<10; i=i+1){
// asigna color
fill(rojo, verde, azul);
// dibuja rectángulo y traslada
rect(0,0, width, 20);
translate(0, 40);
// incrementa los canales con distintos incrementos
25;
rojo = rojo + 10;
verde = verde + 5;
azul = azul + }
Podemos aprovechar el conteo de iteraciones para calcular el valor de los canales de color en cada figura.
A continuación ejemplos con el mismo comportamiento que los mostrados anteriormente con acumuladores.
Nota las similitudes con esos ejemplos previos: ¿cómo estamos asignando el incremento aquí? ¿y cómo estamos asignando el valor inicial de cada canal?
for(int i=0; i<10; i=i+1){
// asigna color en función de i
fill(i*25, 100, 250);
// dibuja rectángulo y traslada
rect(0,0, width, 20);
translate(0, 40);
}
colorMode(HSB, 360, 100, 100);
for(int i=0; i<10; i=i+1){
// asigna color en función de i
fill(i*30, 50, 100);
// dibuja rectángulo y traslada
rect(0,0, width, 20);
translate(0, 40);
}
for(int i=0; i<10; i=i+1){
// asigna color en función de i
fill(i*25, i*10+50, i*5+100);
// dibuja rectángulo y traslada
rect(0,0, width, 20);
translate(0, 40);
}
Conviene utilizar map()
para tener más control sobre los valores iniciales y finales en cada canal, sin tener que estar realizando aritmética.
Por ejemplo, supongamos que queremos un gradiente que vaya del color (240, 17, 88)
al color (246, 250, 61)
en RGB.
Podemos notar que el canal rojo incrementa muy poco, el canal verde disminuye considerabldmente, y el azul incrementa más.
Podemos utilizar map()
para convertir al contador del ciclo i
al valor correspondiente de cada canal.
for(int i=0; i<10; i=i+1){
// convierte a i, que va de 0 a 9 en este caso
// al valor de cada canal
float rojo = map( i, 0, 9, 240, 246); // rojo de 240 a 246
float verd = map( i, 0, 9, 17, 250); // verde de 17 a 250
float azul = map( i, 0, 9, 88, 61); // azul de 88 a 61
fill(rojo, verd, azul);
// dibuja rectángulo y traslada
rect(0,0, width, 20);
translate(0, 40);
}
lerpColor()
Processing tiene una función lerpColor()
que calcula (interpola) el color que habría entre un color inicial y un color final dados, en una “posición” específica dada por un valor entre 0 y 1.
Si la posición es 0, el color corresponde al inicial, y si la posición es 1, el color corresponde al final. Una posición de 0.5 sería el color exactamente enmedio a los dos, y así sucesivamente.
Podemos usar map()
para convertir el contador de iteraciones a un valor entre 0 y 1.
La estructura quedaría así:
// establece colores del gradiente
color(240, 17, 88);
color colorInicial = color(246, 250, 61);
color colorFinal =
for(int i=0; i<10; i=i+1){
// convierte el contador i, que va de 0 a 9 en este caso
// a un número entre 0 y 1
float p = map(i, 0, 9, 0, 1);
// utiliza ese valor para calcular el color correspondiente
lerpColor(colorInicial, colorFinal, p);
color colorGradiente =
// y utiliza el color correspondiente
fill(colorGradiente);
// dibuja rectángulo y traslada
rect(0,0, width, 20);
translate(0, 40);
}
La posición en el gradiente también podría calcularse con aritmética:
float p = i/9.0; // i va de 0 a 9, lo convertimos a un número entre 0 y 1
El punto decimal es importante para tener un resultado de punto flotante y no entera. Otra forma de escribirlo podría ser:
float p = 1.0*i/9; // i va de 0 a 9, lo convertimos a un número entre 0 y 1