png的故事:隔行掃描演算法

發表於2017-06-21

前言

前文已經講解過如何解析一張png圖片,然而對於掃描演算法裡只是說明了逐行掃描的方式。其實png還支援一種隔行掃描技術,即Adam7隔行掃描演算法。

優劣

使用隔行掃描有什麼好處呢?如果大家有去仔細觀察的話,會發現網路上有一些png圖在載入時可以做到先顯示出比較模糊的圖片,然後逐漸越來越清晰,最後顯示出完整的圖片,類似如下效果:Adam7_make_awesome_face

這就是隔行掃描能帶來的效果。隔行掃描一共會進行1到7次掃描,每一次都是跳著部分畫素點進行掃描的,先掃描到畫素點可以先渲染,每多一次掃描,圖片就會更清晰,到最後一次掃描時就會掃描完所有畫素點,進而渲染出完整的圖片。

當然,也因為要進行跳畫素掃描,整張圖片會儲存更多額外資料而導致圖片大小會稍微變大,具體增加了什麼額外資料下文會進行講解。

生成

要匯出一張基於Adam7隔行掃描的png圖片是非常簡單,我們可以藉助Adobe的神器——PhotoShop(以下簡稱ps)。我們把一張普通的圖片拖入到ps中,然後依次點選【檔案】-【儲存為Web所用的格式】,在彈出的框裡選擇儲存為PNG-24,然後勾選交錯,最後點選儲存即可。

這裡的交錯就是隻將掃描演算法設為Adam7隔行掃描,如果不勾選交錯,則是普通逐行掃描的png圖片。

原理

Adam7隔行掃描演算法的原理並不難,本質上是將一張png圖片拆分成多張png小圖,然後對這幾張png小圖進行普通的逐行掃描解析,最後將解析出來的畫素資料按照一定的規則進行歸位即可。

分析

在解壓縮完影象資料後就要馬上進行拆圖。拆圖並不難,就是將原本儲存影象資料的Buffer陣列拆分成多個Buffer陣列而已。關鍵的問題是怎麼拆,這時我們先祭上wiki上這張圖:

Adam7_passes

上面這張圖就說明了每次掃描需要掃描到的畫素,正常來說一張基於Adam7隔行掃描的png圖片是要經歷7次掃描的,不過有些比較小的圖片的實際掃描次數不到7次,這是因為有些掃描因為沒有實際畫素點而落空的原因,所以下面的講解還是以標準的7次掃描來講解,本質上此演算法的程式碼寫出來後,是能相容任何大小的png圖片的,因為演算法本身和圖片大小無關。

7次掃描,其實就回答了上面拆圖的問題:要拆成7張小圖。每張小圖就包含了每次掃描時要歸位的畫素點。

以第一次掃描為例:第一次掃描的規則是從左上角(我們設定此座標為(0,0))開始,那麼它掃描到的下一個點是同一行上一個點往右偏移8個畫素,即(8,0)。以此類推,再下一個點就是(16,0)、(24,0)等。噹噹前行所有符合規則的點都掃描完時則跳到下一個掃描行的起點,即(8,0),也就是說第一次掃描的掃描行也是以8個畫素為偏移單位的。直到所有掃描行都已經掃描完成,我們就可以認為這次掃描已經結束,可以考慮進入第二次掃描。

我們以一張10*10大小的png圖片來舉例,下面每個數字代表一個畫素點,數字的值代表這個點在第幾次掃描時被掃描到:

按照規則,在第一次掃描時我們會掃描到4個畫素點,我們把這4個畫素點單獨抽離出來合在一起,就是我們要拆的第一張小圖:

也就是說,我們的第一張小圖就是2*2大小的png圖片。後面的小圖大小以此類推,這樣我們就能得知拆圖的依據了。

拆圖

上面有提到,拆圖本質上就是把存放圖片資料的Buffer陣列進行切分,在nodejs裡的Buffer物件有個很好用的方法——slice,它的用法和陣列的同名方法一樣。

直接用上面的例子,我們的第一張小圖是2*2點png圖片,在假設我們一個畫素點所佔的位元組數是3個,那麼我們要切出來的第一個Buffer子陣列的長度就是2*(2*3+1)。也許就有人好奇了,為什麼是乘以2*3+1而不是直接乘以2*3呢?之前我們提到過,拆成小圖後要對小圖進行普通的逐行掃描解析,這樣解析的話每一行的第一個位元組實際存放的不是影象資料,而是過濾型別,因此每一行所佔用的位元組需要在2*3的基礎上加1。

畫素歸位

其他的小圖拆分的方法是一樣,在最後一次掃描完畢後,我們就會拿到7張小圖。然後我們按照上面的規則對這些小圖的畫素進行歸位,也就是填回去的意思。下面簡單演示下歸位的流程:

待到7張小圖的畫素全部都歸位後,最後我們就能拿到一張完整的png圖片了。

程式碼

整個流程的程式碼如下:

尾聲

整個Adam7隔行掃描的流程大概就是這樣:

png的故事:隔行掃描演算法

 

相關文章