RLE行程長度壓縮算法

 RLE(Run Length Encoding)行程長度壓縮算法(也稱遊程長度壓縮算法),是最早出現、也是最簡單的無損數據壓縮算法。RLE算法的基本思路是把數據按照線性序列分成兩種情況:一種是連續的重複數據塊,另一種是連續的不重複數據塊。對於第一種情況,對連續的重複數據塊進行壓縮,壓縮方法就是用一個表示塊數的屬性加上一個數據塊代表原來連續的若干塊數據。對於第二種情況,RLE算法有兩種處理方法,一種處理方法是用和第一種情況一樣的方法處理連續的不重複數據塊,僅僅是表示塊數的屬性總是1;另一種處理方法是不對數據進行任何處理,直接將原始數據作爲壓縮後的數據。

        爲了更直觀的說明RLE算法,下面就用示例數據就對RLE算法進行演示。首先是第一種情況,原始數據有5個連續相同的數據塊組成:

 

[block] [block] [block] [block] [block]

 

則壓縮後的數據就是:

 

[5] [block]

 

接着是第二種情況,原始數據由連續的不重複數據塊組成:

 

[block1] [block2] [block3] [block4] [block5]

 

按照第一種處理方法,最後的壓縮數據就如以下情形:

 

[1][block1] [1][block2] [1][block3] [1][block4] [1][block5]

 

如果按照第二種處理方法,最後的數據和原始數據一樣:

 

[block1] [block2] [block3] [block4] [block5]

 

數據塊block的長度可以是任意長度,數據塊長度越長則連續重複的概率就越低,壓縮的優勢就體現不出來,因此,大多數RLE算法的實現都使用一個字節作爲數據塊長度。

        接下來本文就介紹幾種RLE算法的實現,首先是最簡單的一種算法實現,這種算法實現對連續的不重複字節採用和重複字節一樣的處理方法,就是在每個字節前增加一個值是1的連續塊數屬性(一個字節)。採用這種處理方法的首先好處是壓縮和解壓縮算法簡單,可以用相同的模式處理兩種情況的壓縮數據,缺點就是當原始數據重複率比較低時,壓縮後的數據長度會超過原始數據的長度,起不到壓縮的作用,最糟糕的情況就是所有數據塊沒有連續重複的情況,壓縮後的數據反而膨脹一倍。壓縮的過程是這樣的,線性掃描原始數據,如果某一個字節後面有重複的字節,則增加重複計數,然後繼續向後掃描,直到找到一個不重複的字節,然後將塊數和這個字節的數據依次寫入壓縮數據,然後從新的開始字節繼續掃描直到原書數據結束。算法中需要注意的一點就是塊數屬性是用一個字節存儲的,因此最大值就是255,當連續的相同數據超過255個字節時,就從第255個字節處斷開,將第256個字節以及256字節後面的數據當成新的數據處理。這種RLE壓縮算法的C語言實現如下:

 6 int Rle_Encode_N(unsigned char *inbuf, int inSize, unsigned char *outbuf, intonuBufSize)

 7 {

 8     unsigned char *src = inbuf;

 9     int i;

10     int encSize = 0;

11 

12     while(src < (inbuf + inSize))

13     {

14         if((encSize + 2) > onuBufSize) /*輸出緩衝區空間不夠了*/

15         {

16             return -1;

17         }

18         unsigned char value = *src++;

19         i = 1;

20         while((*src == value) && (< 255))

21         {

22             src++;

23             i++;

24         }

25         outbuf[encSize++] = i;

26         outbuf[encSize++] = value;

27     }

28 

29     return encSize;

30 }

 

        對於字符串“AAABBBBBCD”,用這種RLE算法Rle_Encode_N()函數壓縮後的數據就是:0x03,0x41,0x05,0x42,0x01,0x43,0x01,0x44共8個字節,比原始長度10個字節少了2個字節,實現了數據長度的壓縮。

        解壓縮的過程也很簡單,就是定爲到第一個塊數屬性字節位置,根據塊數屬性的值n,連續向解壓縮緩衝區還原n個原始數據,原始數據就是塊數屬性後面一個字節的數據,然後偏移到下一個塊數屬性字節位置繼續上述處理,直到壓縮數據結束。解壓縮算法的C語言實現如下:

32 int Rle_Decode_N(unsigned char *inbuf, int inSize, unsigned char *outbuf, intonuBufSize)

33 {

34     unsigned char *src = inbuf;

35     int i;

36     int decSize = 0;

37 

38     while(src < (inbuf + inSize))

39     {

40         int count = *src++;

41         if((decSize + count) > onuBufSize) /*輸出緩衝區空間不夠了*/

42         {

43             return -1;

44         }

45         unsigned char value = *src++;

46         for(= 0; i < count; i++)

47         {

48             outbuf[decSize++] = value;

49         }

50     }

51 

52     return decSize;

53 }

        這個最簡單的的RLE算法存在着一個致命問題,就是對連續出現的不重複數據,會因爲插入太多塊數屬性字節而膨脹,如果一段數據連續出現重複數據的情況很少,大多數是連續不重複的數據,則使用上面的算法會導致數據沒有被壓縮,反而增大了,在極端的情況下,會因爲插入的塊數屬性字節而導致數據膨脹一倍。針對這種情況,人們對算法進行了改進,改進的關鍵點就是對連續出現的不重複數據,不再簡單插入塊數屬性,而是將其直接作爲壓縮後的數據處理。這樣會遇到一個問題,就是解碼的過程中,如何判斷一個數據是塊數屬性數據還是正常的數據?方法就是對塊數屬性字節設置標誌位,將塊數屬性字節的高兩位作爲標誌位,當這兩個標誌位是連續的兩個1時,則判斷此字節數據是塊數屬性字節,它的剩下的6個位就是重複塊數。如果這兩個標誌位不是連續的1,則認爲這個字節是正常數據。現在的問題是,如果用戶數據中出現了與標誌位衝突的數據怎麼辦?其實沒有太好的方法,解決這個問題的方案就是插入值是1的塊數屬性字節。往好的方面想,這個改進對於數據不超過192(0xC2)的原始數據是非常有效的,著名的圖像文件格式PCX格式,就是使用了這種改進的壓縮算法,效果還是不錯的。下面就來看看一個改進的壓縮算法實現:

55 int Rle_Encode_P(unsigned char *inbuf, int inSize, unsigned char *outbuf, intonuBufSize)

56 {

57     unsigned char *src = inbuf;

58     int i;

59     int encSize = 0;

60 

61     while(src < (inbuf + inSize))

62     {

63         unsigned char value = *src++;

64         i = 1;

65         while((*src == value) && (< 63))

66         {

67             src++;

68             i++;

69         }

70 

71         if((encSize + i + 1) > onuBufSize) /*輸出緩衝區空間不夠了*/

72         {

73             return -1;

74         }

75         if(> 1)

76         {

77             outbuf[encSize++] = i | 0xC0;

78             outbuf[encSize++] = value;

79         }

80         else

81         {

82             if((value & 0xC0) == 0xC0)

83             {

84                 outbuf[encSize++] = 0xC1;

85             }

86             outbuf[encSize++] = value;

87         }

88     }

89 

90     return encSize;

91 }

        對於字符串“AAABBBBBCD”,用這種RLE算法的Rle_Encode_P()函數壓縮後的數據就是:0xC3,0x41,0xC5,0x42,0x43,0x44共6個字節,比原始長度10個字節少了4個字節,對於原始數據都小於192的數據能夠有效地抑制因插入塊數屬性過多導致的數據膨脹。

        使用這種算法解壓縮,需要判斷當前數據是否有塊屬性標誌,如果有則從低6位bit中去到重複數據的塊數n,然後將下一個字節的數據重複複製n次。如果當前數據沒有塊屬性標誌,則直接使用當前數據,具體實現的C代碼見Rle_Decode_P()函數:

 93 int Rle_Decode_P(unsigned char *inbuf, int inSize, unsigned char *outbuf, intonuBufSize)

 94 {

 95     unsigned char *src = inbuf;

 96     int i;

 97     int decSize = 0;

 98     int count = 0;

 99 

100     while(src < (inbuf + inSize))

101     {

102         unsigned char value = *src++;

103         int count = 1;

104         if((value & 0xC0) == 0xC0) /*是否有塊屬性標誌*/

105         {

106             count = value & 0x3F; /*低位是count*/

107             value = *src++;

108         }

109         else

110         {

111             count = 1;

112         }

113         if((decSize + count) > onuBufSize) /*輸出緩衝區空間不夠了*/

114         {

115             return -1;

116         }

117         for(= 0; i < count; i++)

118         {

119             outbuf[decSize++] = value;

120         }

121     }

122 

123     return decSize;

124 }

        上述優化後的RLE算法,在原始數據普遍大於192(0xC0)的情況下,其優化效果相對於優化前的算法沒有明顯改善。原因在於,原始的RLE算法和改進後的RLE算法對於連續出現的不重複數據的處理方式都是一個一個處理的,沒有把不重複數據作爲一個整體進行處理。現在考慮再對原始的RLE算法進行優化,主要優化思想就是對連續的不重複數據進行整理處理,用一個和處理連續重複數據一樣的標誌,標識後面的數據是長度爲n的連續不重複數據。這樣的標誌字節就相當於是數據塊的塊頭部分,描述後面跟的數據類型以及數據長度。由於標誌是始終存在於數據塊的前面,因此就不需要區分標誌字節和原始數據,也就是說,第一個改進算法中用“高兩位是連續的1”的方式區分標誌字節和數據都是沒有必要的,唯一需要區分的是後面跟的數據類型。區分的方法就是對標誌字節的8個bit進行分工,用高位一個bit表示後面跟的數據類型,如果這個bit是1則表示後面跟的是連續重複的數據,如果這個bit是0則表示後面跟的是連續不重複的數據。標誌字節的低7位bit存儲一個數字(最大值是127),對於連續重複數據,這個數字表示需要重複的次數,對於連續不重複數據,這個數字表示連續不重複數據塊的長度。需要注意的是,只有重複次數超過2的數據才被認爲是連續重複數據,因爲如果數據的重複次數是2,壓縮後加上標誌字節後總的長度沒有變化,因此沒有必要處理。

        下面根據上述優化思想進行算法設計,首先是壓縮算法。在這種情況下,壓縮算法就比前兩種RLE壓縮算法複雜一些,就是要能識別連續的重複數據和連續的不重複數據。首先設置搜索起始位置,算法開始時這個搜索起始位置就是原始數據的第一個字節。每次搜索就是從起始位置開始向後搜索比較數據,根據搜索比較結果,一種情況就是後面數據重複且數據長度超過2,則設置連續重複數據的標誌,然後繼續向後查找,直到找到第一個與之不相同的數據爲止,將這個位置記爲下次搜索的起始位置,根據位置差計算重複次數,連重複標誌和重複次數以及原始數據一起寫入壓縮數據;另一種情況是後面的數據都沒有連續重複的,則繼續向後查找,直到找到連續重複的數據,然後設置不重複數據標誌,將新位置記爲下次搜索的起始位置,最後將標誌字節寫入壓縮數據並將原始數據複製到壓縮數據。從新的搜索起始位置重複上面的過程,直到原始數據結束。函數Rle_Encode_O()就是上述算法的C語言實現(只有算法的主體部分):

185 int Rle_Encode_O(unsigned char *inbuf, int inSize, unsigned char *outbuf, intonuBufSize)

186 {

187     unsigned char *src = inbuf;

188     int i;

189     int encSize = 0;

190     int srcLeft = inSize;

191 

192     while(srcLeft > 0)

193     {

194         int count = 0;

195         if(IsRepetitionStart(src, srcLeft)) /*是否連續三個字節數據相同?*/

196         {

197             if((encSize + 2) > onuBufSize) /*輸出緩衝區空間不夠了*/

198             {

199                 return -1;

200             }

201             count = GetRepetitionCount(src, srcLeft);

202             outbuf[encSize++] = count | 0x80;

203             outbuf[encSize++] = *src;

204             src += count;

205             srcLeft -= count;

206         }

207         else

208         {

209             count = GetNonRepetitionCount(src, srcLeft);

210             if((encSize + count + 1) > onuBufSize) /*輸出緩衝區空間不夠了*/

211             {

212                 return -1;

213             }

214             outbuf[encSize++] = count;

215             for(= 0; i < count; i++) /*逐個複製這些數據*/

216             {

217                 outbuf[encSize++] = *src++;;

218             }

219             srcLeft -= count;

220         }

221     }

222     return encSize;

223 }

        現在用數據“AAABBBBBCABCDDD”檢驗上述算法,得到壓縮後的數據:0x83,0x41,0x85,0x42,0x04,0x43,0x41,0x42,0x43,0x83,0x44,原始數據長度是15字節,壓縮後是11字節,這種改進後的算法,原始數據越長,壓縮的效果就越明顯。

        這種改進方法的解壓縮算法就比較簡單了,因爲兩種情況下的數據的首部都有標誌,只要根據標誌判斷如何處理就可以了。首先從壓縮數據中取出一個字節的標誌字節,然後判斷是連續重複數據的標誌還是連續不重複數據的標誌,如果是連續重複數據,則將標誌字節後面的數據重複複製n份,;如果是連續不重複數據,則將連續複製標誌字節後面的n個數據。n的值是標誌字節與0x3F做與操作後得到,因爲標誌字節的低7位bit就是數據塊數屬性。

        改進的解壓縮算法如下:

225 int Rle_Decode_O(unsigned char *inbuf, int inSize, unsigned char *outbuf, intonuBufSize)

226 {

227     unsigned char *src = inbuf;

228     int i;

229     int decSize = 0;

230     int count = 0;

231 

232     while(src < (inbuf + inSize))

233     {

234         unsigned char sign = *src++;

235         int count = sign & 0x3F;

236         if((decSize + count) > onuBufSize) /*輸出緩衝區空間不夠了*/

237         {

238             return -1;

239         }

240         if((sign & 0x80) == 0x80) /*連續重複數據標誌*/

241         {

242             for(= 0; i < count; i++)

243             {

244                 outbuf[decSize++] = *src;

245             }

246             src++;

247         }

248         else

249         {

250             for(= 0; i < count; i++)

251             {

252                 outbuf[decSize++] = *src++;

253             }

254         }

255     }

256 

257     return decSize;

258 }

用前面Rle_Encode_O()函數得到的壓縮數據進行驗證,結果正確。

        當今常用的壓縮軟件普遍使用從LZ77壓縮算法改進的LZSS壓縮算法。LZSS壓縮算法是一種基於字典模型的壓縮算法,算法依據是在文本流中詞彙或短語很可能會重複出現,同樣圖像流中圖像模式很也可能會重複出現。因此,在處理的過程中,構造一個編碼表,用較短的編碼代替重複序列,就可以有效地減少數據大小。與LZSS算法相比,RLE的壓縮率顯然沒有LZSS的高,但是RLE算法具有速度快的優勢,在LZSS算法出現之前,還是得到了很廣泛的應用,比如著名的圖像文件格式PCX就是使用了本文提到的第二種RLE算法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章