计算机能以位图和矢量图格式显示图像。位图又称点阵图或光栅图,它使用我们称为像素(象素,Pixel)的一格一格的小点来描述图像。计算机屏幕其实就是一张包含大量像素点的网格。当我们把位图放大时,每一个像素小点看上去就像是一个个马赛克色块。矢量图 使用直线和曲线来描述图形,这些图形的元素是一些点、线、矩形、多边形、圆和弧线等等,它们都是通过数学公式计算获得的。位图和矢量图最简单的区别就是:矢量图可以无限放大,而且不会失真;而位图则不能。
Bitmap-File图形文件是Windows采用的图形文件格式,在Windows环境下运行的所有图像处理软件都支持BMP图像文件格式。Windows系统内部各图像绘制操作都是以BMP为基础的。Windows 3.0以后的BMP文件都是指设备无关位图(DIB,device-independent bitmap)。BMP位图文件默认的文件扩展名是.BMP,有时它也会以.DIB或.RLE作扩展名。
bmp本身是非压缩的,相对jpeg这类压缩的图片size要大得多,但这也是它的特点,设备无关化,无需任何算法处理,何以在任何设备上显示。
BMP格式的文件从头到尾依次是:1、bmp文件头(bmp file header),共14字节;2、位图信息头(bitmap information),共40字节;3、调色板(color palette),可选;4、位图数据(data bits);
我们使用如下这张图片进行距离分析:
typedef struct {
unsigned short int type; /* 文件类型 常见BM(0x42,0x4d) */
unsigned int size;/* 文件大小 */
unsigned short int reserved1, reserved2; /* 保留位 */
unsigned int offset;/* 数据区在文件中的位置偏移量byte */
} bmp_file_header;
0~ 1字节:0x42,0x4d,字符BM,表示文件类型;2~ 5字节:0xc0036 = 786468 = 768kB,和文件大小一样;6~ 9字节:保留位;A~ D字节:0x36 = 54,表示54字节开始是真实的数据偏移;
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;
压缩字段:
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字节,文件数据长度;
当前这个文件没有调色板(从文件头看出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;
调色版的本质就是,当画面颜色种类较少,可以用更少的空间存储文件。
从文件头可以看到,54字节开始是真实的数据。从文件信息头上看,是RGB非压缩的图像,每个像素24字节,即R、G、B分别占用8bit组成一个像素,默认存储数据位B、G、R顺序(低地址到高地址),这个和jpeg不同。全部数据组成了接下来的内容。注意,通常bmp的像素是从下到上、从左到右保存的,相当于第一个数据其实是最后一行第一个点。每一行的大小都向上取整为4字节(32位DWORD)的倍数,来保证每一行的起始地址必须为4的倍数;
以下为我写的一个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