Abordé este problema hace unos años y subí mi solución a github como https://github.com/rossturner/HTML5-ImageUploader
La respuesta de robertc utiliza la solución propuesta en la publicación del blog de Mozilla Hacks , sin embargo, descubrí que esto proporcionaba una calidad de imagen realmente pobre al cambiar el tamaño a una escala que no era 2: 1 (o un múltiplo de la misma). Comencé a experimentar con diferentes algoritmos de cambio de tamaño de imagen, aunque la mayoría terminó siendo bastante lenta o de lo contrario tampoco fue de gran calidad.
Finalmente, encontré una solución que creo que se ejecuta rápidamente y que también tiene un rendimiento bastante bueno, ya que la solución de Mozilla de copiar de 1 lienzo a otro funciona rápidamente y sin pérdida de calidad de imagen en una proporción de 2: 1, dado un objetivo de x píxeles de ancho y píxeles de alto, uso este método de cambio de tamaño del lienzo hasta que la imagen esté entre x y 2 x , y y y 2 y . En este punto, paso a cambiar el tamaño de la imagen algorítmica para el "paso" final de cambiar el tamaño al tamaño deseado. Después de probar varios algoritmos diferentes, me decidí por la interpolación bilineal tomada de un blog que ya no está en línea pero que es accesible a través de Internet Archive, que da buenos resultados, aquí está el código aplicable:
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;
}
}
};
Esto reduce la imagen a un ancho de config.maxWidth
, manteniendo la relación de aspecto original. En el momento del desarrollo, esto funcionaba en iPad / iPhone Safari, además de los principales navegadores de escritorio (IE9 +, Firefox, Chrome), por lo que espero que aún sea compatible dada la mayor aceptación de HTML5 en la actualidad. Tenga en cuenta que la llamada canvas.toDataURL () toma un tipo mime y una calidad de imagen que le permitirá controlar la calidad y el formato del archivo de salida (potencialmente diferente de la entrada si lo desea).
El único punto que esto no cubre es mantener la información de orientación, sin el conocimiento de estos metadatos, la imagen se redimensiona y se guarda tal cual, perdiendo cualquier metadato dentro de la imagen para orientación, lo que significa que las imágenes tomadas en un dispositivo de tableta "al revés" fueron renderizado como tal, aunque se habrían volteado en el visor de la cámara del dispositivo. Si esto es una preocupación, esta publicación de blog tiene una buena guía y ejemplos de código sobre cómo lograr esto, que estoy seguro podría integrarse en el código anterior.