因为学校的作业要求用C语言实现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]
按照这种处理方法,最后的数据和原始数据一样:
[data1] [data2] [data3] [data4] [data5]
现在有一个问题。在 RLE 算法解码的时候,如何区分连续重复和非重复数据?
解决方法是把连续非重复数据也当成一组数据整体考虑。首先给连续重复数据和连续非重复数据都附加一个表示长度的属性字节,并利用这个长度属性字节的最高位来区分两种情况。
长度属性字节的最高位如果是 1,则表示后面紧跟的是个重复数据,需要重复的次数由长度属性字节的低 7 位(最大值是 127)表示。长度属性字节的最高位如果是 0,则表示后面紧跟的是非重复数据,长度也由长度属性字节的低 7 位表示。
采用这种优化方式,压缩后的数据非常有规律,两种类型的数据都从长度属性字节开始,除了标志位的不同,后跟的数据也不同。第一种情况后跟一个字节的重复数据,第二种情况后跟的是若干个字节的连续非重复数据。
釆用前面给出的优化方式,编码算法不仅要能够识别连续重复数据和连续非重复数据两种情况,还要能够统计出两种情况下数据块的长度。
编码算法从数据的起始位置开始向后搜索,如果发现后面是重复数据且重复次数超过 2,则设置连续重复数据的标志并继续向后查找,直到找到第一个与之不相同的数据为止,将这个位置记为下次搜索的起始位置,根据位置差计算重复次数,最后长度属性字节以及一个字节的原始重复数据一起写入压缩数据;如果后面数据不是连续重复数据,则继续向后搜索查找连续重复数据,直到发现连续重复的数据且重复次数大于 2 为止,然后设置不重复数据标志,将新位置记为下次搜索的起始位置,最后将长度属性字节写入压缩数据并将原始数据逐字节复制到压缩数据。然后从上一步标记的新的搜索起始位开始,一直重复上面的过程,直到原始数据结束。
Rle_Encode() 函数是 RLE 算法的实现
IsRepetitionStart() 函数判断从 src 开始的数据是否是连续重复数据。根据算法要求,只有数裾重复出现两次以上才算作连续重复数据,因此 IsRepetitionStart() 函数检査连续的3字节是否是相同的数据,如果是则判定为出现连续重复数据。之所以要求至少要 3 字节的重复数据才判定为连续重复数据,是为了尽量优化对短重复数据间隔出现时的压缩效率。
举个例子,对于这样的数据“AABCCD”,如果不采用这个策略,最终的压缩数据应该是 :
[0x82][A][0x01][B][0x82][C][0x01][D]
注:此时A重复次数为2,保存长度数据的字节数据为100000010,开头的1表示为重复数据块,转换为十六进制为[0x82]
压缩后数据长度是 8 字节。如果采用这个策略,则上述数据就被认定为连续非重复数据,会被压缩为:
[0x06][A][A][B][C][C][D]
压缩后数据长度是 7 字节,这样的数据越长,效果越明显。
如果是连续重复数据,则调用 GetRepetitionCount() 函数计算出连续重复数据的长度,将长度属性字节的最高位罝 1 并向输出缓冲区写入一个字节的重复数据。
如果不是连续重复数据,则调用 GetNonRepetitionCount() 函数计算连续非重复数据的长度,将长度属性字节的极高位罝 0 并向输出缓冲区复制连续的多个非重复数据。
int IsrepetitionStart(unsigned char *src,int srcLeft){
if(srcLeft<3){
return 0;
}
if((src[0]==src[1])&&(src[1]==src[2])){
return 1;
}
return 0;
}
int GetRepetitionCount(unsigned char *src,int srcLeft){
int repeatedbuf=src[0];
int length=1;
while(length<srcLeft&&length<0x7f&&src[length]==repeatedbuf){
length++;
}
return length;
}
int GetNonRepetitionCount(unsigned char *src,int srcLeft){
if(srcLeft<3){
return srcLeft;
}
int length=2;
int a=src[0],b=src[1];
while(length<srcLeft&&length<0x7f&&((a!=b)||(b!=src[length]))){
a=b;
b=src[length];
length++;
}
return length;
}
int Rle_Encode(unsigned char *inbuf,int inSize,unsigned char *outbuf,int onuBufSize)
{
unsigned char *src=inbuf; //src为输入指针
int i;
int encSize=0;
int srcLeft=inSize; //srcleft为剩余数组元素
while(srcLeft>0){ //srcleft=0时代表输入结束
int count=0;
if(IsrepetitionStart(src,srcLeft)){ //是否连续三个字节数据相同?
if((enSize+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((enSize+count+1)>onuBufSize){ //缓存数组设置太小了
return -1;
}
outbuf[encSize++]=count;
for(i=0;i<count;i++){ //逐个写入这些数据
outbuf[encSize++]=*src++;
}
srcLeft-=count;
}
}
return encSize;
}
Rle_Decode() 函数是解压缩算法的实现代码,每组数据的第一字节是长度标识字节,其最高位是标识位,低 7 位是数据长度属性,根据标识位分别进行处理即可。
因为两种情况下的压缩数据首部都是 1 字节的长度属性标识,只要根据这个标识判断如何处理就可以了。首先从压缩数据中取出 1 字节的长度属性标识,然后判断是连续重复数据的标识还是连续非重复数据的标识:
如果是连续重复数据,则将标识字节后面的数据重复复制 n 份写入输出缓冲区;
如果是连续非重复数据,则将标识字节后面的 n 个数据复制到输出缓冲区。n 的值是标识字节与 0x7F 做与操作后得到,因为标识字节低 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 & 0x7F;
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;
}