圖片的壓縮上傳


背景

  實際生產中經常遇到這樣的場景:為減小服務器壓力,上傳附件尤其是圖片的時候,往往需要限制上傳文件的大小。而限制的方案也有兩種,一種就是限制用戶可上傳的文件大小,由用戶來選擇上傳的文件和如果文件過大由用戶自行進行壓縮裁剪;另一種就是由服務進行圖片的壓縮和大小控制然后再上傳到服務器。這里主要介紹的是第二種方案。

主要技術

  前邊有介紹過證書的生成和下載,其中就有證書的壓縮和打包的相關操作,感興趣的可以看下本人的那篇文章。這里同樣是采用的該原理,步驟如下:

關鍵步驟

  圖片文件-->文件流(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="width: 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壓縮構成。

  壓縮后的結果就是一串處理后的編碼,保存到數據庫中,占用空間會小很多,在使用的時候,再進行逆向解析渲染。

  具體代碼暫無。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM