【算法】解释RLE8压缩的BMP文件
BMP文件是位图文件,它自带了一种很无用的压缩功能——Run-Length Encode压缩算法。BMP文件的结构是一个BITMAPFILEHEADER和一个BITMAPINFO,后面就是位图信息了。其中BITMAPINFO是一个BITMAPINFOHEADER和一个调色板。这几个结构体都定义在Windows.h里(可能并不是直接在里面定义的)
BITMAPINFOHEADER中的biCompression成员指定了这个位图是否经过压缩。它有四个数值:BI_RGB(没有压缩)、BI_RLE8(8位RLE压缩)、BI_RLE4(4位RLE压缩)、BI_BITFIELD(在BITMAPINFO的调色板位置指定了颜色值的红绿蓝的位域定义,这个不是“压缩”)。
如果BITMAPINFOHEADER.biCompression的值是BI_RLE8或BI_RLE4,那么BITMAPINFOHEADER.biSizeImage的值就是压缩过的全部位图数据的尺寸了。
它的压缩算法是这样的:
用非零字节表示重复的像素,比如03 55,相当于55 55 55,或者10 AA,那就是AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA。
而如果是零,那么紧接着后面的那个字节用于表示“特殊的情况”。如果是00 00,那么表示这是一行的结尾,如果是00 01,那是整个位图的结尾,而如果是00 02,那么紧接着后面的两个字节表示下一个要描述的像素的坐标偏移量(比如00 02 10 10,表示下一个要描述的像素的位置在相对当前像素(16,16)的位置)
如果紧接着后面的字节不是0、1、2,那么表示后面的字节是“没有经过压缩”的,这些字节应该是2字节对齐,不足的用00补全,比如00 05 12 34 56 78 9A 00,表示12 34 56 78 9A。
假设一个位图有如下的数据:
03 04 05 06 00 03 45 56 67 00 02 78 00 02 05 01
02 78 00 00 09 1E 00 01
那么它解压后应该是这样:
04 04 04 06 06 06 06 06 45 56 67 78 78 然后下一个像素的位置相对于当前是05 01
78 78 跳到下一行
1E 1E 1E 1E 1E 1E 1E 1E 1E 位图结束
用类似C语言的伪代码表示解压RLE8的过程是:unsigned x,y;
size_t i;
BITMAPFILEHEADER BMFH;
BITMAPINFOHEADER BMIF;
BYTE*pbImage;
int Escape=0;
int Repeat=0;
int Absolute=0;
BYTE NbToCopy,NbCopied;
int Delta=0;
//打开文件
fp=fopen(文件名,"rb");
//读取BMP文件头
fread(&BMFH,1,sizeof(BMFH),fp);
fread(&BMIF,1,sizeof(BMIF),fp);
//分配内存存储压缩后的数据
pbImage=(BYTE*)malloc(BMIF.biSizeImage);
//读取位图文件
fread(pbImage,1,BMIF.biSizeImage,fp);
x=0;y=0;
for(i=0;i<BMIF.biSizeImage;i++)
{
if(Escape)//如果当前字节是“额外信息”
{
if(Delta==1)//如果是要确定下一个像素的X位置
{
x+=(int)pbImage;//当前字节决定下一个像素的X位置
Delta=2;//确定下一个像素的Y位置
continue;
}
if(Delta==2)//如果是要确定下一个像素的Y位置
{
y+=(int)pbImage;//当前字节决定下一个像素的Y位置
Delta=0;//像素确定完毕
continue;
}
if(Absolute)//绝对模式,也就是复制没有经过压缩的数据
{
if(NbCopied<NbToCopy)//如果没有复制完数据
{
画像素(x++,y,pbImage);//x,y,颜色
NbCopied++;
}
else if(NbCopied & 1)//绝对模式下的原始数据必须是2字节对齐。
NbCopied++;
else//已经复制完并且对齐
Absolute=0;
continue;
}
//如果没有确定对这个字节的操作
switch(pbImage)
{
case 0://行结尾
x=0;
y++;
break;
case 1://位图结尾
goto EndOfBitmap;
case 2://确定下一个像素的位置
Delta=1;
break;
default://绝对模式
Absolute=1;
NbToCopy=pbImage;
NbCopied=0;
break;
}
continue;
}
if(Repeat)//如果上一个字节确定了这个字节的重复量
{
while(Repeat--)//重复画这个字节
画像素(x++,y,pbImage);//x,y,颜色
Repeat=0;
continue;
}
if(pbImage)//任何操作都没有确定
Repeat=(int)pbImage;
else
Escape=1;
}
EndOfBitmap:
//位图读取完毕或者用x86汇编表示就是这样:mov esi,源 ;源=RLE压缩数据
mov edi,目标 ;目标=解压后的位图数据
xor ecx,ecx
ParseRLE8: ;开始分析RLE压缩的数据
lodsb ;先读取一个字节
or al,al ;判断是否进入Escape
jz Escape
mov cl,al ;非Escape模式,重复写入下一个字节。
lodsb
rep stosb
jmp ParseRLE8 ;继续
Escape: ;Escape模式:
lodsb ;读取一个字节,判断操作
cmp al,0
jz EOL ;0=行结尾
cmp al,1
jz EOB ;1=位图结尾
cmp al,2
jz Delta ;2=设置下一个像素的偏移坐标
mov cl,al ;其它=绝对模式
shr cl,1
rep movsw ;先按照字复制
and al,1 ;然后复制剩余部分
jz .nomore
movsb
.nomore:
jmp ParseRLE8 ;继续
EOL: ;行结尾处理方式
mov eax,edi
xor edx,edx
sub eax,目标 ;取得当前总共写入的字节数
div dword[每行字节数];计算当前的行数
inc eax
mul dword[每行字节数];再计算下一行的字节偏移
add eax,目标 ;然后加上目标的地址,计算下一个像素的指针。
mov edi,eax
jmp ParseRLE8 ;继续
EOB: ;位图结尾
jmp 搞定
Delta: ;指定下一个像素的位置
xor eax,eax
lodsb
add edi,eax ;x偏移
lodsb ;y偏移
mul dword[每行字节数]
add edi,eax
jmp ParseRLE8 ;继续 这两种语言的处理逻辑完全不一样。C语言是基于当前读取的字节来判断,而汇编的则是直接根据需求读取要解压的内容然后直接写目标。
页:
[1]