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 utilizza la soluzione proposta nel post sul blog di Mozilla Hacks , tuttavia ho scoperto che ciò ha dato una qualità dell'immagine davvero scadente quando si ridimensionava su una scala che non era 2: 1 (o un suo multiplo). Ho iniziato a sperimentare diversi algoritmi di ridimensionamento delle immagini, sebbene la maggior parte finisse per essere piuttosto lenta o altrimenti non era eccezionale in termini di qualità.
Alla fine ho trovato una soluzione che credo si esegua rapidamente e abbia anche prestazioni abbastanza buone - poiché la soluzione Mozilla di copiare da una tela all'altra funziona rapidamente e senza perdita di qualità dell'immagine con un rapporto di 2: 1, dato un obiettivo di x pixel di larghezza e y altezza pixel, che utilizzano questo metodo di ridimensionamento tela finché l'immagine è tra x e 2 x ed y e 2 y . A questo punto passo quindi al ridimensionamento algoritmico dell'immagine per il "passo" finale del ridimensionamento fino alla dimensione target. Dopo aver provato diversi algoritmi ho optato per l'interpolazione bilineare presa da un blog che non è più online ma accessibile tramite Internet Archive, che fornisce 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 a una larghezza di config.maxWidth
, mantenendo le proporzioni originali. Al momento dello sviluppo questo funzionava su iPad / iPhone Safari oltre ai principali browser desktop (IE9 +, Firefox, Chrome), quindi mi aspetto che sarà ancora compatibile dato il più ampio utilizzo di HTML5 oggi. Nota che la chiamata canvas.toDataURL () accetta un tipo mime e una qualità dell'immagine che ti permetteranno di controllare la qualità e il formato del file di output (potenzialmente diverso da inserire se lo desideri).
L'unico punto che non copre è il mantenimento delle informazioni sull'orientamento, senza la conoscenza di questi metadati l'immagine viene ridimensionata e salvata così com'è, perdendo qualsiasi metadata all'interno dell'immagine per orientamento, il che significa che le immagini scattate su un dispositivo tablet "sottosopra" erano renderizzato 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 ed esempi di codice su come realizzare questo, che sono sicuro che potrebbe essere integrato con il codice sopra.