Monday, April 24, 2017

Usando "Dithering"... O cuando la impresora es blanco y negro


Estamos acostumbrados a procesar imágenes con muchos colores. Las tarjetas gráficas actuales pueden desplegar 16 millones de colores y de hecho, ya el ojo no llega a distinguir diferencias entre algunos tonos. Esa parece ser la razón por la cual los fabricantes de hardware ya no buscan más colores o más resolución, pues estamos llegando a los límites que nuestro ojo puede percibir.

Pero curiosamente, a pesar de la gran tecnología que ahora tenemos ya incluso en nuestras casas, hay procedimientos que se necesitan seguir haciendo como hace ya muchos años. Por ejemplo, consideremos que tenemos una impresora que solamente usa tinta negra. En ese caso solamente tendremos dos posibles "colores" para imprimir: blanco (sin imprimir) y negro (impresión con tinta). Esto significa un problema, porque si tenemos una imagen en tonos de grises, es decir, con 256 posibles tonos de gris, entonces no podríamos imprimirla para que se viesen esos tonos.

El problema se puede solucionar si observamos cómo vemos e interpretamos. Por ejemplo, si hacemos una especie de tablero de ajedrez con dos colores, digamos rojo y azul, pues veremos una malla con colores alternativos. Pero hagamos las casillas más chicas. En la medida que las reducimos, podemos ver de pronto que el rojo y el azul se transforman en un violetas. Si usamos una lupa podremos ver que ese violeta no existe, lo que ocurre es que nuestro cerebro se encarga de interpretar así lo que vemos. Pues bien, esto es lo que se llama dithering y es el proceso que de alguna u otra manera usan las impresoras actuales cuando tienen que imprimir imágenes de las cuales no tienen todos los colores.


El "dithering" o tramado, tiene que ver con un concepto fundamental: "la difusión del error". Por ejemplo, supongamos que queremos imprimir la imagen en tonos de gris pero en una impresora que sólo tiene dos posibles colores, como mencionamos negro (tinta) y blanco (no tinta). Podemos entonces ejecutar el siguiente procedimiento: Consideremos que el primer pixel tiene un valor de 96 (considerando que es una imagen en tonos de gris en donde el Rojo, Verde y Azul de cada punto gráfico tiene el mismo valor). Podemos preguntarnos entonces ¿qué tan lejos está el valor 96 del 255 o del 0. ¿De cuál está más cerca? Hallaremos que el 96 está más cerca del 0 y entonces diremos que el error en la impresión es de 96 tonos. Es decir, como el 0 es el negro, el 96 está a esa cantidad de tonos del negro.

Consideremos ahora que el siguiente valor en nuestra imagen es también 96. Aquí es donde el dithering se usa. Tomamos el error y lo sumamos al pixel que estamos procesando. esto nos dará 192 y este valor está más cerca del blanco que del negro, por lo que pintamos un punto en blanco (es decir, no imprimimos nada en ese punto en la hoja) y vemos qué tan lejos está el 192 del 255. Veremos que está a -63 puntos de ese valor. Es decir, cuando me acerco al 255 el error es negativo. Cuando estoy más cerca del negro, el valor es positivo.


En la medida que seguimos con este algoritmo, veremos que estamos "difundiendo el error" de forma tal que el pixel anterior (y su error), inciden en el siguiente pixel y así sucesivamente. Cuando termino con una línea de pixeles, redefino la difusión del error en cero y vuelvo a empezar a procesar la siguiente línea.

Cabe señalar que hay que tener cuidado por si ocurre que un valor se sale de rango hacia arriba (mayor de 255) o hacia abajo (menor que cero). En este caso, la difusión del error es igual a cero.

El algoritmo básico (en Delphi) es éste:

begin
   for i := 1 to Image1.Picture.Height do
    begin
      Application.ProcessMessages;
      Error_Dif := 0;
      k := 1;
      for j := 1 to Image1.Picture.Width do
       begin
        R := (Image1.Canvas.Pixels[j,i] shr 16) and $FF;
        NewByte := Error_Dif + R;
        if NewByte >= 255 then
        begin
          NewByte := 255;
          Image2.Canvas.Pixels[j,i] := RGB(255,255,255);
          Error_Dif := 0;
        end
        else
        if NewByte <= 0 then
        begin
          NewByte := 0;
          Image2.Canvas.Pixels[j,i] := RGB(0,0,0);
          Error_Dif := 0;
        end
        else
        if 255 - NewByte > 127 then
        begin
          //pixel en negro
          Image2.Canvas.Pixels[j,i] := RGB(0,0,0);
          k := 1;
          Error_Dif := NewByte * k;
        end
        else
        begin
          //pixel en blanco
          Image2.Canvas.Pixels[j,i] := RGB(255,255,255);
          k := -1;
          Error_Dif := 255 - (NewByte);
          Error_Dif := Error_Dif * k;
        end;
      end;
    end;

Tomé una imagen ejemplo que usan en el artículo que leí (ver referencias), y hallé que mi resultado es el esperado.

En los próximos artículos seguiremos hablando de otras interesantes técnicas para hacer "dithering", algo que hacen las impresoras hoy en día y que muchos no estaban siquiera enterados de este proceso.

Referencias:

TannerHelland (blog) 


No comments: