1. 程式人生 > >RLE壓縮演算法詳解

RLE壓縮演算法詳解

RLE壓縮演算法(下簡稱RLE演算法)的基本思路是把資料按照線性序列分成兩種情況:一種是連續的重複資料塊,另一種是連續的不重複資料塊。

RLE演算法的原理就是用一個表示塊數的屬性加上一個資料塊代表原來連續的若干塊資料,從而達到節省儲存空間的目的。一般RLE演算法都選擇資料塊的長度為 1 位元組,表示塊數的誠性也用1位元組表示,對於顏色數小於 256 色的影象檔案或文字檔案,塊長度選擇 1 位元組是比較合適的。

連續重複資料的處理

RLE 演算法有很多優化和改進的變種演算法,這些演算法對連續重複資料的處理方式基本上都是一樣的。對於連續重複出現的資料,RLE演算法一般用兩位元組表示原來連續的多位元組重複資料。我們用一個例子更直觀地說明 RLE 演算法對這種情況的處理,假如原始資料有 5 位元組的連續資料:

[data] [data] [data] [data] [data]

則壓縮後的資料就包含塊數和 [data] 兩位元組,其中 [data] 只儲存了一次,節省了儲存空間:

[5] [data]

需要注意的是,一般 RLE 演算法都採用插入一個長度屬性位元組儲存連續資料的重複次數,因此能夠表達的扱大值就是 255 位元組,如果連續的相同資料超過 255 位元組時,就從第 255 位元組處斷開,將第 256 位元組以及 256 位元組後面的數裾當成新的數椐處理。

隨著 RLE 演算法採用的優化方式不同,這個長度屬性位元組所表達的意義也不同,對於本節給出的這種優化演算法,長度屬性位元組的最高位被用來做一個標誌位,只有 7 位用來表示長度。

連續非重複資料的處理

對於連續的非重複資料,RLE 演算法有兩種處理方法:
  • 一種處理方法是將每個不重複的資料當作只重複一次的連續重複資料處理,在演算法實現上就和處理連續重複資料一樣;
  • 另一種處理方法是不對資料進行任何處理,直接將原始資料作為壓縮後的資料儲存。

假如有以下 5 位元組的連續非重複資料:

[datal] [data2] [data3] [data4] [data5]

按照第一種處理方法,最後的壓縮資料就如下所示:

[1][datal] [1][data2] [1][data3] [1][data4] [1][data5]

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

[data1] [data2] [data3] [data4] [data5]

如果採用第一種方式處理連續非重複資料,則存在一個致命的問題,對連續出現的不重複資料,會因為插入太多塊數屬性位元組而膨脹一倍,如果原始資料主要是隨機的非重複資料,則採用這種方式不僅不能起到壓縮資料的目的,反而起到惡化的作用。多數經過優化的 RLE 演算法都會選擇使用第二種方式處理連續非重複資料,但是這就引入了新問題,在 RLE 演算法解碼的時候,如何區分連續重複和非重複資料?

前面己經提到,如果把非重複資料當作獨立的單次重複資料處理,反而會造成資料膨脹,但是如果把連續非重複資料也當成一組資料整理考慮呢?這是一個優化的思路,首先,給連續重複資料和連續非重複資料都附加一個表示長度的屬性位元組,並利用這個長度屬性字 節的最高位來區分兩種情況。

長度屬性位元組的最高位如果是 1,則表示後面緊跟的是個重複資料,需要重複的次數由長度屬性位元組的低 7 位(最大值是 127)表示。長度屬性位元組的最高位如果是 0,則表示後面緊跟的是非重複資料,長度也由長度屬性位元組的低 7 位表示。

採用這種優化方式,壓縮後的資料非常有規律,兩種型別的資料都從長度屬性位元組開始,除了標誌位的不同,後跟的資料也不同。第一種情況後跟一個位元組的重複資料,第二種情況後跟的是若干個位元組的連續非重複資料。

演算法實現

首先介紹一下資料壓縮的編碼過程如何實現。釆用前面給出的優化方式,編碼演算法不僅要能夠識別連續重複資料和連續非重複資料兩種情況,還要能夠統計出兩種情況下資料塊的長度。

編碼演算法從資料的起始位置開始向後搜尋,如果發現後面是重複資料且重複次數超過 2,則設定連續重複資料的標誌並繼續向後查詢,直到找到第一個與之不相同的資料為止,將這個位置記為下次搜尋的起始位置,根據位置差計算重複次數,最後長度屬性位元組以及一個位元組的原始重複資料一起寫入壓縮資料;如果後面資料不是連續重複資料,則繼續向後搜尋查詢連續重複資料,直到發現連續重複的資料且重複次數大於 2 為止,然後設定不重複資料標誌,將新位置記為下次搜尋的起始位置,最後將長度屬性位元組寫入壓縮資料並將原始資料逐位元組複製到壓縮資料。然後從上一步標記的新的搜尋起始位開始,一直重複上面的過程,直到原始資料結束。
int Rle_Encode(unsigned char *inbuf, int inSize, unsigned char *outbuf, int onuBufSize)
{
    unsigned char *src = inbuf;
    int i;
    int encSize = 0;
    int srcLeft = inSize;
    while(srcLeft > 0)
    {
        int count = 0;
        if(IsRepetitionStart(src, srcLeft)) /*是否連續三個位元組資料相同? */
        {
            if ((encSize + 2) > onuBufSize) /* 輸出緩衝區空間不夠了 */
            {
                return -1;
            }
            count = GetRepetitionCount(src, srcLeft);
            outbuf[encSize++] = count | 0x80;
            outbuf[encSize++] = *src;
            src += count;
            srcLeft -= count;
        }
        else
        {
            count = GetNonRepetitionCount(src, srcLeft);
            if ((encSize + count + 1) > onuBufSize) /* 輸出緩衝區空間不夠了 */
            {
                return -1;
            }
            outbuf[encSize++] = count;
            for(i = 0; i < count; i++) /*逐個複製這些資料*/
            {
                outbuf[encSize++] = *src++;;
            }
            srcLeft -= count;
        }
    }
    return encSize;
}
Rle_Encode() 函式是 RLE 演算法的實現,它通過呼叫 IsRepetitionStart() 函式判斷從 src 開始的資料是否是連續重複資料:
  • 如果是連續重複資料,則呼叫 GetRepetitionCount() 函式計算出連續重複資料的長度,將長度屬性位元組的最高位罝 1 並向輸出緩衝區寫入一個位元組的重複資料。
  • 如果不是連續重複資料,則呼叫 GetNonRepetitionCount() 函式計算連續非重複資料的長度,將長度屬性位元組的極高位罝 0 並向輸出緩衝區複製連續的多個非重兌資料。

GetRepetitionCount() 函式和 GetNonRepetitionCount() 函式都比較簡單,此處就不列出程式碼了。

根據演算法要求,只有數裾重複出現兩次以上才算作連續重複資料,因此 IsRepetitionStart() 函式檢査連續的3位元組是否是相同的資料,如果是則判定為出現連續重複資料。之所以要求至少要 3 位元組的重複資料才判定為連續重複資料,是為了儘量優化對短重複資料間隔出現時的壓縮效率。

舉個例子,對於這樣的資料“AABCCD”,如果不採用這個策略,最終的壓縮資料應該是 [0x82][A][0x01][B][0x82][C][0x01][D],壓縮後資料長度是 8 位元組。如果採用這個策略,則上述資料就被認定為連續非重複資料局,最終被壓縮為 [0x06][A][A][B][C][C][D],壓縮後資料長度是 7 位元組,這樣的資料越長,效果越明顯。

解壓縮演算法相對比較簡單,因為兩種情況下的壓縮資料首部都是 1 位元組的長度屬性標識,只要根據這個標識判斷如何處理就可以了。首先從壓縮資料中取出 1 位元組的長度屬性標識,然後判斷是連續重複資料的標識還是連續非重複資料的標識:
  • 如果是連續重複資料,則將標識位元組後面的資料重複複製 n 份寫入輸出緩衝區;
  • 如果是連續非重複資料,則將標識位元組後面的 n 個數據複製到輸出緩衝區。n 的值是標識位元組與 0x3F 做與操作後得到,因為標識位元組低 7 位就是資料長度屬性。
int Rle_Decode(unsigned char *inbuf, int inSize, unsigned char *outbuf, int onuBufSize) {
    unsigned char *src = inbuf;
    int i;
    int decSize = 0;
    int count = 0;
    while(src < (inbuf + inSize))
    {
        unsigned char sign = *src++;
        int count = sign & 0x3F;
        if ((decSize + count) > onuBufSize) /* 輸出緩衝區空間不夠了 */
        {
            return -1;
        }
        if ((sign & 0x80) == 0x80) /* 連續重複資料標誌 */
        {
            for(i = 0; i < count; i++)
            {
                outbuf[decSize++] = *src;
            }
            src++;
        }
        else
        {
            for(i = 0; i < count; i++)
            {
                outbuf[decSize++] = *src++;
            }
        }
    }
    return decSize;
}
Rle_Decode() 函式是解壓縮演算法的實現程式碼,每組資料的第一位元組是長度標識位元組,其最高位是標識位,低 7 位是資料長度屬性,根據標識位分別進行處理即可。