天天看點

線上解析Base64編碼圖像

上一篇介紹中,我們将二進制檔案(BLOB)儲存為Base64編碼的文本,這些文本可以内嵌在XML的标簽中,是以二進制資訊它可以随着XML檔案被拷貝、下載下傳而不用擔心資訊會缺失。這項技術也在email郵件中被廣泛使用。 

浏覽器對Base64的支援  

圖像是最經常被使用的一種二進制檔案。而現代的浏覽器的進步日新月異,IE7,FireFox和其他浏覽器為包括Base64在内各種編碼的圖像資訊提供了很好的支援。是以圖形資訊可以以下面的形式呈現在頁面中、 

Java代碼  

  1. <img src="data:image/gif;base64,R0lGODlhDwAPAKECAAAAzMzM/  
  2. wAAACwAAAAADwAPAAACIISPeQHsrZ5ModrLlN48CXF8m2iQ3YmmKqVlRtW4ML  
  3. wWACH+H09wdGltaXplZCBieSBVbGVhZCBTbWFydFNhdmVyIQAAOw=="  
  4. alt="Base64 encoded image" width="150" height="150"/>  

這種做法有利有弊,好處是浏覽器可以在一個連接配接中得到完成的頁面内容,不好的地方時圖像的大小會增加1/3。是以,這種内嵌的方法适合對小的圖形元素比如圖示、圓角等等進行處理,進而減少浏覽器打開的連接配接數,但對大的照片、圖檔(量少而大)等等則不應該使用Base64編碼以免影響下載下傳速度。 

為了得到剛才的Base64編碼,我們将上一篇的Java修改成Struts Action,并借用了JIMI進行圖形的讀取和格式轉換,Base64編碼器則改為更普遍的Apache Commons元件,代碼如下: 

Java代碼  

  1. public class Base64ImageAction extends ActionSupport {  
  2.     private final static String galleryName = "gallery";  
  3.     private static String parent = null;  
  4.          private String encodeString = null;  
  5.     public String getEncodeString() {  
  6.         return encodeString;  
  7.     }  
  8.     public void setEncodeString(String encodeString) {  
  9.         this.encodeString = encodeString;  
  10.     }  
  11.     private String getImageFullPath() {  
  12.         parent = new File(this.getClass().getClassLoader().getResource(  
  13.                     File.separator).getPath()).getParent()+File.separator+"flag.jpg";  
  14.     }  
  15.     public String execute() {  
  16.         ByteArrayOutputStream output = new ByteArrayOutputStream();  
  17.         try {  
  18.             JimiReader reader = Jimi.createJimiReader(this.getImageFullPath());  
  19.             Image image = reader.getImage();  
  20.             Jimi.putImage("image/png", image, output);  
  21.             output.flush();  
  22.             output.close();  
  23.             this.encodeString = Base64.encodeBase64String(output.toByteArray());  
  24.         } catch (IOException e) {  
  25.             e.printStackTrace();  
  26.         } catch (JimiException e) {  
  27.             e.printStackTrace();  
  28.         }  
  29.         return SUCCESS;  
  30.     }  
  31. }  

對應的View端是個十分簡單的Freemarker模闆: 

Html代碼  

  1. <html>  
  2. <head>  
  3. <title>Hello,World</title>  
  4. </head>  
  5. <body>  
  6. <img src="data:image/png;base64,${encodeString}" />  
  7. </body>  
  8. </html>  

處理古代浏覽器  

世界總是不是那麼完美,盡管大部分現代浏覽器對Base64的處理都十分完善,但是我們不能不考慮到一些“古老”的浏覽器,而現在還是普遍使用的“古老”的浏覽器,就當屬IE6,在IE6裡試圖浏覽上面的圖檔可能會得到一個紅叉叉。我們不得不為IE6做一些特殊處理,利用下面的javascript,我們把Base64字串傳回伺服器端,重新解析成圖檔 

Javascript代碼  

  1. // a regular expression to test for Base64 data  
  2. var BASE64_DATA = /^data:.*;base64/i;  
  3. // path to the PHP module that will decode the encoded data  
  4. var base64Path = "/my/path/base64.php";  
  5. function fixBase64(img) {  
  6.   // check the image source  
  7.   if (BASE64_DATA.test(img.src)) {  
  8.     // pass the data to the PHP routine  
  9.     img.src = base64Path + "?" + img.src.slice(5);  
  10.   }  
  11. };  
  12. // fix images on page load  
  13. onload = function() {  
  14.   for (var i = 0; i < document.images.length; i++) {  
  15.     fixBase64(document.images[i]);  
  16.   }  
  17. };  

伺服器端的Struts可以參考上面的例子做反向操作,具體從略。 

更完美的方法  

将Base64傳回伺服器解碼是不錯的IE6更新檔,但是違背了我們的初衷,對IE6來說,浏覽器連接配接數并未有任何減少。更直接的想法,是否能用Javascript直接在浏覽器中,對Base64文本進行解碼呢?我們構思的場景如下:伺服器端先将圖檔轉換成PNG格式以友善用戶端進行處理,Base64編碼之後,利用JSON将文本傳遞給浏覽器用戶端進行處理。 

我們選擇PNG圖形格式是因為PNG已經俨然成為新的Web圖形标準,它格式非常簡單,可以很友善的用javascript進行處理而不需要借助浏覽器的支援。我們知道javascript直接不能處理二進制資料,但是現在這不是個問題,伺服器端已經準備好了Base64編碼的文本資料,現在我們隻需要一個javascript的Base64解析器,你可以在這裡找到一個notmasteryet的Base64解析器。 

現在PNG圖形格式采用了DEFLATE作為唯一的壓縮算法,該算法也廣泛應用在ZIP,GZIP等壓縮格式中。PNG圖像格式檔案(或者稱為資料流)由一個8位元組的PNG檔案署名(PNG file signature)域和按照特定結構組織的3個以上的資料塊(chunk)組成。 

PNG定義了兩種類型的資料塊,一種是稱為關鍵資料塊(critical chunk),這是标準的資料塊,另一種叫做輔助資料塊(ancillary chunks),這是可選的資料塊。關鍵資料塊定義了4個标準資料塊,其中圖像資料塊IDAT(image data chunk):它存儲實際的資料, PNG總的資料流采用DEFLAT進行壓縮。此外還擦用三角過濾“delta filters”來過濾每一行的像素的未壓縮資料。DEFLAT和delta壓縮在其他資料和文本進行中也被廣泛應用。PNG格式你可以參考<a href="http://www.libpng.org/pub/png/spec/1.1/PNG-Contents.html" target="_blank" rel="external nofollow" >官方文檔</a>。 

很棒的,notmasteryet也為我們提供了一個DEFLAT解壓器。 

最後,我們把這些組合起來: 

Html代碼  

  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
  2. <html xmlns="http://www.w3.org/1999/xhtml">  
  3. <head>  
  4.     <title>Demo JavaScript PNG Viewer</title>  
  5.  </head>  
  6. <body onload="show(gravatar);">  
  7. <script src="../Source/Base64.js" type="text/javascript"></script>  
  8. <script src="../Source/Deflate.js" type="text/javascript"></script>  
  9. <script src="../Source/PNG.js" type="text/javascript"></script>  
  10. <script type="text/javascript">  
  11. var gravatar = 'iVBORw0KGgoAAAANSUhEUgAAA.......資料從略......55CYII=';  
  12. String.prototype.padRight = function(c, n){  
  13.     var txt = '';  
  14.     for(var i=0;i<n-this.length;i++) txt += c;  
  15.     return txt + this;  
  16. };  
  17. function show(data){  
  18.     var png = new PNG(data);  
  19.     var img = document.getElementById('image'), limg = document.getElementById('largeimage');  
  20.     document.getElementById('nativeimage').src = 'data:image/png;base64,' + data;  
  21.     img.innerHTML = '';  
  22.     limg.innerHTML = '';  
  23.     img.style.width = png.width + 'px';  
  24.     img.style.height = png.height + 'px';  
  25.     limg.style.width = (png.width * 3) + 'px';  
  26.     limg.style.width = (png.height * 3) + 'px';  
  27.     var line;  
  28.     while(line = png.readLine())  
  29.     {  
  30.         for (var x = 0; x < line.length; x++){  
  31.             var px = document.createElement('div'), px2 = document.createElement('div');  
  32.             px.className = px2.className = 'pixel';  
  33.             px.style.backgroundColor = px2.style.backgroundColor = '#' + line[x].toString(16).padRight('0', 6);  
  34.             img.appendChild(px);  
  35.             limg.appendChild(px2);  
  36.         }  
  37.     }  
  38. }  
  39. </script>  
  40. <div id="image"></div>  
  41. <div id="largeimage"></div>  
  42. <img id="nativeimage" />  
  43. </body>  
  44. </html>   

還可以更完美  

回顧上一篇的例子,我們用了ihard.net提供了Base64編碼,它提供一個GZIP編碼參數,你可以發現如此編碼之後的文本大小和原來的圖形大小相差無幾。利用上一節提供了javascript是不是可以解決Base64編碼後檔案大小增加的問題?留着思考吧。