C语言读取BMP文件头、信息头及像素信息

来源:互联网 发布:淘宝卖家管理流程图 编辑:程序博客网 时间:2024/05/16 08:25

本学期的实验之一,看网上许多文章关于读取像素的处理并不是很完整,就放上来一个。

/***************************************************** * usage:<BMP file><r output><g output><b output><copy file> *****************************************************/#include <stdio.h>#include <stdlib.h>typedef unsigned short int WORD;typedef unsigned int DWORD;typedef unsigned char BYTE;typedef struct tagBITMAPFILEHEADER{    WORD bfType; // 位图文件的类型,必须为BM(0-1字节)    DWORD bfSize;  // 位图文件的大小,以字节为单位(2-5字节)    WORD bfReserved1;  // 位图文件保留字,必须为0(6-7字节)    WORD bfReserved2;  // 位图文件保留字,必须为0(8-9字节)    DWORD bfOffBits;  // 位图数据的起始位置(10-13字节),以相对于    // 位图文件头的偏移量表示,以字节为单位} BITMAPFILEHEADER;typedef struct tagBITMAPINFOHEADER{    DWORD biSize;  // 本结构所占用字节数(14-17字节)    DWORD biWidth;  // 位图的宽度,以像素为单位(18-21字节)    DWORD biHeight;  // 位图的高度,以像素为单位(22-25字节)    WORD biPlanes;  // 目标设备的级别,必须为1(26-27字节)    WORD biBitCount;  // 每个像素所需的位数,必须是1(双色),(28-29字节)    // 4(16色),8(256色)或24(真彩色)之一    DWORD biCompression;  // 位图压缩类型,必须是 0(不压缩),(30-33字节)    // 1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一    DWORD biSizeImage;  // 位图的大小,以字节为单位(34-37字节)    DWORD biXPelsPerMeter;  // 位图水平分辨率,每米像素数(38-41字节)    DWORD biYPelsPerMeter;  // 位图垂直分辨率,每米像素数(42-45字节)    DWORD biClrUsed;  // 位图实际使用的颜色表中的颜色数(46-49字节) 设为0的话,则说明使用所有调色板项,即2 biBitCount种颜色    DWORD biClrImportant; // 位图显示过程中重要的颜色数(50-53字节)}BITMAPINFOHEADER;typedef struct tagRGBQUAD{BYTE rgbBlue; // 蓝色的亮度(值范围为0-255)BYTE rgbGreen;  // 绿色的亮度(值范围为0-255)BYTE rgbRed;  // 红色的亮度(值范围为0-255)BYTE rgbReserved; // 保留字,必须为0} RGBQUAD;int main(int argc, char** argv){    FILE *result_r;// 输出的三个通道文件    FILE *result_g;    FILE *result_b;FILE *bmp;// 要读入的BMP文件    FILE *copy;// 要复制的BMP文件    long real_w;// 一个扫描行的实际占用字节数    long offset = 0;// 用于偏移每行结尾处的冗余数据    BITMAPFILEHEADER header;// BMP文件头BITMAPINFOHEADER info;// BMP信息头        if (argc!=6) {        printf("usage:<BMP file><r file><g file><b file><copy file>");        return -1;    }        if ((bmp=fopen(argv[1], "r"))==NULL) {        printf("the file of BMP isn't exist\n");        return -1;    }    copy=fopen(argv[5],"w");        fread(&header,14,1,bmp);    fwrite(&header, 14, 1, copy);        fread(&info,40,1,bmp);    fwrite(&info, 40, 1, copy);    printf("宽:%d 高:%d\n",info.biWidth,info.biHeight);printf("每个像素所需的位数: %d\n", info.biBitCount);printf("位图压缩类型: %d\n", info.biCompression);printf("位图的总颜色数: %d\n", info.biClrUsed);        result_r=fopen(argv[2],"w");    result_g=fopen(argv[3],"w");    result_b=fopen(argv[4],"w");        real_w=(info.biWidth*info.biBitCount+31)/32*4;    offset=real_w-(info.biWidth * info.biBitCount+7) / 8;    if(info.biBitCount==24) // 真彩色{int i=0;        for (;i<info.biHeight;i++){            int j=0;            for (; j<info.biWidth; j++) {                RGBQUAD rgb;                char string[16];                fread(&rgb.rgbBlue,1,1,bmp);                fwrite(&rgb.rgbBlue, 1, 1, copy);                fread(&rgb.rgbGreen,1,1,bmp);                fwrite(&rgb.rgbGreen, 1, 1, copy);                fread(&rgb.rgbRed,1,1,bmp);                fwrite(&rgb.rgbRed, 1, 1, copy);                rgb.rgbReserved=0;                sprintf(string, "%d ", rgb.rgbRed);                fputs(string,result_r);                sprintf(string, "%d ", rgb.rgbGreen);                fputs(string,result_g);                sprintf(string, "%d ", rgb.rgbBlue);                fputs(string,result_b);                            }            if(offset&&j!=info.biWidth-1)            {                fseek(bmp, offset, SEEK_CUR);            }            fputs("\n", result_r);            fputs("\n", result_g);            fputs("\n", result_b);}} else { // 索引颜色        int i=0;        int index=0;        int biClrUsed;                RGBQUAD *clTable;// 用于存储颜色表        biClrUsed=1<<info.biBitCount;        clTable = (RGBQUAD *)malloc(biClrUsed*sizeof(RGBQUAD));                for(;i<biClrUsed;i++){// 获取颜色表            fread(&clTable[i], 4, 1, bmp);            fwrite(&clTable[i], 4, 1, copy);            clTable[i].rgbReserved=0;        }        i=0;                for (;i<info.biHeight;i++)        {            int j=0;            WORD tmp;            for (; j<info.biWidth; j++) {                RGBQUAD rgb;                char string[16];                                if (info.biBitCount==8) {// 8位,一个一个地取索引                    fread(&tmp,1,1,bmp);                    fwrite(&tmp, 1, 1, copy);                    index = tmp;                } else if (info.biBitCount==4) {// 4位,每两次循环取一次索引                    if (j%2) {                        index = tmp&0xF;                    } else {                        fread(&tmp, 1, 1, bmp);                        fwrite(&tmp, 1, 1, copy);                        index = tmp>>4;                    }                } else if (info.biBitCount==1) {// 1位,每8次循环取一次索引                    int joffset = j%8;                    if (!joffset){                        fread(&tmp, 1, 1, bmp);                        fwrite(&tmp, 1, 1, copy);                    }                    index = (tmp>>(7-joffset))&0x1;                }                                rgb=clTable[index];                sprintf(string, "%d ", rgb.rgbRed);                fputs(string,result_r);                sprintf(string, "%d ", rgb.rgbGreen);                fputs(string,result_g);                sprintf(string, "%d ", rgb.rgbBlue);                fputs(string,result_b);            }            if(offset)            {   int i=0;                fseek(bmp, offset, SEEK_CUR);                for (; i<offset; i++) {                    WORD zero=0;                    fwrite(&zero, 1, 1, copy);                }            }            fputs("\n", result_r);            fputs("\n", result_g);            fputs("\n", result_b);        }                free(clTable);    }    fclose(result_r);    fclose(result_g);    fclose(result_b);fclose(bmp);    fclose(copy);    printf("解析完毕。\n");return 0;    }

一些地方看不懂的可以看我的实验报告:

数字图像实验:BMP文件的读取

实验报告

实验内容

从指定路径读取一个BMP文件,提取其文件头和信息头,并根据图片的像素深度分不同情况来读出图片的像素数据。分三通道输出到三个文本文件中。同时,还创建了一个原BMP文件的拷贝,将原图片的文件头、信息头、颜色表、像素数据分块拷贝到新的文件中。

实验环境

OS:Mac OS X 10.8 Mountain Lion 64位

编译器:gcc 4.2 (Apple)

CPU:2.5GHz Intel Core i5

IDE:XCode 4.6

测试环境:XCode,Terminal

实验思路

使用纯C语言。首先,声明三个结构体,分别用于存储BMP文件的文件头、信息头和颜色表。使用fopen函数打开BMP文件,并使用fread函数将BMP的文件头、信息头分块读入相应结构体的变量header和info中。

由于BMP文件中,一个扫描行的字节数必须是4的倍数,在水平像素不足4的倍数时,多余空位用0补齐。我们在读取像素信息的时候不需要这些垃圾信息,因此这里先计算几个值用作以后处理垃圾信息的参数。

一个是long型变量real_w,该变量表示BMP文件一个扫描行实际占用的位数。它的计算公式是(info.biWidth*info.biBitCount+31)/32*4;

另一个是long型变量offset,该变量表示BMP文件一个扫描行的像素信息所占的位数与真实占用位数的差值,这个变量将在以后用于fseek的移位操作,计算式子是real_w-(info.biWidth * info.biBitCount+7)/8。

此后,通过读取info变量中的biBitCount成员来判断图片的像素深度,若为24位,则无颜色表,我们使用一个嵌套循环,外层是图片的高度(像素为单位),内层是图片的宽度(像素为单位),对于每一个像素,我们都用fread读取图片中3字节的数据并写进相应的三个输出文件中。在每一行读取完毕后,我们使用fseek,传入之前的offset变量,将文件位置指针偏移到下一行的行首,从而跳过每一行的垃圾数据。

若像素深度不为24位,则是索引颜色图片,我们先读取颜色表。索引颜色的数量使用1<<info.biBitCount算出(相当于2的info.biBitCount次方),并从图片中读取这么多的颜色数据(一个颜色占四个字节),用一个指针clTable指向这块数据。此后使用类似于24位中我们使用的嵌套循环,一个像素一个像素地读取,在内层循环中,我们定义一个整形变量index,用于存储当前像素所对应的颜色索引。

对于index的取值,我们分以下几种情况讨论:

1.若像素深度为8位,则1个字节1个像素,我们在每次循环中直接读取1个字节的数据并赋给index即可;

2.若像素深度为4位,则1个字节2个像素,我们用取余的方式j%2(j是内层循环的控制变量)判断其值,若为0,表示当前像素存储在一个字节的高4位字节处,我们先用fread取出该字节,再将该字节右移4位,这样就得到了其高字节4位数对应的整形值;若为1, 表示当前像素存储在一个字节的低4位字节处,我们直接将上次取得的字节与00001111B作与运算,这样就取出了这个字节的低4位。

3.若像素深度为1位,则1个字节1个像素,我们用类似2中的方式,对j取8的余数,仅当j%8为0时,我们才出图片中取出一个字节;对每次循环,我们将取出字节(设为tmp)作如下处理:index = (tmp>>(7-j%8))&0x1来依次取出tmp的每一位。

获得了索引index以后,只需在clTable中取出指定位置的颜色,输出到相应的文本文件中即可。

对于复制BMP文件的操作,我们只需要创建一个FILE指针copy,在每次从BMP中用fread取数据的地方,同样调用fwrite将前面取出的数据再写入copy中,此外,在按行读取像素时,由于我们使用了fseek偏移了文件位置指针,我们也要相应的向copy中写入相中位数的0(这里我们用一个小小的for循环实现)。

实验心得

BMP文件相对于WAV文件更加复杂,例如头文件内容更多,根据像素深度不同数据存储差异较大,也存在很多必须要关注的细节,如:

1.在24位BMP文件中,一个像素仅占用3个字节(对应颜色的B,G,R),不存储RGBQUARD的保留字,而在非24位的BMP文件的颜色表中,对于每个颜色却使用4个字节,这一差异在很多资料中都没有给出。

2.当像素深度为4或1时,1个字节中会存储多个像素,这里涉及了低层的位运算操作。此外,这种情况也必须注意偏移量offset的取整问题。

3.此程序还有一定的缺陷,因其不能读取非标准格式的BMP文件(例如,在PhotoShop中,若使用“局部”方式创建索引颜色,有可能使原文件的颜色表数不足2的N次方个。导致程序读取失败,要正确读取,一定要在转换成BMP时选择Window或Mac OS颜色表。


原创粉丝点击