zoukankan      html  css  js  c++  java
  • 图片的压缩上传

    背景

      实际生产中经常遇到这样的场景:为减小服务器压力,上传附件尤其是图片的时候,往往需要限制上传文件的大小。而限制的方案也有两种,一种就是限制用户可上传的文件大小,由用户来选择上传的文件和如果文件过大由用户自行进行压缩裁剪;另一种就是由服务进行图片的压缩和大小控制然后再上传到服务器。这里主要介绍的是第二种方案。

    主要技术

      前边有介绍过证书的生成和下载,其中就有证书的压缩和打包的相关操作,感兴趣的可以看下本人的那篇文章。这里同样是采用的该原理,步骤如下:

    关键步骤

      图片文件-->文件流(base64位编码)-->canvas-->压缩-->生成压缩后的文件-->上传。

      这里的压缩过程,做了相应的优化。优化方案有两种,一种是重复压缩,一种是计算比例压缩。

      而由于压缩比和文件大小并不是正比例关系,所有可以保险起见再乘以一个系数。比如:quality: 1024*0.7/fileObj.size(0.7是保险系数,1024是限制大小1M的意思,可根据个人需要自行调整参数,也可以封装成接口参数统一修改)

      这里还自行封装了一个进度组件,使用的是原生js。

    代码

      代码和相关注释如下:

      1 <!DOCTYPE html>
      2 <html>
      3 <head>
      4     <meta charset="UTF-8">
      5     <title>文件压缩上传</title>
      6     <script type="text/javascript">
      7         /*
      8         三个参数
      9         file:一个是文件(类型是图片格式),
     10         w:一个是文件压缩的后宽度,宽度越小,字节越小
     11         objDivOrCallback:一个是容器或者回调函数
     12         photoCompress()
     13          */
     14         function photoCompress(file,w,objDivOrCallback) {
     15             var ready = new FileReader()
     16             /*开始读取指定的Blob对象或File对象中的内容. 当读取操作完成时,readyState属性的值会成为DONE,如果设置了onloadend事件处理程序,则调用之.同时,result属性中将包含一个data: URL格式的字符串以表示所读取文件的内容.*/
     17             ready.readAsDataURL(file)
     18             ready.onload = function() {
     19                 var re = this.result
     20                 canvasDataURL(re, w, objDivOrCallback)
     21             }
     22         }
     23         function canvasDataURL(path, obj, callback) {
     24             var img = new Image()
     25             img.src = path
     26             img.onload = function(){
     27                 var that = this
     28                 // 默认按比例压缩
     29                 var w = that.width,
     30                     h = that.height,
     31                     scale = w / h
     32                 w = obj.width || w
     33                 h = obj.height || (w / scale)
     34                 var quality = 0.7  // 默认图片质量为0.7
     35                 //生成canvas
     36                 var canvas = document.createElement('canvas')
     37                 var ctx = canvas.getContext('2d')
     38                 // 创建属性节点
     39                 var anw = document.createAttribute("width")
     40                 anw.nodeValue = w
     41                 var anh = document.createAttribute("height")
     42                 anh.nodeValue = h
     43                 canvas.setAttributeNode(anw)
     44                 canvas.setAttributeNode(anh)
     45                 ctx.drawImage(that, 0, 0, w, h)
     46                 // 图像质量
     47                 if(obj.quality && obj.quality <= 1 && obj.quality > 0) {
     48                     quality = obj.quality
     49                 }
     50                 // quality值越小,所绘制出的图像越模糊
     51                 var base64 = canvas.toDataURL('image/jpeg', quality)
     52                 // 这里不能直接quality: 0.2,因为这样就相当于还是在原来的大小的基础上压缩
     53                 var bl = convertBase64UrlToBlob(base64)
     54                 // 如果还大于1M,继续压缩--代码待优化,可以减去重复生成文件和转码的过程
     55                 if (bl.size/1024 > 1025) {
     56                     // 其实也可以在这里直接写一个匹配压缩比直到大小小于1的方法
     57                     photoCompress(bl, {
     58                     quality: 0.5 * obj.quality
     59                   }, callback)
     60                 } else {
     61                     callback(bl)
     62                 }
     63                 // 回调函数返回base64的值--改为返回文件对象
     64                 // callback(base64)
     65             }
     66         }
     67         /**
     68          * 将以base64的图片url数据转换为Blob
     69          * @param urlData
     70          *            用url方式表示的base64图片数据
     71          */
     72         function convertBase64UrlToBlob(urlData) {
     73             var arr = urlData.split(','), mime = arr[0].match(/:(.*?);/)[1],
     74                 bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n)
     75             while(n--) {
     76                 u8arr[n] = bstr.charCodeAt(n)
     77             }
     78             return new Blob([u8arr], {type:mime})
     79         }
     80 
     81 
     82         var xhr
     83         //上传文件方法
     84         function UpladFile() {
     85             var fileObj = document.getElementById("file").files[0] // js 获取文件对象
     86             var url = "http://pxjy.api.test.nercel.cn/file/publicFile/upload" // 接收上传文件的后台地址 
     87 
     88             var form = new FormData() // FormData 对象
     89 
     90             if(fileObj.size/1024 > 1025) { //大于1M,进行压缩上传
     91                 photoCompress(fileObj, {
     92                     // 这里还有一种方案,那就是这里的quality改为计算压缩比(由于压缩比和文件大小并不是正比例关系,所有可以保险起见再乘以一个系数)
     93                     // 压缩比计算的方案:quality: 1024*0.7/fileObj.size--0.7是保险系数--这些参数可以进一步封装
     94                     quality: 0.2
     95                 // }, function(base64Codes){
     96                 // 修改为返回文件对象
     97                 }, function(bl){
     98                     //console.log("压缩后:" + base.length / 1024 + " " + base);
     99                     // var bl = convertBase64UrlToBlob(base64Codes)
    100                     // form.append("file", bl, "file_"+Date.parse(new Date())+".jpg"); // 文件对象
    101                     form.append("multipartFile", bl, "file_"+Date.parse(new Date())+".jpg") // 文件对象
    102                     xhr = new XMLHttpRequest()  // XMLHttpRequest 对象
    103                     xhr.open("post", url, true) //post方式,url为服务器请求地址,true 该参数规定请求是否异步处理。
    104                     xhr.setRequestHeader("enctype", "multipart/form-data") // 设置请求头
    105                     xhr.setRequestHeader("Authorization", "Bearer 8d782bb1-768f-4fa7-80d2-5e2b6d6a6f64") // 设置请求头
    106                     // open后才可以设置头
    107                     xhr.onload = uploadComplete //请求完成
    108                     xhr.onerror =  uploadFailed //请求失败
    109 
    110                     xhr.upload.onprogress = progressFunction//【上传进度调用方法实现】
    111                     xhr.upload.onloadstart = function(){//上传开始执行方法
    112                         ot = new Date().getTime()   //设置上传开始时间
    113                         oloaded = 0//设置上传开始时,以上传的文件大小为0
    114                     };
    115 
    116                     xhr.send(form) //开始上传,发送form数据
    117                 })
    118             } else { //小于等于1M 原图上传
    119                 // form.append("file", fileObj) // 文件对象
    120                 form.append("multipartFile", fileObj) // 文件对象
    121                 xhr = new XMLHttpRequest()  // XMLHttpRequest 对象
    122                 xhr.open("post", url, true) //post方式,url为服务器请求地址,true 该参数规定请求是否异步处理。
    123                 xhr.setRequestHeader("enctype", "multipart/form-data") // 设置请求头
    124                 xhr.setRequestHeader("Authorization", "Bearer 8d782bb1-768f-4fa7-80d2-5e2b6d6a6f64") // 设置请求头
    125                     // open后才可以设置头
    126                 xhr.onload = uploadComplete //请求完成
    127                 xhr.onerror =  uploadFailed //请求失败
    128 
    129                 xhr.upload.onprogress = progressFunction//【上传进度调用方法实现】
    130                 xhr.upload.onloadstart = function() {//上传开始执行方法
    131                     ot = new Date().getTime()   //设置上传开始时间
    132                     oloaded = 0//设置上传开始时,以上传的文件大小为0
    133                 }
    134 
    135                 xhr.send(form) //开始上传,发送form数据
    136             }
    137         }
    138 
    139         //上传成功响应
    140         function uploadComplete(evt) {
    141             //服务断接收完文件返回的结果
    142             var data = JSON.parse(evt.target.responseText)
    143             if(data.code === 200) {
    144                 uploadSuccess()
    145             } else {
    146                 uploadFailed()
    147             }
    148 
    149         }
    150         //上传失败
    151         function uploadFailed(evt) {
    152             alert("上传失败!")
    153         }
    154         //上传成功
    155         function uploadSuccess(evt) {
    156             alert("上传成功!")
    157         }
    158         //取消上传
    159         function cancleUploadFile(){
    160             xhr.abort()
    161         }
    162 
    163         //上传进度实现方法,上传过程中会频繁调用该方法
    164         function progressFunction(evt) {
    165             var progressBar = document.getElementById("progressBar")
    166             var percentageDiv = document.getElementById("percentage")
    167             // event.total是需要传输的总字节,event.loaded是已经传输的字节。如果event.lengthComputable不为真,则event.total等于0
    168             if (evt.lengthComputable) {//
    169                 progressBar.max = evt.total
    170                 progressBar.value = evt.loaded
    171                 percentageDiv.innerHTML = Math.round(evt.loaded / evt.total * 100) + "%"
    172             }
    173             var time = document.getElementById("time")
    174             var nt = new Date().getTime()//获取当前时间
    175             var pertime = (nt-ot)/1000 //计算出上次调用该方法时到现在的时间差,单位为s
    176             ot = new Date().getTime() //重新赋值时间,用于下次计算
    177             var perload = evt.loaded - oloaded //计算该分段上传的文件大小,单位b
    178             oloaded = evt.loaded//重新赋值已上传文件大小,用以下次计算
    179             //上传速度计算
    180             var speed = perload/pertime//单位b/s
    181             var bspeed = speed
    182             var units = 'b/s'//单位名称
    183             if(speed/1024>1) {
    184                 speed = speed/1024
    185                 units = 'k/s'
    186             }
    187             if(speed/1024>1) {
    188                 speed = speed/1024
    189                 units = 'M/s'
    190             }
    191             speed = speed.toFixed(1)
    192             //剩余时间
    193             var resttime = ((evt.total-evt.loaded)/bspeed).toFixed(1)
    194             time.innerHTML = ',速度:'+speed+units+',剩余时间:'+resttime+'s'
    195             if(bspeed==0) time.innerHTML = '上传已取消'
    196         }
    197     </script>
    198 </head>
    199 <body>
    200 <progress id="progressBar" value="0" max="100" style=" 300px;"></progress>
    201 <span id="percentage"></span><span id="time"></span>
    202 <br /><br />
    203 <input type="file" id="file" name="myfile" accept="image/x-png, image/jpg, image/jpeg, image/gif"/>
    204 <input type="button" onclick="UpladFile()" value="上传" />
    205 <input type="button" onclick="cancleUploadFile()" value="取消" />
    206 </body>
    207 </html>

      此处是借鉴网上思路的基础上的个人修改完善后的代码, 并且有待有时间的时候做进一步封装优化和封装成npm组件以及vue组件。

      代码git地址:

      https://github.com/MRlijiawei/components/blob/master/file/%E5%9B%BE%E7%89%87%E5%8E%8B%E7%BC%A9%E4%B8%8A%E4%BC%A0.html

    扩展

      png图片的另一种压缩方案

      png的简介

      什么是png:

        PNG的全称叫便携式网络图型(Portable Network Graphics)是目前最流行的网络传输和展示的图片格式,原因有如下几点:

    • 无损压缩:PNG图片采取了基于LZ77派生算法对文件进行压缩,使得它压缩比率更高,生成的文件体积更小,并且不损失数据。

    • 体积小:它利用特殊的编码方法标记重复出现的数据,使得同样格式的图片,PNG图片文件的体积更小。网络通讯中因受带宽制约,在保证图片清晰、逼真的前提下,优先选择PNG格式的图片。

    • 支持透明效果:PNG支持对原图像定义256个透明层次,使得图像的边缘能与任何背景平滑融合,这种功能是GIF和JPEG没有的。

    当初就是因为png的透明特性才开始喜欢它的。

      png的类型:

    • PNG 8:PNG 8中的8,其实指的是8bits,相当于用2^8(2的8次方)大小来存储一张图片的颜色种类,2^8等于256,也就是说PNG 8能存储256种颜色,一张图片如果颜色种类很少,将它设置成PNG 8得图片类型是非常适合的。

    • PNG 24:PNG 24中的24,相当于3乘以8 等于 24,就是用三个8bits分别去表示 R(红)、G(绿)、B(蓝)。R(0~255),G(0~255),B(0~255),可以表达256乘以256乘以256=16777216种颜色的图片,这样PNG 24就能比PNG 8表示色彩更丰富的图片。但是所占用的空间相对就更大了。

    • PNG 32:PNG 32中的32,相当于PNG 24 加上 8bits的透明颜色通道,就相当于R(红)、G(绿)、B(蓝)、A(透明)。R(0~255),G(0~255),B(0~255),A(0~255)。比PNG 24多了一个A(透明),也就是说PNG 32能表示跟PNG 24一样多的色彩,并且还支持256种透明的颜色,能表示更加丰富的图片颜色类型。

      png图片的数据编码:

      PNG图片的数据结构其实跟http请求的结构很像,都是一个数据头,后面跟着很多的数据块,如下图所示:

    使用16进制编码打开png图片,部分编码示例如下:

    8950 4e47 0d0a 1a0a:这个是PNG图片的头,所有的PNG图片的头都是这一串编码,图片软件通过这串编码判定这个文件是不是PNG格式的图片。

    0000 000d:是iHDR数据块的长度,为13。

    4948 4452:是数据块的type,为IHDR,之后紧跟着是data。

    0000 0292:是图片的宽度。

    0000 024e:是高度。

    以此类推,每一段十六进制编码就代表着一个特定的含义。感兴趣的可以自行百度。

    所以,颜色重复度越大的、越接近的(渐变的颜色或透明度等),编码重复度就越大,就越容易压缩。

      压缩原理:

      png图片用差分编码(Delta encoding)对图片进行预处理,处理每一个的像素点中每条通道的值。

      压缩阶段会将预处理阶段得到的结果进行Deflate压缩,它由 Huffman 编码 和 LZ77压缩构成。

      压缩后的结果就是一串处理后的编码,保存到数据库中,占用空间会小很多,在使用的时候,再进行逆向解析渲染。

      具体代码暂无。

  • 相关阅读:
    omnibus gitlab-ce安装
    Helm
    pod状态为Back-off
    我的博客即将入驻“云栖社区”,诚邀技术同仁一同入驻。
    云主机搭建Kubernetes 1.10集群
    Linux清除Windows密码
    Nginx负载均衡之健康检查
    MariaDB主从复制搭建
    Python基础
    Tomcat URL重写
  • 原文地址:https://www.cnblogs.com/ljwsyt/p/10868001.html
Copyright © 2011-2022 走看看