Veremos cómo pasar una imagen a escala de grises con javascript, pero sólo a efectos didácticos, ya que a efectos prácticos es más rápido y conveniente usar otras técnicas que ya hemos visto, tanto del lado del
cliente como del lado del
servidor para lograr lo mismo.
Al igual que en otras ocasiones, usaremos canvas para conseguir el efecto en los navegadores estándar y filtros propietarios para Internet Explorer. Pero esta vez, debemos admitirlo, Explorer trabaja mucho mejor y más rápido que el resto de los navegadores y esto se debe a que, para convertir la imagen a escala de grises usando canvas, estamos obligados a recorrer cada uno de los pixeles de la imagen.
Veamos primero un ejemplo y luego explicaremos de manera sucinta cómo funciona:
Nota Importante: Los ejemplos mostrados en este artículo pueden mejorarse de manera drástica. En este otro
artículo mostramos cómo.
El código utilizado es el siguiente:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Escala de grises con javascript</title>
<script>
function toGrayScale(im){
if(document.createElement("canvas").getContext){
var ref = document.createElement("canvas");
ref.width = im.width || im.offsetWidth;
ref.height =im.height || im.offsetHeight;
var context = ref.getContext("2d");
context.drawImage(im,0,0);
var iData=context.getImageData(0,0, ref.width, ref.height);
var mx= ref.width* ref.height*4;
var x=0;
im.parentNode.replaceChild(ref,im);
(function f(){
var r=iData.data[x];
var g=iData.data[x+1];
var b=iData.data[x+2];
var a=iData.data[x+3];
var m=(r+g+b)/3;
iData.data[x]=m;
iData.data[x+1]=m;
iData.data[x+2]=m;
context.putImageData(iData, 0, 0);
x+=4;
document.getElementById('log').innerHTML=Math.floor(x/mx*100)+'%';
if(x<mx){
setTimeout(f,0);
}else{
document.getElementById('log').innerHTML='';
}
})();
}else{
im.style.filter = 'progid:DXImageTransform.Microsoft.BasicImage(grayscale=1)';
document.getElementById('log').innerHTML='';
}
}
</script>
</head>
<body>
<img style="cursor:pointer" onclick="toGrayScale(this)" id="im" src="4389e6d.jpg" />
<div id="log" style="font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px">click sobre la imagen para pasar a escala de grises</div>
</body>
</html>
Valiéndonos del método
getImageData del objeto canvas podemos obtener un array representativo de los pixeles contenidos dentro de la superficie cuyas coordenadas le pasemos como agumentos. Pero este array no tiene una relación 1 a 1 con cada pixel, sino que cada pixel ocupa, dentro de este array, 4 posiciones, las cuales corresponden a los valores rgba de dicho pixel. Es decir, la superficie completa del lienzo está representada no por un array de longitud igual a ancho x alto, sino por un array de longitud igual a ancho x alto x 4.
Entonces, si queremos manipular el primer pixel de una imagen, suponiendo que ya hemos obtenido el objeto imageData y lo hemos almacenado en la variable iData, podríamos acceder a los valores rgba de ese pixel de la siguiente manera:
var r=iData.data[0];
var g=iData.data[1];
var b=iData.data[2];
var a=iData.data[3];
Una vez obtenidos esos valores, una de las manera para pasar el pixel a escala de grises es redefiniendo sus valores rgb a la media aritmética de las variables r, g y b:
var r=iData.data[0];
var g=iData.data[1];
var b=iData.data[2];
var a=iData.data[3];
var m=(r+g+b)/3;
iData.data[0]=m;
iData.data[1]=m;
iData.data[2]=m;
Y si queremos pasar toda la imagen a escala de grises debemos hacer lo mismo con cada uno de sus pixeles, lo cual, como vemos en el ejemplo, demora un tiempo considerable, salvo en Explorer, navegador en el cual no es necesario recorrer los pixeles 1 a 1.
No obstante esta deficiencia de velocidad, canvas permite obtener efectos que en Explorer no podemos lograr con filtros.
Por ejemplo, si volviendo a nuestro ejemplo del pixel número uno, en lugar de igualar el canal rojo a la media de los valores rgb lo igualáramos al valor máximo que este admite (255):
var r=iData.data[0];
var g=iData.data[1];
var b=iData.data[2];
var a=iData.data[3];
var m=(r+g+b)/3;
iData.data[0]=255;
iData.data[1]=m;
iData.data[2]=m;
E hiciéramos luego lo mismo con cada uno de los pixeles de la imagen, obtendríamos esto: