项目简介:
在Linux系统中,利用LCD和触摸屏模拟器,显示某文件夹(相册)中的bmp和jpg图片,鼠标点击可切换上一张、下一张,同时,利用madplay播放器播放音乐。
环境搭载:
所用软件以及工具如下:
1.VMware Workstation 16 Play搭载Ubuntu18.04的Linux操作系统。
2.使用VScode代码编写软件,配置好C语言开发环境。
3.Linux系统中:
(1)安装LCD和触摸屏模拟器。
(2)移植libjpeg开源库(用于解压jpg格式文件)。
(3)安装madplay播放器。
核心代码:
首先,运行LCD模拟器,模拟一块分辨率为800*480的LCD屏幕,利用mmap函数将LCD设备文件映射到内存中,再通过内存指针plcd直接在内存中访问LCD文件,读取或写入文件内容。
void init_lcd()
{
//1. 打开LCD设备文件
int fd = open("/dev/ubuntu_lcd", O_RDWR);
if(fd == -1)
{
perror("open");
return ;
}
//2. 映射
plcd = mmap(NULL, 800*480*4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
}
bmp图片的显示:利用draw_bmp函数,读取bmp文件信息(文件宽、高、色深)得出像素数组的大小,通过显示像素数组,完成在bmp图片在LCD上的显示。
//显示 像素数组
void draw_image(int x0,int y0,int width, int height , const unsigned char image[])
{
int i,j,k=0;
int color;
for(i=height;i>=0;i--)
for(j=0;j<width;j++)
{
color = image[k]|(image[k+1]<<8)|(image[k+2]<<16);
k = k+3;
draw_point(j+x0, i+y0, color);
}
}
//读bmp文件,获取 w,h,depth
int draw_bmp(int x0,int y0, const char *bmpfile)
{
unsigned char buf[4];
int fd = open(bmpfile,O_RDONLY);
if(fd < 0)
{
perror("open");
return -1;
}
read(fd, buf, 2);
if(buf[0]=='B' && buf[1]=='M')
{
printf("It's bmpfile\n");
}
else
return -1;
//1. 读出文件的 宽,高,色深:才能知道像素数组的大小
lseek(fd, 0x12,SEEK_SET);
read(fd, buf, 4);
int width = (buf[3]<<24)|(buf[2]<<16)|(buf[1]<<8)|buf[0];
//2. 读出文件的 高
lseek(fd, 0x16,SEEK_SET);
read(fd, buf, 4);
int height = (buf[3]<<24)|(buf[2]<<16)|(buf[1]<<8)|buf[0];
//3. 读出文件的 色深
lseek(fd, 0x1c,SEEK_SET);
read(fd, buf, 2);
int depth = (buf[1]<<8)|buf[0]; //24
// 4:像素数组大小: width * height *depth/8
// 读 像素数组
unsigned char *image = (unsigned char*)malloc(width * height *depth/8);
lseek(fd, 54, SEEK_SET); // bmp 信息head占了 54个字节
read(fd, image, width * height *depth/8);
draw_image(x0,y0, width, height, image);
}
jpg图片的显示:调用draw_jpg函数,初始化解压对象,打开要解压的jpg文件并调用jepg_start_decompress函数启动解压过程,再读取文件,获取jpg图像数据(图像的宽、高、颜色分量),并使用循环逐行读取、解压图像数据,并在内存中显示每个像素点的颜色值。从而完成jpg图片在LCD上的显示。
int draw_jpg(int x0,int y0, const char *jpgfile)
{
//1. 声明一个 解压对象
struct jpeg_decompress_struct dinfo;
//声明一个出错信息的对象
struct jpeg_error_mgr jerr;
//2. 初始化这个解压对象
jpeg_create_decompress(&dinfo);
//初始化这个出错对象
dinfo.err = jpeg_std_error(&jerr);
//3. 打开要解压的文件
FILE * infile = fopen(jpgfile,"r");
if(infile == NULL)
{
perror("fopen");
return -1;
}
//4. 指定要解压的图像文件
jpeg_stdio_src(&dinfo, infile);
jpeg_read_header(&dinfo, TRUE);
//5. 启动解压
jpeg_start_decompress(&dinfo);
int width,height,components;
width = dinfo.output_width;
height = dinfo.output_height;
components = dinfo.output_components;
printf("width:%d, height:%d, components:%d\n",dinfo.output_width, dinfo.output_height,dinfo.output_components);
//6. 分配内存,每次读一行 显示
unsigned char * image = (unsigned char *)malloc(width*components);
int color,k=0,x;
while(dinfo.output_scanline<dinfo.output_height)
{
jpeg_read_scanlines(&dinfo, &image, 1 ); // 800x3
for(x=0,k=0;x<width;x++)
{
color = image[k]|(image[k+1]<<8)|(image[k+2]<<16);
k = k+3;
draw_point(x0+x, y0+dinfo.output_scanline, color);
}
}
}
接着是触摸屏模块,模块功能为:从触摸屏设备/dev/ubuntu_event中读取触摸事件(鼠标点击)的坐标信息,并返回一个包含x和y坐标的结构体pos。该模块用于实现鼠标点击切换图片。
struct pos getPos()
{
struct pos p;
p.x = -1;
p.y = -1;
int fd = open("/dev/ubuntu_event", O_RDONLY);
if(fd < 0)
{
perror("open");
return p;
}
// 读触摸屏驱动文件:可以读到一个 事件的结构体
struct input_event ev;
while(1)
{
read(fd, &ev, sizeof(ev));
//printf("type=%d, code=%d, value=%d\n", ev.type, ev.code, ev.value);
if(ev.type==3 && ev.code==0)
{
//printf("x = %d ", ev.value);
p.x = ev.value;
}
else if(ev.type==3 && ev.code==1)
{
//printf("y = %d\n", ev.value);
p.y = ev.value;
close(fd);
return p;
}
}
}
最后,重点介绍项目功能实现的核心方法:
1.创建图片节点:包含用于保存bmp或jpg文件名的filename数组、用于保存文件修改时间的time结构体,以及分别指向上一节点和下一节点的指针prev和next。
typedef struct node{
struct timespec time;
char filename[50];
struct node *next;//指针域:指向下一个节点
struct node *prev;//指针域:指向上一个节点
}NODE;
2.读文件目录:搜素目录,读目录项,将bmp和jpg文件名和修改时间存入节点。完成此操作后,节点即代表一张图片,再将图片插入链表。
NODE * search_dir(NODE *head, char *dir)
{
//搜索目录,如果找到有 bmp ,jpg 插入链表中
DIR *dp = opendir(dir);
struct dirent *p; // 目录项
struct stat statbuf;
NODE s;
while(p = readdir(dp)) //当 p == NULL 就读完了
{
if(isBmpJpg(p->d_name)==BMP || isBmpJpg(p->d_name)==JPG)
{
sprintf(s.filename,"%s/%s",dir,p->d_name);
stat(p->d_name, &statbuf); //获取了 文件的 属性
s.time = statbuf.st_mtim; //文件的修改时间
head = insert(head, s);//插入链表
}
}
closedir(dp); //关闭目录
return head;
}
3.图片节点插入链表:这里我用的是排序法插入,按文件修改时间time.tv_sec的先后顺序插入图片节点,并确定好每个节点的prev和next指针的指向,从而创建了一个双向循环链表。
NODE *insert(NODE *head, NODE s)
{
NODE *new1,*p;
p = head;
new1 = (NODE *)malloc(sizeof(NODE));
strcpy(new1->filename , s.filename);
new1->time = s.time;
if(head==NULL)//如果是空链表
{
head = new1;
new1->next = new1;
new1->prev = new1;
p = head;
return head;
}
else if (p->next == p)//当链表只有一个节点时,新节点插在其后
{
p->next = new1;
new1->prev = p;
p->prev = new1;
new1->next = p;
if( new1->time.tv_sec <= p->time.tv_sec)//new1的数据比p还小,则交换二者数据
{
int time = p->time.tv_sec;
p->time.tv_sec = new1->time.tv_sec;
new1->time.tv_sec = time;
}
return head;
}
else if (p->next != p)//当链表有多个节点时
{
while(p->next != head)//p不是尾节点
{
if(p->time.tv_sec <= new1->time.tv_sec&&p->next->time.tv_sec > new1->time.tv_sec)//找一个数据比new1小,且其下一节点数据比new1大的节点,将new1插在中间。
{
new1->next = p->next;
p->next->prev = new1;
p->next = new1;
new1->prev = p;
return head;
}
p = p->next;
}
}
p->next = new1;
new1->prev = p;
new1->next = head;
head->prev = new1;
return head;
}
4.项目功能的实现:在主循环中,通过getPos函数返回的坐标信息判断鼠标点击的是哪个区域,再利用双向循环链表实现切换上一张、下一张图片。此外,定义了一个线程函数musicplayer,并在main函数中利用pthread_create函数创建一个线程,用于在相册放映的同时,在线程中播放音乐。部分核心代码如下:
void * musicplayer(void *arg)
{
int oldstate;
char song[20];
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE , &oldstate);
pthread_detach(pthread_self());
sprintf(song, "madplay mp3/3.mp3");
system(song);
}
void photo_play(NODE *head)
{
int color[7]={0xff0000,0xff00,0xff,0xffff00,0xff00ff,0xffff,0xffffff};
NODE *p = head;
draw_bmp(0, 0, "imagefiles/1.bmp");
struct pos po;
while(1)
{
po = getPos();
if(po.x <300 && po.y <300 ) //上一张
{
brush_lcd(color[6]);
p = p->prev;
if(isBmpJpg(p->filename)==BMP)
{
draw_bmp(0, 0, p->filename);
}
else if(isBmpJpg(p->filename)==JPG)
draw_jpg(0, 0, p->filename);
}
else if(po.x >500 && po.y < 200)//下一张
{
brush_lcd(color[6]);
p = p->next;
if(isBmpJpg(p->filename)==BMP)
{
draw_bmp(0, 0, p->filename);
}
else if(isBmpJpg(p->filename)==JPG)
draw_jpg(0, 0, p->filename);
}
else if(po.x >500 && po.y > 300)//结束播放
{
break;
}
}
}
功能演示:
演示视频如下: