Saltar al contenido

¿Cómo comprimir una imagen a través de Javascript en el navegador?

Te recomendamos que revises esta respuesta en un entorno controlado antes de enviarlo a producción, un saludo.

Solución:

En breve:

  • Lea los archivos utilizando la API FileReader HTML5 con .readAsArrayBuffer
  • Cree un Blob con los datos del archivo y obtenga su URL con window.URL.createObjectURL (blob)
  • Cree un nuevo elemento de imagen y establezca su src en la URL del blob del archivo
  • Envía la imagen al lienzo. El tamaño del lienzo se establece en el tamaño de salida deseado
  • Obtenga los datos reducidos del lienzo a través de canvas.toDataURL (“image / jpeg”, 0.7) (establezca su propio formato y calidad de salida)
  • Adjunte nuevas entradas ocultas al formulario original y transfiera las imágenes dataURI básicamente como texto normal
  • En el backend, lea el dataURI, decodifique desde Base64 y guárdelo

Código fuente.

Veo dos cosas que faltan en las otras respuestas:

  • canvas.toBlob (cuando esté disponible) es más eficaz que canvas.toDataURLy también async.
  • el archivo -> imagen -> lienzo -> conversión de archivo pierde datos EXIF; en particular, los datos sobre la rotación de imágenes establecidos comúnmente por teléfonos / tabletas modernos.

El siguiente guión trata de ambos puntos:

// 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],  '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 ejemplo:

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 => );
;

La respuesta de @PsychoWoods es buena. Me gustaría ofrecer mi propia solución. Esta función de Javascript toma una URL de datos de imagen y un ancho, la escala al nuevo ancho y devuelve una nueva URL de datos.

// Take an image URL, downscale it to the given width, and return a new image URL.
function downscaleImage(dataUrl, newWidth, imageType, imageArguments)  "image/jpeg";
    imageArguments = imageArguments 

Este código se puede utilizar en cualquier lugar donde tenga una URL de datos y desee una URL de datos para una imagen reducida.

Recuerda que puedes comunicar esta crónica si te fue útil.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

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