在一个惠风和畅适合写码的下午,技术群里的一位小伙伴抛出了这样一个问题
有同学了解为什么canvas.toDataURL 在处理图片后,有的图片体积比原图还大吗?
这个问题一出,立刻涌现出了四种意见
A. base64 字符肯定要比原来长,谁base64它都长
B. chrome的处理算法不一样,chromium出来背锅啦
C. toDataURL用的姿势不对,还是要多学习一下
D. 图处理了信息自然多了,体积大了是没办法的事情
(你站哪一队?在评论区留下你的第一反应吧)
大家经常会使用 toDataURL + blob的方式,来把canvas中的图像变成可下载的文件, 一个图片文件经过了怎样的旅程才能到我们面前? 上面小伙伴的问题,到底原因如何?
一张图是怎么来的
带着这些问题就让我们来看看一张图片是怎么来的?
怎么描述一张图
图片是颜色的容器,如果要描述颜色 ,可以用rgb三种颜色,通过rgb三种颜色的混合就可以模拟出所需要的颜色,就像我们写css的color一样,给出rgb。
描述的方法找到了,接下来就来聊聊怎么存储这些数据呢?
方案1: 逐个编码
“一个一个记录就好了,很容易想到的方法。” —— BMP
在以前的windows系统中,可以经常见到这种Bmp图像格式。这种类型可以逐个记录RGB数据,在逻辑编写上非常简单。
方案2: 颜色表
“不要每个像素都存,做映射表,岂不美哉” —— Gif
同样的问题Gif格式给出了不同的方案,作为动图格式,往往多个画面内容类似,与其一个一个进行记录,不如构造一个字典,一张图片里面所用到的颜色终究是可以穷举出来的,那么在字典中完全可以存储下所有用到的颜色,这样可以在对应的像素点中只记录index。
例如下图,假设有一张5个像素的图片,如果记录rgb的话,那么需要记录三个数字 3*5 = 15;可是如果记录index的话只需要记录1个数字 + 3个颜色表就可以了。1 * 5 + 3 = 8;也就是说只要图片中的颜色相同的越多,那么这种方法的效果就会越来越好。
颜色表是一种无损压缩方案,无损压缩就是经过压缩后,信息还可以恢复为原样。上面的压缩表,如果我们把每个像素中存储的index 与颜色表进行匹配,的确可以恢复出来每个像素点的RGB值。
这种方案的效果也是很不错的,在一般情况下压缩比可以达到10:1。
那非一般的情况是什么呢?
假设我们有一段字符,可以认为这就是一张图片,一个字母就是一个像素,字母的颜色就是像素的颜色,现在用颜色表的思路来处理这段字符信息
构建索引表
经过我们的编码
效果不错!非常高效的帮助我们压缩了数据
却说又来了一个字符串:
老样子我们继续构建索引表然后编码:
结果我们发现经过编码后的数据,反而比编码前更多了,还不如逐个编码呢!
图片事小,小钱钱事大,有开过站点的兄弟应该明白,这些图片流量可都是钱的,每多1%的数据,少的都是兜里的小钱钱。
从结果上来看无损压缩在一些特殊情况反而会增加信息的长度,从表象上看,这种当变化很快的信息似乎和结果有些关系
方案3:有损压缩
“不需要的就不要记了,看我斩尽芜杂” —— PNG
有损压缩对应无损压缩,压缩后的信息是不可以恢复原样的。
就像小时候做过的古文翻译一样:
逐字翻译就像无损压缩,还可以恢复成文言文;
意译就像有损压缩,意思没错但是想恢复回文言文就不容易了。
比如朋友邀请出仕,古文与白话文的拒绝辞令:
无损压缩 vs 有损压缩
那么上面辞令的有损压缩“干不了 谢谢”,都丢弃了什么信息呢?比如 详细的拒绝的原因(才疏学浅)、个人感受(不堪从命)。这些不是那么重要的信息,就是有损压缩要“损”的地方。
有损压缩,损的到底是什么
在图片中,很重要一个部分就是:视觉冗余
当然还有同样重要的编码冗余等等,就不展开了
这里的3个像素块一眼能区分出来差异吗?
是有差异的,这三个颜色块的红色通道分量都增加了一些。
可能有的设计师同学可以区分的出来,但我是区分不出来哈哈~
那么,再加大难度,如果放在1080p 的画布上 200w像素中我们能区分出来吗?
所以这些信息有那么重要吗? 似乎也不是那么重要,毕竟我们看起来都一样,用上面的3个颜色的粒子来说,本来颜色表需要记录3个颜色,但是如果我们觉得这三种颜色看着都一样,不如记一个吧,反正看不出来。在图形领域有句话说:“看起来是对的,它就是对的”
接下来回到我们刚才的颜色表问题,有损压缩派会怎么解决这个问题?
原始数据:
经过图形噪声抑制等技术手段,把不重要的信息平滑化后,让数据变化不要那么快:
最后编码:
相对于无损压缩的方案:
解决问题✿✿ヽ(°▽°)ノ✿
RGB vs YUV
Png 8 vs Png32
png图片是上述算法的落实者,在衍生过程中出现了 Png8\24\32 多种格式
后面的数字意思就是这个格式的颜色表有多大:例如8bit-256色
有损压缩的确能解决问题,但是人们在使用过程中发现,png8和32到底还是能肉眼看出来的:
rgb的能力是有极限的,越是压榨颜色的极限,就会越为其所困,除非超越rgb的桎梏.
如果有种颜色表示的方法能让颜色变化很小,而且大部分都一样就好了。
于是人们尝试不用rgb来表示颜色了,人们希望有一种格式在颜色表达上能够更加“平滑”一些。
YUV
后来人们发现一种方案:YUV。这种通过 Y 明度、 U 色度、 V 浓度 三个颜色通道来表示颜色,可以解决上面的问题,为什么呢?
- 能力通用:通过YUV同样可以表示出rgb覆盖的色域效果
在y=0.5 的情况下,通过uv的变化亦可以表示出rgb的色域
- 频率特性
人们发现yuv表示的颜色中,uv分量变化的幅度非常小,非常适合用来做有损压缩。
比如下面的图片,第一张是原图,剩下的图片依次是yuv分量,这里面的y分量的图片似乎和原图非常相似了,看起来就像是原图的灰度图一样了。
而uv的图片的质量似乎就差一些了,一些山的细节都没有了,大部分的颜色似乎是一样的?
大部分一样这是好事呀~ 这就是意思差距很小,也就说如果我们微调一下,肉眼可能会看不出来。我们上面就提到过期望的颜色是怎么样的呢 —— 变化小,大部分一样。
这样的话就非常适合我们来做有损压缩了。
余弦变换与量化
yuv颜色帮助我们解决了信号表示的问题。
还有一个问题是:如何识别出不重要的信息,从而进行丢弃达到不会严重损害图像质量的目的。
期望能够找到对人眼不敏感像素信息,这样即使进行了平滑化等操作,也不会被人所感知。
同种物体的多种表示方法
这里要引入一种新的表示方法,就像同样颜色可以用rgb也可以用yuv 两种不同的表示方法来表示一样,像素的坐标表示也有类似的方法:
笛卡尔坐标vs极坐标
一种是常用的笛卡尔坐标,使用 x,y 两个坐标轴来表示。
一种是极坐标,使用半径与旋转角表示。
所以同样的,在图像领域一张图像也有多种标识方式,通过颜色分量值表示是一种方法,另一种方式就是通过 频域 的方式
二维信号 -> 频率信号
为什么 频域 可以帮我们解决 “识别出不重要的信息” 这个问题呢? 因为我们需要识别出来某些颜色差距很小,差距小了,才能平滑化成一个颜色,这样就能让人发现不了。
而频域非常擅长表示信息之间的变化情况,比如多个像素之间的变化是快还是慢,变化慢就是颜色之间差距小。
(这里是整个文章中最难懂的部分,大家打起精神!我们本次主要说明DCT的目的和作用,具体的实现会在下一篇文章中详细说明,如果大家希望催更务必多点赞评论,你的支持就是最大的鼓励!)
在频域的表示方式中,我们可以通过余弦变换(DCT)的方式来找到,具体是 哪里的像素变化比较慢,可以被我们平滑化,我们用下面的矩阵类比yuv中的u分量,经过DCT(实际是经过DCT+量化)之后,变成了一些频域数据。
原来的u分量 3*3 需要在颜色表中记录9个数,经过DCT之后,由于有多个0的存在,现在只需要在颜色表记录8个数字了。
如果给一个更加“强力”的DCT处理,那么数字0就会越来越多,从而让我们记录的数据越来越少
DCT作用类似于一个函数,输入一个颜色分量的矩阵和处理力度 返回一个处理后的结果:
const dct = (inputMatrix,strength) => outputMatrix
最后,经过DCT“挑选”后的像素,就是适合平滑化的数据,因为有很多0,再用无损压缩就可以达到非常好的效果了。
DCT是非常重要的算法,在我们日常看的视频中也会存在这种算法来进行帧内预测,是视频编码中使用非常广泛的一种。
编码成为文件
一个图像能成为jpeg就要走完我们上述的所有流程,才能变成一张jpeg
一张图是怎么渲染的
如果说编码是压缩过程,渲染就是解压缩过程,大部分是对编码操作逐步进行逆运算。
由于chrome底层是采用OpenGL 中的GLSL进行渲染的,所以图片最后在解码变成RGB了之后,被opengl所渲染,就呈现在我们面前。
回到开始
终于搞明白了一张图片的诞生和渲染,就来回到最开始的问题吧
为什么toDataURL之后图片体积变大了呢?
A. base64 字符肯定要比原来长,谁base64它都长
正确。不仅base64会长,通过base64存下的文件也是变大的
B. chrome的处理算法不一样,chromium出来背锅啦
正确。每个厂家算法都是不同的,比如phototshop支持10级的压缩处理
C. toDataURL用的姿势不对,还是要多学习一下
正确。chrome默认压缩质量是0.92,给小一点可以解决一些问题
D. 图处理了信息自然多了,体积大了是没办法的事情
正确。就像一张全黑的图片,体积一定是很小的
总结
这次主要和大家一起探讨了
- 图片编码中的常用压缩方式:有损、无损的使用场景
- 各种颜色格式的使用场景,解决了什么问题
- 了解相同图片下体积背后有所不同的原因
你对哪部分最感兴趣呢?快来评论告诉笔者吧!
今天的文章调用canvas.toDataURL 之后发生了什么分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/15561.html