图形图像基础 之 bmp介绍

文章目录

    • 一、bmp相关概念
      • 位图Bitmap 与 矢量图Vector---用点表示 还是 用公式表示
      • bmp(Bitmap-File)---一种非压缩图形文件位图格式,后缀bmp
    • 二、bmp文件格式
      • 2.1 位图文件头bitmap-file header---用于进行文件的识别,14字节
      • 2.2 位图信息头bitmap information header---图像的详细信息,40字节
      • 2.3 调色板color palette(可选)
      • 2.4 位图存储数据data bits
    • 三、代码示例---从jpeg图片转化到bmp图片
    • 参考

一、bmp相关概念

位图Bitmap 与 矢量图Vector—用点表示 还是 用公式表示

计算机能以位图和矢量图格式显示图像。位图又称点阵图或光栅图,它使用我们称为像素(象素,Pixel)的一格一格的小点来描述图像。计算机屏幕其实就是一张包含大量像素点的网格。当我们把位图放大时,每一个像素小点看上去就像是一个个马赛克色块。矢量图 使用直线和曲线来描述图形,这些图形的元素是一些点、线、矩形、多边形、圆和弧线等等,它们都是通过数学公式计算获得的。位图和矢量图最简单的区别就是:矢量图可以无限放大,而且不会失真;而位图则不能。

bmp(Bitmap-File)—一种非压缩图形文件位图格式,后缀bmp

Bitmap-File图形文件是Windows采用的图形文件格式,在Windows环境下运行的所有图像处理软件都支持BMP图像文件格式。Windows系统内部各图像绘制操作都是以BMP为基础的。Windows 3.0以后的BMP文件都是指设备无关位图(DIB,device-independent bitmap)。BMP位图文件默认的文件扩展名是.BMP,有时它也会以.DIB或.RLE作扩展名。
bmp本身是非压缩的,相对jpeg这类压缩的图片size要大得多,但这也是它的特点,设备无关化,无需任何算法处理,何以在任何设备上显示。

二、bmp文件格式

BMP格式的文件从头到尾依次是:1、bmp文件头(bmp file header),共14字节;2、位图信息头(bitmap information),共40字节;3、调色板(color palette),可选;4、位图数据(data bits);
我们使用如下这张图片进行距离分析:
图形图像基础 之 bmp介绍_第1张图片
图形图像基础 之 bmp介绍_第2张图片

2.1 位图文件头bitmap-file header—用于进行文件的识别,14字节

typedef struct { 
unsigned short int type; /* 文件类型 常见BM(0x42,0x4d) */ 
unsigned int size;/* 文件大小 */ 
unsigned short int reserved1, reserved2; /* 保留位 */ 
unsigned int offset;/* 数据区在文件中的位置偏移量byte */ 
} bmp_file_header;

图形图像基础 之 bmp介绍_第3张图片
0~ 1字节:0x42,0x4d,字符BM,表示文件类型;2~ 5字节:0xc0036 = 786468 = 768kB,和文件大小一样;6~ 9字节:保留位;A~ D字节:0x36 = 54,表示54字节开始是真实的数据偏移;

2.2 位图信息头bitmap information header—图像的详细信息,40字节

typedef struct { 
	unsigned int biSize; /* 位图信息头的大小 */ 
	int biWidth; /* 位图的宽度,单位是像素 */
	int biHeight; /* 位图的高度,单位是像素, 如果该值是一个负数表示倒向位图 */
	unsigned short int biPlanes; /* 为目标设备说明位面数,其值将总是被设为1 */
	unsigned short int biBitCount; /* 说明bit数/像素,其值为1、4、8、16、24、或32 */
	unsigned int biCompression; /* 压缩的类型,常见是0表示非压缩,见下表 */
	unsigned int biSizeImage; /* 图像的大小,当用BI_RGB格式-非压缩时,可设置为0 */
	unsigned int biXPelsPerMeter; /* 水平分辨率,用像素/米表示 */
	unsigned int biYPelsPerMeter; /* 垂直分辨率,用像素/米表示 */
	unsigned int biClrUsed; /* 使用的彩色表中的颜色索引数(设为0的话,则说明使用所有调色板项) */
	unsigned int biClrImportant; /* 对图像显示有重要影响的颜色索引的数目,如果是0表示都重要 */
} bmp_info_header;

压缩字段:
图形图像基础 之 bmp介绍_第4张图片
图形图像基础 之 bmp介绍_第5张图片
0~ 3字节:0x28=40 ,文件头长度40字节;4~ 7字节:0x200 = 512,图像宽度;8~ 11字节:0x200 = 512,图像高度;12~ 13字节:0x1固定;14~ 15字节:0x18=24 bit每像素;16~ 19字节:0x0 非压缩;20~ 23字节:0xc0000=786432字节,文件数据长度;

2.3 调色板color palette(可选)

当前这个文件没有调色板(从文件头看出data数据从54bit开始,而文件头+信息头已经54bit了)。
调色板怎么理解?我们常见的像素数据本身代表了这个像素的RGB数值,比如RGB888这种格式,位宽24bit,R、G、B分别占用8bit组成一个像素。还有另一种方法,类似hash表,像素内容存储的是一个索引坐标,通过索引去一个数据数组中找到真正的RGB具体数值,而这个数据数组就是调色板 。调色板可以包含很多条目,条目个数就是图像中所使用的颜色的个数。每个条目用来描述一种颜色,包含4个字节,其中三个表示蓝色、绿色和红色,第四个字节没有使用(大多数应用程序将它设为0);对于每个字节,数值0表示该颜色分量在当前的颜色中没有使用,而数值255表示这种颜色分量使用最大的强度。

typedef struct  {
    unsigned char rgbBlue;
    unsigned char rgbGreen;
    unsigned char rgbRed;
    unsigned char rgbReserved;
} rgb_quad;

调色版的本质就是,当画面颜色种类较少,可以用更少的空间存储文件。

2.4 位图存储数据data bits

从文件头可以看到,54字节开始是真实的数据。从文件信息头上看,是RGB非压缩的图像,每个像素24字节,即R、G、B分别占用8bit组成一个像素,默认存储数据位B、G、R顺序(低地址到高地址),这个和jpeg不同。全部数据组成了接下来的内容。注意,通常bmp的像素是从下到上、从左到右保存的,相当于第一个数据其实是最后一行第一个点。每一行的大小都向上取整为4字节(32位DWORD)的倍数,来保证每一行的起始地址必须为4的倍数;
图形图像基础 之 bmp介绍_第6张图片

三、代码示例—从jpeg图片转化到bmp图片

以下为我写的一个jpeg转bmp图片的c代码。jpeg不熟悉可以看我这篇文章:https://blog.csdn.net/runafterhit/article/details/119300530
几个前提:这里我默认只支持rgb888这种位宽的格式,写死了bit深度24bit,没有考虑bmp每行4字节对齐(分辨率选择保证),比较简单,没有用调色板缩小文件size。
几个注意要点:1、bmp的每个点是BRG,而jpeg是RGB,每个点数据需要反序;2、bmp是从最后一行到第一行,也需要反序;

#include "stdio.h"
#include "stdlib.h"
#include "jpeglib.h"
#include 
#include 
struct my_error_mgr {
  struct jpeg_error_mgr pub;    /* "public" fields */
  jmp_buf setjmp_buffer;        /* for return to caller */
};
typedef struct my_error_mgr * my_error_ptr;
void my_error_exit (j_common_ptr cinfo) {
    my_error_ptr myerr = (my_error_ptr) cinfo->err;
    (*cinfo->err->output_message) (cinfo);
    longjmp(myerr->setjmp_buffer, 1);
}

#pragma pack(1)
typedef struct { 
	unsigned short int type; /* 文件类型 常见BM(0x42,0x4d) */ 
	unsigned int size;/* 文件大小 */ 
	unsigned short int reserved1, reserved2; /* 保留位 */ 
	unsigned int offset;/* 数据区在文件中的位置偏移量byte */ 
} bmp_file_header;

typedef struct { 
	unsigned int biSize; /* 位图信息头的大小 */ 
	unsigned int biWidth; /* 位图的宽度,单位是像素 */
	unsigned int biHeight; /* 位图的高度,单位是像素, 如果该值是一个负数表示倒向位图 */
	unsigned short int biPlanes; /* 为目标设备说明位面数,其值将总是被设为1 */
	unsigned short int biBitCount; /* 说明bit数/像素,其值为1、4、8、16、24、或32 */
	unsigned int biCompression; /* 压缩的类型,常见是0表示非压缩,见下表 */
	unsigned int biSizeImage; /* 图像的大小,当用BI_RGB格式-非压缩时,可设置为0 */
	unsigned int biXPelsPerMeter; /* 水平分辨率,用像素/米表示 */
	unsigned int biYPelsPerMeter; /* 垂直分辨率,用像素/米表示 */
	unsigned int biClrUsed; /* 使用的彩色表中的颜色索引数(设为0的话,则说明使用所有调色板项) */
	unsigned int biClrImportant; /* 对图像显示有重要影响的颜色索引的数目,如果是0表示都重要 */
} bmp_info_header;
#pragma pack()

void write_bmp_file_header(FILE *outfile, unsigned int size) {

	bmp_file_header bfheader = {0};
	bfheader.type = 0x4d42; /* 文件类型 常见BM(0x42,0x4d) */ 
	bfheader.size = size + 0x36;/* 文件大小,数据data大小+文件头+信息头 */ 
	bfheader.offset = 0x36;/* 数据区在文件中的位置偏移量byte */ 
	fwrite(&bfheader, sizeof(bmp_file_header), 1, outfile);
	return;
}

void write_bmp_info_header(FILE *outfile, unsigned int size, int width, int height) {
	bmp_info_header bfinfo = {0};

	bfinfo.biSize = 0x28; /* 位图信息头的大小 */ 
	bfinfo.biWidth = width; /* 位图的宽度,单位是像素 */
	bfinfo.biHeight = height; /* 位图的高度,单位是像素 */
	bfinfo.biPlanes = 1; /* 为目标设备说明位面数,其值将总是被设为1 */
	bfinfo.biBitCount = 24; /* 说明bit数/像素,其值为1、4、8、16、24、或32 */
	bfinfo.biCompression = 0; /* 压缩的类型,常见是0表示非压缩 */
	bfinfo.biSizeImage = size; /* 图像的大小,当用BI_RGB格式-非压缩时,可设置为0 */
	bfinfo.biXPelsPerMeter = 0; /* 水平分辨率,用像素/米表示 */
	bfinfo.biYPelsPerMeter = 0; /* 垂直分辨率,用像素/米表示 */
	bfinfo.biClrUsed = 0; /* 使用的彩色表中的颜色索引数 */
	bfinfo.biClrImportant = 0; /* 对图像显示有重要影响的颜色索引的数目 */
	fwrite(&bfinfo, sizeof(bmp_info_header), 1, outfile);
	return;
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("please intput like: ./a.out xxx.jpeg\n");
    }
	// 打开输入文件
    FILE *infile = fopen(argv[1], "rb");
	if (infile == NULL) {
        printf("intput file %s open failed!\n", argv[1]);
        return -1;
    }
	// 创建输出文件
    FILE *outfile = fopen("./output.bmp", "w+");
	if (outfile == NULL) {
        printf("out file open failed!\n");
        return -1;
    }

	// 定义cinfo数据结构,libjpeg定义的解码器上下文
    struct jpeg_decompress_struct cinfo;
	// 设置报错回调,如果libjpeg解析过程出错,会调用
    struct my_error_mgr jerr;
    cinfo.err = jpeg_std_error(&jerr.pub);
    if (setjmp(jerr.setjmp_buffer)) {
        printf("set jmp error failed!\n");
        fclose(infile);
        return -1;
    }

	// 1.jpeg开始解码准备
	// 创建解码器
    jpeg_create_decompress(&cinfo);
	// 设置解码器输入,绑定文件
    jpeg_stdio_src(&cinfo, infile);
	// 读取文件头,这一步之后cinfo中文件信息才可信
    jpeg_read_header(&cinfo, TRUE);
	// 开始解码,默认输出是RGB,还可以cinfo.out_color_space设置
    jpeg_start_decompress(&cinfo);
    // 计算输出的行stride,jpeg库是按行往外读取,要计算每行读取多少数据
    int row_stride = cinfo.output_width * cinfo.output_components;
	// 调用libjpeg内存申请接口,申请一个一行长的buffer
    JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
	// 申请一个完整的buffer存储jpeg的输出
    char *jpegout = malloc(cinfo.output_height * row_stride);
	
	// 2.写bmp文件文件头和信息头
	write_bmp_file_header(outfile, cinfo.output_height * row_stride);
	write_bmp_info_header(outfile, cinfo.output_height * row_stride, cinfo.output_width, cinfo.output_height);
	
	// 3.读取jpeg解码内容,写到bmp文件中
    // 循环从libjpeg读取行 到 buffer,然后将buffer内容写入内存
	while (cinfo.output_scanline < cinfo.output_height) {
        jpeg_read_scanlines(&cinfo, buffer, 1);
		char *src = buffer[0];
		char *dst = jpegout + (cinfo.output_scanline - 1) * row_stride;
		int index;
		// 排序调整,jpeg是RGB顺序,bmp是BRG,再放入内存时需要调整
		for (index = 0; index < cinfo.output_width; index++) {
			*(dst + index*3 + 2) = *(src + index*3);
			*(dst + index*3 + 1) = *(src + index*3 + 1);
			*(dst + index*3) = *(src + index*3 + 2);
		}
    }

	// bmp从最后一行开始存数据,一直存到第一行,因此需要调整顺序
	int i;
	for (i = cinfo.output_height - 1; i >=0; i--) {
        	fwrite(jpegout + i * row_stride, row_stride, 1, outfile);
	}

	// 释放内存
    free(jpegout);
	// 关闭全部文件
    close(infile);
    close(outfile);
	return 0;
}
// 编译
gcc sample_jpeg.c -ljpeg
// 测试,查看输出的output.bmp文件
./a.out ./test3.jpeg

参考

维基百科:https://chi.jinzhao.wiki/wiki/BMP
https://blog.csdn.net/qingchuwudi/article/details/25785307

你可能感兴趣的:(音视频领域业务)