Dec
04

Aumentar a velocidade no carregamento de PNG’s convertendo-os para SWF

Fizemos na Webfuel um projecto há uns meses para um dos principais criadores de joias nacionais. Um dos requisitos do cliente era que na secção de showroom, cada joia pudesse ser apresentada dentro de uma ferramenta de zoom que permitisse ver a joia em detalhe - implicando que as fotos das joias tivessem resoluções superiores a 700×700 pixeis. Outro dos requisitos, era que as imagens das joias não tivessem fundo para que pudessem encaixar correctamente no layout do site - implicando que teria que ser escolhido um formato que suportasse transparência, neste caso PNG. Para terminar, era imperativo que o cliente, sem quaiquer conhecimentos de informática, pudesse adicionar e actualizar as fotos das joias através do backoffice - implicando que cada foto fosse um PNG colocado no servidor pelo cliente através das funcionalidades do backoffice.

Estas pré-condições implicaram o recurso ao formato PNG - o único que permitiria resolver o problema, visto a norma JPEG2000 não ser suportada pelo Flash Player. Porém, adoptar o formato PNG para as fotos das joias com as dimensões acima referidas resultou em ficheiros de cerca de 500KB. Isto, num site com cerca de 200 a 300 jóias, com várias fotos cada.

Depois do deployment do site constatou-se o esperado: em ligações rápidas, os 500KB de cada foto não representavam grandes problems, mas em ligações lentas podia ser desesperante esperar de 10 a 20 segundos para a foto carregar. E com o disseminar recente das ligações 3,5G (kangurus, vodafone e tmn connect box, etc), tornou-se problemático pelo que tivemos que procurar por uma solução.

Encontrámos uma extensão chamada PNG2SWF pertencente ao pacote SWFTools que permitia converter um PNG para um SWF. Como é sabido, um PNG embebido em SWF pode levar compressão sendo mantida a transparência, pelo que decidimos fazer algumas experiências. Após alguns testes, tivemos resultados impressionantes: imagens de 500 Kb passaram para 60 Kb sem serem perdidos os canais alpha. Exactamente o que precisávamos!

O problema que surgiu de seguida consistia em saber como integrar o PNG2SWF com o site / backoffice sem afectar a experiência do utilizador. Era importante que o cliente continuasse a utilizar o backoffice como sempre, sem trabalho adicional.

Fizemos então um script simples, que vos ofereço adiante, e que consiste num género de proxy para carregar PNGs, só que devolve o PNG convertido para SWF, e escalado para dimensões arbitrárias escolhidas pelo programador.

O download do script pode ser feito aqui: pngoptimize . O source code pode ser visto abaixo:

<?php

    // 31-10-2008
    // pngOptimize.php by João Saleiro - Webfuel ( joao.saleiro@webfuel.pt)
    // Todo:
    // - receive quality from $_GET vars
    // - set default values for quality, w and h

    $image = $_GET['url'];
    $w = $_GET['w'];
    $h = $_GET['h'];

    $date = filemtime($image);

    // Generate SWF filename
    $swf = $image . $w . $h .'_'. $date .'.swf';

    // Generate SWF if it doesn't exist
    if (!file_exists($swf))
    {
        // Calculate dimensions
        list($width, $height) = getimagesize($image);

        $proportion = 1;
        if ($width > $height)
            $proportion = $w/$width;
        else
            $proportion = $h/$height;

        // Load image and preserver transparency
        $im = @imageCreateFromPNG ($image);
        imagealphablending($im, false);
        imageinterlace ( $im, 0);
        imagesavealpha($im, true);

        // Create new Image
        $im_dest = imagecreatetruecolor ($width*$proportion,  $height*$proportion);

        // Set transparency
        $background = imagecolortransparent($im);
        imagecolortransparent($im_dest, $background);
        imagealphablending($im_dest, false);
        imagesavealpha($im_dest, true);

        // Resize old image to new image
        imagecopyresampled($im_dest, $im, 0, 0, 0, 0, $width*$proportion, $height*$proportion, $width, $height);

        // Save new image
        $tempName=$image.'temp.png';
        imagepng($im_dest, $tempName);

        // Clean memory
        imagedestroy($im);
        imagedestroy($im_dest);    

        // Convert new image to SWF
        shell_exec("./png2swf -j 85 -o $swf $tempName");

        // Remove temp file
        unlink($tempName);
    }

    // If we get here, and there's no file, we don't return nothing
    if (!file_exists($swf))
        exit(0);

    // Return generated SWF
    header("Content-type: application/x-shockwave-flash");
    $fp = fopen($swf,"rb");
    fpassthru($fp);
    fclose($fp);
?>

O algoritmo é relativamente simples:

  1. O script recebe por GET os parâmetros: url, w e h, que correspondem ao endereço relativo do PNG, e as dimensões que queremos para o nosso SWF resultante;
  2. O script vai então gerar um nome de ficheiro único para aquele url, com aquelas dimensões, e para a data do PNG (i.e. se o PNG for actualizado pelo cliente o script detecta que há um PNG novo, o que implica gerar um novo SWF com novo nome);
  3. É verificado se já existe algum SWF com aquele nome único (i.e. uma versão em cache daquele PNG já convertido para SWF com aquelas dimensões). Se existir, o SWF é aberto e lido, e feito um fpassthru do ficheiro (i.e. é devolvido o conteúdo do ficheiro SWF), depois de definido o header application/x-shockwave-flash para que o Flash possa interpretar o resultado do ficheiro PHP como sendo um SWF;
  4. Se não existir nenhum SWF, o script vai abrir o PNG referido no URL, e criar um novo PNG temporário com as dimensões referidas, mantendo a transparência;
  5. É então feita a conversão desse PNG temporário para SWF através de um shell_exec que executa o png2swf com os parâmetros necessários para a conversão, sendo gerado um SWF com o nome de ficheiro referido acima, e devolvido com o fpassthru.

Para utilizar este script é necessário:

  • Colocar no servidor, na mesma directoria do pngConvert.php, o png2swf (linux) ou png2swf.exe (windows);
  • Permissões de execução do png2swf nessa directoria;
  • Permissões do php para a chamada de comandos externos (shell_exec);
  • Permissões de escrita nas directorias onde estão os ficheiros PNG para serem lá colocados os ficheiros SWF;

Do lado do cliente, o código necessário para carregar um PNG convertido para SWF através do pngConvert é:

  • MXML:
<mx:Image source="pngConvert.php?url=imagem.png&w=100&h=100"/>
  • Actionscript 3 (i.e. Flash)
var l:Loader=new Loader();
l.load(new URLRequest('pngConvert.php?url=imagem.png&w=100&h=100'));
addChild(l);

O script ainda pode levar algumas melhorias, nomeadamente:

  • a qualidade da imagem também ser passada por GET;
  • se os parâmetros w e h não estiverem definidos, o SWF gerado é da mesma dimensão que o PNG original.

Não implementei essas melhorias porque não precisei na altura, mas se alguém quiser melhorar, é bem vindo - publicarei depois aqui a versão melhorada, com devidos créditos.

Espero que esta informação e script vos sejam úteis. O script pode ser utilizado livremente e só pedimos que seja colocado um comentário neste post com o endereço do site onde o estão a usar, para alegrarem o nosso dia.

2 Comments

Make A Comment
  • a gravatar PauloAzevedo Said:

    Interessante artigo João, parabéns. Tens algum sitio onde se possa ver isso a funcionar?

    Relativamente ao script em si e fazendo uma análise superficial, atenção:
    - Receber o nome do ficheiro da imagem sem qualquer verificação é má ideia! Pois alguém pode servir-se do teu script para gerar os próprios swf ou algo pior …

    - Não vejo motivação para receber os parâmetros apenas por _GET.

    - Esse sistema de cache que implementaste não acaba por deixar “lixo”? Penso que deverias comparar as datas de modificação dos ficheiros originais.

  • a gravatar João Saleiro Said:

    Boas,

    são bons argumentos, especialmente no que toca à não validação do parâmetro url. É muito fácil fazer um ataque à maquina com o script acima.
    Quando tiver tempo, a ver se implemento algum workaround. Entretanto, alguma sugestão?

Comments RSS Feed   TrackBack URL

Leave a comment

top