256色bmp图像还原记录:
通过分析bmp图片的格式,可以完成BMP图片的打开和保存
一、bmp格式:
典型的 BMP 图像文件由四部分组成:
1 . 位图文件头数据结构 ,它包含 BMP 图像文件的类型、显示内容等信息;
2 . 位图信息数据结构 ,它包含有 BMP 图像的宽、高、压缩方法,以及定义颜色等信息;
3. 调色板 ,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图( 24 位的 BMP )就不需要调色板;
4 . 位图数据 ,这部分的内容根据 BMP 位图使用的位数不同而不同,在 24 位图中直接使用 RGB ,而其他的小于 24 位的使用调色板中颜色索引值。
① BMP 文件头 (14 字节 )
BMP 文件头数据结构含有 BMP 文件的类型、文件大小和位图起始位置等信息 。 其结构定义如下 :
int bfType ; // 位图文件的类型,必须为 ' B '' M ' 两个字母 (0-1 字节 ) int bfSize ; // 位图文件的大小,以字节为单位 (2-5 字节 ) int bfReserved1 ; // 位图文件保留字,必须为 0(6-7 字节 ) int bfReserved2 ; // 位图文件保留字,必须为 0(8-9 字节 ) int bfOffBits ; // 位图数据的起始位置,以相对于位图 (10-13 字节 ) |
② 位图信息头 (40 字节 )
BMP 位图信息头数据用于说明位图的尺寸等信息。
int Size ; // 本结构所占用字节数 (14-17 字节 ) int image_width ; // 位图的宽度,以像素为单位 (18-21 字节 ) int image_heigh ; // 位图的高度,以像素为单位 (22-25 字节 ) int Planes ; // 目标设备的级别,必须为 1(26-27 字节 ) int biBitCount ; // 每个像素所需的位数,必须是或 1,4,8 24(// 真彩色 ) 之一 (28-29字节 ) int biCompression ; // 位图压缩类型,必须是 0( 不压缩 ), 1(BI_RLE8 压缩类型 ) 或// 2(BI_RLE4 压缩类型 ) 之一 (30-33 字节 ) int SizeImage ; // 位图 数据 的大小,以字节为单位 (34-37 字节 ) int biXPelsPerMeter ; // 位图水平分辨率,每米像素数 (38-41 字节 ) int biYPelsPerMeter ; // 位图垂直分辨率,每米像素数 (42-45 字节 ) int biClrUsed ; // 位图实际使用的颜色表中的颜色数 (46-49 字节 ) int biClrImportant ; // 位图显示过程中重要的颜色数 (50-53 字节 ) |
③ 颜色表
颜色表中 的个数有 biBitCount 来确定 。 当 biBitCount=1,4,8 时,分别有 2,16,256个颜色 ; 当 biBitCount=24 时,没有颜色表项。
在windows中每个颜色是 b g r a 四个字节保存,a代表透明度,如果是1位位图,那么颜色表一共站八个字节,如果是4位位图,颜色表站84个字节,如果是8位位图,需要表示256中颜色,每种颜色站四个字节,所以颜色表一共站1024个字节。
256色的时候,windows中,位图数据的大小就是0-255,代表着调色板中的数据,每次读八个为就是一个像素信息,其他位图类似。
④ 位图数据
位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右 , 扫描行之间是从下到上。位图的一个像素值所占的字节数 :
当 biBitCount=1 时, 8 个像素占 1 个字节 ;
当 biBitCount=4 时, 2 个像素占 1 个字节 ;
当 biBitCount=8 时, 1 个像素占 1 个字节 ;
当 biBitCount=24 时, 1 个像素占 3 个字节 ;
Windows 规定一个扫描行所占的字节数必须是 4 的倍数 ( 即以 long 为单位 ), 不足的以 0 填充,具体数据举例:
也就是说,写入图片一行的像素信息的时候,每一行的字节数都必须是4的倍数,不足的在后面补0,然后才又开始写入下一行的像素信息。
位图数据,其实就是在描述图片上每一个点的颜色,在windos中,是先写入该图片的最有一排像素点,从下往上,从左往右,没写完一排,如果写入的字节数不是4的倍数,就会补0,比如写入了3个字节,就不要再补一个字节的0,然后开始写下一排的数据。
对于单色位图,只需要一个位就可以表示其所有的颜色可能,黑白,对于16色位图,也就是有16中颜色的可能,那么需要四个位就可以表示16中可能,对于256色位图,那么需要8个位才能表示256中情况,对于真彩色,也就是表示所有的颜色,那么需要三个字节才可以表示所有情况(透明度不算),对于单色,16色,256色位图来说,位图数据读到的是调色板上的索引,通过这个索引在调试版中找到对应的颜色,然后显示在屏幕上。所以对于单色位图,他的调色板只有黑白两种颜色,16色位图,他的调色板有16种颜色,256色的调色板有256中颜色,如果是24位位图,他的位图信息,就是保存的真实颜色,每读三个字节就表示了一个像素点的颜色,不需要再进行调色板查找对应的颜色。
*******在还原的例子程序中我并没有还原成RGBA 还是按照BGRA的方式还原的 其它只有调整一下tt的位置就可以了
*******windows在保存的时候是先保存的最后一排的数据,从下往上从左往右保存的,还原时已经还原成正常的了.
*******先取图像中的一点(y*width+x)[y是高变量 x是宽变量]数据, 得到数据[永远都在0-255之间]来找调色板中的真实颜色数据.
用到这张256色的图片
#include<stdio.h>
#include<stdlib.h>
#include<GLUT/GLUT.h>
#include <string.h>
GLint imagewidth,imageheight;
unsigned char* PixelData;void decodeimages(const char* filename,GLint *w,GLint *h){FILE *fp;fp = fopen(filename, "r");if (fp == NULL) exit(EXIT_FAILURE);int width,height;fseek(fp, 0x12, SEEK_SET);fread(&width, sizeof(int), 1, fp);fread(&height, sizeof(int), 1, fp);*w = width;*h = height;fseek(fp, 0x36, SEEK_SET);//1024字节调色板unsigned char color[256][4];for (int i=0; i<256; i++) {fread(&color[i], 4, 1, fp);}fseek(fp, 0x436, SEEK_SET);unsigned char data[width*height];fread(&data, width*height, 1, fp);fclose(fp);PixelData = (unsigned char*)malloc(width*height*4);memset(PixelData, 0, width*height*4);unsigned char tt[4];int count = 0;if (height>0){//高度大于0图片是正的 小于0是从下到上从左到右还原for(int i = 0;i<height;i++){for (int j=0; j<width; j++) {//数据索引int index = i*width+j;//调色板索引int cindex = data[index];//图像存储是BRG模式 还原为RGBAtt[0] = color[cindex][2];tt[1] = color[cindex][1];tt[2] = color[cindex][0];tt[3] = color[cindex][3];memcpy(PixelData+count, tt, 4);count +=4;}}}else{for (int i=height-1; i>=0; i--) {for (int j=0; j<width; j++) {//数据索引int index = i*width+j;//调色板索引int cindex = data[index];//图像存储是BRG模式 还原为RGBAtt[0] = color[cindex][2];tt[1] = color[cindex][1];tt[2] = color[cindex][0];tt[3] = color[cindex][3];memcpy(PixelData+count, tt, 4);count +=4;}}}
}void disPlay(){glDrawPixels(imagewidth,imageheight, GL_RGBA, GL_UNSIGNED_BYTE, PixelData);glutSwapBuffers();
}int main(int argc, char* argv[]){char *filename = "003140.bmp";decodeimages(filename,&imagewidth,&imageheight);glutInit(&argc, argv);glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);glutPositionWindow(200, 200);glutInitWindowSize(imagewidth, imageheight);glutCreateWindow("还原BMP");glutDisplayFunc(&disPlay);glutMainLoop();free(PixelData);return EXIT_SUCCESS;
}
运行效果: