JS前端繪圖canvas模糊問(wèn)題示例高清圖解
緣起
模糊在 canvas 中應(yīng)該算是個(gè)經(jīng)典問(wèn)題了,相信大家也曾經(jīng)看過(guò)很多相關(guān)文章,但總是記不住,因?yàn)楦拍詈芏?,描述的也不夠明確,所以我就自己總結(jié)了一篇,刨去了復(fù)雜概念,順帶畫(huà)了幾張高清圖,以此加深理解(我覺(jué)得畫(huà)的賊好??,記不住就來(lái)打我)。
模糊的原因
總的來(lái)說(shuō)模糊的原因大致可分為以下三點(diǎn):
1、canvas 的大小和 css 的大小不一致
- 首先讓我們先拋棄 css 大小的概念,請(qǐng)記住我們所有的繪圖操作都是在 canvas 這個(gè)畫(huà)布大小上進(jìn)行的;
- canvas 繪制完成后最終在頁(yè)面渲染的時(shí)候才和 css 大小有關(guān)。這時(shí)候把 canvas 想象成一張大小固定的照片,把 css 想象成一個(gè)容器,不管 css 尺寸如何,這張照片都會(huì)鋪滿(mǎn)整個(gè)容器(機(jī)制就是這樣,沒(méi)有為啥??)。所以如果長(zhǎng)寬比例相同就會(huì)等比縮放;
- 如果長(zhǎng)寬比例不同就會(huì)拉伸變形;如果大小一樣就剛剛好。
就像下面這樣:
所以一般情況下我們需要把 canvas 和 css 設(shè)置成一樣大:
<canvas width="600" height="600" style="width: 600px; height: 600px;"></canvas>
2、當(dāng)繪制的東西不足 1px,會(huì)自動(dòng)補(bǔ)足 1px
大家應(yīng)該也有聽(tīng)說(shuō)個(gè)這個(gè)說(shuō)法,說(shuō)在 (100, 0) 處畫(huà)一條 1px 的豎線(xiàn),畫(huà)出來(lái)的線(xiàn)會(huì)感覺(jué)很模糊。
因?yàn)檫@涉及到 canvas 繪制線(xiàn)條的方式,(100, 0)是線(xiàn)的中心點(diǎn),繪制的時(shí)候會(huì)從這個(gè)點(diǎn)向兩邊擴(kuò)散 1px/2=0.5px,而不是單邊擴(kuò)散,但是我們無(wú)法繪制出半個(gè)像素,最小也得是 1px,所以就會(huì)自動(dòng)補(bǔ)足成 1px(而且是純色),這樣一來(lái)兩邊各擴(kuò)散 1px 總共就變成了 2px。
但僅僅這樣線(xiàn)條也不應(yīng)該模糊,頂多變粗了,再?gòu)?fù)雜的點(diǎn)的圖形也只是會(huì)表現(xiàn)出有鋸齒感而已。其實(shí)模糊的主要原因在于自動(dòng)補(bǔ)足的這一部分的顏色已經(jīng)不是之前那個(gè)顏色了,而是根據(jù)某種算法(比如取周?chē)慕粕┥傻摹?/p>
那為什么要做顏色改變呢?主要就是為了解決剛才說(shuō)的鋸齒問(wèn)題而做的一種平滑處理的手段。
沒(méi)看懂???那就看圖,大概是下面這個(gè)樣子????:
當(dāng)然也不是說(shuō)你畫(huà) 1px 就模糊,如果你在(100.5, 0)畫(huà)條 1px 的豎線(xiàn),是不會(huì)變粗也不會(huì)模糊的,應(yīng)該說(shuō)是出現(xiàn)半個(gè)像素的時(shí)候或多或少會(huì)模糊些,包括 translate(0.5, 0.5) 也會(huì)造成模糊。
所以在使用 canvas 畫(huà)圖時(shí),應(yīng)注意減少值出現(xiàn)半個(gè)像素的情況,或者繪制的時(shí)候多加半個(gè)像素,這也是種優(yōu)化(包括計(jì)算取整也是)??梢钥纯聪旅娴膶?duì)比圖感受一下????:
3、受到高清屏的影響
事實(shí)上你會(huì)發(fā)現(xiàn)即便 canvas 和 css 大小一樣可能還是會(huì)模糊,這是受到了 dpr(設(shè)備像素比,其他概念就別去記啦??)的影響,如下圖所示:
圖沒(méi)看懂???那就來(lái)看看文字解說(shuō):假設(shè)我們現(xiàn)在 canvas 和 css 的大小都是 10 * 10,那么 canvas 畫(huà)完的照片中就會(huì)有 100 個(gè)(像素)點(diǎn),也就是只有 100 個(gè)點(diǎn)的信息;
但是到了高清屏中(如 dpr = 2),我們需要 400 個(gè)點(diǎn)的信息,原來(lái)的點(diǎn)不夠用怎么辦?于是就會(huì)有一套算法來(lái)自動(dòng)生成這些點(diǎn)的信息,從而造成了模糊。那應(yīng)該怎么辦呢???
我們需要更多的點(diǎn),所以可以這樣子搞,把畫(huà)布放大 dpr 倍,也就是把 canvas 變大 dpr 倍,而 css 的大小保持不變,雖然 canvas 變大了,但是最終在頁(yè)面上繪制的時(shí)候放到 css 中又會(huì)因?yàn)榈缺瓤s放(上面第一點(diǎn)說(shuō)到的原因)變回來(lái)了原來(lái)大小,這樣一折騰,點(diǎn)就變多了。
但是要注意什么呢,畫(huà)布變大了,相應(yīng)的繪制操作(畫(huà)圓、畫(huà)矩形等)也需要相應(yīng)放大,一般有兩種方法,很多文章都只告訴你放大,但是卻沒(méi)告訴你放大之后怎么做,所以看看下面的代碼應(yīng)該會(huì)幫助你更好的理解????:
// 方法一:放大畫(huà)布之后,直接用 scale 放大整個(gè)坐標(biāo)系 // * 但是你要知道我們一直是在放大的坐標(biāo)系上繪制的,可能不知道什么時(shí)候(比如重新設(shè)置畫(huà)布寬高),scale 可能就會(huì)被重置成 1 了,從而造成畫(huà)面錯(cuò)亂 adaptDPR() { // 在初始化 canvas 的時(shí)候就要調(diào)用該方法 const dpr = window.devicePixelRatio; const { width, height } = this.canvas; // 重新設(shè)置 canvas 自身寬高大小和 css 大小。放大 canvas;css 保持不變,因?yàn)槲覀冃枰敲炊嗟狞c(diǎn) this.canvas.width = Math.round(width * dpr); this.canvas.height = Math.round(height * dpr); this.canvas.style.width = width + 'px'; this.canvas.style.height = height + 'px'; // 直接用 scale 放大整個(gè)坐標(biāo)系,相對(duì)來(lái)說(shuō)就是放大了每個(gè)繪制操作 this.ctx2d.scale(dpr, dpr); // 接下來(lái)的繪制操作和往常一樣,比如畫(huà)個(gè)矩形 ctx2d.strokeRect(x, y, w, h);原來(lái)該怎么算就怎么算,該怎么調(diào)用還是怎么調(diào)用 }
// 方法二:放大畫(huà)布之后,需要把每一個(gè)繪制的 api 都乘以 dpr // * 這樣一來(lái)使用的時(shí)候就會(huì)很麻煩,所以我們需要把所有的繪制操作進(jìn)行統(tǒng)一封裝 // 可以參考這個(gè)庫(kù):https://github.com/jondavidjohn/hidpi-canvas-polyfill,不過(guò)這個(gè)庫(kù)也不是所有 api 都覆蓋 adaptDPR() { // 在初始化 canvas 的時(shí)候就要調(diào)用該方法 const dpr = window.devicePixelRatio; const { width, height } = this.canvas; // 重新設(shè)置 canvas 自身寬高大小和 css 大小。放大 canvas;css 保持不變,因?yàn)槲覀冃枰敲炊嗟狞c(diǎn) this.canvas.width = Math.round(width * dpr); this.canvas.height = Math.round(height * dpr); this.canvas.style.width = width + 'px'; this.canvas.style.height = height + 'px'; // 注意這里沒(méi)有用 scale } // 接下來(lái)在每個(gè)涉及繪制的 api 時(shí)都乘以 dpr,比如繪制矩形的時(shí)候 strokeRect(x: number, y: number, w: number, h: number) { const { ctx2d, dpr } = this; if (!ctx2d) return; x = x * dpr; y = y * dpr; w = w * dpr; h = h * dpr; ctx2d.strokeRect(x, y, w, h); }
兩種方法都極力推薦大家試一試,感受一下差別,真的要試一試,不然記不住的,文章這東西過(guò)眼云煙??。
這里補(bǔ)充一個(gè)小知識(shí)點(diǎn):scale
和 setTransform
這兩個(gè) canvas api 都可以進(jìn)行縮放,不同的是 scale
會(huì)在當(dāng)前的變換效果上進(jìn)行疊加縮放,而 setTransform
則會(huì)覆蓋當(dāng)前的變換效果。
一個(gè)是疊加,一個(gè)是覆蓋,記得區(qū)分開(kāi)。
結(jié)語(yǔ)
可能有的同學(xué)還是會(huì)感到迷糊,所以感興趣的同學(xué)可以看看這個(gè)github,看看具體是怎樣在項(xiàng)目中應(yīng)用的,當(dāng)然也有配套的文章說(shuō)明(??函數(shù)曲線(xiàn)的繪制,縱享絲滑)。
以上就是JS前端繪圖canvas模糊問(wèn)題示例高清圖解的詳細(xì)內(nèi)容,更多關(guān)于JS前端繪圖canvas模糊的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微信小程序 簡(jiǎn)單DEMO布局,邏輯,樣式的練習(xí)
這篇文章主要介紹了微信小程序 簡(jiǎn)單DEMO布局,邏輯,樣式的練習(xí)的相關(guān)資料,這里寫(xiě)一個(gè)簡(jiǎn)單實(shí)例練習(xí)小程序的布局,并附實(shí)例代碼和實(shí)現(xiàn)效果圖,需要的朋友可以參考下2016-11-11微信小程序 中wx.chooseAddress(OBJECT)實(shí)例詳解
這篇文章主要介紹了微信小程序 中wx.chooseAddress(OBJECT)實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-03-03Fabric.js 實(shí)現(xiàn)變換視窗示例詳解
這篇文章主要為大家介紹了Fabric.js 實(shí)現(xiàn)變換視窗示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09網(wǎng)頁(yè)里控制圖片大小的相關(guān)代碼
網(wǎng)頁(yè)里控制圖片大小的相關(guān)代碼...2006-06-06