C语言读写BMP图片(附Github下载链接和视频讲解地址)

BMP全称(BITMAP)是微软WINDOWS系统默认使用的一种通用图片数据存储格式,特点是结构清晰,解析简单和多平台支持广泛。其文件结构由文件头结构体,文件信息头结构体,调色板(可选),以及图片数据(压缩编码可选)组成。一个BMP文件的解析,从二进制低位开始,层层递进,每一层包含了下一层的信息,非常适合C语言进行操作。

首先介绍一下从BMP文件二进制数据前两字节,一般为‘B’ 'M'的ASCII码,其他的标识在微软未公开的OS使用,我们暂时忽略。

【1】首先读取一张BMP图像的头两个字节,一般情况下是0x42 0x4D,就是‘B’, 'M'的ASCII码,如果是两个字节一起读取就是0x4D42(lsb),十进制是19778。

【2】紧跟着就是12字节的文件头包含文件大小,有无调色板以及图像数据在偏移多少字节可以获取。

【3】在12字节的文件头后面是BMP的信息头文件。一共40字节,包含着BMP图像的宽,高,单像素大小,压缩模式,分辨率尺寸等具体的图像信息。

【4】如果有调色板的情况下在这个40字节的信息头文件之后直接存储调色板信息,每个调色板是4个字节,分别是B,G,R,A的映射值。

【5】最后的存储空间就是图像数据了,需要注意几个地方。第一,图像是按行反着存储的,第一行被存储到了最后一行,第二行存储在了最后第二行,以此类推。第二,图像如果是多通道的按BGRA方式存储。比如24位的图像单像素尺寸是已BGR顺序存储的。第三,图像每一行的大小必须是四字节对齐,比如8位图像511x511的BMP图像,每一行是511字节的图像数据自动扩展为512字节一行,用0x00补齐。

下面是12字节文件头结构体:

上图是BMP文件头结构体

bfSize是32位的元素,表示整个BMP文件的大小,单位是字节。

bfOffBits是32位的元素,表示从BMP二进制起始地址开始偏移多少字节是图像存储数据。根据之前讲解的内容,在文件存储数据前有2字节的’BM‘,12字节的文件头,40字节的信息头和调色板(如果有的话),所以总结下来bfOffBits 为54字节时没有调色板,大于54字节时,多出来的字节数就是调色板大小。

下面是40字节的信息头

其中biSize是整个信息头的大小为40字节

biWidth和biHeight代表了图像的像素分辨率,比如640x480

biPlanes这里默认为1,大部分情况都是1个plane。

biBitCount代表单个像素的位数,比如24是三字节的像素一般是BGR,32位一般是BGRA。

biCompression指的是压缩模式,0代表不压缩,1代表BGR555,2代表BGR565,3是其他模式。

biSizeImage这个变量蕴含着整个图像存储文件的大小,程序可以根据这个大小开辟空间。

biXPelsPerMeter和biYPelsPerMeter是用来描述原图的真实尺寸的,可以忽略。

biClrUsed和biClrImportant可以默认设置为0.

接下来就用C语言简单实现一下BMP文件的读和存

 上图展示了读取BMP图像的过程:

【1】打开文件流,读取前两个字节

    FILE * fp = fopen(path,"rb");
    uint16_t bfType;  
    fread(&bfType,sizeof(bfType),1,fp);

【2】 读取文件头和信息头,计算调色板大小

    fread(bmpFileHeader_p,sizeof(BMP_FILE_HEADER),1,fp);
    fread(bmpInfoHeader_p,sizeof(BMP_INFO_HEADER),1,fp);
    size_t colorTableSize = bmpFileHeader_p->bfSize - bmpInfoHeader_p->biSizeImage -2 
                            - sizeof(BMP_FILE_HEADER) - sizeof(BMP_INFO_HEADER);
    printf("colorTableSize=%d\n",colorTableSize);

【3】 首先讨论没有调色板的情况,根据读到的信息头确定图像宽高和大小,开辟空间并按行反着读取到内存空间。

    printf("bfType: %d %d [%c%c] \n",bfType&0xff,bfType>>8,bfType&0xff,bfType>>8);
    uint32_t channel = bmpInfoHeader_p->biBitCount>>3;
    uint32_t dataSize=bmpInfoHeader_p->biSizeImage;
    *dataPtr=(uint8_t *)malloc(dataSize);
    for(uint32_t i=0;i<bmpInfoHeader_p->biHeight;i++)
    {
        fread((*dataPtr) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*channel,sizeof(uint8_t),bmpInfoHeader_p->biWidth*channel,fp);
    }

【4】当步骤【2】读到的调色板大小超过0,就先读取调色板,再按行反着读取图像数据,然后根据调色板的映射还原图像的色彩信息。

    if(colorTableSize>0)
    {
        uint8_t * table = (uint8_t *)malloc(colorTableSize);
        fread(table,1,colorTableSize,fp);
        // for(int i=0;i<colorTableSize;i++)
        // {
        //     printf("colortable[%3d]: %3d\n",i,table[i]);
        // }
        printf("bfType: %d %d [%c%c] \n",bfType&0xff,bfType>>8,bfType&0xff,bfType>>8);
        uint32_t channel = bmpInfoHeader_p->biBitCount>>3;
        uint32_t raw_dataSize=bmpInfoHeader_p->biSizeImage;
        uint32_t raw_channel = channel;
        channel =3;
        uint32_t dataSize    =bmpInfoHeader_p->biWidth * bmpInfoHeader_p->biHeight * channel;
        *dataPtr=(uint8_t *)malloc(dataSize);
        uint8_t * raw=(uint8_t *)malloc(raw_dataSize);
        // fread(raw,sizeof(uint8_t),raw_dataSize,fp);
        for(uint32_t i=0;i<bmpInfoHeader_p->biHeight;i++)
        {
            fread((raw) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*raw_channel,sizeof(uint8_t),bmpInfoHeader_p->biWidth*raw_channel,fp);
        }
        BMP_COLOR_TBL * tbl_array = (BMP_COLOR_TBL*)table;


        for(uint32_t i=0;i<bmpInfoHeader_p->biHeight;i++)
        {
            uint8_t * write_line_ptr = (*dataPtr) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*channel;
            uint8_t * raw_line_ptr   = (raw) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*raw_channel;
            for(uint32_t j=0;j<bmpInfoHeader_p->biWidth;j++)
            {
                uint8_t v = raw_line_ptr[j];
                BMP_COLOR_TBL tbl_array_ = tbl_array[v];
                write_line_ptr[j*channel+0] = tbl_array_.B;
                write_line_ptr[j*channel+1] = tbl_array_.G;
                write_line_ptr[j*channel+2] = tbl_array_.R;
                
            }
            // fread((*dataPtr) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*channel,sizeof(uint8_t),bmpInfoHeader_p->biWidth*channel,fp);
        }
        free(raw);
        bmpInfoHeader_p->biBitCount=channel<<3;
        bmpInfoHeader_p->biSizeImage = bmpInfoHeader_p->biWidth * bmpInfoHeader_p->biHeight * channel;
        free(table);
    }

【5】关闭文件流,完成BMP文件读取

fclose(fp);

以下是完整读取BMP文件函数

void readBmpFromFile(char* path,BMP_FILE_HEADER * bmpFileHeader_p,BMP_INFO_HEADER * bmpInfoHeader_p,uint8_t ** dataPtr)
{
    FILE * fp = fopen(path,"rb");
    uint16_t bfType;  
    fread(&bfType,sizeof(bfType),1,fp);
    fread(bmpFileHeader_p,sizeof(BMP_FILE_HEADER),1,fp);
    fread(bmpInfoHeader_p,sizeof(BMP_INFO_HEADER),1,fp);
    size_t colorTableSize = bmpFileHeader_p->bfSize - bmpInfoHeader_p->biSizeImage -2 -sizeof(BMP_FILE_HEADER) - sizeof(BMP_INFO_HEADER);
    printf("colorTableSize=%d\n",colorTableSize);
    if(colorTableSize>0)
    {
        uint8_t * table = (uint8_t *)malloc(colorTableSize);
        fread(table,1,colorTableSize,fp);
        // for(int i=0;i<colorTableSize;i++)
        // {
        //     printf("colortable[%3d]: %3d\n",i,table[i]);
        // }
        printf("bfType: %d %d [%c%c] \n",bfType&0xff,bfType>>8,bfType&0xff,bfType>>8);
        uint32_t channel = bmpInfoHeader_p->biBitCount>>3;
        uint32_t raw_dataSize=bmpInfoHeader_p->biSizeImage;
        uint32_t raw_channel = channel;
        channel =3;
        uint32_t dataSize    =bmpInfoHeader_p->biWidth * bmpInfoHeader_p->biHeight * channel;
        *dataPtr=(uint8_t *)malloc(dataSize);
        uint8_t * raw=(uint8_t *)malloc(raw_dataSize);
        // fread(raw,sizeof(uint8_t),raw_dataSize,fp);
        for(uint32_t i=0;i<bmpInfoHeader_p->biHeight;i++)
        {
            fread((raw) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*raw_channel,sizeof(uint8_t),bmpInfoHeader_p->biWidth*raw_channel,fp);
        }
        BMP_COLOR_TBL * tbl_array = (BMP_COLOR_TBL*)table;


        for(uint32_t i=0;i<bmpInfoHeader_p->biHeight;i++)
        {
            uint8_t * write_line_ptr = (*dataPtr) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*channel;
            uint8_t * raw_line_ptr   = (raw) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*raw_channel;
            for(uint32_t j=0;j<bmpInfoHeader_p->biWidth;j++)
            {
                uint8_t v = raw_line_ptr[j];
                BMP_COLOR_TBL tbl_array_ = tbl_array[v];
                write_line_ptr[j*channel+0] = tbl_array_.B;
                write_line_ptr[j*channel+1] = tbl_array_.G;
                write_line_ptr[j*channel+2] = tbl_array_.R;
                
            }
            // fread((*dataPtr) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*channel,sizeof(uint8_t),bmpInfoHeader_p->biWidth*channel,fp);
        }
        free(raw);
        bmpInfoHeader_p->biBitCount=channel<<3;
        bmpInfoHeader_p->biSizeImage = bmpInfoHeader_p->biWidth * bmpInfoHeader_p->biHeight * channel;
        free(table);
    }
    else
    {
    printf("bfType: %d %d [%c%c] \n",bfType&0xff,bfType>>8,bfType&0xff,bfType>>8);
    uint32_t channel = bmpInfoHeader_p->biBitCount>>3;
    uint32_t dataSize=bmpInfoHeader_p->biSizeImage;
    *dataPtr=(uint8_t *)malloc(dataSize);
    for(uint32_t i=0;i<bmpInfoHeader_p->biHeight;i++)
    {
        fread((*dataPtr) + (bmpInfoHeader_p->biHeight-i-1)*bmpInfoHeader_p->biWidth*channel,sizeof(uint8_t),bmpInfoHeader_p->biWidth*channel,fp);
    }
    }
    fclose(fp);
}

存BMP文件也是类似的操作,

一下是没有调色板的BMP文件存储

void saveBmpDataToFile(uint8_t * data_p,uint32_t pixelW,uint32_t pixelH,uint32_t pixelS,char * path,int compLevel)
{
    //s0 compute key parameters info
    uint32_t imageSize = pixelW * pixelH * pixelS;
    uint32_t bitsPerPixel = pixelS <<3;
    uint32_t imageDataOffsetFromHead       = 2+sizeof(BMP_INFO_HEADER) + sizeof(BMP_FILE_HEADER);
    uint32_t imageDataOffsetFromFileHeader = sizeof(BMP_INFO_HEADER);
    uint32_t fileSize                      = 2+imageSize + sizeof(BMP_INFO_HEADER) + sizeof(BMP_FILE_HEADER);
    FILE * fp = fopen(path,"wb");
    //s1 save 'B' 'M'
    char Format_BM[2]={'B','M'};
    fwrite(Format_BM,sizeof(char),2,fp);
    //s2 save 12 bytes File Header
    BMP_FILE_HEADER fileHeader;
    fileHeader.bfOffBits  = imageDataOffsetFromHead;
    fileHeader.bfSize     = fileSize;
    fileHeader.bfReserved1=0;
    fileHeader.bfReserved2=0;
    fwrite(&fileHeader,sizeof(BMP_FILE_HEADER),1,fp);
    //s3 save 40 bytes Info Header
    BMP_INFO_HEADER infoHeader;
    infoHeader.biSize           = imageDataOffsetFromFileHeader;
    infoHeader.biWidth          = pixelW;
    infoHeader.biHeight         = pixelH;
    infoHeader.biPlanes         = 1;
    infoHeader.biBitCount       = bitsPerPixel;
    infoHeader.biCompression    = 0;
    infoHeader.biSizeImage      = imageSize;
    infoHeader.biXPelsPerMeter  = 37795;
    infoHeader.biYPelsPerMeter  = 37795;
    infoHeader.biClrUsed        = 0;
    infoHeader.biClrImportant   = 0;
    fwrite(&infoHeader,sizeof(BMP_INFO_HEADER),1,fp);
    //s4 save image data with inversed lines
    for(uint32_t i = 0;i<pixelH;i++)
    {
        fwrite(data_p+(pixelH-i-1)*(pixelW*pixelS),sizeof(uint8_t),pixelW*pixelS,fp);
    }
    fclose(fp);
}

以下是有调色板情况下的BMP文件存储

void saveBmpDataToFile_colorTBL(uint8_t * data_p,uint32_t pixelW,uint32_t pixelH,uint32_t pixelS,uint8_t * colorTbl,uint32_t colorTblSize,char * path)
{
    if(colorTblSize==0) {printf("error! colorTblSize could not be zero!!!\n");return;};
    //s0 compute key parameters info
    uint32_t imageSize = pixelW * pixelH * pixelS;
    uint32_t bitsPerPixel = pixelS <<3;
    uint32_t imageDataOffsetFromHead       = 2+colorTblSize+sizeof(BMP_INFO_HEADER) + sizeof(BMP_FILE_HEADER);
    uint32_t imageDataOffsetFromFileHeader = sizeof(BMP_INFO_HEADER);
    uint32_t fileSize                      = 2+colorTblSize+imageSize+ sizeof(BMP_INFO_HEADER) + sizeof(BMP_FILE_HEADER);
    FILE * fp = fopen(path,"wb");
    //s1 save 'B' 'M'
    char Format_BM[2]={'B','M'};
    fwrite(Format_BM,sizeof(char),2,fp);
    //s2 save 12 bytes File Header
    BMP_FILE_HEADER fileHeader;
    fileHeader.bfOffBits  = imageDataOffsetFromHead;
    fileHeader.bfSize     = fileSize;
    fileHeader.bfReserved1=0;
    fileHeader.bfReserved2=0;
    fwrite(&fileHeader,sizeof(BMP_FILE_HEADER),1,fp);
    //s3 save 40 bytes Info Header
    BMP_INFO_HEADER infoHeader;
    infoHeader.biSize           = imageDataOffsetFromFileHeader;
    infoHeader.biWidth          = pixelW;
    infoHeader.biHeight         = pixelH;
    infoHeader.biPlanes         = 1;
    infoHeader.biBitCount       = bitsPerPixel;
    infoHeader.biCompression    = 0;
    infoHeader.biSizeImage      = pixelW*pixelH*1;
    infoHeader.biXPelsPerMeter  = 37795;
    infoHeader.biYPelsPerMeter  = 37795;
    infoHeader.biClrUsed        = 0;
    infoHeader.biClrImportant   = 0;
    fwrite(&infoHeader,sizeof(BMP_INFO_HEADER),1,fp);
    //s4 save color table
    fwrite(colorTbl,sizeof(uint8_t),colorTblSize,fp);
    //s5 save image data with inversed lines
    for(uint32_t i = 0;i<pixelH;i++)
    {
        fwrite(data_p+(pixelH-i-1)*(pixelW*pixelS),sizeof(uint8_t),pixelW*pixelS,fp);
    }
    fclose(fp);
}

该工程已经同步到GIHUB,

下载地址:

https://github.com/leonard73/LeoCPorintg.git

视频讲解地址:

纯C语言手撸BMP读写_哔哩哔哩_bilibili

  • 4
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值