HTML5在上传前预先调整图像大小

这是一个刮面器。

记住,我们有HTML5本地存储和XHR V2等等。我想知道是否有人能找到一个可行的例子,甚至只是给我一个是或不是这个问题:

有没有可能使用新的本地存储(或其他)来预先调整图像的大小,以便不知道如何调整图像大小的用户可以将其10MB的图像拖到我的网站中,使用新的本地存储来调整大小,然后以较小的大小上传。

我很清楚你可以用Flash,Java小程序,Active X..问题是你是否可以使用JavaScript+HTML5。

期待这一次的回应。

现在的TA.

192236 次浏览

可以,使用文件API,然后可以使用画布元素处理图像。

这篇Mozilla黑客博客文章将引导您完成大部分流程。作为参考,这里是从博客文章中汇编的源代码:

// from an input element
var filesToUpload = input.files;
var file = filesToUpload[0];


var img = document.createElement("img");
var reader = new FileReader();
reader.onload = function(e) {img.src = e.target.result}
reader.readAsDataURL(file);


var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);


var MAX_WIDTH = 800;
var MAX_HEIGHT = 600;
var width = img.width;
var height = img.height;


if (width > height) {
if (width > MAX_WIDTH) {
height *= MAX_WIDTH / width;
width = MAX_WIDTH;
}
} else {
if (height > MAX_HEIGHT) {
width *= MAX_HEIGHT / height;
height = MAX_HEIGHT;
}
}
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);


var dataurl = canvas.toDataURL("image/png");


//Post dataurl to the server with AJAX

如果你不想另起炉灶,你可以尝试Plupload.com

对上述内容的更正:

<img src="" id="image">
<input id="input" type="file" onchange="handleFiles()">
<script>


function handleFiles()
{
var filesToUpload = document.getElementById('input').files;
var file = filesToUpload[0];


// Create an image
var img = document.createElement("img");
// Create a file reader
var reader = new FileReader();
// Set the image once loaded into file reader
reader.onload = function(e)
{
img.src = e.target.result;


var canvas = document.createElement("canvas");
//var canvas = $("<canvas>", {"id":"testing"})[0];
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);


var MAX_WIDTH = 400;
var MAX_HEIGHT = 300;
var width = img.width;
var height = img.height;


if (width > height) {
if (width > MAX_WIDTH) {
height *= MAX_WIDTH / width;
width = MAX_WIDTH;
}
} else {
if (height > MAX_HEIGHT) {
width *= MAX_HEIGHT / height;
height = MAX_HEIGHT;
}
}
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);


var dataurl = canvas.toDataURL("image/png");
document.getElementById('image').src = dataurl;
}
// Load files into file reader
reader.readAsDataURL(file);




// Post the data
/*
var fd = new FormData();
fd.append("name", "some_filename.jpg");
fd.append("image", dataurl);
fd.append("info", "lah_de_dah");
*/
}</script>

几年前,我解决了这个问题,并将我的解决方案作为https://github.com/rossturner/HTML5-ImageUploader上传到GitHub.

RobertC的答案使用了Mozilla黑客博客文章中提出的解决方案,但是我发现当调整到不是2:1(或其倍数)的比例时,图像质量非常差。我开始尝试不同的图像大小调整算法,尽管大多数最终都很慢,或者质量也不是很好。

最后,我想出了一个解决方案,我认为它执行得很快,而且性能也很好-因为从一个画布复制到另一个画布的Mozilla解决方案工作得很快,而且在2:1的比例下不会损失图像质量,给定的目标是X像素宽和是的像素高,我使用这种画布调整大小的方法,直到图像介于X和2X之间。以及是的和2是的。在这一点上,我然后转向算法图像调整大小的最后“步骤”,即向下调整到目标大小。在尝试了几种不同的算法后,我决定使用双线性插值,它取自一个博客,该博客已不再在线,但可通过互联网档案访问,它提供了良好的结果,以下是适用的代码:

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;
}
}
};

这会将图像缩小到config.maxWidth的宽度,同时保持原始纵横比。在开发的时候,除了主要的桌面浏览器(IE9+,Firefox,Chrome)之外,它还可以在iPad/iPhone Safari上运行,所以我希望它仍然可以兼容HTML5。请注意,canvas.toDataURL()调用接受MIME类型和图像质量,这将允许您控制质量和输出文件格式(如果您愿意,可能与输入不同)。

唯一没有涵盖的一点是维护方向信息,在不知道此元数据的情况下,图像会按原样调整大小并保存,丢失图像中用于方向的任何元数据,这意味着在平板设备上“上下颠倒”拍摄的图像会按此方式呈现,尽管它们会在设备的相机取景器中翻转。如果这是一个问题,这篇博文有一个关于如何实现这一点的很好的指南和代码示例,我相信可以集成到上面的代码中。

适用于我的贾斯汀的回答的修改:

  1. 添加了IMG.onload
  2. 用一个真实的例子扩展POST请求

function handleFiles()
{
var dataurl = null;
var filesToUpload = document.getElementById('photo').files;
var file = filesToUpload[0];


// Create an image
var img = document.createElement("img");
// Create a file reader
var reader = new FileReader();
// Set the image once loaded into file reader
reader.onload = function(e)
{
img.src = e.target.result;


img.onload = function () {
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);


var MAX_WIDTH = 800;
var MAX_HEIGHT = 600;
var width = img.width;
var height = img.height;


if (width > height) {
if (width > MAX_WIDTH) {
height *= MAX_WIDTH / width;
width = MAX_WIDTH;
}
} else {
if (height > MAX_HEIGHT) {
width *= MAX_HEIGHT / height;
height = MAX_HEIGHT;
}
}
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);


dataurl = canvas.toDataURL("image/jpeg");


// Post the data
var fd = new FormData();
fd.append("name", "some_filename.jpg");
fd.append("image", dataurl);
fd.append("info", "lah_de_dah");
$.ajax({
url: '/ajax_photo',
data: fd,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(data){
$('#form_photo')[0].reset();
location.reload();
}
});
} // img.onload
}
// Load files into file reader
reader.readAsDataURL(file);
}
fd.append("image", dataurl);

这是行不通的。在PHP端,你不能用这个保存文件。

改为使用此代码:

var blobBin = atob(dataurl.split(',')[1]);
var array = [];
for(var i = 0; i < blobBin.length; i++) {
array.push(blobBin.charCodeAt(i));
}
var file = new Blob([new Uint8Array(array)], {type: 'image/png', name: "avatar.png"});


fd.append("image", file); // blob file

接受的答案效果很好,但调整大小逻辑忽略了图像仅在一个轴上大于最大值的情况(例如,高度>最大高度,但宽度<=最大宽度)。

我认为下面的代码以一种更直接、更实用的方式处理所有情况(如果使用纯JavaScript,则忽略TypeScript类型注释):

private scaleDownSize(width: number, height: number, maxWidth: number, maxHeight: number): {width: number, height: number} {
if (width <= maxWidth && height <= maxHeight)
return { width, height };
else if (width / maxWidth > height / maxHeight)
return { width: maxWidth, height: height * maxWidth / width};
else
return { width: width * maxHeight / height, height: maxHeight };
}

在画布元素中调整图像大小通常是个坏主意,因为它使用最便宜的框插值。所得到的图像在质量上显著降低。我建议使用http://nodeca.github.io/pica/demo/,它可以执行Lanczos转换。上面的演示页面显示了Canvas和Lanczos方法之间的差异。

它还使用Web Workers来并行调整图像大小。还有WebGL实现。

有一些在线图像缩放工具使用PICA来完成这项工作,如https://myimageresizer.com

如果您想要使用简单易用的上传管理器,并在上传前调整大小,您可以使用Dropzone.JS

它具有内置的调整大小功能,但如果需要,您可以提供自己的功能。

打字稿

async resizeImg(file: Blob): Promise<Blob> {
let img = document.createElement("img");
img.src = await new Promise<any>(resolve => {
let reader = new FileReader();
reader.onload = (e: any) => resolve(e.target.result);
reader.readAsDataURL(file);
});
await new Promise(resolve => img.onload = resolve)
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
let MAX_WIDTH = 1000;
let MAX_HEIGHT = 1000;
let width = img.naturalWidth;
let height = img.naturalHeight;
if (width > height) {
if (width > MAX_WIDTH) {
height *= MAX_WIDTH / width;
width = MAX_WIDTH;
}
} else {
if (height > MAX_HEIGHT) {
width *= MAX_HEIGHT / height;
height = MAX_HEIGHT;
}
}
canvas.width = width;
canvas.height = height;
ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
let result = await new Promise<Blob>(resolve => { canvas.toBlob(resolve, 'image/jpeg', 0.95); });
return result;
}