C应用 -BMP图片存储格式及生成

前言

由于工作需要,最近在折腾一个工业相机,在提供的 CSDK 中,仅提供了获取信息流的方法,若想将图像保存,需自行实现,由于不想借助 OpenCV 去处理(主要是还没装),而且 OPenCV官方 在逐渐抛弃 C 版本 。于是便有了这篇文章。

准备工作

要想实现图片保存,首先我们应先了解BMP图片是如何存储的。

我的方法很简单粗暴,直接上网下载了一张 *.bmp 图片,找不到 bmp格式 的图片没关系,任意下载一张后,通过图片在线转换工具得到 BMP文件

迅捷 - 图片转BMP格式在线一键转换

下载完成后,若直接使用记事本打开,会呈现出乱码。这是由于使用了 ASCII编码 显示,那么我们更换一下打开的工具,这里我使用的是 Sublime Text 3

由于Sublime Text 3 可以预览 bmp文件,于是我将后缀改为 txt 再打开。

在这里插入图片描述

存储形式

BMP 文件格式,又称为 Bitmap(位图) 或是 DIB ( Device-Independent Device,设备无关位图),是 Windows系统 中广泛使用的图像文件格式。

由于它可以不作任何变换地保存图像像素域的数据,因此成为我们取得 RAW数据 的重要来源。Windows 的图形用户界面( graphical user interfaces )也在它的内建图像子系统GDI 中对 BMP 格式提供了支持 1

位图图像文件由若干大小固定(文件头)和大小可变的结构体按一定的顺序构成 2

几经演变,这部分版本较多。下图来自维基百科:
在这里插入图片描述

位图头文件信息

这部分数据块位于文件开头,用于进行文件的识别。典型的应用程序会首先普通读取这部分数据以确保的确是位图文件并且没有损坏。所有的整数值都以 小端序 存放(即最低有效位前置) 2

域名 大小 说明
文件标识 2byte BMP文件 常见"BM"(ASCII),即 0x420x4D
文件大小 4byte 双字形式存储。我这里是8a38 0400;即 00048a38(H)= 276618(D)
保留字 4byte 默认为 0
偏移量 4byte 位图数据(像素数组)的地址偏移(byte),也就是起始地址;记录该项便于随机读取

PS:

  • 文件标志

    • BM – Windows 3.1x, 95, NT, … etc.
    • BA – OS/2 struct Bitmap Array
    • CI – OS/2 struct Color Icon
    • CP – OS/2 const Color Pointer
    • IC – OS/2 struct Icon
    • PT – OS/2 Pointer
  • 文件大小的计算
    F i l e s i z e ( ) ≈ 54 + 4 ⋅ 2 n + w i d t h ⋅ h e i g h t ⋅ n 8 Filesize() {\displaystyle \approx 54+4\cdot 2^{n}+{\frac { {\rm {width}}\cdot {\rm {height}}\cdot n}{8}}} Filesize()54+42n+8widthheightn

位图信息头

该部分从文件的第 15byte 开始,存储详细的图片信息。

这部分数据块对应了 WindowsOS/2 中的内部使用的头结构以及其它一些版本的变体。但所有版本均以一个 DWORD位(32位)开始,用以说明该数据块的大小,使得应用程序能够根据这个大小来区分该图像实际使用了哪种版本的 DIB头结构 2

下图为所有不同版本的DIB头:
在这里插入图片描述
出于兼容性的考量,大多数应用程序使用较旧版本的 DIB头 保存文件。在不考虑 OS/2 的情况下,当前通用的格式为 BITMAPINFOHEADER 版本 2

以下是其详细内容:

域名 大小 说明
图像信息头长度 4byte 信息头长度,一般为 28(H)
图像宽度 4byte 位图图像宽度,单位:像素
图像高度 4byte 位图图像高度,单位:像素
图像面数 2byte 恒为 1
图像像素位数 2byte 1 表示单色位图4 表示16色位图8 表示256色位图16 表示16位高彩色位图24 表示24位真彩色位图32 表示32位增强真彩色位图
压缩种类 4byte 0表示不压缩1表示8位RLE压缩2表示4位RLE压缩3表示位域存放压缩
位图数据大小 4byte 该值一定为 4的倍数
水平分辨率 4byte 单位:像素/米
垂直分辨率 4byte 单位:像素/米
使用的颜色数 4byte 单位:0 表示默认值,亦可为 2^n
重要的颜色数 4byte 0 亦代表默认值,当数值等于 “颜色数” 的数值的时候表示所有颜色一样重要

PS:

  • 压缩种类在这里插入图片描述
  • 位图数据大小计算
    • 先计算每行所占字节数,再乘以列数
    • 代码直接看 最简版本

调色板

调色板是一张映射表,标识颜色索引号与其代表的颜色的对应关系。

图像像素位数 <= 8 时,调色板不可省略。为什么呢?

16位位图 的为 真彩色,最高一位为 0,然后五位是 红色,中间五位是 绿色最低五位是 蓝色

而典型的位图文件使用 RGB彩色模型。在这种模型中,每种颜色都是由不同强度的 红色(R)绿色(G)蓝色(B) 组成的,也就是说,每种颜色都可以使用红色、绿色和蓝色的值所定义 2

那么以 8位位图 为例,最多能表示 256种颜色;由于每个颜色都有RGB三原色,也就是要3个字节 表示,这样的话 256 并不能表示所有的颜色。

在这里插入图片描述
这就需要一个索引,用一个字节的索引指向 3个字节 表示的 颜色(RGB)

如果把这 3个字节 表示为一个 Color类型;那么调色板就是 Color的数组,也就是个二维数组 palette[N][4],其中 N 是颜色的数量 3

对于 32位位图,则可用 RGBA

位图数据

Height 为正数时,图像倒立,从下到上,从左到右,以行为主序排列。
Height 为负数时,从上到下存储。

Windows 默认的扫描的最小单位是 4字节,如果数据对齐满足这个值的话对于数据的获取速度等都是有很大的增益的。

因此,BMP图像 顺应了这个要求,要求每行的数据的长度必须是 4的倍数,如果不够需要进行 比特填充(以 0 填充),这样可以达到按行的快速存取。这样的话,位图数据的大小就不一定是 宽x高x每像素字节数 了,因为每行还可能有 0填充。这也是 位图数据大小 一定是 4的倍数 的原因。

此外需要注意的是:在 windows 中,颜色顺序是:B G R

实现

那么我们如何将 视频流数据 转换为 bmp文件 保存?

首先我们需要创建一个文件,然后按照 BMP 的存储格式写入该文件,最后保存为 *.bmp即可。

最简版本

以下代码,参考 C语言集锦(一) C代码生成图片:BMP、PNG和JPEG 4,我只是完成了小部分修改,增加了可读性。

/************************* Save 24bit BMP ****************************/
#include <stdio.h>
#include <stdlib.h>

#define w 200
#define h 200

void WriteBMP(char*img,const char* filename)
{
    
    
    // 计算每行所占字节数
    // +3是怕出现不满足4的倍数这种情况
    // /4*4的目的是保证结果为4的倍数
    int l=(w*3+3)/4*4;
    
    // 位图头文件信息(不包含BMP标志)+信息数据
    // 利用或运算巧妙的将图像面数及像素位数合并
    int bmi[]= {
    
    l*h+54,0,54,40,w,h,1|((3*8)<<16),0,l*h,0,0,0,0};
    //创建/打开文件
    FILE *fp = fopen(filename,"wb");
    //写入BMP标志
    fprintf(fp,"BM");
    //写入位图头文件信息+信息数据
    fwrite(&bmi,52,1,fp);
    //写入位图数据
    fwrite(img,1,l*h,fp);
    fclose(fp);
}
int main()
{
    
    
    char img[w*h*3];
    //随机位图数据
    for(int i=0; i<w*h*3; i++)
        img[i]=rand()%256;
    WriteBMP(img,"test.bmp");
    return 0;
}

在这里插入图片描述

结构体实现

下面,我们使用结构体实现,特别需要注意的是:考虑字对齐

关于字对齐,可查看我之前的文章 C - 字对齐那些事儿

#include <stdio.h>
#include <stdlib.h>

#define w 200
#define h 200


#pragma pack(2)

struct file_head
{
    
    
	short  fhType;
	unsigned int fhSize;
	unsigned int fhRd;
	unsigned int fhOffset;
};

struct bmp_head
{
    
    
    unsigned int bhSize;
    unsigned int bhWidth;
    unsigned int bhHight;
    short bhPlances;
    short bhBitCount;
    unsigned int bhCompression;
    unsigned int bhSizeImage;
    unsigned int bhXPelsPerMeter;
    unsigned int bhYPelsPerMeter;
    unsigned int bhClrUsed;
    unsigned int bhClrImportant;
};
#pragma pack()

#pragma pack(1)
//BGR
struct clrtest
{
    
    
    unsigned char rgbBlue;
    unsigned char rgbGreen;
    unsigned char rgbRed;
    //unsigned char rgbReserved;
};
#pragma pack()

int main(){
    
    

    struct file_head file_h;
    struct bmp_head bmp_h;
    int l = (w*3 +3)/4*4;
    //unsigned char img[l*h];
    struct clrtest image[w*h];
    
    file_h.fhType = 0x4D42;
    file_h.fhSize = 120054;
    file_h.fhRd = 0;
    file_h.fhOffset = 54;

    bmp_h.bhSize = 40;
    bmp_h.bhWidth = w;
    bmp_h.bhHight = h;
    bmp_h.bhPlances = 1;
    bmp_h.bhBitCount = 24;
    bmp_h.bhCompression = 0;
    bmp_h.bhSizeImage = l*h;
    bmp_h.bhXPelsPerMeter = 0;
    bmp_h.bhYPelsPerMeter = 0;
    bmp_h.bhClrUsed = 0;
    bmp_h.bhClrImportant = 0;

    FILE *fp = fopen("testbmp.bmp","wb");
    fwrite(&file_h,14,1,fp);
    fwrite(&bmp_h,40,1,fp);

    //Generate a Blud picture
    for (int i = 0; i < w; i++)
    {
    
    
        for(int j = 0; j < h; j++)
        {
    
    
            image[i*w+j].rgbBlue = 255;
            image[i*w+j].rgbGreen = 0;
            image[i*w+j].rgbRed = 0;
            //image[i*j].rgbReserved = 0;
        }
    }

    fwrite(&image,sizeof(image),1,fp);

    printf("file_head: sizeof:%d\r\n",sizeof(file_h));
    printf("bmp_head: sizeof:%d\r\n",sizeof(bmp_h));

    fclose(fp);
    return 0;
}

在这里插入图片描述

应用

画圆

            if(10000 == (i - 100)*(i - 100) + (j - 100)*(j - 100))
            {
    
    
                image[i*w+j].rgbRed = 255;
                image[i*w+j].rgbGreen = 255;
                image[i*w+j].rgbBlue = 255;
            }
            else
            {
    
    
                image[i*w+j].rgbRed = 0;
                image[i*w+j].rgbGreen = 0;
                image[i*w+j].rgbBlue = 0;
            }

在这里插入图片描述
效果有点差,让我想起了浮点数的比较 if( float_number == xxx )

改进

            if(-1 < (i - 100)*(i - 100) + (j - 100)*(j - 100)  - 10000 < 1)
            {
    
    
                image[i*w+j].rgbRed = 255;
                image[i*w+j].rgbGreen = 255;
                image[i*w+j].rgbBlue = 255;
            }
            else
            {
    
    
                image[i*w+j].rgbRed = 0;
                image[i*w+j].rgbGreen = 0;
                image[i*w+j].rgbBlue = 0;
            }

在这里插入图片描述
正在我准备收拾东西走人的时候,突然听到了…那就画一道彩虹吧。

在这里插入图片描述

生成彩虹

for (int i = 0; i < w; i++)
    {
    
    
        for(int j = 0; j < h; j++)
        {
    
    
            if (i < 28)
            {
    
    
                image[i*w+j].rgbRed = 139;
                image[i*w+j].rgbGreen = 0;
                image[i*w+j].rgbBlue = 255;
                continue;
            }
            else if (i < 56)
            {
    
    
                image[i*w+j].rgbRed = 0;
                image[i*w+j].rgbGreen = 0;
                image[i*w+j].rgbBlue = 255;
                continue;
            }
            else if (i < 84)
            {
    
    
                image[i*w+j].rgbRed = 0;
                image[i*w+j].rgbGreen = 127;
                image[i*w+j].rgbBlue = 255;
                continue;
            }
            else if (i < 112)
            {
    
    
                image[i*w+j].rgbRed = 0;
                image[i*w+j].rgbGreen = 255;
                image[i*w+j].rgbBlue = 0;
                continue;
            }
            else if (i < 140)
            {
    
    
                image[i*w+j].rgbRed = 255;
                image[i*w+j].rgbGreen = 255;
                image[i*w+j].rgbBlue = 0;
                continue;
            }
            else if (i < 168)
            {
    
    
                image[i*w+j].rgbRed = 255;
                image[i*w+j].rgbGreen = 165;
                image[i*w+j].rgbBlue = 0;
                continue;
            }
            else
            {
    
    
                image[i*w+j].rgbRed = 255;
                image[i*w+j].rgbGreen = 0;
                image[i*w+j].rgbBlue = 0;
                continue;
            }

在这里插入图片描述

参考鸣谢


  1. BMP文件格式详解(BMP file format) ↩︎

  2. 维基百科 - BMP ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  3. C语言解析BMP图片 ↩︎

  4. C语言集锦(一) C代码生成图片:BMP、PNG和JPEG ↩︎

猜你喜欢

转载自blog.csdn.net/weixin_40774605/article/details/106011198