Last Updated: 2023-03-27 09:34:57 Monday
-- TOC --
BMP图像文件,即bitmap文件,其实就是没有任何压缩的RGB格式,但这种Raw格式,也是一种存储格式。(就像用wav格式存放raw声音数据一样,也需要一点规范)
Bitmap是Windows操作系统中的标准图像文件格式,可以分成两类:设备相关位图(DDB)和设备无关位图(DIB),DDB已经基本停用。
Bitmap文件格式由4部分组成:
现在,我们来拆解一个bmp文件。
文件头占14bytes
BM
>>> f = open('desk.bmp', 'rb')
>>> head = f.read(14)
>>> head[:2]
b'BM'
>>> int.from_bytes(head[2:6], 'little')
4196406 # byte
>>> head[6:10]
b'\x00\x00\x00\x00'
>>> int.from_bytes(head[10:14], 'little')
54 # means no color table
图像描述区占40 bytes,如果没有颜色表,整个bmp文件的头刚好是54bytes!
图像描述区,一般占40bytes
说一般,是因为这个区域的前4个byte,定义了这个区域的大小,也就有可能存在这个区域不是40bytes的情况:
>>> desc = f.read(40)
>>> int.from_bytes(desc[:4], 'little')
40
宽和高:
>>> int.from_bytes(desc[4:8], 'little')
1366
>>> int.from_bytes(desc[8:12], 'little')
768
plane数和bit-depth:
>>> int.from_bytes(desc[12:14], 'little')
1
>>> int.from_bytes(desc[14:16], 'little')
32 # 4 bytes per pixel
>>>
>>> 1366*768*4 + 54
4196406 # file size
这个区域后面的数据(测试用bmp文件,是微信截图保存的):
>>> int.from_bytes(desc[16:20], 'little')
0
>>> int.from_bytes(desc[20:24], 'little')
0
>>> int.from_bytes(desc[24:28], 'little')
3780
>>> int.from_bytes(desc[28:32], 'little')
3780
>>> int.from_bytes(desc[32:36], 'little')
0
>>> int.from_bytes(desc[36:40], 'little')
0
颜色表
在真彩色(24或32位)模式无颜色表。当存在颜色表的时候,每bit depth数据作为颜色表索引值,获取到对应的颜色。
其他色彩bmp图像的颜色表的大小,根据所使用的颜色模式而定:
每4字节表示一种颜色,分别是B(蓝色)、G(绿色)、R(红色)、Alpha通道(像素的透明度值,一般不需要,不透明为0xFF)。即首先4字节表示颜色号0的颜色,接下来表示颜色号1的颜色,依此类推。
数据区
32 bit depth,每4bytes表示一个pixel数据,难道不是吗?
扫描是从左到右,从下到上,raster scan。
24位RGB按照BGR
的顺序来存储每个像素的各颜色通道的值,一个像素的所有颜色分量值都存完后才存下一个下一个像素,不进行交织存储。32位数据按照BGRA的顺序存储。(这种存储方式,有个专业术语,叫做packed)
位图全部像素数据,在存储的过程中是按照自下而上,自左往右的顺序进行排列的,这点为什么是这样,我也不太清楚,我估计是历史遗留问题。还有一点,单个像素的通道存储顺序也是反着的,实际是按照BGR的顺序来存储的,这点很好理解,因为在存储过程中,RGB三个字节是同时写入的,低字节在前,所以实际在读取的时候顺序却是BGR。这两点一定要注意,在实际使用过程中很容易出错。
行padding
24位的Bitmap文件,存在行padding的问题。
有一张照片的大小为
2127*1200*3=7657200
字节,再加上两个文件头占用的54个字节,总共大小应该为7657200+54=7657254,但是实际大小却是7660854字节,与实际结果还有差距,这是怎么回事呢?实际上,这是Windows操作系统中的内存对齐造成的,Windows要求位图的每一行像素所占字节数必须被4整除,因为这样对操作系统读取数据非常方便,若不能被4整除,则在该位图每一行的十六进制码末尾"补"1至3个字节的"00"。
例如我们这张图片每行为2127个像素,每行占用
2127*3=6381
字节,6381除以4还余1,所以需要在每行再补齐3个字节,这样每行实际占用6381+3=6384个字节,总共像素占用6384*1200=7660800
字节,另外再加上两个文件头信息占用的54个字节,整张图片的大小为7660854,刚好与我们右键信息的大小一致。
用libjpeg-turbo将32位bmp文件转为jpeg
这段代码转换的jpeg图像,与bmp图像,正好上下翻转了,因为bmp文件数据区的数据存储,是从左到右,从下到上的!
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include "jpeglib.h"
#pragma comment(lib, "jpeg.lib")
#pragma comment(lib, "turbojpeg.lib")
int main(int argc, char* argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s input.bmp output.jpg\n", argv[0]);
return 1;
}
char* input_file = argv[1];
char* output_file = argv[2];
FILE* infile = fopen(input_file, "rb");
if (infile == NULL) {
fprintf(stderr, "Error opening input file: %s\n", input_file);
return 1;
}
// Read the BMP file header and extract the image width and height
unsigned char bmp_header[54];
fread(bmp_header, sizeof(unsigned char), 54, infile);
int bmp_width = *(int*)&bmp_header[18];
int bmp_height = *(int*)&bmp_header[22];
// Allocate memory for the BMP image data
int input_components = 4;
int bmp_size = bmp_width * bmp_height * input_components;
unsigned char* bmp_data = (unsigned char*)malloc(bmp_size);
fread(bmp_data, sizeof(unsigned char), bmp_size, infile);
fclose(infile);
// Initialize the JPEG compression parameters
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
FILE* outfile = fopen(output_file, "wb");
if (outfile == NULL) {
fprintf(stderr, "Error opening output file: %s\n", output_file);
return 1;
}
jpeg_stdio_dest(&cinfo, outfile);
cinfo.image_width = bmp_width;
cinfo.image_height = bmp_height;
cinfo.input_components = input_components;
cinfo.in_color_space = JCS_EXT_BGRA;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, 80, TRUE);
jpeg_start_compress(&cinfo, TRUE);
JSAMPROW row_pointer[1];
int row_stride = bmp_width * input_components;
// Convert and write the BMP image data to JPEG format
while (cinfo.next_scanline < cinfo.image_height) {
row_pointer[0] = &bmp_data[cinfo.next_scanline * row_stride];
jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
fclose(outfile);
// Free the BMP image data memory
free(bmp_data);
return 0;
}
先将BGRA数据现在内存中上下翻转,然后再走jpeg压缩,就好了!不要先jpeg压缩,再flip,这样做画面有轻微漂移。
libjpeg-turbo项目内有example.c和tjexample.c!
本文链接:https://cs.pynote.net/ag/image/202206242/
-- EOF --
-- MORE --