J'ai abordé ce problème il y a quelques années et téléchargé ma solution sur github sous le nom https://github.com/rossturner/HTML5-ImageUploader
La réponse de robertc utilise la solution proposée dans le billet de blog Mozilla Hacks , mais j'ai trouvé que cela donnait une très mauvaise qualité d'image lors du redimensionnement à une échelle qui n'était pas 2: 1 (ou un multiple de celle-ci). J'ai commencé à expérimenter avec différents algorithmes de redimensionnement d'image, bien que la plupart aient fini par être assez lents ou bien de mauvaise qualité non plus.
Enfin, j'ai trouvé une solution qui, je pense, s'exécute rapidement et offre également de très bonnes performances - car la solution Mozilla de copie d'une toile à une autre fonctionne rapidement et sans perte de qualité d'image à un rapport 2: 1, étant donné un objectif de x pixels de large et y pixels de haut, j'utilise cette méthode de redimensionnement de la toile jusqu'à ce que l'image soit comprise entre x et 2 x , et y et 2 y . À ce stade, je passe ensuite au redimensionnement algorithmique de l'image pour la dernière "étape" de redimensionnement jusqu'à la taille cible. Après avoir essayé plusieurs algorithmes différents je me suis installé sur une interpolation bilinéaire tirée d'un blog qui n'est plus en ligne mais accessible via Internet Archive , ce qui donne de bons résultats, voici le code applicable:
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;
}
}
};
Cela réduit une image à une largeur de config.maxWidth
, en conservant le rapport hauteur / largeur d' origine. Au moment du développement, cela fonctionnait sur iPad / iPhone Safari en plus des principaux navigateurs de bureau (IE9 +, Firefox, Chrome), donc je pense qu'il sera toujours compatible compte tenu de l'adoption plus large de HTML5 aujourd'hui. Notez que l'appel canvas.toDataURL () prend un type mime et une qualité d'image qui vous permettront de contrôler la qualité et le format du fichier de sortie (potentiellement différent de l'entrée si vous le souhaitez).
Le seul point que cela ne couvre pas est de conserver les informations d'orientation, sans connaissance de ces métadonnées, l'image est redimensionnée et enregistrée telle quelle, perdant toutes les métadonnées de l'image pour l'orientation, ce qui signifie que les images prises sur une tablette "à l'envers" étaient rendus comme tels, bien qu'ils auraient été retournés dans le viseur de l'appareil photo. Si cela est un problème, cet article de blog contient un bon guide et des exemples de code sur la façon d'accomplir cela, qui, j'en suis sûr, pourraient être intégrés au code ci-dessus.