智能仓库系统(二)之BMP图片显示处理

什么是 BMP 格式?

BMP是英文Bitmap(位图)的简写,它是Windows操作系统中的标准图像文件格式,能够被多种Windows应用程序所支持。随着Windows操作系统的流行与丰富的Windows应用程序的开发,BMP位图格式理所当然地被广泛应用。这种格式的特点是包含的图像信息较丰富,几乎不进行压缩,但由此导致了它与生俱生来的缺点–占用磁盘空间过大所以,目前BMP在单机上比较流行

下面,我们通过一个例子来验证一下同一张图片保存为 png 格式和 bmp 格式 的大小对比。我们保存 bmp 格式时选择 24位位图。
在这里插入图片描述
在这里插入图片描述
上图足以证明一张图片保存为 png 只需要 12k,而保存为 bmp 格式,则需要 1.4M。所以 bmp 相比于其它图片格式来说占用磁盘空间非常大。

BMP 的文件结构

在这里插入图片描述
上面我们已经了解 bmp 格式的图片是不会压缩的,所以占用空间非常大。那我们可不可以根据一张 bmp 格式的图片来计算图片的大小呢?比如下面一张 bmp 图片,它的详细信息如下:
在这里插入图片描述

可以看到,这张图片宽 800 像素,高 480 像素,它的大小为 1.09 M(1.152.054 字节)。

大小 = 800 * 480 * 3(RGB) = 1152000。最后 54 个字节就是 文件头 + 信息头

那么 bmp 文件的文件头和信息头又包含什么信息呢?

14字节文件头:

字节字节号解释说明
424D1-2可以转换为字符 ‘B’、‘M’,是用于识别 BMP 文件的标志
B634 18003-6存放的是位图大小
0000 00007-10保留字节,必须为0
3600 000011-14表示位图阵列相对于文件头的偏移量,计算得54,即位图阵列从第 55 个字节开始

40字节信息头
在这里插入图片描述


读取 BMP 图片

首先我们定义一个结构体来接收 BMP 文件的文件头:

// 定义一个结构体(存储 BMP 的文件头)
typedef struct{
	char bfType[2];		// 识别 BMP 的标志:'B'/'M'
	int bfSize;			// 位图文件的大小
	int bfReserved;		// 保留字节,必须为0
	int bfOffBits;		// 偏移量
}bfHeader;

但是这里会涉及到一个内存对齐的问题,一个 fileHeader 结构体会占用 16 个字节,但是我们的文件头只有 14 个字节。所以 我们把 bfType 单独定义,不放在结构体中。

char bfType[3];		// 识别 BMP 的标志:'B'/'M',最后一个字节放'/0'
// 定义一个结构体(存储 BMP 的文件头)
typedef struct{
	int bfSize;			// 位图文件的大小
	int bfReserved;		// 保留字节,必须为0
	int bfOffBits;		// 偏移量
}bfHeader;

我们有定义一个结构体来存储 BMP 文件的信息头:

typedef struct{
	int 	biSize;			// 信息头的大小
	int 	biWidth;		// 图像宽度(单位:像素)
	int 	biHeight;		// 图像高度(单位:像素)
	short 	biPlanes;		// 目标设备说明颜色平面数,总被设为1
	short 	biBitCount;		// 说明 比特数/像素数,值有 1、2、4、8、16、32
	int 	biCompression;	// 说明图像的压缩类型,最常用的就是0,不压缩
	int 	biSizeImages;	// 位图数据的大小
	int 	biXPelsPerMeter;// 水平分辨率,单位是 像素/米
	int 	biYPelsPerMeter;// 垂直分辨率,单位是 像素/米
	int 	biClrUsed;		// 位图使用的调色板中的颜色索引数
	int 	biClrImportant;	// 对图像显示有重要影响的颜色索引数
	
}bfInfo;

下面我们就可以读取 BMP 文件数据了,代码如下:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>

char 	bfType[3];		// 识别 BMP 的标志:'B'/'M',最后一个字节放'/0'
// 定义一个结构体(存储 BMP 的文件头)
typedef struct{
	int 	bfSize;			// 位图文件的大小
	int 	bfReserved;		// 保留字节,必须为0
	int 	bfOffBits;		// 偏移量
}bfHeader;

typedef struct{
	int 	biSize;			// 信息头的大小
	int 	biWidth;		// 图像宽度(单位:像素)
	int 	biHeight;		// 图像高度(单位:像素)
	short 	biPlanes;		// 目标设备说明颜色平面数,总被设为1
	short 	biBitCount;		// 说明 比特数/像素数,值有 1、2、4、8、16、32
	int 	biCompression;	// 说明图像的压缩类型,最常用的就是0,不压缩
	int 	biSizeImages;	// 位图数据的大小
	int 	biXPelsPerMeter;// 水平分辨率,单位是 像素/米
	int 	biYPelsPerMeter;// 垂直分辨率,单位是 像素/米
	int 	biClrUsed;		// 位图使用的调色板中的颜色索引数
	int 	biClrImportant;	// 对图像显示有重要影响的颜色索引数
	
}bfInfo;

int main()
{
	int rfd, ret;
	bfHeader fh;		// 文件头结构体
	bfInfo info;		// 信息头结构体
	rfd = open("./img/test.bmp", O_RDONLY);
	assert(rfd > 0);

	// 先读取2个字节放到 type 中
	ret = read(rfd, bfType, 2);
	assert(ret == 2);
	
	// 读取12个字节到 fh 中
	ret = read(rfd, &fh, sizeof(fh));
	assert(ret == sizeof(fh));

	// 读取信息头
	ret = read(rfd, &info, sizeof(info));
	assert(ret == sizeof(info));

	printf("bfType = %s\n", bfType);
	printf("bfSize = %d Bytes\n", fh.bfSize);
	printf("biWidth = %d, biHeight = %d\n", info.biWidth, info.biHeight);

	close(rfd);
	
	return 0;
}

运行结果如下:
在这里插入图片描述

上面我们已经对 BMP 文件的信息头和文件头进行了读取,下面就应该对 BMP文件数据进行读取了。

我们首先需要知道的是没在数据部分,每个像素点(RGB三个字节)是反向存储的。如下图
在这里插入图片描述
读取图片数据代码如下:

//rgb像素点占用的总字节数
	int rgb_size = pic_info.width*pic_info.height*3;
	//在堆空间申请内存存放RGB数据
	//unsigned char *rgb_space = malloc(rgb_size);
	unsigned char *rgb_space = new unsigned char[rgb_size];
	if(rgb_space == NULL)
	{
		perror("malloc rgb_space fail!");
		return -1;
	}	
	//读取图片的RGB数据
	read(pic_fd, rgb_space, rgb_size);

	
	//ARGB像素点占用的总字节数
	int argb_size = pic_info.width*pic_info.height*4;
	//在堆空间申请内存存放RGB数据
	//unsigned int *argb_space = malloc(argb_size);
	unsigned int *argb_space = new unsigned int[argb_size];
	if(argb_space == NULL)
	{
		perror("malloc argb_space fail!");
		return -1;
	}	

	
	int x, y, i=0;
	unsigned char R,G,B;
	
	//把RGB数据转换为32位的ARGB数据,并倒转图片缓存到ARGB缓存
	for(y = 0; y < pic_info.height; y++)
	{ 
		for(x = 0; x < pic_info.width; x++)
		{
			B = *(rgb_space + 3*i + 0);
			G = *(rgb_space + 3*i + 1);
			R = *(rgb_space + 3*i + 2);
			i++;
			
			//480-->479行	y=0	-->480	需要pic_info.height-1=479
			
			//把RGB数据转换为32位的ARGB数据
			*(argb_space + (pic_info.height - 1 - y) * pic_info.width + x) = 0 | R<<16 | G<<8 | B<<0;
		}                      
	} 
	i= 0;  
	//显示处理
	// (x_s, y_s):表示你想从屏幕的(x_s, y_s)坐标开始显示图片
	for(y = y_s; y < pic_info.height + y_s; y++)
	{
		for(x = x_s; x < pic_info.width + x_s; x++)
		{		
			i++;
			
			if((x > 0 && x < 800) && (y > 0 && y < 480))
			{
				//把转换后的ARGB数据显示
				*(lcd_p + y*800 + x) = *(argb_space + i);	//*(argb_space+y*pic_info.height+x);
			}
		}                      
	}	

到此,在屏幕上显式 bmp 图片的代码编写完成了。下面是完整代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>

char f_type[3];	//储存文件类型,"BM"

struct fileinfo
{
	int f_size;		//文件大小
	int res;        //保留,为0
	int f_seekbits; //从文件头开始到实际像素点的偏移量
};

struct bmpinfo
{
	int b_size;		//图片信息头大小
	int width;      //图片宽度
	int height;     //图片高度
	short plane;    //图层数量,24位图只有1层
	short bit;      //色深,24位图是24
	int compress;	//是否压缩,0表示不压缩
	int image_size;	//位图阵列大小
	int x_pels;		//水平分辨率
	int y_pels;		//垂直分辨率
	int clr;		//调色板用到的颜色数量,24位图没有调色板
	int clr_important;	//调色板重要的颜色
};
 

int show_bmp(int x_s, int y_s, char *filename, unsigned int *lcd_p)
{
	//以只读的方式打开图片文件
	int pic_fd = open(filename, O_RDONLY);
	if(pic_fd == -1)
	{
		perror("open pic fail!");
		return -1;
	}
	
	struct fileinfo finfo;
	struct bmpinfo pic_info;
	
	//读取14字节的文件信息
	read(pic_fd, f_type, 2);	//文件类型类型,2字节
	read(pic_fd, &finfo, sizeof(finfo));	//剩余12字节的文件信息
	
	//40字节的图片信息
	read(pic_fd, &pic_info, sizeof(pic_info));
	
	
	printf("type:%s\n", f_type);
	printf("width :%d\n", pic_info.width);
	printf("height:%d\n", pic_info.height);
	printf("bit:%d\n", pic_info.bit);
	
	
	
	//rgb像素点占用的总字节数
	int rgb_size = pic_info.width*pic_info.height*3;
	//在堆空间申请内存存放RGB数据
	//unsigned char *rgb_space = malloc(rgb_size);
	unsigned char *rgb_space = new unsigned char[rgb_size];
	if(rgb_space == NULL)
	{
		perror("malloc rgb_space fail!");
		return -1;
	}	
	//读取图片的RGB数据
	read(pic_fd, rgb_space, rgb_size);

	
	//ARGB像素点占用的总字节数
	int argb_size = pic_info.width*pic_info.height*4;
	//在堆空间申请内存存放RGB数据
	//unsigned int *argb_space = malloc(argb_size);
	unsigned int *argb_space = new unsigned int[argb_size];
	if(argb_space == NULL)
	{
		perror("malloc argb_space fail!");
		return -1;
	}	

	
	int x, y, i=0;
	unsigned char R,G,B;
	
	//把RGB数据转换为32位的ARGB数据,并倒转图片缓存到ARGB缓存
	for(y = 0; y < pic_info.height; y++)
	{ 
		for(x = 0; x < pic_info.width; x++)
		{
			B = *(rgb_space + 3*i + 0);
			G = *(rgb_space + 3*i + 1);
			R = *(rgb_space + 3*i + 2);
			i++;
			
			//480-->479行	y=0	-->480	需要pic_info.height-1=479
			
			//把RGB数据转换为32位的ARGB数据
			*(argb_space + (pic_info.height - 1 - y) * pic_info.width + x) = 0 | R<<16 | G<<8 | B<<0;
		}                      
	}         
	
	i=0;
	
	//显示处理
	for(y = y_s; y < pic_info.height + y_s; y++)
	{
		for(x = x_s; x < pic_info.width + x_s; x++)
		{		
			i++;
			
			if((x > 0 && x < 800) && (y > 0 && y < 480))
			{
				//把转换后的ARGB数据显示
				*(lcd_p + y*800 + x) = *(argb_space + i);	//*(argb_space+y*pic_info.height+x);
			}
		}                      
	}

	
	//释放堆空间内存
	delete rgb_space;
	delete argb_space;
	//关闭图片文件
	close(pic_fd);
	
	return 0;
}


我把它封装成了一个头文件,下面是测试代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
#include "show_bmp.h"

int main()
{
	//以读写的方式打开屏幕驱动文件
	int lcd_fd = open("/dev/ubuntu_lcd", O_RDWR);
	if(lcd_fd == -1)	//打开文件失败,结束程序
	{
		perror("open lcd_fd fail!");
		return -1;
	}
	
	printf("lcd_fd = %d\n", lcd_fd);

	

	unsigned int *lcd_p = mmap(NULL, 800*480*4, PROT_READ|PROT_WRITE, MAP_SHARED, lcd_fd, 0);
	if(lcd_p == MAP_FAILED )	//打开文件失败,结束程序
	{
		perror("mmap lcd fail!");
		return -1;
	}
	
	
	show_bmp(0, 0, "./pic/longmao.bmp", lcd_p);  // 800 * 480
	show_bmp(100, 100, "./pic/longmao2.bmp", lcd_p);	// 400 * 240, 从(100,100)开始显示

	// 移动特效
	/*for(int x=-400; x<400; x+=10)
	{
		show_bmp(x, 100, "./pic/longmao2.bmp", lcd_p);
		usleep(100*1000);
	}*/
	
	
	//解除屏幕映射
	munmap(lcd_p, 800*480*4);
	//关闭文件
	close(lcd_fd);
	
	
	return 0;
}

运行结果如下:可以观察到两张图片嵌套显示。
在这里插入图片描述


Linux 触摸事件

上面已经实现了再屏幕上显示 bmp 图片,那么我们可不可以给屏幕添加一个点击事件,当鼠标点击屏幕,打印出该点的左边。

首先,我们得了解 struct input_event 这个结构体。定义在头文件 #include <linux/input.h>

struct input_event {
		struct timeval time;	//按键按下的时间
		__u16 type;		//类型,用于区别是键盘还是触摸屏数据
		__u16 code;		//代码,用来确定键值、X坐标或者Y坐标
		__s32 value;	//键值,坐标值
};
  • type:设备类型。可以设置为如下,我们主要会用到 键盘或者按键,0x01
define EV_SYN 0x00 表示设备支持所有的事件
define EV_KEY 0x01 键盘或者按键,表示一个键码
define EV_REL 0x02 鼠标设备,表示一个相对的光标位置结果
define EV_ABS 0x03 手写板产生的值,其是一个绝对整数值
define EV_MSC 0x04 其他类型
define EV_LED 0x11 LED灯设备
define EV_SND 0x12 蜂鸣器,输入声音
define EV_REP 0x14 允许重复按键类型
define EV_PWR 0x16 电源管理事件
define EV_FF_STATUS 0x17
define EV_MAX 0x1f
define EV_CNT (EV_MAX+1)

完整代码如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>



int main()
{
	//以读写的方式打开触摸屏驱动文件
	int ts_fd = open("/dev/ubuntu_event", O_RDWR);
	if(ts_fd == -1)	//打开文件失败,结束程序
	{
		perror("open ts fail!");
		return -1;
	}
	
	printf("ts_fd = %d\n", ts_fd);
	
	/*
	struct input_event {
		struct timeval time;	//按键按下的时间
		__u16 type;		//类型,用于区别是键盘还是触摸屏数据
		__u16 code;		//代码,用来确定键值、X坐标或者Y坐标
		__s32 value;	//键值,坐标值
	};
	*/
	
	struct input_event ts;
	
	int x=0, y=0;
	
	while(1)
	{
		read(ts_fd, &ts, sizeof(ts));
		
		/*
		printf("type = %d\n", ts.type);
		printf("code = %d\n", ts.code);
		printf("value= %d\n", ts.value);
		
		type = 3	EV_ABS
		code = 0	ABS_X
		value= 442

		type = 3	EV_ABS
		code = 1	ABS_Y
		value= 319
		*/
		
		//判断是否为触摸屏事件
		if(ts.type == EV_ABS)
		{
			//判断是否为X轴的数据
			if(ts.code == ABS_X)
			{
				x = ts.value;
			}
			//判断是否为Y轴的数据
			else if(ts.code == ABS_Y)
			{
				y = ts.value;
			}
		}
		
		if(x>0 && y>0)
		{
			printf("x:%d,y:%d\n", x, y);
			
			x=y=0;
		}
	}
	
	
	//关闭触摸屏
	close(ts_fd);
	
	return 0;
}

运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值