前端png图片压缩后背景变黑?音视频如何截取第一帧作为封面?疑难杂症攻克篇----2022年度技术总结

2,447 阅读11分钟

前言

  本文是对前端图片压缩、音视频疑难杂症的汇总,并且深入分析病症,本文将带你深入分析其原理、思考分析其问题、实践得出其解决方案。(我觉得面对问题,最重要的是思考分析的过程,以过程为导向,那么结果必然是水到渠成,所以我的文章写出来我遇到问题以及解决问题的过程,希望给卿带去的不仅仅是一份绝决方案,更是一种解决问题的能力。)

授人以鱼不如授人以渔

  之前我写过很多前端上传图片、音视频的一些解决方案,这些方案的确能够解决百分之99的“正常的业务场景需求”,而那剩下的百分之一,恰巧就在我的评论区了,可恶.jpg image.png 前端音视频、文件、图片上传解决方案(追求极致,手把手教给你)

  每一位掘友的评论我都会认真去看,每次看到道友的称赞,我都心花怒放,能开心一整天。

能得到每一位读者的认可都是对我最大的鼓励,感谢每一位读者。

image.png

  每次看到道友提出的问题,我都会第一时间响应,奈何有很多疑难杂症是亘古长留的,随便一查基本都没有什么好的解决方案,这些问题我也无法解决,但是这些问题也一直在我的心上,我也一直在需求答案的路途之上。当遇上挑战时,我们都会想逃避,试图忘记。毕竟人人都想“躺平”,但是我只要想起来就睡不着觉😭😭😭。属实难受,看来必须要解决这些“疑难杂症”。

当遇到无法解决的问题时,唯有深究其根本,剖析其核心,思考变通之道。

image.png

问题一:png图片透明区域为什么会被填充成黑色呢?

由于实在找不到什么好的解答,只能靠自己,这个问题我溯源到了计算机基础的图像知识,在计算机的世界,所有的数据都只是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,JavaMATLAB接口,并支持Windows,Linux,Android和Mac OS,OpenCV主要倾向于实时视觉应用,并在可用时利用MMXSSE指令, 如今也提供对于C#、Ch、Ruby,GO的支持。

alpha通道

阿尔法通道α Channel或Alpha Channel)是指一张图片的透明和半透明度。例如:一个使用每个像素16比特存储的位图,对于图形中的每一个像素而言,可能以5个比特表示红色,5个比特表示绿色,5个比特表示蓝色,最后一个比特是阿尔法。在这种情况下,它要么表示透明要么不是,因为阿尔法比特只有0或1两种不同表示的可能性。又如一个使用32个比特存储的位图,每8个比特表示红绿蓝,和阿尔法通道。在这种情况下,就不光可以表示透明还是不透明,阿尔法通道还可以表示256级的半透明度,因为阿尔法通道有8个比特可以有256种不同的数据表示可能性。

思考分析

在用canvastoDataURL处理png时,发现透明区域被填充成黑色。
为什么canvaspng的透明区域转成黑色呢?
简单来说就是,在image/png格式的图片转换成image/jpeg格式的图片过程中,canvas转换之前移除了alpha通道,所以透明区域被填充成了黑色。
但是,我们希望的是,canvas可以将png的透明区域填充成白色。
那么怎么将canvas中的透明区域填充成白色呢?

思考解决方案

找到问题后,咱们先不讲可行性。尽可能想出多的解决方案:

  • 猜想1、将文件类型设置成image/png

image.png

  • 猜想2、既然透明图片会出现黑底,那么我们就压缩前通过canvas把图片底色变成白色?
  • 猜想3、选取第三方库处理

实践检验真理

猜想一:将文件类型设置成image/png

当我们把type设置为image/png,图片并没有进行压缩,前文前端图片最优化压缩方案中,咱们得出图片的最优化压缩放大其实就两种, 第一种是修改质量实现压缩

第二种是修改尺寸实现压缩

image.png

翻阅资料发现这个quality参数是指定清晰度的,只支持image/jpeg 或 image/webp 格式,不支持png,png设置了没用

在指定图片格式为 image/jpeg 或 image/webp 的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。

image.png

所以说这个方案想要压缩,只能是在接受尺寸大小改变的情况下可行。 总结一下就是猜想一的确能解决问题,但改变了图片尺寸

猜想二:压缩前通过canvas把图片底色变成白色

这个猜想其实就是在canvas绘制前填充白色背景: 也就两行代码

context.fillStyle = '#fff'
context.fillRect(0, 0, img.width, img.height)

这个的确解决了咱们的png图片压缩后背景色变黑的问题,但是同时存在一点瑕疵,(它改变了图片类型,大家有没有发现这个点。)

总结一下就是猜想二的确能解决问题,但改变了图片类型Vue3+TS写个图片压缩的公共方法的基础上增加两行代码即可实现此猜想,完整代码请见文中。 image.png

/**
 * 图片压缩方法
 * @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.png

猜想三:选取第三方库处理

借助第三方工具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()">
  1. 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);
}
  1. 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);
  })
}

image-conversion文档地址

image.png

得出结论

结论1:在接受尺寸大小改变的情况下,可以设置图片类型type设置为image/png

结论2:在接受文件类型改变的前提下,可以在canvas绘制前填背景颜色(白色或者其他色均可实现)

结论3:在项目允许第三方依赖前提下,使用第三方库image-conversion实现图片压缩

音视频如何截取第一帧作为封面?

思路很简单:播放视频,截取视频封面
有没有更好的方法后续将继续深入探索。

这个问题的解决方案很容易想到

  • 页面隐藏一个video标签

  • 用户选择视频后,借助 window.URL.createObjectURL(file)创建一个本地视频链接给页面隐藏的video标签

  • 让它播放,借助videoonloadedmetadataontimeupdate方法创建cavas画布截屏

  • 截取完整后,删除视频链接window.URL.revokeObjectURL(videoUrl)释放内存

完全不够看?往期精彩回顾

image.png

学会这些鲜有人知的coding技巧,从此早早下班liao-JavaScript实战技巧篇

image.png 前端图片最优化压缩方案

image.png Vue实战技巧Element Table二次封装

写在最后

我是凉城a,一个前端,热爱技术也热爱生活。

与你相逢,我很开心。

如果你想了解更多,请点这里,期待你的小⭐⭐

  • 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
  • 本文首发于掘金,未经许可禁止转载💌

「回顾2022,展望2023,我正在参与2022年终总结征文大赛活动