為什么要在前端處理圖片
最近開發業務系統時遇到了一個不常見的需求:處理圖片使除了筆跡外的內容顯示為白色/透明
業務場景:
用戶將個人簽名圖片上傳到系統中,管理員使用這個簽名打印文件。原本打印功能需要的是白底黑字的簽名,但由於沒有特殊限制,大部分用戶就直接用筆紙簽名拍照后就上傳到系統中了(實際上應該上傳白紙黑字掃描件)。這導致管理員打印時把會把照片的背景色也打印到紙張上,影響打印效果。為了方便管理員操作(避免再去讓用戶重新上傳簽名),需要提供一個能夠將手寫簽名照處理成符合打印要求的圖片的功能
目標效果
實現原理
思路: 參考PS中的色階工具,灰度轉換 + 閾值處理(二值化)
Step 1: 使用 Canvas 讀取和修改圖片的色值
在學校時就有選修過數字圖像處理的課程,所以我可以確定這個需求簡單實現起來並不會太難(產品要求不高的情況下),但是以前學習時都是使用 MATLAB 處理圖片的,對怎么使用瀏覽器處理圖片並不清楚。
經過查詢得知,Canvas 可以獲取指定區域的像素值,張鑫旭的這篇文章就是不錯的示例,相關API:
-
canvas.drawImage
: 在 Canvas 上繪制圖片 -
canvas.getImageData
: 獲取 Canvas 上指定區域的像素值 -
canvas.putImageData
: 修改 Canvas 上指定區域的像素值
Step 2: 將用戶上傳的彩色照片轉換成灰度圖
因為最終需要的簽名是白底黑字的,不需要彩色,所以可以將圖片轉換成灰度圖方便計算:
canvas.getImageData
返回的是 Uint8ClampedArray(960000)
數組,其中 960000 = 600(原圖寬) * 400(原圖高) * 4(每個像素對應數組中4個元素)
,每個像素對應數組中4個元素分別是 [R, G, B, Alpha]
即 [紅, 綠, 藍, 透明度]
,取值范圍均為 [0-255]
灰度計算的經驗公式: Gray = 0.299 * R + 0.587 * G + 0.114 * B
const gray =
0.299 * imageData.data[coordinate] +
0.587 * imageData.data[coordinate + 1] +
0.114 * imageData.data[coordinate + 2];
// 賦值給 RGB,將彩圖轉換成灰度圖
imageData.data[coordinate] = gray;
imageData.data[coordinate + 1] = gray;
imageData.data[coordinate + 2] = gray;
Step 3: 根據閾值處理圖片(色階工具)
這里取一個上閾值 thresholdWhite
和下閾值 thresholdBlack
,均為 [0-255]
,對所有像素做如下處理:灰度值大於上閾值修改成白色,灰度值低於下閾值修改成黑色
通過測試得到一個適用於大部分場景的閾值計算方式(其中 avg
為灰度圖的均值):
thresholdWhite = avg - 40; // 灰度大於該閾值會變成白色(255,255,255)
thresholdBlack = avg - 80; // 灰度低於該閾值會變成純黑色(0,0,0)
灰度圖處理分為兩步:
-
根據閾值計算色值轉換函數,使用一個一維數組表示
const transformMatrix = Array.from({ length: 256 }); // 色值轉換函數 for (let i = 0; i < 256; i++) { if (i > thresholdWhite) { transformMatrix[i] = 255; // 白色 } else if (i < thresholdBlack) { transformMatrix[i] = 0; // 黑色 } else { transformMatrix[i] = i; // 保持不變 } }
-
根據色值轉換函數處理圖片
const gray = transformMatrix[imageData.data[coordinate]]; imageData.data[coordinate] = gray; imageData.data[coordinate + 1] = gray; imageData.data[coordinate + 2] = gray;
優缺點
優點:
- 純前端實現,無需修改服務器上存儲的原圖
- 實現簡單,沒有復雜的邏輯,用戶使用時不需要有處理圖片的知識,只需要調節閾值即可
缺點:
- 自動處理只是簡單計算了平均值,未必是最好的效果,有時仍需要管理員手動調整
- 照片中的陰影過黑和筆記過淡都會影響處理效果
最終效果(生產環境)
實現代碼
注:由於 Canvas 對跨域的限制,需要啟動一個服務訪問 index.html,Canvas 才能正常加載圖片,推薦使用 vscode 中的 live server 插件或 webstorm 自帶的預覽功能
待改進
- 在上述基礎上,加入一些更好的算法處理圖片中的陰影
- 考慮以圖片增加濾波器的方式,對筆跡較細的簽名進行加粗