Algoritmo para determinar el grado del modo oscuro en imágenes invertidas
11 de junio de 2024 • fjbalon
No son pocas las ocasiones en que los sitios web ofrecen, a través de código javascript y cookies, sistemas de switch (o conmutador, en cristiano) del sistema claro u oscuro. Por la salud visual de los lectores, creo importante mantener este elemento fácil. En mi experiencia personal, creo que con un modo claro (en base a blanco) y un moco oscuro (base a negro puro) es más que suficiente. Además, el segundo se supone que aporta un mejor rendimiento en pantallas amoled, y visualmente un negro puro queda mucho más estético, a pesar de la tentación de mantener unos tonos grisáceos que, según otros, mejoran enormemente la lectura; pero ni tengo el conocimiento para defenderlo ni el necesario para atacarlo. El negro puro se constituye como un elemento, en mi uso personal, básico: utilizo un gestor de ventanas en mosaico con fondo negro, el fondo de la terminal st en negro, etc. Quiero entonces que, en cohesión con mi ecosistema, el fondo sea negro. Simple.
¿Cómo lograrlo en un sitio web estático? Simple, CSS trae la solución en sí mismo: la función de medios CSS
@media (prefers-color-scheme: light) {
body {
color: black;
background-color: white;
}
}
@media (prefers-color-scheme: dark) {
body {
color: white;
background-color: black;
}
}
Pero el problema de todo modo switch de modo oscuro son las imágenes. No hablamos de imágenes vectoriales como SVG, que tienen ya el propio soporte en CSS, sino imágenes de mapas de bits que, a menudo, no tienen siempre transparencia y el fondo blanco contrasta con el fondo negro, dejando un aspecto muy poco estético. Yo, que debo ser muy quisquilloso, no puedo consentirlo.
Se me ocurren un par de formas de solucionar esto:
.only-on-dark {
display: none;
}
@media (prefers-color-scheme: dark) {
.only-on-dark {
display: block;
}
.only-on-light {
display: none;
}
}
De tal forma que se indique en el HTML ambas imágenes con la clase a la que se deben:
<img class="only-on-dark" src="dark-image.png" />
<img class="only-on-light" src="image.png" />
Quizá sea preferible también embeberlo directamente en el HTML, de tal forma que:
<picture>
<!-- Usuario prefiere modo claro -->
<source srcset="image.png" media="(prefers-color-scheme: light)"/>
<!-- Usuario prefiere modo oscuro -->
<source srcset="dark-image.png" media="(prefers-color-scheme: dark)"/>
<!-- Usuario no indica preferencia -->
<img src="image.png"/>
</picture>
De esta forma evitaríamos la descarga de la variante oculta.
Una seguna solución, más eficiente en cuanto a que no necesitaríamos dos versiones de la imagen, sería invertir a través de CSS el color de la imagen original (siempre en el caso de que la inversión no suponga la subversión de la imagen, claro está) en caso de encontrarnos en modo oscuro.
De tal forma que mantendríamos una clase, sea por ejemplo
<img class="color-invertible" src="image.png"/>
Para que en el CSS le indicásemos, dentro de
@media (prefers-color-scheme: dark) {
.color-invertible {
filter: invert(100%);
}
}
Pero, ¿y si no estoy usando como creo más conveniente un fondo totalmente negro? ¿y si el fondo que estoy usando es, por ejemplo, (28, 28, 28)?... Creo conveniente realizar antes un breve repaso al funcionamiento técnico del color RGB:
RGB es, entonces, un modelo de color basado en la síntesis aditiva (permite explicar la obtención de un color a partir de la suma de los componentes de color), que permite representar un color mediante la mezcla por adición de los tres colores de luz primarios (rojo, verde y azul - red, green and blue). Si bien el propio modelo de color RGB no define por sí mismo lo que significa exactamente cada uno de los valores primarios, siendo que pueden existir colores notablemente diferentes en distintos dispositivos a pesar de usar el mismo color, esto es algo que no aplica para este caso, pues nos movemos siempre dentro del propio modelo.
En computación, para indicar con qué proporción es mezclado cada color, se asigna un valor a cada uno de estos tres colores, de forma que el valor 0 indica que no interviene en la mezcla y, a medida que el valor aumenta, se entiende que aporta intensidad a ésta. Aunque el intervalo de valores podría ser cualquiera, es frecuente que cada color primario se codifique con un byte (8 bits). Así, de manera prácticamente estándar, la intensidad de cada uno de los componentes se mide según una escala del 0 al 255.
De tal forma que el rojo se obtiene con (255, 0, 0), el verde con (0, 255, 0) y el azul con (0, 0, 255), obteniendo en cada caso un resultado monocromático. La ausencia de color -o negro- se obtiene cuando los tres componentes son 0: (0, 0, 0). Amarillo sería (255, 255, 0), cian (0, 255, 255) y magenta (255, 0, 255). El color blanco de forma mezclando en el nivel máximo los colores primarios, o sea, (255, 255, 255). En el caso de la presente web, contamos con un gris propio de (28, 28, 28). El lector puede usar un color picker para experimentar y jugar con estos colores.
En términos más técnicos del tema objeto del artículo, invertir el color en RGB significa restar cada componente del color al valor máximo posible. Así:
Donde
De blanco a negro:
De negro a blanco:
Por lo que si necesito que la inversión del color de fondo corresponda con el RGB (28, 28, 28):
La teoría de inversión de color RGB la tenemos clara. Sin embargo, no podemos indicar directamente que requerimos el valor (277, 277, 277) al fondo de una imagen para que se corresponda con éste en modo oscuro. Ya que lo requerimos es el valor o porcentaje de inversión necesaria para que se produzca el cálculo anterior transparentemente.
Si el caso fuera invertir de blanco (255, 255, 255) a negro (0, 0, 0) y viceversa en todas las ocasiones, es decir, si mantenemos un fondo blanco, y negro en modo oscuro. Simplemente valdría con usar la unidad
Pero si requerimos de una inversión parcial, la fórmula sería:
Donde
Finalmente, el valor
Obtniendo así el filtro de inversión
#include <stdio.h>
#include <stdlib.h>
double inversion(int x);
int main(int argc, char *argv[]) {
if (argc != 4) {
printf("Uso: %s r g b\n", argv[0]);
return 1;
}
int r = atoi(argv[1]);
int g = atoi(argv[2]);
int b = atoi(argv[3]);
printf("Valor RGB objetivo: (%d, %d, %d)\n", r, g, b);
inversion(r);
inversion(g);
inversion(b);
return 0;
}
double inversion(int x) {
double r = (double)x /255.0;
double p = 100.0 - (r * 100.0);
printf("%f\n", p);
return p;
}
Obtniendo así el valor de inversión parcial que permite filtrar la imagen y que se mimetice con el fondo de una forma plenamente estética y armoniosa. He aquí un ejemplo de la imagen invertida para con el fondo actual (28, 28, 28) con un valor de filtro del 89.0196078431%:
En computación, principalmente en algunos lenguajes como HTML, CSS o Javascript, se emplea la notación hexadecimal del color. Este sistema combina los tres códigos numéricos en dos dígitos hexadecimales para expresar las intensisdades de los colores primarios RGB. Es decir, es un apantallamiento del RGB.
Así, como codificamos en hexadecimal, la base correspnde a la numeración del