信息技術——計算機圖形和圖像處理——便攜式網(wǎng)絡圖形:功能規(guī)范(ISO/IEC 15948:2003-E)
2003年11月10日W3C正式推薦
當前版本:http://www.w3.org/TR/2003/REC-PNG-20031110
先前版本:http://www.w3.org/TR/2003/PR-PNG-20030520
編輯:David Duce(牛津布魯克斯大學壮不,第二版)
作者:在網(wǎng)頁上自己找吧。
第二版內容相比第一版修正了一些規(guī)范设凹,相關細節(jié)請參閱勘誤表。
另外,這個文檔還沒有被翻譯成中文蕊唐。
Copyright ? 2003 W3C? (MIT, ERCIM, Keio), All Rights Reserved. W3C liability, trademark, document use and software licensing rules apply.
摘要
便攜式網(wǎng)絡圖形(PNG)是一種可擴展的文件格式归敬,用于圖像的無損壓縮存儲。它可以保存圖片的多項內容硕勿,例如顏色索引哨毁、灰度通道、彩色通道源武、透明通道挑庶、1至16位的深度采樣等。是 GIF 格式的無專利代替品软能,也可以在一定范圍內代替 TIFF 格式使用迎捺。
該格式具有流式讀取,快速檢測文件完整性的特點查排,避免了文件傳輸中的由網(wǎng)絡波動導致的一些問題凳枝,在實際應用中表現(xiàn)出色。此外跋核,格式中可以添加伽馬曲線信息以及色度信息岖瑰,使其在不同平臺、不同的屏幕色差中表現(xiàn)一致砂代。
本文檔是專門用于闡釋 PNG 網(wǎng)絡流媒體格式的標準規(guī)范蹋订。
文檔狀態(tài)
本文檔是2003年10月14日公布的第二版PNG標準規(guī)范,同時也是國際標準 ISO/IEC 15948∶2003刻伊。除了封面和樣板差異露戒,文檔內容與國際標準內容一致。但不保證此文檔長期有效捶箱,請在W3C出版物列表上查找和更新最新版本智什。
文檔的內容基于1996年10月公布的第一版PNG標準規(guī)范,結合了所有已知的勘誤和澄清丁屎,已通過 ISO/IEC/JTC 1/SC 24荠锭、W3C、PNG 開發(fā)小組的審查晨川。截至公布日证九,已擁有超過180種圖像瀏覽器*、超過100種圖像編輯軟件*支持了 PNG 格式共虑;SVG* 瀏覽器也都完全支持了 PNG 格式愧怜;盡管 HTML 規(guī)范并沒有提及,但是仍有60款 HTML 瀏覽器*提供了選擇看蚜。相關軟件的支持可以訪問pngstatus獲取叫搁。
如需反饋,請通過郵箱聯(lián)系 PNG 組,問題和答復會在網(wǎng)頁上公布渴逻。
此文檔內容網(wǎng)上公開疾党,出版商 PNG 組不收取任何專利費用。
注意:
1惨奕、網(wǎng)頁上的這篇文檔雪位,為了保證圖片在 HTML 瀏覽器中正常顯示,同時提供了 SVG梨撞、PNG 兩種格式雹洗,在不同的瀏覽器中會顯示不同的版本。
2卧波、一些Bug时肿,現(xiàn)在已經(jīng)解決了。
文檔還不存在英語以外的版本(我是假的)港粱。
說明
本國際標準的設計目標是:
- 可移植性:格式的編碼螃成、解碼和傳輸應與軟件、硬件無關查坪。
- 完備性:盡可能兼容真彩寸宏、索引和灰度圖像,在每種情況下都可以添加透明度偿曙、顏色空間信息和輔助信息(如文本注釋)氮凝。
- 串行編碼解碼:可以串行生成和讀寫,使數(shù)據(jù)能夠在串行通信上實時生成和顯示望忆。
- 漸進式圖像:在傳輸數(shù)據(jù)流時罩阵,可以開始呈現(xiàn)圖像的近似值,并隨著數(shù)據(jù)流的接收而逐漸清晰炭臭。
- 傳輸錯誤:可以可靠地檢測數(shù)據(jù)傳輸錯誤永脓。
- 無損耗:過濾和壓縮應該保存所有的信息。
- 性能:任何過濾鞋仍、壓縮和漸進式圖像呈現(xiàn)都應以高效解碼和呈現(xiàn)為目標。讀取圖像(解碼)比寫入圖像(編碼)更重要搅吁,必要時犧牲編碼速度威创。
- 壓縮:壓縮要有效率。
- 簡單性:標準要簡單谎懦,容易被開發(fā)者理解肚豺。
- 互換性:PNG 解碼器應能讀取所有符合 PNG 規(guī)范的數(shù)據(jù)/流。
- 靈活性:可以添加私人擴展界拦,但不能損害互換性吸申。
- 不觸犯法律:不使用侵權的算法。
目錄
[TOC]
1 范圍
便攜式網(wǎng)絡圖形(PNG 讀作 “ping”)是一種可擴展的文件格式,用于圖像的無損壓縮存儲截碴。它可以保存圖片的多項內容梳侨,例如顏色索引、灰度通道日丹、彩色通道走哺、透明通道、1至16位的深度采樣等哲虾。是 GIF 格式的無專利代替品丙躏,也可以在一定范圍內代替 TIFF 格式使用。
該格式具有流式讀取束凑,漸進式讀取晒旅,快速檢測文件完整性的特點,避免了文件傳輸中的由網(wǎng)絡波動導致的一些問題汪诉,在實際應用中表現(xiàn)出色敢朱。此外,格式中可以添加伽馬曲線信息以及色度信息摩瞎,使其在不同平臺拴签、不同的屏幕色差中表現(xiàn)一致。
本文檔是專門用于闡釋 PNG 網(wǎng)絡流媒體格式的標準規(guī)范旗们。
2 參考文獻
參考了下列規(guī)范性文件蚓哩。這些規(guī)范有可能失效,但這里沿用舊版上渴;對于沒有標明日期的岸梨,使用最新版本。
文檔編號 | 文檔名稱 | 備注 |
---|---|---|
ISO 639:1988 | 語言名稱表示代碼 | |
ISO/IEC 646:1991 | 國際標準化組織信息技術——信息交換用 ISO 7位編碼字符集 | |
ISO/IEC 3309:1993 | 信息技術——電信和系統(tǒng)間的信息交換——高級數(shù)據(jù)鏈路控制(HDLC)程序稠氮,幀結構 | |
ISO/IEC 8859-1:1998 | 信息技術——8位單字節(jié)編碼圖形字符集——第1部分:拉丁字母表第1號 | 1 |
ISO/IEC 9899:1990(R1997) | C語言 | |
ISO/IEC 10646-1:1993 | 信息技術——通用多八位字節(jié)編碼字符集(UCS)——第1部分:體系結構和基本多語言體系 | |
IEC 61966-2-1 | 多媒體系統(tǒng)和設備——顏色測量和管理——第2-1部分:默認RGB顏色空間 | 2 |
CIE-15.2 | 比色法曹阔,第二版(CIE Publication15.2-1986 / ISBN 3-900-734-00-3) | |
ICC.1: 1998-09 | 文件顏色格式(by International Color Consortium) | 3 |
ICC.1A: 1999-04 | ICC.1: 1998-09的添加版 | 3 |
RFC-1123 | 互聯(lián)網(wǎng)主機的需求——應用和支持 | 4 |
RFC-1950 | ZLIB壓縮數(shù)據(jù)格式規(guī)范,第3.3版 | 5 |
RFC-1951 | DEFLATE壓縮數(shù)據(jù)格式規(guī)范隔披,第1.3版 | 6 |
RFC-2045 | MIME(多用途互聯(lián)網(wǎng)郵件擴展)——第一部分:互聯(lián)網(wǎng)消息體的格式 | 7 |
RFC-2048 | MIME——第四部分:注冊程序 | 8 |
RFC-3066 | 語言鑒別標簽 | 9 |
備注:
- 為了方便使用赃份,這里是簡化的規(guī)范,只需要使用部分編碼奢米,參考:https://www.w3.org/TR/PNG/iso_8859-1.txt抓韩,當然,你可以使用它的字符超集鬓长。
- 參考:http://www.iec.ch/
- 參考:http://www.color.org/
- 參考:http://www.ietf.org/rfc/rfc1123.txt
- 參考:http://www.ietf.org/rfc/rfc1950.txt
- 參考:http://www.ietf.org/rfc/rfc1951.txt
- 參考:http://www.ietf.org/rfc/rfc2045.txt
- 參考:http://www.ietf.org/rfc/rfc2048.txt
- 參考:http://www.ietf.org/rfc/rfc2066.txt
3 術語谒拴、定義和縮寫術語
本文檔適用以下定義
術語 | 定義 |
---|---|
alpha | 表示像素的透明度,0 表示像素完全透明英上,值越大越不透明炭序。 |
alpha 壓縮 | 不使用透明通道,通過標記一些顏色或者灰度苍日,使這些顏色或灰度的被當作透明惭聂。 |
alpha 分離 | 當所有顏色都是不透明的時候,就不需要 alpha 通道了易遣。 |
alpha 表 | 一個用于表示透明的索引表彼妻。 |
輔助塊 | PNG 文件由許多塊組成,輔助塊是一些記載額外信息的塊豆茫,允許缺失/跳過侨歉。 |
位深度 | [3.1](# 3.1 位深度) |
字節(jié) | 字節(jié)占用8位內存;也稱八位位組揩魂。一個字節(jié)可以表示 0~255幽邓。 |
字節(jié)順序 | [3.2](#3.2 字節(jié)順序) |
通道 | 一個像素用于表示紅、綠火脉、藍牵舵、灰度和透明度的部分。 |
色度 | 顏色是由亮度和色度共同表示的倦挂,可以通過色圖來查找顏色畸颅。是色圖啦! |
塊 | 一個標準的 PNG 文件由許多塊組成方援,分為關鍵塊和輔助塊没炒。 |
顏色類型 | PNG 圖片有五種:灰度、真彩犯戏、索引送火、灰度帶透明、真彩帶透明先匪,記做顏色類型种吸。 |
復合 | 通過透明度合并前景圖像和背景圖像來形成圖像。 |
關鍵塊 | 必須讀取的 PNG 塊。 |
數(shù)據(jù)流 | 是文件的一部分或者全部字節(jié)順序。數(shù)據(jù)流是即時生成的內存數(shù)據(jù)。 |
deflate | 一種 LZ77 解壓算法。PNG 使用了這個算法的 0 模式袒啼。參考 RFC-1951 |
交付圖像 | 通過數(shù)據(jù)流解碼得到的圖像,表示已經(jīng)可以交付用戶/圖像緩沖區(qū)使用了拓萌。 |
濾波器 | 為了提高圖片壓縮效率碍沐,用來轉換圖片掃描數(shù)據(jù)的過濾。 |
圖像緩沖區(qū) | 內存中的一個區(qū)域激涤,其中的數(shù)據(jù)專門用于屏幕顯示拟糕。 |
伽馬曲線值 | 人眼對自然亮度感知是非線性的(韋伯-費希勒定理)判呕。 |
灰度 | 使用單一通道,來表示亮度送滞,也就是單純的黑白圖片侠草。 |
圖片數(shù)據(jù) | 掃描圖片后得到的一維數(shù)組,這個數(shù)組可以映射在內存塊里供用戶使用犁嗅。 |
索引顏色 | [3.3](#3.3 索引顏色) |
構建索引 | 指索引顏色的排序边涕,通過序號可以從調色板中獲取顏色。此外還有 alpha 表索引褂微。 |
交錯模式 | [3.4](#3.4 交錯模式) |
無損壓縮 | 數(shù)據(jù)壓縮的方法功蜓,可以精確地重建原始數(shù)據(jù)。 |
有損壓縮 | 數(shù)據(jù)壓縮的方法宠蚂,近似地重建原始數(shù)據(jù)式撼。(我要的是全損壓縮的算法啦!) |
亮度 | 人眼感知的亮度求厕。亮度和色度組合成為了人眼中的色彩著隆。它的能量級別是對數(shù)增長的。 |
LZ77 | Ziv 和 Lempel 先生在1977年發(fā)表了一篇論文呀癣,論文描述了這種數(shù)據(jù)壓縮算法美浦。 |
網(wǎng)絡字節(jié)順序 | 與主機字節(jié)順序相反,網(wǎng)絡字節(jié)順序是指從低位往高位排序的字節(jié)项栏。 |
調色板 | 調色板是一個顏色數(shù)組浦辨,每個顏色占用3個8位字節(jié),最多儲存256種顏色忘嫉。 |
Pass 提取 | 一種提取圖片縮略圖的方法荤牍,用于交錯模式。 |
像素 | 在計算機屏幕上庆冕,每個顏色的點被稱為像素康吵,它是一種字節(jié)數(shù)據(jù)。 |
PNG 數(shù)據(jù)流 | 符合 PNG 規(guī)范的數(shù)據(jù)流访递。 |
PNG 解碼器 | 可以處理 PNG 數(shù)據(jù)流晦嵌,生成交付圖像的東西,一般是軟件解碼拷姿。 |
PNG 編輯器 | 可以修改惭载、生成符合規(guī)范的 PNG 數(shù)據(jù)流的軟件。 |
PNG 編碼器 | 將二維像素數(shù)組轉化為 PNG 數(shù)據(jù)流的東西响巢。 |
PNG 文件 | 硬盤中保存的 PNG 數(shù)據(jù)流描滔。 |
PNG 4字節(jié)帶符號整數(shù) | 4個字節(jié)的帶符號整數(shù)可以表示 |
PNG 4字節(jié)無符號整數(shù) | 4個字節(jié)的帶符號整數(shù)可以表示 0 到 |
PNG 圖像 | PNG 編碼器所產(chǎn)出的圖像文件。 |
PNG 簽名 | PNG 數(shù)據(jù)流的開頭8個字符拘泞,用以區(qū)別與其它數(shù)據(jù)流纷纫。 |
縮小圖 | 也就是交錯模式下,Pass 提取所解析出來的縮小圖陪腌,最多七張辱魁。下面也會稱為縮略圖。 |
標準圖像 | BMP诗鸭、GIF染簇、JPG 等其他標準圖像文件。 |
RGB 合并 | 將不同的顏色通道混合在一起只泼。因為在 PNG 中剖笙,不同的通道是排在一起的。 |
樣本 | 集合中隨機抽取的元素请唱,用它來代表全部的元素特性弥咪。屬于泛指。 |
樣本深度 | 樣本的二進制長度十绑。 |
樣本深度放縮 | 標準圖像的樣本深度不是統(tǒng)一的聚至,但是 PNG 要求一致,所以需要放縮這些深度本橙。 |
掃描線 | 圖像一行一行的數(shù)據(jù)扳躬。因為以前的顯示器是一行一行顯示的。 |
原圖 | 給 PNG 編碼器處理前的圖像甚亭。 |
真彩 | 包含三個以上(紅贷币、綠、藍)通道的圖像模式亏狰。 |
白點 | [3.5](#3.5 白點) |
zlib | 一個用于壓縮解壓縮的函數(shù)庫役纹。 |
CRC | 循環(huán)冗余校驗碼,用于檢測傳輸誤差的校驗碼暇唾。通過加密算法計算得到促脉。 |
CRT | 陰極射線管,曾經(jīng)的顯示器〔咧荩現(xiàn)在用 LCD 液晶顯示器瘸味。 |
LSB | 在網(wǎng)絡字節(jié)順序中,最高位的改動對值的影響最小够挂,被稱為最低有效比特(LSB)旁仿。 |
LUT | Look up table 的縮寫,是計算機上一種常用的數(shù)據(jù)轉跳方式孽糖。 |
MSB | 在網(wǎng)絡字節(jié)順序中丁逝,最低位的改動對字節(jié)值的影響最大汁胆,被稱為最高有效比特(MSB)梭姓。 |
3.1 位深度
索引模式下霜幼,表示索引顏色的字節(jié)長;顏色模式下誉尖,表示顏色樣本的字節(jié)長罪既。
一般來說很少見到 1,2铡恕,4 的深度琢感,因為不好讀取,現(xiàn)代計算機采用內存對齊的讀取方式探熔,如果是長度小于8比特驹针,就需要額外使用位域(bit field)讀取了,效率就會降低诀艰。
3.2 字節(jié)順序
在計算機發(fā)明的時候柬甥,設計了緩存、內存和硬盤的訪問方式——地址查詢其垄。
地址從低往高排列苛蒲,依次讀取出來的數(shù)據(jù)被稱為大端模式(Big-endian),反過來绿满,從地址高位往低位讀就是小端模式(Little-endian)臂外。這兩種都是字節(jié)順序。
大端模式和人的閱讀方式相同喇颁,但是不知道為什么被淘汰了漏健,它現(xiàn)在主要在 IBM PowerPC 和網(wǎng)絡上運作,所以也被稱為網(wǎng)絡字節(jié)順序(network byte order)橘霎。
而現(xiàn)在的主機一般使用的 X86蔫浆、ARM 處理器都是小端模式,被稱為主機字節(jié)順序(host byte order)茎毁。
PNG 使用網(wǎng)絡字節(jié)順序克懊。
3.3 索引顏色
普通的真彩圖片,至少由紅七蜘、綠谭溉、藍三個通道組成,也就是每個像素占用了三個字節(jié)以上的空間橡卤。這樣圖片的壓縮效率就很低扮念。
因此,我們構建一個調色板碧库,在調色板里預設好我們需要使用到的顏色柜与,后續(xù)使用時巧勤,只需要提供調色板的索引位置就可以了。
而且為了防止調色板占用過大空間弄匕,我們把調色板的容量設定在256以內颅悉。索引位置也就不會超過256,只需要一個字節(jié)就可以表示迁匠,一個字節(jié)只占用一個通道剩瓶,因此索引模式下,只有一個通道城丧。
3.4 交錯模式
一個圖片是由許多像素點組成的延曙,可以視作一個二維數(shù)組。我們可以提取其中幾個特殊的位置亡哄,比如橫行每隔一個提取一個像素枝缔,豎列每隔一個提取一個像素,這樣組成一個新的二維數(shù)組蚊惯,可以簡略失真的表示原圖愿卸。這種提取方法叫做 Pass 提取(pass extraction)拣挪,提取之后形成的數(shù)據(jù)是交錯的擦酌,每段都可以包含整個圖片的縮略部分。只需要重新組合就能產(chǎn)生完整的圖像菠劝,即使沒有完整的數(shù)據(jù)赊舶,只要其中一部分就可以得到縮略圖。是網(wǎng)絡傳輸中加快圖像傳輸?shù)姆绞礁险铮贿^現(xiàn)在網(wǎng)速很快笼平,幾乎用不到了。
3.5 白點
色圖的中間區(qū)域是白色的舔痪,被我們稱作白點寓调,是色圖的一個參數(shù)。另一個參數(shù)是基準锄码,用作色圖的平移夺英。
我們可以設置白點的坐標,來改變中間白色區(qū)域滋捶,使其向紅色痛悯、綠色、藍色偏移重窟,用以調整色圖的變化程度载萌。
4 概念
4.1 圖像
PNG 規(guī)范不指定應用程序接口,但是涉及四種圖像:原圖、標準圖像扭仁、PNG 圖像垮衷、交付圖像。關系如下:
graph LR;
subgraph ;
id0[原圖]--理論上-->id1[標準圖像];
id0-.實際上.->id2[PNG 圖像];
end;
id2--編碼-->id3[PNG 數(shù)據(jù)流];
id3---id5[PNG 文件];
id3--解碼-->id4[PNG 圖像];
subgraph ;
id6[標準圖像]--理論上-->id7;
id4-.實際上.->id7[交付圖像];
end;
像素通道 | 紅色通道 | 綠色通道 | 藍色通道 | 透明通道 |
---|---|---|---|---|
二進制編碼舉例 | 1000 | 11011 | 0010 | 1000 |
二進制編碼占用的比特數(shù)乖坠,就是樣本深度搀突。
4.2 顏色空間
PNG 有三種管理色彩空間的方法:使用 ICC 配置、使用 sRGB 配置瓤帚、使用色度基準和白點位置配置描姚。
ICC 配置比較靈活,易于適配戈次;sRGB 配置需要設置一個特定的顏色空間,可能會占用較多的容量筒扒;最后一種比較精確怯邪。前兩種也推薦使用伽馬值。
4.3 標準圖像與 PNG 圖像的變換
4.3.1介紹
我們需要通過一些手段花墩,將標準圖像轉換為 PNG 圖像悬秉。流程如下:
graph LR;
id[標準圖像]-->id0[透明通道分離];
id0-->id4[RGB 合并];
id4-->id5[alpha 壓縮];
id5-->id7[樣本深度放縮];
id0-->id6[構建索引];
id6-->id7;
id7-->id8[PNG 圖片];
4.3.2 透明通道分離
分離透明通道,實際上冰蘑,很多標準圖像沒有透明通道和泌,這樣可以默認為無透明度,節(jié)省一個通道祠肥。
4.3.3 構建索引
如果不同像素值的個數(shù)少于 256武氓,樣本深度小于等于8,可以開始構建索引仇箱。
4.3.4 RGB 合并
如果县恕,顏色樣本深度一致,而且每個通道都一樣的值剂桥,可以用一個通道來表示所有忠烛,即灰度圖。
4.3.5 alpha 壓縮
不用 alpha 通道表示透明度的一種方法权逗,需要設置背景色美尸。
4.3.6 樣本深度放縮
不是所有的深度都被 PNG 支持,只有 1斟薇、2师坎、4、8奔垦、16屹耐,如果不是這些數(shù)的話,深度就要通過軟件調節(jié)。
比如原始深度是 5惶岭,現(xiàn)在要把它變成 8寿弱,也就是擴大了。
如果不同通道有不同的深度按灶,我們就會選取最大深度來調整症革。
這種深度變換是可逆的。
4.4 PNG 圖像
一共有五種:
- 真彩透明(RGBA):每個像素由四個樣本組成:紅色鸯旁、綠色噪矛、藍色和透明。
- 灰度透明(LA):每個像素由兩個樣本組成:灰色和透明铺罢。
- 真彩(RGB):每個像素由三個樣本組成:紅色艇挨、綠色、藍色和藍色韭赘∷醣酰可以設置透明色來表示透明。
- 灰度(L):每個像素由單個樣本組成:灰色泉瞻÷雎可以設置透明色來表示透明。
- 索引:每個像素由調色板中的索引組成(可能還有 alpha 表)袖牙。
4.5 PNG 的編碼
4.5.1 介紹
- Pass 提炔嗑蕖:交錯模式下,PNG 圖像的像素可以被重新排列鞭达,以形成多個較小的圖像司忱,縮小圖,或者直接稱呼為 Pass碉怔。
- 掃描線序列化:將圖片(原圖或縮小的圖片)的數(shù)據(jù)從左到右烘贴、一行一行的讀取,形成一個個數(shù)組撮胧。
- 過濾:使用濾波器將每個掃描線數(shù)組轉換桨踪。
- 壓縮:將過濾后的數(shù)據(jù)壓縮。
- 分塊:將壓縮過的數(shù)據(jù)分為多個塊芹啥,并添加校驗碼锻离。
- 數(shù)據(jù)流構建:塊被插入到數(shù)據(jù)流中。
graph LR;
id[PNG 圖片]-->id0[Pass 提取];
id0-->id1[掃描線序列化];
id1-->id2[過濾];
id2-->id3[壓縮];
id3-->id4[分塊];
id4-->id5[數(shù)據(jù)流構建];
4.5.2 Pass 提取
這里采用兩種方法進行 Pass 提取墓怀。
第一種是空方法汽纠,也就是什么都不做。(所以為什么要這么死板的把這個也計作一種方法)
第二種通過多次掃描得到七個縮小圖傀履。也就是 Adam7 算法(不是深度學習的那個 Adam 算法)虱朵。
不過這個算法在國內網(wǎng)站這個幾乎找不到,維基百科https://en.wikipedia.org/wiki/Adam7_algorithm)介紹的也不是很清楚。所以我這里就簡單說一下:
- 這個算法的目標是把一張圖片分成很多縮略圖碴犬,這個變換必須是可逆的絮宁,多個縮略圖也能轉換成一張完整的圖片。這樣就能防止文件損壞后服协,無法再讀取圖片了绍昂,因為縮略圖還在,通過一定的嘗試偿荷,還是可以恢復原始的圖片窘游。
- 最簡單的分法,就是隔列/行提取跳纳,比如:每隔一個像素提取一個(或者每隔一行提取一行)忍饰,就可以得到兩個數(shù)組,每個數(shù)組都是原始圖像的縮略數(shù)據(jù)棒旗。Adam 算法也是在隔列/行提取的基礎上展開的喘批。
- 把原始的二維像素數(shù)組視作一個棋盤,每格代表一個像素铣揉。原始的數(shù)組就是從左到右烙常、從上到下一格一格的提取『撸現(xiàn)在我們把這個讀取面擴大,像”田“字一樣滴须,一次性讀取四個像素台猴,按照下表中 Adam3 的 2x2 矩陣來分(分成四個縮略圖也是可以的朽合,但是這里分成三個),第一個像素放在第一張縮略圖上饱狂,第二個像素放在第二張縮略圖上曹步,第三和第四個像素放在第三張縮略圖上。如果讀取到邊緣休讳,無法讀出“田”字數(shù)據(jù)讲婚,就假裝讀取,把該有的像素放到縮略圖上俊柔,不存在的就跳過筹麸。這就有 3 個縮略圖。
- 再次擴展雏婶,按照 Adam5 的 4x4 矩陣讀取物赶,就可以讀取 5 個縮略圖。再擴展一次就是 8x8 矩陣了留晚,可以一次性提取 7 個縮略圖酵紫。PNG 規(guī)范采用的時候,普遍圖片不超過三四百的尺寸,用 8x8 矩陣足夠了奖地。所以選定 Adam7 作為交錯模式的算法橄唬。
Adam1 矩陣: // 也就是普通的掃描方法
1
Adam3 矩陣: // 它是由 Adam1 插值得到的,注意它的奇數(shù)行和奇數(shù)項與 Adam1 相同
1, 2 // 偶數(shù)列添加 2
3, 3 // 偶數(shù)行添加 3
Adam5 矩陣: // 它是由 Adam3 插值得到的鹉动,注意它的奇數(shù)行和奇數(shù)項與 Adam3 相同
1, 4, 2, 4 // 偶數(shù)列添加 4
5, 5, 5, 5 // 偶數(shù)行添加 5
3, 4, 3, 4
5, 5, 5, 5
Adam7 矩陣: // 它是由 Adam5 插值得到的轧坎,注意它的奇數(shù)行和奇數(shù)項與 Adam5 相同
1, 6, 4, 6, 2, 6, 4, 6 // 偶數(shù)列添加 6
7, 7, 7, 7, 7, 7, 7, 7 // 偶數(shù)行添加 7
5, 6, 5, 6, 5, 6, 5, 6
7, 7, 7, 7, 7, 7, 7, 7
3, 6, 4, 6, 3, 6, 4, 6
7, 7, 7, 7, 7, 7, 7, 7
5, 6, 5, 6, 5, 6, 5, 6
7, 7, 7, 7, 7, 7, 7, 7
4.5.3 掃描線序列化
把上面的到的縮小圖(當然空方法讀出來的是原圖),逐行再讀取一遍泽示。(這里的操作就可以有很多了缸血,比如把上面的提取變成一個 yield)
4.5.4 過濾
有幾種過濾類型,會把過濾類型寫到過濾數(shù)組之前械筛。
4.5.5 壓縮
就是編碼加密捎泻。
4.5.6 分塊
把編碼后的數(shù)據(jù)分成一塊或者多塊。
4.6 附加信息
信息類型 | 描述 |
---|---|
背景色 | 顯示圖像時要使用固體背景顏色埋哟。 |
伽馬和色度 | 圖像輸出強度的伽馬值笆豁,以及圖像中使用的色度范圍。 |
國際刑事法院簡介 | 顏色空間的描述赤赊。 |
圖像直方圖 | 預計圖像使用調色板頻率闯狱。 |
物理像素尺寸 | 用于顯示 PNG 圖像的預期像素大小和寬高比,你要知道像素和真實長度是有區(qū)別的抛计。 |
有效位 | 在樣本中有效的位數(shù)/深度哄孤。 |
sRGB色彩空間 | 渲染意向(由 ICC 定義)和圖像采樣符合的顏色空間。 |
建議調色板 | 簡化的調色板吹截,當計算機不能顯示圖像中的全部顏色時瘦陈,可以使用該調色板。 |
文本數(shù)據(jù) | 與圖像相關的文本信息波俄。 |
時間 | 文件修改時間晨逝。 |
透明信息(色) | 沒有透明通道時,用這個代替透明懦铺。 |
4.7 PNG 數(shù)據(jù)流
4.7.1 塊
一個標準的 PNG 文件由許多塊組成捉貌,每個塊有四個部分:長度、名稱阀趴、數(shù)據(jù)主體昏翰、校驗碼。
4.7.2 塊類型
標準的 PNG 定義有 18 種塊類型刘急,此外你可以添加自定義的各種塊棚菊。
這 18 種塊類型有:
關鍵塊:
IHDR(image header 文件頭)、PLTE(palette 調色板)叔汁、IDAT(image data 圖片內容)统求、IEND(image end 文件結尾)
輔助塊:
透明相關:tRNS(transparency information 透明信息)
顏色相關:cHRM(chromaticities and white point 色度和白點)检碗、gAMA(gamma 伽馬值)、iCCP(embedded ICC profile 嵌入式 ICC 概述)码邻、sBIT(significant bits 有效位)折剃、sRGB(standard RGB colour space 標準RGB顏色空間)
文本相關:iTXt(international textual data 國際化文本)、tEXt(textual data 文本)像屋、zTXt(zip textual data 壓縮的文本)
時間相關:tIME(last-modification time 最新修改時間)
其他:bKGD(background colour 背景色)怕犁、hIST(histogram 直方圖)、pHYs(physical pixel dimensions 物理像素尺寸)己莺、sPLT(Suggested palette 建議調色板)奏甫、
4.8 錯誤處理
傳輸錯誤或文件損壞,這會破壞數(shù)據(jù)流的大部分或全部凌受;語法錯誤阵子,出現(xiàn)無效塊或者丟失塊。
兩種錯誤處理方式要區(qū)別胜蛉。
4.9 拓展和注冊
你可以向 ISO/IEC 或者 PNG Group 提交相關擴展挠进,注冊新的塊類型和文本關鍵字,拓展新的過濾算法誊册、交錯模式的算法领突、壓縮算法。
5 數(shù)據(jù)流結構
5.1 介紹
就是數(shù)據(jù)流的二進制的結構啦案怯。
5.2 PNG 簽名
所有 PNG 數(shù)據(jù)流的前八個字符攘须,都是 137 80 78 78 71 13 10 26 10
用 bytes 表示就是 b'\x89PNG\r\n\x1a\n'
這個簽名表示接下來的數(shù)據(jù)都是 PNG 數(shù)據(jù)流,如果遇到空字符不要打斷殴泰,需要出現(xiàn) IEND 才算結束。
5.3 塊布局
每個塊由這四個部分組成:
- 長度浮驳,占有4個字節(jié)悍汛,表示數(shù)據(jù)主體的長度。
- 塊名稱至会,占有4個字節(jié)离咐,不同的塊有不同的類型標記。
- 數(shù)據(jù)主體奉件。(當長度為 0 時宵蛀,數(shù)據(jù)主體可以為空)
- CRC 校驗碼,占有4個字節(jié)县貌,是類型標記和數(shù)據(jù)主體的 crc32 加密碼术陶。
5.4 塊名稱約定
通過名稱約定,使得 PNG 解碼器在不能識別當前塊的用途時煤痕,也能通過名稱來獲取相關信息梧宫。
塊的名稱有四位:
第一位表示輔助接谨,小寫表示這個塊是輔助塊,大寫表示這個塊是關鍵塊塘匣。
第二位表示私有脓豪,小寫表示這個塊是私有的,而非國際標準定義的忌卤,大小表示前述 18 種塊類型扫夜。
第三位是保留位,小寫表示這個塊是被拋棄的驰徊,大寫表示可以使用笤闯。(用于約定將來的擴展)
第四位表示復制安全性,也就是 PNG 編輯器在編輯圖片的時候辣垒,如果遇到不安全的數(shù)據(jù)塊望侈,就不會完全的復制,而是有選擇的勋桶,大寫表示 PNG 編輯器可以完全的復制脱衙,而不需要擔心任何問題。
5.5 循環(huán)冗余碼算法
具體參考 crc32 算法
5.6 塊排序
因為 PNG 圖像是可以流式讀取的例驹,也就是說不需要讀到文件尾捐韩,就可以在 PNG 瀏覽器里預覽了。
所以有些東西需要在讀取圖像內容之前準備好鹃锈,比如索引調色板荤胁。
塊名稱 | 允許多個 | 順序限制 |
---|---|---|
IHDR | 必須第一位 | |
PLTE | Shall be first | |
IDAT | √ | 如果有多個,必須連續(xù)出現(xiàn) |
IEND | 必須最后 | |
cHRM | PLTE 之前 | |
gAMA | PLTE 之前 | |
iCCP | PLTE 之前屎债,與 sRGB 互斥 | |
sBIT | PLTE 之前 | |
sRGB | PLTE 之前仅政,與 iCCP 互斥 | |
bKGD | PLTE 和 IDAT 之間 | |
hIST | PLTE 和 IDAT 之間 | |
tRNS | PLTE 和 IDAT 之間 | |
pHYs | IDAT 之前 | |
sPLT | √ | IDAT 之前 |
tIME | 無 | |
iTXt | √ | 無 |
tEXt | √ | 無 |
zTXt | √ | 無 |
6 標準圖像與 PNG 圖像的變換
好像在 4.3 章節(jié)寫過了?
6.1 顏色類型和值
我們在 4.4 章寫過盆驹,有五種顏色類型:
顏色類型 | 值 | 通道數(shù) |
---|---|---|
灰度 | 0 | 1 |
真彩 | 2 | 3 |
索引 | 3 | 1 |
灰度帶透明 | 4 | 2 |
真彩帶透明 | 6 | 4 |
顏色類型記錄在 IHDR 里圆丹。
灰度模式下,亮度取決于 gAMA躯喇、sRGB辫封、iCCP,如果沒有這些廉丽,則取決于機器倦微。
顏色樣本不一定和光強成正比,可以通過設置 gAMA 來調節(jié)正压。
值的計算方法如下:初始為 0欣福,使用了調色板加上1,使用了真彩加上2蔑匣,使用了透明通道加上4劣欢∽厮校灰度下是無法使用索引。
6.2 透明的表示方法
有四種方式表示透明:使用透明通道凿将、使用 tRNS 塊設置透明顏色信息校套、索引中在 tRNS 設置 alpha 表、不使用透明通道也不使用 tRNS 表示完全不透明牧抵。
透明通道的樣本深度是 8 和 16笛匙,透明通道保存在像素之中, 表示完全透明犀变,
表示完全不透明妹孙。透明度用于圖像前景色和背景色的復合。
一些普通的圖片不包含透明度获枝,甚至已經(jīng)把像素值乘以透明度蠢正,提前做好了以黑色為背景的復合步驟;但是 PNG 不這么做省店。
7 PNG 圖像編碼
7.1 整形和字節(jié)順序
整形(int)是多位字節(jié)嚣崭,short 是兩個,int 是四個懦傍,long 是八個雹舀。
PNG 使用的是網(wǎng)絡字節(jié)順序,MSB 在高位粗俱,LSB 在低位说榆。
MSB | B2 | B1 | LSB | |
---|---|---|---|---|
short | 15 14 13 12 11 10 9 | - | - | 8 7 6 5 4 3 2 1 0 |
int | 31 30 29 28 27 26 25 24 | 23 22 21 20 19 18 17 16 | 15 14 13 12 11 10 9 | 8 7 6 5 4 3 2 1 0 |
7.2 掃描線
也就是每個 PNG 圖像的行,緊湊排列各個像素寸认。
深度少于 8 時签财,掃描線結尾可能是湊不齊字節(jié),這些不使用的字節(jié)不進行處理偏塞。
7.3 濾波器
濾波器可以提高壓縮數(shù)據(jù)的壓縮性荠卷,而且是可逆的。PNG 允許過濾掃描線數(shù)據(jù)烛愧,換句話說就是可以不進行濾波。
過濾后的字節(jié)序列與過濾前的相同掂碱,但是根據(jù)不同的濾波類型怜姿,會在最前面添加一個字節(jié)的標記。如果沒有增加長度就表明沒有過濾過疼燥。具體過濾方法沧卢,在后面解釋。
8 交錯模式和 Pass 提取
交錯模式可以提高 CRT 顯示器上網(wǎng)絡圖片的加載速度(換句話醉者,沒有網(wǎng)絡但狭,沒有 CRT 顯示器披诗,交錯模式就沒有用了)。
參考[4.5.2 Pass 提取](#4.5.2 Pass 提取)
介于 Adam7 的特點立磁,寬或者高小于5的圖像會缺少縮略圖(2在第五列呈队,3在第五行)。
9 過濾
9.1 過濾方法和濾波器類型
過濾的目的在于提高壓縮率唱歧。過濾的方法不是唯一的宪摧,交錯模式下,所有縮小圖都應該使用同一種方法的濾波器颅崩;非交錯模式下几于,只有一張圖,當然也是只有一種方法沿后。
這個標準里定義了一種 0 號方法沿彭,其他的編號為將來保留。0 號方法包含五種類型的濾波器尖滚,每個掃描線可以使用不同的濾波器類型喉刘。
PNG 規(guī)范不強制濾波器的類型,具體的選擇方法后面在說熔掺。
9.2 0 號方法
濾波器是針對字節(jié)的饱搏,和像素、通道置逻、深度無關推沸。只要給字節(jié)就能過濾。
這里是幾個參數(shù)的定義:
- x:將要被過濾的字節(jié)券坞;
- a:前一個像素中對應 x 的字節(jié)鬓催;比如 x 是一個像素的第 2 個字節(jié),a 就是前一個像素中第 2 個字節(jié)恨锚;如果一個像素只有一個字節(jié)宇驾,x 就是這個像素的唯一字節(jié),a 就是前一個像素的唯一字節(jié)猴伶,兩者緊挨著课舍;
- b:前一行像素中對應 x 的字節(jié);
- c:b 所在像素的前一個像素對應 x 的字節(jié)(也是對應 b 的字節(jié))他挎;
... c b ...
... a x ...
Org() 表示字節(jié)原始值筝尾;
Flt() 表示過濾后的值;
Rc() 表示重構的值办桨;
Paeth() 參見[9.4 濾波器類型4](#9.4 濾波器類型4)筹淫。
濾波器類型 | 名稱 | 過濾函數(shù) | 重建函數(shù) |
---|---|---|---|
0 | None | Flt(x) = Org(x) |
Rc(x) = Flt(x) |
1 | Sub | Flt(x) = Org(x) - Org(a) |
Rc(x) = Flt(x) + Rc(a) |
2 | Up | Flt(x) = Org(x) - Org(b) |
Rc(x) = Flt(x) + Rc(b) |
3 | Average | Flt(x) = Org(x) - floor((Org(a) + Org(b)) / 2) |
Rc(x) = Flt(x) + floor((Rc(a) + Rc(b)) / 2) |
4 | Paeth | Flt(x) = Org(x) - Paeth(Org(a), Org(b), Org(c)) |
Rc(x) = Flt(x) + Paeth(Rc(a), Rc(b), Rc(c)) |
如果沒有前一個像素,就用 0 代替呢撞。每個縮小圖的第一行沒有前一行损姜,也用 0 代替饰剥。
因為使用了過濾,重建時也要按照這個順序進行計算摧阅。
濾波器的輸入輸出值都是無符號字節(jié)汰蓉。
9.3 濾波器類型3
0、1逸尖、2 三種類型的濾波器都很簡單古沥,隔列/隔行相減就是。
但是第三種類型娇跟,Flt(x) = Org(x) - floor((Org(a) + Org(b)) / 2)
中岩齿,Org(a) + Org(b)
存在溢出的情況,不能使用 byte 運算苞俘,應該是 short 或者更多位盹沈,當然也有右移的算法。
9.4 濾波器類型4
Paeth 算法先計算三個相鄰像素(左吃谣、上乞封、左上)的線性值,選擇與計算值最接近的相鄰像素再次計算岗憋。注意緩存不要溢出肃晚。它的函數(shù)如下:
def Paeth(a, b, c):
p = a + b - c
pa = abs(p - a)
pb = abs(p - b)
pc = abs(p - c)
if pa <= pb and pa <= pc:
Pr = a
elif pb <= pc:
Pr = b
else:
Pr = c
return Pr
def Paeth(a, b, c):
p = a + b - c
p = {abs(p - a): a, abs(p - b): b, abs(p - c): c}
return p[sorted(p.keys())[0]]
10 壓縮
與上面的過濾一樣,默認的就是 0 號方法仔戈。這兩個在 IHDR 里面有標記关串。
當然,這里用的是 zlib 的壓縮监徘,使用默認的等級為 8晋修,壓縮字節(jié)不超過 32768。
import zlib
zlib.compress(data=..., level=8)
壓縮塊的內容 | 字節(jié)數(shù) |
---|---|
zlib 壓縮標志 | 1 |
附加校驗標志 | 1 |
壓縮數(shù)據(jù)塊 | n |
校驗值 | 4 |
這個校驗值和 PNG 塊的校驗值不一樣凰盔,兩者不能混同墓卦。
多個過濾后的行,會被打包壓縮成一個 zlib 數(shù)據(jù)流户敬,并放到多個 PNG 塊里落剪,多個 PNG 塊解開得到的是一個 zlib 數(shù)據(jù)流。
當然尿庐,這個還涉及到異步讀取著榴。zlib 數(shù)據(jù)流本身就是可以中斷的,即使中斷屁倔,排列較前的數(shù)據(jù)還是可以讀取出來的,這樣才有交錯模式的解讀暮胧,所以針對上面的 python 方法锐借,有如下改進:
import zlib
decompress = zlib.decompressobj()
for chunk_type, chunk_data, chunk_crc in IDATs:
unzip_data = decompress.decompress(chunk_data)
handle(unzip_data)
unzip_data = decompress.flush()
handle(unzip_data)
構建一個持續(xù)的讀取问麸,邊讀取邊解析。
11 塊的介紹
下面是 18 個 PNG 規(guī)范塊的介紹:
11.1 b'IHDR'
IHDR 是 PNG 數(shù)據(jù)流中的第一個塊钞翔。組成如下:
用途 | 字節(jié)長度 | 類型 |
---|---|---|
表示寬度 | 4 | unsigned int |
表示高度 | 4 | unsigned int |
表示深度 | 1 | 1严卖,2,4布轿,8哮笆,16 |
表示顏色類型 | 1 | 0,2汰扭,3稠肘,4,6 |
壓縮方法萝毛,這里只有 0 號方法 | 1 | 0 |
濾波方法项阴,這里只有 0 號方法 | 1 | 0 |
是否交錯 | 1 | bool |
所以 IHDR 塊長度是 13 不會改變。
11.2 b'PLTE'
用途 | 字節(jié)長度 | 類型 |
---|---|---|
紅色通道 | 1 | unsigned byte |
綠色通道 | 1 | unsigned byte |
藍色通道 | 1 | unsigned byte |
... | ... | ... |
調色板是一個二維數(shù)組笆包,可以看作 Array[n][3]环揽,用索引 n 來表示顏色。
因此庵佣,n 不會超過 256歉胶,PLTE 塊的長度也是 3 的倍數(shù)。
調色板無論如何都是 8 位深度巴粪,即使圖像是 1耙箍、2、4 的深度蒸健,調色板也還是 8勘高。
11.3 b'IDAT'
所有的 IDAT 塊合起來是一個 zlib 數(shù)據(jù)流,參考[10 壓縮](#10 壓縮)晶通。
11.4 b'IEND'
這段數(shù)據(jù)是空的璃氢,表示 PNG 數(shù)據(jù)流結束了。當然狮辽,這個塊損壞也不會有事一也。
11.5 b'tRNS'
這是表示透明度信息的塊,有三種構成:
灰度模式(凡是等于這個灰度的顏色被認做透明)
用途 | 字節(jié)長度 | 類型 |
---|---|---|
灰度樣本值 | 2 | unsigned short |
真彩模式(由這三個值表示一個透明色)
用途 | 字節(jié)長度 | 類型 |
---|---|---|
紅色樣本值 | 2 | unsigned short |
綠色樣本值 | 2 | unsigned short |
藍色樣本值 | 2 | unsigned short |
索引模式(索引模式下喉脖,tRNS 相當于一個 alpha 表椰苟,這個表和索引一樣大,對應表示索引的透明度)
用途 | 字節(jié)長度 | 類型 |
---|---|---|
表示調色板[0]的透明度 | 1 | unsigned short |
表示調色板[1]的透明度 | 1 | unsigned short |
... | ... | ... |
為什么灰度模式和真彩模式用 2 字節(jié)表示呢树叽,因為需要適配 16 位深度舆蝴,而索引模式深度永遠小于等于 8。
11.6 b'cHRM'
這個塊用于設置一個 CIE 色度空間。組成如下:
用途 | 字節(jié)長度 | 類型 |
---|---|---|
白點橫坐標 | 4 | unsigned int |
白點縱坐標 | 4 | unsigned int |
紅點橫坐標 | 4 | unsigned int |
紅點縱坐標 | 4 | unsigned int |
綠點橫坐標 | 4 | unsigned int |
綠點縱坐標 | 4 | unsigned int |
藍點橫坐標 | 4 | unsigned int |
藍點縱坐標 | 4 | unsigned int |
儲存值是實際值的 100000 倍洁仗。
CIE 色度空間是一個二維圖像层皱,它由紅綠藍白四個點構建一個近視三角形的包圍圈,來設置顏色的偏移程度赠潦。
11.7 b'gAMA'
這個塊只儲存了一個 unsigned int叫胖,同樣的它的值需要除以 100000,得到實際的 gamma 值她奥。
11.8 b'iCCP'
這個塊用于設置一個 ICC 描述瓮增。
用途 | 字節(jié)長度 | 類型 |
---|---|---|
ICC 描述的名稱 | 1-79 | char[] |
空字符 | 1 | 0 |
壓縮方法 | 1 | unsigned byte |
壓縮的描述信息 | n | compressed bytes |
11.9 b'sBIT'
PNG 只支持固定的深度,如果原圖深度不匹配哩俭,就會強制的放縮绷跑,但是原始信息會保留在這里,用于恢復原始圖像携茂。(所以一般不會用到你踩,為什么要把標準圖像轉化成非標準圖像呢)
針對不同通道數(shù),有不同的 sBIT 長度讳苦。
顏色類型 | 長度(字節(jié)) |
---|---|
灰度(0) | 1(灰度) |
真彩(2)索引(3) | 3(紅+綠+藍) |
灰度帶透明(4) | 2(灰度+透明) |
真彩帶透明(6) | 4(紅+綠+藍+透明) |
11.10 b'sRGB'
使用 sRGB 色彩空間带膜,這時候不能用 ICC 描述了。sRGB 只包含有一個 unsigned byte鸳谜,表示渲染意圖膝藕。
值的意義如下:
渲染意圖的值 | 含義 | 說明 |
---|---|---|
0 | 感知 | 犧牲色度準確度,對輸出設備的色域進行適配咐扭,用于照片芭挽。 |
1 | 相對色度 | 顏色匹配外觀的圖像,如標識蝗肪。 |
2 | 飽和 | 保留飽和度袜爪,適用于圖表和圖形。 |
3 | 絕對色度 | 保證圖像的絕對色度薛闪,用于不同設備的圖像的輸出辛馆。 |
使用 sRGB 時,推薦使用 gAMA豁延、cHRM昙篙,因為有些設備不支持 sRGB,這樣就可以兼容性使用诱咏。
11.11 b'tExt'
關鍵字 | 后續(xù)文字的含義 |
---|---|
Title | 標題 |
Author | 作者 |
Description | 對于圖片的描述 |
Copyright | 圖像版權 |
Creation Time | 文件創(chuàng)建時間 |
Software | 文件創(chuàng)建使用的軟件 |
Disclaimer | 法律免責申明 |
Warning | 關于內容的警告 |
Source | 拍攝圖像的設備 |
Comment | 其他建議 |
上面是一個文本信息會使用到的關鍵字苔可,關鍵字其實不是很關鍵,就是一個定義而已袋狞,可以自己改動焚辅。但是符合上述的是可以被圖像軟件標準讀取映屋。
tEXt 含有如下成分:
用途 | 字節(jié)長度 | 類型 |
---|---|---|
關鍵字 | 1-79 | char[] |
空字符 | 1 | 0 |
文本串 | n | char[] |
11.12 b'zTXt'
用途 | 字節(jié)長度 | 類型 |
---|---|---|
關鍵字 | 1-79 | char[] |
空字符 | 1 | 0 |
壓縮方法 | 1 | unsigned byte |
壓縮文本 | n | compressed data |
當然,這里的壓縮方法也是 0同蜻,用 zlib 解壓后面的數(shù)據(jù)
11.13 b'iTXt'
國際文本數(shù)據(jù)秧荆,有點高大上的感覺。
用途 | 字節(jié)長度 | 類型 |
---|---|---|
關鍵詞 | 1-79 | char[] |
空字符 | 1 | 0 |
壓縮標志 | 1 | bool |
壓縮方法(當然這里也是 0) | 1 | 0 |
用于標記語言種類 | 0字節(jié)或更多字節(jié)(字符串) | char[] |
空字符 | 1 | 0 |
翻譯的關鍵詞 | n | char[] |
空字符 | 1 | 0 |
文本 | n | char[] |
語言種類參見 RCF-3066埃仪、ISO 646、ISO 639陕赃。
11.14 b'bKGD'
背景色哦卵蛉。
顏色類型 | 長度(字節(jié)) |
---|---|
灰度(0)灰度帶透明(4) | 2 |
真彩(2)真彩帶透明(6) | 6(紅+綠+藍,每個通道么库。2 字節(jié)) |
索引(3) | 1(指向調色板順序) |
11.15 b'hIST'
直方圖給出了調色板中每種顏色的近似使用頻率傻丝。
如果 PNG 瀏覽器無法提供調色板里所有的顏色,那么直方圖可以輔助創(chuàng)建相似的調色板供使用诉儒。
當然葡缰,現(xiàn)在不存在不能提供完整的調色板的軟件了。
用途 | 字節(jié)長度 | 類型 |
---|---|---|
頻率 | 2 | unsigned short |
... | ... | ... |
11.16 b'pHYs'
這個塊用于表示像素在屏幕上的實際尺寸忱反。結構如下:
用途 | 字節(jié)長度 | 類型 |
---|---|---|
橫向每個單位的像素 | 4 | unsigned int |
縱向每個單位的像素 | 4 | unsigned int |
單位說明 | 1 | unsigned byte |
單位說明有兩個泛释,F(xiàn)alse 的時候表示這個塊只表示長寬的比,而不是真實值温算;True 的時候以米為單位怜校,即一個單位為一米,一米包含多少個像素注竿。
11.17 b'sPLT'
用途 | 字符長度 | 類型 |
---|---|---|
調色板名稱 | 1-79 | char[] |
空字符 | 1 | 0 |
樣本深度 | 1 | 1茄茁、2、4巩割、8裙顽、16 |
紅色通道 | 1-2 | unsigned byte/short |
植物通道 | 1-2 | unsigned byte/short |
藍色通道 | 1-2 | unsigned byte/short |
alpha | 1-2 | unsigned byte/short |
頻率 | 2 | unsigned short |
... | ... | ... |
具體的通道長度,由樣本深度決定宣谈,深度為 16 就是兩個愈犹,小于等于 8 就是一個。
調色板名稱是區(qū)分大小寫的蒲祈,并受到與關鍵字參數(shù)相同的限制甘萧。
在灰度PNG圖像中,每個目通常相等的紅色梆掸、綠色扬卷、藍色和藍色值,但這不是必需的酸钦。
每一個頻率值與圖像的像素的比例成正比怪得,不是實際頻率。
11.18 b'tIME'
用途 | 字節(jié)長度 | 類型 |
---|---|---|
表示年的數(shù)字 (用完整數(shù)字;如:1995 而不是 95) | 2 | unsigned short |
表示月的數(shù)字 (1-12) | 1 | unsigned byte |
表示日的數(shù)字 (1-31) | 1 | unsigned byte |
表示時的數(shù)字(0-23) | 1字節(jié) | unsigned byte |
表示分的數(shù)字(0-59) | 1字節(jié) | unsigned byte |
表示秒的數(shù)字(允許跳躍秒徒恋,0-60) | 1字節(jié) | unsigned byte |
這里用宇宙時間(Universal Time蚕断,UTC)
12 PNG 編碼和解碼
后面的公式太復雜了,算了入挣,直接上簡單的代碼吧
import struct
import zlib
import io
import itertools
HEADER = b'\x49\x48\x44\x52'
PALETTE = b'\x50\x4c\x54\x45'
DATA = b'\x49\x44\x41\x54'
END = b'\x49\x45\x4e\x44'
COLOR_TYPE_GRAY = 0
COLOR_TYPE_GRAY_ALPHA = 4
COLOR_TYPE_PALETTE = 3
COLOR_TYPE_RGB = 2
COLOR_TYPE_RGB_ALPHA = 6
def read_iterator(iter_data, size):
for _ in range(size):
try:
yield next(iter_data)
except StopIteration:
yield 0
# 這兩個函數(shù)是通過輸入長和寬亿乳,來分割當前圖像像素的
def adam7iter(width, height):
reduced_order = 0
reduced_seven = (
((height + 7) // 8, (width + 7) // 8),
((height + 7) // 8, (width + 3) // 8),
((height + 3) // 8, (width + 3) // 4),
((height + 3) // 4, (width + 1) // 4),
((height + 1) // 4, (width + 1) // 2),
((height + 1) // 2, (width + 0) // 2),
((height + 0) // 2, (width + 0) // 1)
)
for reduced_height, reduced_width in reduced_seven:
if reduced_height and reduced_width:
yield reduced_order
for _ in range(reduced_height):
yield reduced_width
reduced_order -= 1
def adam1iter(width, height):
yield 0
for _ in range(height):
yield width
adam7sub1 = (
0, 5, 3, 5, 1, 5, 3, 5,
6, 6, 6, 6, 6, 6, 6, 6,
4, 5, 4, 5, 4, 5, 4, 5,
6, 6, 6, 6, 6, 6, 6, 6,
2, 5, 3, 5, 2, 5, 3, 5,
6, 6, 6, 6, 6, 6, 6, 6,
4, 5, 4, 5, 4, 5, 4, 5,
6, 6, 6, 6, 6, 6, 6, 6,
)
def do_nothing(*args):
pass
def sub_reconstructor(previous_line, current_line, start_bytes):
for i, j in enumerate(range(start_bytes, len(current_line))):
a = current_line[i]
x = current_line[j]
current_line[j] = (x + a) & 0xff
def up_reconstructor(previous_line, current_line, start_bytes):
for i in range(len(current_line)):
x = current_line[i]
b = previous_line[i]
current_line[i] = (x + b) & 0xff
def average_reconstructor(previous_line, current_line, start_bytes):
for i, j in enumerate(range(len(current_line)), start=-start_bytes):
x = current_line[j]
if i < 0:
a = 0
else:
a = current_line[i]
b = previous_line[j]
current_line[j] = (x + ((a + b) >> 1)) & 0xff
def predictor_reconstructor(previous_line, current_line, start_bytes):
for i, j in enumerate(range(len(current_line)), start=-start_bytes):
x = current_line[j]
if i < 0:
pr = previous_line[j]
else:
a = current_line[i]
c = previous_line[i]
b = previous_line[j]
pa = abs(b - c)
pb = abs(a - c)
pc = abs(a + b - c - c)
if pa <= pb and pa <= pc:
pr = a
elif pb <= pc:
pr = b
else:
pr = c
current_line[j] = (x + pr) & 0xff
reconstruct_function = (do_nothing, sub_reconstructor, up_reconstructor,
average_reconstructor, predictor_reconstructor)
def un_filter_image_lines(image_lines, pixel_bytes):
""" 返回每行的過濾 """
previous_line = None
for line in image_lines:
filter_mode = line[0]
filter_line = line[1:]
reconstruct_function[filter_mode](
previous_line, filter_line, pixel_bytes
)
previous_line = filter_line
yield filter_line
def un_interlace(un_filter_images, width, height, pixel_bytes):
# 下面方法,把每個圖片的所有行當成一個一緯數(shù)組來看待径筏,也就有 7 個數(shù)組
reduced = tuple(itertools.chain(*m) for m in un_filter_images)
for h in range(height):
h_1 = (h % 8) << 3 # 偏移量
for w in range(width):
image_order = adam7sub1[h_1 + w % 8] # 當前圖像的編號
current = reduced[image_order] # 切換數(shù)組
for _ in range(pixel_bytes): # 從數(shù)組里提取多個字節(jié)
yield next(current)
def read_png(stream):
if not stream.read(8) == b'\x89PNG\r\n\x1a\n':
stream.seek(0, 0) # 重置數(shù)據(jù)流
return
# 讀取需要的數(shù)據(jù)
width = height = depth = color_type = interlace = pixel_bytes = \
palette = None
data = []
while True:
try:
length, mask = struct.unpack('!I4s', stream.read(8))
chunk = stream.read(length)
crc32 = struct.unpack('!I', stream.read(4))[0]
if zlib.crc32(mask + chunk) != crc32:
break
if mask == HEADER:
(width, height, depth, color_type, compress_method,
filter_method, interlace) = struct.unpack('!2I5B', chunk)
planes = (1, -1, 3, 1, 2, -1, 4)[color_type]
pixel_bytes = (depth * planes + 7) // 8
elif mask == PALETTE:
palette = tuple(
chunk[i: i + 3] for i in range(0, length, 3))
elif mask == DATA:
data.append(chunk)
elif mask == END:
break
except struct.error:
break
# LZ77 解壓
decompress_obj = zlib.decompressobj()
unzip_data = itertools.chain(
*(decompress_obj.decompress(chunk) for chunk in data),
decompress_obj.flush()
)
# 按行分割葛假,adam 表示 pass 提取算法得到的縮小圖的每行像素個數(shù)
adam = (adam7iter if interlace else adam1iter)(width, height)
reduced_images = tuple([] for _ in range(7 if interlace else 1)) # 容器
current_image = 0 # 不要這個變量也能正常運行
while True:
try:
line_bytes = next(adam)
if line_bytes > 0: # 大于 0 表示讀取長度
reduced_images[current_image].append(bytearray(
# 這是一個從迭代器中依次讀取數(shù)量個數(shù)的方法
# 下面的意思是從 unzip_data 里讀取一定數(shù)量的字節(jié)
# 這個字節(jié)長度是縮小圖每行的字節(jié)長度 +1,多出來的是濾波標記
read_iterator(unzip_data, line_bytes * pixel_bytes + 1))
)
else: # 小于等于 0 表示切換圖片滋恬,注意縮小圖是 7 張
current_image = abs(line_bytes)
except StopIteration:
break
# 濾波重構
un_filter_images = ( # 把多個圖片的打包在一起
# 把一個圖片的行打包在一起
tuple(un_filter_image_lines(image_lines, pixel_bytes))
for image_lines in reduced_images
)
# 數(shù)據(jù)回填
if interlace:
result = un_interlace(un_filter_images, width, height, pixel_bytes)
else:
result = itertools.chain(*next(un_filter_images)) # 這時只有一個圖片
if depth == 16: # 要記得放縮深度哦
result = (j for i, j in enumerate(result) if (i % 2))
elif palette:
result = itertools.chain(*(palette[i] for i in result))
return width, height, color_type, bytes(result)
def t_gray_to_rgb_alpha(data, alpha):
for i in data:
yield i
yield i
yield i
yield alpha
def t_gray_alpha_to_rgb_alpha(data, background):
while True:
try:
gray, alpha = next(data), next(data)
yield background + (gray - background) * alpha // 255
except StopIteration:
break
def t_palette_to_rgb(data, palette, alpha, background):
br, bg, bb = palette[background]
for m in data:
rr, rg, rb = palette[m]
ra = alpha[m]
yield br + (rr - br) * ra // 255
yield bg + (rg - bg) * ra // 255
yield bb + (rb - bb) * ra // 255
def t_palette_to_rgb_alpha(data, palette, alpha):
for m in data:
rr, rg, rb = palette[m]
yield rr
yield rg
yield rb
yield alpha[m]
def t_palette_to_rgb2(data, palette):
for i in data:
rr, rg, rb = palette[i]
yield rr
yield rg
yield rb
def t_rgb_to_rgb(data, alpha, background):
br, bg, bb = background
while True:
try:
rr, rg, rb = next(data), next(data), next(data)
yield br + (rr - br) * alpha // 255
yield bg + (rg - bg) * alpha // 255
yield bb + (rb - bb) * alpha // 255
except StopIteration:
break
def t_rgb_to_rgb_alpha(data, alpha):
while True:
try:
yield next(data)
yield next(data)
yield next(data)
yield alpha
except StopIteration:
break
def t_rgb_alpha_to_rgb(data, background):
br, bg, bb = background
while True:
try:
rr, rg, rb, ra = next(data), next(data), next(data), next(data)
yield br + (rr - br) * ra // 255
yield bg + (rg - bg) * ra // 255
yield bb + (rb - bb) * ra // 255
except StopIteration:
break
def read_png2(stream):
if not stream.read(8) == b'\x89PNG\r\n\x1a\n':
stream.seek(0, 0) # 重置數(shù)據(jù)流
return
# 讀取需要的數(shù)據(jù)
width = height = depth = color_type = interlace = pixel_bytes = \
palette = background = extra_alpha = None
data = []
while True:
try:
length, mask = struct.unpack('!I4s', stream.read(8))
chunk = stream.read(length)
crc32 = struct.unpack('!I', stream.read(4))[0]
if zlib.crc32(mask + chunk) != crc32:
break
if mask == HEADER:
(width, height, depth, color_type, compress_method,
filter_method, interlace) = struct.unpack('!2I5B', chunk)
planes = (1, -1, 3, 1, 2, -1, 4)[color_type]
pixel_bytes = (depth * planes + 7) // 8
elif mask == PALETTE:
palette = tuple(
chunk[i: i + 3] for i in range(0, length, 3))
elif mask == DATA:
data.append(chunk)
elif mask == END:
break
elif mask == b'tRNS': # 透明信息
if color_type == COLOR_TYPE_GRAY:
extra_alpha = chunk[1] # 只取一半
elif color_type == COLOR_TYPE_RGB: # 只取一半
extra_alpha = chunk[1], chunk[3], chunk[5]
if color_type == COLOR_TYPE_PALETTE:
# 這時候是一個 alpha table
length = len(chunk)
extra_alpha = tuple(
chunk[i] if i < length else 255 for i in range(256)
)
elif mask == b'bKGD':
if color_type in (COLOR_TYPE_GRAY, COLOR_TYPE_GRAY_ALPHA):
background = chunk[1]
elif color_type in (COLOR_TYPE_RGB, COLOR_TYPE_RGB_ALPHA):
background = chunk[1], chunk[3], chunk[5]
elif color_type == COLOR_TYPE_PALETTE:
background = ord(chunk) # 指向 palette 不過這里寫做代替
except struct.error:
break
# LZ77 解壓
decompress_obj = zlib.decompressobj()
unzip_data = itertools.chain(
*(decompress_obj.decompress(chunk) for chunk in data),
decompress_obj.flush()
)
# 按行分割聊训,adam 表示 pass 提取算法得到的縮小圖的每行像素個數(shù)
adam = (adam7iter if interlace else adam1iter)(width, height)
reduced_images = tuple([] for _ in range(7 if interlace else 1)) # 容器
current_image = 0 # 不要這個變量也能正常運行
while True:
try:
line_bytes = next(adam)
if line_bytes > 0: # 大于 0 表示讀取長度
reduced_images[current_image].append(bytearray(
# 這是一個從迭代器中依次讀取數(shù)量個數(shù)的方法
# 下面的意思是從 unzip_data 里讀取一定數(shù)量的字節(jié)
# 這個字節(jié)長度是縮小圖每行的字節(jié)長度 +1,多出來的是濾波標記
read_iterator(unzip_data, line_bytes * pixel_bytes + 1))
)
else: # 小于等于 0 表示切換圖片恢氯,注意縮小圖是 7 張
current_image = abs(line_bytes)
except StopIteration:
break
# 濾波重構
un_filter_images = ( # 把多個圖片的打包在一起
# 把一個圖片的行打包在一起
tuple(un_filter_image_lines(image_lines, pixel_bytes))
for image_lines in reduced_images
)
# 數(shù)據(jù)回填
if interlace:
result = un_interlace(un_filter_images, width, height, pixel_bytes)
else:
result = itertools.chain(*next(un_filter_images)) # 這時只有一個圖片
if depth == 16: # 要記得放縮深度哦
result = (j for i, j in enumerate(result) if (i % 2))
if color_type == COLOR_TYPE_GRAY:
if extra_alpha: # 如果有額外的 alpha 通道
if background: # 把額外通道合并到灰度通道上带斑,這比較簡單
mode = 'gray'
result = (background + (i - background) * extra_alpha // 255
for i in result)
else:
mode = 'alpha' # 為了把透明度全部體現(xiàn)
result = t_gray_to_rgb_alpha(result, extra_alpha)
else: # 不做任何處理
mode = 'gray'
elif color_type == COLOR_TYPE_GRAY_ALPHA:
# 因為已經(jīng)有了 alpha 通道,所以不關心 extra_alpha
mode = 'alpha'
result = t_gray_alpha_to_rgb_alpha(result, background or 0)
elif color_type == COLOR_TYPE_PALETTE:
if extra_alpha:
if background:
mode = 'rgb'
result = t_palette_to_rgb(
result, palette, extra_alpha, background
)
else:
mode = 'alpha'
result = t_palette_to_rgb_alpha(result, palette, extra_alpha)
else:
mode = 'rgb'
result = t_palette_to_rgb2(result, palette)
elif color_type == COLOR_TYPE_RGB:
if extra_alpha:
if background:
mode = 'rgb'
result = t_rgb_to_rgb(result, extra_alpha, background)
else:
mode = 'alpha'
result = t_rgb_to_rgb_alpha(result, extra_alpha)
else: # 不做任何處理
mode = 'rgb'
elif color_type == COLOR_TYPE_RGB_ALPHA:
if background:
mode = 'rgb'
result = t_rgb_alpha_to_rgb(result, background)
else:
mode = 'alpha'
else:
raise
return width, height, mode, bytes(result)
# 初版不支持額外的透明信息和背景色
read_png = read_png2
def write_png(width, height, mode, data):
if mode == 'light':
color_type = COLOR_TYPE_GRAY
row_bytes = width
elif mode == 'rgb':
color_type = COLOR_TYPE_RGB
row_bytes = width * 3
elif mode == 'alpha':
color_type = COLOR_TYPE_RGB_ALPHA
row_bytes = width * 4
else:
raise
header_data = struct.pack('!2I5B', width, height, 8, color_type, 0, 0, 0)
header_crc32 = struct.pack('!I', zlib.crc32(HEADER + header_data))
def meow():
iter_data = iter(data)
for _ in range(height):
yield 0
for _ in range(row_bytes):
yield next(iter_data)
filtered_image = bytes(meow())
zipped_image = zlib.compress(filtered_image)
zipped_length = struct.pack('!I', len(zipped_image))
zipped_crc32 = struct.pack('!I', zlib.crc32(DATA + zipped_image))
result = io.BytesIO()
result.write(b'\x89PNG\r\n\x1a\n')
result.write(b'\x00\x00\x00\r\x49\x48\x44\x52')
result.write(header_data)
result.write(header_crc32)
result.write(zipped_length)
result.write(DATA)
result.write(zipped_image)
result.write(zipped_crc32)
result.write(b'\x00\x00\x00\x00\x49\x45\x4e\x44\xaeB`\x82')
return result.getvalue()
def write_png2(width, height, mode, data, data_split=40503):
if mode == 'light':
color_type = COLOR_TYPE_GRAY
row_bytes = width
elif mode == 'rgb':
color_type = COLOR_TYPE_RGB
row_bytes = width * 3
elif mode == 'alpha':
color_type = COLOR_TYPE_RGB_ALPHA
row_bytes = width * 4
else:
raise
header_data = struct.pack('!2I5B', width, height, 8, color_type, 0, 0, 0)
header_crc32 = struct.pack('!I', zlib.crc32(HEADER + header_data))
filtered_image = io.BytesIO()
for i in range(0, len(data), row_bytes):
filtered_image.write(b'\x00')
filtered_image.write(data[i: i + row_bytes])
zipped_data = zlib.compress(filtered_image.getvalue())
split_data = (zipped_data[i: i + data_split]
for i in range(0, len(zipped_data), data_split))
result = io.BytesIO()
result.write(b'\x89PNG\r\n\x1a\n')
result.write(b'\x00\x00\x00\r\x49\x48\x44\x52')
result.write(header_data)
result.write(header_crc32)
for s_data in split_data:
s_length = struct.pack('!I', len(s_data))
s_crc32 = struct.pack('!I', zlib.crc32(DATA + s_data))
result.write(s_length)
result.write(DATA)
result.write(s_data)
result.write(s_crc32)
result.write(b'\x00\x00\x00\x00\x49\x45\x4e\x44\xaeB`\x82')
return result.getvalue()
# 初版不支持 256 * 256 以上的信息
write_png = write_png2