原文連接:https://ssshooter.com/2019-04...javascript
以前作文件上傳和 canvas 修圖時接觸到幾個格式,這裏打算整理一下他們的關係html
<input type="file" id="avatar" name="avatar" accept="image/png, image/jpeg" />
使用 input 獲取文件時,你拿到的就是 file
對象,而 file
繼承於 blob
,因此直接講比較陌生的 blob 吧。java
BLOB (binary large object),二進制大對象,是一個能夠存儲二進制文件的「容器」。git
使用 Blob 可讓你在瀏覽器生成一個臨時文件,使用 URL.createObjectURL()
獲取他的連接,你就能像服務器文件同樣使用他。es6
let temp = new Blob(['hello fantasy']) URL.createObjectURL(temp) // 返回 "blob:https://ssshooter.com/1bf84bba-b53a-4155-8348-33a487e8ab7e"
返回的 url 前面是主機,後面是一個惟一識別碼。github
在建立這個臨時文件後,只要不關閉當前頁面,這個文件就會一直存在於內存,你須要主動運行 URL.revokeObjectURL(url)
刪除引用。算法
在控制檯打出 blob 你根本不知道里面是啥,那麼怎麼讀取 blob 呢?canvas
藉助 FileReader,你能夠把 Blob 讀取爲 Buffer。數組
FileReader 對象容許 Web 應用程序異步讀取存儲在用戶計算機上的文件(或原始數據緩衝區)的內容,使用 File 或 Blob 對象指定要讀取的文件或數據。
var reader = new FileReader() reader.addEventListener('loadend', function() { // reader.result 包含轉化爲類型數組的blob }) reader.readAsArrayBuffer(blob)
通過 FileReader 的讀取,你能看到計算機儲存數據的本質 —— 二進制數據。瀏覽器
ArrayBuffer 相似數組,每一格放入 1Byte(8bit)數據,也就是八位的 0 或 1,因此換成十進制一格最大是 255.
例如:
ArrayBuffer 不能直接操做,而是要經過類型數組對象(下面列出來的)或 DataView 對象來操做,它們會將緩衝區中的數據表示爲特定的格式,並經過這些格式來讀寫緩衝區的內容。
對應視圖的轉換還很神奇的,ArrayBuffer 是一個格子 8 位,也就是跟 xx8Array 是同樣的,天然不用轉換,而 16 以後的都是把每幾個格子合成一個。
// 建立 buffer ab = new ArrayBuffer(4) // 建立視圖 a = new Uint8Array(ab) // 經過視圖操做 buffer a[0] = 2 a[1] = 25 a[2] = 31 a[2] = 233
打印 ab 會輸出:
爲了方便理解他們如何轉換,我把他轉爲 2 進制:
// Int16Array ;['1100100000010', '11101001'] // Int32Array ;['111010010001100100000010'] // Uint8Array ;['10', '11001', '11101001', '0']
可能還沒能看出來?那再在前面補 0:
// Int16Array ;['0001100100000010', '0000000011101001'] // Int32Array ;['00000000111010010001100100000010'] // Uint8Array ;['00000010', '00011001', '11101001', '00000000']
這就很明顯能看出:要轉換就要在分組以後把同一組數據從右到左拼接。
不過我自覺通常不太會用到這麼細緻的 bit 操做(
另外,canvas 能夠經過 ctx.createImageData() 獲得 ImageData
。
ImageData.data
就是一個 Uint8ClampedArray
,裏面順序放着圖片每個像素的 rgba 值。你能夠對這個 Uint8ClampedArray
進行一系列操做,再用 canvas.toBlob
這個 buffer 變回 Blob,就完成了圖片編輯的操做。
經過這個操做,再結合一些卷積核相關知識,就能完成相似這個卷積核圖片修改器的功能。
Data Url 是一個前綴爲 data:
的協議,你能夠藉助這個協議在文檔中嵌入一些小文件(最多見就是內聯圖片了),數據格式以下:
data:[<mediatype>][;base64],<data>
mediatype 填入MIME 類型,MIME 也用於服務器返回數據時指定數據類型;base64 是一種編碼方式;後面接着就是數據本體。
幾個例子:
data:,Hello%2C%20World!
data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D
data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E
data:text/html,<script>alert('hi');</script>
上面提到的 base64 不算是一種加密算法,它只是簡單地將每 3 個 8bit 字符轉換爲 4 個 6Bit 字符(base64 只有 2^6 = 64 種字符,所以得名),這樣保證了傳輸中一定使用 ASCII 中可見字符,不會出奇怪的空白字符或是功能性標誌 。
因爲是 3 個字符變 4 個,那麼很明顯了,base64 編碼後,編碼對象的體積會變成原來的 4/3 倍。
特別要注意的是若是 bit 數不能被 3 整除,須要在末尾添加 1 或 2 個 byte(8 或 16bit),而且末尾的 0 不使用 A 而使用 =,這就是爲何 base64 有的編碼結果後面會有一或兩個等號。
喜聞樂見的舉例時間:
前置知識點:utf8 與 unicode 的關係
你可使用 charCodeAt() 獲取一個字符的 unicode 編碼(0 到 65535 之間的整數),可是 unicode 只是一個碼錶,並非一個具體的編碼方式,utf8 纔是。因此你拿到的英文字母編碼先後同樣的,可是漢字(等字符)就不一樣了。
爲了直接獲得漢字的 utf8 碼,使用 TextEncoder
(其實還能夠選擇使用 encodeURIComponent
處理漢字,可是英文又不能正常轉換了)。
下面用 JavaScript 簡單寫個 base64 轉換流程:
var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' Array.from(new TextEncoder().encode('碧藍幻想')) .map(val => val.toString(2).padStart(8, 0)) // 轉二進制,補充到八位 .join('') .replace(/([10]{6})/g, '$1,') // 每六位插個逗號用於拆分 .split(',') // 拆分爲每 6 位一組 .map(val => parseInt(val.padEnd(6, 0), 2)) // 補充後面的 0(可是沒有補夠) .map(val => b64[val]) .join('')
結果基本是對的,可是後面的 =
仍是用了普通的 A
並且位數沒有加夠,因此上面轉換出來的不是標準 base64,僅供參考
blob url
Data Url
參考
// 10 var a = 10 // 8 var b = 0o1234567 // 或直接在前面加0,若是後面數字都小於8就自動變成8進制 var c = 01234567 // 2 var d = 0b1010101110101 // 16 var e = 0xe87a90
parseInt(0o1234567, 8) parseInt(0b1010101110101, 2) parseInt(0xe87a90, 16)
;(123456).toString(2) ;(123456).toString(8) ;(123456).toString(16)