static-httpd

Sitio web estático (HTML + CSS) que sostiene el blog del autor
Log | Files | Refs

index.html (14664B)


      1 <!DOCTYPE html>
      2 <html>
      3 <head>
      4 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
      5 <meta name="viewport" content="width=device-width, initial-scale=1" />
      6 <title>Algoritmo para determinar el grado del modo oscuro en imágenes invertidas</title>
      7 <link rel="icon" type="image/png" href="/favicon.png" />
      8 <link rel="stylesheet" type="text/css" href="../article.css" />
      9 </head>
     10 <body>
     11 <header>
     12 <p class="title">Algoritmo para determinar el grado del modo oscuro en imágenes invertidas</p>
     13 <p class="date">11 de junio de 2024 • fjbalon</p>
     14 </header>
     15 
     16 <p>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.</p>
     17 <p>¿Cómo lograrlo en un sitio web estático? Simple, CSS trae la solución en sí mismo: la función de medios CSS <emb>prefers-color-scheme</emb> se utiliza para detectar si un usuario ha solicitado temas de colores claros u oscuros, siendo que indica su preferencia a través de una configuración del sistema operativo o una configuración del agente de usuario que indica el propio navegador. De tal forma que podemos configurar las preferencias para el modo claro o light y oscuro o dark. Tampoco es estrictamente necesario establecer ambos, simplemente con dejar uno, el otro se establecería como defecto negativo.</p>
     18 <pre><code>@media (prefers-color-scheme: light) {
     19     body {
     20         color: black;
     21         background-color: white;
     22     }
     23 }
     24      
     25 @media (prefers-color-scheme: dark) {
     26     body {
     27         color: white;
     28         background-color: black;
     29     }
     30 }</code></pre>
     31 <p>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.</p>
     32 <p>Se me ocurren un par de formas de solucionar esto:</p>
     33 <ol>
     34 <li>Utilizando un switch con dos imágenes, una para modo claro y otra para modo oscuro. Indicando un CSS:
     35 <pre><code>.only-on-dark {
     36     display: none;
     37 }
     38     
     39 @media (prefers-color-scheme: dark) {
     40     .only-on-dark {
     41         display: block;
     42     }
     43     
     44     .only-on-light {
     45         display: none;
     46     }
     47 }</code></pre>
     48 <p>De tal forma que se indique en el HTML ambas imágenes con la clase a la que se deben:</p>
     49 <pre><code>&lt;img class="only-on-dark" src="dark-image.png" />
     50 &lt;img class="only-on-light" src="image.png" /></code></pre>
     51 <p>Quizá sea preferible también embeberlo directamente en el HTML, de tal forma que:</p>
     52 <pre><code>&lt;picture>
     53     &lt;!-- Usuario prefiere modo claro -->
     54     &lt;source srcset="image.png" media="(prefers-color-scheme: light)"/>
     55     
     56     &lt;!-- Usuario prefiere modo oscuro -->
     57     &lt;source srcset="dark-image.png"  media="(prefers-color-scheme: dark)"/>
     58     
     59     &lt;!-- Usuario no indica preferencia -->
     60     &lt;img src="image.png"/>
     61 &lt;/picture></code></pre>
     62 <p>De esta forma evitaríamos la descarga de la variante oculta.</p>
     63 </li>
     64 <li>
     65 <p>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.</p>
     66 <p>De tal forma que mantendríamos una clase, sea por ejemplo <emb>color-invertible</emb>, que identificaría a la imagen:</p>
     67 <pre><code>&lt;img class="color-invertible" src="image.png"/></code></pre>
     68 <p>Para que en el CSS le indicásemos, dentro de <emb>prefers-color-scheme: dark</emb>:</p>
     69 <pre><code>@media (prefers-color-scheme: dark) {
     70     .color-invertible {
     71         filter: invert(100%);
     72     }
     73 }</code></pre>
     74 </li>
     75 </ol>
     76 <p>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:</p>
     77 <p>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 - <i>red, green and blue</i>). 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.</p>
     78 
     79 <p>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.</p>
     80 
     81 <p>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 <a href="https://imagecolorpicker.com/es">color picker</a> para experimentar y jugar con estos colores.</p>
     82 
     83 <p>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í:</p>
     84 
     85 <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\frac{(y-y_{1})}{(y_{2}-y_{1})}=\frac{(x-y_{1})}{(x_{2}-x_{1})}" display="block">
     86 <mrow>
     87 <mn>255</mn>
     88 <mo>-</mo>
     89 <mi>x</mi>
     90 <mo>=</mo>
     91 <mi>i</mi>
     92 </mrow>
     93 </math>
     94 
     95 <p>Donde <emb>x</emb> es el valor actual, e <emb>i</emb> el resultado invertido. Por ejemplo:</p>
     96 
     97 <p>De blanco a negro:</p>
     98 
     99 <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\frac{(y-y_{1})}{(y_{2}-y_{1})}=\frac{(x-y_{1})}{(x_{2}-x_{1})}" display="block">
    100 <mfenced open="(" close=")">
    101 <mtable>
    102 <mtr>
    103 <mtd><mn>(255</mn></mtd>
    104 <mtd><mn>255</mn></mtd>
    105 <mtd><mn>255)</mn></mtd>
    106 </mtr>
    107 </mtable>
    108 </mfenced>
    109 </math>
    110 
    111 <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\frac{(y-y_{1})}{(y_{2}-y_{1})}=\frac{(x-y_{1})}{(x_{2}-x_{1})}" display="block">
    112 <mrow>
    113 <mn>255</mn>
    114 <mo>-</mo>
    115 <mn>255</mn>
    116 <mo>=</mo>
    117 <mn>0</mn>
    118 </mrow>
    119 </math>
    120 
    121 <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\frac{(y-y_{1})}{(y_{2}-y_{1})}=\frac{(x-y_{1})}{(x_{2}-x_{1})}" display="block">
    122 <mfenced open="(" close=")">
    123 <mtable>
    124 <mtr>
    125 <mtd><mn>0</mn></mtd>
    126 <mtd><mn>0</mn></mtd>
    127 <mtd><mn>0</mn></mtd>
    128 </mtr>
    129 </mtable>
    130 </mfenced>
    131 </math>    
    132 <p>De negro a blanco:</p>
    133       
    134 <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\frac{(y-y_{1})}{(y_{2}-y_{1})}=\frac{(x-y_{1})}{(x_{2}-x_{1})}" display="block">
    135 <mfenced open="(" close=")">
    136 <mtable>
    137 <mtr>
    138 <mtd><mn>(0</mn></mtd>
    139 <mtd><mn>0</mn></mtd>
    140 <mtd><mn>0)</mn></mtd>
    141 </mtr>
    142 </mtable>
    143 </mfenced>
    144 </math>
    145 
    146 <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\frac{(y-y_{1})}{(y_{2}-y_{1})}=\frac{(x-y_{1})}{(x_{2}-x_{1})}" display="block">
    147 <mrow>
    148 <mn>255</mn>
    149 <mo>-</mo>
    150 <mn>0</mn>
    151 <mo>=</mo>
    152 <mn>255</mn>
    153 </mrow>
    154 </math>
    155 
    156 <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\frac{(y-y_{1})}{(y_{2}-y_{1})}=\frac{(x-y_{1})}{(x_{2}-x_{1})}" display="block">
    157 <mfenced open="(" close=")">
    158 <mtable>
    159 <mtr>
    160 <mtd><mn>255</mn></mtd>
    161 <mtd><mn>255</mn></mtd>
    162 <mtd><mn>255</mn></mtd>
    163 </mtr>
    164 </mtable>
    165 </mfenced>
    166 </math>
    167 
    168 <p>Por lo que si necesito que la inversión del color de fondo corresponda con el RGB (28, 28, 28):</p>
    169 
    170 <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\frac{(y-y_{1})}{(y_{2}-y_{1})}=\frac{(x-y_{1})}{(x_{2}-x_{1})}" display="block">
    171 <mrow>
    172 <mn>255</mn>
    173 <mo>-</mo>
    174 <mi>x</mi>
    175 <mo>=</mo>
    176 <mn>28</mn>
    177 </mrow>
    178 </math>
    179 
    180 <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\frac{(y-y_{1})}{(y_{2}-y_{1})}=\frac{(x-y_{1})}{(x_{2}-x_{1})}" display="block">
    181 <mrow>
    182 <mi>x</mi>
    183 <mo>=</mo>
    184 <mn>227</mn>
    185 </mrow>
    186 </math>
    187 
    188 <p>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.</p>
    189 <p>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 <emb>filter: invert(1);</emb> ó el porcentaje total <emb>filter: invert(100%);</emb>.</p>
    190 <p>Pero si requerimos de una inversión parcial, la fórmula sería:</p>
    191 
    192 <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\frac{(y-y_{1})}{(y_{2}-y_{1})}=\frac{(x-y_{1})}{(x_{2}-x_{1})}" display="block">
    193 <mrow>
    194 <mi>r</mi>
    195 <mo>=</mo>
    196 <mi>x</mi>
    197 <mo>(</mo>
    198 <mn>1</mn>
    199 <mo>-</mo>
    200 <mi>p</mi>
    201 <mo>)</mo>
    202 <mo>+</mo>
    203 <mo>(</mo>
    204 <mn>255</mn>
    205 <mo>-</mo>
    206 <mi>x</mi>
    207 <mo>)</mo>
    208 <mi>p</mi>
    209 </mrow>
    210 </math>
    211 
    212 <p>Donde <emb>r</emb> es el resultado deseado (o sea, 28), <emb>x</emb> el valor original (del blanco, 255) y <emb>p</emb> el porcentaje deseado. Desarrollamos:</p>
    213 
    214 <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\frac{(y-y_{1})}{(y_{2}-y_{1})}=\frac{(x-y_{1})}{(x_{2}-x_{1})}" display="block">
    215 <mrow>
    216 <mn>28</mn>
    217 <mo>=</mo>
    218 <mn>255</mn>
    219 <mo>(</mo>
    220 <mn>1</mn>
    221 <mo>-</mo>
    222 <mi>p</mi>
    223 <mo>)</mo>
    224 <mo>+</mo>
    225 <mo>(</mo>
    226 <mn>255</mn>
    227 <mo>-</mo>
    228 <mn>255</mn>
    229 <mo>)</mo>
    230 <mi>p</mi>
    231 </mrow>
    232 </math>
    233 
    234 <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\frac{(y-y_{1})}{(y_{2}-y_{1})}=\frac{(x-y_{1})}{(x_{2}-x_{1})}" display="block">
    235 <mrow>
    236 <mn>28</mn>
    237 <mo>=</mo>
    238 <mn>255</mn>
    239 <mo>*</mo>
    240 <mi>p</mi>
    241 <mo>0</mo>
    242 <mi>p</mi>
    243 </mrow>
    244 </math>
    245 
    246 <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\frac{(y-y_{1})}{(y_{2}-y_{1})}=\frac{(x-y_{1})}{(x_{2}-x_{1})}" display="block">
    247 <mrow>
    248 <mn>28</mn>
    249 <mo>=</mo>
    250 <mn>255</mn>
    251 <mo>*</mo>
    252 <mi>p</mi>
    253 </mrow>
    254 </math>
    255 
    256 <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\frac{(y-y_{1})}{(y_{2}-y_{1})}=\frac{(x-y_{1})}{(x_{2}-x_{1})}" display="block">
    257 <mrow>
    258 <mi>p</mi>
    259 <mo>=</mo>
    260 <mfrac>
    261 <mrow>
    262 <mn>28</mn>
    263 </mrow>
    264 <mrow>
    265 <mn>255</mn>
    266 </mrow>
    267 </mfrac>
    268 </mrow>
    269 </math>
    270 
    271 <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\frac{(y-y_{1})}{(y_{2}-y_{1})}=\frac{(x-y_{1})}{(x_{2}-x_{1})}" display="block">
    272 <mrow>
    273 <mi>p</mi>
    274 <mo>=</mo>
    275 <mn>0.109803921569</mn>
    276 </mrow>
    277 </math>
    278 
    279 <p>Finalmente, el valor <emb>p</emb> lo convertimos a porcentaje y restamos del total para obtener el valor deseado:</p>
    280 
    281 <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\frac{(y-y_{1})}{(y_{2}-y_{1})}=\frac{(x-y_{1})}{(x_{2}-x_{1})}" display="block">
    282 <mrow>
    283 <mn>0.109803921569</mn>
    284 <mo>*</mo>
    285 <mn>100</mn>
    286 <mo>=</mo>
    287 <mn>10.9803921569</mn>
    288 </mrow>
    289 </math>
    290 
    291 <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\frac{(y-y_{1})}{(y_{2}-y_{1})}=\frac{(x-y_{1})}{(x_{2}-x_{1})}" display="block">
    292 <mrow>
    293 <mn>100</mn>
    294 <mo>-</mo>
    295 <mn>10.9803921569</mn>
    296 <mo>=</mo>
    297 <mn>89.0196078431</mn>
    298 </mrow>
    299 </math>
    300 
    301 <p>Obtniendo así el filtro de inversión <emb>filter: invert(89.0196078431%);</emb>. He desarrollado un pequeño código que automatiza este cálculo:</p>
    302 <pre><code>#include &lt;stdio.h>
    303 #include &lt;stdlib.h>
    304 
    305 double inversion(int x);
    306 
    307 int main(int argc, char *argv[]) {
    308     if (argc != 4) {
    309         printf("Uso: %s r g b\n", argv[0]);
    310         return 1;
    311     }
    312 
    313     int r = atoi(argv[1]);
    314     int g = atoi(argv[2]);
    315     int b = atoi(argv[3]);
    316 
    317     printf("Valor RGB objetivo: (%d, %d, %d)\n", r, g, b);
    318 
    319     inversion(r);
    320     inversion(g);
    321     inversion(b);
    322 
    323     return 0;
    324 }
    325 
    326 double inversion(int x) {
    327     double r = (double)x /255.0;
    328     double p = 100.0 - (r * 100.0);
    329     printf("%f\n", p);
    330     return p;
    331 }</code></pre>
    332 <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%:</p>
    333 <img style="display: inline-block; width: 49%; vertical-align: top;" src="RGB.png" />
    334 <img style="display: inline-block; width: 49%; vertical-align: top; filter: invert(89.0196078431%);" src="RGB.png" />
    335 <p>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.</p>
    336 <p>Así, como codificamos en hexadecimal, la base correspnde a la numeración del <emb>0</emb> al <emb>9</emb> y las seis letras <emb>a</emb>, <emb>b</emb>, <emb>c</emb>, <emb>d</emb>, <emb>e</emb>, y <emb>f</emb>. No corresponde a este artículo desarrollar la numeración hexadecimal. Entonces, la intensidad máxima es <emb>ff</emb>, que se corresponde con <emb>(15×16)+15=255</emb> en decimal. La nula, evidentemente es <emb>00</emb>. Así, por ejemplo el valor RGB (113, 97, 228) corresponde al valor hexadecimal <emb>#7161E4</emb>.</p>
    337 </body>
    338 </html>