如何在client端壓縮圖片並將資料送到後端


#1

你好,想要詢問關於client端壓縮圖片的問題。

目前的專案有使用者上傳圖片的欄位,但是現在的手機常常預設的檔案都很大,所以不希望使用者上傳太大的檔案佔掉太多資源。

在後端的部分已經有限制使用者的圖片大小(gem使用carrierwave),但是Nginx這邊似乎有設定訪客只能上傳1M的資料,上傳超出就會噴”413 Request Entity Too Large“。即便可以設定client_max_body_size,讓Nginx接受更大的上傳資料,但依舊要送很大的資料到後端,所以就在想說不如在client端就先壓縮檔案大小。

目前和公司前端討論的情況是,先用javascript壓縮後,接著另開API透過ajax將資料傳到後端。

但是不確定這樣是不是最好的做法,因為感覺能夠將壓縮後的檔案,一樣像往常一樣塞到表單中送出,而不是透過ajax送到後端。

因此上來論壇請教,不知道是否有其他人有遇過類似的問題並有相對應的解決方法?
感謝各位的耐心閱讀。


#2

其實就我目前的專案而言,都還是全傳,之前寫了篇在這邊

對 server 而言 incoming 流量基本上應該都不算 … 因為去請機房應該都對等頻寬,然後你只會把 upload 那邊用滿||||(user 的 upload 對 server 而言是 download) 當然我的專案需要上傳的都是機敏資料,留原檔的尺寸都要非常高就是了,而壓縮過水可以把髒東西清光(隱碼之類的),所以請一定要清 …

留 EXIF 備存可以方便後續分析使用者的相機機種甚至定位之類的,不過也單純的是輔助資料,看你們要不要留來做判斷哩,然而你說的是 JS 壓縮後傳出,這點當然 ok,不過接下來是品質因素不是?

你應該可以用 JS 的 FileReader 或之類的鬼取到檔案大小,讀 raw file header 分析後取得寬高 size 之後才判定是否壓縮,而通常你壓縮的檔案會碰到的條件應該會有:

A:尺寸過大
B:檔案過大

這部分就有趣了,類似你去產生一張白雜訊的圖片,它可以很小張,但會觸碰到B,而 JPG 一壓會噴死光光 … 而如果你送一張全白的圖片,則可能會會觸碰到A,但B卻爆小 … 所以我建議你抓兩者乘積之類的來當標準會好點?

另外這邊有趣的應該是,如果你用 FileReader 取 buffer 後應該就可以"重傳",其實很多 JS 作品都有,包括 MEGA 免費空間,這概念也等同 torrent / ed2k 之類的,大概就是自己做 chunk 機制,把一個檔案切細,之後看跑幾隻 worker 然後同時上傳,失敗重試,還可以回應使用者目前上傳多少 % 和速度之類的,大概就這些唄,但請記得,上面可能都在原檔的傳輸上面,因為你要做 JPG 壓縮的話可能需要全載到記憶體內才能執行,而 … 請確保記憶體是否足夠的問題唄|||

而你的 server 端可以收到類似 tmp 下,等全部傳完後再組成 raw file 或 tmpfile 丟給 gem 即可(如果你還要用) gem 的話,這邊大概就這樣唄?

如果你讀到記憶體後,傳到 server 的 params 應該就不會是 tmpfile 而是一般的 post text,可以自己再組成 StringIO(其他程式語言應該叫做 StringBuffer)或是 tmpfile,如果自幹 call system 的話上面那篇也有教你 stdin 的寫法就是,大概就這樣唄?這邊我之前沒看到有方便的 gem 或自動化的做法就是,所以請嘗試自己寫看看哩?

這邊還有另外一個重點是 … 你在 JS 下處理 binary 後,轉成 POST 後的編碼,如果是 Base64 而非 binary 的話,至少會多 1/3 檔案大小,所以要實際量測過,否則你會白忙哩 … 其實這主題最好的做法還是要過 APP 才是卍解才是 … 而非貧弱的 JS … |||


#3

主流解法好像都是使用 canvas
當然縮圖的品質就不能強求
幾個答案也有提到這樣做的缺點

未來也許能靠 web assembly 做吧。


#4

嘛,當然可以哩,但其實都是怕隱碼攻擊,所以就算在 client 做,到 server 後都還是希望再轉一次清垃圾之類的,不然你全吃之後吐給其餘 user 的都是糟糕物哩

排除這個話題之外,其實 JavaScript 有全套的東西了,包括 JPEG 的整套 lib,所以可以在 JavaScript 內做 jpg 壓縮,還可以選擇壓縮比之類的鬼,當然包括縮圖之類的也都有,畢竟把後端處理 binary / 演算法之類的流程搬給 JavaScript 即可,但又歸到之前所說的安全性時 … 嗯 …

當然還有另外一個解法啦,類似來源 2M 對象 512k,可以中間先把 2M 改成 1M,然後後端才把 1M 清除成 512k,不過這樣就 … 累很多哩 …


#5

謝謝大家的協助,後來我們前端圖片壓縮的部分是先將格式轉成Base64,接著再用Canvas改變圖片尺寸,然後再傳到後端。圖片上傳的部分是使用Carrierwave,但因為要處理Base64的格式,所以用了carrierwave-base64這個gem來處理資料。

關於Base64還有Canvas的部分可以參考這篇