JavaScript

Juan Mellado, 19 Junio, 2010 - 11:07

WebGL Cheat SheetWebGL es la forma de acceder a OpenGL desde JavaScript. A diferencia de lo que inicialmente pudiera pensarse, no es una especificación de muy alto nivel que busca encapsular todo el trabajo duro detrás de una librería con un API simplificado. Todo lo contrario, es de tan de bajo nivel como pueda serlo el propio OpenGL. El famoso "canvas" es sólo la puerta de entrada, la forma de instanciar un contexto de trabajo gráfico. A partir de ahí sigue siendo necesario crear buffers, hacer binds, operar matrices, compilar shaders, ... y toda la parafernalia habitual de los programas que trabajan con OpenGL.

Gracias a que navegadores como Chrome o Firefox están empezando a implementar esta tecnología, más el primero que el segundo, se están empezando a ver algunas demos tecnológicas muy interesantes. En estos días me ha llamado particularmente la atención un visor de ficheros MD5 que muestra un modelo de una criatura de DOOM 3 con sus texturas aplicadas, e incluso con una animación. Muy espectacular, sobre todo porque el modelo en si mismo lo es. La demo es de Brandon Jones, el mismo que hizo el visor de ficheros Collada, mostrando en ese caso un modelo generado con "Spore Creature Creator".

He estado mirando la documentación, y para hacerme una idea de como estaba montado el API he acabado escribiendo una pequeña hoja de referencia (pulsar en la imagen que acompaña este post para agrandarla). No obstante, las funciones de WebGL no bastan por si solas, por lo que he añadido también un listado de referencia del "OpenGL ES Shading Language" necesario para escribir los shaders. Y es que como ya dije previamente, utilizar WebGL es como usar OpenGL. Aplican las mismas técnicas, por lo que es necesario escribir vertex shaders y fragment shaders. Se puede incluso desarrollar un motor utilizando Deferred Shading, las bases con las que se trabaja son las mismas.

Hay algunas diferencias y restricciones entre WebGL y su hermano mayor OpenGL, pero son bastantes técnicas, están en la documentación oficial, y creo que pueden obviarse en un primer momento. Lo que más está preocupando en estos momentos es la "descarga de contenidos". Es decir, la descarga de los modelos, texturas, animaciones, shaders, y toda esa serie de ficheros que luego se quieren mostrar por pantalla. En una distribución normal se empaqueta y se obliga al cliente a descargar y instalar. En una distribución web la cosa cambia un poco, ya que obliga a esperar que el navegador descargue todos los ficheros, y además normalmente son muchos ficheros sueltos que pueden estar, o no, en la cache del navegador. Los métodos tradicionales a base de referencias (links) puede que no sean lo más apropiado para este tipo de aplicaciones. Lo curioso es que si al final se llega a otra solución, más parecida a la distribución de las aplicaciones tradicionales, ¿no estamos entrando en una contradicción?

Una última nota, para probar las demos de los enlaces que he puesto es necesario tener un navegador que soporte WebGL. A día de hoy recomiendo Chrome. No obstante, la versión actual no tiene habilitado WebGL por defecto. Para activarlo hay que pasarle la cadena "--in-process-webgl --enable-webgl" como parámetro en la línea de comandos.

Juan Mellado, 31 Mayo, 2010 - 09:08

Flash Apple Android Windows MobileSmokescreen es otro proyecto que trata de reproducir Flash en el navegador a través de un programa escrito completamente en JavaScript utilizando HTML5. En la web del proyecto se pueden ver algunas demos, e incluso un video con las demos ejecutándose sobre un iPad. Muy similar al proyecto Gordon, en bastantes aspectos.

Por el momento no resulta muy espectacular, ya que las películas Flash que requieren más proceso se ejecutan de una forma bastante lenta. Va requerir un poco más de optimización. Para lo que sirven en realidad este tipo de proyectos es para darse cuenta de que muchas cosas que se hacen "por inercia" con Flash se pueden hacer también con otras tecnologías. El principal problema es que no existen herramientas tan sencillas y productivas como Flash para hacerlas. Hay es donde se tienen que poner las pilas los desarrolladores. Y evidentemente falta que Internet Explorer empiece a soportar todas estas tecnologías. Microsoft debería tomar nota para la versión 9 de su navegador, que ya le toca. Silverlight y HTML5 pueden coexistir.

Según la web del proyecto, el código fuente de Smokescreen se liberará dentro de un tiempo. No obstante, a dia de hoy se puede analizar el JavaScript que se ejecuta en la página de las demos: http://smokescreen.us/demos/js/smokescreen.0.1.3-min.js

Leyendo entre líneas, se puede ver que han implementado un parser de ficheros SWF, de igual forma que se observa algunos nombres de clases del core del runtime de Flash para el player. Lo que no parece tener soporte el reproductor es para ActionScript, al menos en su versión 3, aunque teniendo sólo el código "minificado" es bastante complicado de saber a ciencia cierta.

En cualquier caso, si bien estos "experimentos" resultan interesantes, la guerra entre Flash y HTML5 se ha enfriado un poco con la liberación de la versión Flash 10.1 (beta) que ya se puede ejecutar en móviles con Android 2.2 (Froyo). Google lo tiene claro: dar soporte para Flash, y otros ingenios como Unity por ejemplo. Si los usuarios no quieren utilizarlo que no lo hagan, es algo opcional.

En la pasada Google I/O 2010 se pudieron ver bastantes cosas relativas a este tema, incluida una presentación de una aplicación de Adobe, aún en pañales, para generar animaciones directamente en HTML5 de forma similar a como se hace actualmente en Flash.

Temas: JavaScript
Juan Mellado, 29 Mayo, 2010 - 13:51

El traductor de Google cada día funciona mejor, y hace tiempo que quería ponerlo en mi web. Lo acabo de hacer, y la verdad es que me ha resultado muy sencillo. Sólo hay que ejecutar tres simples pasos descritos en el siguiente enlace:

http://translate.google.com/translate_tools

Lo que más me ha costado es conseguir integrarlo con el diseño de la web. Los colores por defecto del traductor no encajaban con el sitio donde quería ponerlo, en la cabecera con el fondo azul, así que al final he tenido que buscar un poco para ver como resolverlo.

El API no permite personalizar los colores del traductor, así que la única solución que se recomienda es utilizar una hoja de estilo propia para sobrescribir las clases CSS originales cuyos nombres se pueden obtener con un poco de ingenieria inversa sin mucho esfuerzo:

<style>
  div.goog-te-gadget{
    color:#ffffff;
  }
  a.goog-logo-link:link,
  a.goog-logo-link:visited{
    color:#ffffff;
    font-weight: normal;
  }
</style>

Lo único que no ha quedado demasiado legible es el logo de Google. Creo que una imagen alternativa con un borde blanco alrededor de cada letra podría solucionar ese problema. A ver si actualizan el API algún día y permiten personalizar este tipo de cosas.

Juan Mellado, 8 Mayo, 2010 - 09:00

Después de haber conseguido parsear con éxito un fichero Flash mediante un programa en JavaScript, me he animado a programar una máquina virtual, también en JavaScript, que ejecute el código ActionScript 3 leído del fichero SWF sin utilizar el plugin de Adobe.

La ventaja de hacerlo en JavaScript es que ambos lenguajes se basan en la misma especificación ECMA-262, por lo que muchas cosas pueden traducirse directamente de un lenguaje a otro. Pero eso es en un mundo ideal, en la vida real ocurre que cada cual implementa la especificación de una forma ligeramente distinta, aparte de que la propia especificación deja margen para que ciertas partes se implementen de forma arbitraria. De hecho, es algo bastante conocido que la implementación del propio JavaScript varía de un navegador a otro.

Para simplificar estoy suponiendo que el comportamiento del código ejecutado en JavaScript es lo suficientemente parecido al de ActionScript 3. Siempre habrá tiempo para ajustar las pequeñas diferencias que aparezcan. Para el código estándar que no fuerce la mezcla de tipos de forma innecesaria, ni se base en efectos laterales, debería bastar. Y de hecho, ya he conseguido ejecutar sin mayor problema pequeños programas ActionScript 3 compuestos por operaciones matemáticas, comparaciones y bucles.

Siguiendo las especificaciones de Adobe, la ejecución de una función en ActionScript 3 requiere que la máquina virtual instancie una estructura de datos locales a dicha ejecución compuesta por una pila de operandos, una pila de ámbitos (scope) y un conjunto de registros.

LocalDataArea = function(){
  this.stack = [];
  this.scopeStack = [];
  this.registers = [];
};

Para empezar me he centrado en los opcodes que manipulan los registros o la pila de operandos, ya que normalmente son los más fáciles de implementar en cualquier tipo de emulador, y permiten tener en muy poco tiempo un prototipo ejecutando código.

La estructura principal de la máquina vitual es un simple switch que determina el código a ejecutar en función del opcode actual sobre el contador de programa.

switch(opcode){
...
case 0x02: //nop
  break;
...
case 0x28: //pushnan
  lda.stack.push(NaN);
  break;
...
case 0xd0: //getlocal_0
  lda.stack.push( lda.registers[0] );
  break;
...

Dejo para más adelante las consideraciones acerca del rendimiento, y realizar pruebas para ver es si mejor, o no, tener una función para cada opcode con el objetivo de agilizar la localización e invocación del código asociado a cada opcode. De momento me he centrado en implementar la mayor cantidad de opcodes posibles, unos cincuenta en estos momentos, sobre un total de aproximadamente ciento cincuenta con los que cuenta la especificación.

Una de las cosas "divertidas" de este proyecto es que me permite depurar código ActionScript 3 directamente desde el propio navegador mediante Firebug. Un fichero SWF generado en modo debug incluye opcodes de depuración con nombres de ficheros, números de línea y nombres de variable. Cuando se produce un error, como el intento de ejecución de un opcode no implementado, me basta con poner un punto de parada y ver que línea concreta del código fuente original ha generado el error, e incluso el nombre de las variables que contiene cada registro local en ese momento.

Buscando proyectos similares por Internet me he encontrado con un fork de Gordon por Brian McKelvey. Tiene implementado el parser de ficheros abcFile (que contienen el código ActionScript 3 compilado) aprovechando la clase base de lectura de Gordon, pero aún poca cosa de la máquina virtual en si misma. De hecho, me ha dado la impresión de lo que pretende no es construir una máquina virtual que interprete el código en tiempo de ejecución, sino trasladar los opcodes a código JavaScript directamente en forma de texto para luego evaluar la cadena resultante. Habrá que seguirle la pista.

Juan Mellado, 17 Abril, 2010 - 09:13

Flash Apple Android Windows MobilePues sí, este es otro post sobre Flash y HTML5, o no.

Antecedentes
Últimamente anda el patio bastante revuelto por toda una cadena de acontecimientos (en orden no cronológico):
1) La presentación de la nueva versión de Adobe Flash Professional CS5 y, no menos importante, de Flex 4 y el rebautizado Flash Builder 4.
2) La preparación de la nueva versión Adobe Flash Player 10.1 con soporte para su ejecución en dispositivos móviles.
3) El cambio de licencia de Apple para limitar las herramientas de desarrollo que pueden utilizarse para producir aplicaciones que se ejecuten en sus plataformas ("Applications must be originally written in Objective-C, C, C++, or JavaScript as executed by the iPhone OS WebKit engine").
4) El soporte cada vez más limitado de Flash en dispositivos móviles con Android y Windows Mobile, frente a las enormes expectativas originales.
5) El vídeo de la presentación de DreamWeaver CS5 donde se ejecuta una animación realizada en Flash dentro una página web mediante JavaScript sin necesidad de tener el plugin instalado.
6) La aparición de proyectos personales como Gordon para la ejecución en navegadores de animaciones hechas en Flash utilizando sólo JavaScript.
7) La conversión del motor de Quake II para su ejecución con Google Web Toolkit mediante JavaScript en la parte cliente, con cierta tecnología auxiliar, y Java en la parte servidora.

Ufffff.......

Dejo a cada cual la interpretación de las decisiones empresariales y su conveniencia, yo prefiero centrarme en la parte técnica. Y más concretamente en la ejecución nativa de aplicaciones Flash sobre un navegador con tecnologías estándar frente al uso de plugins específicos.

El formato SWF
El punto de partida es que las herramientas que desarrolla la propia Adobe para la creación de "películas" Flash están muy establecidas en el mercado por su facilidad de uso y alta productividad. Existe una base enorme de aplicaciones ya desarrolladas bajo esta tecnología, y se quiere reducir la inversión necesaria en tiempo y dinero para ejecutarlas en otros entornos distintos para los que fueron originalmente diseñadas, como el caso de los dispositivos móviles.

Ahora bien, ¿qué tiene dentro un fichero "Flash"?, y sobre todo, ¿cómo podemos sustituirlos utilizando tecnología estándar?

Los ficheros que contienen las aplicaciones para Flash tienen extensión SWF. Y las especificaciones técnicas de este formato de archivos son definidas por Adobe que las hace pública con cada nueva versión. Muy a grandes rasgos, lo que contiene un fichero SWF es una cabecera y una serie de bloques denominados tags. Cada tag es de un tipo que determina la información que contiene y como debe interpretarse. Hay tags que contienen recursos, como imágenes, sonidos, músicas, vídeos, fuentes de texto, o cualquier tipo de información binaria arbitraria. Otros tags que contienen la definición y posicionamiento de determinados objetos, como botones, figuras, textos, o estilos creados con el IDE. Y otros últimos tags que contienen acciones para el control de la reproducción, como saltar a un determinado frame por ejemplo, e incluso código compilado en ActionScript.

En consecuencia, simplificando muchísimo el asunto, el plugin de Adobe consta de al menos cuatro elementos principales: un parser de ficheros SWF, un motor de control y renderizado, una máquina virtual para ejecutar ActionScript, y un conjunto de librerías que proporcionan el runtime que utiliza el código ActionScript.

Un Parser
Pero vayamos por partes. ¿Se puede parsear un fichero SWF desde JavaScript?

En líneas generales, la tarea de hacer un parser de ficheros SWF no es complicada, y lo digo por experiencia propia. No sólo porque exista un documento que describa en detalle el formato, sino porque hay bastante código fuente desarrollado que puede utilizarse como referencia. Empezando por las clases Java de SWFUtils liberadas como código abierto por parte de Adobe dentro del Flex SDK. Montar el proyecto y empezar a depurar la carga de un fichero SWF paso a paso es cuestión de minutos. Y de hecho, es bastante recomendable hacerlo, ya que los fuentes contienen información adicional que no se encuentra en la documentación oficial.

Hacer un parser equivalente en JavaScript no es complicado, y vuelvo a hablar por experiencia propia. Aunque en este aspecto la referencia, e hilo conductor de este post, es el proyecto Gordon de Tobias Schneider, también de código abierto. Lo que hace Gordon es una petición del fichero SWF al servidor web a través de un objeto XMLHttpRequest y procesa el bloque de datos recibidos. Los ficheros SWF están comprimidos en formato zip, por lo que antes los descomprime, también desde JavaScript, mediante la librería inflate de Masanao Izumo un hack consistente en añadir una cabecera PNG a los datos y asignándoselos a una etiqueta <img>. Una vez descomprimido en memoria se extrae y procesa cada tag de forma individual.

Los Recursos Embebidos
A medida que se va leyendo el contenido de un fichero SWF, ¿un navegador puede procesar de forma nativa todos los recursos embebidos dentro de un fichero SWF? Es más, ¿se pueden instanciar de forma dinámica con JavaScript?

Los formatos de imágenes soportados por Flash son JPEG, PNG y GIF. En HTML se pueden referenciar con la tradicional etiqueta <img>. Y con JavaScript se pueden leer e instanciar de forma dinámica utilizando el elemento canvas de HTML5 a través de su CanvasRenderingContext2D para acabar referenciándolas luego por su uri.

var context = canvas.getContext("2d");
var imageData = context.createImageData(width, height);
imageData.data = bitmap;
context.putImageData(imageData, 0, 0);
var uri = canvas.toDataURL();

Los sonidos se pueden reproducir en HTML5 mediante la etiqueta <audio>. Aunque otro cantar son los formatos soportados y los codecs disponibles. Flash soporta ADPCM, MP3, Nellymoser y Speex. En HTML5 se pueden embeber sonidos mediante una referencia en el HTML del cliente, e incluso reproducirlos usando streaming.

<audio src="audio.spx" type="audio/ogg; codecs=speex">
</audio>

Lo que parece estar más limitado actualmente en JavaScript es la creación dinámica de sonidos, ya que hasta donde yo alcanzo, no existe un método estándar que permita instanciarlos en tiempo de ejecución con el objetivo de obtener una referencia que alimente al atributo "src" de la etiqueta. Aunque incluso para esto hay una solución: el uso de "data URI", tal y como se describe en el RFC2397. Es decir, serializando el stream de bytes del sonido en una cadena de texto en base 64.

src="data:video/ogg,OggS%00%02%00%00 ..."

Como nota al margen, comentar que Google utiliza esta técnica con las pequeñas imágenes que presenta a veces en la página de resultados de su famoso buscador.

Flash soporta vídeo en formato Sorenson H.263. Y en HTML5 se pueden reproducir mediante la etiqueta <video>. Aunque la "guerra de los codecs de vídeo" ha dejado un poco coja la especificación del estándar, al no exigir ninguna implementación concreta. En cualquier caso, en HTML5 los ficheros de vídeo se referencian de igual forma que los ficheros de audio.

<video src="videofile.ogg">
</video>

Tanto los sonidos como los vídeos son tratados de una manera uniforme por HTML5, con funciones accesibles en JavaScript que permiten gestionarlos (play, pause, seek, volume, ...), y eventos relacionados con los cambios que se producen en los mismos (onplay, onseeked, onvolumechange, ...)

var audio = new Audio("audio.ogg");
audio.volume = .5;
audio.play();

Las fuentes de texto utilizadas dentro de un fichero SWF pueden tomarse del PC local donde se ejecute el reproductor, o pueden ir embebidas dentro del propio fichero SWF. Y aunque nada haga sospecharlo, aquí es donde el asunto se pone interesante. Hasta donde yo alcanzo, no se pueden instanciar fuentes de forma dinámica con HTML5. La definición de las fuentes dentro de los navegadores es cosa de CSS. Con la propiedad "font-family" se pueden tomar las fuentes del PC local, y con la regla "@font-face" de un servidor web. Y repito, esto no es parte de HTML5, sino de CSS3.

@font-face {
  font-family: customFont;
  src: url(customFont.ttf);
}

Y más interesante todavía. Si que se pueden generar fuentes de texto de forma dinámica con JavaScript, pero utilizando SVG, una tecnología totalmente distinta, mucho más antigua, y que tampoco es parte de HTML5.

En los ficheros SWF se almacenan las secuencias de trazos que deben dibujarse para representar cada carácter de cada fuente de texto de forma individual. Y esas "secuencias", llamadas tradicionalmente "paths", juegan un papel importante dentro de todo este circo tecnológico. La definición de fuentes en SVG se realiza de una forma bastante natural a partir de ellos. SVG es una tecnología para gráficos vectoriales, y en este aspecto es mucho más similar a la idea original de Flash que el elemento canvas de HTML5.

El resto de recursos que pueden encontrarse dentro de un fichero SWF son las propias películas en sí que se construyen con el IDE. La parte más importante de Flash. La definición de cada fotograma de cada película desglosada hasta el mínimo detalle. El conjunto de cada primitiva básica que se utiliza (línea, rectángulo, círculo, ...) junto con su estilo (color, trama, efecto, ...) y su matriz de transformación (traslación, rotación, escalado, ...). Un vector de objetos y comandos que nuevamente tiene un equivalente natural en los paths de SVG.

En SVG se puede indicar una lista de comandos de forma abreviada asociada a un elemento:

<path d="M 100 100 L 300 100 L 200 300 z" />

El equivalente con HTML5 es ir llamando función a función:

context.beginPath();
context.moveTo(10, 100);
context.lineTo(300, 100);
context.lineTo(200, 300);
context.closePath();

El hecho de que el formato final en que se almacenan las películas de Flash se representen mejor en SVG que utilizando el elemento canvas de HTML5 es bastante significativo. Quizás deba ser este el camino a seguir. Y me refiero al hecho de que la solución puede pasar por usar una tecnología estándar específica para la representación de gráficos vectoriales en el navegador, no sólo el elemento canvas de HTML5 de propósito más general y posiblemente más orientado a la gestión de gráficos rasterizados.

SVG además soporta animaciones, eventos, e incluso la ejecución de scripts.

Todo lo demás
Aún quedan un par de puntos interesantes por tratar. ¿Puede un navegador interpretar código ActionScript compilado? ¿E implementar todas las clases de las librerías del runtime de Flash?

El plugin de Flash contiene una interesante pieza de código llamada AVM2 (ActionScript Virtual Machine 2). La máquina virtual encargada de interpretar el código ActionScript 3 compilado que se encuentra dentro de los ficheros SWF. Adobe, en un movimiento bastante interesado interesante por su parte, lo liberó como código abierto cediéndolo a la fundación Mozilla bajo la forma de un proyecto de colaboración llamado Tamarin. De igual forma, las especificaciones técnicas del lenguage también son públicas.

A Flash siempre le estará faltando la ejecución nativa de ActionScript 3 en los navegadores. Liberando su máquina virtual se facilitaba que los navegadores la incorporasen permitiendo dicha ejecución, pero eso es algo que de momento no ha ocurrido. Aunque no estoy yo muy convencido de que vayamos a tener JavaScript como lenguaje de referencia para siempre en los navegadores. La etiqueta <script> de HTML se creó para dar cabida a cualquier tipo de lenguaje, y ya va siendo que la utilicemos. ActionScript es bastante conocido por seguir el estándar ECMA-262, pero no tanto por implementar también el ECMA-357, lo que añade tipos, funciones y operadores para procesar XML de forma nativa dentro del propio lenguaje.

Realizar un intérprete de ActionScript en JavaScript es factible, pero el rendimiento que se pueda obtener es discutible. Es algo sobre lo que no tengo una idea muy clara definida. Habrá que esperar a que algún proyecto con cierta solvencia lo implemente, posiblemente Gordon, o hacer algunas pruebas por mi cuenta y riesgo.

Y por último, la pieza que completa el puzzle es el runtime de Flash. Quien esté dispuesto a implementar un visor de ficheros SWF ha de estar dispuesto a implementar todos los paquetes de clases de máximo nivel que ofrece el Flash Player API, o al menos los de uso más frecuente. Tarea que ya han acometido proyectos como Gnash, el reproductor GNU de ficheros SWF.

Concluyendo
La línea argumental que se ha seguido hasta el momento se basa en realizar todo el procesamiento de los ficheros SWF en la parte cliente, como reemplazo natural del plugin de Flash. No obstante, también cabe la posibilidad de que los ficheros se "parseen" en el servidor, con la idea de extraer los recursos que tienen embebidos y utilizar sus urls correspondientes dentro la página HTML del cliente de la forma tradicional. El problema es que esto conllevaría a que se produciesen muchas más peticiones del cliente al servidor, un tráfico de red mucho mayor, y se perdería las ventajas de tener todo en un único fichero siguiendo el formato compacto de Adobe. Pero por otra parte, realizar todo el proceso en el cliente con JavaScript puede ser prohibitivo.

Pero si el formato SWF no se adapta a nuestras necesidades, y resulta un tanto pesado su tratamiento para conseguir unas simples animaciones, ¿por qué no usamos otro? ¡Precisamente es lo que está haciendo Adobe! En el famoso vídeo, sobre el minuto 04:14, se puede ver que en realidad la animación hecha en Flash se exporta a un fichero en formato FXG. Un formato definido por la propia Adobe, y cuyas especificaciones técnicas son públicas.

El formato FXG copia sigue la línea marcada por SVG. Nuevamente SVG. Y hace que sea más fácil de procesar por parte de JavaScript. En el vídeo se aprecia la llamada a una función renderFlashFXG importada de un fichero "renderFXG.js".

Cambiar el formato SWF a otro que tenga más cosas preprocesadas de cara a su tratamiento por parte de JavaScript puede ser la solución. Puede que no sea tan compacto como el original, pero con el previsible e inevitable aumento del ancho de banda disponible esto no debería importar.

Un apunte final
En cualquier caso, lo que no deberían perder de vista algunos desarrolladores, sobre todo los que basan su modelo de negocio en la publicidad online, típico en los juegos Flash, es que lo que les interesa controlar no es sólo el número total de terminales vendidos por una determinada empresa, sino tambíen los que mayor tráfico de red generan, ya que deberían ser estos los que le permitan obtener mayores beneficios. Y a este respecto, el aumento del tráfico de red generado por terminales con Android es algo muy a tener en cuenta.