BMP图片读写接口函数

来源:互联网 发布:echo linux 写入文件 编辑:程序博客网 时间:2024/04/30 08:47

我很早就学习了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 __cplusplusextern "C" {#endif#ifdef WIN32#include <Windows.h>#elsetypedef 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));// 40typedef 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 <stdio.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 --> rgbvoid 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


0 0
原创粉丝点击