图片压缩
# 图片压缩
最近做的一个项目,为了让小程序的banner图片加载的快一点,遂在小程序的后台管理系统上对图片动点手脚
首先介绍一下几种相关的格式转换
# file2DataUrl(file, callback)
这是将file转为dataurl,使用FileReader读取file,将file转为base64字符串,在result可以拿到内容
function file2DataUrl(file, callback) {
var reader = new FileReader();
reader.onload = function () {
callback(reader.result);
};
reader.readAsDataURL(file);
}
2
3
4
5
6
7
Data URL 由四个部分组成:前缀(data:)、指示数据类型的 MIME 类型、如果非文本则为可选的 base64 标记、数据本身:
data:<mediatype>,<data>
比如一张 png 格式图片,转化为 base64 字符串形式:data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAgAElEQVR4XuxdB5g
# file2Image(file, callback)
除了上面的base64可以把图片转成url供img显示之外,还可以直接用URL对象,引用保存在 File 和 Blob 中数据的 URL。使用对象 URL 的好处是可以不必把文件内容读取到 JavaScript 中 而直接使用文件内容
function file2Image(file, callback) {
var image = new Image();
var URL = window.webkitURL || window.URL;
if (URL) {
var url = URL.createObjectURL(file);
image.onload = function() {
callback(image);
URL.revokeObjectURL(url);
};
image.src = url;
} else {
file2DataUrl(file, function(dataUrl) {
image.onload = function() {
callback(image);
}
image.src = dataUrl;
});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
值得注意的是
要创建对象 URL,可以使用 window.URL.createObjectURL() 方法,并传入 File 或 Blob 对象。如果不再需要相应数据,最好释放它占用的内容。但只要有代码在引用对象 URL,内存就不会释放。要手工释放内存,可以把对象 URL 传给 URL.revokeObjectURL()。
# image2Canvas(image)
利用 drawImage 方法将 Image 对象绘画在 Canvas 对象上。
drawImage 有三种语法形式:
void ctx.drawImage(image, dx, dy);
void ctx.drawImage(image, dx, dy, dWidth, dHeight);
void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
image绘制到上下文的元素;sx绘制选择框左上角以Image为基准X轴坐标;sy绘制选择框左上角以Image为基准Y轴坐标;sWidth绘制选择框宽度;sHeight绘制选择框宽度;dxImage的左上角在目标canvas上X轴坐标;dyImage的左上角在目标canvas上Y轴坐标;dWidthImage在目标canvas上绘制的宽度;dHeightImage在目标canvas上绘制的高度;

function image2Canvas(image) {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
return canvas;
}
2
3
4
5
6
7
8
# canvas2DataUrl(canvas, quality, type)
HTMLCanvasElement 对象有 toDataURL(type, encoderOptions) 方法,返回一个包含图片展示的 data URL。同时可以指定输出格式和质量
参数分别为:
type图片格式,默认为image/png。encoderOptions在指定图片格式为image/jpeg或image/webp的情况下,可以从0到1的区间内选择图片的质量。如果超出取值范围,将会使用默认值0.92,其他参数会被忽略。
function canvas2DataUrl(canvas, quality, type) {
return canvas.toDataURL(type || 'image/jpeg', quality || 0.8);
}
2
3
# dataUrl2Image(dataUrl, callback)
图片链接也可以是 base64 字符串,直接赋值给 Image 对象 src 即可。
function dataUrl2Image(dataUrl, callback) {
var image = new Image();
image.onload = function() {
callback(image);
};
image.src = dataUrl;
}
2
3
4
5
6
7
# dataUrl2Blob(dataUrl, type)
将 data URL 字符串转化为 Blob对象。主要思路是:先将 data URL 数据(data) 部分提取出来,用 atob 对经过 base64 编码的字符串进行解码,再转化成 Unicode 编码,存储在Uint8Array(8位无符号整型数组,每个元素是一个字节) 类型数组,最终转化成 Blob 对象。
function dataUrl2Blob(dataUrl, type) {
var data = dataUrl.split(',')[1];
var mimePattern = /^data:(.*?)(;base64)?,/;
var mime = dataUrl.match(mimePattern)[1];
var binStr = atob(data);
var arr = new Uint8Array(len);
for (var i = 0; i < len; i++) {
arr[i] = binStr.charCodeAt(i);
}
return new Blob([arr], {type: type || mime});
}
2
3
4
5
6
7
8
9
10
11
12
# canvas2Blob(canvas, callback, quality, type)
HTMLCanvasElement 有 toBlob(callback, [type\], [encoderOptions])方法创造 Blob 对象,用以展示 canvas 上的图片;这个图片文件可以被缓存或保存到本地,由用户代理端自行决定。第二个参数指定图片格式,如不特别指明,图片的类型默认为 image/png,分辨率为 96dpi。第三个参数用于针对image/jpeg 格式的图片进行输出图片的质量设置。
function canvas2Blob(canvas, callback, quality, type){
canvas.toBlob(function(blob) {
callback(blob);
}, type || 'image/jpeg', quality || 0.8);
}
2
3
4
5
为兼容低版本浏览器,作为 toBlob 的 polyfill 方案,可以用上面 data URL 生成 Blob 方法 dataUrl2Blob 作为HTMLCanvasElement 原型方法。
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function (callback, type, quality) {
let dataUrl = this.toDataURL(type, quality);
callback(dataUrl2Blob(dataUrl));
}
});
}
2
3
4
5
6
7
8
# blob2DataUrl(blob, callback)
将 Blob 对象转化成 data URL 数据,由于 FileReader 的实例 readAsDataURL 方法不仅支持读取文件,还支持读取 Blob 对象数据,这里复用上面 file2DataUrl 方法即可:
function blob2DataUrl(blob, callback) {
file2DataUrl(blob, callback);
}
2
3
# blob2Image(blob, callback)
将 Blob 对象转化成 Image 对象,可通过 URL 对象引用文件,也支持引用 Blob 这样的类文件对象,同样,这里复用上面 file2Image 方法即可:
function blob2Image(blob, callback) {
file2Image(blob, callback);
}
2
3
# upload(url, file, callback)
上传图片(已压缩),可以使用 FormData 传入文件对象,通过 XHR 直接把文件上传到服务器。
function upload(url, file, callback) {
var xhr = new XMLHttpRequest();
var fd = new FormData();
fd.append('file', file);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
// 上传成功
callback && callback(xhr.responseText);
} else {
throw new Error(xhr);
}
}
xhr.open('POST', url, true);
xhr.send(fd);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
也可以使用 FileReader 读取文件内容,转化成二进制上传
function upload(url, file) {
var reader = new FileReader();
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');
reader.onload = function() {
xhr.send(reader.result);
};
reader.readAsBinaryString(file);
}
2
3
4
5
6
7
8
9
10
11
12
现在重头戏
# 流程
我们的流程是 上传的图片 -> Image对象 -> 写入Canvas画布 -> 利用canvas.toDataURL/toBlob 将 canvas 压缩并导出为 base64 或 Blob -> 将 base64 或 Blob 转化为 File
因为这是folk别人的项目进行学习的,这是代码 (opens new window)
# 遇到的问题
# 原尺寸输出不符合要求
为了避免压缩图片变形,一般采用等比缩放,首先要计算出原始图片宽高比 aspectRatio
用户设置的输出照片的高乘以 aspectRatio,得出等比缩放后的宽
若比用户设置宽的小,则用户设置的高为为基准缩放,否则以宽为基准缩放。
通过drawImage进行改造一下
function image2Canvas(image, destWidth, destHeight) {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = destWidth || image.naturalWidth;
canvas.height = destHeight || image.naturalHeight;
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
return canvas;
}
2
3
4
5
6
7
8
# png格式的图片进行同格式压缩体积反增大
原因:
关键的压缩API均未支持png格式
toBlob(callback, [type], [encoderOptions])参数encoderOptions用于针对image/jpeg格式的图片进行输出图片的质量设置;toDataURL(type, encoderOptions参数encoderOptions在指定图片格式为image/jpeg或image/webp的情况下,可以从0到1的区间内选择图片的质量。
解决方法:
我们可以设置一个阈值,如果 png 图片的质量小于这个值,就还是压缩输出 png 格式,这样最差的输出结果不至于质量太大
在此基础上,如果压缩后图片大小 “不减反增”,我们就兜底处理输出源图片给用户。当图片质量大于某个值时,我们压缩成 jpeg 格式
// `png` 格式图片大小超过 `convertSize`, 转化成 `jpeg` 格式
if (file.size > options.convertSize && options.mimeType === 'image/png') {
options.mimeType = 'image/jpeg';
}
// 省略一些代码
// ...
// 用户期待的输出宽高没有大于源图片的宽高情况下,输出文件大小大于源文件,返回源文件
if (result.size > file.size && !(options.width > naturalWidth || options.height > naturalHeight)) {
result = file;
}
2
3
4
5
6
7
8
9
10
# 大尺寸 png 格式图片在一些手机上,压缩后出现“黑屏”现象
| 浏览器 | 最大宽高 | 最大面积 |
|---|---|---|
| Chrome | 32,767 pixels | 268,435,456 pixels(e.g.16,384 x 16,384) |
| Firefox | 32,767 pixels | 472,907,776 pixels(e.g.22,528 x 20,992) |
| IE | 8,192 pixels | N/A |
| IE Mobile | 4,096 pixels | N/A |
如果图片尺寸过大,在创建同尺寸画布,再画上图片,就会出现异常情况,即生成的画布没有图片像素,而画布本身默认给的背景色为黑色,这样就导致图片“黑屏”情况。
这里可以通过控制输出图片最大宽高防止生成画布越界,并且用透明色覆盖默认黑色背景解决解决“黑屏”问题: