HTML5 Apis – Crea tu propio Instagram

Sigamos con la serie de posts sobre las APIs de HTML5. Ahora le toca al canvas, uno de los elementos más revolucionarios de HTML5. Yo siempre digo que si <video /> hirío a Flash, entonces <canvas /> lo mata definitivamente.

Que es el canvas? Pues dicho rápido y mal: Un nuevo elemento de HTML, que nos permite tener una superficie de dibujo. El canvas por si mismo no tiene una API asociada, en su lugar se obtiene un contexto de dibiujo sobre el canvas. Es dicho contexto el que nos proporciona una API para interaccionar con el canvas.

En la actualidad hay dos especificaciones de contextos distintas:

  • 2d: Para operaciones de 2D. Contiene una API muy sencilla para por un lado dibujar en el canvas (líneas, círculos, cuadrados, etc) y por otra acceder directamente al contenido binario del canvas.
  • webgl: Para operaciones 3D. Ofrece una API basada en OpenGL ES 2.0.

Actualmente todos los navegadores soportan el contexto 2d, y la mayoría soportan webgl. La excepción es IE y la razón principal (al margen de ciertas vulnerabilidades descubiertas en webgl) es que no es un estándard W3C.

No voy a explicar en este post como dibujar en el canvas, hay multitud de tutoriales para ello. En su lugar vamos a ver como combinar el canvas, File Api, un poco de drag and drop y acceso al contenido binario del canvas para crearnos nuestro propio Instagram 🙂

Paso 1 – Declaración del <canvas />

Bueno… eso no tiene apenas ningún secreto, basta con añadir un tag <canvas> y darle un tamaño:

<div>

    <canvas height=»400″ width=»600″ id=»mc» styleborder: 1px solid black»></canvas> 

</div>

Paso 2 – Habilitar drag and drop

Ahora lo que queremos es que el usuario pueda hacer drop de un fichero local sobre el canvas y que nosotros lo podamos procesar. Todavía no hemos visto la API nativa de drag and drop que incorpora HTML5 pero vamos a ver lo básico ahora para habilitarla. Debemos seguir dos pasos para que un elemento sea una zona donde se pueda hacer drop:

  1. Asociarnos a su evento dragOver y allí hacer un preventDefault. Eso es porque por defecto todos los objetos del DOM heredan una implementación de dicho evento que significa “no se puede hacer drop aquí”. Al rededinir dicho evento quitamos este comportamiento por defecto y nuestro elemento del DOM pasa a ser un zona donde se puede hacer drop. El evento dragOver se dispara cuando estamos arrastrando algo y pasamos por encima del elemento.
  2. Asociarnos a su evento drop y allí recoger los datos que se hayan arrastrado. En el caso que nos ocupa (arrastre de ficheros locales hacia un elemento del DOM), el evento de drop tiene una propiedad dataTransfer que tiene una propiedad files que es una FileList (de File API), con la información de los ficheros que se hayan arrastrado.

Por lo tanto añadamos el mínimo código script para soportar el drag and drop:

<script type=»text/javascript»>

    var canvas = document.getElementById(«mc»);

    canvas.addEventListener(«dragover», handleDragOver, false);

    canvas.addEventListener(«drop», handleDrop, false);

    function handleDragOver(e) {

        e.preventDefault();

    }

 

    function handleDrop(e) {

        var files = e.dataTransfer.files;

        if (files.length > 0) {

            var file = files[0];

            procesarFichero(file);

        }

 

        e.preventDefault();

    }

 

    function procesarFichero(file) {

        alert(«has arrastrado « + file.name + «->» + file.type);

    }

</script>

Con esto ya tenemos el drag and drop habilitado para el canvas y podemos arrastrar y soltar en él, ficheros locales!

Paso 3 – Leer la imagen y colocarla en el canvas

De todas las funciones que nos ofrece el contexto 2d para el canvas, hay una que nos interesa ahora mismo que es drawImage. Esta función nos permite recoger el contenido de una imagen ya existente y colocarlo en el canvas. Por lo tanto antes de usar drawImage necesitamos tener una imagen cargada. Ya vimos en el post dedicado a File API como leer un objeto File y asignarlo a una imagen: con el método ReadAsDataURL. Bien, pues lo hacemos:

function procesarFichero(file) {

    var fr = new FileReader();

    fr.addEventListener(«loadend», function(e) {

        var datauri = e.target.result;

        loadCanvas(datauri);

    }, false);

 

    fr.readAsDataURL(file);

}

function loadCanvas(datauri) {

    alert(datauri);

}

Ahora tan solo nos falta colocar la imagen en el canvas. Para ello, primero la tenemos que colocar en un objeto Imagen y luego sí, ya en el canvas:

function loadCanvas(datauri) {

    var img = new Image();

    img.addEventListener(«load», function(e) {

        var ctx = canvas.getContext(«2d»);

        ctx.drawImage(img, 0, 0);

    }, false);

    img.src = datauri;

}

A destacar de este código anterior:

  1. El uso de getContext(«2d») para obtener el contexto 2d. Ojo, que la cadena que se pasa a getContext es case-sensitive!
  2. Creamos una imagen y le asignamos como src el resultado de readAsDataURL.
  3. Usamos drawImage para dibujar la imagen en el canvas. Importante que esto lo tenemos que hacer dentro del evento load de la imagen, para asegurarnos que esta estará cargada.

Paso 4: Crear un filtro

Vamos a crear un filtro para manipular nuestra imagen. En este ejemplo será muy sencillo: un botón que invertirá los colores de la imagen.

Para implementar este filtro debemos acceder al contenido binario del canvas. Dicho contenido es muy sencillo: cada pixel del canvas ocupa 4 bytes (rojo, verde, azul y canal alfa), y está guardado por filas (es decir, primero todos los pixels de la primera fila de izquierda a derecha y así sucesivamente).

El método getImageData del contexto, nos devuelve dicho array rellenado con el contenido actual del canvas. A dicho método se le pasa el rectángulo que queremos obtener (punto top-left, ancho y alto) y nos devolverá el array correspondiente. Cada píxel ocupará, insisto, 4 posiciones de dicho array (el array es de bytes).

Así me creo mi función de filtro, que simplemente invertirá los valores de r,g,b dejando el canal alfa tal cual estaba:

function applyFilter() {

    var ctx = canvas.getContext(«2d»);

    var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

    for (var idx = 0; idx < imageData.data.length; idx+=4) {

        var r = imageData.data[idx];

        var g = imageData.data[idx + 1];

        var b = imageData.data[idx + 2];

 

        imageData.data[idx] = 255 – r;

        imageData.data[idx + 1] = 255 – g;

        imageData.data[idx + 2] = 255 – b;

    }

    ctx.putImageData(imageData, 0, 0);

}

Ahora tan solo me falta añadir un botón a la página y enlazar el click del botón con el método applyFilter.

Paso 5: Enviar los datos del canvas al servidor

Una vez hemos modificado el canvas nos puede interesar mandar la imagen que hay en el canvas hacia el servidor. Para ello no hay ninguna función específica, así que veremos como podemos hacerlo.

Hay un método toBlob en el propio canvas que nos devuelve un Blob (es casi equivalente al File de File API que conocemos, de hecho File deriva de Blob), pero es muy nuevo y no está apenas implementado en ningún navegador, así que lo descartamos. Dicho método sería la manera más directa, ya que luego con un FormData podemos mandar directamente este Blob (como vimos en el post de uploads asíncronos). Pero como digo, queda descartado.

Pero bueno, tampoco es ningún drama. La solución pasa por crearnos el Blob a mano y rellenarlo con los datos del canvas… Veamos:

function uploadCanvas() {

    var ctx = canvas.getContext(«2d»);

    var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

    var xhr = new XMLHttpRequest();

 

    var arrayBuffer = new ArrayBuffer(imageData.data.length);

    var view = new Uint8Array(arrayBuffer);

    for (var idx = 0; idx < imageData.data.length; idx++) {

        view[idx] = imageData.data[idx];

    }

    var blob = new Blob([view]);

    var formData = new FormData();

    formData.append(«canvas», blob);

    formData.append(«w», canvas.width.toString());

    formData.append(«h», canvas.height.toString());

    xhr.open(«POST», «@Url.Action(«Index», «Instagram»)«, true);

    xhr.send(formData);

}

Para crear un Blob debemos crear antes un ArrayBuffer con los datos de dicho Blob, y para acceder (y rellenar) un ArrayBuffer debemos hacerlo a través de un Uint8Array. De ahí que el código es un poco liadillo.

Luego, además de los datos binarios del canvas, mandamos el ancho y el alto, para tenerlos en servidor.

Ahora nos falta la parte de servidor:

[HttpPost]

public ActionResult Index(HttpPostedFileBase canvas, int w, int h)

{

    var stream = canvas.InputStream;

    var bmp = new Bitmap(w, h);

    int r, g, b, a;

    for (var row = 0; row < h; row++)

    {

        for (var col = 0; col < w; col++)

        {

            r = stream.ReadByte();

            g = stream.ReadByte();

            b = stream.ReadByte();

            a = stream.ReadByte();

            bmp.SetPixel(col, row, Color.FromArgb(a, r, g, b));

        }

    }

    bmp.Save(«d:\filename.png», ImageFormat.Png);

    return new HttpStatusCodeResult(200);

}

¿Sencillo no? Creamos un bitmap, decodificamos los datos binarios del canvas y “pintamos” el Bitmap. Luego lo guardamos y listo 😉

Paso 6: Guardar en el cliente

Para finalizar esta maravilla que estamos creando vamos a añadir un botón para guardar la imagen modificada en el cliente, sin necesidad de subirla al servidor.

Para ello nos vamos a aprovechar de un método existente en el canvas llamado toDataURL, que como supondrás nos convierte el contenido del canvas a una data URL:

function saveCanvas() {

    var datauri = canvas.toDataURL(«image/png»);

    window.open(datauri, «preview», «width=» + (canvas.width + 20) +«,height=» + (canvas.height + 20));

}

Esta función la tengo enlazada al evento click de un botón y lo que hace es simplemente mostrar la imagen en un popup y así el usuario puede hacer «guardar como» y listos.

Existe una API definida (FileWriter) para escribir ficheros desde javascript pero su soporte es hoy en día tan minoritario que no tiene sentido plantearse el usarla. Otra alternativa, pero también minoritaria (sólo funciona con Chrome) sería usar un tag <a /> dinámico con el atributo download y forzar el click con javascript.

Saludos!

HTML5 Apis – Upload de ficheros con Ajax, File Api y Progress Api

¡Buenas! En el post anterior vimos el funcionamiento de File Api y como leer ficheros locales en servidor. En este post vamos a seguir usando File Api pero lo vamos a combinar con XMLHttpRequest y progress Api para ver como podemos hacer uploads de ficheros al servidor de forma fácil y asíncrona.

Para empezar vamos a montar la página:

<!DOCTYPE html>

<html>

<head>

    <meta name="viewport" content="width=device-width" />

    <title>Index</title>

</head>

<body>

    <div>

        Selecciona fichero.

        <input type="file" id="file" value="enviar" /> <br />

        <progress id="progress"></progress>

    </div>

</body>

</html>

Un simple input file y un progress para poder mostrar el progreso.

Ahora el siguiente punto es usar XMLHttpRequest para hacer la petición y enviar el fichero de forma asíncrona. Para ello nos vamos a aprovechar de la característica de que el XMLHttpRequest puede enviar un objeto File. Para ello usamos FormData que nos simplifica el código. Usando formData nos basta con una llamada a append para añadir el fichero. No solo esto, al usar FormData XMLHttpRequest usará el content-type correcto (“multipart/form-data”) al realizar la petición, sin que lo tengamos que establecer nosotros. Fijaos como després de abrir la conexión (xhr.open) nos limitamos a mandar el FormData que es quien contiene los datos 🙂

<script type="text/javascript">       

    function ficheroSeleccionado(e) {

        if (e.target.files.length > 0) {

            subirFichero(e.target.files[0]);

        }

    }

 

    function subirFichero(file) {

        var xhr = new XMLHttpRequest();

        var formData = new FormData();

        formData.append("file", file);

 

        xhr.addEventListener("error", function(e) {

            alert("Error subiendo el archivo.");

            var progress = document.getElementById("progress");

            progress.value = 0;

        }, false);

 

        xhr.addEventListener("load", function(e) {

            alert("fichero subido: " + e.target.status + "->" + e.target.statusText);

        });

 

        xhr.open(‘POST’, @Url.Action("Index"), true);

        xhr.send(formData);

    }

    document.getElementById("file").addEventListener("change", ficheroSeleccionado, false);

</script>

Nota: El objeto File se añade con el nombre “file” al FormData porque ese es el nombre del parámetro en la acción de destino del controlador (he usado ASP.NET MVC).

Con esto ya tenemos un file uploader con Ajax… ¿Qué sencillo, no? Pues, tal y como diría cierto conejo pesado, ¡no se vayan todavía, aún hay más!

Progress API

El objeto XMLHttpRequest define una propiedad llamada upload que devuelve el XMLHttpRequestUpload asociado. Pues bien dicho objeto define un evento, llamado “progress” que se va lanzando cada cierto tiempo donde nos informa del estado de la petición. Es decir, que ahora podemos saber cada cierto tiempo el total de bytes que se tienen que enviar y el total ya enviados. Y con ello podemos mostrar una progress bar (en mi caso he usado directamente un <progress>):

xhr.upload.addEventListener("progress", function(e) {

    if (e.lengthComputable) {

        var progress = document.getElementById("progress");

        progress.max = e.total;

        progress.value = e.loaded;

    }

}, false);

Y lisos, es así de sencillo! Con ello ya tenemos un file uploader, que funciona por Ajax y que además con una progress nos va mostrando como va la subida de ficheros al servidor.

¿Fácil no? 🙂

PD: Si vas a probar el código en local recuerda que .NET limita el tamaño de la petición por defecto (sobre unos 4MB creo). Si vas a probar con archivos grandes usa <httpRuntime maxRequestLength=”xxx” /> para establecer el tamaño máximo de petición.

PD2: Y recuerda que aún cuando uses httpRuntime, hay otro límite que aplica IIS (maxAllowedContentLength): http://msdn.microsoft.com/en-us/library/ms689462%28VS.90%29.aspx

HTML5 Apis: File API

¡Muy buenas! Vamos a empezar una serie de posts (que como digo siempre, a ver donde nos llevan) sobre las APIs de HTML5, dado que hay muchas (algunas más conocidas que otras). La que veremos en este post es File API que dicho rápidamente nos permite leer ficheros locales usando javascript.

Si al leer que ahora podemos leer ficheros desde javascript se te han puesto los pelos como escarpias pensando en los posibles agujeros de seguridad, tranquilo: no hay forma alguna de leer un fichero a través de su ruta. Siempre se requiere que sea el usuario el que inicie la acción y abra explícitamente el fichero.

Leyendo datos de un fichero

Lo primero que necesitamos para poder acceder al contenido de un fichero es que el usuario lo abra. ¿Y que mecanismo tenemos en HTML para permitir al usuario seleccionar uno o varios ficheros? Exacto! El feote <input type=”file” /> y eso es lo primero que necesitamos.

    1 <!DOCTYPE html>

    2 

    3 <html>

    4     <head>

    5         <title>title</title>

    6     </head>

    7     <body>

    8         <div>

    9             Selecciona un fichero: <br />

   10             <input type=»file» value=»Leer» id=»fichero»/> 

   11         </div>

   12         <script type=»text/javascript»>

   13             function ficheroSeleccionado(evt) {

   14                 var ficheros = evt.target.files;

   15                 // Tan solo procesaremos el primer fichero

   16                 var fichero = ficheros[0];

   17             }

   18             document.getElementById(«fichero»).addEventListener(‘change’, ficheroSeleccionado, false);

   19         </script>

   20     </body>

   21 </html>

Si ejecuto eso mismo en Chrome, y pongo un breakpoint en la línea 17 veo lo siguiente:

image

Podemos ver como efectivamente la propiedad files del <input> me ha devuelto una FileList y el primer elemento es un File con la información del archivo seleccionado. ¡Bien!

Una vez tenemos un objeto File ya podemos leerlo, para ello debemos instanciar un FileReader y llamar a algunos de sus métodos:

  • readAsBinaryString: para leerlo de forma binaria . Ese método devuelve una cadena con el contenido binario. Si te parece raro que se reciba una cadena con el contenido en binario, decirte que a los que crearon la API les debió parecer lo mismo (léete la PD al final del post).
  • readAsText: para leerlo como texto
  • readAsDataURL: para leerlo como “data-url” (eso es ideal para previews de imágenes).

Vamos a ver un ejemplo rapidísimo: vamos a crear un visor hexadecimal. Para ello añadimos dos <div>s, uno de los cuales contendrá los códigos hexadecimales de cada byte del archivo y otro contendrá el carácter ASCII imprimible asociado:

    1         <style>

    2             .terminal {font-family: courier new}

    3             #raw {float: left}

    4             #rawtext {float:right}

    5         </style>

    1         <div id=»raw» class=»terminal»></div>

    2         <div id=»rawtext» class=»terminal» ></div>

Y ahora usaremos readAsBinaryString para obtener el contenido binario del archivo.

Cuando usemos las funciones de FileReader debemos tener presente que esas NO devuelven ningún resultado, si no que nos lanzan un evento cuando la lectura ha finalizado, y entonces podemos consultar el valor de la propiedad result del FileReader. Para ello modificamos la función ficheroSeleccionado para que quede tal y como sigue:

    1 function ficheroSeleccionado(evt) {

    2     var ficheros = evt.target.files;

    3     // Tan solo procesaremos el primer fichero

    4     var fichero = ficheros[0];

    5     var reader = new FileReader();

    6 

    7     reader.onloadend = function(ievt) {

    8         if (ievt.target.readyState == FileReader.DONE) {

    9             mostrarResultado(ievt.target.result);

   10         }

   11     };

   12 

   13     reader.readAsBinaryString(fichero);

   14 }

Fijaos que asignamos una función gestora al evento loadend, que nos lanza el FileReader para indicar que la lectura ha finalizado. En esta función gestora nos limitamos a asegurarnos que realmente la lectura no ha dado error y si es el caso llamamos a mostrarResultado que será la función que rellenerá los dos <div>s:

    1 function mostrarResultado(resultado) {

    2     var raw = document.getElementById(«raw»);

    3     var rawtext = document.getElementById(«rawtext»);

    4     var shtmlraw = «»;

    5     var shtmltext = «»;

    6     for (var idx = 0; idx < resultado.length; idx++) {

    7         var ascii = resultado.charCodeAt(idx);

    8         var codigo = ascii.toString(16);

    9         if (codigo.length < 2) codigo = «0» + codigo;

   10         shtmlraw += codigo + «&nbsp;»;

   11         shtmltext += ascii >= 32 && ascii <= 127 ? «&#» + ascii : «.»;

   12         if ((idx + 1) % 8 == 0 && (idx + 1) % 24 != 0) {

   13             shtmlraw += «&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;»;

   14         }

   15         if ((idx+1) % 24 == 0) {

   16             shtmlraw += «<br />»;

   17             shtmltext += «<br />»;

   18         }

   19     }

   20 

   21     raw.innerHTML = shtmlraw;

   22     rawtext.innerHTML = shtmltext;

   23 }

No hay mucho que contar sobre esa función, es javascript puro y duro 🙂 Un consejo por si alguna vez usáis la propiedad innerHTML para asignar código HTML a un elemento del DOM: es muuuuuuuuy lenta. Así que es mejor ir creando el código en una cadena y usar innerHTML una sola vez al final.

¡Y listos! Ya puedo seleccionar un fichero cualquiera y ver su contenido binario:

image

Ahhh… que nostalgia de aquellos tiempos de MS-DOS, con aquellos editores hexadecimales tan bonitos, eh??? Pues bueno, aquí tenéis una buena imitación, eso sí solo lectura, que File API no permite modificar en ningún caso el contenido de ningún archivo!

Previsualizando imágenes

Bueno… ya puestos vamos a añadir un poco de funcionalidad a nuestro editor hexadecimal. Si el archivo seleccionado es una imagen vamos a visualizarla como tal (junto con su volcado hexadecimal).

Y es que File API nos permite de forma muy fácil crear una previsualización de una imagen, gracias al método readAsDataURL. Ya hablé hace tiempo en este blog de las data url así que no me repetiré ahora, simplemente para resumir: el atributo src de un <img> puede ser una URI especial (que empieza por data:) y que tiene el contenido en Base64 de la imagen a mostrar.

Pues bien, eso es precisamente lo que nos devuelve readAsDataURL. Así para previsualizar una imagen basta con asignar su atributo src al valor leído por el FileReader (en el evento loadend por supuesto). Veamos como queda el código completo.

En este caso el código queda un poco más liado porque lo que debemos hacer ahora es:

  1. Si el fichero es una imagen debemos
    1. Leerlo usando readAsDataURL y previsualizarlo
    2. Leerlo de nuevo usando readAsBinaryString y previsualizarlo
  2. Si el fichero NO es una imagen debemos
    1. Leerlo usando readAsBinaryString y previsualizarlo

Para ello modificamos primero la función ficheroSeleccionado para saber si el fichero es una imagen o no y actuar en consecuencia:

    1 function ficheroSeleccionado(evt) {

    2     var ficheros = evt.target.files;

    3     // Tan solo procesaremos el primer fichero

    4     var fichero = ficheros[0];

    5     var reader = new FileReader();

    6 

    7     if (fichero.type.match(‘image.*’)) {

    8         leerImagen(reader, fichero);

    9     }

   10     else {

   11         leerBinario(reader, fichero);

   12     }

   13 

   14 }

El método leerBinario es simplemente el código de lectura que teniamos antes en ficheroSeleccionado:

    1 function leerBinario(reader, fichero) {

    2     reader.onloadend = function (ievt) {

    3         if (ievt.target.readyState == FileReader.DONE) {

    4             mostrarResultado(ievt.target.result);

    5         }

    6     };

    7 

    8     reader.readAsBinaryString(fichero);

    9 }

El método mostrarResultado por supuesto es el mismo de antes 😉

Vayamos ahora a por el método leerImagen. Este simplemente leerá usando readAsDataURL y luego llamará a leerBinario:

    1 function leerImagen(reader, fichero) {

    2     reader.onloadend = function (ievt) {

    3         if (ievt.target.readyState == FileReader.DONE) {

    4             previsualizarImagen(ievt.target);

    5             leerBinario(reader, fichero);

    6         }

    7     };

    8     reader.readAsDataURL(fichero);

    9 }

Y para completar la ecuación tan solo nos queda el método previsualizarImagen:

    1 function previsualizarImagen(filereader) {

    2     var datauri = filereader.result;

    3     var image = document.getElementById(«preview»);

    4     image.src = datauri;

    5     image.style.visibility = «visible»;

    6 }

Fijaos que se limita a poner en el atributo src de una imagen el valor leído por el FileReader usando readAsDataURL. Y eso es lo que vemos si seleccionamos una imagen:

image

Fijaos como podemos ver la imagen, sin que esa haya subido en ningún momento al servidor.

¡Y listos! Con eso ya hemos visto lo básico de File API. Hay más, pero mejor lo dejamos para otro post de esta serie 😉

PD: Notas sobre soporte de los navegadores.

El código de este post lo he probado con Chrome (a la hora de escribirlo era la versión 23.0, cuando le leáis a saber cual es) y con IE10. Ambos soportan File API (al igual que el resto de los navegadores importantes).

Peeeeeero el método readAsBinaryString NO está soportado en IE10. Pero, que quede claro, no es un incumplimiento del estándard: El estandard de la W3C no define un método readAsBinaryString (lo podeis ver en http://www.w3.org/TR/FileAPI/#dfn-filereader). Existió tiempo atrás pero se marcó como obsoleto (y en la versión final ha desaparecido). Chrome lo mantiene (creo que FF también) pero IE10 es más estricto con el cumplimiento del estandard y no lo hace.

Eso es típico cuando usamos HTML5: algunas de esas APIs están vivas o se han terminado hace muy poco y a veces es fácil caer en métodos obsoletos o ya desparecidos.

PD2: Vale… muy bien ¿pero como acceder al contenido binario usando la última especificación del estandard?

¡Que nadie se alarme! Es muy sencillo: debemos usar el método readAsArrayBuffer() en lugar de readAsBinaryString() que nos devuelve un ArrayBuffer que contiene los valores. Veamoslo.

El cambio a leerBinario es trivial:

    1 function leerBinario(reader, fichero) {

    2     reader.onloadend = function (ievt) {

    3         if (ievt.target.readyState == FileReader.DONE) {

    4             mostrarResultado(ievt.target.result);

    5         }

    6     };

    7 

    8     reader.readAsArrayBuffer(fichero);

    9 }

Y será mostrarResultado el que ahora en lugar de recibir una cadena con el contenido en binario recibe un ArrayBuffer. El tema es que los ArrayBuffer no se pueden consultar directamente. En su lugar debe usarse una vista por encima del ArrayBuffer. En nuestro caso dado que queremos leer byte a byte usaremos un Uint8Array.

El código de mostrarResultado es idéntico al que ya teníamos con la excepción del principio:

    1 function mostrarResultado(resultado) {

    2     var raw = document.getElementById(«raw»);

    3     var rawtext = document.getElementById(«rawtext»);

    4     var shtmlraw = «»;

    5     var shtmltext = «»;

    6     var view = new Uint8Array(resultado);

    7     for (var idx = 0; idx < view.length; idx++) {

    8         var ascii = view[idx];

El resto del código es idéntico. Fijaos como creamos el Uint8Array por encima del ArrayBuffer devuelto y luego ya leemos byte a byte. Dado que ahora view es un array ya de bytes y no una cadena como antes, ya no necesitamos usar charCodeAt() para obtener el código ascii (valor del byte) asociado.

Ahora sí que el código debería funcionar tanto en IE10, como en Chrome, como en FF como en el resto de navegadores importantes!