当前位置: 首页 > 我的程序代码 > 正文

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

本文固定链接: http://www.latelee.org/my-library/my-bmp-utils.html

如无特别说明,迟思堂工作室文章均为原创,转载请注明: BMP图片读写接口函数 | 迟思堂工作室

目前暂无评论

发表评论

*

快捷键:Ctrl+Enter