文章目录

  • 前言
  • 准备工作
  • 存储形式
    • 位图头文件信息
    • 位图信息头
    • 调色板
    • 位图数据
  • 实现
    • 最简版本
    • 结构体实现
  • 应用
    • 画圆
      • 改进
    • 生成彩虹
  • 参考鸣谢

前言

由于工作需要,最近在折腾一个工业相机,在提供的 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
  • 文件大小的计算
    Filesize()≈54+4⋅2n+width⋅height⋅n8Filesize() {\displaystyle \approx 54+4\cdot 2^{n}+{\frac {{\rm {width}}\cdot {\rm {height}}\cdot n}{8}}} Filesize()≈54+4⋅2n+8width⋅height⋅n​

位图信息头

该部分从文件的第 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 200void 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 picturefor (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 ↩︎

C应用 -BMP图片存储格式及生成相关推荐

  1. Java_最不重要位替换(LSB)基于24位BMP图片

    隐写术的一个简单示例 向BMP图片中隐藏一段文字并保存,从保存的图片中提取文字. 原理:把需要隐藏的文本信息转换成二进制字符流,再将其拆分成一个个的0和1,隐藏在像素数据(RGB字节)中,因对RGB的 ...

  2. 【数据压缩】C语言实现bmp图片序列生成yuv视频

    一.实验要求 1.解析BMP格式文件,获取图像信息 2.转化BMP图像为YUV格式的图像 3.多张BMP图像,转化为YUV视频 二.实验内容 1.获取图片 获取(540*720)的bmp图片若干: 2 ...

  3. 任意大小迷宫自动生成+BFS寻路+生成无损迷宫bitmap(.BMP)图片

    目录标题 迷宫游戏 迷宫的生成 BFS寻找路线 最后的一些细枝末节 如何使用程序 链接 I TURN COFFEE INTO CODE! 800x800迷宫自动解路径 镇楼图 https://pan. ...

  4. 纯色bmp图片生成的效率

    各种编程语言生成纯色bmp图片的效率 之前使用了各种语言生成纯色bmp图片,这里汇总并对比下纯色bmp图片文件生成的效率. 主要指标是完成bmp文件生成的耗时时长. 为了公平客观的对比,通过linux ...

  5. php 生成bmp图片,[GD]生成bmp格式的图片(imagebmp)

    GD库里没有生成bmp图片的函数,所以自己写了一个,这个函数尚有一个压缩算法没有写,不过已经够用了.需要的同学可以看看. int imagebmp ( resource image [, string ...

  6. JAVA C# Zxing生成的二维码数据转换为1bit的bmp下发到点阵终端。QRCode去白边,以bmp格式字节流发送,BMP图片解析

    BMP图像文件完全解析 - 知乎 注意: 小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中.越向后地址越高,比如00  01  02,02的地址是2,是高. BMP文 ...

  7. java生成bmp单色位图,关于GDI+生成单色BMP图片的问题,请高手进!

    用GDI+生成的BMP图片颜色位数一般都是24位,我要怎样生成1位的BMP图片呢? 我在网上找了几种方法,有的方法效果是达到了,但是效率非常低,我是要生成几万张甚至几十万张的,所以要求速度要够快. 不 ...

  8. 03 bmp图片生成及像素修改的源码

    bmp图片生成及像素修改的源码 作者 将狼才鲸 创建日期 2022-10-08 Gitee源码地址 CSDN阅读地址 简介:使用Qt.Gcc(可以是Linux或者MinGW等)编译环境,使用C语言编写 ...

  9. C#生成单色bmp图片,转为单色bmp图片 任意语言完全用字节拼一张单色图,LCD取模 其它格式图片转为单色图

    最终效果: V1.8.2 20230419 文字生成单色BMP图片4.exe 默认1280*720  如果显示不全,请把宽和高加大  字体加大. 首先,用windows画板生成一张1*1白色单色图作为 ...

最新文章

  1. zigbee zstack 串口,按键,消息,定时器
  2. Mysql存储过程和存储函数
  3. xlsxwriter php,Xlsxwriter
  4. HttpComponents分析之连接池实现
  5. 成立十个月,融资五个亿,创新奇智完成超4亿人民币A轮和A+轮融资
  6. Python基础教程 第六章 学习笔记
  7. GFP_KERNEL的作用
  8. 记一个单双引号的特别用法
  9. python global用法_python可视化——pyechart库
  10. 电脑和服务器之间怎么传送大文件夹,WIN10两台电脑之间快速传输大量文件 - 卡饭网...
  11. ghost离线备份还原系统,物理机
  12. 天地图显示不全的问题
  13. python安装包提示error: option --single-version-externally-managed not recognized
  14. notify()和notifyAll()有什么区别
  15. 认识PCIe---硬件篇
  16. _nop_();的由来和作用
  17. Java8通过Function获取字段名称
  18. 使用IDL显示DICOM文件的信息
  19. 第五届金鹏奖圆满落幕 2015年度原创游戏榜单出炉
  20. echarts地图api series_echarts实现中国地图数据展示

热门文章

  1. 学asp.net的自己学做网站的第一感受
  2. 查看数据库的版本命令
  3. 密码学-编码算法:Base64编码原理和使用
  4. 屏幕录制工具哪个好用?分享3款相见恨晚的软件
  5. vSphere(一) 标准交换机和分布式交换机
  6. 初二计算机知识,初二信息技术考试试题及答案
  7. JavaScript 循环嵌套案例、while 语句、 do...while 语句、continue 关键字、5 break 关键字
  8. html随机显示图片,DUX主题实现缩略图随机显示
  9. Android Keyboard/Touch Panel分析
  10. Winsock 完成端口模型简介