C 语言生成BMP 文件

针对这个话题其实可以分解为两个议题,一个是 BMP 文件的格式,一个是 C语言如何操作文件。

BMP 文件格式

BMP 是微软在 windows 系统中使用的一种位图图像格式,主要包含调色板图像和直接色图像两大类。

文件格式由文件头、信息头、调色板数据、图像数据四个部分构成。文件头区域包含文件的标识、文件大小和图像数据区偏移量等字段。信息头区域则包含图像宽度、高度、像素格式等信息。所有数据一般按小端字节序来存储,且数据块一般组织成4字节对齐。

图像数据区也不例外,按每行图像的数据字节,按4字节对齐。图像数据按行倒序存放,先存储最后一行图像数据,然后依次存放,直到第一行数据。这样设计,可能是为了从文件尾部往前读的时候,能够直接顺序读出图像数据吧。

备注:相较于windows画图程序,Photoshop保存的BMP文件,其图像数据区末尾多出两个0x00字节(图像数据区大小字段也大了2),可能是为了保证整个文件大小是4字节对齐。

使用调色板的位图图像,在其调色区域存储实际的颜色值,而在图像数据区域存储对调色板的索引值。根据调色板的数量,可以分为单色图像、16色图像和256色图像。使用直接色的位图图像,没有调色板区域,在图像数据区直接存储每行图像的每个像素的RGB颜色数据。根据颜色数据的格式,分为 BGR555、BGR888和 BGRA8888等格式。

本文中只是直接色的格式进行了说明和文件生成。

C 语言如何操作文件

在 C 语言的标准库中,有两大类文件操作接口,一类是比较原始的 open/close/seek/read/write 等接口,直接与系统调用相关联;一类是面向流的 fopen/fclose/fseek/fread/fwrite等接口,会在内部维护文件数据缓冲区,从而更加有效的进行系统调用。本文中采用面向流的文件接口进行文件数据读写。

下面是对这个问题的代码实现。

头文件和类型定义

#include <stdio.h>
#include <stdlib.h>
#include <string.h>typedef unsigned char  U1;
typedef unsigned short U2;
typedef unsigned long  U4;typedef unsigned char  BOOL;
#define TRUE  (0xFFu)
#define FALSE (0x00u)

字节序列化工具函数

以下函数用于将整型无符号数值,按小端字节序存储到字节序列中。
void vd_SerializeLittleEndianU2(U1 * u1_ap_serial, U2 u2_a_value)
{do{if(u1_ap_serial == NULL)break;u1_ap_serial[0] = (U1)u2_a_value;u1_ap_serial[1] = (U1)(u2_a_value >> 8);} while(FALSE);
}void vd_SerializeLittleEndianU3(U1 * u1_ap_serial, U4 u4_a_value)
{do{if(u1_ap_serial == NULL)break;u1_ap_serial[0] = (U1)u4_a_value;u1_ap_serial[1] = (U1)(u4_a_value >> 8);u1_ap_serial[2] = (U1)(u4_a_value >> 16);} while(FALSE);
}void vd_SerializeLittleEndianU4(U1 * u1_ap_serial, U4 u4_a_value)
{do{if(u1_ap_serial == NULL)break;u1_ap_serial[0] = (U1)u4_a_value;u1_ap_serial[1] = (U1)(u4_a_value >> 8);u1_ap_serial[2] = (U1)(u4_a_value >> 16);u1_ap_serial[3] = (U1)(u4_a_value >> 24);} while(FALSE);
}

颜色转换辅助宏

下面的宏用来定义颜色数据,按 RGB 颜色的每个分量为0~255的颜色值,定义到32位整型类型。以及将32位颜色值,转换为16位的 RGB555颜色值。
// AAAAAAAARRRRRRRRGGGGGGGGBBBBBBBB
// 76543210765432107654321076543210
#define BMP_RGBA32(r,g,b,a)  (U4)( ((U4)(U1)(r)<<16) | ((U4)(U1)(g)<<8) | (U4)(U1)(b) | ((U4)(U1)(a)<<24) )
#define BMP_RGB24(r,g,b)     (U4)( ((U4)(U1)(r)<<16) | ((U4)(U1)(g)<<8) | (U4)(U1)(b) )// XRRRRRGGGGGBBBBB
// 0432104321043210
#define BMP_RGBA32TOBMP16(c) (U2)( (((U4)(c)>>9) & 0x7C00u) | (((U4)(c)>>6) & 0x03E0u) | (((U4)(c)>>3) & 0x001F) )

内存中的图像数据

内存中的图像数据的定义和创建,其中的一个技巧是将结构体和图像数据区,在一次malloc 中动态分配的,这样释放内存时也减少了判断。

typedef struct {U4 u4_image_width;U4 u4_image_height;U4 u4_widthbyte;U4 u4_image_size;U2 u2_bitcount;U2 u2_palette_size;U1* u1_p_image_data;
} ST_BITMAP;ST_BITMAP * st_g_CreateBitmap(U4 u4_a_width, U4 u4_a_height, U2 u2_a_bitcount)
{BOOL b_t_success = FALSE;ST_BITMAP * st_tp_bitmap = NULL;U4 u4_t_widthbyte = 0;U4 u4_t_imagesize = 0;do{if((u4_a_width == 0) || (u4_a_width > 4096))break;if((u4_a_height == 0) || (u4_a_height > 4096))break;if((u2_a_bitcount != 16) && (u2_a_bitcount != 24) && (u2_a_bitcount != 32))break;// 4-byte aligned bytes for a line of image datau4_t_widthbyte = (u4_a_width * u2_a_bitcount + 31) / 32 * 4;u4_t_imagesize = (u4_t_widthbyte * u4_a_height);st_tp_bitmap = malloc(sizeof(ST_BITMAP) + u4_t_imagesize); // alloc togetherif(st_tp_bitmap == NULL)break;memset(st_tp_bitmap, 0, sizeof(ST_BITMAP) + u4_t_imagesize);st_tp_bitmap->u4_image_width = u4_a_width;st_tp_bitmap->u4_image_height = u4_a_height;st_tp_bitmap->u4_widthbyte = u4_t_widthbyte;st_tp_bitmap->u4_image_size = u4_t_imagesize;st_tp_bitmap->u2_bitcount = u2_a_bitcount;st_tp_bitmap->u2_palette_size = 0;// pointer to the address next to the structst_tp_bitmap->u1_p_image_data = (U1 *)st_tp_bitmap + sizeof(ST_BITMAP);b_t_success = TRUE;} while(FALSE);return st_tp_bitmap;
}

作为安全处理,在释放内存之前,先用 memset 清除了原来结构体中的数据,如果使用方在内存被释放后,继续访问该内存数据,出现 NULL 指针崩溃。

void vd_g_FreeBitmap(ST_BITMAP * st_ap_bitmap)
{BOOL b_t_success = FALSE;do{if(st_ap_bitmap == NULL)break;memset(st_ap_bitmap, 0, sizeof(ST_BITMAP));free(st_ap_bitmap);b_t_success = TRUE;} while(FALSE);
}

保存图像数据到BMP文件

下面将内存中的图像数据保存到文件,其中的一个技巧是将文件头和信息头,预先放入了字节数组,除了 AA、BB直到 FF 的数据之外,其他的数据对于当前版本的 BMP 来说,都可以用默认值,其字段不需要特别处理。需要注意的是位图的图像数据是按图像行倒序的,所以写入到文件时,依次写入最后一行到第一行。如果要做读取 BMP 文件时,也需要注意这个问题。

void vd_g_SaveBitmap(const ST_BITMAP * st_ap_bitmap, const char * sz_ap_path)
{BOOL b_t_success = FALSE;U1 u1_tp_bitmap_header[] ={0x42, 0x4D, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, //  2 AA->FileSize0x00, 0x00, 0xBB, 0xBB, 0xBB, 0xBB, 0x28, 0x00, // 10 BB->OffBits0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xDD, 0xDD, // 18 CC->Width0xDD, 0xDD, 0x01, 0x00, 0xEE, 0xEE, 0x00, 0x00, // 22 DD->Height0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, // 28 EE->BitCount0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 34 FF->ImageSize0x00, 0x00, 0x00, 0x00, 0x00, 0x00};FILE *file_bitmap = NULL;U4 u4_t_y;U4 u4_t_pixel_offset = 0;do{if(st_ap_bitmap == NULL)break;if((st_ap_bitmap->u2_bitcount != 16) && (st_ap_bitmap->u2_bitcount != 24) && (st_ap_bitmap->u2_bitcount != 32))break;if(sz_ap_path == NULL)break;file_bitmap = fopen(sz_ap_path, "wb");if(file_bitmap == NULL)break;// set bitmap head infovd_SerializeLittleEndianU4(&u1_tp_bitmap_header[2], sizeof(u1_tp_bitmap_header) + st_ap_bitmap->u4_image_size);vd_SerializeLittleEndianU4(&u1_tp_bitmap_header[10], sizeof(u1_tp_bitmap_header));vd_SerializeLittleEndianU4(&u1_tp_bitmap_header[18], st_ap_bitmap->u4_image_width);vd_SerializeLittleEndianU4(&u1_tp_bitmap_header[22], st_ap_bitmap->u4_image_height);vd_SerializeLittleEndianU2(&u1_tp_bitmap_header[28], st_ap_bitmap->u2_bitcount);vd_SerializeLittleEndianU4(&u1_tp_bitmap_header[34], st_ap_bitmap->u4_image_size);// write bitmap file headfwrite(u1_tp_bitmap_header, sizeof(u1_tp_bitmap_header), 1L, file_bitmap);// write bitmap image data, bottom to topu4_t_pixel_offset = st_ap_bitmap->u4_image_height * st_ap_bitmap->u4_widthbyte;for(u4_t_y = 0; u4_t_y < st_ap_bitmap->u4_image_height; u4_t_y++){u4_t_pixel_offset -= st_ap_bitmap->u4_widthbyte;fwrite(&st_ap_bitmap->u1_p_image_data[u4_t_pixel_offset], st_ap_bitmap->u4_widthbyte, 1L, file_bitmap);}b_t_success = TRUE;} while(0);if(file_bitmap)fclose(file_bitmap);
}

以图像数据为画布画点

设置内存中的图像数据,这里是简单的写入一个像素。可以在此基础上,结合计算机图形学的算法,进行画直线、画圆、椭圆、填充等。

void vd_SetBitmapPixel(ST_BITMAP * st_ap_bitmap, U4 u4_a_x, U4 u4_a_y, U4 u4_a_color)
{U4 u4_t_pixel_offset = 0;U2 u2_t_color = 0;do{if(st_ap_bitmap == NULL)break;if(u4_a_x >= st_ap_bitmap->u4_image_width)break;if(u4_a_y >= st_ap_bitmap->u4_image_height)break;u4_t_pixel_offset = u4_a_y * st_ap_bitmap->u4_widthbyte + u4_a_x * st_ap_bitmap->u2_bitcount / 8;switch(st_ap_bitmap->u2_bitcount){case 16:u2_t_color = BMP_RGBA32TOBMP16(u4_a_color);vd_SerializeLittleEndianU2(&st_ap_bitmap->u1_p_image_data[u4_t_pixel_offset], u2_t_color);break;case 24:vd_SerializeLittleEndianU3(&st_ap_bitmap->u1_p_image_data[u4_t_pixel_offset], u4_a_color);break;case 32:vd_SerializeLittleEndianU4(&st_ap_bitmap->u1_p_image_data[u4_t_pixel_offset], u4_a_color);break;default:break;}} while(FALSE);
}

综合应用

最后以对函数的实际使用来举例,先创建位图,然后写入数据,最后保存成BMP图像文件。

int main()
{ST_BITMAP * st_tp_bitmap = NULL;do{// create bitmap, size:20x10 format:RGB555->16// also support format RGB888->24 and RGBA8888->32st_tp_bitmap = st_g_CreateBitmap(20, 10, 16);if(st_tp_bitmap == NULL)break;// draw pixels on bitmapvd_SetBitmapPixel(st_tp_bitmap, 0, 0, BMP_RGB24(255, 0, 0)); // redvd_SetBitmapPixel(st_tp_bitmap, 1, 1, BMP_RGB24(0, 255, 0)); // greenvd_SetBitmapPixel(st_tp_bitmap, 2, 2, BMP_RGB24(0, 0, 255)); // blue// save to filevd_g_SaveBitmap(st_tp_bitmap, "test.bmp");} while(FALSE);if(st_tp_bitmap)vd_g_FreeBitmap(st_tp_bitmap);return 0;
}

保存的图像文件如下所示,黑色的背景上,按红、绿、蓝填充了三个像素。

使用十六进制编辑器查看文件

我们还可以使用十六进制编辑器来查看这个文件,可以看到文件有42 4D 开头,也就是 BM的ASCII 编码,实际上也是作为图像格式的识别码而出现的。黑色折线条上方的是文件头和信息头,黑色线条下方的是图像数据区域。因为是生成的16位的直接色图像文件,没有并没有调色板数据。

总结

对于 BMP 位图格式而言,相对是比较简单的格式,可以用上面的代码进行处理。
代码主要演示了,在内存中的图像结构体定义和内存分配、释放,图像数据的修改,以及图像文件的生成。

扩展

对于 ICO 图标文件、ANI 图标动画文件,可以在一个文件中包含多个位图以及掩码图案,格式会复杂一些。而对于 PNG、JPG 这样的格式,则需要借助 libpng、libjpeg 和 zlib 等开源库代码进行读写了。

C语言生成BMP文件相关推荐

  1. c语言生成bmp文件后不可读,怪事!!关于读bmp文件!

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 #include "stdio.h" typedef struct { char buf[28]; int biBitCount; } ...

  2. Verilog实现生成BMP文件(BMP文件格式,二进制文件读写)

    Git地址: https://gitee.com/whik/bmp_gen_c_and_verilog/tree/master/verilog BMP文件格式详解参考: BMP文件格式详解 C语言实现 ...

  3. 图像处理基本方法-c语言生成纯色BMP文件

    图像处理基本方法-C语言生成纯色BMP文件 1.目的 我们有时需要产生各种各样的纯色图片,于是设计了这个简单的基本的图像处理小程序. 本文主要实现基本的BMP图片数据的生成过程,并保存为Windows ...

  4. ffmpeg解码视频存为BMP文件

    ffmpeg解码视频存为BMP文件 分类: ffmpeg2011-07-28 12:13 8人阅读 评论(0) 收藏 举报 view plain #include <windows.h> ...

  5. [Win] 利用Memory DC抽取EXE的图标并保存为BMP文件

    预告篇- 由于时间比较紧.下次等整理好再放上来吧. 大致过程就是 创建窗口句柄->得到HDC->创建MEMDC->再copy数据->最后显示或者保存为BMP文件, 转载于:ht ...

  6. 读取SD卡里面的BMP文件 显示到TFT上

    读取SD卡里面的BMP文件 显示到TFT上 http://blog.csdn.net/yunxianpiaoyu/article/details/8841755 我刚好最近做了一个BMP565格式的图 ...

  7. C++读图片——Mac下对于bmp文件读写读取过大的解决方案

    Mac下对bmp文件处理时size过大的解决方案 当在读写bmp信息头的时候,首先对根据C和C++的对齐预编译指令进行对齐之后,当进行读文件之后会出现biWidth和biHeight过大的情况.本篇文 ...

  8. java解析bmp文件

    最近想做一个图片查看器,因为bmp的图片简单些,也就从这个入手. 运用的基本知识还是IO的,关键是在于对于"协议"的理解. 一直觉得这些个协议是个很帅气的东西.感觉就像密码一样,你 ...

  9. 【转】DCMTK开源库的学习笔记1:将DCM文件保存成BMP文件或数据流(即数组)

    转自:https://blog.csdn.net/zssureqh/article/details/8784980 DCMTK开源库介绍: DCMTK是目前最全面实现DICOM3.0标准的开源库,通过 ...

最新文章

  1. 解决Apache CXF 不支持传递java.sql.Timestamp和java.util.HashMap类型问题
  2. WPF 自定义控件的坑(蠢的:自定义控件内容不显示)
  3. AI技术在游戏开发中的五种有效尝试
  4. MongoDB 分布式部署教程
  5. jq 方法函数(淡入淡出,查找元素,过滤)遍历
  6. 记录知识点或技术方案信息
  7. 客户组网服务案列_山西seo关键词排名优化案列
  8. CentOS7安装Java,java高级面试笔试题
  9. 电脑亮度多少对眼睛好_激光治疗近视眼大概要多少费用,保护好眼睛要做到哪几点...
  10. YACC、LEX、JAVACC-------常用的编译工具
  11. git的安装和简单使用
  12. T7315 yyy矩阵折叠(长)
  13. springboot单元测试_springboot2.x基础教程:单元测试
  14. t-SNE可视化-Python实现
  15. 备案域名基础知识,网站备案新政策
  16. java 中查询余额怎么写_查询余额示例代码
  17. 《星露谷物语》mod安装超详细教程
  18. DB2 的REORG_学习(1)_REORG INDEXES/TABLE Command
  19. 如何使用 JS 破解轻量级滑块验证码
  20. 页面加密代码,附效果演示

热门文章

  1. 基于PLC控制四自由度气动式机械手设计
  2. rk3288 android7.1 横竖屏切换(动画过度)
  3. ubuntu 20.04 主题美化
  4. Selenium 之订制启动Chrome的选项(Options)
  5. Cerebral Cortex:调节γ振荡可以促进大脑连接性而改善认知障碍
  6. 关于emWin显示矢量字体TTF所踩的坑
  7. 基于小梅哥AC620开发板的NIOS II LWIP百兆以太网例程移植到自己做的板子上
  8. linux 下 su - oracle 切换不了
  9. 忧郁的loli od链接爬取
  10. win10如何使用命令行通过URL下载文件?