canvas 圖片、文字模糊問題

注:[n]標識爲遺留問題,在文章末尾遺留問題部分有詳細解釋說明。javascript

以前作了一個在線給圖片添加文本框的工具,大致思路是先把圖片加載到一個 DOM 結構中,而後經過 html2canvas 導出到一個canvas,最後經過 canvas 自帶的 toDataURL 方法導出成圖片。css

這個思路並不複雜,可是中間遇到幾個小問題:html

  1. 跨域圖片的導出問題:你能夠把圖片繪製到 canvas 中,可是不能作任何有關導出數據的操做(好比 toDataURL ),由於 canvas 認爲它本身是被污染(tainted)的。(固然本地上傳的圖片是不存在這個問題的)html5

    This protects users from having private data exposed by using images to pull information from remote web sites without permission.java

    ——出自 canvas-todataurl-securityerrorweb

    大概意思是說,這樣能夠保護用戶隱私數據不被暴露。canvas

  2. 在 retina 屏幕上canvas 的內容顯示變模糊。跨域

  3. 圖片模糊就算了,爲何fillText輸入的文字也會模糊?並且導出來會清晰一點(可是仍是模糊)瀏覽器

解決過程:安全

  1. 第一個問題其實就是解決咱們熟悉的跨域問題。這個工具的主要使用場景是在海外的 i8n 項目,圖片通常放在海外的圖片服務器上。我給圖片添加 crossorigin:anonymous 不生效,因此決定換條路。

    既然傳統的跨域用法是失敗的,可是咱們知道 <img>src 屬性能夠用 base64編碼後的數據表示圖片的內容,這樣不會存在跨域問題。因此我想用 FileReader 轉換圖片格式。可是後來才發現 FileReader 一樣不容許處理跨域資源…計劃泡湯。

    而後發現這麼個工具CORS Anywhere,是給你的請求頭部加 CORS header 的。這樣一來應該能夠解決跨域問題。(未具體嘗試)

  2. 這個問題纔是今天想講的主題。

    先把網上的解決方法貼出來:

    devicePixelRatio = window.devicePixelRatio || 1,
    backingStoreRatio = context.webkitBackingStorePixelRatio || 1,
    ratio = devicePixelRatio / backingStoreRatio;
    
    var w = $("#code").width();
    var h = $("#code").height();
    
    //要將 canvas 的寬高設置成容器寬高的 2 倍
    var canvas = document.createElement("canvas");
    canvas.width = w * ratio;
    canvas.height = h * ratio;
    canvas.style.width = w + "px";
    canvas.style.height = h + "px";
    var context = canvas.getContext("2d");
    //而後將畫布縮放,將圖像放大兩倍畫到畫布上
    context.scale(ratio,ratio);
    複製代碼

    上面的代碼咱們分兩部分看,先忽略上面定義 ratio 值的部分,往下看。 說明一下,canvas 的屬性 width/height和樣式表裏指定的寬高不一樣,前者肯定了這個畫布的內容大小,然後者只是顯示上的大小。因此上面代碼就不難理解了,實際上是把畫布的內容高寬放大二倍,而樣式上不變,視覺上就會變得精細不少,和二倍圖的原理基本上是相似的。

    道理我都懂,可是代碼開頭那一大堆在算什麼?

    按照上面的邏輯來講,咱們只須要經過 devicePixelRatio 判斷設備是否是 retina 屏幕(不嚴格地說)就能夠了。爲何要算他和backingStoreRatio的比值,這又是個什麼東西?

    咱們在往 canvas 裏畫任何東西的時候,實際上瀏覽器都在把這些寫到了一個後備存儲空間裏。瀏覽器在從新繪製到屏幕時候,數據就是來自這裏。webkitBackingStorePixelRatio這個值告訴咱們的是後備空間相對 canvas 自己容量的大小。

    如今咱們知道了這個值的做用,它是如何控制展現的?

    1x1x1

    上圖展現的是 dpr:bk === 1 的狀況,就像沒有出現 retina 屏幕這件事同樣,導出和匯入兩不相干。 關鍵是二者值都爲2的時候也是如此。因此即便是在 retina 屏幕上,也有可能不作多餘的代碼處理圖片也能夠很清楚。這也是爲何咱們說計算 ratio 的值時咱們要算兩者的比值而不是單純用 dpr。 並且這兩個更多時候確實沒有任何關係,並非 dpr 爲2 bk 的值就也必定高。

    1x1x1
    dpr:bk === 2問題出現了。咱們原樣把圖片放進來,canvas 由於 bk 值爲1因此沒有對圖片作其餘處理,再展現到頁面上的時候就會模糊。這其實跟通常的圖片在 retina 屏幕上模糊的緣由相同。

    好比咱們有一個長寬都爲30px的圖,放到 retina 屏幕上佔有 30 csspx 的寬度,可是實際上填充他寬度的有60個物理像素。咱們的圖片只提供了30個已知的像素值,其他的30個只能靠瀏覽器根據周圍的像素點去計算。因此會模糊。

  3. 下面來討論爲何文字模糊的問題。 剛開始看到文字模糊的時候以爲沒什麼難理解的,明顯是和圖片一個套路。可是細想以爲不對,圖片是由於在 dpr 爲2的狀況下,圖片內容寬和圖片樣式寬倒是相等的因此模糊。可是文字在我打到頁面上到畫到 canvas 的過程當中,實際像素數是足夠的,爲何會模糊?

    在查了部分資料以後發現,在頁面上字體的展現和在 Canvas 裏 用fillText 去繪製文字是不同的,後者實際上是在 canvas 裏「畫」字,而這個畫的結果的展現單元和上面圖片是同樣的,到如今爲止咱們能夠把這個過程和圖片展現想成相同的了。

    至於爲何下載後會清楚一些可是卻不「那麼清楚」,咱們當作兩個問題來解答。 爲何會清楚一些?由於模糊其實是瀏覽器渲染時候的行爲,下載以後查看圖片是沒有這個像素估算的過程的。 爲何卻不那麼清楚?詳細的我不想講了,具體的能夠看這個回答

遺留問題: [1]: 發送的 file 協議的請求到服務器端判斷跨域的時候和 http 是同樣的標準嗎?我我的以爲其實應該是的,由於同源策略自己的目的就是出於安全,這一點和你客戶端的協議實際上是不要緊的。

參考文章:

High DPI Canvas

設備像素,設備獨立像素,CSS像素

Canvas text rendering (blurry)

相關文章
相關標籤/搜索