图片压缩
# 图片压缩
最近做的一个项目,为了让小程序的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
绘制选择框宽度;dx
Image
的左上角在目标canvas
上X
轴坐标;dy
Image
的左上角在目标canvas
上Y
轴坐标;dWidth
Image
在目标canvas
上绘制的宽度;dHeight
Image
在目标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 |
如果图片尺寸过大,在创建同尺寸画布,再画上图片,就会出现异常情况,即生成的画布没有图片像素,而画布本身默认给的背景色为黑色,这样就导致图片“黑屏”情况。
这里可以通过控制输出图片最大宽高防止生成画布越界,并且用透明色覆盖默认黑色背景解决解决“黑屏”问题: