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