canvas 圖片、文字模糊問題

注:[n]標(biāo)識為遺留問題,在文章末尾遺留問題部分有詳細(xì)解釋說明。

之前做了一個(gè)在線給圖片添加文本框的工具,大體思路是先把圖片加載到一個(gè) DOM 結(jié)構(gòu)中,然后通過 html2canvas 導(dǎo)出到一個(gè)canvas,最后通過 canvas 自帶的 toDataURL 方法導(dǎo)出成圖片。

這個(gè)思路并不復(fù)雜,但是中間遇到幾個(gè)小問題:

  1. 跨域圖片的導(dǎo)出問題:你可以把圖片繪制到 canvas 中,但是不能做任何有關(guān)導(dǎo)出數(shù)據(jù)的操作(比如 toDataURL ),因?yàn)?canvas 認(rèn)為它自己是被污染(tainted)的。(當(dāng)然本地上傳的圖片是不存在這個(gè)問題的)

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

    ——出自 canvas-todataurl-securityerror

    大概意思是說,這樣可以保護(hù)用戶隱私數(shù)據(jù)不被暴露。

  2. 在 retina 屏幕上canvas 的內(nèi)容顯示變模糊。

  3. 圖片模糊就算了,為什么fillText輸入的文字也會模糊?而且導(dǎo)出來會清晰一點(diǎn)(但是還是模糊)

解決過程:

  1. 第一個(gè)問題其實(shí)就是解決我們熟悉的跨域問題。這個(gè)工具的主要使用場景是在海外的 i8n 項(xiàng)目,圖片一般放在海外的圖片服務(wù)器上。我給圖片添加 crossorigin:anonymous 不生效,所以決定換條路。

    既然傳統(tǒng)的跨域用法是失敗的,但是我們知道 <img>src 屬性可以用 base64編碼后的數(shù)據(jù)表示圖片的內(nèi)容,這樣不會存在跨域問題。所以我想用 FileReader 轉(zhuǎn)換圖片格式。但是后來才發(fā)現(xiàn) FileReader 同樣不允許處理跨域資源…計(jì)劃泡湯。

    然后發(fā)現(xiàn)這么個(gè)工具CORS Anywhere,是給你的請求頭部加 CORS header 的。這樣一來應(yīng)該可以解決跨域問題。(未具體嘗試)

  2. 這個(gè)問題才是今天想講的主題。

    先把網(wǎng)上的解決方法貼出來:

    devicePixelRatio = window.devicePixelRatio || 1,
    backingStoreRatio = context.webkitBackingStorePixelRatio || 1,
    ratio = devicePixelRatio / backingStoreRatio;
    
    var w = $("#code").width();
    var h = $("#code").height();
    
    //要將 canvas 的寬高設(shè)置成容器寬高的 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和樣式表里指定的寬高不同,前者確定了這個(gè)畫布的內(nèi)容大小,而后者只是顯示上的大小。所以上面代碼就不難理解了,其實(shí)是把畫布的內(nèi)容高寬放大二倍,而樣式上不變,視覺上就會變得精細(xì)很多,和二倍圖的原理基本上是類似的。

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

    按照上面的邏輯來說,我們只需要通過 devicePixelRatio 判斷設(shè)備是不是 retina 屏幕(不嚴(yán)格地說)就可以了。為什么要算他和backingStoreRatio的比值,這又是個(gè)什么東西?

    我們在往 canvas 里畫任何東西的時(shí)候,實(shí)際上瀏覽器都在把這些寫到了一個(gè)后備存儲空間里。瀏覽器在重新繪制到屏幕時(shí)候,數(shù)據(jù)就是來自這里。webkitBackingStorePixelRatio這個(gè)值告訴我們的是后備空間相對 canvas 本身容量的大小。

    現(xiàn)在我們知道了這個(gè)值的作用,它是如何控制展示的?

    1x1x1

    上圖展示的是 dpr:bk === 1 的情況,就像沒有出現(xiàn) retina 屏幕這件事一樣,導(dǎo)出和匯入兩不相干。
    關(guān)鍵是兩者值都為2的時(shí)候也是如此。所以即使是在 retina 屏幕上,也有可能不做多余的代碼處理圖片也可以很清楚。這也是為什么我們說計(jì)算 ratio 的值時(shí)我們要算二者的比值而不是單純用 dpr。
    而且這兩個(gè)更多時(shí)候確實(shí)沒有任何關(guān)系,并不是 dpr 為2 bk 的值就也一定高。

    1x1x1

    dpr:bk === 2問題出現(xiàn)了。我們原樣把圖片放進(jìn)來,canvas 因?yàn)?bk 值為1所以沒有對圖片做其他處理,再展示到頁面上的時(shí)候就會模糊。這其實(shí)跟一般的圖片在 retina 屏幕上模糊的原因相同。

    比如我們有一個(gè)長寬都為30px的圖,放到 retina 屏幕上占有 30 csspx 的寬度,但是實(shí)際上填充他寬度的有60個(gè)物理像素。我們的圖片只提供了30個(gè)已知的像素值,其余的30個(gè)只能靠瀏覽器根據(jù)周圍的像素點(diǎn)去計(jì)算。所以會模糊。

  3. 下面來討論為什么文字模糊的問題。
    剛開始看到文字模糊的時(shí)候覺得沒什么難理解的,明顯是和圖片一個(gè)套路。但是細(xì)想覺得不對,圖片是因?yàn)樵?dpr 為2的情況下,圖片內(nèi)容寬和圖片樣式寬卻是相等的所以模糊。但是文字在我打到頁面上到畫到 canvas 的過程中,實(shí)際像素?cái)?shù)是足夠的,為什么會模糊?

    在查了部分資料之后發(fā)現(xiàn),在頁面上字體的展示和在 Canvas 里 用fillText 去繪制文字是不一樣的,后者其實(shí)是在 canvas 里「畫」字,而這個(gè)畫的結(jié)果的展示單元和上面圖片是一樣的,到現(xiàn)在為止我們可以把這個(gè)過程和圖片展示想成相同的了。

    至于為什么下載后會清楚一些但是卻不「那么清楚」,我們當(dāng)做兩個(gè)問題來解答。
    為什么會清楚一些?因?yàn)槟:龑?shí)際上是瀏覽器渲染時(shí)候的行為,下載之后查看圖片是沒有這個(gè)像素估算的過程的。
    為什么卻不那么清楚?詳細(xì)的我不想講了,具體的可以看這個(gè)回答

遺留問題:
[1]: 發(fā)送的 file 協(xié)議的請求到服務(wù)器端判斷跨域的時(shí)候和 http 是一樣的標(biāo)準(zhǔn)嗎?我個(gè)人覺得其實(shí)應(yīng)該是的,因?yàn)橥床呗员旧淼哪康木褪浅鲇诎踩@一點(diǎn)和你客戶端的協(xié)議其實(shí)是沒關(guān)系的。

參考文章:

High DPI Canvas

設(shè)備像素,設(shè)備獨(dú)立像素,CSS像素

Canvas text rendering (blurry)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容