Redimensionar imagem antes de enviar com Javascript

Normalmente, quando fazemos o upload de uma imagem nós precisamos redimensiona-lá para reduzir seu tamanho. Esse processo normalmente é feito do lado servidor, ou seja, enviamos a imagem na sua forma e tamanho original e então redimensionamos no servidor utilizando PHP, por exemplo.

Porém, em um aplicativo de galeria de fotos, por exemplo, o usuário normalmente baixa as fotos (em alta resolução) de sua câmera e quer em seguida enviar para o aplicativo. Supondo que ele deseja enviar 200 fotos, cada uma com 5 MB, seria necessário fazer o upload de 1 GB de dados para o servidor, para que então possamos reduzi-las em um tamanho viável.

Sendo assim, pretendo apresentar nesse artigo uma solução para este problema. Vamos ver como redimensionar a imagem antes de enviar ao servidor, utilizando Javascript. Para isso faremos um formulário para envio de várias imagens que serão salvas em uma pasta no servidor.

Confira o exemplo em funcionamento, clicando aqui.

O formulário

<!DOCTYPE html>
<html>

<head>

    <meta charset="utf-8">
    <title>Cadastro de Foto</title>

    <script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
    <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
    <script src="js/canvas-to-blob.min.js"></script>
    <script src="js/resize.js"></script>

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">

</head>

<body>

<div class="container">

    <h1>Cadastro de Foto</h1>

    <form method="post" action="#" role="form">

        <div class="progress">
            <div id="progresso" class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="0"
                 aria-valuemin="0" aria-valuemax="100" style="width: 0%;"></div>
        </div>

        <div class="form-group row">

            <div class="col-xs-12">
                <input id="imagem" type="file" accept="image/*" multiple/>
            </div>

        </div>

    </form>

</div>

</body>
</html>

Criamos um formulário com um input para envio das imagens. Utilizamos a propriedade accept para definir que só serão permitidos imagens e a propriedade multiple que permite o envio de várias imagens. Criamos também uma barra de progresso que será utilizada para indicar o progresso de envio, utilizando Bootstrap. Além do JQuery e Bootstrap, utilizaremos também a biblioteca resize.js que facilitará o redimensionamento.

O lado cliente

Primeiramente, vamos declarar as variáveis que serão utilizadas e definir os eventos.

// Iniciando biblioteca
var resize = new window.resize();
resize.init();

// Declarando variáveis
var imagens;
var imagem_atual;

// Quando carregado a página
$(function ($) {

    // Quando selecionado as imagens
    $('#imagem').on('change', function () {
        enviar();
    });

});

Iniciamos a biblioteca, criamos a variável imagens que será utilizada para armazenar as imagens e a variável imagem_atual que será utilizada para controlar o índice da imagem que está sendo enviada. Vinculamos um evento change no nosso input, ou seja, quando o usuário selecionar os arquivo esse evento será chamado, que por sua vez, chamará a função enviar().

Enviar

Vamos criar a função para iniciar o envio da imagens.

/*
 Envia os arquivos selecionados
 */
function enviar() 
{
    // Verificando se o navegador tem suporte aos recursos para redimensionamento
    if (!window.File || !window.FileReader || !window.FileList || !window.Blob) {
        alert('O navegador não suporta os recursos utilizados pelo aplicativo');
        return;
    }

    // Alocando imagens selecionadas
    imagens = $('#imagem')[0].files;

    // Se selecionado pelo menos uma imagem
    if (imagens.length > 0) 
    {
        // Definindo progresso de carregamento
        $('#progresso').attr('aria-valuenow', 0).css('width', '0%');

        // Escondendo campo de imagem
        $('#imagem').hide();

        // Iniciando redimensionamento
        imagem_atual = 0;
        redimensionar();
    }
}

Primeiramente, verificamos se o navegador do usuário possui os recursos necessários para manipulação de arquivos. Alocamos as imagens selecionadas na variável imagens. Verificamos se alguma imagem foi selecionada; caso haja, iniciamos a barra de progresso, escondemos o input, zeramos a variável de controle e chamamos a função redimensionar().

Redimensionar

Essa função é responsável por redimensionar, salvar e passar para próxima imagem recursivamente. Isso é necessário pois o processo de redimensionamento da imagem é assíncrono, ou seja, o resultado não é retornado imediatamente, portanto não podemos fazer um simples for percorrendo as imagens e redimensionando. Sendo assim, a variável global imagem_atual é quem controla o fluxo das imagens.

/*
 Redimensiona uma imagem e passa para a próxima recursivamente
 */
function redimensionar()
{
    // Se redimensionado todas as imagens
    if (imagem_atual > imagens.length)
    {
        // Definindo progresso de finalizado
        $('#progresso').html('Imagen(s) enviada(s) com sucesso');

        // Limpando imagens
        limpar();

        // Exibindo campo de imagem
        $('#imagem').show();

        // Finalizando
        return;
    }

    // Se não for um arquivo válido
    if ((typeof imagens[imagem_atual] !== 'object') || (imagens[imagem_atual] == null))
    {
        // Passa para a próxima imagem
        imagem_atual++;
        redimensionar();
        return;
    }

    // Redimensionando
    resize.photo(imagens[imagem_atual], 800, 'dataURL', function (imagem) {

        // Salvando imagem no servidor
        $.post('ajax/salvar.php', {imagem: imagem}, function() {

            // Definindo porcentagem
            var porcentagem = (imagem_atual + 1) / imagens.length * 100;

            // Atualizando barra de progresso
            $('#progresso').text(Math.round(porcentagem) + '%').attr('aria-valuenow', porcentagem).css('width', porcentagem + '%');

            // Aplica delay de 1 segundo
            // Apenas para evitar sobrecarga de requisições
            // e ficar visualmente melhor o progresso
            setTimeout(function () {
                // Passa para a próxima imagem
                imagem_atual++;
                redimensionar();
            }, 1000);

        });

    });
}
  • Linha 7 até 20: Essa função será chamada recursivamente, ou seja, por ela mesmo. Portanto precisamos ter um ponto final, que no caso é quando nossa variável de controle imagem_atual for maior que a quantidade de imagens. Caso isso aconteça, exibimos uma mensagem de sucesso e finalizamos a chamada da função;
  • Linha 23 até 29: Este teste é necessário, pois alguns navegadores incluem um valor nulo quando armazenamos a imagem do input, portanto caso não seja uma imagem válida, passamos para a próxima imagem, ou seja, incrementamos a variável de controle, chamamos a função novamente e finalizamos essa chamada;
  • Linha 32: Chamamos o método de redimensionamento da biblioteca. Passamos como parâmetro a imagem (imagens[imagem_atual]), a largura em pixel para qual a imagem será redimensionada, o tipo do retorno (dataURL ou file) e por fim o callback, ou seja, a função que será chamada quando o processo finalizar;
  • Linha 35: Neste trecho a imagem já foi redimensionada e está armazenada na variável imagem, portanto fazemos uma requisição AJAX ao servidor, passando a imagem via método POST;
  • Linha 38: Através de um cálculo simples, definimos a porcentagem que a imagem atual representa no total de imagens;
  • Linha 41: Definimos a porcentagem atual da barra de progresso;
  • Linha 46 até 50: Através da função setTimeout(), definimos que o trecho de código adiante só será chamado após um segundo. Isso é interessante para evitar sobrecarga no servidor e até mesmo no navegador do usuário, e também para a barra de progresso ficar mais suave. Sendo assim, um segundo após a imagem ser salva no servidor, passamos para a próxima imagem.

Limpar

/*
 Limpa os arquivos selecionados
 */
function limpar()
{
    var input = $("#imagem");
    input.replaceWith(input.val('').clone(true));
}

Utilizamos este método para limpar o input. É necessário para funcionar em versões anteriores do Internet Explorer.

O lado servidor

Recebemos a imagem no servidor em formato base64. Precisamos converte-lá em arquivo para salvá-la em disco.

<?php

// Recuperando imagem em base64
// Exemplo: data:image/png;base64,AAAFBfj42Pj4
$imagem = $_POST['imagem'];

// Separando tipo dos dados da imagem
// $tipo: data:image/png
// $dados: base64,AAAFBfj42Pj4
list($tipo, $dados) = explode(';', $imagem);

// Isolando apenas o tipo da imagem
// $tipo: image/png
list(, $tipo) = explode(':', $tipo);

// Isolando apenas os dados da imagem
// $dados: AAAFBfj42Pj4
list(, $dados) = explode(',', $dados);

// Convertendo base64 para imagem
$dados = base64_decode($dados);

// Gerando nome aleatório para a imagem
$nome = md5(uniqid(time()));

// Salvando imagem em disco
file_put_contents("../img/{$nome}.jpg", $dados);

Recebemos e armazenamos a imagem. Através das funções list() e explode() do PHP, separamos as informações contidas na imagem. Convertemos a imagem para arquivo através da função base64_decode(), geramos um nome único para ela e salvamos em uma pasta do servidor utilizando a função file_put_contents().

Conclusão

O redimensionamento de imagens no lado cliente reduz drasticamente o tempo de espera no envio das imagens, porém requer mais processamento da máquina do usuário, pois o redimensionamento está sendo feito pelo próprio navegador, mesmo assim é muito vantajoso quando trabalhamos com grande quantidade de imagens.

Vimos um exemplo simples que utiliza uma biblioteca pronta para fazer o redimensionamento, caso você precise de algo mais complexo será necessário entender melhor sobre a Canvas API do HTML5, que possibilita manipular diversos aspectos da imagem.

Enfim, espero que este exemplo tenha sido útil para você entender como funciona o redimensionamento de imagens no lado cliente.

Código fonte disponível em: https://github.com/rafaelcouto/redimensionar-imagem-antes-de-enviar-com-javascript/

Referências