Como comprimir uma imagem através de Javascript no navegador?

TL; DR;

Existe uma forma de comprimir uma imagem (principalmente jpeg, png e gif) directamente do lado do navegador, antes de a carregar ? Tenho a certeza que o JavaScript consegue fazer isto, mas não consigo encontrar uma forma de o conseguir.


Aqui está o cenário completo que eu gostaria de implementar:

  • o utilizador vai ao meu site, e escolhe uma imagem através de um elemento {[[0]},
  • esta imagem é recuperada através de JavaScript, fazemos alguma verificação, como o formato de ficheiro correcto, tamanho máximo do ficheiro etc,
  • se tudo estiver bem, uma antevisão da imagem é mostrada na página,
  • o utilizador pode fazer algumas operações básicas, tais como rodar a imagem em 90° / -90°, recortá-la de acordo com uma relação pré-definida, etc, ou o utilizador pode enviar outra imagem e voltar ao passo 1,
  • quando o Utilizador está satisfeito, a imagem editada é então comprimida e" gravada " localmente (não gravada num ficheiro, mas na memória/página do navegador), -
  • o utilizador preenche um formulário com dados como nome, idade, etc.,
  • se o Utilizador carregar no botão "Terminar" , então o formulário que contém dados + imagem comprimida é enviado para o servidor (sem AJAX),

o processo completo até à última etapa deve ser feito do lado do cliente,e deve ser compatível no último Chrome e Firefox, Safari 5+ e IE 8+. Se possível, apenas JavaScript deve ser usado (mas eu tenho certeza que isso não é possível).

Ainda não programei nada, mas já pensei nisso. Arquivo a leitura local é possível através do File API, a antevisão e a edição da imagem podem ser feitas com o elemento Canvas, mas não consigo encontrar uma forma de fazer a parte de compressão da imagem.

de Acordo com html5please.com e caniuse.com, apoiando aqueles navegador é muito difícil (graças ao IE), mas pode ser feito usando o suporte retroativo, tais como FlashCanvas e FileReader.

Na verdade, o objectivo é reduzir o tamanho do ficheiro, por isso vejo a imagem compressão como solução. Mas, eu sei que imagens carregadas vão ser exibidas no meu site, todas as vezes no mesmo lugar ,e eu sei a dimensão desta área de exibição (eg. 200x400). Então, eu poderia redimensionar a imagem para caber nessas dimensões, reduzindo assim o tamanho do arquivo. Não faço ideia qual seria a taxa de compressão para esta técnica.

O que achas ? Tens algum conselho para me dizer ? Sabe como comprimir um navegador de imagens em JavaScript ? Obrigado pela tua ajuda. resposta.

Author: pomeh, 2013-02-03

6 answers

Resumindo:

  • Leia os ficheiros com a API HTML5 FileReader .readAsArrayBuffer
  • criar e Blob com os dados do ficheiro e obter o seu url com a janela .ENDERECO.createObjectURL (blob)
  • Crie um novo elemento de imagem e configure o seu src para o ficheiro blob url
  • Envie a imagem para a tela. O tamanho da área de desenho está definido para o tamanho de saída desejado
  • retirem os dados da área de desenho através da tela.toDataURL ("image/jpeg", 0, 7) (defina o seu próprio formato de saída e qualidade)
  • Anexar novas entradas escondidas à forma original e transferir as imagens dataURI basicamente como texto normal
  • na infra-estrutura, leia o dataURI, decodifice do Base64, e guarde-o

Fonte: Código .

 99
Author: psychowood, 2014-11-13 20:06:49
A resposta de @ PsychoWoods é boa. Gostaria de oferecer a minha própria solução. Esta função Javascript pega um URL de dados de imagem e uma largura, escala-o para a nova largura, e retorna um novo URL de dados.
// Take an image URL, downscale it to the given width, and return a new image URL.
function downscaleImage(dataUrl, newWidth, imageType, imageArguments) {
    "use strict";
    var image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl;

    // Provide default values
    imageType = imageType || "image/jpeg";
    imageArguments = imageArguments || 0.7;

    // Create a temporary image so that we can compute the height of the downscaled image.
    image = new Image();
    image.src = dataUrl;
    oldWidth = image.width;
    oldHeight = image.height;
    newHeight = Math.floor(oldHeight / oldWidth * newWidth)

    // Create a temporary canvas to draw the downscaled image on.
    canvas = document.createElement("canvas");
    canvas.width = newWidth;
    canvas.height = newHeight;

    // Draw the downscaled image on the canvas and return the new data URL.
    ctx = canvas.getContext("2d");
    ctx.drawImage(image, 0, 0, newWidth, newHeight);
    newDataUrl = canvas.toDataURL(imageType, imageArguments);
    return newDataUrl;
}

Este código pode ser usado em qualquer lugar que tenha um URL de dados e queira um URL de dados para uma imagem desligada.

 9
Author: Daniel Allen Langdon, 2016-11-26 15:45:21
Vejo duas coisas que faltam nas outras respostas:
  • canvas.toBlob (quando disponível) é mais performante do que canvas.toDataURL, e também async.
  • a conversão de ficheiros -> imagem -> tela -> perde os dados do EXIF; em particular, os dados sobre a rotação de imagens normalmente definidos pelos telemóveis/tablets modernos.

O seguinte script lida com ambos os pontos:

// From https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob, needed for Safari:
if (!HTMLCanvasElement.prototype.toBlob) {
    Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
        value: function(callback, type, quality) {

            var binStr = atob(this.toDataURL(type, quality).split(',')[1]),
                len = binStr.length,
                arr = new Uint8Array(len);

            for (var i = 0; i < len; i++) {
                arr[i] = binStr.charCodeAt(i);
            }

            callback(new Blob([arr], {type: type || 'image/png'}));
        }
    });
}

window.URL = window.URL || window.webkitURL;

// Modified from https://stackoverflow.com/a/32490603, cc by-sa 3.0
// -2 = not jpeg, -1 = no data, 1..8 = orientations
function getExifOrientation(file, callback) {
    // Suggestion from http://code.flickr.net/2012/06/01/parsing-exif-client-side-using-javascript-2/:
    if (file.slice) {
        file = file.slice(0, 131072);
    } else if (file.webkitSlice) {
        file = file.webkitSlice(0, 131072);
    }

    var reader = new FileReader();
    reader.onload = function(e) {
        var view = new DataView(e.target.result);
        if (view.getUint16(0, false) != 0xFFD8) {
            callback(-2);
            return;
        }
        var length = view.byteLength, offset = 2;
        while (offset < length) {
            var marker = view.getUint16(offset, false);
            offset += 2;
            if (marker == 0xFFE1) {
                if (view.getUint32(offset += 2, false) != 0x45786966) {
                    callback(-1);
                    return;
                }
                var little = view.getUint16(offset += 6, false) == 0x4949;
                offset += view.getUint32(offset + 4, little);
                var tags = view.getUint16(offset, little);
                offset += 2;
                for (var i = 0; i < tags; i++)
                    if (view.getUint16(offset + (i * 12), little) == 0x0112) {
                        callback(view.getUint16(offset + (i * 12) + 8, little));
                        return;
                    }
            }
            else if ((marker & 0xFF00) != 0xFF00) break;
            else offset += view.getUint16(offset, false);
        }
        callback(-1);
    };
    reader.readAsArrayBuffer(file);
}

// Derived from https://stackoverflow.com/a/40867559, cc by-sa
function imgToCanvasWithOrientation(img, rawWidth, rawHeight, orientation) {
    var canvas = document.createElement('canvas');
    if (orientation > 4) {
        canvas.width = rawHeight;
        canvas.height = rawWidth;
    } else {
        canvas.width = rawWidth;
        canvas.height = rawHeight;
    }

    if (orientation > 1) {
        console.log("EXIF orientation = " + orientation + ", rotating picture");
    }

    var ctx = canvas.getContext('2d');
    switch (orientation) {
        case 2: ctx.transform(-1, 0, 0, 1, rawWidth, 0); break;
        case 3: ctx.transform(-1, 0, 0, -1, rawWidth, rawHeight); break;
        case 4: ctx.transform(1, 0, 0, -1, 0, rawHeight); break;
        case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
        case 6: ctx.transform(0, 1, -1, 0, rawHeight, 0); break;
        case 7: ctx.transform(0, -1, -1, 0, rawHeight, rawWidth); break;
        case 8: ctx.transform(0, -1, 1, 0, 0, rawWidth); break;
    }
    ctx.drawImage(img, 0, 0, rawWidth, rawHeight);
    return canvas;
}

function reduceFileSize(file, acceptFileSize, maxWidth, maxHeight, quality, callback) {
    if (file.size <= acceptFileSize) {
        callback(file);
        return;
    }
    var img = new Image();
    img.onerror = function() {
        URL.revokeObjectURL(this.src);
        callback(file);
    };
    img.onload = function() {
        URL.revokeObjectURL(this.src);
        getExifOrientation(file, function(orientation) {
            var w = img.width, h = img.height;
            var scale = (orientation > 4 ?
                Math.min(maxHeight / w, maxWidth / h, 1) :
                Math.min(maxWidth / w, maxHeight / h, 1));
            h = Math.round(h * scale);
            w = Math.round(w * scale);

            var canvas = imgToCanvasWithOrientation(img, w, h, orientation);
            canvas.toBlob(function(blob) {
                console.log("Resized image to " + w + "x" + h + ", " + (blob.size >> 10) + "kB");
                callback(blob);
            }, 'image/jpeg', quality);
        });
    };
    img.src = URL.createObjectURL(file);
}

Uso de exemplo:

inputfile.onchange = function() {
    // If file size > 500kB, resize such that width <= 1000, quality = 0.9
    reduceFileSize(this.files[0], 500*1024, 1000, Infinity, 0.9, blob => {
        let body = new FormData();
        body.set('file', blob, blob.name || "file.jpg");
        fetch('/upload-image', {method: 'POST', body}).then(...);
    });
};
 8
Author: Simon Lindholm, 2017-06-30 14:46:34

Editar: de acordo com o comentário do Sr. Me sobre esta resposta, parece que a compressão está agora disponível para formatos JPG / WebP (ver https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL ).

Tanto quanto sei, não se pode comprimir imagens usando tela, em vez disso, pode-se redimensioná-la. Usando tela.toDataURL não lhe permitirá escolher a taxa de compressão a usar. Você pode dar uma olhada em canimage que faz exatamente o que você quer : https://github.com/nfroidure/CanImage/blob/master/chrome/canimage/content/canimage.js

Na verdade, muitas vezes é suficiente apenas redimensionar a imagem para diminuir o seu tamanho, mas se você quiser ir mais longe, você terá que usar o arquivo método recentemente introduzido.readAsArrayBuffer para obter um buffer contendo os dados da imagem.

Então, basta usar uma vista de dados para ler o seu conteúdo de acordo com a especificação do formato da imagem ( http://en.wikipedia.org/wiki/JPEG ou http://en.wikipedia.org/wiki/Portable_Network_Graphics).

Vai ser difícil lidar com a compressão de dados de imagem, mas é pior tentar. Por outro lado, você pode tentar excluir os cabeçalhos PNG ou os dados JPEG exif para tornar a sua imagem menor, deve ser mais fácil de fazer isso.

Você terá que criar outro DataWiew em outro buffer e preenchê-lo com o conteúdo da imagem filtrada. Então, você só terá que codificar seu conteúdo de imagem para DataURI usando janela.btoa.

Avisa-me se implementares algo semelhante, será interessante ver o código.
 3
Author: nfroidure, 2018-06-25 14:33:32

Tive um problema com a função downscaleImage() colocada acima por @daniel-allen-langdon, na medida em que as propriedades image.width e image.height não estão disponíveis imediatamente porque a carga da imagem é assíncrona.

Por favor, veja o exemplo do TypeScript actualizado abaixo que leva isto em conta, usa as funções async e redimensiona a imagem com base na maior dimensão, em vez de apenas a largura

function getImage(dataUrl: string): Promise<HTMLImageElement> 
{
    return new Promise((resolve, reject) => {
        const image = new Image();
        image.src = dataUrl;
        image.onload = () => {
            resolve(image);
        };
        image.onerror = (el: any, err: ErrorEvent) => {
            reject(err.error);
        };
    });
}

export async function downscaleImage(
        dataUrl: string,  
        imageType: string,  // e.g. 'image/jpeg'
        resolution: number,  // max width/height in pixels
        quality: number   // e.g. 0.9 = 90% quality
    ): Promise<string> {

    // Create a temporary image so that we can compute the height of the image.
    const image = await getImage(dataUrl);
    const oldWidth = image.naturalWidth;
    const oldHeight = image.naturalHeight;
    console.log('dims', oldWidth, oldHeight);

    const longestDimension = oldWidth > oldHeight ? 'width' : 'height';
    const currentRes = longestDimension == 'width' ? oldWidth : oldHeight;
    console.log('longest dim', longestDimension, currentRes);

    if (currentRes > resolution) {
        console.log('need to resize...');

        // Calculate new dimensions
        const newSize = longestDimension == 'width'
            ? Math.floor(oldHeight / oldWidth * resolution)
            : Math.floor(oldWidth / oldHeight * resolution);
        const newWidth = longestDimension == 'width' ? resolution : newSize;
        const newHeight = longestDimension == 'height' ? resolution : newSize;
        console.log('new width / height', newWidth, newHeight);

        // Create a temporary canvas to draw the downscaled image on.
        const canvas = document.createElement('canvas');
        canvas.width = newWidth;
        canvas.height = newHeight;

        // Draw the downscaled image on the canvas and return the new data URL.
        const ctx = canvas.getContext('2d')!;
        ctx.drawImage(image, 0, 0, newWidth, newHeight);
        const newDataUrl = canvas.toDataURL(imageType, quality);
        return newDataUrl;
    }
    else {
        return dataUrl;
    }

}
 1
Author: Russell Briggs, 2018-08-02 16:40:03

Para a compressão de imagens JPG pode usar a melhor técnica de compressão chamada JIC (Compressão de imagem Javascript) isto irá definitivamente ajudá-lo -- > https://github.com/brunobar79/J-I-C

 0
Author: Aravind Bhat K, 2017-01-31 07:20:01