图片为blob形式能做什么?

4,164 阅读6分钟
  • 感悟源头

前几天读到掘金大神一篇有深度的文章 为什么视频网站的视频链接地址是blob?

让kk对之前在业务上使用过的blob对象有了深入的了解。所以有感而发,也想要整理一下在之前的场景下,如何运用blob对象对图片进行转码、上传等操作。

  • 什么是blob

kk查询了 MDN 对blob的定义

Blob 对象表示一个不可变、原始数据的类文件对象。Blob 表示的不一定是JavaScript原生格式的数据。File 接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。

基于此定义的业务思考,kk可联想到 File对象 就是平常在form表单post形式上传图片文件的所对应的的形式。既然File基于blob,那么post方法上传一个blob对象也是可行的。

  • 业务场景

如上图为身份证识别+视频活体检测在H5端实现的一个需求。在用户拍照上传身份证时候,前端要对该图片进行压缩、转码并上传,与此同时保证上传的性能和后端OCR算法的识别精度。

这里顺便想提及下:这个项目在刚开始的时候,上传的性能比较差,原因是上传的图片为原图,容量很大导致上传时间太长,影响了用户体验。

大家也知道,在微信里发送原图也是需要一定时间和资源的,在H5端也不例外。

经过反复试验,iOS的手机(kk试验的是iPhone 7 Plus 后置1200万像素),上传的原图容量为3M,换在最新发布的安卓手机上,情况就变得更加恶劣。设计师那台最新发布的荣耀V20,2000万像素,发送一张原图高达8M,这就尴尬了。这势必要在H5端上传之前对图片进行压缩。

要用到blob的地方就在于压缩过后,图片转码并上传的过程。事不宜迟,上代码。

  • 功能实现

功能实现主要分为如下四大块

  • 图片选取
  • 图片压缩
  • blob转码
  • 图片上传
    1. 图片选取

    preview(event) {
      var _this = this;

      const eventPath = event.path || (event.composedPath && event.composedPath());
      // 当选取一张图片时,新版iOS Safari浏览器会将图片存储在composedPath里,或者从composedPath方法获取,而普通浏览器依然会存放于path里
      var domId = eventPath[0].id;
      var cardType = _this.cardTypeJudge(eventPath[0].id);
      
      let files = document.getElementById(eventPath[0].id).files[0];

      if (files.size / 1048576 > 15) {
       
        _this.confirmShow = true;
        _this.confirmReason = '图片容量过大,请重新拍摄';
        document.getElementById(domId).value = null
        return false;
      }
      _this.smImage(files).then(resone => {
  
        //上传图片
        _this.uploadFile(resone, cardType).then(restwo => {
          // console.log('restwo:' + restwo);
          document.getElementById(eventPath[0].id).value = null;
          _this.handleIdCardUpdateSuccess(_this.app.idCardFrontSrc, _this.app.idCardBackSrc, 1, cardType);
          _this.submitBtnStatus();
        });
      });
    },
  • 解析

在用户从系统相册选定图片后,H5需要获取触发事件元素冒泡过程的所有元素,即点击选择的图片地址。

经过kk分析,在Chrome中可以通过event.path获取,Firefox和Safari中发现event并没有path属性,确实需要调用event.composedPath()方法或者从event.composedPath取得。
这样才能保证H5在各移动端浏览器的功能兼容性。

Event.composedPath() 是浏览器一个新的标准,没想到移动端的更新步伐那么快。

如下是kk参考其他作者整理的获取图片元素地址的方法:

element.onClick(event) {
  const ev = window.event || event;
  const path = event.path || (event.composedPath && event.composedPath());
  console.log(path)  //[button#btn, div, body, html, document, Window]
}
    1. 图片压缩

 smImage(files) {
      var _this = this;
      return new Promise((resolve, reject) => {
        // 压缩图片需要的一些元素和对象
        var reader = new FileReader(),
          //创建一个img对象
          img = new Image();

        // 缩放图片需要的canvas
        var canvas = document.getElementById("imgcanvas");;
        var context = canvas.getContext('2d');

        // base64地址图片加载完毕后
        img.onload = function () {
          // 图片原始尺寸
          var originWidth = this.width;
          var originHeight = this.height;
          // 最大尺寸限制,可通过国设置宽高来实现图片压缩程度
          var maxWidth = 1300,
            maxHeight = 1300;
          // 目标尺寸
          var targetWidth = originWidth,
            targetHeight = originHeight;
          // 图片尺寸超过1300x1300的限制
          if (originWidth > maxWidth || originHeight > maxHeight) {
            if (originWidth / originHeight > maxWidth / maxHeight) {
              // 更宽,按照宽度限定尺寸
              targetWidth = maxWidth;
              targetHeight = Math.round(maxWidth * (originHeight / originWidth));
            } else {
              targetHeight = maxHeight;
              targetWidth = Math.round(maxHeight * (originWidth / originHeight));
            }
          }
          // canvas对图片进行缩放
          canvas.width = targetWidth;
          canvas.height = targetHeight;
          // 清除画布
          context.clearRect(0, 0, targetWidth, targetHeight);
          // 图片压缩
          context.drawImage(img, 0, 0, targetWidth, targetHeight);
          /*第一个参数是创建的img对象;第二个参数是左上角坐标,后面两个是画布区域宽高*/
          //压缩后的图片base64 url
          /*canvas.toDataURL(mimeType, qualityArgument),mimeType 默认值是'image/jpeg';
           * qualityArgument表示导出的图片质量,只要导出为jpg和webp格式的时候此参数才有效果,默认值是0.92*/
          var newUrl = canvas.toDataURL('image/jpeg', 0.5); //base64 格式
          
          var blob = _this.convertBase64UrlToBlob(newUrl);
          // console.log(blob);
          // console.log('canvas宽度:' + targetWidth + '   长度:' + targetHeight);
          if (targetWidth < targetHeight) {
            _this.isLongImg = true;
          } else {
            _this.isLongImg = false;
          }
          resolve(blob);
        };

        // 文件base64化,以便获知图片原始尺寸
        img.src = _this.app.getFilePath(files);
      })
    }
  • 解析

图片压缩总体采用canvas重绘的方法,对图片的尺寸和分辨率两方面压缩,其阈值通过canvas的各种API可实现人工调整。

以上所述,重绘大致分为两步:尺寸限定和压缩绘制。

  • 尺寸限定

先创建一个canvas画布,并限制其画布宽高。kk将画布宽maxWidth高maxHeight阈值都限定在了1300px,同时取得图片的原始宽originWidth高originHeight进行比较。

当width和height缩减到同比例下低于宽高限定阈值时,则根据画布宽高drawImage重绘。

  • 压缩绘制

主要取决于canvas.toDataURL() 方法中。当图片重绘至画布后,通过此方法可输出一个Data URI,即默认为png的base64串。

压缩的比例,图片输出的分辨率就取决于这个方法的第二个参数:encoderOptions 。

encoderOptions

调整这个参数就可以导出不同分辨率的base64串,kk在这里调整的是0.5,即50%分辨率的压缩。

    1. blob转码

上面kk也提及过,使用post方法上传文件,会取得一个File对象(基于blob),并作为二进制流(binary)上传到服务端。

现在重绘的图片呈现的形式是base64,则需要一个base64转blob的方法。

 convertBase64UrlToBlob(urlData) {
      var arr = urlData.split(','),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new Blob([u8arr], {
        type: mime
      });
    }

这个方法目前是怎么进行转码的kk还没弄清楚,只是作为Util工具类一直运用在该项目里,后期弄懂后会更新。

    1. 图片上传

    1. 阈值调整

在此功能流程中,可调整的阈值有:

  • 图片尺寸压缩的临界值:maxWidth 和 maxHeight(本质是canvas画布的尺寸)
  • 导出图片的分辨率:本质是canvas.toDataURL()的参数设置

阈值调整后,性能相比于原来提升了50%,同时也保证了文字识别OCR的精度(实际上算法精度对图片分辨率的要求并不需要原图那么高,所以进行适当的压缩并不影响)。

性能提升50%体现在两方面:功能提升和体验提升。

  • 功能提升

原图容量由8M及以上压缩至2M以下,iOS原图更是压缩至1M以下,容量压缩了70%左右。

  • 体验提升

图片上传速度由原来的8.0s左右,提升至现在的2.0s~2.6s(3s以内,也取决于当时的网络状况和服务器的接口响应)。

结语

这是我对这个项目开发经验的整理,以及blob对象运用的一些个人理解。望指正!