BMP图片读写接口函数
我很早就学习了BMP位图。印象中,那时应该是在研究AVI视频文件格式时顺便研究的,或者是研究YUV转RGB时顺便研究的。但未写文章出来,我一直以为我的学习只有在发表了文章才算是完结,否则不能算是我做过了这个事。在这里补上当初读、写BMP的函数代码。
头文件如下:
/** * @file bmp_utils.h * @author Late Lee * @date 2012-7-2 13:21:53 * @brief * BMP相关工具函数,目前只针对24位图片测试 * * 1、在VS003及GCC下编译测试通过; * 2、解决了BMP图片倒立、偏色、倾斜等问题。 * 3、BMP图像每行数据需要4字节对齐,即一行数据不足4的倍数,以0补。 * 解决此问题方法:设置2个变量: * width_byte:实际的RGB每一行的字节数 * stride_byte:4字节对齐的每一行的字节数(已对齐时两者相等) * 保存时,另外开辟一个考虑了4字节对齐的缓冲区,每拷贝一行数据(width_byte), * 跳过stride_byte个字节,即跳到4字节对齐的下一行。 * 读取时,只读width_byte,并且跳过每行最后补的0。 * 4、图像倒立:读取与保存BMP时,将数据倒过来: * 读取时,将读到的数据由下往上存放到缓冲区 * 保存时,将数据由下往上拷贝到缓冲区 * 5、偏色:BMP排序为BGR,将RGB数据的G、B调换位置即可。 * 6、倾斜:读取BMP时,未跳过补充的0。 * * 笔记: BMP图片结构,基中第1、第2部分占54字节,真彩色图没有第三部分 _______________________________ | BITMAPFILEHEADER | |_______________________________| | BITMAPINFOHEADER | |_______________________________| | n * RGBQUAD | |_______________________________| | image data | |_______________________________| * 对于2色位图,用1位表示该象素的颜色(一般0表示黑,1表示白),一个字节可以表示8个象素。调色板:2*4=8 对于16色位图,用4位表示一个象素的颜色,以一个字节可以表示2个象素。调色板:16*4=64 对于256色位图,一个字节表示1个象素。调色板:256*4=1024 对于真彩色图,三个字节表示1个象素。无调色板 * 单色BMP图:调色板占8字节,故头部占用54+8=62字节,后面为像素字节, 注意每行字节需要4字节对齐, 举例:16*16像素单色位图,一行占16/8 = 2字节,需要补2字节。 实际像素字节:16*16/2 = 32字节,补齐字节:2*16 = 32,共64字节 头部共62字节,故该图片总大小为64+62=126字节 */ #ifndef _BMP_UTILS_H #define _BMP_UTILS_H #ifdef __cplusplus extern "C" { #endif #ifdef WIN32 #include <Windows.h> #else typedef unsigned char BYTE; typedef unsigned short WORD; typedef unsigned long DWORD; typedef long LONG; #pragma pack(push) // 2字节对齐,共14 #pragma pack(2) typedef struct tagBITMAPFILEHEADER { WORD bfType; // 文件类型, 0x4d42 DWORD bfSize; // 文件总大小 WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; // 实际位图数据偏移 } BITMAPFILEHEADER; //__attribute__ ((packed)); // 40 typedef struct tagBITMAPINFOHEADER{ DWORD biSize; // 本结构体长度 LONG biWidth; // 宽(单位像素) LONG biHeight; // 高(单位像素) WORD biPlanes; // 为1 WORD biBitCount; // 像素占用位数 1(2^1=2黑白二色), 4(2^4=16色),8(2^8=256色),24(真彩色),32 DWORD biCompression; // 压缩类型,不压缩:BI_RGB(0) DWORD biSizeImage; // 位图数据大小,如果是不压缩类型,可以为0 LONG biXPelsPerMeter; // 水平分辨率,单位是每米的象素个数 LONG biYPelsPerMeter; // 垂直分辨率 DWORD biClrUsed; // 位图实际使用的颜色表中的颜色数 DWORD biClrImportant; // 位图显示过程中重要的颜色数 } BITMAPINFOHEADER; //__attribute__ ((aligned(2))); typedef struct tagRGBQUAD { BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; } RGBQUAD; typedef struct tagBITMAPINFO{ BITMAPINFOHEADER bmiHeader; RGBQUAD bmiColors[1]; } BITMAPINFO; // __attribute__ ((aligned(2))); #pragma pack(pop) #endif #undef ALIGN #define ALIGN(x, n) (((x)+(n)-1)&~((n)-1)) /** * RGB互换R、B顺序 * * @param[IN] rgb_buffer RGB缓冲区 * @param[IN] len 缓冲区大小 * * @return none * * @note * 缓冲区数据可以是RGB,也可以是BGR,该函数只是将B、G进行互换 */ void swap_rgb(unsigned char* rgb_buffer, int len); /** * 分析BMP文件头部 * * @param[IN] bmp_file BMP图片文件名称 * * @return * 0: 成功 * -1: 文件不存在或不是BMP文件 */ int analyse_bmp_file(const char* bmp_file); /** * 读取BMP图片文件 * * @param[IN] bmp_file BMP图片文件名称 * * @param[OUT] rgb_buffer RGB数据(实际为BGR) * @param[OUT] size RGB数据大小 * @param[OUT] width 图片宽 * @param[OUT] height 图片高 * * @return * 0:成功 * -1:读取文件失败,或不是BMP文件,或申请内存失败 * @note * rgb_buffer为二级指针,内存由该函数分配,需要自行释放 * rgb_buffer数据排列顺序为BGR,因此,处理时可能需要转换成RGB顺序 */ int read_bmp_file(const char* bmp_file, unsigned char** rgb_buffer, int* size, int* width, int* height); int read_bmp_file_1(const char* bmp_file, unsigned char** rgb_buffer, int* rgb_size, unsigned char** palette_buf, int* palette_len, int* width, int* height); /** * 保存BMP文件 * * @param[IN] bmp_file BMP图片文件名称 * * @param[IN] rgb_buffer RGB数据(实际为BGR) * @param[IN] width 图片宽 * @param[IN] height 图片高 * * @return * 0:成功 * -1:打开文件失败 * @note * BMP图片颜色分量实际为BGR,因此,需要事先将rgb_buffer数据排列顺序转换成BGR。 */ int write_bmp_file(const char* bmp_file, unsigned char* rgb_buffer, int width, int height); int write_bmp_file_1(const char* bmp_file, unsigned char* rgb_buffer, unsigned char* palette_buf, int* palette_len, int width, int height); #ifdef __cplusplus }; #endif #endif /* _BMP_UTILS_H */
实现代码:
#include <stdlib.h> #include <string.h> #include <math.h> #include <limits.h> #include "bmp_utils.h" //#include "debug.h" // 注:只针对24位图片 int analyse_bmp_file(const char* bmp_file) { #if 0 FILE* fp; BITMAPFILEHEADER bmpHeader; BITMAPINFOHEADER bmpInfo; int rgb_size1 = 0; int rgb_size2 = 0; int width = 0; int height = 0; int padding = 0; int stride_byte = 0; int color_num = 0; int paltette_len = 0; char* palette = NULL; fp = fopen(bmp_file, "rb"); if (fp == NULL) { printf("open file %s failed.\n", bmp_file); return -1; } fread(&bmpHeader, 1, sizeof(BITMAPFILEHEADER), fp); fread(&bmpInfo, 1, sizeof(BITMAPINFOHEADER), fp); if (bmpHeader.bfType != (('M' << 8) | 'B')) { printf("Sorry, not bmp picture.\n"); return -1; } width = bmpInfo.biWidth; height = (int)fabs((double)bmpInfo.biHeight); switch(bmpInfo.biBitCount) { case 1: color_num = 2; break; case 4: color_num = 16; break; case 8: color_num = 256; break; case 24: default: color_num = 0; break; } stride_byte = ALIGN(width*bmpInfo.biBitCount/8, 4); padding = stride_byte - width*bmpInfo.biBitCount/8; paltette_len = color_num * sizeof(RGBQUAD); rgb_size1 = bmpHeader.bfSize - sizeof(BITMAPFILEHEADER) - sizeof(BITMAPINFOHEADER) - paltette_len; rgb_size2 = stride_byte*height; // 打印结构体中每个成员 printf("file name: %s\n", bmp_file); printf("file type: %c%c %x\n", (bmpHeader.bfType)>>8, (bmpHeader.bfType)&0xff, bmpHeader.bfType); printf("file size: %d(B) = %0.2f(KB) = %0.2f(MB)\n", bmpHeader.bfSize, (float)bmpHeader.bfSize/1024.00, (float)bmpHeader.bfSize/1024.00/1024.00); printf("offset of image data: %d\n", bmpHeader.bfOffBits); ////////////////////////////////// printf("biSize: %d\n", bmpInfo.biSize); printf("width: %d\n", bmpInfo.biWidth); printf("height: %d\n", bmpInfo.biHeight); printf("Plane: %d\n", bmpInfo.biPlanes); printf("BitCount: %d\n", bmpInfo.biBitCount); printf("biCompression: %d\n", bmpInfo.biCompression); printf("biSizeImage: %d\n", bmpInfo.biSizeImage); printf("XPelsPerMeter: %d\n", bmpInfo.biXPelsPerMeter); printf("YPelsPerMeter: %d\n", bmpInfo.biYPelsPerMeter); printf("biClrUsed: %d\n", bmpInfo.biClrUsed); printf("biClrImportant: %d\n", bmpInfo.biClrImportant); printf("width*3: %d stride byte: %d padding: %d\n", width*3, stride_byte, padding); printf("rgb buffer size: %d %d\n", rgb_size1,rgb_size2); if (color_num != 0) { palette = (char *)malloc(paltette_len * sizeof(char)); fread(palette, paltette_len, 1, fp); printf("palette:\n"); //dump(palette, paltette_len); } #endif return 0; } int read_bmp_file(const char* bmp_file, unsigned char** rgb_buffer, int* size, int* width, int* height) { int ret = 0; FILE* fp; BITMAPFILEHEADER bmpHeader; BITMAPINFOHEADER bmpInfo; int tmp_width = 0; int tmp_height = 0; int rgb_size = 0; int stride_byte = 0; // 每行占用字节数(4字节对齐) int width_byte = 0; // 每行真正有效字节数 int padding = 0; // 需要对齐的字节数 unsigned char* tmp_buf = 0; int color_num = 0; int palette_len = 0; int i = 0; fp = fopen(bmp_file, "rb"); if (fp == NULL) { printf("open file %s failed.\n", bmp_file); return -1; } ret = fread(&bmpHeader, 1, sizeof(BITMAPFILEHEADER), fp); if (ret != sizeof(BITMAPFILEHEADER)) { printf("read BITMAPFILEHEADER failed.\n"); return -1; } ret = fread(&bmpInfo, 1, sizeof(BITMAPINFOHEADER), fp); if (ret != sizeof(BITMAPINFOHEADER)) { printf("read BITMAPINFOHEADER failed read: %d %d.\n", ret, sizeof(BITMAPINFOHEADER)); return -1; } if (bmpHeader.bfType != (('M' << 8) | 'B')) { printf("Sorry, not bmp picture.\n"); return -1; } tmp_width = bmpInfo.biWidth; tmp_height = (int)fabs((double)bmpInfo.biHeight); // 预防高为负数的情况 // 真正RGB数据大小 rgb_size = tmp_width * tmp_height * bmpInfo.biBitCount/8; *width = tmp_width; *height = tmp_height; *size = rgb_size; /** * 每行占用字节数,与下式结果相同 * stride_byte = (width * bmpInfo.biBitCount/8+3)/4*4; */ stride_byte = ALIGN(tmp_width*bmpInfo.biBitCount/8, 4); width_byte = tmp_width * bmpInfo.biBitCount/8; /** * 补齐字节,与下式结果相同 * padding = (4 - width * 3 % 4) % 4; * 实现未使用 */ padding = stride_byte - width_byte; // 判断调色板 switch(bmpInfo.biBitCount) { case 1: color_num = 2; break; case 4: color_num = 16; break; case 8: color_num = 256; break; case 24: default: color_num = 0; break; } // todo:读取调色板 palette_len = color_num * sizeof (RGBQUAD); // 计算偏移量与实际偏移量比较,如不等,颜色数出错 if (bmpHeader.bfOffBits != sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + palette_len) { return -1; } printf("debug--:\nfile size: %d rgb size: %d %d stride byte: %d padding: %d BitCount: %d\n", (int)bmpHeader.bfSize, rgb_size, stride_byte*tmp_height, stride_byte, padding, bmpInfo.biBitCount); if (color_num != 0) { // 跳到图像数据处 fseek(fp, palette_len, SEEK_CUR); } // 申请合适的内存 *rgb_buffer = (unsigned char *)malloc(sizeof(char) * rgb_size); if (*rgb_buffer == NULL) { return -1; } // 将读取的数据倒着存放到缓冲区(即BMP图像第一行数据放到缓冲区最后一行,等等), // 这样图像才是正常的,否则图像是倒立的 tmp_buf = *rgb_buffer + rgb_size; for (i = 0; i < tmp_height; i++) { tmp_buf -= width_byte; ret = fread(tmp_buf, 1, width_byte, fp); if (ret != width_byte) { free(*rgb_buffer); return -1; } fseek(fp, padding, SEEK_CUR); } #if 0 // 顺序读文件,读到的图像是倒立的 unsigned char* tmp_buf = *rgb_buffer; size_t readByte = 0; for (int i = 0; i < tmp_height; i++) { readByte += fread(tmp_buf, 1, width_byte, fp); fseek(fp, padding, SEEK_CUR); tmp_buf += width_byte; } #endif return 0; } int write_bmp_file(const char* bmp_file, unsigned char* rgb_buffer, int width, int height) { #define BPP 24 // 目前只考虑24色位图 BITMAPFILEHEADER bmpHeader; BITMAPINFOHEADER bmpInfo; FILE* fp = NULL; int offset = 0; int stride_byte = 0; // 每行占用字节数(4字节对齐) int width_byte = 0; // 每行真正有效字节数 int rgb_size = 0; int padding = 0; unsigned char* tmp_buf = NULL; int i = 0; fp = fopen(bmp_file, "wb"); if (fp == NULL) { printf("open %s failed\n", bmp_file); return -1; } offset = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER); //54字节 // 4字节对齐 ((width * 24 + 31) / 32) * 4 // 如已经对齐,则rowStride与实际宽一致,如不对齐rowStride会比宽大一些 // stride_byte = ((width * 24 + 31) >> 5) << 2; stride_byte = ALIGN(width*BPP/8, 4); width_byte = width*BPP/8; rgb_size = stride_byte * height; // 已考虑对齐 bmpHeader.bfType = ('M' << 8) | 'B'; bmpHeader.bfSize = offset + rgb_size; // BMP文件总大小 bmpHeader.bfReserved1 = 0; bmpHeader.bfReserved2 = 0; bmpHeader.bfOffBits = offset; bmpInfo.biSize = sizeof(BITMAPINFOHEADER); bmpInfo.biWidth = width; bmpInfo.biHeight = height; bmpInfo.biPlanes = 1; bmpInfo.biBitCount = BPP; bmpInfo.biCompression = 0; bmpInfo.biSizeImage = rgb_size; bmpInfo.biXPelsPerMeter = 0; bmpInfo.biYPelsPerMeter = 0; bmpInfo.biClrUsed = 0; bmpInfo.biClrImportant = 0; // 需要填充字节,BMP要求每一行数据必须4字节对齐,不足以0补。 //padding = (4 - width * 3 % 4) % 4; // 实际未使用到 padding = stride_byte - width_byte; printf("debug--:\nwidth: %d height: %d padding: %d rgb_size: %d, stride_byte: %d\n", width, height, padding, rgb_size, stride_byte); tmp_buf = (unsigned char *)malloc(sizeof(char) * rgb_size); if (tmp_buf == NULL) { return -1; } memset(tmp_buf, '\0', sizeof(char) * rgb_size); // 倒着拷贝到缓冲区 for (i = 0; i < height; i++) { // 每一行的实际数据为width * 3(R、G、B) memcpy(tmp_buf + i * stride_byte, rgb_buffer + (height - i - 1) * width_byte, width_byte); } fwrite(&bmpHeader, 1, sizeof(BITMAPFILEHEADER), fp); fwrite(&bmpInfo, 1, sizeof(BITMAPINFOHEADER), fp); fwrite(tmp_buf, 1, rgb_size, fp); free(tmp_buf); return 0; } // rgb --> bgr or // bgr --> rgb void swap_rgb(unsigned char* rgb_buffer, int len) { int i = 0; for (i = 0; i < len; i += 3) { unsigned char tmp; tmp = rgb_buffer[i]; rgb_buffer[i] = rgb_buffer[i + 2]; rgb_buffer[i + 1] = rgb_buffer[i + 1]; rgb_buffer[i + 2] = tmp; } }
说明:
最后写的那个RGB交换函数,是因为当初未了解libjpeg解压jpeg可以选择RGB的格式,以为只有是RGB,但BMP却只认BGR,因此就自己写了个R、B交换函数。但libjpeg本身是支持BGR的格式的。
看了几年前自己写的代码,觉得能写Doxygen风格的注释很不容易,希望自己能坚持下去,而不用理会其它的影响。
李迟 2015.7.9
近来经济拮据,如本文对阁下有帮助,可慷慨解囊赞助笔者以输出更多好文章。
[email protected] 或 微信fly_camel_fly 均可。感谢!
本文固定链接: /my-library/my-bmp-utils.html
【下一篇】在公司工作四年记