Visor de Fotos Flex con Picasa (II)

En la anterior entrada resolvimos la todo lo concerniente a la recogida de datos de un álbum alojado en Picasa. En este post veremos una forma de resolver la parte visual del visor de fotos de la cabecera de este blog.

headerphotos

El planteamiento principal a la hora de pensar técnicamente este visor simple de fotos era como hacerlo de forma que la memoria se mantuviera estable y no tuviéramos problemas si manteníamos la ejecución durante largo tiempo, teniendo en cuenta que estamos todo el rato cargando imágenes que se quedan en el fondo del visor y que van a ir siendo tapadas en parte por las nuevas fotos.

La forma óptima es usar solo dos objetos. Un control Image de Flex que es el que irá cargando cada foto, y un Bitmap que será sobre el que iremos copiando los pixeles de cada imagen una vez terminen el recorrido de su animación (este componente puede ser un UIComponent). Una vez desarrollado este concepto, vemos que el profiler se mantiene constante como era de esperar en cuanto a la memoria consumida.

visorfotos_profiler

En cuanto a la programación llevada a cabo. Los dos objetos que intervienen son el control Imagen y un UIComponent que servirá de “lienzo” para ir copiando las fotos e ir manteniendo el uso de memoria:

<mx :UIComponent id="canvas"/>
<mx :Image id="img" smoothBitmapContent="true" complete="photoEffect.play();"/>

A continuación un poco de código de inicialización para entender que variables hay implicadas:

// --- Definición de variables y código de inicialización
// --- Bitmap sobre el que copiaremos las imagenes
private var collage:Bitmap;
// --- Array de datos descargado de PIcasa (ver post anterior)
private var fotos_arr:Array;

// --- Inicialización del canvas (UIComponent) sobre el que pintaremos las imagenes
canvas.width = width;
canvas.height = height;
collage = new Bitmap(new BitmapData(width, height, true, 0x000000));
canvas.addChild(collage);

Luego tenemos un método que se encarga de “lanzar una foto” como podemos ver a continuación:

public function throwOnePhoto():void {
        //-- Sacamos la imagen que lanzaremos del array de fotos de picasa al azar
	var tmp:Object = fotos_arr.splice(Math.random()*fotos_arr.length,1).pop();
	
	// --- hacemos los calculos iniciales (rotacion,escala,...) para dicha imagen
	angle = range*Math.random()-range/2;
	finalAngle = -angle+Math.random()*20;
	rot.angleTo = finalAngle;			
	img.scaleX = img.scaleY = 2;
	img.alpha = 0;
        // --- sacamos la url de los datos de picasa y añadimos el parametro imgmax. Esto último es *crucial* para evitar errores de carga en algunos navegadores (como siempre IE da sus problemillas). Otro pequeño truco indispensable para que funcione este servicio de Picasa correctamente.
	img.source = tmp.media.content.url + "?imgmax=800";
	img.width = tmp.media.content.width;
	img.height = tmp.media.content.height;
	img.x = Math.random()*(width - img.width);
	img.y = Math.random()*(height - img.height);
	img.rotation = angle;
	
        // --- dibujamos en la imagen un fondo blanco y proyectamos una sombra para darle aspecto de foto a la imagen
	img.graphics.clear();
	img.graphics.beginFill(0xffffff);
	img.graphics.drawRect(-5, -5, img.width+10, img.height+10);
	img.graphics.endFill();
	img.filters = [new DropShadowFilter(1, 90, 0, .75, blur, blur, 2, 2)];
}

Cada vez que queramos lanzar una foto, llamaremos al método anterior.

En cuanto al efecto utilizado cuando “lanzamos la foto”, se trata de una combinación de efectos ejecutados en paralelo:

<fx : Declarations>
	<s : Parallel id="photoEffect" duration="4000" effectEnd="copyImage(event)" >
		<s : Fade alphaTo="1" duration="3000"/>
		<s : Rotate id="rot" startDelay="500" duration="3500" autoCenterTransform="true"/>
		<s : Scale scaleXFrom="2" scaleYFrom="2" duration="3000" scaleXTo="1" scaleYTo="1" startDelay="1000" autoCenterTransform="true"/>
	</s>
</fx>

En este caso, el truco está en el uso de los parámetros startDelay y duration para hacer la combinación de timelines que buscamos. Otro parámetro clave en la nueva arquitectura de efectos de Flex 4 es autoCenterTransform, cuyo cometido es hacer que los efectos Move, Scale y Rotate actúen al unisono mediante un proxy que hace que el valor solo cambie una vez como resultado de todos los cambios de efectos sobre el objeto (de esta forma se evitan los parpadeos o “brincos” inesperados de los objetos animados.

Finalmente y una vez que el efecto a terminado, tenemos que ejecutar la parte más delicada del proceso. La copia del estado visual de la imagen en nuestro lienzo para poder reutilizar el componente Image nuevamente y evitar el uso innecesario de memoria, como comentamos anteriormente. Para ello llamamos al método copyImage cuando el efecto a terminado su ejecución:

			
//cuando termina el effecto
private function copyImage(event:EffectEvent):void {
	drawOntoBitmapData(img,collage.bitmapData);
				
	if(fotos_arr.length > 0)
		throwOnePhoto();
}

El método drawOntoBitmapData se encarga de dibujar el objeto Image sobre el Bitmap. En el conseguimos los limites de la imagen mediante getBounds (teniendo en cuenta que hemos aplicado un filtro de sombra). Y tenemos que tener igualmente en cuenta la rotación y la traslación aplicada para hacer la copia final en nuestro lienzo. Aquí se trata de darle un poco de uso a nuestros conocimientos matemáticos ;).

	
private function drawOntoBitmapData(source : IBitmapDrawable, target : BitmapData) : void {
	var bounds : Rectangle = DisplayObject(source).getBounds(DisplayObject(source));
	var bitmapData : BitmapData = new BitmapData(bounds.width+blur*2, bounds.height+blur*2, true, 0x00000000);
	var mangle:Number = finalAngle*Math.PI/180;
	var sinVal:Number = Math.sin(mangle);
	var cosVal:Number = Math.cos(mangle);
	
	var m1:Matrix= new Matrix(1, 0, 0, 1, -bounds.x+blur, -bounds.y+blur);
	bitmapData.draw(source, m1, null, null, null, true);
	
	var m2:Matrix= new Matrix(1, 0, 0, 1, bounds.x - blur, bounds.y - blur);
	m2.translate(img.x*cosVal+img.y*sinVal, img.y*cosVal-img.x*sinVal);
	m2.rotate(mangle);
	target.draw(bitmapData, m2, null, null, null, true);			
}

Y ya está. El efecto visual está terminado y listo para usar en nuestro blog :).

Espero que os haya parecido interesante. El siguiente paso es empezar a jugar con lo conseguido y experimentar con las nuevas cosas que ofrece Flex 4…¿qué tal si aplicamos ahora un efecto Rotate3D?

😉

3 Comentarios

  1. Buen ejemplo,
    realmente se mantiene estable el consumo de memoria. Es muy interesante hacer incapié en este aspecto para eliminar la creencia popular que Flex se come la memoria. Consume la que tú quieras si tomas medidas y optimizas tal y como se puede ver en tu ejemplo.

    +1 al efecto de Rotate3D! 🙂

    Saludos

  2. Totalmente de acuerdo. Muchos de los fiascos que suelen pasar en este tipo de software suele ser porque al final nos encontramos un rendimiento pésimo y un consumo de memoria fuera de lo común. Creo que es un tema a tener en cuenta desde el principio y que hay que ir manteniendo conforme desarrollas un software. En este caso es sencillo por que no deja de ser una mini aplicación, pero bueno, me pareció interesante sacarlo a la luz.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *