图片的本质
以下内容转载自 JPEG 图片存储格式与元数据解析
例如,一张 4px × 4px 的彩色图片,未压缩的的原始图像数据,就是一个 4 × 4 矩形网格,每一个网格代表一个像素
而彩色图片的每一个像素,又是由 红,绿,蓝 三基色构成,如下图右边所示,红绿蓝,对应于 r g b 三个数值,也就是我常说的 RGB 色彩模式。
RGB,我们在计算机视觉领域,又称为颜色通道,彩色图像有三个通道值,每个颜色通道,都是一个 0~255 的整数值,占用一个字节(Byte)的存储空间。1 个像素点需要 3 个字节
因此,我们很容易计算上面这张 4×4 彩色图片占用的存储空间为 4 × 4 × 3 = 48 字节 (Bytes) 。换算成我们熟悉的 KB,就是 48 / 1024 = 0.046875 KB,不到 0.1 KB。
事实上,我们很少见到这么小的图片,甚至在我们的个人电脑和手机上,根本无法正常看到这么小的图片。这里为了方便理解和计算,做了技术上的处理,而不是真实看到的图片大小。
拓展:按照在电脑上常用的分辨率 72 (像素/英寸),即 每 2.54 厘米 容纳 72 个像素,或者说,一个像素占用的屏幕尺寸是 0.35 毫米,那么上面 4 × 4 图片,在屏幕上 1:1 显示,占用屏幕的物理尺寸只有 1.4 × 1.4 毫米。显然,用肉眼是无法看清的。
在理解一张 4 × 4 的彩色图片占用存储空间大小,我们同样的方式计算如下,320 × 320 的彩色图片,这个大小在我们日常生活,也不算一张大图,相当于我们用作微信头像的大小。
我相信我们可以很快得出结果,320 × 320 × 3 = 300 KB ,相当于上面 4 × 4 图片的 6000 多倍。
iPhone 拍的一张图片在未压缩
的情况下,所占用的存储空间大小是 3024 × 4032 × 3 = 35 MB 。而实际,如下图,在我的 Mac 上看到的图片, 只有 6.8 M ,说明我们在使用手机拍摄照片后,在保存在相册之前,相机程序已经自动对我们拍摄的照片照片进行了压缩。
图片的二进制形式
不同于普通文本文件,图片在计算机里存储形式,是二进制文件。
我们可以借助一个命令行工具 hexdump
来查看图片的二进制形式:
输出结果如下图所示:
图中,红线框圈住的部分,是图片数据的字节流编址,可以看作是为了查看方便,添加的行号,红框右边的才是图片的真实存储字节流,并且每行显示 16 个字节。当然不管是“行号”还是图片数据,为了显示的简介性,默认都是用了十六进制
。
- RGB 表示形式: RGB(256,256,256)
- 十六进制表示形式: #ffffff
图片压缩的原理
尺寸压缩
10 × 10 的像素点区域块被用最中间那一个像素点代替
编码压缩
1. 有损压缩
有损压缩是利用了人类对图像或声波中的某些频率成分不敏感
的特性,允许压缩过程中损失一定的信息
;虽然不能完全恢复原始数据,但是所损失的部分对理解原始图像的影响缩小,却换来了大得多的压缩比
本质和尺寸压缩本质上一样,用最中间的一个像素点代替周围的像素点
2. 行程长度编码法(无损压缩)
常用的无损压缩算法,将一扫描行中颜色值相同
的相邻像素用两个字节来表示, 第一个字节是一个计数值, 用于指定像素重复的次数; 第二个字节是具体像素的值。能够比较好地保存图像的质量,但是相对有损压缩
来说这种方法的压缩率比较低
例如:499 500 500 500 501 → 499 500×3 501
3. 熵编码法(无损压缩)
熵编码法是一种进行无损数据压缩的技术,在这个技术中一段文字中的每个字母被一段不同长度的比特(Bit)所代替。与此相对的是LZ77
或者LZ78
等数据压缩方法,在这些方法中原文的一段字母列被其它字母取代。
本质上看就是利用一个算法,把一段字母用一个或单个字母代替(端到端之间可以存一个压缩字符映射表)
例如:499 500 500 500 501 → -1 0 500 0 1
使用 Canvas 压缩图片
以下内容转载自 JS 图片压缩
压缩思路
涉及到 JS 的图片压缩,我的想法是需要用到 Canvas 的绘图能力,通过调整图片的分辨率或者绘图质量来达到图片压缩的效果,实现思路如下:
- 获取上传 Input 中的图片对象 File
- 将图片转换成 base64 格式
- base64 编码的图片通过 Canvas 转换压缩,这里会用到的 Canvas 的 drawImage 以及 toDataURL 这两个 Api,一个调节图片的分辨率的,一个是调节图片压缩质量并且输出的,后续会有详细介绍
- 转换后的图片生成对应的新图片,然后输出
base64 编码指的是把二进制变成字符的过程,base64 解码就是把字符变回二进制的过程示例:
优缺点介绍
不过 Canvas 压缩的方式也有着自己的优缺点:
- 优点:实现简单,参数可以配置化,
自定义
图片的尺寸,指定区域裁剪
等等。 - 缺点:只有 jpeg 、webp 支持原图尺寸下图片质量的调整来达到压缩图片的效果,其他图片格式,仅能通过调节尺寸来实现
代码实现
<template>
<div class="container">
<input type="file" id="input-img" @change="compress" />
<a :download="fileName" :href="compressImg" >普通下载</a>
<button @click="downloadImg">兼容 IE 下载</button>
<div>
<img :src="compressImg" />
</div>
</div>
</template>
<script>
export default {
name: 'compress',
data: function() {
return {
compressImg: null,
fileName: null,
};
},
components: {},
methods: {
compress() {
// 获取文件对象
const fileObj = document.querySelector('#input-img').files[0];
// 获取文件名称,后续下载重命名
this.fileName = `${new Date().getTime()}-${fileObj.name}`;
// 获取文件后缀名
const fileNames = fileObj.name.split('.');
const type = fileNames[fileNames.length-1];
// 压缩图片
this.handleCompressImage(fileObj, type);
},
handleCompressImage(img, type) {
const vm = this;
let reader = new FileReader();
// 读取文件
reader.readAsDataURL(img);
reader.onload = function(e) {
let image = new Image(); //新建一个img标签
image.src = e.target.result;
image.onload = function() {
let canvas = document.createElement('canvas');
let context = canvas.getContext('2d');
// 定义 canvas 大小,也就是压缩后下载的图片大小
let imageWidth = image.width; //压缩后图片的大小
let imageHeight = image.height;
canvas.width = imageWidth;
canvas.height = imageHeight;
// 图片不压缩,全部加载展示
context.drawImage(image, 0, 0);
// 图片按压缩尺寸载入
// let imageWidth = 500; //压缩后图片的大小
// let imageHeight = 200;
// context.drawImage(image, 0, 0, 500, 200);
// 图片去截取指定位置载入
// context.drawImage(image,100, 100, 100, 100, 0, 0, imageWidth, imageHeight);
vm.compressImg = canvas.toDataURL(`image/${type}`);
};
};
},
// base64 图片转 blob 后下载
downloadImg() {
let parts = this.compressImg.split(';base64,');
let contentType = parts[0].split(':')[1];
let raw = window.atob(parts[1]);
let rawLength = raw.length;
let uInt8Array = new Uint8Array(rawLength);
for(let i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
const blob = new Blob([uInt8Array], {type: contentType});
this.compressImg = URL.createObjectURL(blob);
if (window.navigator.msSaveOrOpenBlob) {
// 兼容 ie 的下载方式
window.navigator.msSaveOrOpenBlob(blob, this.fileName);
}else{
const a = document.createElement('a');
a.href = this.compressImg;
a.setAttribute('download', this.fileName);
a.click();
}
},
}
};
</script>
上面的代码是可以直接拿来看效果的,不喜欢用 Vue 的也可以把代码稍微调整一下,下面开始具体分解一下代码的实现思路
1. Input 上传 File 处理
将 File 对象通过 FileReader
的 readAsDataURL
方法转换为URL格式的字符串(base64 编码)
const fileObj = document.querySelector('#input-img').files[0];
let reader = new FileReader();
// 读取文件
reader.readAsDataURL(fileObj);
2. Canvas 处理 File 对象
建立一个 Image
对象,一个 canvas
画布,设定自己想要下载的图片尺寸,调用 drawImage
方法在 canvas 中绘制上传的图片
let image = new Image(); //新建一个img标签
image.src = e.target.result;
let canvas = document.createElement('canvas');
let context = canvas.getContext('2d');
context.drawImage(image, 0, 0);
【drawImage API 解析】
context.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
- img
就是图片对象,可以是页面上获取的 DOM 对象,也可以是虚拟 DOM 中的图片对象。
- dx、dy、dWidth、dHeight
参数必填,用于规定在 Canvas 中绘制图片的大小和起点位置
- sx、sy、swidth、sheight
参数选填,用于裁剪
以下为图片绘制的实例:
context.drawImage(image, 0, 0, 100, 100);
context.drawImage(image, 300, 300, 200, 200);
context.drawImage(image, 0, 100, 150, 150, 300, 0, 150, 150);
Api 中奇怪之处在于,sx、sy、swidth、sheight 为选填参数,但位置在 dx、dy、dWidth、dHeight 之前。
3. Canvas 输出图片
调用 canvas
的 toDataURL
方法可以输出 base64 格式的图片。
canvas.toDataURL(`image/${type}`);
【toDataURL API 解析】
canvas.toDataURL(type, encoderOptions);
- type (可选)
图片格式,默认为 image/png。
- encoderOptions (可选)
在指定图片格式为 image/jpeg 或 image/webp 的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。
4. a 标签的下载
调用 <a>
标签的 download
属性,即可完成图片的下载。
【download API 解析】
// href 下载必填
<a download="filename" href="href"> 下载 </a>
-
filename (选填):规定作为文件名来使用的文本。
-
href:文件的下载地址。