Guardando jpgs en servidor con ActionScript 3

danii . Lunes 20 de septiembre de 2010. a las 11:03

El objetivo es el siguiente: realizar una captura de un MovieClip cualquiera y almacenarla en un archivo jpg en el servidor. Aunque se trata de una tarea sencilla en Flash utilizando ActionScript 3, sí tiene cierta complejidad al requerir librerias externas y comunicación cliente-servidor.

Googleando se encuentran bastantes ejemplos, pero muy pocos de ellos están totalmente actualizados. Mucho ha llovido en este mundillo desde 2006, cuando este proceso se hacía enviando un bitmap pixel a pixel en imágenes de 1MB tardando muchísimo el proceso de guardado. Por eso con este post actualizamos esta funcionalidad qué es muy útil y con la que seguramente casi todos acabamos peleándonos en más de un proyecto.

Podéis ver una pequeña demo funcional aquí (nota: debes permitir ventanas emergentes si quereis ver el jpg almacenado en nuestro servidor):

Get Adobe Flash player

Necesitaremos la Actionscript3 corelib para el codificado de imágenes (aunque ésta es tan sólo una de sus muchas funcionalidades, si no la conocéis todavia echarle un ojo ya mismito!). Una vez la tengamos, importamos el JPGEncoder y vamos a seguir los siguientes pasos:

  • 1- “Capturamos” el movieclip en un BitmapData con el método draw.
  • 2- A continuación, usando ya el JPGEncoder obtenemos a partir de este BitmapData un ByteArray donde estará almacenada en datos binarios la imagen ya en formato jpg. Podemos ver cómo en el siguiente fragmento de código:
    
    //mc es el movieclip que quiero capturar en jpg
    var bmd:BitmapData = new BitmapData(mc.width, mc.height);
    bmd.draw(mc);
    
    var jpgEncoder:JPGEncoder = new JPGEncoder(100);
    var bytes:ByteArray = new ByteArray();
    bytes = jpgEncoder.encode(bmd);
    
    trace(bytes);
    

    Ojo, por que al tracear el bytearray solo veremos esto en la ventana de output:

    ÿØÿà

    Lo cual podría llevarnos a pensar que no funciona nada. Pero siendo las almas inquietas que somos, no nos vamos a quedar sin saber qué significan esos caracteres. Podemos construir un pequeño bucle para mirarlos a nivel de byte:

    for(var i:int=0;i<4;i++)
      trace("ÿØÿà".charCodeAt(i).toString(16));
    

    Y el output de esto es:

    ff d8 ff e0

    Y finalmente, para quedarnos tranquilos, podemos comprobar que esta secuencia de bytes es una cabecera de jpg válida.

  • 3- Una vez tenemos la imagen en binario correctamente almacenada en un ByteArray, tan sólo resta enviarlos al servidor donde un script en PHP (aunque realmente se puede utiilizar cualquier lenguaje servidor que queráis) que se encargará de recogerlos y almacenarlos en el disco duro del servidor, guardando físicamente la imágen. Este paso no tiene realmente ningún misterio, solamente cuidado con poner las cabeceras correctas, ya que hay que tener en cuenta que se están enviando datos binarios:
    var request:URLRequest = new URLRequest("saver.php");
    var loader:URLLoader = new URLLoader();
    
    //definir manejador para el evento complete
    loader.addEventListener(Event.COMPLETE, complete_handler); 
    
    //definir manejador en caso de error i/o
    loader.addEventListener(IOErrorEvent.IO_ERROR, iOError_handler);
    
    request.method = URLRequestMethod.POST;
    request.data = bytes;
    
    var header:URLRequestHeader = new URLRequestHeader("Content-type", "application/octet-stream");
    request.requestHeaders.push(header);
    
    loader.load(request);
    
    

    También es destacable que se asigna directamente el ByteArray al atributo data del objeto URLRequest, es decir no se debe especificar ningún nombre de parámetro ni nada por el estilo.

    Por último, el código PHP en el servidor se encargará de recoger los datos binarios utilizando el input stream de PHP en lugar del menos eficiente (y, según la configuración del servidor, a veces no usable) $HTTP_RAW_POST_DATA y guardarlos en un archivo jpg:

    $data = file_get_contents("php://input");
    $fic_name = 'snapshot'.rand(1000,9999).'.jpg';
    $fp = fopen($fic_name, 'wb');
    fwrite( $fp, $data);
    fclose( $fp );
    echo $fic_name;
    

    Por supuesto, en el script servidor podemos añadir toda la lógica que necesitemos, como por ejemplo almacenado en base de datos, asegurarnos que el nombre de archivo no existe ya, y un largo etcétera. El echo final lo hacemos para comunicarle a flash el nombre de archivo que hemos almacenado, para hacer cualquier tratamiento posterior que queramos (por ejemplo, mostrarlo), y listo!

Etiquetas: , , , , , , ,

10 Comentarios
» Feed RSS de los Comentarios

  1. Marcos dice:

    Muy buen post, útil! Nosotros ya hemos trabajado con esto en AS3 y la conclusión era la misma, lo que si creo que no estábamos usando es el input stream de PHP :) Siempre se saca algo nuevo.

    Recuerdo que en AS2 el procesamiento de una imagen pixel a pixel llegaba a tardar unos 20 segundos entrre el cliente y servidor para 1024 x 768px… pero analizando el posible problema en el estudio logramos dejarlo en 11 segundos en total a base de mandarlo por filas, logrando que lo que es el server tardar prácticamente nada, ya que el mayor problema en PHP era el tratamiento como string del bitmap, al hacerlo como filas el rendimiento se disparaba al trabajar con strings mucho menos extensos :)

    Aunque… a quien le importa ya AS2??? … o quizás aun a unos cuantos? :)

  2. danii dice:

    Gracias Marcos otra vez por la crítica positiva, y tambien por el bonus track del tip para AS2, que aunque yo pongo a diox por testigo que no volveré a tocar, sé que hay pobres almas por ahí que no tienen mas remedio y les puede venir muy bien… ah los misterios de la eficiencia PHP son insondables!

  3. Toni dice:

    Muy bueno el post!!

    Una dudilla, recuerdo que existía un límite en cuanto a la resolución de JPG’s, creo recordar que unos 2000 y pico pixels, todavía existe o lo han mejorado?

    Nosotros utilizamos algo similar en un proyecto que guardaba thumbnails con poca calidad en el server, a través de Blaze enviando byteArrays, y funcionaba de lujo!!

    Muchas gracias y un saludo!!

  4. danii dice:

    Buenas Toni, el limite existia en cuanto al tamaño del bitmap que podia manejar flash, en flash9 eran efectivamente 2000 y pico largos (exactamente 2880×2880 pixels).

    Sin embargo, ahora con flash10 las cosas han cambiado bastante para mejor y se permite un total de mas de 16 millones de pixels (exactamente 16.777.215 ) repartidos como quieras entre width y height. Mira, te referencio a bit101 donde puedes encontrar toda la info al respecto (y digo TODO lo que se te ocurra, si no mira en los comentarios abajo!):
    http://www.bit-101.com/blog/?p=1426

  5. javier dice:

    Buenos tardes tengo una preguntilla, siempre que envío bytes me devuelve yoya, y no sé como enviar la cadena bytArray que te genera el jpgEncode y guardarlo en el servidor.

    He estado buscando mucho y veo mucha gente con el mismo problema, ¿podeis explicarme sobre ello?

  6. danii dice:

    Buenas javier, antes de nada perdona por la tardanza en contestarte.

    Si al hacer un trace del bytearray sólo ves “ÿØÿà”, don’t panic, que como indico en el punto 2 del post vamos bien, ya que ésa es una cabecera de jpg completamente correcta (y los únicos datos binarios que sale por la venta de output de flash).

    Para almacenarlo en servidor, échale un ojo al punto 3 del post, lo que hay que hacer es enviar los datos binarios mediante un objeto URLRequest y un URLLoader y en servidor recogerlos. En nuestro ejemplo hemos utilizado PHP, pero realmente te vale cualquier lenguaje servidor, simplemente has de tener en cuenta que lo que estás recogiendo es un stream de datos binarios.

    Suerte!

  7. Roberto dice:

    bueno les explicare acerca del ÿØÿà bueno como muchos saben es una cabecera. Pero por que no podemos seguir viendo el string???? bueno la cosa es que en las compreciones JPG luego del ÿØÿà viene un codigo hexadecimal 0x00 que equivale a null, cuando llega a esta parte simplemente el resto se borra… y hace que sea impoible trabajarlo internamente desde flash

    para que me entiendan
    for (i=0; i<myByteArray.length; i++) {
    trace(myByteArray[i])
    }

    // y para traducir Decimal a ASCII

    function DecToASCII(vari) {
    return String.fromCharCode("0x"+vari.toString(16).toUpperCase());
    }

  8. Bruno dice:

    Hola

    Excelente trabajo, me soluciono uno de los problemas que tengo, ahora me surge otro, yo tengo una imagen que carga en forma dinámica con uiloader, donde la imagen se pasa a partir de parámetros, una vez que la tengo cargada, tengo 4 flechas que arrastro, suelto y transformo en una determinada posición en la imagen, y debo de grabar esa imagen final en el server. Tienen alguna idea de como puedo hacer esto???

    Saludos

  9. danii dice:

    Buenas Bruno! Lo que debes hacer es añadir las flechas mediante addChild al mismo contenedor que la imagen que estás tratando. A partir de ahí, puedes seguir con el código del ejemplo, ya que cuando utilices el método draw el BitmapData “capturará” todos los contenidos del contenedor, es decir tanto la imagen como las flechas tal y como las tengas posicionadas. Suerte!

  10. miguel gomez dice:

    Hola
    Como puedo hacer para que me obtenga la url de la imagen generada y me le envie por email

Enviar comentario