2013-02-03 14 views
45

TL; DR;Come comprimere un'immagine tramite Javascript nel browser?

C'è un modo per comprimere un'immagine (principalmente jpeg, png e gif) direttamente dal lato del browser, prima di caricarla? Sono abbastanza sicuro che JavaScript possa farlo, ma non riesco a trovare un modo per raggiungerlo.


Ecco lo scenario completo vorrei implementare:

  • l'utente va al mio sito web, e scegliere un'immagine tramite un elemento input type="file",
  • questa immagine viene recuperato tramite JavaScript, facciamo alcune verifiche come il formato file corretto, la dimensione massima del file ecc.
  • se ogni cosa è OK, un'anteprima dell'immagine viene visualizzata sulla pagina,
  • l'utente può fare alcune operazioni di base come come ruotare l'immagine di 90 °/-90 °, ritagliala seguendo un rapporto predefinito, ecc. oppure l'utente può caricare un'altra immagine e tornare al punto 1,
  • quando l'utente è soddisfatto, l'immagine modificata è quindi compresso e "salvato" localmente (non salvato in un file, ma nella memoria del browser/pagina), -
  • l'utente compila un modulo con dati come nome, età ecc,
  • l'utente fa clic sul pulsante "Fine" pulsante, quindi il modulo contenente i dati + immagine compressa viene inviato al server (senza AJAX),

Il processo completo fino all'ultimo passaggio deve essere eseguito lato client e deve essere compatibile sugli ultimi Chrome e Firefox. , Safari 5+ e IE 8+. Se possibile, dovrebbe essere usato solo JavaScript (ma sono abbastanza sicuro che ciò non sia possibile).

Non ho codice in questo momento, ma ci ho già pensato. La lettura dei file in locale è possibile tramite File API, l'anteprima e la modifica delle immagini possono essere eseguite utilizzando l'elemento Canvas, ma Non riesco a trovare un modo per eseguire la parte di compressione dell'immagine.

Secondo html5please.com e caniuse.com, sostenendo coloro browser è molto difficile (grazie a IE), ma potrebbe essere fatto utilizzando polyfill come FlashCanvas e FileReader.

In realtà, l'obiettivo è ridurre la dimensione del file, quindi vedo la compressione dell'immagine come una soluzione. Ma so che le immagini caricate verranno visualizzate sul mio sito Web, ogni volta nello stesso luogo, e conosco la dimensione di questa area di visualizzazione (ad esempio 200x400). Quindi, potrei ridimensionare l'immagine per adattarla a quelle dimensioni, riducendo così la dimensione del file. Non ho idea di quale sarebbe il rapporto di compressione per questa tecnica.

Cosa ne pensi? Hai qualche consiglio da dirmi? Conoscete un modo per comprimere un'immagine lato browser in JavaScript? Grazie per le tue risposte

+0

"solo JavaScript ** (ma sono abbastanza sicuro che questo non è possibile) . ** "Forse potresti smettere di programmare come il suo 1980 dove non è possibile, e iniziare a programmare come il suo 2017 dove è possibile. :) –

risposta

85

In breve:

  • leggere i file utilizzando l'API di HTML5 FileReader con.readAsArrayBuffer
  • Creare e Blob con i dati del file e ottenere il suo URL con window.URL.createObjectURL(blob)
  • Crea nuovo elemento di immagine e impostare è Src al file blob url
  • Invia l'immagine sulla tela. Le dimensioni della tela è impostato su formato di output desiderato
  • Prendi i dati in scala ridotta di ritorno da tela tramite canvas.toDataURL ("image/jpeg", 0,7) (impostare il proprio formato di output e la qualità)
  • Fissare nuovi ingressi nascosti alla forma originale e trasferire le immagini dataURI fondamentalmente come testo normale
  • Su backend, leggere il dataURI, decodifica da Base64, e salvarla

Fonte: code.

+0

Grazie mille! Questo è quello che stavo cercando. Sai quanto è buono il rapporto di compressione con questa tecnica? – pomeh

+0

Oltre alla trasmissione di rete (si sta inviando il contenuto codificato Base64, che non è il migliore), l'algoritmo di compressione dell'immagine è uno di quelli standard, la dimensione dipende dalla qualità e dal formato che si sceglie. – psychowood

+0

Precisamente corretto. –

3

Per quanto ne so, non è possibile comprimere le immagini utilizzando la tela, invece, è possibile ridimensionarla. L'uso di canvas.toDataURL non ti consente di scegliere il rapporto di compressione da utilizzare. Puoi dare un'occhiata a canimage che fa esattamente quello che vuoi: https://github.com/nfroidure/CanImage/blob/master/chrome/canimage/content/canimage.js

In effetti, è spesso sufficiente ridimensionare l'immagine per ridurne la dimensione ma se vuoi andare oltre, dovrai usare di recente metodo file.readAsArrayBuffer per ottenere un buffer contenente i dati dell'immagine.

Poi, basta usare un DataView per leggere il suo contenuto in base alla specifica formato immagine (http://en.wikipedia.org/wiki/JPEG o http://en.wikipedia.org/wiki/Portable_Network_Graphics).

Sarà difficile gestire la compressione dei dati delle immagini, ma è peggio. D'altra parte, puoi provare a eliminare le intestazioni PNG oi dati JPEG exif per rendere l'immagine più piccola, dovrebbe essere più facile farlo.

Dovrai creare un altro DataWiew su un altro buffer e riempirlo con il contenuto dell'immagine filtrata. Quindi, dovrai solo codificare il contenuto dell'immagine in DataURI usando window.btoa.

Fatemi sapere se implementate qualcosa di simile, sarà interessante passare attraverso il codice.

8

La risposta di @PsychoWoods è buona. Vorrei offrire la mia soluzione. Questa funzione Javascript accetta un URL di dati immagine e una larghezza, li ridimensiona alla nuova larghezza e restituisce un nuovo URL di dati.

// 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; 
} 

Questo codice può essere utilizzato ovunque si disponga di un URL di dati e si desideri un URL di dati per un'immagine ridimensionata.

+0

per favore, puoi darmi maggiori dettagli su questo esempio, come chiamare la funzione e come è stato restituito il risultato? – nabil

+0

Ecco un esempio: http://danielsadventure.info/Html/scaleimage.html Assicurati di leggere l'origine della pagina per vedere come funziona. –

0

Per JPG compressione delle immagini è possibile utilizzare la migliore tecnica di compressione chiamato JIC (Javascript compressione delle immagini) Questo sarà sicuramente aiutare ->https://github.com/brunobar79/J-I-C

+1

senza ridurre la qualità –

5

vedo due cose mancanti dalle altre risposte:

  • canvas.toBlob (se disponibile) è più performante di canvas.toDataURL e anche asincrono.
  • il file -> immagine -> tela -> conversione file perde i dati EXIF; in particolare, i dati sulla rotazione delle immagini sono comunemente impostati dai telefoni/tablet moderni.

le seguenti offerte di script con entrambi i punti: utilizzo

// 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); 
} 

Esempio:

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(...); 
    }); 
}; 
deve essere utilizzato
+0

ToBlob ha fatto il trucco per me, creando un file e ricevendo l'array $ _FILES sul server. Grazie! –