如何在浏览器中通过Javascript压缩图像?
TL; DR;
有没有办法在上传之前直接在浏览器端压缩图片(主要是jpeg,png和gif)? 我很确定JavaScript可以做到这一点,但我找不到一个方法来实现它。
以下是我想要实现的完整场景:
- 用户去我的网站,并通过
input type="file"
元素select一个图像, - 这个图像通过JavaScript检索,我们做一些validation,如正确的文件格式,最大的文件大小等,
- 如果一切正常,则在页面上显示图像的预览,
- 用户可以做一些基本的操作,比如将图像旋转90°/ -90°,按照预先设定的比例裁剪等等,或者用户可以上传其他图像并返回到步骤1,
- 当用户满意时,编辑的图像然后在本地被压缩和“保存”(不保存到文件,而是保存在浏览器存储器/页面中)
- 用户用姓名,年龄等数据填写表格,
- 用户点击“完成”button,然后将包含数据+压缩图像的表单发送到服务器(不含AJAX),
到最后一步的全过程应该在客户端完成,并且应该在最新的Chrome和Firefox,Safari 5+和IE 8+上兼容。 如果可能的话,只能使用JavaScript(但我敢肯定,这是不可能的)。
我现在还没有编写任何代码,但我已经考虑过了。 通过File API可以在本地读取文件 ,可以使用Canvas元素进行图像预览和编辑,但是我找不到图像压缩部分的方法 。
根据html5please.com和caniuse.com ,支持这些浏览器是非常困难的(感谢IE),但可以使用像FlashCanvas和FileReader这样的polyfill来完成。
其实,目标是减less文件大小,所以我把图像压缩视为一个解决scheme。 但是,我知道上传的图像将在我的网站上显示,每次都在同一个地方,我知道这个显示区域的尺寸(例如200×400)。 所以,我可以调整图像大小来适应这些尺寸,从而减小文件大小。 我不知道这种技术的压缩比率是多less。
你怎么看 ? 你有什么build议可以告诉我吗? 你知道任何方式来压缩JavaScript的图像浏览器端? 感谢您的回复。
简而言之:
- 使用.readAsArrayBuffer使用HTML5 FileReader API读取文件
- 使用文件数据创builde Blob,并使用window.URL.createObjectURL(blob)获取其URL。
- 创build新的图像元素,并将其src设置为文件blob url
- 将图像发送到canvas。 canvas大小设置为所需的输出大小
- 通过canvas.toDataURL(“image / jpeg”,0.7)从canvas获取缩小的数据(设置自己的输出格式和质量)
- 将新的隐藏input附加到原始表单,并基本上以普通文本forms传输dataURI图像
- 在后端读取dataURI,从Base64解码并保存
来源: 代码 。
@PsychoWoods'的答案是好的。 我想提供我自己的解决scheme。 这个Javascript函数获取图像数据的URL和宽度,将其缩放到新的宽度,并返回一个新的数据URL。
// Take an image URL, downscale it to the given width, and return a new image URL. function downscaleImage(dataUrl, newWidth, imageType, imageArguments) { "use strict"; var image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl; // Provide default values imageType = imageType || "image/jpeg"; imageArguments = imageArguments || 0.7; // Create a temporary image so that we can compute the height of the downscaled image. image = new Image(); image.src = dataUrl; oldWidth = image.width; oldHeight = image.height; newHeight = Math.floor(oldHeight / oldWidth * newWidth) // Create a temporary canvas to draw the downscaled image on. canvas = document.createElement("canvas"); canvas.width = newWidth; canvas.height = newHeight; // Draw the downscaled image on the canvas and return the new data URL. ctx = canvas.getContext("2d"); ctx.drawImage(image, 0, 0, newWidth, newHeight); newDataUrl = canvas.toDataURL(imageType, imageArguments); return newDataUrl; }
这个代码可以在任何有数据URL的地方使用,并且需要一个缩小图像的数据URL。
我看到其他答案中缺less两件事情:
-
canvas.toBlob
(当可用时)比canvas.toDataURL
更canvas.toDataURL
,也是asynchronous的。 - 文件 – >图像 – >canvas – >文件转换丢失EXIF数据; 特别是现代手机/平板电脑通常设置的关于图像旋转的数据。
以下脚本处理这两点:
// From https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob, needed for Safari: if (!HTMLCanvasElement.prototype.toBlob) { Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { value: function(callback, type, quality) { var binStr = atob(this.toDataURL(type, quality).split(',')[1]), len = binStr.length, arr = new Uint8Array(len); for (var i = 0; i < len; i++) { arr[i] = binStr.charCodeAt(i); } callback(new Blob([arr], {type: type || 'image/png'})); } }); } window.URL = window.URL || window.webkitURL; // Modified from https://stackoverflow.com/a/32490603, cc by-sa 3.0 // -2 = not jpeg, -1 = no data, 1..8 = orientations function getExifOrientation(file, callback) { // Suggestion from http://code.flickr.net/2012/06/01/parsing-exif-client-side-using-javascript-2/: if (file.slice) { file = file.slice(0, 131072); } else if (file.webkitSlice) { file = file.webkitSlice(0, 131072); } var reader = new FileReader(); reader.onload = function(e) { var view = new DataView(e.target.result); if (view.getUint16(0, false) != 0xFFD8) { callback(-2); return; } var length = view.byteLength, offset = 2; while (offset < length) { var marker = view.getUint16(offset, false); offset += 2; if (marker == 0xFFE1) { if (view.getUint32(offset += 2, false) != 0x45786966) { callback(-1); return; } var little = view.getUint16(offset += 6, false) == 0x4949; offset += view.getUint32(offset + 4, little); var tags = view.getUint16(offset, little); offset += 2; for (var i = 0; i < tags; i++) if (view.getUint16(offset + (i * 12), little) == 0x0112) { callback(view.getUint16(offset + (i * 12) + 8, little)); return; } } else if ((marker & 0xFF00) != 0xFF00) break; else offset += view.getUint16(offset, false); } callback(-1); }; reader.readAsArrayBuffer(file); } // Derived from https://stackoverflow.com/a/40867559, cc by-sa function imgToCanvasWithOrientation(img, rawWidth, rawHeight, orientation) { var canvas = document.createElement('canvas'); if (orientation > 4) { canvas.width = rawHeight; canvas.height = rawWidth; } else { canvas.width = rawWidth; canvas.height = rawHeight; } if (orientation > 1) { console.log("EXIF orientation = " + orientation + ", rotating picture"); } var ctx = canvas.getContext('2d'); switch (orientation) { case 2: ctx.transform(-1, 0, 0, 1, rawWidth, 0); break; case 3: ctx.transform(-1, 0, 0, -1, rawWidth, rawHeight); break; case 4: ctx.transform(1, 0, 0, -1, 0, rawHeight); break; case 5: ctx.transform(0, 1, 1, 0, 0, 0); break; case 6: ctx.transform(0, 1, -1, 0, rawHeight, 0); break; case 7: ctx.transform(0, -1, -1, 0, rawHeight, rawWidth); break; case 8: ctx.transform(0, -1, 1, 0, 0, rawWidth); break; } ctx.drawImage(img, 0, 0, rawWidth, rawHeight); return canvas; } function reduceFileSize(file, acceptFileSize, maxWidth, maxHeight, quality, callback) { if (file.size <= acceptFileSize) { callback(file); return; } var img = new Image(); img.onerror = function() { URL.revokeObjectURL(this.src); callback(file); }; img.onload = function() { URL.revokeObjectURL(this.src); getExifOrientation(file, function(orientation) { var w = img.width, h = img.height; var scale = (orientation > 4 ? Math.min(maxHeight / w, maxWidth / h, 1) : Math.min(maxWidth / w, maxHeight / h, 1)); h = Math.round(h * scale); w = Math.round(w * scale); var canvas = imgToCanvasWithOrientation(img, w, h, orientation); canvas.toBlob(function(blob) { console.log("Resized image to " + w + "x" + h + ", " + (blob.size >> 10) + "kB"); callback(blob); }, 'image/jpeg', quality); }); }; img.src = URL.createObjectURL(file); }
用法示例:
inputfile.onchange = function() { // If file size > 500kB, resize such that width <= 1000, quality = 0.9 reduceFileSize(this.files[0], 500*1024, 1000, Infinity, 0.9, blob => { let body = new FormData(); body.set('file', blob, blob.name || "file.jpg"); fetch('/upload-image', {method: 'POST', body}).then(...); }); };
据我所知,你不能使用canvas压缩图像,相反,你可以调整它的大小。 使用canvas.toDataURL不会让你select使用的压缩比。 你可以看看canimage,它完全是你想要的: https : //github.com/nfroidure/CanImage/blob/master/chrome/canimage/content/canimage.js
实际上,调整图像的大小通常足以减小图像的大小,但如果您想进一步研究,则必须使用新引入的方法file.readAsArrayBuffer来获取包含图像数据的缓冲区。
然后,根据图像格式规范( http://en.wikipedia.org/wiki/JPEG或http://en.wikipedia.org/wiki/Portable_Network_Graphics )使用DataView来读取它的内容。
这将是很难处理图像数据压缩,但它是一个更糟糕的尝试。 另一方面,您可以尝试删除PNG标头或JPEG Exif数据,以使图像更小,这样做应该更容易。
您将不得不在另一个缓冲区上创build另一个DataWiew并将其填充过滤的图像内容。 然后,您只需要使用window.btoa将图像内容编码为DataURI。
让我知道,如果你实现类似的东西,将通过代码感兴趣。
对于JPG图像压缩,你可以使用最好的压缩技术称为JIC(Javascript图像压缩)这一定会帮助你 – > https://github.com/brunobar79/JIC