HTML5 Ridimensionare le immagini prima del caricamento

Ecco un graffiatore di noodle.

Tenendo presente che abbiamo memoria locale HTML5 e xhr v2 e cosa no. Mi chiedevo se qualcuno potesse trovare un esempio funzionante o anche solo darmi un sì o no per questa domanda:

È ansible preimmaginare un’immagine utilizzando la nuova memoria locale (o qualsiasi altra cosa), in modo che un utente che non ha la minima idea di ridimensionare un’immagine possa trascinare la sua immagine da 10mb nel mio sito Web, ridimensionandola utilizzando la nuova localstorage e POI caricarlo alla dimensione più piccola.

So perfettamente che puoi farlo con Flash, applet Java, X attivo … La domanda è se puoi fare con Javascript + Html5.

In attesa di risposta su questo.

Ta per ora.

Sì, utilizza l’ API File , quindi puoi elaborare le immagini con l’ elemento Canvas .

Questo post sul blog di Mozilla Hacks ti guida attraverso la maggior parte del processo. Per riferimento ecco il codice sorgente assemblato dal post del blog:

// from an input element var filesToUpload = input.files; var file = filesToUpload[0]; var img = document.createElement("img"); var reader = new FileReader(); reader.onload = function(e) {img.src = e.target.result} reader.readAsDataURL(file); var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0); var MAX_WIDTH = 800; var MAX_HEIGHT = 600; var width = img.width; var height = img.height; if (width > height) { if (width > MAX_WIDTH) { height *= MAX_WIDTH / width; width = MAX_WIDTH; } } else { if (height > MAX_HEIGHT) { width *= MAX_HEIGHT / height; height = MAX_HEIGHT; } } canvas.width = width; canvas.height = height; var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, width, height); var dataurl = canvas.toDataURL("image/png"); //Post dataurl to the server with AJAX 

Ho affrontato questo problema alcuni anni fa e ho caricato la mia soluzione su github come https://github.com/rossturner/HTML5-ImageUploader

La risposta di robertc usa la soluzione proposta nel post del blog di Mozilla Hacks , tuttavia ho trovato che questo ha dato una qualità dell’immagine davvero scarsa quando si ridimensiona ad una scala che non era 2: 1 (o un suo multiplo). Ho iniziato a sperimentare con algoritmi di ridimensionamento delle immagini diversi, anche se la maggior parte ha finito per essere piuttosto lenta o anche la qualità non era eccezionale.

Alla fine ho trovato una soluzione che ritengo sia rapida e che abbia anche buone prestazioni – poiché la soluzione Mozilla di copiare da una canvas all’altra funziona rapidamente e senza perdita di qualità dell’immagine con un rapporto 2: 1, dato un objective di x pixel larghi e alti y pixel, utilizzo questo metodo di ridimensionamento della canvas fino a quando l’immagine è compresa tra x e 2 xe y y 2 y . A questo punto mi rivolgo quindi al ridimensionamento di un’immagine algoritmica per il “passo” finale del ridimensionamento verso il basso fino alla dimensione di destinazione. Dopo aver provato diversi algoritmi ho optato per l’interpolazione bilineare presa da un blog che non è più online ma accessibile tramite l’archivio Internet , che dà buoni risultati, ecco il codice applicabile:

 ImageUploader.prototype.scaleImage = function(img, completionCallback) { var canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height); while (canvas.width >= (2 * this.config.maxWidth)) { canvas = this.getHalfScaleCanvas(canvas); } if (canvas.width > this.config.maxWidth) { canvas = this.scaleCanvasWithAlgorithm(canvas); } var imageData = canvas.toDataURL('image/jpeg', this.config.quality); this.performUpload(imageData, completionCallback); }; ImageUploader.prototype.scaleCanvasWithAlgorithm = function(canvas) { var scaledCanvas = document.createElement('canvas'); var scale = this.config.maxWidth / canvas.width; scaledCanvas.width = canvas.width * scale; scaledCanvas.height = canvas.height * scale; var srcImgData = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height); var destImgData = scaledCanvas.getContext('2d').createImageData(scaledCanvas.width, scaledCanvas.height); this.applyBilinearInterpolation(srcImgData, destImgData, scale); scaledCanvas.getContext('2d').putImageData(destImgData, 0, 0); return scaledCanvas; }; ImageUploader.prototype.getHalfScaleCanvas = function(canvas) { var halfCanvas = document.createElement('canvas'); halfCanvas.width = canvas.width / 2; halfCanvas.height = canvas.height / 2; halfCanvas.getContext('2d').drawImage(canvas, 0, 0, halfCanvas.width, halfCanvas.height); return halfCanvas; }; ImageUploader.prototype.applyBilinearInterpolation = function(srcCanvasData, destCanvasData, scale) { function inner(f00, f10, f01, f11, x, y) { var un_x = 1.0 - x; var un_y = 1.0 - y; return (f00 * un_x * un_y + f10 * x * un_y + f01 * un_x * y + f11 * x * y); } var i, j; var iyv, iy0, iy1, ixv, ix0, ix1; var idxD, idxS00, idxS10, idxS01, idxS11; var dx, dy; var r, g, b, a; for (i = 0; i < destCanvasData.height; ++i) { iyv = i / scale; iy0 = Math.floor(iyv); // Math.ceil can go over bounds iy1 = (Math.ceil(iyv) > (srcCanvasData.height - 1) ? (srcCanvasData.height - 1) : Math.ceil(iyv)); for (j = 0; j < destCanvasData.width; ++j) { ixv = j / scale; ix0 = Math.floor(ixv); // Math.ceil can go over bounds ix1 = (Math.ceil(ixv) > (srcCanvasData.width - 1) ? (srcCanvasData.width - 1) : Math.ceil(ixv)); idxD = (j + destCanvasData.width * i) * 4; // matrix to vector indices idxS00 = (ix0 + srcCanvasData.width * iy0) * 4; idxS10 = (ix1 + srcCanvasData.width * iy0) * 4; idxS01 = (ix0 + srcCanvasData.width * iy1) * 4; idxS11 = (ix1 + srcCanvasData.width * iy1) * 4; // overall coordinates to unit square dx = ixv - ix0; dy = iyv - iy0; // I let the r, g, b, a on purpose for debugging r = inner(srcCanvasData.data[idxS00], srcCanvasData.data[idxS10], srcCanvasData.data[idxS01], srcCanvasData.data[idxS11], dx, dy); destCanvasData.data[idxD] = r; g = inner(srcCanvasData.data[idxS00 + 1], srcCanvasData.data[idxS10 + 1], srcCanvasData.data[idxS01 + 1], srcCanvasData.data[idxS11 + 1], dx, dy); destCanvasData.data[idxD + 1] = g; b = inner(srcCanvasData.data[idxS00 + 2], srcCanvasData.data[idxS10 + 2], srcCanvasData.data[idxS01 + 2], srcCanvasData.data[idxS11 + 2], dx, dy); destCanvasData.data[idxD + 2] = b; a = inner(srcCanvasData.data[idxS00 + 3], srcCanvasData.data[idxS10 + 3], srcCanvasData.data[idxS01 + 3], srcCanvasData.data[idxS11 + 3], dx, dy); destCanvasData.data[idxD + 3] = a; } } }; 

Questo ridimensiona un’immagine fino ad una larghezza di config.maxWidth , mantenendo le proporzioni originali. Al momento dello sviluppo, questo ha funzionato su iPad / iPhone Safari in aggiunta ai principali browser desktop (IE9 +, Firefox, Chrome), quindi mi aspetto che sarà ancora compatibile data la più ampia diffusione di HTML5 oggi. Si noti che la chiamata a canvas.toDataURL () richiede un tipo mime e una qualità dell’immagine che consentiranno di controllare la qualità e il formato del file di output (potenzialmente diverso dall’input se lo si desidera).

L’unico punto che non copre è il mantenimento delle informazioni di orientamento, senza la conoscenza di questi metadati l’immagine viene ridimensionata e salvata così com’è, perdendo i metadati all’interno dell’immagine per orientarsi, il che significa che le immagini scattate su un tablet “capovolte” erano reso come tale, anche se sarebbero stati capovolti nel mirino della fotocamera del dispositivo. Se questo è un problema, questo post sul blog ha una buona guida e esempi di codice su come realizzare questo, che sono sicuro che potrebbe essere integrato con il codice sopra.

Correzione di cui sopra:

    

Modifica della risposta di Justin che funziona per me:

  1. Aggiunto img.onload
  2. Espandi la richiesta POST con un esempio reale
 function handleFiles() { var dataurl = null; var filesToUpload = document.getElementById('photo').files; var file = filesToUpload[0]; // Create an image var img = document.createElement("img"); // Create a file reader var reader = new FileReader(); // Set the image once loaded into file reader reader.onload = function(e) { img.src = e.target.result; img.onload = function () { var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0); var MAX_WIDTH = 800; var MAX_HEIGHT = 600; var width = img.width; var height = img.height; if (width > height) { if (width > MAX_WIDTH) { height *= MAX_WIDTH / width; width = MAX_WIDTH; } } else { if (height > MAX_HEIGHT) { width *= MAX_HEIGHT / height; height = MAX_HEIGHT; } } canvas.width = width; canvas.height = height; var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, width, height); dataurl = canvas.toDataURL("image/jpeg"); // Post the data var fd = new FormData(); fd.append("name", "some_filename.jpg"); fd.append("image", dataurl); fd.append("info", "lah_de_dah"); $.ajax({ url: '/ajax_photo', data: fd, cache: false, contentType: false, processData: false, type: 'POST', success: function(data){ $('#form_photo')[0].reset(); location.reload(); } }); } // img.onload } // Load files into file reader reader.readAsDataURL(file); } 

Se non vuoi reinventare la ruota puoi provare su plupload.com

 fd.append("image", dataurl); 

Questo non funzionerà. Dal lato PHP non è ansible salvare il file con questo.

Utilizza invece questo codice:

 var blobBin = atob(dataurl.split(',')[1]); var array = []; for(var i = 0; i < blobBin.length; i++) { array.push(blobBin.charCodeAt(i)); } var file = new Blob([new Uint8Array(array)], {type: 'image/png', name: "avatar.png"}); fd.append("image", file); // blob file 

La risposta accettata funziona in modo ottimale, ma la logica di ridimensionamento ignora il caso in cui l’immagine è più grande del massimo in uno solo degli assi (ad esempio, altezza> altezza massima ma larghezza <= larghezza massima).

Penso che il seguente codice si prenda cura di tutti i casi in un modo più diretto e funzionale (ignorare le annotazioni del tipo typescript se si utilizza plain javascript):

 private scaleDownSize(width: number, height: number, maxWidth: number, maxHeight: number): {width: number, height: number} { if (width <= maxWidth && height <= maxHeight) return { width, height }; else if (width / maxWidth > height / maxHeight) return { width: maxWidth, height: height * maxWidth / width}; else return { width: width * maxHeight / height, height: maxHeight }; } 

Ridimensionare le immagini in un elemento canvas è in genere una ctriggers idea poiché utilizza l’interpolazione box più economica. L’immagine risultante evidente si degrada in qualità. Ti consiglio di utilizzare http://nodeca.github.io/pica/demo/ che può invece eseguire la trasformazione di Lanczos. La pagina dimostrativa sopra mostra la differenza tra gli approcci su canvas e Lanczos.

Utilizza inoltre i web worker per ridimensionare le immagini in parallelo. Esiste anche l’implementazione WEBGL.

Ci sono alcuni resizer di immagini online che usano pica per fare il lavoro, come https://myimageresizer.com

Puoi utilizzare dropzone.js se vuoi utilizzare un gestore di upload semplice e facile con ridimensionamento prima delle funzioni di caricamento.

Ha funzioni di ridimensionamento incorporate, ma puoi fornirne le tue se lo desideri.

Dattiloscritto

 async resizeImg(file: Blob): Promise { let img = document.createElement("img"); img.src = await new Promise(resolve => { let reader = new FileReader(); reader.onload = (e: any) => resolve(e.target.result); reader.readAsDataURL(file); }); await new Promise(resolve => img.onload = resolve) let canvas = document.createElement("canvas"); let ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0); let MAX_WIDTH = 1000; let MAX_HEIGHT = 1000; let width = img.naturalWidth; let height = img.naturalHeight; if (width > height) { if (width > MAX_WIDTH) { height *= MAX_WIDTH / width; width = MAX_WIDTH; } } else { if (height > MAX_HEIGHT) { width *= MAX_HEIGHT / height; height = MAX_HEIGHT; } } canvas.width = width; canvas.height = height; ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, width, height); let result = await new Promise(resolve => { canvas.toBlob(resolve, 'image/jpeg', 0.95); }); return result; }