micropython驱动ST7789v液晶屏幕显示24位真彩BMP文件图片

继续折腾ST7789v液晶屏幕,这次我们要在屏幕上显示BMP文件图片。

一、BMP图片文件格式

BMP图片文件格式问题,网上有很多文章能找到,我也找了很多资料来学习。关于文件头的问题,网上的文章大概都讲得很透彻。我只是拿这个ST7789v液晶来玩玩,没那么多时间去研究和兼容每种格式,所以我只打算锚定其中一种比较简单的24位真彩色格式来显示在我的液晶上,其他格式可以在电脑上转换成24位真彩色格式后再传给液晶。

所以,在文章的开头,我还是得啰嗦一下24位真彩色格式的BMP文件结构。

BMP文件包含四个部分:
1.位图文件头(BITMAPFILEHEADER)(14字节)
2.位图信息头(BITMAPINFOHEADER)(40字节)
3.颜色表*(RGBQUAD[])(不一定存在)
4.像素阵列(Pixels[][])(图像数据)

其中1和2是固定的大小;3在24位真彩色格式中是没有的,可以忽略;4就是图像数据了。

1.文件头

文件头的结构定义如下:

typedef struct tagBITMAPFILEHEADER{
        WORD    bfType;                  // 位图文件的类型,必须为BMP (2个字节)
        DWORD    bfSize;                  // 位图文件的大小,以字节为单位 (4个字节)
        WORD    bfReserved1;             // 位图文件保留字,必须为0 (2个字节)
        WORD    bfReserved2;             // 位图文件保留字,必须为0 (2个字节)
        DWORD    bfOffBits;               // 位图数据的起始位置,以相对于位图 (4个字节)
    } BITMAPFILEHEADER;

其中文件的大小bfSize稍微有点用,可以用以下代码读取出来:

f.seek(2)#文件大小
buff=f.read(4)
fileSize=buff[3]*1024+buff[2]*512+buff[1]*256+buff[0]
print("BMP文件大小:",fileSize,"字节")

下图是本文所用的一张图片用二进制编辑器打开后的文件大小数值。

 注意,低位在后,高位在前。图中文件大小是十六进制的0x00016926,十进制是92454字节。

后续占用多个字节的数值都是用这种低位在后,高位在前的规则。

2.信息头

信息头的结构体定义如下:
typedef struct tagBITMAPINFOHEADER{
        DWORD biSize;             // 本结构所占用字节数  (4个字节)
        LONG biWidth;              // 位图的宽度,以像素为单位(4个字节)
        LONG biHeight;             // 位图的高度,以像素为单位(4个字节)
        WORD biPlanes;            // 目标设备的级别,必须为1(2个字节)
        WORD biBitCount;         // 每个像素所需的位数,必须是1(双色)、// 4(16色)、8(256色)、
                                //24(真彩色)或32(增强真彩色)之一 (2个字节)
        DWORD biCompression;     // 位图压缩类型,必须是 0(不压缩)、 1(BI_RLE8 
                                // 压缩类型)或2(BI_RLE4压缩类型)之一 ) (4个字节)
        DWORD biSizeImage;         // 位图的大小,以字节为单位(4个字节)
        LONG biXPelsPerMeter;      // 位图水平分辨率,每米像素数(4个字节)
        LONG biYPelsPerMeter;   // 位图垂直分辨率,每米像素数(4个字节)
        DWORD biClrUsed;        // 位图实际使用的颜色表中的颜色数(4个字节)
        DWORD biClrImportant;   // 位图显示过程中重要的颜色数(4个字节)
    } BITMAPINFOHEADER;

其中有几个信息是必须要取出来的。

biWidth;              // 位图的宽度,以像素为单位(4个字节),--即图片横向的像素宽度。

biHeight;             // 位图的高度,以像素为单位(4个字节),--即图片纵向的像素高度。

biBitCount;         // 每个像素所需的位数,必须是1(双色)、// 4(16色)、8(256色)、
                                //24(真彩色)或32(增强真彩色)之一 (2个字节)。---判断图片格式是不是24位真彩色

3.颜色表

24位真彩色格式没有颜色表,直接忽略。

4.像素阵列

很多文章里没有详细介绍这部分的格式,我也走了写弯路,所以我必须把这部分详细讲一下。

24位真彩色文件格式里,像素阵列是从第55个字节开始的(文件头14+信息头40),从0开始的话是54。

1个像素占3个字节,顺序分别是B、G、R(注意顺序不是RGB!!!)。

但是的但是,你要注意了!第55字节并不是图片左上第一个像素点!!!

数据的规律是这样的。

首先,由于某些原因(DWORD),像素阵列对图片每一行数据的字节数进行了约束,必须是4的整数倍。比如,图像横向时5个像素点,每像素3字节,3*5=15字节;为了是4的倍数,这时就会填充以0x00填充1个字节。这时图片一行所占用的字节数是16,而不是15,这个知识点网上大多数文章都提到了。

然后下面我要讲的这个知识点网上那些文章就没有告诉你了。

用个例子说明:

假设一张图片是横向5像素,纵向4像素。前面讲了,横向每行占用的字节数是16字节。一共4行,占用的字节数就应该是16*4=64字节。

这64字节在BMP文件中的存放时,行顺序是倒着像下图这样存放的(图中蓝色箭头的顺序):

即:首先是最后一行,然后倒数第二行。。。。最后是第一行。

如果不知道这个规律,你的图像显示可能变成这个样子(倒着显示):

二、源代码

好了,知识点到此就讲完了,剩下的就是源代码了。

'''
    本程序只处理24位真彩色bmp文件。24位真彩色即RGB888,分别用8位(1个字节)
来表达一个像素点的R、G、B色彩信息。
    BMP文件包含四个部分:
    1.位图文件头(BITMAPFILEHEADER)
    2.位图信息头(BITMAPINFOHEADER)
    3.颜色表*(RGBQUAD[])----不一定存在
    4.像素阵列(Pixels[][])

各个部分的结构:
    1.文件头(14字节)
    typedef struct tagBITMAPFILEHEADER{
        WORD    bfType;                  // 位图文件的类型,必须为BMP (2个字节)
        DWORD    bfSize;                  // 位图文件的大小,以字节为单位 (4个字节)
        WORD    bfReserved1;             // 位图文件保留字,必须为0 (2个字节)
        WORD    bfReserved2;             // 位图文件保留字,必须为0 (2个字节)
        DWORD    bfOffBits;               // 位图数据的起始位置,以相对于位图 (4个字节)
    } BITMAPFILEHEADER;
    
    2.信息头(40字节),用于描述大小等信息
    typedef struct tagBITMAPINFOHEADER{
        DWORD biSize;             // 本结构所占用字节数  (4个字节)
        LONG biWidth;              // 位图的宽度,以像素为单位(4个字节)
        LONG biHeight;             // 位图的高度,以像素为单位(4个字节)
        WORD biPlanes;            // 目标设备的级别,必须为1(2个字节)
        WORD biBitCount;         // 每个像素所需的位数,必须是1(双色)、// 4(16色)、8(256色)、
                                //24(真彩色)或32(增强真彩色)之一 (2个字节)
        DWORD biCompression;     // 位图压缩类型,必须是 0(不压缩)、 1(BI_RLE8 
                                // 压缩类型)或2(BI_RLE4压缩类型)之一 ) (4个字节)
        DWORD biSizeImage;         // 位图的大小,以字节为单位(4个字节)
        LONG biXPelsPerMeter;      // 位图水平分辨率,每米像素数(4个字节)
        LONG biYPelsPerMeter;   // 位图垂直分辨率,每米像素数(4个字节)
        DWORD biClrUsed;        // 位图实际使用的颜色表中的颜色数(4个字节)
        DWORD biClrImportant;   // 位图显示过程中重要的颜色数(4个字节)
    } BITMAPINFOHEADER;

    3.颜色表
    typedef struct tagRGBQUAD 
    {
      BYTE rgbBlue;          // 蓝色的亮度(值范围为0-255)
      BYTE rgbGreen;         // 绿色的亮度(值范围为0-255)
      BYTE rgbRed;           // 红色的亮度(值范围为0-255)
      BYTE rgbReserved;      // 保留,必须为0
    } RGBQUAD;

    可以看到一个RGB表项为4个字节。
    颜色表中RGBQUAD结构数据的个数由位图信息头中的biBitCount来确定:
    当biBitCount=1, 4, 8时,分别有2, 16,256个表项
    !!!!!!!!当biBitCount=24时,没有颜色表项!!!!!!!!!

    4.像素阵列
    记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。位图的一个像素值所占的字节数如下:
    当biBitCount=1时,8个像素占1个字节;
    当biBitCount=4时,2个像素占1个字节;
    当biBitCount=8时,1个像素占1个字节;
    当biBitCount=24时,1个像素占3个字节,分别是R、G、B;
    Windows规定一个扫描行所占的字节数必须是4的倍数(即以long为单位),不足的以0填充。

'''


f=open('csdn.bmp',"rb")
buff=f.read(1)
cnt=0

f.seek(2)#文件大小
buff=f.read(4)
fileSize=buff[3]*1024+buff[2]*512+buff[1]*256+buff[0]
print("BMP文件大小:",fileSize,"字节")


f.seek(18)#水平分辨率
buff=f.read(4)
HResolution=buff[3]*1024+buff[2]*512+buff[1]*256+buff[0]
print("图片水平分辨率:",HResolution,"像素")

f.seek(22)#垂直分辨率
buff=f.read(4)
VResolution=buff[3]*1024+buff[2]*512+buff[1]*256+buff[0]
print("图片垂直分辨率:",VResolution,"像素")


f.seek(28)#每个像素所需的位数
buff=f.read(2)
colorMode=buff[1]*256+buff[0]
print("颜色模式:",colorMode)


BMPBuffer=bytearray(0) #准备传给液晶的buffer

#计算图片每行占用的字节数(向上往4的整数倍靠)
if HResolution*3%4==0:
    bytesPerRow=(HResolution*3//4)*4
else:
    bytesPerRow=(HResolution*3//4+1)*4

rgb565=bytearray(2)

for rowCnt in range(VResolution-1,-1,-1):#行号要倒着来
    f.seek(bytesPerRow*rowCnt+54)#注意偏移量54,因为图像数据是从54字节开始的
    for cntInARow in range(int(bytesPerRow/3)):#读取行中的有效数据,填充的0x00将被忽略
        buff=f.read(3)#RGB888格式,一次读取3字节
        #从24位真彩色RGB888转换为RGB565格式
        blue=buff[0]&0xf8#注意第一字节为蓝色blue数据
        green=buff[1]&0xfc
        red=buff[2]&0xf8
        rgb565[0]=red | (green & 0xe0)>>5
        rgb565[1]=(blue>>3) | ((green & 0x1b) <<3)
        BMPBuffer.append(rgb565[0])
        BMPBuffer.append(rgb565[1])

f.close()


tft.blit_buffer(BMPBuffer, x=80, y=40, width=HResolution, height=VResolution)

显示效果:

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
2022 / 01/ 30: 新版esptool 刷micropython固件指令不是 esptool.py cmd... 而是 esptool cmd... 即可;另外rshell 在 >= python 3.10 的时候出错解决方法可以查看:  已于2022年发布的: 第二章:修复rshell在python3.10出错 免费内容: https://edu.csdn.net/course/detail/29666 micropython语法和python3一样,编写起来非常方便。如果你快速入门单片机玩物联网而且像轻松实现各种功能,那绝力推荐使用micropython。方便易懂易学。 同时如果你懂C语音,也可以用C写好函数并编译进micropython固件里然后进入micropython调用(非必须)。 能通过WIFI联网(2.1章),也能通过sim卡使用2G/3G/4G/5G联网(4.5章)。 为实现语音控制,本教程会教大家使用tensorflow利用神经网络训练自己的语音模型并应用。为实现通过网页控制,本教程会教大家linux(debian10 nginx->uwsgi->python3->postgresql)网站前后台入门。为记录单片机传输过来的数据, 本教程会教大家入门数据库。  本教程会通过通俗易懂的比喻来讲解各种原理与思路,并手把手编写程序来实现各项功能。 本教程micropython版本是 2019年6月发布的1.11; 更多内容请看视频列表。  学习这门课程之前你需要至少掌握: 1: python3基础(变量, 循环, 函数, 常用库, 常用方法)。 本视频使用到的零件与淘宝上大致价格:     1: 超声波传感器(3)     2: MAX9814麦克风放大模块(8)     3: DHT22(15)     4: LED(0.1)     5: 8路5V低电平触发继电器(12)     6: HX1838红外接收模块(2)     7:红外发射管(0.1),HX1838红外接收板(1)     other: 电表, 排线, 面包板(2)*2,ESP32(28)  
这里提供一份C语言代码实现对BMP图片三颜色通道提取,RGB各一张,并另存为24真彩BMP文件的功能: ```c #include <stdio.h> #include <stdlib.h> #pragma pack(push, 1) typedef struct { unsigned short int type; // BM的标志 unsigned int size; // BMP文件的大小 unsigned short int reserved1; // 保留字段1 unsigned short int reserved2; // 保留字段2 unsigned int offset; // 数据偏移 unsigned int header_size; // 头文件大小 int width; // 图像宽度 int height; // 图像高度 unsigned short int planes; // 颜色平面数,总为1 unsigned short int bits_per_pixel; // 每个像素所占数 unsigned int compression; // 压缩类型 unsigned int image_size; // 图像大小 int x_resolution; // 水平分辨率 int y_resolution; // 垂直分辨率 unsigned int num_colors; // 颜色数 unsigned int important_colors; // 重要的颜色数 } BMPHeader; #pragma pack(pop) // 读取BMP文件头信息 void read_header(FILE *fp, BMPHeader *header) { fread(&header->type, sizeof(unsigned short int), 1, fp); fread(&header->size, sizeof(unsigned int), 1, fp); fread(&header->reserved1, sizeof(unsigned short int), 1, fp); fread(&header->reserved2, sizeof(unsigned short int), 1, fp); fread(&header->offset, sizeof(unsigned int), 1, fp); fread(&header->header_size, sizeof(unsigned int), 1, fp); fread(&header->width, sizeof(int), 1, fp); fread(&header->height, sizeof(int), 1, fp); fread(&header->planes, sizeof(unsigned short int), 1, fp); fread(&header->bits_per_pixel, sizeof(unsigned short int), 1, fp); fread(&header->compression, sizeof(unsigned int), 1, fp); fread(&header->image_size, sizeof(unsigned int), 1, fp); fread(&header->x_resolution, sizeof(int), 1, fp); fread(&header->y_resolution, sizeof(int), 1, fp); fread(&header->num_colors, sizeof(unsigned int), 1, fp); fread(&header->important_colors, sizeof(unsigned int), 1, fp); } // 写入BMP文件头信息 void write_header(FILE *fp, BMPHeader *header) { fwrite(&header->type, sizeof(unsigned short int), 1, fp); fwrite(&header->size, sizeof(unsigned int), 1, fp); fwrite(&header->reserved1, sizeof(unsigned short int), 1, fp); fwrite(&header->reserved2, sizeof(unsigned short int), 1, fp); fwrite(&header->offset, sizeof(unsigned int), 1, fp); fwrite(&header->header_size, sizeof(unsigned int), 1, fp); fwrite(&header->width, sizeof(int), 1, fp); fwrite(&header->height, sizeof(int), 1, fp); fwrite(&header->planes, sizeof(unsigned short int), 1, fp); fwrite(&header->bits_per_pixel, sizeof(unsigned short int), 1, fp); fwrite(&header->compression, sizeof(unsigned int), 1, fp); fwrite(&header->image_size, sizeof(unsigned int), 1, fp); fwrite(&header->x_resolution, sizeof(int), 1, fp); fwrite(&header->y_resolution, sizeof(int), 1, fp); fwrite(&header->num_colors, sizeof(unsigned int), 1, fp); fwrite(&header->important_colors, sizeof(unsigned int), 1, fp); } // 从源文件中读取像素数据 void read_pixels(FILE *fp, BMPHeader *header, unsigned char *pixels) { fseek(fp, header->offset, SEEK_SET); fread(pixels, header->image_size, 1, fp); } // 把R,G,B分离出来写入三个文件 void write_RGB_to_files(BMPHeader *header, unsigned char *pixels, const char *file_R, const char *file_G, const char *file_B) { FILE *fp_R = fopen(file_R, "wb"); FILE *fp_G = fopen(file_G, "wb"); FILE *fp_B = fopen(file_B, "wb"); // 写入文件头 write_header(fp_R, header); write_header(fp_G, header); write_header(fp_B, header); // 写入像素数据 int pixel_count = header->width * header->height; unsigned char *p = pixels; for (int i = 0; i < pixel_count; i++) { // 写入R,G,B三个通道 fwrite(p + 2, sizeof(unsigned char), 1, fp_R); fwrite(p + 1, sizeof(unsigned char), 1, fp_G); fwrite(p, sizeof(unsigned char), 1, fp_B); p += 3; } fclose(fp_R); fclose(fp_G); fclose(fp_B); } int main(int argc, char **argv) { if (argc != 2) { printf("Usage: %s <input_file>\n", argv[0]); return 1; } BMPHeader header; unsigned char *pixels; // 打开文件并读取文件头信息 FILE *fp = fopen(argv[1], "rb"); read_header(fp, &header); // 申请内存并读取像素数据 pixels = (unsigned char *)malloc(header.image_size); read_pixels(fp, &header, pixels); // 关闭源文件 fclose(fp); // 把R,G,B分离出来写入三个文件 write_RGB_to_files(&header, pixels, "output_R.bmp", "output_G.bmp", "output_B.bmp"); // 释放内存 free(pixels); return 0; } ``` 代码中使用了 BMPHeader 结构体来表示BMP文件头信息,并通过两个函数 read_header 和 write_header 分别实现了从文件中读取BMP文件头信息和写入BMP文件头信息的功能。read_pixels 函数用来从源文件中读取像素数据,write_RGB_to_files 函数用来把R,G,B分离出来写入三个文件。 主函数中首先判断命令行参数是否正确,然后打开源文件并读取文件头信息和像素数据。之后调用 write_RGB_to_files 函数把R,G,B分离出来写入三个文件,并最后释放内存。 需要注意的是,这段代码中只能处理24真彩BMP文件,不能处理其他格式的BMP文件

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值