本文是对前端图片压缩、音视频疑难杂症的汇总,并且深入分析病症,本文将带你深入分析其原理、思考分析其问题、实践得出其解决方案。(我觉得面对问题,最重要的是思考分析的过程,以过程为导向,那么结果必然是水到渠成,所以我的文章写出来我遇到问题以及解决问题的过程,希望给卿带去的不仅仅是一份绝决方案,更是一种解决问题的能力。)
授人以鱼不如授人以渔
之前我写过很多前端上传图片、音视频的一些解决方案,这些方案的确能够解决百分之99
的“正常的业务场景需求”,而那剩下的百分之一
,恰巧就在我的评论区了,可恶.jpg
前端音视频、文件、图片上传解决方案(追求极致,手把手教给你)
每一位掘友的评论我都会认真去看,每次看到道友的称赞,我都心花怒放,能开心一整天。
能得到每一位读者的认可都是对我最大的鼓励,感谢每一位读者。
每次看到道友提出的问题,我都会第一时间响应,奈何有很多疑难杂症是亘古长留的,随便一查基本都没有什么好的解决方案,这些问题我也无法解决,但是这些问题也一直在我的心上,我也一直在需求答案的路途之上。当遇上挑战时,我们都会想逃避,试图忘记。毕竟人人都想“躺平”,但是我只要想起来就睡不着觉😭😭😭。属实难受,看来必须要解决这些“疑难杂症”。
当遇到无法解决的问题时,唯有深究其根本,剖析其核心,思考变通之道。
由于实在找不到什么好的解答,只能靠自己,这个问题我溯源到了计算机基础的图像知识,在计算机的世界,所有的数据都只是0或1。电脑中只有两个是真正的运算硬件,一个是CPU
,另外一个就是GPU
(图像处理芯片,显卡的核心)。所以说图像能被我们看见,是因为计算机的显卡(GPU
)。
我们所看到的的图像是如何来的?
简单来说,就是由CPU
将计算好显示内容提交到 GPU
,当然也存在CPU
直接下发命令让 GPU 处理计算显示内容(硬件加速),显卡随即将数字模拟信号(显示内容)转换成图像数据信号,又由信号线连接显示器,显示器接到相关信号后,由视放电路通过显象管电子枪射到显象管屏幕上,这就是我们所看到的图像!
计算机图像是什么?
关于计算机图像,可以分为两类:位图(Bitmap
)和矢量图(Metafile
)。
位图由许多的矩形块组成,每个矩形代表一个点,点的个数等于位图的横向矩形块的个数乘上纵向矩形块的个数,每一个点则被称为像素点,而且每个像素点都有确定的颜色,因此形成了一幅完整的图像。通常使用的图像大部分是位图,如相机拍摄的照片,因为位图可以表示图像的细节,能够较好的还原现实场景。位图的缺点是体积比较大,因此产生了很多压缩图像格式来存储位图图像,目前应用最广的是JPEG
格式,另外还有GIF、PNG
等。而且位图在放大时,会出现“锯齿”现象,就是所谓的失真,这也由位图的本质特点决定。所以在现实中,还需要使用另外一种图像格式:矢量图。
矢量图在一些商标设计上使用比较多,矢量图同位图不同,矢量图是利用数学公式通过线段绘制出来的,所以不管如何放大都不会出现失真现象,但是矢量图不能描述非常复杂的图像。所以各种图形图案、CAD
软件等等都是使用矢量格式来保存文件。
关于图片的基础知识储备
跟PE文件有32位和64位一样,位图也是要分位数的,分类依据主要是像素的位数。
位图的每个像素采用不同的位数(即BMP的图像深度),就可以表示出不同的颜色,不同位图的颜色数量计算如下:
- 4位图像:2^4=16
- 8位图像:2^8=256
- 16位图像:2^16=65536
- 24位图像:2^24=16777216
- n位图说明n个二进制位是一个像素,这一个像素中再分配给透明度和
RGB
三原色各一个数值,每一个数值代表该颜色的亮度,因为没有亮度分量,亮度直接可以从颜色分量中得到,每一颜色分量值的范围都是0~255,某一颜色分量的值越大,就表示这一分量的亮度越高,所以可以理解为一个像素由三个平面叠加【一个平面(n/4位二进制数)代表RGB
中的一个颜色或一个元素】,无数个这样的像素叠加形成一个BMP图像。
对于现在的计算机,一般使用32位来表示颜色,32位平分给四个分量,也就是每个分量8位。(红蓝绿每种颜色可以分8种,另一个分量是透明度)这三种颜色组合起来就有256 * 256 * 256 = 16777216种颜色,基本可以表示大自然的任意色彩。
OpenCV
OpenCV
是一个基于Apache2.0
许可(开源)发行的跨平台计算机视觉和机器学习软件库,可以运行在Linux、Windows、Android和Mac OS
操作系统上。 它轻量级而且高效——由一系列C
函数和少量C++
类构成,同时提供了Python、Ruby、MATLAB
等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。
OpenCV用C++语言编写,它具有C ++,Python,Java
和MATLAB
接口,并支持Windows,Linux,Android和Mac OS,OpenCV
主要倾向于实时视觉应用,并在可用时利用MMX
和SSE
指令, 如今也提供对于C#、Ch、Ruby,GO
的支持。
alpha通道
阿尔法通道(α Channel或Alpha Channel
)是指一张图片的透明和半透明度。例如:一个使用每个像素16比特存储的位图,对于图形中的每一个像素而言,可能以5个比特表示红色,5个比特表示绿色,5个比特表示蓝色,最后一个比特是阿尔法。在这种情况下,它要么表示透明要么不是,因为阿尔法比特只有0或1两种不同表示的可能性。又如一个使用32个比特存储的位图,每8个比特表示红绿蓝,和阿尔法通道。在这种情况下,就不光可以表示透明还是不透明,阿尔法通道还可以表示256级的半透明度,因为阿尔法通道有8个比特可以有256种不同的数据表示可能性。
思考分析
在用canvas
的toDataURL
处理png
时,发现透明区域被填充成黑色。
为什么canvas
会png
的透明区域转成黑色呢?
简单来说就是,在image/png
格式的图片转换成image/jpeg
格式的图片过程中,canvas
转换之前移除了alpha
通道,所以透明区域被填充成了黑色。
但是,我们希望的是,canvas
可以将png的透明区域填充成白色。
那么怎么将canvas
中的透明区域填充成白色呢?
思考解决方案
找到问题后,咱们先不讲可行性。尽可能想出多的解决方案:
- 猜想1、将文件类型设置成
image/png
?
- 猜想2、既然透明图片会出现黑底,那么我们就压缩前通过canvas把图片底色变成白色?
- 猜想3、选取第三方库处理
实践检验真理
猜想一:将文件类型设置成image/png
当我们把type设置为image/png
,图片并没有进行压缩,前文前端图片最优化压缩方案中,咱们得出图片的最优化压缩放大其实就两种,
第一种是修改质量实现压缩
第二种是修改尺寸实现压缩
翻阅资料发现这个quality参数是指定清晰度的,只支持image/jpeg
或 image/webp
格式,不支持png,png设置了没用
在指定图片格式为 image/jpeg
或 image/webp
的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92
。其他参数会被忽略。
所以说这个方案想要压缩,只能是在接受尺寸大小改变的情况下可行。 总结一下就是猜想一的确能解决问题,但改变了图片尺寸
猜想二:压缩前通过canvas
把图片底色变成白色
这个猜想其实就是在canvas
绘制前填充白色背景:
也就两行代码
context.fillStyle = '#fff'
context.fillRect(0, 0, img.width, img.height)
这个的确解决了咱们的png图片压缩后背景色变黑的问题,但是同时存在一点瑕疵,(它改变了图片类型,大家有没有发现这个点。)
总结一下就是猜想二的确能解决问题,但改变了图片类型 在 Vue3+TS写个图片压缩的公共方法的基础上增加两行代码即可实现此猜想,完整代码请见文中。
/**
* 图片压缩方法
* @param {Object} file 图片文件
* @param {String} type 想压缩成的文件类型
* @param {Nubmber} quality 压缩质量参数
* @returns 压缩后的新图片
*/
export const compressionFile = async(files, type = 'image/jpeg', quality = 0.5) => {
const file = files[0]?.originalFileObj
const fileName = file.name
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d') as CanvasRenderingContext2D
const base64 = await fileToDataURL(file)
const img = await dataURLToImage(base64)
canvas.width = img.width
canvas.height = img.height
context.clearRect(0, 0, img.width, img.height)
// 在canvas绘制前填充白色背景
context.fillStyle = '#fff'
context.fillRect(0, 0, img.width, img.height)
context.drawImage(img, 0, 0, img.width, img.height)
const blob = (await canvastoFile(canvas, type, quality)) as Blob // quality:0.5可根据实际情况计算
const f = await new File([blob], fileName, {
type: type
})
const re = [{
originalFileObj: f,
path: file.path,
size: f.size,
type: file.type
}]
return re
}
猜想三:选取第三方库处理
借助第三方工具image-conversion
图片压缩
1.安装
npm i image-conversion --save
2.引入,可以在main.js中全局引入,也可以在组件中引入。 我是在组件中引入的
import * as imageConversion from 'image-conversion'
3.使用 in browser:
<script src="https://cdn.jsdelivr.net/gh/WangYuLue/image-conversion/build/conversion.js"></script>
in CommonJS:
const imageConversion = require("image-conversion")
in ES6:
import * as imageConversion from 'image-conversion';
or
import {compress, compressAccurately} from 'image-conversion';
Use examples
<input id="demo" type="file" onchange="view()">
- Compress image to 200kb:
function view(){
const file = document.getElementById('demo').files[0];
console.log(file);
imageConversion.compressAccurately(file,200).then(res=>{
//The res in the promise is a compressed Blob type (which can be treated as a File type) file;
console.log(res);
})
}
// or use an async function
async function view() {
const file = document.getElementById('demo').files[0];
console.log(file);
const res = await imageConversion.compressAccurately(file,200)
console.log(res);
}
- Compress images at a quality of 0.9
function view(){
const file = document.getElementById('demo').files[0];
console.log(file);
imageConversion.compress(file,0.9).then(res=>{
console.log(res);
})
}
结论1:在接受尺寸大小改变的情况下,可以设置图片类型type设置为image/png
结论2:在接受文件类型改变的前提下,可以在canvas
绘制前填背景颜色(白色或者其他色均可实现)
结论3:在项目允许第三方依赖前提下,使用第三方库image-conversion实现图片压缩
思路很简单:播放视频,截取视频封面
有没有更好的方法后续将继续深入探索。
这个问题的解决方案很容易想到
-
页面隐藏一个
video
标签 -
用户选择视频后,借助
window.URL.createObjectURL(file)
创建一个本地视频链接给页面隐藏的video标签
-
让它播放,借助
video
的onloadedmetadata
、ontimeupdate
方法创建cavas
画布截屏 -
截取完整后,删除视频链接
window.URL.revokeObjectURL(videoUrl)
释放内存
学会这些鲜有人知的coding技巧,从此早早下班liao-JavaScript实战技巧篇
我是凉城a,一个前端,热爱技术也热爱生活。
与你相逢,我很开心。
- 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
- 本文首发于掘金,未经许可禁止转载💌