我在几年前解决了这个问题,并将解决方案上传到github,链接为
https://github.com/rossturner/HTML5-ImageUploader。
robertc的答案使用了
Mozilla Hacks blog post中提出的解决方案,但我发现当缩放比例不是2:1(或其倍数)时,这会导致图像质量非常差。我开始尝试不同的图像缩放算法,虽然大多数都很慢,或者质量也不太好。
最终,我想出了一种解决方案,我相信它执行快速并且性能也很好 - 由于Mozilla的解决方案在1个画布上复制到另一个画布时以2:1的比例快速执行且不会损失图像质量,给定目标宽度为
x像素和高度为
y像素,我使用此画布调整大小方法,直到图像大小介于
x和2
x之间,以及
y和2
y之间。在这一点上,我转向算法图像调整,对于最终的“步骤”,将其调整为目标大小。尝试了几种不同的算法后,我选择了从一个不再在线但可以通过
互联网档案馆访问的博客中采用的双线性插值,可以得到良好的结果,以下是适用的代码:
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);
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);
ix1 = (Math.ceil(ixv) > (srcCanvasData.width - 1) ? (srcCanvasData.width - 1) : Math.ceil(ixv));
idxD = (j + destCanvasData.width * i) * 4;
idxS00 = (ix0 + srcCanvasData.width * iy0) * 4;
idxS10 = (ix1 + srcCanvasData.width * iy0) * 4;
idxS01 = (ix0 + srcCanvasData.width * iy1) * 4;
idxS11 = (ix1 + srcCanvasData.width * iy1) * 4;
dx = ixv - ix0;
dy = iyv - iy0;
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;
}
}
};
这个功能将图像缩小到
config.maxWidth
的宽度,保持原始宽高比。在开发时,它适用于iPad/iPhone Safari以及主流桌面浏览器(IE9+,Firefox,Chrome),因此我希望它仍然兼容HTML5的更广泛应用。请注意,canvas.toDataURL()调用需要一个MIME类型和图像质量,这将允许您控制质量和输出文件格式(如果需要,可能与输入不同)。
唯一没有涵盖的是保持方向信息,如果没有这些元数据,图像会按原样调整大小并保存,从而丢失图像内任何有关方向的元数据,这意味着在平板电脑上拍摄的“倒置”图像仍将呈现为倒置状态,尽管在设备的相机取景器中它们已经被翻转了。如果这是一个问题,
这篇博客文章提供了一个很好的指南和代码示例,说明如何完成此操作,我相信可以将其集成到上述代码中。
src
属性之前,应该等待图片的onload
事件(在附加处理程序之后),因为浏览器允许异步解码,像素在drawImage
之前可能不可用。请注意保持原文意思并尽量使翻译通俗易懂。 - Kornelctx.drawImage()
之前,大型图片还没被加载出来。 - Greg