在 HTML DOM 裡面,如果要直接指定圖片、也就是 <img> 的內容、而非一個圖檔網址的話,基本上是可以想辦把圖檔的 binary 內容,直接傳到瀏覽器裡面,然後再透過 DOM 的 Image 物件(w3school),以「data URI scheme」(維基百科)的形式,來指定內容的。
它的使用形式,基本上就是:
Img.src = 'data:image/jpg;base64,xxxxxxxxxxx';
而這邊所指定的 data URL,就是先告訴瀏覽器這是一張格式是 JPEG 的圖檔,然後在「,」之後,就直接給他整張圖的資料。如此一來,瀏覽器就可以直接顯示這張圖片了~
在 Heresy 這邊,基本上就是透過 WebSocket 的 ArrayBuffer(參考),來把 JPEG 檔的資料(Heresy 這邊是直接抓資料、在記憶體內壓縮)、直接傳遞給 JavaScript 的 client 端。
不過,由於 HTML DOM 的 Image 需要的資料格式,是要經過 Base64 編碼(維基百科),所以並不是直接將 JPEG 檔的 binary 資料拿來用就可以了,還需要先把它編碼成 Base64 的形式。而由於經過 Base64 編碼過的資料會比較大、大約會變成本來的 135% 左右,所以為了節省資料傳輸的頻寬,Heresy 這邊是決定以原始資料的形式,把壓縮成 JPEG 後的圖片資料直接傳到瀏覽器、然後在瀏覽器端進行 Base64 的編碼。
而要在瀏覽器內、透過 JavaScript 來進行 Base64 編碼的方法也有很多種,這邊 Heresy 是根據《Encoding XHR image data》的內容,整理一些方法:
FileReader.readAsDataURL()
這邊的作法,是先把資料封包成 Blob 的形式,然後透過 FileReader(參考)提供的 readAsDataURL(),直接把資料重新讀取一遍、轉換為 Data URL 的格式;他的程式寫法大致如下:
var blob = new Blob([buffer], { type: type });
var fr = new FileReader();
fr.onload = function () {
image.src = fr.result;
};
fr.readAsDataURL(blob);
其中,buffer 就是輸入的原始 binary 資料、形式是 ArrayBuffer。而 type 則是他的 MIME 型別,如果是 JPEG 檔的話,就是「image/jpeg」,image 則是要用來顯示圖片的物件。
btoa() String.fromCharCode.apply()
這個做法,主要是透過 btoa()(參考)來把字串轉換成 Base64 的編碼;不過由於 btoa() 只接受字串,所以如果像 Heresy 是使用 ArrayBuffer 的話,則是需要先把他轉換字串。這邊則是透過 String.fromCharCode.apply() 把 ArrayBuffer 的物件。下面就是這邊的程式寫法:
var str = String.fromCharCode.apply(null, new Uint8Array(buffer));
image.src = 'data:image/jpg;base64,' btoa(str);
自己寫函式做轉換
基本上,Base64 的編碼方法並不複雜,網路上也可以找到很多範例可以參考;例如《Javascript base64》這邊就有提供一個實作的範例;另外在《How can you encode a string to Base64 in JavaScript?》也有提供一些其他的方法。
而 Heresy 這邊之前拿來用的,則是《Display image from blob using javascript and websockets》一文中提供的實作,其程式碼如下:
function encode(input) {
var keyStr =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 /=";
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
 
while (i < input.length) {
chr1 = input[i ];
chr2 = i < input.length ? input[i ] : Number.NaN;
chr3 = i < input.length ? input[i ] : Number.NaN;
 
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
 
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = keyStr.charAt(enc1) keyStr.charAt(enc2)
keyStr.charAt(enc3) keyStr.charAt(enc4);
}
return output;
}
在有 encode() 這個函式的情況下,就可以直接把 Uint8Array 的資料,直接進行 Base64 編碼,而不像 btoa() 需要先轉換成字串了。使用的時候,則是如下:
image.src = 'data:image/jpg;base64,' encode(new Uint8Array(buffer));
不過基本上,這個方法由於是自己去寫轉換的程式,所以效率應該會比使用內建的函式來的差;但是好處就是相容性會比較好了~
這篇大概就是簡單整理一下,目前 Heresy 知道的幾種方法了~目前算是解決問題了,可能之後有時間,會再來做效能的調整、測試吧。當然,上面的方法,也不見得是最好的,如果有建議,也歡迎提供。