Archivo por meses: marzo 2008

Nueva API de Wurfl

Tenía pendiente desde hace unos días comentar los cambios producidos en la API de Wurfl.

La teoría

Para los que no sepan de qué estamos hablando, Wurlf es una base de datos de características de dispositivos móviles. A la hora de desarrollar sites para terminales ligeros, uno de los problemas principales es la diversidad de características distintas: tamaños de pantalla, formatos multimedia soportados, lenguajes de programación… Para solucionarlo, los proveedores necesitamos mantener bases de datos de modelos con sus características principales. Una de las opciones es mantener esa base de datos manualmente, de hecho se debe hacer para responder rápidamente a nuevos terminales. Para ello registramos los UserAgents de nuevos modelos que acceden a nuestras aplicaciones para, posteriormente, buscar sus características y añadirlos a nuestra base de datos.

Wurfl es un intento de solucionar este problema desde el software libre y colaborativo. Wurfl es, fundamentalmente, un archivo XML con la información relativa a más de 10.000 terminales distintos. Para consultar este XML la gente del proyecto ha desarrollado API’s en la mayoría de lenguajes utilizados (Java, PHP, Python, Perl…). Lo más curioso es la forma de acceder a los datos. A partir del XML se crean varios miles de archivos de manera que a la hora de buscar las características de un modelo sólo hay que buscar en un archivo. A mi personalmente no me gusta mucho el modelo. Un proyecto paralelo muy interesante es Tera-Wurfl, basándose en el XML de Wurfl crea una base de datos MySQL y modifica la API PHP para consultar los datos en ella en vez de en los miles de archivos. Nosotros hicimos en su día modificaciones sobre este proyecto para utilizarlo con Sql Server.

Wurfl lo mantiene la gente, gente como nosotros, añadiendo nuevos UserAgents. Obviamente esto no es perfecto, lleva a incongruencias y datos erróneos. Recientemente han publicado también una aplicación web para enviar nuevos UserAgents y nuevos modelos. Hemos llegado a la conclusión de que no hay un modelo único válido, sino que debe ser una combinación de Wurfl y de tu base de datos propia.

¿Existen alternavitas a Wurfl? Sí, por supuesto, alguna hay, aunque en general son de pago para uso en producción.

De DetectRight, cuando lo probamos, no me gustaba el modelo, las consultas se hacen online, añadiendo un retardo y tráfico no necesario. Device Atlas me ha parecido más interesante. Lo probamos el día que lo publicaron y no nos convenció, tenia muchas carencias de características, pero estos días lo hemos vuelto a probar y ha mejorado considerablemente, aunque al no ser gratuito, aunque sea poco, no me convence, habrá que seguirle la pista de todos modos..

¿Qúe es lo que ha cambiado entonces en la API?

El proceso por el cual Wurfl averigua a qué modelo pertenece un UserAgent es muy curioso. No se basa en UserAgents o modelos concretos, sino en grupos de UserAgents, de manera que sin llegar a tener un UserAgent ni su modelo registrado puede devolvernos las posibles caracteríscias comparándolo con un modelo semejante. Por ejemplo, es posible que las característias de un Nokia N73 que no tengamos registrado sean iguales o superiores a las de un Nokia N70, luego asumimos las características de este último. Esto es extremadamente útil a la hora de tratar con modelos nuevos ya que oficialmente Wurfl libera actualizaciones un par de veces al año, aunque puedes descargar las versiones diarias del cvs.

Originariamente, el procedimiento para obtener las características de un UserAgent se basaba en que, si no se encontraba un UserAgent idéntico al del cliente, se iba reduciendo la cadena del mismo un caracter hasta encontrar un UserAgent que conincidiese con esa cadena reducida. Obviamente muy eficiente no es el método, pero funcionaba hasta ahora. La introducción de navegadores avanzados en dispositivos de gama alta, derivados de Safari, Opera, etc. llevó a la aparición de UserAgents móviles derivados de Mozilla, como los navegadores web tradicionales. Nokia95 (y la mayoría de nuevos modelos de Nokia), iPhone, Blackberry… casi todos tienen cadenas del tipo:

Mozilla/5.0 (iPhone; U; CPU like Mac OS X; de-de) AppleWebKit/420.1
(KHTML, like Gecko) Version/3.0 Mobile/3B48b Safari/419.3

Mozilla/5.0 (SymbianOS/9.2; U; Series60/3.1 NokiaN95/11.0.026;
Profile MIDP-2.0 Configuration/CLDC-1.1) AppleWebKit/413
(KHTML, like Gecko) Safari/413

El método de reducción es poco eficiente con UserAgents de este tipo. Esto es, pues, lo que han cambiado. El nuevo sistema establece un análisis en dos pasos:

  1. Primero se averigua qué tipo de UserAgent estamos tratando (basado en Mozilla, Microsoft, etc.).
  2. Según el tipo del primer paso, se procesa el UserAgent con el manejador adecuado para encontrar la información requerida.

Por ejemplo, en el primer paso, si la cadena contiene la palara Blackberry sabemos que es un navegador de este tipo de dispositivos. Un algoritmo concreto para éstos nos devolverá el modelo.

Era una modificación imprescindible hoy en día. No por el iPhone como puedan pensar muchos, sino porque la mayoría de nuevos terminales de Nokia ya tienen derivados de Safari, navegadores que por cierto nos han dado algunos problemas desarrollando sites 100% correctos y estandard pero que no se visualizaban bien (a pesar de que en los navegadores de escritorio Safari sí que funcionaba bien).

Luca Passani, alma mater de Wurfl junto a Andrea Trasatti, lo explica mejor que yo aquí.

Internet Móvil: Adaptar o no adaptar

Importante pregunta ésta. Apple y su iPhone quieren que Internet sea universal, independientemente del dispositivo que utilices siempre verás lo mismo. En un mundo ideal sería perfecto. Incluso en un mundo iPhone podría ser aceptable. La realidad es bien distinta y dista mucho de estar cerca ese idealismo.

No tengo un iPhone con lo cual no he podido probar la experiencia de usuario navegando con él, pero sí lo he hecho con la mayoría de terminales normales del mercado, desde modelos de gama baja hasta los últimos Nokia N95 o HTC Touch y similares incluyendo distintos PDA’s y smartphones. Todos estos teléfonos de gama alta tienen algo en común: utilizan navegadores avanzados, versiones móviles de sus homónimos para web en PC’s. Nokia utiliza uno basado en Safari (sí, igual que el iPhone) y HTC, al utilizar Windows Mobile, tiene Internet Explorer. Otros utilizan Opera Mini. Con todos ellos puedes hacer lo mismo, desde ver sitios web desarrollados expresamente para tecnologías móviles hasta sitios web normales. ¿Cuál es el problema entonces? La usabilidad y la navegabilidad. Y esto hablando de los de gama alta, de los demás mejor nos olvidamos en este artículo.

Cuando ves en un móvil una web, estás viendo en una pantalla de un máximo de 320×240 un sitio diseñado para resoluciones de al menos 800×600, y digo al menos cuando la mayoría de sitios hoy en día se hacen para resoluciones superiores .

¿Qué te parecería ver una web diseñada a 1024px en una pantalla de 800×600? Pensarías de todo y cargarías contra los diseñadores, sin duda, ¡y eso que tienes un ratón para desplazarte lateralmente!.

Esta teoría se puede aplicar tal cual en los dispositivos móviles. Pantallas pequeñas (comparadas a las de los PC’s) para ver sites realizados a resoluciones mayores. Además, por mucho que la pantalla sea táctil, la navegavilidad no es la misma que con un ratón. Tendrías que estar desplazándote horizontalmente contínuamente.

Utilizo desde hace casi cuatro años una PDA, principalmente cuando estoy de viaje. La utilizo sobre todo para leer el correo y planificar rutas y visitas o buscar hoteles y restaurantes, con lo que hago uso de Internet. A veces con Wifi, si la encuentro (muy pocas veces), y otras a través del GPRS del móvil. La PDA tiene pantalla táctil y puedes moverte arrastrando el dedo para desplazarte por la página, como véis no es ninguna novedad lo del iPhone. Os puedo asegurar que es insufrible, el scroll horizontal es de lo peor.

A esto hay que sumarle otro problema muy importante cuando hablamos de navegación con móviles: el coste del tráfico de datos. Vale que se puede utilizar Wifi, pero hoy en día no es fácil encontrar puntos de acceso abiertos. Si cuando nos conectamos a través del móvil vemos un sitio web normal, no adaptado, estaremos viendo cientos de menús y banners supérfluos para lo que realmente queremos ver pero que nos ralentizan la navegación, la velocidad de carga (los procesadores móviles están muy limitados) y, sobre todo, el coste del tráfico que es lo que, a fin de cuentas, más nos duele.

Ahora nos quieren vender que la navegación con móviles ha aumentado exponencialmente el último año gracias al iPhone pero no nos aclaran si es navegación Wifi o GPRS. No es lo mismo. Hasta que apareció el iPhone nadie habría pensado en gastarse 500$ y dos años de contrato a razón de una buena cantidad mensual para tener un dispositivo que te permita navegar. Antes del iPhone ya existían dispositivos móviles con Wifi, no es nada nuevo. Encima el iPhone viene sin 3G, es un simple GPRS, con lo que me niego a creer que ese aumento de tráfico de Internet se deba al iPhone propiamente dicho sino a que una gran cantidad de gente ha pasado a tener un dispositivo móvil con Wifi, de otro modo se estarían arruinando pagando a su operadora por ese tráfico GPRS, además de aburrirse de esperar, no nos engañemos, GPRS es lo que es, y si vas a cargar webs ya sabes lo que te espera.

Mi opinión, después de varios años desarrollando portales para dispositivos móviles, es que tardaremos mucho en ver móviles con la usabilidad y navegabilidad necesaria como para ver webs normales. Aquí será fundamental la reducción de precios en la navegación, nos guste o no es el mayor problema. Aunque todos los operadores se pusiesen de acuerdo y ofreciesen tarifas del estilo de Yoigo o Simyo, alrededor de 1 euro diario, si lo utilizásemos todos los días estaríamos pagando más de 30 euros mensuales por Internet en el móvil a lo que habría que sumar los 30 o 40 que ya pagas por el de casa. Yo no lo veo. El segundo problema es el que comentábamos al principio, la gente que tiene un terminal de gama alta de este tipo es un porcentaje diminuto respecto al grueso de usuarios que tienen teléfonos de gamas media y baja y que son, a la larga, los que van a reventar el mercado de Internet para móviles, la gente normal.

Yo, mientras tanto, seguiré adaptando. Hay mucha diversidad de modelos y WML, xHTML Mobile e iMode estarán aquí por muchos años todavía.

Artículos interesantes de los últimos días

De relax en vacaciones: Fallas y Semana Santa

Aunque no lo parezca, los profesionales del sector TI también descansamos, apagamos los portátiles y BlackBerry’s (o casi) y utilizamos el móvil sólo para quedar con los amigos. Es lo que he estado haciendo los últimos días.

Valencia en Fallas

Comenzamos disfrutando de las Fallas el fin de semana. Aquí tenéis el álbum completo de fotos de Fallas. Valencia en Fallas huele a churros y buñuelos y sabe a pasacalles, petardos y caminatas a pié. Si no soportas los petardos ni se te ocurra. Para un gallego como yo lo más impresionante son, sin duda, las mascletàs, no puedes perderte al menos una en la Plaza del Ayuntamiento y otra en alguna falla de barrio, se viven y se sienten mucho más cercanas.

Falla Na JordanaFalla Rocío JuradoIluminación Falla Sueca-Lit. AzorínFalla Convento JerusalemFallas de ValenciaNinot Unicornio, Fallas de Valencia

Fallas es un buen momento también para hacer turismo ya que de paso que caminas para ver fallas te vas cruzando con lo más importante de Valencia. No podéis dejar de pasear por el Barrio del Carmen estos días, os enamorará. Aquí os dejo las estampas más típicas de Valencia.

Lonja de la Seda, ValenciaTorres de Quart, ValenciaIVAM, Instituto Valenciano de Arte ModernoSantos Juanes y fachada posterior del Mercado Central, ValenciaLonja de la Seda, ValenciaMercado Central, ValenciaEstación del Norte, ValenciaEdificio de Correos, Valencia

Después de patearnos Valencia el domingo y lunes, el martes por la tarde decidimos (mi pareja y yo) olvidarnos de la Nit del Foc y de la Cremà y nos vinimos a Pontevedra a pasar las vacaciones con mi familia y amigos, de miércoles a lunes, puesto que estos días también son fiesta en Valencia este año, total seis días, cinco si no contamos el lunes puesto que es el viaje de vuelta.

Cambio de aires

Este año ha habido suerte puesto que casi todos los colegas andaban por aquí y hemos podido comer y beber ampliamente. Antes, por supuesto, hemos pasado horas y horas con mis sobrinitas, Nerea de casi cinco añitos y Aroa de nueve meses recién cumplidos.

No habíamos previsto un cambio de tiempo tan brutal, pasamos de estar a 24º en Valencia y en manga corta desde primeras horas de la mañana a 8º cuando paramos a tomarnos el bocata (cuando viajamos juntos nos gusta parar en un área de descanso y comer tranquilamente al aire libre si el tiempo lo permite, sino nos vamos a alguna cafetería). Esta vez parecía que el tiempo lo permitía pero menudo susto.

Comiendo bajo el frío castellano

Santiago de Compostela

Al llegar a casa de mis padres, más de lo mismo, un frío que pela. Pero a aguantar toca que son vacaciones. El miércoles, aprovechando que la mayoría del país no había comenzado aún las vacaciones y habría poco turista, nos dimos una vuelta por Santiago. Hicimos una ruta típica, entrando por la Porta do Camiño, subiendo hasta la Praza da Quintana, Praterías y Praza do Obradoiro con la catedral. Para la vuelta, bajamos por la Rúa do Franco, recordando tiempos pasados de juergas nocturnas, y regresamos por Rúa do Vilar y Praza do Toural. Santiago siempre impresiona, aunque se te quede la cara morada del frío :).

Praza da Quintana, Santiago de CompostelaPraza de Praterías, Santiago de CompostelaCatedral de Santiago de CompostelaPaxo de Raxoi, Santiago de CompostelaRúa do Franco, Santiago de CompostelaPraza do Toural, Santiago de Compostela

Combarro

El viernes nos acercamos a Combarro a dar una vuelta y a hacernos unas fotos, pero ya se hacía un poco desagradable debido a la avalancha de gente que había. Por cierto, no me han gustado nada las escaleras de madera que han puesto para evitar tener que subir por las piedras de toda la vida, además de no quedar bien inspiran poca confianza. Las fotos no son excesivamente buenas, había demasiada gente. Combarro es mucho más bonito e impresionante de lo que se ve en estas tres fotos.

Vistas de la ría de Pontevedra desde CombarroHórreos de CombarroCruceiro en Combarro

Ya por la noche, churrascada como nos merecemos y a hacer un poco el tonto para bajar la comida. Dejo las fotos de las comidas a Marcos pues ya sabemos que es su especialidad :P.

Para finalizar la noche, algún café y alguna copa mientras manteníamos una animada charla entre buenos amigos.

Pontevedra

El sábado dimos una vuelta por la zona vieja de Pontevedra, paseando y haciéndonos unas fotos. A mi pareja le encanta callejear, qué le voy a hacer, en Valencia apenas lo hacemos y aquí no paramos.

Para quién no lo sepa, Pontevedra es la gran desconocida de Galicia. Tiene el casco antiguo más importante de Galicia después de Santiago.

Entrada a la Praza da Pedreira, PontevedraCallejeando por PontevedraPraza de Méndez Nuñez, con mi colega Valle Inclán, PontevedraPraza de Curros Enríquez, PontevedraEdificio y Convento de San Francisco, PontevedraInmediaciones de la Praza da Ferrería y soportalesCallejeando por Pontevedra, inmediaciones de la Iglesia de San BartoloméPraza da Leña y Museo ProvincialPraza da LeñaCallejeando por Pontevedra, Rúa Figueroa

El casco histórico de Pontevedra está dominado por la piedra, los escudos heráldicos y los soportales, casi todas las plazas los tienen y le dan un encanto especial (además de resguardarte los días de lluvia). Puedes tomarte unas cañas en alguno de los bares de la Praza da Verdura o unas tapas en la Praza da Leña, con su hermoso cruceiro del s.XV. Desde la Praza do Teucro, en homenaje al arquero griego Teucro, fundador de la ciudad según cuenta la leyenda, llegas a la Praza das 5 rúas, centro neurálgico de la marcha nocturna de la ciudad y donde se encuentra la casa donde vivió Valle Inclán.

Las vacaciones ya han terminado, mañana toca viaje de vuelta, y lo peor es que este año, al caer Semana Santa tan pronto, tendrán que pasar más de cuatro meses hasta volver a tener vacaciones.

Editado:
Marcos publica nuestras cenitas aquí, aquí y aquí.

Creando sencillas mashups y widgets con Google Ajax Feed Api

No cabe duda que uno de los mayores avances que se han producido en Internet ha sido el modelo de compartir contenidos generados. Desde la burbuja de 2001, donde se protegían los contenidos hasta el extremo, hemos llegado a un modelo donde prima lo contrario, compartirlos todo lo posible, cuantos más te lean mejor, da igual el medio. En esto han jugado un papel fundamental los sistemas de sindicación, concretamente los RSS. Hoy en día casi todo tiene feeds, da igual el tipo del contenido, no sólo artículos de texto, también fotos, vídeos, twitters y demás.

La teoría

La base de la mayoría de mashups o widgets es la combinación de distintos feeds y su posterior tratamiento y utilización. Un clásico cómo la combinación de fotos de Flickr con mapas de Google es sencillo gracias a los RSS con la información de la geolocalización del primero. La dificultad a la hora de gestionar los feed es la falta de una librería Javascript que permita leer y parsear feeds de cualquier tipo en el navegador sin ayuda de herramientas externas, por lo que hasta ahora lo normal era hacerlo del lado del servidor y devolver la lista de items del feed mediante JSON o simples arrays. Por suerte Google ha pensado en nosotros y nos ha dado la llave de la cerradura. La ventaja de hacerlo así es que todo el trabajo cae en el navegador cliente liberando a nuestro servidor de cargar los XML, parsearlos y hacer el tratamiento posterior. Además, para evitar problemas, tendríamos que cachearlos si no queremos que el rendimiento de nuestras máquinas caiga. Google ya lo hace todo por nosotros. Sí, también ha pensado en eso y no los recarga siempre que los llamas.

Con Google Ajax Feed Api es todo más sencillo puesto que es capaz de leer la mayoría de formatos de feeds y los devuelve en un objeto Javascript fácil de tratar. Para explicar su funcionamiento, nada mejor que un ejemplo práctico.

Hoy en Bricomanía…
Este es nuestro proyecto de hoy. Gracias a Marcos por su cuenta de vídeos en Youtube puesto que yo no tengo. Un pequeño widget con el que visualizar nuestra actividad en los servicios más populares de Internet. Lo puedes poner en el lateral de tu blog 😛 o añadir a tu MySpace.

La prática

Lo primero que haremos será registrar nuestro dominio para utilizar la API, con lo que obtendremos el API Key. Recuerda que esta clave va asociada al dominio y no podrás utilizarla en otro.

Lo siguiente será buscar las URLs de los feed’s a utilizar. En nuestro caso buscamos las de Twitter, Flickr, Youtube, Del.icio.us y LastFM:

Vosotros deberéis utilizar las vuestras, claro, tampoco es que yo tenga mucho interesante que ver o leer.

Veamos como hacerlo con uno de los feeds, los demás se harían del mismo modo. El resto del widget lo montáis como más os guste según lo que queráis hacer. En el caso de nuestro ejemplo definimos una capa (DIV) para cada feed y con los botones de las imágenes cambiamos el estilo display ocultando todas y mostrando la que se ha pulsado. Al final del articulo tenéis el código completo del ejemplo.

Lo primero que debemos hacer es cargar el Javascript de la API con el Api Key que obtuvimos. Ahora, como veis el código inferior, instanciamos la api y definimos una función de callback que se encargará de cargar nuestros feeds cuando se haya cargado la página. En esta función podemos añadir todo el código que queramos ejecutar para iniciar la aplicación. En nuestro caso vamos a cargar nuestro Twitter.

<script src="http://www.google.com/jsapi?key=ABQIAAAAVTgpBmlTiDeEs9UaP69iNRT4lrLtf4G1Npt4CNmX07qB4fHehhQysXL6wgFRWD8d-X_v8iu6Hs2orA" type="text/javascript"></script>
<script type="text/javascript">
function OnLoad() {
	loadTwitter();
}
google.load("feeds", "1");
google.setOnLoadCallback(OnLoad);
</script>

Ahora llega realmente el proceso. Veréis que pocas líneas son necesarias para parsear y mostrar tu Twitter.

function loadTwitter() {
    var feed = new google.feeds.Feed("http://twitter.com/statuses/user_timeline/"+usertwitter+".rss");
    feed.setNumEntries(50);
    feed.load(function(result) {
        var container = document.getElementById("twitter");
        if (!result.error) {
            var entries=result.feed.entries;
            var html="";
            for(var i=0; i<entries.length; i++){
                var entry = entries[i];
                var titulo=String((entry.title)).substring(String(entry.title).indexOf(":")+1);
                html+="<p><b>"+fecha(entries[i])+"</b><br/>"+string_create_urls(titulo)+"</p>";
            }
            container.innerHTML=html;
        }else{
            container.innerHTML="<p>Error cargando el feed</p>";
        }
    }
    )
}

El código casi se explica por sí mismo. Se carga el feed y se define la función que se ejecutará cuando esté cargado. Podemos incluso definir la cantidad máxima de items que se cargarán. Y ya está. Tenemos una colección de objetos con todos los datos, sólo queda iterar sobre ellos y mostrarlos como nos parezca oportuno. Nosotros nos hemos apoyado en un par de funciones auxiliares para mostrar la fecha y los links de nuestro Twitter.

Ahora me diréis: ¡traición! has utilizado innerHTML después de no recomendarlo en un artículo anterior. Bueno, de acuerdo, no tenía ganas de añadir más código y tampoco voy a necesitar acceder a lo añadido :-P, no es una buena excusa pero ahí queda eso. Sigo recomendando que utilicéis DOM para añadir los nuevos elementos. Creo que sería un buen tema para un artículo posterior puesto que el procedimiento es bastante engorroso, por eso innerHTML es tan popular.

Y eso es todo amigos. En el próximo programa veremos cómo posicionar las fotos de Flickr en Google Maps.

Aquí tenéis el ejemplo completo y el código.

Redimensionando GIF’s desde PHP y II

En el primer artículo de esta serie de dos comentamos cómo escalar GIF’s con transparencias.

Recordando conceptos, lo más importante es que la transparencia es un atributo de la imagen, no del color en sí mismo, de manera que se configura un color determinado como transparente y cualquier pixel que tenga el mismo color exacto será transparente.

Veamos ahora qué ocurre si el gif es animado. Si intentamos abrir la imagen con imagecreatefromgif, nos llevaremos la sorpresa de que sólo aparece el primer frame. Ese es el comportamiento normal, ignorar el resto.

La teoría

Un GIF animado está formado por una secuencia de imágenes GIF añadiendo cierta información adicional, como el tiempo que se muestra cada frame. La trasnparencia es común a todos los frames, es decir, vuelve a ser un atributo de la imagen final. Pero esto no es todo, según investigábamos el tema nos encontramos con dos detalles adicionales que entorpecían todavía más el proceso. El primero es que no todos los frames tenían que ser del mismo tamaño, sino que cada uno podía tener un tamaño distinto y, dentro de la información adicional que comentábamos antes, tendríamos los datos de la posición (x,y) donde situar ese frame respecto al primero. Vale, ahora que lo sabemos podemos tomar medidas en el proceso final. Pero cuando estas medidas están contempladas aparece otro problema. El concepto tradicional de secuencia de gif no es tal, en realidad hay dos modos de manejar las capas, según averiguamos con el Gimp:

  • replace: es el comportamiento normal, cada frame reemplaza al anterior, es como una película.
  • combine: los frames se sobreponen unos sobre otro, de manera que, con las transparencias de cada frame, se genera una animación cambiando sólamente la parte de la imagen que cambia. El resultado es un archivo de menor peso.

Después de mil vueltas no encontramos la manera de combinar los frames, sólo podíamos utilizar replace. Llegados a este punto el problema eran los gifs animados con frames de distintos tamaños y combinados.

Antes de entrar en materia con el código, explicaremos a grandes rasgos cómo hacerlo, una vez entendamos el procedimiento será más sencillo ver el código. La solución pasa por separar el GIF animado original en sus frames, tratar cada frame por separado y volver a unirlos generando el nuevo gif animado. A la hora de tratar cada gif tenemos el problema descrito anteriormente con tamaños y replace/combine. El resultado fué generar un animado de tipo replace apartir del combine original fusionando cada frame con el inmediato anterior. Expliquemos esto más detalladamente. Según la teoría vista hasta ahora, en un GIF de tipo combine lo que se ve en cada momento es la superposición del frame actual más todos los anteriores. Es de suponer, además, que tendrán transparencias, puesto que sino uno sustituiría completamente a los otros. Por ejemplo, supongamos que tenemos un GIF de 5 frames y la reproducción va por el tercero. En ese momento estaríamos viendo el primer frame con el segundo encima, que al aplicar la transparencia dejaría ver el primero donde tengamos transparencia y encima de todo nuestro tercer frame que dejaría ver los de abajo. Una imagen vale más que mil palabras:

Ejemplo de gif animado separado en capas

En esta imagen se muestra todo lo explicado. En la fila superior vemos la animación desglosada por frames. Les he puesto fondo gris para que lo veáis, pero en realidad sería transparente. Las líneas discontínuas representan el tamaño de esa capa sobre el tamaño total de la imagen representada por el primer frame. Como podéis apreciar hay variedad en cuanto a tamaño y posiciones. En la fila de abajo he ido fusionando los frames a medida que se van viendo. El segundo frame sería la fusión del primero con el segundo de la fila de arriba, que es en su mayoría transparente y solo añade unas pinceladas sobre el diseño inicial. El tercero es la fusión del segundo con el tercero de arriba, etc. Es el efecto combine, combina todas las capas que va mostrando. El resultado de la fila de abajo es una secuencia replace, cada frame es independiente del otro. Este es nuestro objetivo, convertir una imagen inicial tipo combine en una replace.

Esta sería la imagen original para que entendáis el efecto final..

Ejemplo de gif animado

La práctica

Ahora que ya sabemos qué es lo que tenemos que hacer surge la pregunta del cómo.

Para empezar os preguntaréis cómo separamos un gif animado en sus frames y los volvemos a juntar si las funciones de PHP sólo se quedan con el primer frame. Sencillo, con otras funciones. Aquí es donde vienen a nuestra ayuda un par de estupendas clases realizadas por László Zsidi, GIFEncoder y GIFDecoder. Su función es básicamente lo que dice su nombre, coger un gif y separarlo en sus frames o coger varios frames (o imágenes sueltas en disco) y devolver un GIF animado. El cómo no nos importa, queda fuera del alcance de este artículo y puedes consultar el código de las clases si te interesa. Simplemente comentaré que lo hace todo a nivel de bits, no utiliza ninguna función gráfica predefinida.

Al final de todo tenéis un zip con todo el código fuente y las clases necesarias.

include(dirname(__FILE__)."/GIFDecoder.class.php");
include(dirname(__FILE__)."/GIFEncoder.class.php");         

$gifcontents=file_get_contents("miarchivo.gif");
if(is_gif_animado($gifcontents)){
 $GIFS=new GIFDecoder($gifcontents);
 $ARRS=$GIFS->GIFGetFrames();
 $OFFSETS=$GIFS->GIFGetOffset();
}

Este sería el principio. Es importante comprobar si es una animación, puesto que sino será todo mucho más sencillo como vimos en el primer capítulo. La función la encontraréis en el código. De aquí, lo importante que nos queda es

  • $GIFS->GifGetFrames() contiene los frames del gif
  • $GIFS->GifGetOffset() las desviaciones en la posición de cada capa.

Con esto podemos comenzar a operar sobre cada gif a través del array ARRS. El código está explicado en los comentarios. Es importante anotar que tanto el color de la transparencia como el tamaño de la imagen lo obtenemos con las funciones estandard. Esto lo hacemos así porque no encontré la manera de saber el color de transparencia a traves del GIFDecoder y, ya que tenía la imagen abierta, saco el tamaño.

//con las funciones estandard detectamos el tamaño del primer frame y el color de transparencia
$srcImage = imagecreatefromgif($ipath);
$transcolor=imagecolortransparent($srcImage);
$w=imagesx($srcImage);
$h=imagesy($srcImage);for($i=0; $i<count($ARRS); $i++){
    //el primer frame se queda como esta
    if($i>0){ 	$image=imagecreatefromstring($ARRS[$i]);
 	$wo=imagesx($image);
 	$ho=imagesy($image);
 	$off=$OFFSETS[$i];
 	//fusionamos el frame con el anterior
 	$temp=imagecreatetruecolor($w, $h);
 	$temp2=imagecreatefromstring($ARRS[$i-1]);
 	//hay que tener en cuenta la posible transparencia, sino no hacemos nada
	if($transcolor!=-1){
	 	$trnprt_color = @imagecolorsforindex($image, $transcolor);
 		$trnprt_indx = @imagecolorallocate($temp, $trnprt_color['red'], $trnprt_color['green'], $trnprt_color['blue']);
	 	if($trnprt_indx){
 			imagefill($temp, 0, 0, $trnprt_indx);
 			imagecolortransparent($temp, $trnprt_indx);
	 		imagecolortransparent($image, $transcolor);
 		}
	}          

	//creamos una imagen con el frame anterior y superponemos el actual con el offset necesario
	imagecopymerge($temp, $temp2, 0, 0, 0, 0, $w, $h, 100);
	imagecopymerge($temp, $image, $off[0], $off[1], 0, 0, $wo, $ho, 100);       

 	//capturamos el nuevo frame
	ob_start();
	imagegif($temp);
	$GIFS->ARRS[$i]=ob_get_clean();         

	//ya no hay offsets puesto que todos tienen el mismo tamaño
	$GIFS->offsets[$i][0]=0;
	$GIFS->offsets[$i][1]=0;
	imagedestroy($temp);
    }
}

En este punto tenemos exactamente el proceso que hemos estado explicando. Todos los frames tienen el mismo tamaño y son independientes, es decir, se han ido fusionando hacia arriba de manera que podemos hacer una animación replaced. Interesante es la manera de recuperar el gif de cada frame modificado para restaurarlo al array original. No tenemos otra forma de recuperar el contenido de una imagen que no sea mostrándola o guardándola a un archivo, así que la mostramos y la recuperamos del buffer de salida.

Ahora podemos hacer lo que necesitemos con la imagen, escalarla, imprimir texto, rotarla, etc. Nosotros la escalaremos, haremos un thumbnail de 90×90. Tan sencillo como hacer el proceso en todos y cada uno de los frames.

//ahora si queremos podemos redimensionar los frames, por ejemplo hacemos thumbnail
for($i=0; $i<count($ARRS); $i++){
 	$image=imagecreatefromstring($ARRS[$i]);
 	$wo=imagesx($image);
 	$ho=imagesy($image);
 	$dst_img=imagecreatetruecolor (90, 90);
 	imagecopyresized($dst_img, $image, 0, 0, 0, 0, 90, 90, $wo, $ho);
	ob_start();
	imagegif($dst_img);
	$ARRS[$i]=ob_get_clean();
}

Para mostrar la imagen, se necesite o no guardarla en disco, es necesario pasarla a un archivo, pues es el proceso que hace la clase GIFEncoder. Si después no la necesitamos se puede eliminar directamente como hacemos en el ejemplo.

$gif = new GIFEncoder($GIFS->ARRS, $GIFS->GIFS->GIFGetDelays(), 0, 2, $trnprt_color['red'], $trnprt_color['green'], $trnprt_color['blue'], array(), "bin" );
$fh = fopen("final.gif", 'w');
fwrite($fh, $gif->GetAnimation());
fclose($fh);
header("Content-type: image/gif");
readfile("final.gif");
unlink("final.gif");

Os dejo el enlace con el código fuente y el ejemplo funcionando.

Como véis el proceso es bastante complejo, pero una vez se entiende el funcionamiento del GIF animado todo es más sencillo. Como comentario adicional, si váis a implementar un sistema de escalado en tiempo real, yo pensaría en cachear los resultados por aquello de optimizar un poco el sistema y no sobrecargar la cpu, después de todo estamos hablando de trabajo con imágenes, algo bastante pesado en memoria.

Finalmente, utilizaba inicialmente la función de comprobación de que una imagen es animada de László Zsidi, sin embargo detectamos que, en determinadas situaciones, entraba en un bucle infinito y terminaba saturando nuestro Apache de procesos ocupados en el bucle infinito. Menos mal que tenía otra versión más antigua de una función que hacía lo mismo. Deuteros me lo agradece enormemente.

Precargar CSS con la aplicación Flex

A la hora de crear una aplicación Flex con temas (skins) intercambiables o personalizables, uno de los problemas principales con el que nos encontramos es la precarga del mismo. En general tendríamos por un lado nuestro swf de la aplicación y por el otro el del css que estamos cargando. Asumiendo que cargásemos el tema en el evento creationComplete con

 StyleManager.loadStyleDeclarations("micss.swf", true )

tendríamos un problema de sincronización. Los usuarios no verían el look&feel de nuestra aplicación hasta que se hubiese cargado el swf del tema, mientras tanto se vería el tema por defecto, lo que ofrece una imagen bastante pésima de la aplicación.

Para solucionarlo recurrimos a precargar el tema junto a la aplicación, de manera que cuando lancemos el evento creationComplete, el swf está ya en la caché del navegador y casi instantáneamente se aplica sin que el usuario note apenas retardo.

Para hacer la precarga personalizada de la aplicación deberemos extender el componente DownloadProgressBar. Además de para nuestro propósito de cargar el css, podemos también aprovechar para traducir los textos de la percarga, de manera que aparezcan en castellano, un detalle para los usuarios.

Os dejo el código de ejemplo.

Lo primero será decirle a nuestra aplicación que utilice nuestra precarga personalizada y cargar nuestro swf de estilos. Recuerda que cuando se ejecute el evento creationComplete, este swf estará ya en caché del navegador, no se cargará de nuevo desde el servidor.

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical"
creationComplete="StyleManager.loadStyleDeclarations("micss.swf", true )"
preloader="com.xplota.mainloader.AppProgressBar">

Ahora veremos rápidamente como personalizamos el componente.

package com.xplota.mainloader{
	import mx.preloaders.*;
	import flash.events.ProgressEvent;
	import flash.text.TextFormat;
	import mx.controls.Image;
	import flash.display.Sprite;
	import flash.events.Event;
	import mx.events.FlexEvent;
	import mx.events.RSLEvent;
	import flash.display.Loader;
	import flash.net.URLRequest;
	public class AppProgressBar extends DownloadProgressBar{
	 	private var loader:Loader;
	 	private var _preloader:Sprite;
		public function AppProgressBar() {
	 		super();
	 		//Configuramos las etiquetas
	 		downloadingLabel="Cargando..."
	 		initializingLabel="Iniciando..."
	 		// Set the minimum display time to 2 seconds
	 		MINIMUM_DISPLAY_TIME=2000;
	 	}

		// Override to return true so progress bar appears during initialization.
	 	override protected function showDisplayForInit(elapsedTime:int, count:int):Boolean {
	 		return true;
	 	}

		// Override to return true so progress bar appears during download.
	 	override protected function showDisplayForDownloading(elapsedTime:int, event:ProgressEvent):Boolean {
	 		return true;
	 	}

	 	//cambiamos el color de fuente de la precarga
		 override protected function get labelFormat():TextFormat{
	 		var tf:TextFormat=new TextFormat();
	 		tf.color=0xFFFFFF;
	 		tf.font = "Verdana";
	 		tf.size = 10;
	 		return tf;
	 	}

		override protected function createChildren(): void {
	 		super.createChildren();
	 	}

		 //una vez ha cargado la aplicacion cargamos el tema usando la misma precarga
	 	override protected function completeHandler(event:Event):void{
	 		this.label="Cargando tema...";
	 		loader=new Loader();
	 		loader.load(new URLRequest("css/obsidian.swf"));
			loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, progressHandler);
	 		loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loaderCompleteHandler);
	 	}

		private function loaderCompleteHandler(event:Event):void{
	 		_preloader.addEventListener(FlexEvent.INIT_PROGRESS, initProgressHandler);
	 		dispatchEvent( new Event( Event.COMPLETE ) );
	 	}

		override public function set preloader( preloader:Sprite ):void{
	 		_preloader=preloader;
	 		preloader.addEventListener(ProgressEvent.PROGRESS, progressHandler);
	 		preloader.addEventListener(Event.COMPLETE, completeHandler);
	 		preloader.addEventListener(RSLEvent.RSL_PROGRESS, rslProgressHandler);
	 		preloader.addEventListener(RSLEvent.RSL_COMPLETE, rslCompleteHandler);
	 		preloader.addEventListener(RSLEvent.RSL_ERROR, rslErrorHandler);
	 	}
	}
}

En el constructor de la clase configuramos las etiquetas en nuestro idioma. Aprovechamos para sobreescirbir el método labelFormat de manera que podamos modificar la fuente de las etiquetas para acomodar la precarga a nuestro diseño.

Lo importante llega al sobreescribir el método set preloader. Lo principal es capturar el evento Event.COMPLETE, que se disparará cuando termine la precarga y antes de que se inicie la aplicación. Cuando se dispare este evento lanzaremos la carga de nuestro CSS pasando al mismo preloader el progreso de carga de los estilos, de manera que reaprovechamos la misma precarga. Cuando termine de cargar el CSS será cuando lancemos manualmente el evento Event.COMPLETE del componente, lo cual iniciará la aplicación teniendo ya nuestros estilos en caché.

Este método no sirve sólo para cargar los estilos, podemos precargar cualquier archivo que necesitemos. Nosotros lo hemos utilizado para cargar, además de los estilos, un archivo de configuración XML. Puedes, además, ir cambiando la etiqueta de carga: cargando configuración, cargando tema, etc.

Recuerda que para jugar a extender componentes lo más sencillo siempre es ver como está construído el original. Haciendo ctrl+click en el código sobre un tipo se nos abrirá automáticamente el código fuente del mismo, pudiendo explorar los métodos que tienes disponibles, los que puedes sobreescribir y el funcionamiento completo.

Redimensionando GIF’s desde PHP I

Como desarrollador de tecnologías para móviles, uno de los servicios que hacemos habitualmente es la venta de imágenes, fondos, wallpapers o como queráis llamarlo. Este servicio requiere el escalado de los archivos para adaptarlo al tamaño de pantalla del teléfono del cliente. Además de para la descarga de contenidos, el redimensionamiento de imágenes es necesario también para adaptar el look&feel de los sites al dispositivo del cliente. En un primer momento lo hacíamos con Imagemagick, pero, por alguna extaña razón, a medida que aumentaban las visitas se saturaba el Apache de las máquinas (Windows) puesto que, parece ser, el convert no finalizaba los procesos, y había que reiniciar los servidores web. La solución fue reemplazar el sistema basado en Imagemagick por otro basado en GD, utilizando las funciones nativas de PHP, de manera que todo quedaba integrado en la propia aplicación.

Os estaréis preguntando cual es el problema en el escalado de imágenes con GD. Ninguno, el resultado era perfecto hasta que nos encontramos con gif’s transparentes. Al hacer el escalado se perdía la transparencia sin remedio. Dimos muchas vueltas y probamos distintos métodos que fuimos leyendo por cientos y cientos de foros y blogs, puesto que en la documentación de PHP no quedaba nada claro cómo hacerlo hasta que dimos con la solución.

He decidido separar este artículo en dos partes. En el segundo trataremos el escalado de gif’s animados, un proceso bastante más complejo que los estáticos.

En un GIF, la transparencia viene definida por un color, un solo color, que se define como transparente, de manera que cada pixel que tenga exactamente ese color será transparente, de ahí que, tradicionalmente, se comience por una imagen de fondo blanco o negro y sobre ella se dibuje, guardando ese color de fondo inicial como el de transparencia. La transparencia es una característica de la imagen, no del color en sí. No tiene por qué ser blanco o negro, cualquier color puede ser el transparente, pero una vez definido, cualquier pixel que tenga ese color será transparente. Por otro lado, en un GIF se guarda la paleta de colores que utiliza, de manera que se optimiza el tamaño de la imagen manteniendo la información únicamente de los colores disponibles y referenciándolos por un índice dentro de esa paleta. Esto que puede parecer un dato sin importancia es fundamental a la hora de tener claro el proceso a seguir desde PHP.

Hay varias funciones de PHP que hacen referencia a la transparencia y a los colores. En unas se utilizan los colores como tal en formato RGB, en otras se utiliza el índice de color en la paleta y en otras se necesita la representación del color. Aquí está uno de los problemas, ¿cómo se debe utilizar el resultado de cada una de las funciones disponibles?.

Lo primero que debemos calcular es el porcentaje de escalado que vamos a aplicar. Esto dependerá de nuestra aplicación y del destino de la misma. Si queremos fijar un determinado ancho habrá que calcular el porcentaje sobre el que debemos escalar el alto y viceversa. No entraremos en detalles sobre este aspecto puesto que se sale fuera del objeto de este artículo.

Una vez tenemos claro el nuevo tamaño, debemos, antes de nada, saber cual es el color transparente de la imagen original. Para ello usamos la función imagecolortransparent, si hay transparencia nos devolverá el identificador de ese color. A partir del índice del color transparente, obtenemos la representación RGB en la imagen original con imagecolorsforindex y ya podemos localizar ese color en la nueva imagen con imagecolorallocatealpha. Ya tenemos todo lo necesario. Ahora simplemente rellenamos la nueva imagen con ese color, le decimos que ese color será el transparente y copiamos la imagen original en la nueva escalando según necesitamos.

A la hora de copiar la imagen y escalarla no utilizamos imagecopyresampled ya que, a diferencia de imagecopyresized, hace una interpolazión para suavizar el resultado final. Esto, que en general es correcto, en el gif transparente provoca la deformación de pixeles propios de la interpolación, haciendo que se pierda parte de la trasparencia ya que estos pixeles no son del color exacto.

Este sería el código final.

$srcImage=imagecreatefromgif("demo.gif");
$new_w=160;
$new_h=114;
$dst_img=imagecreatetruecolor($new_w,$new_h);
$transcolor=imagecolortransparent($srcImage);
if($transcolor!=-1){
 $trnprt_color = imagecolorsforindex($srcImage, $transcolor);
 $trnprt_indx = imagecolorallocatealpha($dst_img, $trnprt_color['red'], $trnprt_color['green'], $trnprt_color['blue'], $trnprt_color['alpha']);
 imagefill($dst_img, 0, 0, $trnprt_indx);
 imagecolortransparent($dst_img, $trnprt_indx);
}
imagecopyresized($dst_img,$srcImage,0,0,0,0,$new_w,$new_h,ImageSX($srcImage),ImageSY($srcImage));

Para muestra, un color. Con esta imagen de fondo transparente (sí, vale que no es un Picasso, pero para el ejemplo sirve :P):

Demo Transparente

Conseguimos esto:

Demo Transparente Redimensionada

¿Cómo que falta personal técnico?

Hace unas semanas, Rodolfo Carpintier expresaba su opinión (e insistía) sobre los profesionales del sector informático. Antes lo hizo Enrique Dans aquí y aquí.

Yo, que pertenezco a ese selecto grupo de privilegiados de los que llegan a afirmar que no conocen el paro, no puedo más que sentir indignación. Trabajo en este sector desde 2000 y he visto de todo, incluídas las colas del INEM. Tal como indica Rodolfo, yo soy de los que se creyeron muchos de esos proyectos y terminaron en esas colas.

Ahora bien, cuando hablan de que nos sobra trabajo se refieran a esos de 850 euros mensuales, bueno vale, o 900 (en 12 pagas). Así claro que no hay paro, pero también podemos prostituirnos, bueno, yo lo dudo :). De ahí a los 30.000 euros anuales que comentan creo que hay un largo trecho, y más teniendo en cuenta que hay más país mas allá de Madrid y Barcelona. Yo vivo en Valencia y no he visto una oferta de programador por ese salario nunca, acaso alguna oferta de analista con mucha experiencia.

También habla Rodolfo acerca de startups y equipos de trabajo de horas y horas sin apenas sueldo, a cambio de participaciones en el proyecto. Supongamos que me creo el proyecto y que estoy dispuesto a ello. ¿De qué se supone que voy a vivir los próximos 12 o 24 meses? ¿De lo que he ahorrado los dos años anteriores? Me invade la risa, pero a mi hipoteca todavía más.

Me hace mucha gracia que hablen de que los profesionales con experiencia considerable son cada vez más inalcanzables. ¿Acaso los buenos economistas no lo son? ¿Y los abogados? ¿Y los gestores? Cualquiera de ellos probablemente estará por encima de los buenos técnicos en materia de sueldo.

Enrique comenta que falta personal, mucho personal, y desesperadamente. Que esto está bloqueando el desarrollo de nuevos proyectos en Internet. A esto sólo cabe una respuesta. Mira los salarios que pagan. Él piensa que no puede ser así, por que dada la escasez que existe los precios subirían. Pues no. Hasta hace menos de un año se han cubierto sin problema las demandas de trabajadores a bajos sueldos y siempre había remplazo para ellos. Pero muchos se han cansado. ¿Por qué vas a programar tu por 900 euros si el de al lado, el comercial, que hace bastante menos que tú, se lleva 1800? ¿O acaso en el periodo 2000-2007 no han salido cientos y cientos de programadores? Suma ingenierías técnicas, superiores y ciclos de grado superior. Cientos de profesionales anualmente. La mayoría trabajando por sueldos ridículos en un trabajo altamente especializado. Es indudable que, como bien argumenta Enrique, la profesión sufre de un desprestigio brutal. Pero es un desprestigio provocado por la situación laboral del sector en el periodo 2001-2007.

Todos hablan de la falta de compromiso del profesional y de que quieren hacer sus 8 horas y cobrar su sueldo. ¿A alguien se le ha ocurrido pensar en el compromiso de la empresa para con el trabajador? Osea, queremos pagarles poco y que se comprometan. La empresa para qué se va a comprometer, ya le da trabajo, ¿qué mas quiere?. Pues quiero salir a las 5 de la tarde. Quiero que no me programes una reunión a las 6 de la tarde. Quiero que pienses que soy una persona y necesito mi tiempo libre, que trabajo por necesidad, porque necesito el dinero para vivir. Quiero que entiendas que, si yo estoy contento, mi trabajo será mucho mejor y estaré mas comprometido e integrado y aceptaré más retos y responsabilidades. ¿No lo ves? Además, ahora quieren profesionales comprometidos, que sepan reaccionar y con capacidad de solucionar problemas. Pero, por otro lado, una inmensa mayoría quieren programadores mandados, tú tienes que hacer esto, tú aquello y el otro lo de más allá y lo tenéis que hacer así. A picar código y me avisas cuando termines. Me quedo con esta frase de Enrique Dans:

Y es que pasar de obrero a arquitecto no sólo requiere un nivel superior de cualificación. Supone, además, que existan incentivos para ello.

Ahora está muy complicado el mercado, eso es indudable. Nuestros últimos procesos de selección se han alargado meses y meses y seguimos sin encontrar gente válida. Ya da igual que busques gente con experiencia o comprometida, es que apenas encuentras gente de ningún tipo. Es el precio que nos toca pagar de años de maltrato al profesional.

Personalmente creo que la discusión no debería ser si hay o no gente cualificada sino ¿dónde están todos los profesionales graduados los últimos años? Sencillamente resignados. El que más y el que menos ha pasado por varios trabajillos de esos de sueldos míseros. Para el que se ha pasado estudiando hasta los 23 o 25 años (en el mejor de los casos) es algo frustrante ver que no hay salida. Se han resignado y prefieren un trabajo de esos que les permite vivir tranquilamente y sin agobios esas 8 horas por un sueldo mediocre porque se han dado cuenta que no encontrarán algo mejor, y aunque lo hagan llegan al momento de creerse milongas, aún recuerdo la última vez que oí hablar de incentivos. La situación es el clásico más vale malo conocido que bueno por conocer.

Por favor, dejémonos de quejarnos de una vez. El personal técnico es, en general, gente muy preparada, capaz, profesional y, lo más importante, les encanta su trabajo. Tratémosla como tal y démosle lo que se merece. A fin de cuentas, tal como dicen todos estos gurús, en sus manos está el desarrollo de tu negocio. Recuperemos el valor de nuestra profesión.

Otro día hablaremos de la parte de culpa de las cárnicas consultoras, de las subcontratas y de la situación del sector en las Pymes, mayoría de empresas en este país.