Web

ArinOs

ArinOs es un proyecto que empecé hace bastante tiempo, cuando apareció por aquel entonces ChromeOS. La idea de ChromeOs me parecía interesante pero también tenia mis dudas sobre las soluciones “todo en la nube” en cuento a privacidad y libertad. Además me parecía difícil de meter mano en su interior y mas aun en caso de querer integrarlo en nuevos dispositivos.

Es por eso que el objetivo de ArinOs era ofrecer una plataforma sencilla en su concepto y software que permitiera poder crear un Sistema Operativo Web de forma sencilla sobre todo orientado a poder ser adaptado y extendido fácilmente a diferentes dispositivos, como puede ser un punto de información, una maquina dispensadora de billetes de transporte o un televisor.

ArinOs Logo

La idea era simple, y en cierta medida existían aproximaciones similares en el caso de los terminales móviles como era PhoneGap: Extender la API de JavaScript para poder conectarnos con un “core” externo que extendiera las funcionalidades de nuestro navegador y así poder acceder mediante JavaScript a código de alto nivel que nos permitiera por ejemplo acceder a dispositivos industriales o funcionalidad especifica no común.

Por tanto podemos decir que ArinOs se basa en un navegador extendido que permite comunicarnos con un servidor (en este caso en Python) que interpreta los comandos, los ejecuta y nos devuelve el resultado. Por tanto podemos crear nuestra librería en Python, por un lado, y en JavaScript, por otro, y comunicarlas a través de ArinOS.

ArinOs schema

La elección de Python como entrono para el servidor, se justificaba, basándonos en la posibilidad de cargar código de forma dinámica y que además nos permite acceder a librerías externas mediante ctypes.

La intención de ArinOs, por tanto no es competir con B2G (FirefoxOs) o ChromeOs, ni mucho menos, sino la de ofrecer una herramienta que permita introducir dispositivos marginales al mundo de las aplicaciones y sistemas operativos HTML5/WEB.

Todavía este proyecto esta en una fase muy temprana y solo tengo partes parciales de la funcionalidad. Espero sacar algún rato entre todos mis proyectos personales para seguir avanzando ya que queda mucho trabajo, como implementar diferentes métodos de comunicación entre el cliente y el servidor (síncrona o asíncrona), establecer una estructura del servidor correcta donde sea sencillo incluir librerías tanto en Python como en código nativo o definir una estructura para aplicaciones y que incluso, en un futuro, que tanto las aplicaciones de Chrome como de FirefoxOs puedan funcionar en este sistema.

De momento podéis acceder a su repositorio de GitHub en la siguiente dirección.

https://github.com/ikeralbeniz/ArinOs

Firefox TV: (Firefox OS + Raspberry Pi) * Set-Top-Box

Cuando llegó mi Raspberry Pi, me surgieron miles de ideas de como aprovechar este mini-ordenador que cuesta al rededor de 35€. Por un lado se me ocurrió compilar un receptor software de DVB-T2 en el que estamos trabajando en el laboratorio y también se me ocurrió utilizarlo como centro multimedia con un cliente web de Bittorrent para añadir mis torrents desde el trabajo. Pero finalmente la idea que mas me motivaba y que mezclaba varios proyectos que tenia entre manos era la de hacer correr el sistema operativo B2G (Boot To Gecko) o también llamado Firefox OS en mi Raspberry Pi. Firefox Os es un nuevo sistema operativo WEB basado en en el motor de Firefox (Gecko), que tiene como intención competir con Android y IOs en el mundo de los terminales móviles inteligentes. En este caso la apuesta de Firefox OS es que todas las aplicaciones e incluso el sistema operativo (o por lo menos la parte de interfaz) sea Web, estrategia mas consolidada en los sistemas operativos de los televisores inteligentes y que viene impulsada por las capacidades que ofrece HTML5.

Firefox OS

Teniendo en cuenta todo esto ¿Por que no desarrollar un pequeño Set-Top-Box utilizando una Raspberry Pi en el que corra Firefox Os adaptado a las características (dimensión e interfaces de entrada) de un televisor?. La idea es ofrecer una alternativa low-cost y lo mas OpenSource posible a soluciones del estilo smart-hub de Samsung, con todas las posibilidades que ofrecería en cuanto a explotación de nuestros televisores como centro de ocio, trabajo o incluso servicios de tele-asistencia.

En siguiente posts explicaré los pasos que estoy siguiendo para configurar mi Firefox TV ya que por ahora tengo una pequeña aproximación que le faltan algunos retoques como se puede ver en el siguiente vídeo.

Desproteger Web-Fonts con Google Docs

Como comente en un articulo anterior, la ultima versión de CSS (CSS3) permite utilizar fuentes propias en nuestras paginas Web gracias al elemento @fotn-face. Por tanto si disponemos de una fuente MiFuente.ttf podremos utilizarla en nuestra página Web de la siguiente manera.

@font-face {
	font-family:Mifuente;
	src: url(‘MiFuente.ttf’);
}

@font-face {
	font-family: Mifuente;
	font-weight: bold; 
	src: url('MiFuente.ttf’);
}

Que luego podremos usarla de igual manera que otras fuentes

body{
	font-family: Mifuente;
}

Al igual que en las imágenes es posible utilizar la etiqueta data y adjuntar la fuente en base64.

@font-face {
	font-family: ' Mifuente ';
	src:          url(data:font/truetype;charset=utf-8;base64,AAEAAAAQAQAABAAARkZUTVtQ+h4AAAEMAA………..AMrRFmwFCs=) format('truetype');
}

Por eso muchos diseñadores Web utilizan esta ultima formular para intentar dificultar el acceso a la fuente, en caso de que esta haya sido desarrollada por el. Aun así es relativamente sencillo poder copiar la fuente y usarla en nuestra Web, pero.. ¿y usarla en nuestro PC? En este caso seria tan sencillo como coger todo el stream en base64 y decodificarlo. Existen librerías para todos los lenguajes de programación que permiten convertir de/a base64, pero si queréis ir a los sencillo, tenéis un montón de conversores online disponibles. Yo recomiendo el siguiente ya que permite convertir a un binario.

http://www.motobit.com/util/base64-decoder-encoder.asp

En la imagen siguiente podemos ver como podemos convertir una fuente en base64 a un binario TTF.

Una vez tenemos la fuente podremos usarla con todas nuestra aplicaciones (Si estamos en Windows deberemos arrástrala a la carpeta FONTS). Aun así, algunos diseñadores de fuentes, utilizan técnicas de ofuscación que permiten que una fuente pueda ser utilizada en nuestra Web pero no en nuestro PC. Y esto es posible porque existe software capaz de alterar la estructura interna de las fuentes de tal forma que puede ser reenderizada por los navegadores pero no por los PCs.

En estos casos deberemos realizar los siguientes pasos:

  1. Usando Gmail nos enviaremos la fuente (con extensión TTF) a nosotros mismos
  2. Gmail nos mostrara nuestro adjunto con las típicas opciones de VER o DESCARGAR
  3. Pulsaremos VER y se nos mostrara la fuente con el visualizador de fuentes de Google Docs
  4. En el menú FILE pulsaremos en PRINT (PDF)
  5. Cancelaremos la impresión y guardaremos el PDF

Ya tenemos nuestra fuente en formato PDF, por lo que ahora necesitaremos convertirla de PDF a otros formatos que nos sean mas útiles. Para ello usaremos otro servicio online:

http://onlinefontconverter.com/myfonts.php

Este servicio nos permitirá convertir nuestra fuente en PDF a una gran variedad de fuentes, entre las cuales nos interesa:

  • OTF: Formato Open Source de nuestra fuente, 100% compatible con sistema GNU/Linux
  • TTF: Formato típico de fuente que solemos tener instalado en sistemas Windows
  • DFONT: Formato de fuente para MAC
  • SVG: Formato de fuente VECTORIAL propuesto por W3C, muy útil si utilizas inkscape ya que es posible modificarla.

Espero que este truco os sea de gran utilidad ;)

Iconic Stroke: una fuente de iconos muy interesante

En las ultima semanas tengo el blog un poco parado ya que estoy a tope con otros proyectos. Entre ellos, algunos relacionados, como no, con aplicaciones móviles. Uno de estos proyectos lo estoy realizando junto a Javier Jimenez que es un experto en HTML5, JavaScript y todo lo relacionado con UX en general.

Diseñando una aplicación, a Javi se le ocurrió la idea de usar una fuente de Iconos en vez de imágenes para aligerar la aplicación y que fuera mas fácil de diseñar y trabajar con transparencias etc… La fuente en cuestión es la Iconic Stroke y las posibilidades que ofrece creo que son muy interesantes, no solo de cara a no tener que usar imagenes, ya que CSS3 nos permite usar fuente propias, sino que, de cara a diseñar iconos también nos puede ser de gran ayuda. Por eso os dejo aquí un link de donde podéis descargar esta fuente y un ejemplo de los iconos, cargando la fuente con CSS3.

http://googlefontdirectory.googlecode.com/hg/iconic/IconicStroke.ttf

Solo veréis los iconos si usáis un navegador que soporte CSS3 como Firefox, Crome o Safari.
! # $ % & ( ) *
! # $ % & ( ) *
+ , - . / 0 1 2 3 4
+ , - . / 0 1 2 3 4
5 6 7 8 9 : ; < = >
5 6 7 8 9 : ; < = >
? @ A B C D E F G H
? @ A B C D E F G H
I J K L M N O P Q R
I J K L M N O P Q R
S T U V W X Y Z [ \
S T U V W X Y Z [ \
] ^ _ ` a b c d e f
] ^ _ ` a b c d e f
g h i j k l m n o p
g h i j k l m n o p
q r s t u v w x y z
q r s t u v w x y z
{ | } ~
{ | } ~

XML, JSON… Protocol Buffers

El intercambio de información en los sistema distribuidos o en cualquier tipo de servicio, es un tema clave, para lo cual, a lo largo de los años han surgido un montón de soluciones y tecnologías. Hasta ahora la formula mas utilizada era el formato XML, en muchos caso envueltos de una capa extra de gestión, como son el caso de SOAP o WCF impulsados por Microsoft. En general XML sigue siendo claro dominador a la hora de proveer información estructurada. Los feeds de nuestra Web, Sitemaps o muchos proyectos de OpenData proporcionan información en este formato.

A pesar de ello, XML para muchos es un formato engorroso, su interpretación o extracción de la información, a pesar de que existen muchas librerías que lo facilitan, suele traer de cabeza a muchos desarrolladores y cada vez, tiene mas presencia la tecnología JSON a la hora de acceder a servidores de datos, APIs, etc.. la facilidad para generar y recuperar la información que ofrece JSON esta haciendo que sea estándar de facto en prácticamente todos los servicios que permiten interactuar con servicios terceros. Empresas como Facebook o WOT, apuestan por JSON para el acceso a la información, sobre todo cuando esta se genera de forma dinámica. Aun así en la mayoría de los casos sigue estando opcional el acceso s los datos en XML.

En este articulo trato de presentar Protocol Buffers, una solución que pretende ser mas optima, en lo que respecta a trafico de datos, y mas opaca, si lo que interesa es que la información no viaje de forma totalmente visible. Personalmente, creo que lo mas interesante de este sistema es que reduce considerablemente el volumen de trafico generado en cada petición de información, lo cual es muy interesante si lo que queremos es desarrollar un servicio explotado por aplicaciones móviles, donde podemos conseguir aplicaciones mas fluidas, si conseguimos que el tiempo de conexión sea el menor posible.

En mi caso, Protocol Buffers me parece una solución interesante, ya que suelo usar Google App Engine, como web service generador de datos, y para terminales móviles suelo trabajar con Android, para el cual existen librerias. Además hay que tener en cuenta que muchas de las aplicaciones de serie en Android como el Market usan Protocol Buffers internamente (a pesar de que oficialmente la SDK de Android no ofrece acceso a dichas librerías).

En próximos post explicare como manejar Protocol Buffers en python.

Posibilidad de Cross-Site Scripting mediante Jquery.getJson() para OpenBizkaibus API

He actualizado la API de OpenBizkaibus para que sea posible realizar peticiones getJson() desde otras paginas. A continuación se muestra un ejemplo de cómo realizar esta petición. En el ejemplo se muestra como completar un selectbox con el listado de municipios de la consulta Consultar_FamiliasCentros y una vez seleccionado un municipio se completará otro selectbox con las lineas mediante LineasMunicipio.

[html]
<!DOCTYPE html>
<html>
<head>
<style>img{ height: 100px; float: left; }</style>
<script src="http://code.jquery.com/jquery-1.4.4.js"></script>
</head>
<body>

<p>
<select name="town" id="townsel">
</select>
</p>
<p>
<select name="lines" id="linesselsel">
</select>
</p>
<script>

$(document).ready(function(){

//Get Town list
$.getJSON("http://openbizkaibus.appspot.com/api/Consultar_FamiliasCentros?jsoncallback=?",
{},
function(data) {
$.each(data, function(i,object){
$.each(object.Registros, function(i,registro){
$(‘#townsel’).append(new Option(registro.DescripcionElemento, registro.CodigoElemento, true, true));
});
});
});

//
$("#townsel").change(function()
{
var CodigoElemento
var DescripcionElemento

CodigoElemento = $("#townsel").val();
DescripcionElemento = $("#townsel option:selected").text();

if(CodigoElemento.length < 2){
CodigoElemento = "00"+CodigoElemento;
}
if(CodigoElemento.length < 3){
CodigoElemento = "0"+CodigoElemento;
}

$.getJSON("http://openbizkaibus.appspot.com/api/LineasMunicipio?jsoncallback=?",
{
codmunicipio: CodigoElemento,
descmunicipio: DescripcionElemento
},
function(data) {
$(‘#linesselsel’).find(‘option’).remove().end();
$.each(data, function(i,object){
$(‘#linesselsel’).append(new Option(object.DenominacionLinea, object.CodigoLinea, true, true));
});
});
});
});
</script>
</body>
</html>
[/html]

Para ver el ejemplo funcionando pulsar aquí.

Haciendo hablar a Twitter

Hace tiempo que tenia pendiente hacer un mashup de la librería javascript de Twitter, Google Translate y las nuevas oportunidades de HTML5. Pues al final me he animado y no ha sido muy complicado: por un lado detectar el idioma del tweet, por otro lanzar la ejecución del el audio, y por ultimo integrar estas dos funciones el la librería widget.js que ofrece Twitter.

Esta es una primera aproximación, que puede ser mejorada considerablemente. A continuación el código y ejemplo (esta probado que funciona en chrome).

El widget.js modificado se puede descargar aqui.

[html]
<html>
<head>
</head>
<body>
<script type="text/javascript" src="http://www.google.com/jsapi?key=AIzaSyBMhwGvMwDbGgXIVU4SI_aVy7AjeGFnorw"></script>
<script type="text/javascript">
google.load("language", "1");

var auxtest = "";
var elapsedtww = 0;

function cleanHtml(html){
var result = html.replace(/<.*?>(.*?)<\/.*?>/g, function(a,s){return s;});
var result2 = result.replace(/#(.*?)/g, function(a,s){return s;});
var result3 = result2.replace(/@(.*?)/g, function(a,s){return s;});
return cleanLinks(result3);
}

function cleanLinks(text){
var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
return text.replace(exp,"");
}

function sleep(milliSeconds){
var startTime = new Date().getTime(); // get the current time
while (new Date().getTime() < startTime + milliSeconds); // hog cpu
}

function submitGtext(text) {
while (elapsedtww > 0){
sleep(1000);
}
elapsedtww = 1;
auxtest = cleanHtml(text);
google.language.detect(auxtest, playVoice);
return false;
}

function playVoice(result){
var langcode = ‘en’;
if (result.language) {
var language = ‘unnamed’;
for (l in google.language.Languages) {
if (google.language.Languages[l] == result.language) {
language = l;
break;
}
}

if(language == "SPANISH"){
langcode = ‘es’;
}
if(language == "PORTUGUESE"){
langcode = ‘pt’;
}
if(language == "GERMAN"){
langcode = ‘de’;
}
}
var audio_file = document.getElementById(‘voice’);
while(audio_file.currentTime < audio_file.duration && audio_file.currentTime != 0){
sleep(100);
}
audio_file.src = ‘http://translate.google.com/translate_tts?q=’+escape(html_entity_decode(auxtest))+’&tl=’+langcode;
document.getElementById(‘audiopaths’).value = document.getElementById(‘audiopaths’).value +"\r\n\r\n"+html_entity_decode(auxtest);
audio_file.load();
audio_file.play();
elapsedtww = 0;
}

function html_entity_decode(str)
{
try
{
var tarea=document.createElement(‘textarea’);
tarea.innerHTML = str; return tarea.value;
tarea.parentNode.removeChild(tarea);
}
catch(e)
{
//for IE add <div id="htmlconverter" style="display:none;"></div> to the page
document.getElementById("htmlconverter").innerHTML = ‘<textarea id="innerConverter">’ + str + ‘</textarea>’;
var content = document.getElementById("innerConverter").value;
document.getElementById("htmlconverter").innerHTML = "";
return content;
}
}
</script>
<script src="http://www.ikeralbeniz.net/wp-content/uploads/2010/12/widget.js"></script>
<script>
new TWTR.Widget({
version: 2,
type: ‘search’,
search: ‘meneame_net’,
interval: 9000,
title: ‘Test’,
subject: ‘Test de GoogleTranslate + twiiter’,
width: 250,
height: 300,
theme: {
shell: {
background: ‘#8ec1da’,
color: ‘#ffffff’
},
tweets: {
background: ‘#ffffff’,
color: ‘#444444′,
links: ‘#1985b5′
}
},
features: {
scrollbar: false,
loop: true,
live: true,
hashtags: true,
timestamp: true,
avatars: true,
toptweets: true,
behavior: ‘default’
}
}).render().start();
</script>
<audio id="voice" src="">
<h3>Este ejemplo solo funciona con navegadores que soportan HTML5 como CHROME</h3>
</audio>
<body>
</html>
[/html]




Este ejemplo solo funciona con navegadores que soportan HTML5 como CHROME

Ahora entiendo porque la gente usa FeedBurner

En los últimos meses mi Hosting me estaba cobrando por sobrepasar el tráfico mensual contratado, lo cual me extrañaba bastante ya que el tráfico que soporta mi blog no es muy grande. Así que hoy me he puesto en contacto con soporte y les he pedido que me facilitaran unos logs de trafico para saber que paginas son las que mas se descargan para ver si era cierto o si tenia sentido los datos de trafico que comentaban (ya que la información que yo manejaba a través de Google Analytics era bastante contradictoria). Daba por hecho de que había elementos que no están controlados por Google Analytics ya que este se basa en un javascript que solo esta presente en contenidos HTML.

La respuesta tardo un par de horas, y era que las subscripciones RSS a mi pagina consumían casi 1GB al mes..

# Hits KBytes URL
1 7240 13.16% 927842 57.01% /feed/
2 7395 13.45% 178363 10.96% /
3 722 1.31% 28968 1.78% /wp-content/……/header_footer.jpg
4 723 1.31% 25916 1.59% /wp-content/……/light.gif
5 1042 1.89% 15324 0.94% /wp-content/……/style.css

Como se suele decir “me quede anonadado”, y antes de que el mes que viene me cobrem otro plus me he puesto manos a la obra a instalar un plugin de FeedBurner para WordPress. Este plugin lo que hace es redirigir los feeds a FeedBurner de tal modo que sea este quien soporta la carga de trafico. Además genera estadísticas que pueden ser interesantes para saber cuanta gente me sigue.

Actualización [07/12/2010]:

En el siguiente grafico es posible apreciar el decrementos sustancial del ancho de banda consumido por el RSS después redirigir el trafico a FeedBurner. Hay que tener en cuenta que FeedBurner genera un tráfico constante de actualización, con lo que con pocos usuarios sindicados a tu RSS la mejora en consumo de ancho de banda es menor.

Jugando con HTML5: Animaciones

Seguimos con HTML5, pero en este caso vamos a jugar con animaciones. En este caso vamos a hacer una animación simple controlada mediante el teclado. La idea es establecer las bases para hacer un juego sencillo (tipo RPG). Como no podía ser de otra forma, nos hemos basado en el juego Zelda para hacer nuestra demostración (con un poco de suerte alguno se anima y se curra un Zelda en HTML5..).

La forma en la que vamos a hacer la animación es muy sencilla: vamos a dibujar una imagen de Zelda en un canvas con un fondo determinado. Y vamos a capturar los eventos Keydown. En función de la tecla seleccionada (arriba, abajo, derecha e izquierda) cambiaremos la imagen de Zelda y la moveremos. Para generar el efecto de animación, lo que haremos es refrescar el canvas borrándolo y redibujandolo cada vez que pulsemos una tecla.

Una aproximación mas optimizada seria solo borrara el área a redibujar, pero en nuestro caso de cara a simplificar el código, borraremos y dibujaremos el canvas completo.

Además en nuestro caso solo redibujaremos el canvas cada vez que se pulse una tecla ya que solo tenemos un elemento móvil que se controla mediante teclado. En caso de tener otros elementos móviles no controlables (los enemigos.. hacer que el mar se mueva..) deberemos redibujar el canvas automáticamente con un timer: setInterval(funcion tiempo);

Inicializar la Animación

Inicializaremos las variables globales y dibujaremos la animación por primera vez. En el caso de que el redibujado sea automático, como hemos explicado antes, sera aquí donde lancemos el timer.

[code lang="javascript"]
var x = 0;
var y = 0;
var iangle = 0;
var pravangle = 0;
var step = 0;
var imgsrc = "zd_dwn.png";
window.addEventListener('keydown',doKeyDown,true);
window.onload = function() {
drawZelda();
//return setInterval(drawZelda, 500);
}
[/code]

Dibujado de la Animación

Basicamente lo que hacemos es definir el area del canvas y en función de la dirección de las teclas que pulsemos, y cantas veces seguidas se pulse, se nos mostrara una imagen u otra en una posición definida por x e y (que controlaremos con el teclado). Es decir, cuando se cambia de dirección de avance se muestra un icono, y si esta dirección se mantiene, se intercalan diferentes imagenes para generar la animación de avance.

[code lang="javascript"]
unction drawZelda(){
var drawingCanvas = document.getElementById('myDrawing');
drawingCanvas.setAttribute('width', 500);
drawingCanvas.setAttribute('height', 500);
// Check the element is in the DOM and the browser supports canvas
if(drawingCanvas.getContext) {
// Initaliase a 2-dimensional drawing context
var ctx = drawingCanvas.getContext('2d');
ctx.clearRect(0, 0, 500, 500);
ctx.beginPath();
switch(iangle){
case 0:
if(iangle != pravangle){
imgsrc = "zd_dwn.png";
pravangle = iangle;
step = 0;
}else{
if(step > 0){
if(step == 1){
imgsrc = "zd_dwn1.png";
}else if(step == 2){
imgsrc = "zd_dwn.png";
}else{
imgsrc = "zd_dwn2.png";
}
}
}
break;
case 0.5:
if(iangle != pravangle){
imgsrc = "zd_rgt.png";
pravangle = iangle;
step = 0;
}else{
if(step > 0){
if(step == 1){
imgsrc = "zd_rgt1.png";
}else if(step == 2){
imgsrc = "zd_rgt.png";
}else{
imgsrc = "zd_rgt2.png";
}
}
}
break;
case 1:
if(iangle != pravangle){
imgsrc = "zd_up.png";
pravangle = iangle;
step = 0;
}else{
if(step > 0){
if(step == 1){
imgsrc = "zd_up1.png";
}else if(step == 2){
imgsrc = "zd_up.png";
}else{
imgsrc = "zd_up2.png";
}
}else{
imgsrc = "zd_up1.png";
}
}
break;
case 1.5:
if(iangle != pravangle){
imgsrc = "zd_lft.png";
pravangle = iangle;
step = 0;
}else{
if(step > 0){
if(step == 1){
imgsrc = "zd_lft1.png";
}else if(step == 2){
imgsrc = "zd_lft.png";
}else{
imgsrc = "zd_lft2.png";
}
}
}
break;
}
ctx.fillStyle = "#ff0000";
var myImage = new Image();
myImage.onload = function() {
ctx.drawImage(myImage, 238+x , 238+y, 24, 24);
}
myImage.src = imgsrc;
ctx.stroke();
ctx.fill();
ctx.closePath();
}
}
[/code]

Capturando el teclado

Esta función lo que hace basicamente es cambiar la posición de Zelda, en función de las teclas que se pulsan. La forma de cambiar la posición de incrementando o decrementando x e y, que son variables globales que se usan en drawZelda para dibujar a nuestros personaje.

Después de pulsar una tecla se regenera la imagen para que se agan efectivos los cambios. Esto no haría falta si hubiéramos definido un timer para el redibujado.

[code lang="javascript"]
function doKeyDown(evt){
if(evt.keyCode >= 37 && evt.keyCode <= 40){
switch (evt.keyCode) {
case 38: /* Up arrow was pressed */
if(y > -239){
y--;
iangle = 1;
}
break;
case 40: /* Down arrow was pressed */
if(y < 237){
y++;
iangle = 0;
}
break;
case 37: /* Left arrow was pressed */
if(x > -239){
x--;
iangle = 1.5;
}
break;
case 39: /* Right arrow was pressed */
if(x < 237){
x++;
iangle = 0.5;
}
break;
}
if(step > 0){
if(step == 1){
step = 2;
}else if(step == 2){
step = 3;
}else{
step = 1;
}
}else{
step = 1;
}
drawZelda();
}
}
[/code]

El Ejemplo

Aquí tenemos un ejemplo funcional de lo explicado en este articulo. espero que os sea de gran utilidad y podáis hacer juegos que nos hagan disfrutar a todos. ;)

NOTA: Para que el navegador no haga scroll en algunos navegadores se puede evitar pulsando Ctrl.



Your browser doesn’t support canvas.


Jugando con HTML5: Canvas y SVG (II)

En el artículo anterior analizábamos el formato SVG con lo que ya tenemos nuestras primeras pistas de como parsear una imagen vectorial y dibujarla en un canvas. Por tanto en este artículo vamos a explicar como recorrer el formato SVG con un parser de XML e iremos dibujando cada elemento en el canvas. En la siguientes indicaciones se dará por hecho que el lector tiene conocimientos básicos en HTML y JavaScript.

Lo primero que vamos a hacer es definir una estructura HTML básica sobre la que dibujar:

[code lang="html"]








[/code]

Básicamente disponemos de un documento HTML con un elemento canvas (html5) con el identificador “canvas”. Además le hemos dado estilo añadiéndole un borde negro. La etiqueta BODY tiene definido un método onLoad que hará que se ejecute la función JavaScript draw() al cargarse la pagina. Es en este método draw es donde parsearemos el SVG y dibujaremos en el canvas.

Por tanto pasaremos a explicar cada parte del código de la función draw().

Parser XML

Lo primero que hay que hacer es abrir el SVG y recorrerlo con el DOM XML que soporte nuestro navegador, para ello usamos el siguiente código:

[code lang="javascript"]
if (window.XMLHttpRequest)
{ // code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}
else
{ // code for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.open("GET","test.svg",false);
xmlhttp.send();
xmlDoc=xmlhttp.responseXML;
[/code]

Por tanto a partir de ahora trabajaremos con el objeto xmlDoc que dispone de métodos que nos permitirá recorre la estructura XML de forma sencilla.

Elemento CANVAS

El otro elemento importante de nuestra aplicación es el canvas donde dibujaremos. Por tanto buscaremos nuestro canvas a partir del ID(“canvas”) que definimos y de el obtendremos el contexto “2D” que contiene los métodos para poder dibujar sobre el.

[code lang="javascript"]
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
[/code]

Empezamos a recorrer el XML

Como hemos dicho en la estructura SVG disponemos de una única etiqueta SVG, por tanto podemos obtener dicho elemento mediante el método getElementsByTagName que nos dará un array de elemento SVG. Al haber solo uno, solo trabajaremos con la posición 0 del array. Por tanto podremos acceder a todos los métodos y nodos hijos de la etiqueta SVG a través del elemento xmlDoc.getElementsByTagName("svg")[0]. La etiqueta SVG tiene dos atributos que nos interesan a la hora de inicializar el canvas, y son la anchura y la altura. Por tanto, a través de la función setAttribute definiremos al anchura y altura del canvas:

[code lang="javascript"]
canvas.setAttribute('width',xmlDoc.getElementsByTagName("svg")[0].getAttribute("width"));
canvas.setAttribute('height',xmlDoc.getElementsByTagName("svg")[0].getAttribute("height"));
[/code]

Una vez configurada el área de trabajo, pasaremos a recorrer todas las capas G del documento y dibujaremos todas las figuras de cada capa:

[code lang="javascript"]
var layers = xmlDoc.getElementsByTagName("g");
for(var l = 0; l < layers.length; l++){
var cchildn = xmlDoc.getElementsByTagName("g")[l].childNodes.length;
for(var g = 0; g < cchildn; g++){

….

}
}
[/code]

Es decir, buscamos todos lo elemento G y por cada elemento recorremos todos su subelementos. Para eso obtendremos el numero de nodos hijos y analizaremos cada uno de ellos para saber que tipo de nodos son y como actuar en función de ello.

En el artículo anterior comentamos que solo nos íbamos a centrar en dos tipos de elementos o nodos: los RECT y los PATH, por tanto usando la propiedad nodeName comprobaremos que tipo de nodo es cada nodo hijo y en función de ello ejecutaremos una rutina u otra para dibujar el elemento.

Nodo RECT

En caso de que el nodo sea un rectángulo, podemos comprobarlo de la siguiente manera:

[code lang="javascript"]
if(xmlDoc.getElementsByTagName("g")[l].childNodes[g].nodeName == "rect"){
ctx.beginPath();

ctx.stroke();
ctx.fill();
}
[/code]

Iniciamos con beginPath para indicar que queremos crear una nueva figura (todos los estilos y demás propiedades definidas a partir de ahora solo afectan a este path). Y terminamos con stroke, que dibuja los trazos definidos en el canvas y fill que los rellena.

Como comentamos en el articulo anterior, tanto RECT como PATH tienen un atributo estilo que define el color de fondo de la figura, color de borde, grosor.. por tanto antes que nada deberemos parsear dicho atributo y aplicar los estilos correspondientes:

[code lang="javascript"]
var rstyle = xmlDoc.getElementsByTagName("g")[l].childNodes[g].getAttribute("style").split(';');
for(var j=0; j < rstyle.length; j++){
if(rstyle[j].split(':')[0] == "fill"){
ctx.fillStyle = rstyle[j].split(':')[1];
}

if(rstyle[j].split(':')[0] == "stroke"){
ctx.strokeStyle = rstyle[j].split(':')[1];
}

if(rstyle[j].split(':')[0] == "stroke-width"){
ctx.lineWidth = rstyle[j].split(':')[1];
}
}
[/code]

Por tanto recogeremos los valores fill,stroke y stroke-width del atributo style y por cada uno de ellos definiremos el estilo del path con fillstyle,strokestyle y linewidth respectivamente.

Ya solo nos quedará recoger los datos del rectángulo y dibujarlo.

[code lang="javascript"]
var rwidth = parseInt(xmlDoc.getElementsByTagName("g")[l].childNodes[g].getAttribute("width"));
var rheight = parseInt(xmlDoc.getElementsByTagName("g")[l].childNodes[g].getAttribute("height"));
var rx = parseInt(xmlDoc.getElementsByTagName("g")[l].childNodes[g].getAttribute("x"));
var ry = parseInt(xmlDoc.getElementsByTagName("g")[l].childNodes[g].getAttribute("y"));
ctx.moveTo(rx,ry);
ctx.lineTo(rx+rwidth,ry);
ctx.lineTo(rx+rwidth,ry+rheight);
ctx.lineTo(rx,ry+rheight);
ctx.lineTo(rx,ry-lw);
[/code]

A pesar de que se pueden dibujar rectángulos de muchas formas, nosotros los dibujaremos mediante cuatro líneas, ya que conocemos la posición del vértice superior izquierdo y la anchura y altura del rectángulo. Además esta aproximación permite integrar la posibilidad de bordes redondeados solo con definir un radio de borde y la función quadraticCurveTo (no confundir con la propiedad "lineJoin =round" que redondea la unión entre líneas, aunque en algún caso, de radios muy pequeños, nos puedan dar el mismo resultado).

Nodo PATH

El nodo PATH al igual que el RECT tiene un atributo estilo que lo recorreremos de igual manera, pero en el caso de PATH, como comentamos en el articulo anterior, la estructura del trazo esta descrita en el atributo d, bajo una estructura conocida. Por tanto deberemos recorrer dicha estructura y dibujar los diferentes segmentos en los que se divide el trazo.

[code lang="javascript"]
var datribute = xmlDoc.getElementsByTagName("g")[l].childNodes[g].getAttribute("d");
var datributedata = ""+datribute.replace(/\,/g,' ')+"";
var darray = datributedata.split(" ");
var arc = 0;
for(var j = 0; j < darray.length; j++){
if(darray[j] == "M"){
ctx.moveTo(darray[j+1],darray[j+2]);
j = j+2;
}
if(darray[j] == "L"){
ctx.lineTo(darray[j+1],darray[j+2]);
j = j+2;
}
if(darray[j] == "C"){
ctx.bezierCurveTo(darray[j+1],darray[j+2],darray[j+3],darray[j+4],darray[j+5],darray[j+6]);
j = j+6;
}
if(darray[j] == "A"){
if(arc == 0){
ctx.scale(1, 1);
ctx.arc(parseInt(darray[j+1]), parseInt(darray[j+2]), parseInt(darray[j+6]/2), 0, Math.PI*2, false);
addJsText("ctx.arc("+parseInt(darray[j+1])+","+parseInt(darray[j+2])+","+parseInt(darray[j+6]/2)+", 0, Math.PI*2, false);");
j = j+7;
ctx.stroke();
ctx.fill();
addJsText("ctx.stroke();");
addJsText("ctx.fill();");
ctx.beginPath();
addJsText("ctx.beginPath();");
arc = arc + 1;
}
}
}
[/code]

Por tanto en el caso de estructuras M realizaremos un moveTo, en el caso de L dibujaremos una línea con lineTo, en el caso de C una curva con bezierCurveTo y en el caso de A un arco con arc (en el caos de A la aproximación está simplificada, pendiente de un mejor análisis)

Ejemplo

A continuación tenemos un ejemplo funcional de lo aquí descrito, donde podéis ver todo el javascript completo y con el que podéis hacer diferentes pruebas. Este script no pretende ser una solución para convertir lo archivos svg a canvas, sino una explicación de como se podría crear una librería para hacerlo o como base en caso de que se quisiera hacer un desarrollo a medida para analizar algún tipo de dibujo vectorial concreto.

1 2  Scroll to top