希望通過本文的介紹,能給目前對lzw算法及其在gif圖像中的應用不太了解,但又渴望了解的人壹些啟發和幫助。只是為了引玉,希望園裏的兄弟們提出寶貴意見。
1的全稱是什麽?LZW?
倫佩爾-齊夫-韋爾奇(LZW)。
2.LZW的引入和壓縮原理是什麽?
LZW壓縮算法是壹種新穎的壓縮方法,由萊姆普爾-齊夫-韋爾奇創立,並以他們的名字命名。它采用了高級的字符串表壓縮,將每個第壹個字符串放在壹個字符串表中,並使用壹個數字來表示該字符串。壓縮後的文件只存儲數字,不存儲字符串,大大提高了圖像文件的壓縮效率。神奇的是,這個字符串表在壓縮或解壓縮時都能正確建立,壓縮或解壓縮後又被丟棄。
在LZW算法中,首先建立壹個字符串表,將每個第壹次出現的字符串放入字符串表中,用壹個數字表示,這個數字與字符串在字符串表中的位置有關,這個數字存儲在壹個壓縮文件中。如果該字符串再次出現,它可以被代表它的數字替換,這個數字存儲在文件中。壓縮後丟棄字符串表。例如,如果“print”字符串在壓縮時用266表示,那麽每當它再次出現時就用266表示,“print”字符串將存儲在字符串表中。當圖像解碼過程中遇到數字266時,可以從字符串表中找出用266表示的字符串“print”,解壓時可以根據壓縮數據重新生成字符串表。
3.在詳細介紹算法之前,先列出壹些與算法相關的概念和詞匯。
1)'字符':壹個字符,壹個基本的數據元素,在壹個普通的文本文件中占據1個單獨的字節,但是在壹個圖像中,它是壹個表示給定像素的顏色的索引值。
2)“CharStream”:數據文件中的字符流。
3)“前綴”:前綴。和這個詞的意思壹樣,代表壹個字符之前最直接的字符。前綴字符的長度可以是0,前綴和字符可以組成壹個字符串。
4)‘後綴’:後綴,是壹個字符。壹個字符串可以由(A,B)組成。a是前綴,b是後綴。當A的長度為0時,它代表根,Root。
5)‘Code:代碼,用來表示壹個字符串的位置編碼。
6)“條目”,壹個代碼和它所代表的字符串。
4.壓縮算法的簡單例子並不是要完全實現lzw算法,而是從最直觀的角度來看LZW算法的思想。
LZW壓縮的原始數據ABCCAABCDDAACCDB。
原始數據中只包含四個字符(A,B,C,D),這四個字符可以用壹個2-2位數來表示,0-A,1-B,2-C,3-D,從最直觀的角度來看,原始字符串中有重復的字符:ABCCAABCDDAACCDB,4表示AB,4。
5.5的適用範圍。LZW算法
為了區分代表壹個字符串的值(代碼)和原來的單個數據值(字符串),需要使它們的數值域不重合,用0-3來代表A-D,所以AB必須用大於3的數值來代替。再比如,原來的數值範圍可以用8位表示,所以認為原來的數值範圍是0 ~ 255,壓縮程序生成的標簽範圍不能是0 ~。我們只能從256開始,但這樣會超出8位表示的範圍,所以我們必須至少擴展壹位的數據位數,但這不是增加了1個字符所占用的空間嗎?但是,壹個字符可以代表幾個字符。比如255以前是8位,現在用256來表示254和255這兩個數字是值得的。從這個原理可以看出,LZW算法的適用範圍是原始數據串要有大量重復多次的子串,重復次數越多,壓縮效果越好。相反,越是糟糕,實際上可能越是增加而不是減少。
6.6中的特殊標記。LZW算法
隨著新字符串的發現,標簽將繼續增長。如果原始數據過大,生成的標簽表會越來越大。這時候操作這壹套會造成效率問題。如何避免這個問題?Gif使用lzw算法的方式是,當標簽集足夠大時,它顯然不會增加,所以只需從頭開始。在這個位置,插入了壹個標簽,意味著我重新開始構造字典,之前的標簽全部無效,開始使用新的。
這時,另壹個問題出現了。它有多大?這個標簽集的合適大小是多少?理論上,標簽集越大,壓縮比越高,但開銷也越高。壹般根據處理速度和內存空間來選擇。GIF規範規定12位,如果表達式範圍超過12位,就要推倒重來。為了提高壓縮比,GIF使用了可變字長。比如原來的數據是8位,那麽開始的時候先加1位,初始字長變成9位,然後開始加標簽。當標簽加到512時,也就是大於9是能表示的最大數據量時,意味著後面的標簽只能用10比特表示,所以從這裏開始,後面的字長就是10。以此類推,到2 12,也就是4096的時候,在這裏插壹個清號,從後面開始,從9位數開始。
GIF指定的CLEAR標誌的值是原始數據字長的最大值加上1。如果原始數據字長是8,那麽清零標誌是256,如果原始數據字長是4,那麽就是16。此外,GIF還指定了壹個結束標誌END,其值為CLEAR標誌加1。因為GIF中規定的位數是1(單色圖像)、4 (16彩色)和8 (256彩色),如果在1比特的情況下只擴展1比特來表示4個狀態,那麽加上壹個清除標誌和壹個結束標誌就會用完,所以1比特就會用完。在另外兩種情況下,初始字長分別為5位和9位。此處引用了壓力例程。
//
//基於“壓縮”的倫佩爾-齊夫壓縮。GIF修改者
//大衛·羅利·(mgardi@watdcsu.waterloo.edu)
//常規定義
靜態只讀int位= 12;
靜態只讀int HSIZE = 5003// 80%的入住率
// GIF圖像壓縮-修改的“壓縮”
//
//基於:compress.c文件壓縮ala IEEE計算機,6月1984。
//
//作者:斯潘塞·w·托馬斯(decvax!哈波。猶他-cs!猶他-gr!托馬斯)
//吉姆·麥基(decvax!麥克瓦克斯!吉姆)
//斯蒂夫·戴維斯(decvax!vax135!petsd!佩奧拉。srd)
// Ken Turkowski (decvax!decwrl!turtlevax!肯)
//詹姆斯·A·伍茲(decvax!ihnp4!艾米斯。頜)
// Joe Orost (decvax!vax135!petsd!喬)
int n _ bits//位數/代碼數
int maxbits = BITS//用戶可設置的最大位數/碼
int maxcode//給定n_bits的最大代碼
int maxmax code = 1 & lt;& lt比特;//永遠不要生成此代碼
int[]htab = new int[HSIZE];//這是壹個用於哈希的包。妳可以很快在裏面找到1把鑰匙。
int[]codetab = new int[HSIZE];
int hsize = HSIZE//用於動態調整表格大小
int free _ ent = 0;//第壹個未使用的條目
//塊壓縮參數——所有代碼用完之後,
//並且壓縮率改變,重新開始。
bool clear _ flg = false
//算法:在
//前綴代碼/下壹個字符組合。我們做了壹個Knuth的變體
//算法D(第3卷,秒。6.4)連同g .克諾特的相對-總理
//輔助探測器。在這裏,模塊化的第壹個探索是讓路
//到更快的異或操作。也使用進行塊壓縮
//自適應復位,從而在壓縮時清除代碼表
//比率減小,但是在表填充之後。可變長度輸出
//此時會重新調整代碼的大小,並生成壹個特殊的清除代碼
//用於解壓縮器。後期添加:根據以下內容構建表格
//文件大小可以顯著提高小文件的速度。請指導
//關於這個實現的問題交給ames!下巴。
int g _ init _ bits
int ClearCode
int EOFCode
//輸出
//
//輸出給定的代碼。
//輸入:
// code:壹個n_bits位的整數。如果== -1,那麽EOF。這假設
//那n _ bits = & ltwordsize - 1。
//輸出:
//將代碼輸出到文件。
//假設:
//字符長度為8位。
//算法:
//維護壹個二進制字符長的緩沖區(這樣8個代碼將
//恰好適合它)。使用VAX insv指令插入每個
//依次編碼。當緩沖區填滿時,清空它並重新開始。
int cur _ accum = 0;
int cur _ bits = 0;
int []掩碼=
{
0x0000,
0x0001,
0x0003,
0x0007,
0x000F,
0x001F,
0x003F,
0x007F,
0x00FF,
0x01FF,
0x03FF,
0x07FF,
0x0FFF,
0x1FFF,
0x3FFF,
0x7FFF,
0x ffff };
//到目前為止這個“數據包”中的字符數
int a _ count
//定義包累加器的存儲
byte[] accum =新字節[256];
// -
public LZWEncoder(int width,int height,byte[] pixels,int color_depth)
{
imgW =寬度;
imgH =身高;
pixAry =像素;
initCodeSize = Math。Max(2,color _ depth);
}
//在當前數據包的末尾添加壹個字符,如果是254
//字符,將數據包刷新到磁盤。
void Add(字節c,流輸出)
{
accum[a _ count++]= c;
if(a _ count & gt;= 254)
沖洗(流出);
}
//清除哈希表
//清除塊壓縮的表
void ClearTable(流輸出)
{
ResetCodeTable(hsize);
free _ ent = clear code+2;
clear _ flg = true
輸出(清除代碼、輸出);
}
//重置代碼表
//全部初始化為-1
void ResetCodeTable(int hsize)
{
for(int I = 0;我& lthsize++i)
htab =-1;
}
void Compress(int init_bits,Stream outs)
{
int fcode
int I/* = 0 */;
int c;
int ent
int disp
int hsize _ reg
int hshift
//設置全局變量:g_init_bits -初始位數
//原始數據的字長。在gif文件中,原始數據的字長可以是1(單色圖像)、4(16色)、8(256色)。
//開頭加1。
//但是當原始數據長度為1時,從3開始。
//所以原來的長度是1->;3,4->5,8->九
//?原始數據的字長是1,為什麽起始長度是3??
//如果+1=2,只能表示四種狀態,clearcode和endcode會用完。所以必須擴展到3。
g _ init _ bits = init _ bits
//設置必要的值
//需要加壹個明確的標記嗎?
//GIF為了提高壓縮比,采用了可變字長(VCL)。比如原始數據是8位,那麽從1位開始(8+1=9)。
//當標簽達到2 ^ 9 = 512,超過當前長度9所能表示的最大值時,那麽後面的標簽就必須用10位來表示。
//以此類推,當標簽達到2 12時,就不能再擴展了,因為最大值是12。妳需要在2 12 = 4096的位置插入壹個明碼,也就是說從現在開始,妳要從9位數重新開始。
clear _ flg = false
n _ bits = g _ init _ bits
//得到可以用n位數表示的最大值(在壹張gif圖片中,壹般是3,5,9開頭,所以maxcode壹般是7,365,438+0,565,438+065,438+0)。
max code = max code(n _ bits);
//表示我已經從這裏開始構造字典了,前面的標記都無效。
//開始使用新的標記。這個標簽集的合適大小是多少?據說理論上壓縮比越大壓縮比越高(個人感覺太大不壹定好)。
//但是,處理成本也成倍增長。
//gif規定clearcode的值是原始數據最大字長可以表示的值+1;例如,如果原始數據長度為8,那麽clear code = 1 < & lt;(9-1)=256
clear code = 1 & lt;& lt(init _ bits-1);
//結束標誌是clearcode+1。
eof code = clear code+1;
//取消到此結束。
free _ ent = clear code+2;
//清除數量
a _ count = 0;//清除數據包
//從圖像中獲取下壹個像素
ent = next pixel();
h shift = 0;
for(fcode = hsize;fcode & lt65536;fcode *= 2)
++ h shift;
//設置哈希代碼範圍
h shift = 8-h shift;//設置哈希代碼範圍界限
hsize _ reg = hsize
//清除存儲標簽的固定大小哈希表,相當於壹個字典。
ResetCodeTable(hsize _ reg);//清除哈希表
輸出(清除代碼、輸出);
outer_loop : while ((c = NextPixel())!= EOF)
{
fcode =(c & lt;& ltmaxbits)+ent;
I =(c & lt;& lt^耳鼻喉科;// xor哈希
//嘿嘿,小樣,又來了,我認識妳。
if (htab == fcode)
{
ent = codetab
繼續;
}
//這小子,初來乍到。
else if(htab & gt;= 0) //非空槽
{
disp = hsize _ reg-I;//二級哈希(在g .克諾特之後)
如果(i == 0)
disp = 1;
做
{
if((I-= disp)& lt;0)
I+= hsize _ reg;
if (htab == fcode)
{
ent = codetab
goto outer _ loop
}
} while(htab & gt;= 0);
}
輸出(ent,outs);
//由此可以看出,ent是前綴,當前正在處理的字符符號是後綴。
ent = c;
//判斷終止符是否超出當前位數所能表示的範圍。
if(free _ ent & lt;maxmaxcode)
{
//如果沒有超級
codetab = free _ ent++;//code-& gt;散列表
//在//哈希表中建立相應的索引。
htab = fcode
}
其他
//描述超出了目前可以表達的範圍。清空字典,重新開始。
可清除(出局);
}
//輸出最終代碼。
輸出(ent,outs);
輸出(EOFCode,outs);
}
// -
公共空編碼(流操作系統)
{
os。WriteByte(轉換。ToByte(init codesize));//寫入“初始代碼大小”字節
//這個圖像包含多少像素?
剩余= imgW * imgH//重置導航變量
//當前處理的像素索引
curPixel = 0;
Compress(initCodeSize + 1,OS);//壓縮並寫入像素數據
os。write byte(0);//寫塊終止符
}
//將數據包刷新到磁盤,並重置累加器
無效沖洗(流出)
{
if(a _ count & gt;0)
{
出局。WriteByte(轉換。ToByte(a _ count));
出局。Write(accum,0,a _ count);
a _ count = 0;
}
}
///& lt;總結& gt
///得到可以用n位數表示的最大值。
///& lt;/summary & gt;
///& lt;param name = " n _ bits " & gt位數,壹般n _ bits = 9
///& lt;returns & gt最大值,例如n_bits=8,則返回值為2 ^ 8-1 = 255
int MaxCode(int n_bits)
{
return(1 & lt;& ltn _ bits)-1;
}
// -
//返回圖像的下壹個像素
// -
///& lt;總結& gt
///從圖像中獲取下壹個像素
///& lt;/summary & gt;
///& lt;returns & gt& lt/returns & gt;
private int NextPixel()
{
//還有多少像素沒有處理?
//如果不是,返回結束標誌。
if(剩余== 0)
返回EOF
//否則,處理下壹個,並將未處理的像素數設置為-1。
-剩余;
//當前正在處理的像素
int temp = cur pixel+1;
//如果當前處理的像素在像素範圍內
if(temp & lt;pixAry。GetUpperBound( 0))
{
//下壹個像素
byte pix = pixAry[cur pixel++];
返回像素& amp0xff
}
返回0xff
}
///& lt;總結& gt
///將字輸出到輸出流
///& lt;/summary & gt;
///& lt;param name = " code " & gt要輸出的單詞
///& lt;param name = " outs " & gt輸出流
void輸出(int代碼,流輸出)
{
//獲取當前標誌位可以表示的最大標誌值。
當前累計& amp= masks[cur _ bits];
if(cur _ bits & gt;0)
cur _ accum | =(code & lt;& ltcur _ bits);
其他
//如果標誌位為0,則當前標簽為輸入流。
cur _ accum = code
//當前能量標誌的最大漢字長度(9-10-11-12-9-10。。。。。。。)
cur _ bits+= n _ bit;
//如果當前最大長度大於8
while(cur _ bits & gt;= 8)
{
//向流中輸出壹個字節。
Add((字節)(cur _ accum & amp0xff)、outs
//將當前標簽向右移動8位。
cur _ accum & gt& gt= 8;
cur _ bits-= 8;
}
//如果下壹個條目對於代碼大小來說太大,
//然後增加,如果可能的話。
if(free _ ent & gt;maxcode || clear_flg)
{
if (clear_flg)
{
max code = max code(n _ bits = g _ init _ bits);
clear _ flg = false
}
其他
{
++ n _ bits;
if (n_bits == maxbits)
maxcode = maxmaxcode
其他
max code = max code(n _ bits);
}
}
if (code == EOFCode)
{
//在EOF時,寫入緩沖區的其余部分。
while(cur _ bits & gt;0)
{
Add((字節)(cur _ accum & amp0xff)、outs
cur _ accum & gt& gt= 8;
cur _ bits-= 8;
}
沖洗(流出);
}
}
}
}