bmp图像压缩算法详细解析.docx
《bmp图像压缩算法详细解析.docx》由会员分享,可在线阅读,更多相关《bmp图像压缩算法详细解析.docx(17页珍藏版)》请在冰豆网上搜索。
bmp图像压缩算法详细解析
问题:
将一张bmp图像的灰度值压缩存储到一个中间文件,然后利用中间文件还原这张图片。
初一看,这应该是两个程序吧,一个压缩程序一个解压程序。
那就先压缩好喽,恩,压缩...可是要怎么读取它的灰度值呀文件里不会只保存它的灰度值吧,点开属性,发现这是一张256*192的图片,如果图片文件里只有灰度值,那么大小应该是256*192B,而实际大小是50230字节。
。
可见还有其它信息,根据经验,应该还有一个对图像的描述信息吧,这样那些图像显示程序才能知道以怎样的方式去显示它,毕竟不是所有的bmp图片都是灰度图片。
额,只好求助XX了.............经过整理,我把bmp图像编码格式发到下面。
BMP文件被分成4个部分:
位图文件头(BitmapFileHeader)、位图信息(BitmapInfoHeader)、颜色表(ColorMap)和位图数据(即图像数据,DataBits或DataBody)
第1部分为位图文件头BITMAPFILEHEADER,是一个结构体类型,该结构的长度是固定的,为14个字节。
其定义如下:
typedefstructtagBITMAPFILEHEADER
{
WORDbfType;位图文件类型,必须是0x424D,即字符串“BM”
DWORDbfSize;位图文件大小,包括这14个字节。
WORDbfReserved1;Windows保留字,暂不用。
WORDbfReserved2;Windows保留字,暂不用。
DWORDbfOffBits;从文件头到实际的位图数据的偏移字节数
}BITMAPFILEHEADER,FAR*LPBITMAPFILEHEADER,*PBITMAPFILEHEADER;
第2部分为位图信息头BITMAPINFOHEADER,也是一个结构体类型的数据结构,该结构的长度也是固定的,为40个字节(WORD为无符号16位整数,DWORD为无符号32位整数,LONG为32位整数)。
其定义如下:
typedefstructtagBITMAPINFOHEADER
{
DWORDbiSize;本结构的长度,为40个字节。
LONGbiWidth;位图的宽度,以像素为单位。
LONGbiHeight;位图的高度,以像素为单位.
WORDbiPlanes;目标设备的级别,必须是1。
WORDbiBitCount每个像素所占的位数(bit),其值必须为1(黑白图像)、4(16色图)8(256色)、24(真彩色图),新的BMP格式支持32位色。
DWORDbiCompression;位图压缩类型,有效的值为BI_RGB(未经压缩)、BI_RLE8、BI_RLE4、BI_BITFILEDS(均为Windows定义常量)。
这里只讨论未经压缩的情况,即biCompression=BI_RGB。
DWORDbiSizeImage;实际的位图数据占用的字节数
LONGbiXPelsPerMeter;指定目标设备的水平分辨率,单位是像素/米。
LONGbiYPelsPerMeter;指定目标设备的垂直分辨率,单位是像素/米。
DWORDbiClrUsed;位图实际用到的颜色数,如果该值为零则用到的颜色数为2的biBitCount次幂。
DWORDbiClrImportant;位图显示过程中重要的颜色数,如果该值为零则认为所有的颜色都是重要的
}BITMAPINFOHEADER,FAR*LPBITMAPINFOHEADER,*PBITMAPINFOHEADER;
·
第3部分为颜色表。
颜色表实际上是一个RGBQUAD结构的数组,数组的长度由biClrUsed指定(如果该值为零,则由biBitCount指定,即2的biBitCount次幂个元素)。
RGBQUAD结构是一个结构体类型,占4个字节,其定义如下:
typedefstructtagRGBQUAD
{
BYTErgbBlue;该颜色的蓝色分量;
BYTErgbGreen;该颜色的绿色分量;
BYTErgbRed;该颜色的红色分量;
BYTErgbReserved;保留字节,暂不用。
}RGBQUAD;
有些位图需要颜色表;有些位图(如真彩色图)则不需要颜色表,颜色表的长度由BITMAPINFOHEADER结构中biBitCount分量决定。
对于biBitCount值为1的二值图像,每像素占1bit,图像中只有两种(如黑白)颜色,颜色表也就有2^1=2个表项,整个颜色表的大小为8个字节;对于biBitCount值为8的灰度图像,每像素占8bit,图像中有2^8=256种颜色,颜色表也就有256个表项,且每个表项的R、G、B分量相等,整个颜色表的大小为1024个字节;而对于biBitCount=24的真彩色图像,由于每像素3个字节中分别代表了R、G、B三分量的值,此时不需要颜色表,因此真彩色图的BITMAPINFOHEADER结构后面直接就是位图数据。
第4部分是位图数据,即图像数据,其紧跟在位图文件头、位图信息头和颜色表(如果有颜色表的话)之后,记录了图像的每一个像素值。
对于有颜色表的位图,位图数据就是该像素颜色在调色板中的索引值;对于真彩色图,位图数据就是实际的R、G、B值(三个分量的存储顺序是B、G、R)。
下面分别就2色、16色、256色和真彩色位图的位图数据进行说明:
—对于2色位图,用1位就可以表示该像素的颜色,所以1个字节能存储8个像素的颜色值。
—对于16色位图,用4位可以表示一个像素的颜色。
所以一个字节可以存储2个像素的颜色值。
—对于256色位图,1个字节刚好存储1个像素的颜色值。
—对于真彩色位图,3个字节才能表示1个像素的颜色值。
需要注意两点:
第一,Windows规定一个扫描行所占的字节数必须是4的倍数,不足4的倍数则要对其进行扩充。
假设图像的宽为biWidth个像素、每像素biBitCount个比特,其一个扫描行所占的真实字节数的计算公式如下:
DataSizePerLine=(biWidth*biBitCount/8+3)/4*4
那么,不压缩情况下位图数据的大小(BITMAPINFOHEADER结构中的biSizeImage成员)计算如下:
biSizeImage=DataSizePerLine*biHeight
第二,一般来说,BMP文件的数据是从图像的左下角开始逐行扫描图像的,即从下到上、从左到右,将图像的像素值一一记录下来,因此图像坐标零点在图像左下角。
好了,有了以上的知识后,可以开始压缩程序啦~恩,现在问题是怎么读取那两个结构体,大家都知道c语言的文件读取有两种方式,一个是按字节读取,一个是按位读取,那么用哪个呢只好又XX了................好了,XX好了:
二进制方式很简单,读文件时,会原封不动的读出文件的全部內容,写的時候,也是把內存缓冲区的內容原封不动的写到文件中。
而文本方式就不一样了,在读文件时,会将换行符号CRLF(0x0D0x0A)全部转换成单个的0x0A,并且当遇到结束符CTRLZ(0x1A)时,就认为文件已经结束。
相应的,写文件时,会将所有的0x0A换成0x0D0x0A。
所以,若使用文本方式打开二进制文件时,就很容易出现文件读不完整,或內容不对的错误。
即使是用文本方式打开文本文件,也要谨慎使用,比如复制文件,就不应该使用文本方式。
(更多类容请自己XX)
;
这里我们选择二进制的读入方式
fread(&h,sizeof(BITMAPFILEHEADER),1,fp);
以上以读取头部为例,其它的类似(注意要按顺序读取,不要弄反了)
好了,到此第一步已经完成了我们把灰度信息读取到了一个数组里,那开始压缩了,这个按书上那个程序就好,压完得到一个l[],b[],分别记录每一段的长度和那一段每个像素需要的位数,特别提醒,书上的程序有一个bug!
!
原文output函数里有一段是这样的:
,
for(intj=1;j<=m;j++){l[j]=l[s[j]];b[j]=b[s[j]];}
应该是
for(j=1;j<=m;j++){
l[j]=l[s[j]];
max=-1;
~
for(i=s[j-1]+1;i<=s[j];i++){)
好了,灰度值我们知道怎么压缩了,下面就开始压缩吧,头部,头部信息,调试板不变,直接写进文件,额不对,我们怎么知道压缩后的灰度值数组有多长还记得这个吗DWORDbiSizeImage;实际的位图数据占用的字节数,我们把压缩后的灰度值数组大小存进去,到时候解压的时候别忘了恢复哈。
压缩完了,下面开始解压,解压就是压缩的逆过程
同样的,我们用i指示当前要读取的字节,left指示当前字节剩余的可用位数,bit表示要读取的灰度值的位数,将结果存到temp里面
if(left<=bit){
$
temp+=((a[i++]<<(8-left))&0xff)>>(8-bit);
temp+=a[i]>>(8-(bit-left));
left=8-(bit-left);
}else{
temp+=((a[i]>>(left-bit)<<(8-bit))&0xff)
*
>>(8-bit);
left-=bit;
}
对运算符的优先级不了解的话还是老老实实加括号吧(同样的自己在草稿纸上演算一下)
至此,大功告成了。
将bmp四部分的信息按顺序写入文件就好了。
下面贴上可以运行的代码,写得有点乱,有时间重构下...
>
压缩程序
#include<>
#include<>
#include<>
#include<>
,
#definelmax256//压缩时每一段包含的最大像素数
#defineheader11//每一段需要11位来保存这一段的信息(8位用来表示段长度
//,3位用来表示每一像素占的位数)
intbmpWidth;//图像的宽
intbmpHeight;//图像的高
~
RGBQUAD*pColorTable;//颜色表指针
intbiBitCount;//图像类型,每像素位数
unsignedchar*pBmpBuf;//灰度值数组
intlineByte;//每一行的字节数
BITMAPFILEHEADERh;//头部
)
BITMAPINFOHEADERhead;//头部信息
inti,j;//循环要用到的遍历变量
int*s,*l,*b;//这三个数组的意义和书上是一样的
intm;//分割的段数
·
intle(inti)//log(i+1)向上取整
{
intk=1;
i=i/2;
while(i>0){
\
k++;
i=i/2;
}
returnk;
}
《
voidtraceback(intn,ints[],intl[])//和书上一样不解释了
{
if(n==0)
return;
traceback(n-l[n],s,l);
{
s[m++]=n-l[n];
}
voidcompress(unsignedcharp[],ints[],intl[],intb[])//这个也和书上一样
{
intn=bmpWidth*bmpHeight;
'
intbmax;
s[0]=0;
for(i=1;i<=n;i++){
b[i]=le(p[i]);
bmax=b[i];
<
s[i]=s[i-1]+bmax;
l[i]=1;
for(j=2;j<=i&&j<=lmax;j++){
if(bmax
bmax=b[i-j+1];
~
if(s[i]>s[i-j]+j*bmax){
s[i]=s[i-j]+j*bmax;
l[i]=j;
}
}
)
s[i]+=header;
}
}
voidcollect(unsignedcharp[],ints[],intl[],intb[])
{
)
intmax;
intn=bmpWidth*bmpHeight;
traceback(n,s,l);
s[m]=n;
for(j=1;j<=m;j++){
)
l[j]=l[s[j]];
max=-1;
for(i=s[j-1]+1;i<=s[j];i++){//特别注意这个地方,书上错了
if(le(p[i])>max)//b[i]应该取第i段中占用位数最大的那个
max=le(p[i]);
&
}
b[j]=max;
}
}
¥
voidstore(unsignedchar*a,int*j,int*left,intbit,inttemp)//将temp存到数组里
{
if(*left<=bit){
a[(*j)++]+=temp>>(bit-(*left));
a[(*j)]+=(temp<<(8+(*left)-bit))&0xff;
<
*left=8-(bit-(*left));
}else{
a[(*j)]+=(temp<<(*left-bit))&0xff;
*left-=bit;
}
[
}
voidcompressToFile(char*file,unsignedcharp[],intl[],intb[],intm)//压缩存到文件里
{
FILE*out=fopen(file,"wb");
(
intn=bmpWidth*bmpHeight,left,k=1,t;
unsignedchar*a=(unsignedchar*)malloc(n);
memset(a,0,n);
left=8;
j=0;
~
for(i=1;i<=m;i++){
store(a,&j,&left,8,l[i]-1);
store(a,&j,&left,3,b[i]-1);
t=k+l[i];
for(;k¥
store(a,&j,&left,b[i],p[k]);
}
}
=j+1;//修改文件大小,这个解压图像中要用到
,
fwrite(&h,sizeof(BITMAPFILEHEADER),1,out);
fwrite(&head,sizeof(BITMAPINFOHEADER),1,out);
fwrite(pColorTable,sizeof(RGBQUAD),256,out);
fwrite(a,1,j+1,out);
fclose(out);
~
}
unsignedchar*readBmp(char*file)//读取文件
{
]
unsignedchar*pBmpBuf;
FILE*fp=fopen(file,"rb");
fread(&h,sizeof(BITMAPFILEHEADER),1,fp);//获取图片头信息
fread(&head,sizeof(BITMAPINFOHEADER),1,fp);//定义位图信息头结构变量,读取位图信息头进内存,存放在变量head中
bmpWidth=;//获取图像宽、高、每像素所占位数等信息
@
bmpHeight=;
biBitCount=;
lineByte=(bmpWidth*biBitCount/8+3)/4*4;//定义变量,计算图像每行像素所占的字节数(必须是4的倍数)
if(biBitCount==8){
pColorTable=(RGBQUAD*)malloc(sizeof(RGBQUAD)*256);//申请颜色表所需要的空间,读颜色表进内存
】
fread(pColorTable,sizeof(RGBQUAD),256,fp);//灰度图像有颜色表,且颜色表表项为256
}
pBmpBuf=(unsignedchar*)malloc(sizeof(unsignedchar)*(lineByte//申请位图数据所需要的空间,读位图数据进内存
*bmpHeight+1));
fread(pBmpBuf+1,1,lineByte*bmpHeight,fp);//为了数组下标从1开始
,
fclose(fp);//关闭文件
returnpBmpBuf;
}
intmain()
^
{
char*file="";
pBmpBuf=readBmp(file);
s=(int*)malloc(sizeof(int)*(bmpWidth*bmpHeight+1));
l=(int*)malloc(sizeof(int)*(bmpWidth*bmpHeight+1));
<
b=(int*)malloc(sizeof(int)*(bmpWidth*bmpHeight+1));
compress(pBmpBuf,s,l,b);
collect(pBmpBuf,s,l,b);
compressToFile("",pBmpBuf,l,b,m);
return0;
\
}
解压程序
#include<>
#include<>
#include<>
…
#include<>
intbmpWidth;//图像的宽
intbmpHeight;//图像的高
RGBQUAD*pColorTable;//颜色表指针
[
intbiBitCount;//图像类型,每像素位数
unsignedchar*pBmpBuf;//要恢复灰度值数组
intlineByte;//每一行的字节数
BITMAPFILEHEADERh;//头部
BITMAPINFOHEADERhead;//头部信息
、
unsignedchar*a;//压缩的灰度值数组
voidload(unsignedchar*a,int*i,int*left,intbit,int*temp)//将temp存到数组里
{
if(*left<=bit){
!
*temp+=((a[(*i)++]<<(8-(*left)))&0xff)>>(8-bit);
*temp+=a[(*i)]>>(8-(bit-(*left)));
*left=8-(bit-(*left));
}else{
*temp+=((a[(*i)]>>((*left)-bit)<<(8-bit))&0xff)
—
>>(8-bit);
*left-=bit;
}
}
(
voidcraming()
{
FILE*ss=fopen("","wb");
intleft=8,bit,length,k;
inti=0;//解压前数组index
【
intj=0;//解压后数组index
while(i<{
length=0;
bit=0;
load(a,&i,&left,8,&length);
(
load(a,&i,&left,3,&bit);
length++;//因为压缩的时候有减1
bit++;
k=j;
for(;jload(a,&i,&left,bit,&pBmpBuf[j]);
}
}
=bmpWidth*bmpHeight;//恢复像素所占的空间
fwrite(&h,sizeof(BITMAPFILEHEADER),1,ss);
fwrite(&head,sizeof(BITMAPINFOHEADER),1,ss);
fwrite(pColorTable,sizeof(RGBQUAD),256,ss);
fwrite(pBmpBuf,1,,ss);
fclose(ss);
}
unsignedchar*readBmp(char*file)
{
unsignedchar*a;
FILE*fp=fopen(file,"rb");
fread(&h,sizeof(BITMAPFILEHEADER),1,fp);//获取图片头信息
fread(&head,sizeof(BITMAPINFOHEADER),1,fp);//定义位图信息头结构变量,读取位图信息头进内存,存放在变量head中
bmpWidth=;//获取图像宽、高、每像素所占位数等信息
bmpHeight=;
biBitCount=;
lineByte=(bmpWidth*biBitCount/8+3)/4*4;//定义变量,计算图像每行像素所占的字节数(必须是4的倍数)
if(biBitCount==8){
pColorTable=(RGBQUAD*)malloc(sizeof(RGBQUAD)*256);//申请颜色表所需要的空间,读颜色表进内存
fread(pColorTable,sizeof(RGBQUAD),256,fp);//灰度图像有颜色表,且颜色表表项为256
}
a=(unsignedchar*)malloc;
fread(a,1,,fp);
fclose(fp);//关闭文件
returna;
}
intmain()
{
char*file="";
a=readBmp(file);
pBmpBuf=(unsignedchar*)malloc(sizeof(unsignedchar)*lineByte
*bmpHeight);/