图片显示
BMP格式解码
BMP是一种常见的图片文件格式,广泛的用于存储像素数据。
1、BMP文件的基本构成
BMP文件主要由以下部分组成:
- 头文件:描述文件的基本信息,例如文件大小和数据偏移量
- 信息头:描述图像的详细信息,例如宽度、高度、颜色等。
- 调色板【可选】:对于地位深度的图像,会包含调色板,用于将索引映射带具体的RGB颜色。
- 像素数据:实际的图像数据,存储方式却决于颜色深度和压缩格式。
2、解码步骤
读取文件头(14字节)
- 2字节:文件标识符,通常BM(0x424D),表示这是BMP文件。
- 4字节:文件总大小,字节为单位。
- 4字节:保留字段,通常为0,无意义。
- 4字节:像素数据起始偏移量,告诉我们从文件开头到像素数据的距离。
读取信息头(通常为40字节)
4字节:信息头大小。
4字节:图像宽度。
4字节:图像高度。
2字节:颜色平面数,通产为1
2字节:每像素位数,常见的1、4、8、16、24、32.
4字节:压缩方式,0:无压缩、1:PLE8位;2、RLE 4位。
偏移 34-37(4字节):像素数据大小(字节),若无压缩可忽略。
偏移 38-41(4字节):水平分辨率(像素/米),可忽略。
偏移 42-45(4字节):垂直分辨率(像素/米),可忽略。
偏移 46-49(4字节):调色板颜色数,0表示默认值(2^n,n为颜色深度)。
偏移 50-53(4字节):重要颜色数,通常为0。
读取调色板(如果存在)
如果颜色深度≤8(如1位、4位、8位),会有调色板。
每个颜色占用4字节,按BGR顺序存储(蓝、绿、红、保留字节,通常为0)。
调色板大小 = 颜色数 × 4字节,颜色数由信息头中的字段或默认值(2^颜色深度)确定。
24位及以上颜色深度通常没有调色板,直接使用RGB值。
读取像素数据
- 像素数据的存储方式取决于颜色深度和压缩格式。
- 对于无压缩的BMP:
- 行字节数:根据宽度和颜色深度计算。每行字节数必须是4的倍数,若不足则填充0
- 存储顺序:通常从图像底部开始,自左向右逐行向上(若高度为正值)。
- 颜色格式:
- 24位:每个像素3字节,按BGR顺序(蓝、绿、红)。
- 32位:每个像素4字节,按BGRA顺序(蓝、绿、红、透明度)。
代码分析
1、全局变量定义
1 | char bmp_files[MAX_BMP_FILES][MAX_PATH_LEN]; |
bmp_files:二维字符数组,用于存储 BMP 文件的完整路径(如 “0:/PICTURE/image.bmp”)。MAX_BMP_FILES 和 MAX_PATH_LEN 在头文件中定义。
bmp_file_count:记录扫描到的 BMP 文件数,初始为 0。
current_bmp_index:当前显示的 BMP 文件索引,初始为 -1(未选择)。
img_file:FATFS 的文件对象,用于操作 SD 卡上的文件。
img_buffer:指向动态分配的内存,用于存储 BMP 像素数据,初始为 NULL。
img_dsc:LVGL 的图像描述符,定义图像的宽度、高度、颜色格式等。
2、stricmp 函数:不区分大小写的字符串比较
1 | int stricmp(const char *s1, const char *s2) { |
- 如果字符是大写字母A到Z,则通过+32转换为小写。
- 如果字符不相同,返回插值。
3、scan_bmp_files 函数:扫描BMP文件
1 | void scan_bmp_files(void) { |
- 打开图片目录。
- 读取目录项:使用f_readdir 逐个读取目录中文件的信息,存储在fno中。
- 使用strrchr找文件名中.后面的名字,提取字符串。
- 输出打印找到的每个BMP文件路劲和总数。
4、load_image_from_sd 函数:加载并显示BMP图像
参数检查
1
2
3
4if (index < 0 || index >= bmp_file_count) {
printf("无效的 BMP 索引: %d\r\n", index);
return;
}释放就缓冲区
1
2
3
4if (img_buffer != NULL) {
myfree(SRAMEX, img_buffer);
img_buffer = NULL;
}打开找到的BMP文件
1
2
3
4
5res = f_open(&img_file, bmp_files[index], FA_READ);
if (res != FR_OK) {
printf("打开文件失败: %s,错误码: %d\r\n", bmp_files[index], res);
return;
}检查文件大小
1
2
3
4
5
6uint32_t file_size = f_size(&img_file);
if (file_size > MAX_BMP_SIZE) {
printf("BMP 文件过大: %s,大小: %lu 字节\r\n", bmp_files[index], file_size);
f_close(&img_file);
return;
}读取BMP文件头
1
2
3
4
5
6res = f_read(&img_file, header_buf, sizeof(header_buf), &br);
if (res != FR_OK || br != sizeof(header_buf)) {
printf("读取 BMP 文件头失败\r\n");
f_close(&img_file);
return;
}- 读取前 54 字节(文件头 14 字节 + 信息头 40 字节),存储到 header_buf 中。
解析BMP信息
1
2
3
4
5
6bmp_header = (BITMAPINFOHEADER *)(header_buf + 14);
width = bmp_header->biWidth;
height = bmp_header->biHeight;
bpp = bmp_header->biBitCount;
data_offset = *(uint32_t *)(header_buf + 10);
img_size = width * height * (bpp / 8);- 从头信息中提取高度、宽度、位深度、像素偏移量
- 计算像素数据大小。
检查位深度
1
2
3
4
5if (bpp != 16 && bpp != 24 && bpp != 32) {
printf("不支持的 BMP 格式,位深度: %d\r\n", bpp);
f_close(&img_file);
return;
}分配缓冲区,并读取像素数据
1
2
3img_buffer = mymalloc(SRAMEX, img_size);
f_lseek(&img_file, data_offset);
res = f_read(&img_file, img_buffer, img_size, &br);转换为RGB565格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14if (bpp == 32) {
uint8_t *src = img_buffer;
uint16_t *dst = (uint16_t *)mymalloc(SRAMEX, width * height * 2);
for (uint32_t i = 0; i < width * height; i++) {
uint8_t b = src[i * 4 + 0] >> 3;
uint8_t g = src[i * 4 + 1] >> 2;
uint8_t r = src[i * 4 + 2] >> 3;
uint8_t a = src[i * 4 + 3];
dst[i] = (a < 128) ? 0x0000 : (r << 11) | (g << 5) | b;
}
myfree(SRAMEX, img_buffer);
img_buffer = (uint8_t *)dst;
img_size = width * height * 2;
}将 BGRA(蓝绿红透明)转换为 RGB565(5位红、6位绿、5位蓝)。
如果透明度(a)< 128,则像素设为透明(0x0000)。
RGB565是一种16位的颜色编码格式:
R:5位
G:6位
B:5位
在内存中表示如下:
1
RRRRRGGG GGGBBBBB
32位代表着R=8位、G=8位、B=8位、透明色=8位。
8位颜色代表着颜色的每个颜色,就像8位的红色右2^8=256种颜色细分。
5 位颜色:用 5 位表示,只能表示 0 到 31 的值(2⁵ = 32 种可能)。
6 位颜色:用 6 位表示,能表示 0 到 63 的值(2⁶ = 64 种可能)。
BMP文件种假设使用32位的,而屏幕使用是RGB565.则需要从8位转化为5位或者6位。需要一一对应起来。
- 从8位转换为5位:相当于256种颜色转换为32种颜色。所以要比例是:256/32=8.
- 在二进制种8可以用右移3位来表示。
1
2
3
4
5
6
7
8
9
10
11else if (bpp == 24) {
uint8_t *src = img_buffer;
uint16_t *dst = (uint16_t *)img_buffer;
for (uint32_t i = 0; i < width * height; i++) {
uint8_t b = src[i * 3 + 0] >> 3;
uint8_t g = src[i * 3 + 1] >> 2;
uint8_t r = src[i * 3 + 2] >> 3;
dst[i] = (r << 11) | (g << 5) | b;
}
img_size = width * height * 2;
}- 将 BGR 转换为 RGB565,直接覆盖原缓冲区。
垂直翻转图像
1
2
3
4
5
6
7
8uint16_t *buf = (uint16_t *)img_buffer;
for (uint16_t y = 0; y < height / 2; y++) {
for (uint16_t x = 0; x < width; x++) {
uint16_t temp = buf[y * width + x];
buf[y * width + x] = buf[(height - 1 - y) * width + x];
buf[(height - 1 - y) * width + x] = temp;
}
}- BMP 文件存储顺序:BMP 文件的像素数据通常从图像的底部开始,按行顺序存储到顶部(如果信息头中的高度字段 biHeight 为正值)。
配置LVGL并显示
1
2
3
4
5
6
7
8
9img_dsc.header.w = width;
img_dsc.header.h = height;
img_dsc.header.cf = LV_IMG_CF_TRUE_COLOR;
img_dsc.data_size = img_size;
img_dsc.data = img_buffer;
lv_img_set_src(img_obj, &img_dsc);
lv_img_set_zoom(img_obj, 128); // 50% 缩放
lv_obj_set_pos(img_obj, 100, 100);