Resolvi esse problema há alguns anos e carreguei minha solução no github como https://github.com/rossturner/HTML5-ImageUploader
A resposta de robertc usa a solução proposta na postagem do blog Mozilla Hacks , no entanto, eu achei que essa qualidade de imagem era realmente ruim ao redimensionar para uma escala que não era 2: 1 (ou um múltiplo dela). Comecei a experimentar diferentes algoritmos de redimensionamento de imagem, embora a maioria acabasse sendo muito lenta ou também não era boa em qualidade.
Finalmente, eu vim com uma solução que, acredito, é executada rapidamente e tem um desempenho muito bom também - já que a solução Mozilla de copiar de uma tela para outra funciona rapidamente e sem perda da qualidade da imagem na proporção 2: 1, dado o objetivo de x pixels de largura e y pixels de altura, que usam este método de redimensionamento de lona até que a imagem está entre x e 2 x , e Y e 2 y . Nesse ponto, volto para o redimensionamento algorítmico da imagem para a "etapa" final de redimensionar para o tamanho alvo. Depois de tentar vários algoritmos diferentes, decidi pela interpolação bilinear retirada de um blog que não está mais online, mas acessível através do Internet Archive, que fornece bons resultados, eis o código aplicável:
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;
}
}
};
Isso reduz a imagem a uma largura de config.maxWidth
, mantendo a proporção original. No momento do desenvolvimento, isso funcionou no iPad / iPhone Safari, além dos principais navegadores de desktop (IE9 +, Firefox, Chrome), então espero que ainda seja compatível, dada a adoção mais ampla do HTML5 atualmente. Observe que a chamada canvas.toDataURL () usa um tipo MIME e qualidade de imagem que permite controlar a qualidade e o formato do arquivo de saída (potencialmente diferente da entrada, se desejar).
O único ponto que isso não cobre é a manutenção das informações de orientação, sem o conhecimento desses metadados, a imagem é redimensionada e salva como está, perdendo quaisquer metadados na imagem para orientação, o que significa que as imagens tiradas em um tablet "de cabeça para baixo" foram renderizados como tal, apesar de terem sido invertidos no visor da câmera do dispositivo. Se isso for uma preocupação, esta postagem no blog tem um bom guia e exemplos de código sobre como fazer isso, que eu tenho certeza que poderia ser integrado ao código acima.