第三阶段应用层——1.11 数码相册—setting_page设置页面的显存管理、页面规划、输入控制

数码相册——setting_page主界面的显存管理、页面规划、输入控制

  • 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
  • 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
  • 参考资料:《嵌入式Linux应用开发手册》、《嵌入式Linux应用开发手册第2版》
  • 开发环境:Linux 3.4.2内核、arm-linux-gcc 4.3.2工具链
  • 源码仓库:https://gitee.com/d_1254436976/Embedded-Linux-Phase-3


一、前言

【1.10 数码相册—main_page主界面的显存管理、页面规划、输入控制】中,实现了main_page主界面的显示与按键控制,在这篇博文中,实现setting_page设置界面的测试,实现如下目的:

  1. 有效的管理内存,实现页面的快速刷新
  2. 合理的规划页面空间,达到较好的显示效果
  3. 有效的输入区域实现触控的效果

二、setting_page页面的实现

由于需要程序改动的地方过多,所以这篇博文会比较侧重于描述思想与方法

1、关键的结构体

/* 描述图片信息 */
typedef struct PixelDatas{
	int bpp;					//图片的bpp
	int width;					//图片的宽度
	int height;					//图片的高度
	int linebytes;				//图片每行占据的字节数
	int TotalBytes;				//图片总大小
	unsigned char *PixelDatas;	//存储图片具体像素数据的首地址
}T_PixelDatas, *PT_PixelDatas;

/* 页面内存数据是否被使用 */
typedef enum {
	VMS_FREE = 0,				//空闲
	VMS_USED_FOR_PREPARE,		//子线程占用
	VMS_USED_FOR_CURMAIN,		//当前主线程占用
}E_VideoMemState;

/* 页面内存数据是否已准备好 */
typedef enum {
	PS_BLANK = 0,				//数据空白
	PS_GENERATING,				//数据生成中
	PS_GENERATED,				//数据完毕
}E_PicState;

/* 图标布局信息 */
typedef struct Layout {
	char *strIconName;			//图标的名字
	int TopLeftX;				//图标左上角x坐标
	int TopLeftY;				//图标左上角y坐标
	int BotRightX;				//图标右下角x坐标
	int BotRightY;				//图标右下角x坐标
}T_Layout, *PT_Layout;

/* 页面图标信息 */
typedef struct PageLayout {
	int bpp;					//显示页面的bpp
	int MaxTotalBytes;			//最大图标的大小
	PT_Layout ptLayout;			//指向描述该页面图标数组的指针
}T_PageLayout, *PT_PageLayout;

/* 描述页面内存块 */
typedef struct VideoMem {
	int id;							//页面内存的id	
	int isDevFB;					//当前描述的内存是否为Framebuffer:1-是,2-否
	E_VideoMemState eVideoMemState;	//描述该页面内存数据的使用状态
	E_PicState ePicState;			//描述该页面内存数据是否已准备好
	T_PixelDatas tPixelDatas;		//描述图片信息	
	struct VideoMem *ptNext;		//指向下一结点的指针
}T_VideoMem, *PT_VideoMem;

/* 描述页面行为 */
typedef struct PageAction {
	char *name;
	int (*Run)(void);
	int (*GetInputEvent)(PT_PageLayout ptPageLayout, PT_InputEvent ptInputEvent);
	int (*Prepare)(void);
	struct PageAction *ptNext;
}T_PageAction, *PT_PageAction;

2、显存管理

2.1 思想

对于页面的显示LCD与Framebuffer之间通过LCD控制器来进行像素信息的传递,当我们需要显示图片时,只需要在Framebuffer中写入颜色信息就可以在LCD上显示出来。
应用程序过大,会导致显示的很慢,此时需要优化措施

  1. 在内存中开辟一个与Framebuffer大小相同的内存空间
  2. 提前把需要显示的内容写到新开辟的内存空间中;(3、页面规划与显示中介绍)
  3. 需要显示时,直接把新开辟的内存空间memcpyFramebuffer。(3、页面规划与显示中介绍)

2.2 具体的实现

  • 步骤描述:
    在这里插入图片描述
/*---------------------------显存管理----------------------------------------*/
页面管理内存 = 页面描述信息 + 显存

int AllocVideoMem(int num)函数:
	1. 获得LCD显示设备的分辨率以及bpp,计算得到需要开辟内存空间的大小(单位:byte)以及行宽(单位:byte)
	
	2. 先把设备本身的Framebuffer放入链表
		2.1 开辟一个大小为sizeof(T_VideoMem)内存,用来存放页面描述信息,地址放在ptNew结构体指针中
		
		2.2 根据1中获取的信息设置该内存的描述信息,以及把Framebuffer的地址放入结构体变量中
		
		2.3 强制设置其eVideoMemState状态位为占用状态
		
		2.4 采用头插法插入链表
		
	3. 后根据num分配用于页面管理的内存并设置结构体
		3.1 开辟一个大小为sizeof(T_VideoMem) + VMSize内存,用来存放页面描述信息与显存,地址放在ptNew结构体指针中
		
		3.2 根据1获得的信息,设置该内存的描述信息与显存的地址
		
		3.3 采用头插法插入链表
  • 代码实现:
int AllocVideoMem(int num)
{
	int i;
	int bpp;
	int linebytes;
	int xres, yres;
	int VMSize;
	PT_VideoMem ptNew;
	PT_VideoMem ptTmp;

	bpp = yres = xres = 0;
	
	GetDispResolution(&xres, &yres, &bpp);
	VMSize    = xres * yres * bpp / 8;
	linebytes = xres * bpp / 8;
	
	/* 1、先把设备本身的Framebuffer放入链表 */
	ptNew = (PT_VideoMem)malloc(sizeof(T_VideoMem));
	if (ptNew == NULL) {
		DebugPrint(APP_ERR"Framebuffer set fail!\n");
		goto fail;
	}

	/* 设置该内存所描述页面的信息 */
	ptNew->id 					  = 0;
	ptNew->isDevFB 				  = 1;
	ptNew->eVideoMemState         = VMS_FREE;
	ptNew->ePicState              = PS_BLANK;
	ptNew->tPixelDatas.bpp        = bpp;
	ptNew->tPixelDatas.height     = yres;
	ptNew->tPixelDatas.width      = xres;
	ptNew->tPixelDatas.linebytes  = linebytes;		
	ptNew->tPixelDatas.PixelDatas = s_ptDefaultDispOpr->pDispMem;
	ptNew->tPixelDatas.TotalBytes = VMSize;

	/* 强制设置设备本身的Framebuffer状态为被占用 */
	if (num != 0)
		ptNew->eVideoMemState = VMS_USED_FOR_CURMAIN;

	/* 头插法插入链表 */
	ptNew->ptNext = s_ptVieoMemHead;
	s_ptVieoMemHead = ptNew;

	/* 2、后分配用于页面管理的内存并设置结构体,组成:页面描述 + 显存 */
	for (i = 0; i < num; i++) {
		ptNew = (PT_VideoMem)malloc(sizeof(T_VideoMem) + VMSize);
		if (ptNew == NULL) {
			DebugPrint(APP_ERR"ptNew malloc fail,already malloc num: %d!\n", i);
			goto fail;
		}

		/* 设置该内存所描述页面的信息 */
		ptNew->id 					  = 0;
		ptNew->isDevFB 				  = 0;
		ptNew->eVideoMemState         = VMS_FREE;
		ptNew->ePicState              = PS_BLANK;
		ptNew->tPixelDatas.bpp        = bpp;
		ptNew->tPixelDatas.height     = yres;
		ptNew->tPixelDatas.width      = xres;
		ptNew->tPixelDatas.linebytes  = linebytes;		
		ptNew->tPixelDatas.PixelDatas = (unsigned char *)(ptNew + 1);
		ptNew->tPixelDatas.TotalBytes = VMSize;

		/* 头插法插入链表 */
		ptNew->ptNext = s_ptVieoMemHead;
		s_ptVieoMemHead = ptNew;
	}

	return 0;
	
fail:
	while (s_ptVieoMemHead) {
		ptTmp = s_ptVieoMemHead->ptNext;
		free(s_ptVieoMemHead);
		s_ptVieoMemHead = ptTmp;
	}

	return -1;
}

3、页面规划与显示

3.1 思想

  • 页面规划:
    为了适应多种尺寸的LCD设备以及良好的视觉感受,所以图标的显示如下,尺寸计算可以适当:(不通用于其他页面,每个页面都需要制定不同的方案
    在这里插入图片描述
  • 显示:
    对于多个页面的显示与切换,需要大致做到以下步骤:
    1、获得该页面的显存
    2、描画该页面内存的显示数据
    3、刷新到显示设备的Framebuffer

3.2 具体的实现

  • 步骤描述:
1.1 获得显存

1.2 描画数据
	需要判断ePicState页面数据状态位是否处于准备完毕状态:
	若是则表示数据已经准备好,则直接进行步骤3
	
	否则进行如下措施:
	1.2.1 清空屏幕
	
	1.2.2 获得LCD显示设备的分辨率以及bpp
	
	1.2.3 根据事先约定的LCD上的页面规划,进行图标的尺寸计算
		
	1.2.4 根据上述数据,计算并设置该页面内存的描述信息:height高度、width宽度、bpp、linebytes行宽(单位:byte)、TotalBytes显示页面的内存大小(单位:byte)
	
	1.2.5 根据TotalBytes的数据来malloc分配存储LCD显示的图片数据的内存,把内存的首地址存储到页面内存的PixelDatas
	
	1.2.6 描绘该页面的需要显示的图标源bmp信息	
		1.2.6.1 调用GetPixelDatasForIcon(),获取每个图标的bmp像素信息
		
		1.2.6.2 调用PicZoom(),根据2.2.2中获得的LCD显示的图标信息与2.2.6.1获得的源bmp文件信息,采用近邻取样插值,对LCD显示的图标的像素信息进设置
		
		1.2.6.3 调用PicMerge(),根据2.2.6.2获得的LCD显示的图标信息,把数据整合到2.2.5中的页面内存的PixelDatas所指向的内存中
		
		1.2.6.4 调用FreePixelDatasForIcon(),释放2.2.6.1中所开辟的内存
				移动到下一个图标的在LCD中显示的位置
		
	1.2.7 释放2.2.5中所开辟的内存,并更新该页面的ePicState页面数据状态位为数据完毕位

1.3 刷新设备并解放显存
  • 改进
    1、在结构体内部定义另一结构体指针方便管理该页面图标的信息
    2、对于每个页面需要使用到的公共部分定义为函数
    3、做了信息的判断防止每个页面界面进行切换时重复计算该页面图标的坐标
/* 定义结构体数组存储该页面的每个图标 */
static T_Layout s_tSettingPageIconsLayout[] = {
	{"select_fold.bmp",  0, 0, 0, 0},
	{"interval.bmp", 	 0, 0, 0, 0},
	{"return.bmp",       0, 0, 0, 0},
	{NULL, 				 0, 0, 0, 0},	//结尾标志
};

/* 定义一个新的结构体存储,成员变量指向该页面所有图标信息的结构体地址 */
static T_PageLayout s_tSettingPageLayout = {
	.MaxTotalBytes  = 0,
	.ptLayout       = s_tSettingPageIconsLayout,
};
  • 函数的实现
static void ShowSettingPage(PT_PageLayout ptPageLayout)
{

	int error;
	PT_VideoMem ptVideoMem;
	PT_Layout ptLayout;
	ptLayout = ptPageLayout->ptLayout;

	/* 获得显存 */
	ptVideoMem = GetVideoMem(ID("Setting"), 1);
	if (ptVideoMem == NULL) {
		DebugPrint(APP_ERR"Can not get video mem for Setting_page!\n");
		return ;
	}
	
	/* 描画数据 */
	/* 如果还没有计算过各图标的坐标 */
	if (ptLayout[0].TopLeftX == 0)
		CalcSettingPageLayout(ptPageLayout);

	error = GeneratePage(ptPageLayout, ptVideoMem);	
	
	/* 刷新/加载到设备 */
	FlushVideoMemToDev(ptVideoMem);

	/* 设置页面内存为空闲状态 */
	PutVideoMem(ptVideoMem);
}

4、触摸输入的设置

4.1 思想

对于页面中需要显示的图标,每个图标都有对应的有效触摸区域。当用户触摸到对应的区域时,进入不同的页面(未实现,下篇博客实现)且该图标的颜色变化
由于这里使用到输入系统,即【1.7 数码相册—电子书(5)—多线程支持多输入】中介绍的,会使用到触摸屏子线程与tslib库

4.2 具体实现

  • 步骤描述
2.1 调用SettingPageGetInputEvent(),获得该页面的输入事件与对应图标的索引,
		由于调用到之前的输入事件子线程,所以在这里程序会处于休眠,有输入事件才唤醒
		
2.2.1 若输入事件得到的压力值为0(松开) 且 pressured==1曾经按下,则调用ReleaseButton(),根据indexPressured,把图标颜色取反
		2.1.1 根据按键的索引进入不同的页面
				2.1.1.1 indexPressured == 1,显示interval_page页面
						若显示interval_page页面退出,则显示当前页面
				
				2.1.1.2 indexPressured == 2,返回上一级页面
				
2.2.2 若输入事件得到的压力值为1(按下) 且 已经获得了按键的index索引 且 pressured==0未按下
  • 代码实现
/* 调用GetInputEvent(), 获得输入事件,进而处理 */
	while (1) {
		index = SettingPageGetInputEvent(&s_tSettingPageLayout, &tInputEvent);
		
		/* 松开和按下不在同一个图标范围内 */
		if (tInputEvent.pressure == 0) {
			
			/* 曾经有按键按下 */
			if (pressured) {
				ReleaseButton(&s_tSettingPageIconsLayout[indexPressured]);
				pressured = 0;

				/* 松开与按下的按键为同一个 */
				if (indexPressured == index) {
					switch (indexPressured) {
					case 1: /* interval按钮 */
						
						Page("interval")->Run();
						ShowSettingPage(&s_tSettingPageLayout);
						break;
						
					case 2:		/* 返回按键 */
						
						return 0;

					default:
						break;
					}
				}
				
				indexPressured = -1;
			}
		} else {
			/* 松开和按下都在同一个图标范围内 */
			/* 按下 */
			if (index != -1) {

				/* 之前未按下 */
				if (!pressured) {
					pressured = 1;
					indexPressured = index;
					PressButton(&s_tSettingPageIconsLayout[indexPressured]);
				}
			}
		}
	}

5、页面的切换

main_page主页面与setting_page设置页面的切换,二者通过在对应页面的GetInputEvent()函数获取当前按下图标的索引值,根据索引值执行Page("xxx")->Run();进入到不同页面

  • main_page主页面:
	switch (indexPressured) {
	case 2:		/* setting_page */
		/* 设置页面 */
		Page("setting")->Run();
	
		/* 返回主页面 */
		ShowMainPage(&s_tMainPageLayout);
		break;
	
	default:
		break;
	}
  • setting_page设置页面:
	switch (indexPressured) {
	case 1: /* interval按钮 */
		/* 设置时间间隔页面 */
		Page("interval")->Run();
		
		/* 返回设置页面 */
		ShowSettingPage(&s_tSettingPageLayout);
		break;
		
	case 2:		/* 返回按键 */	
		return 0;
	
	default:
		break;
	}

三、编译与运行

1、编译

执行make生成可执行文件digitpic文件

2、运行

  1. 可执行文件digitpic放入到根文件系统
  2. icon目录下的图标文件放入到根文件系统的/etc/digitpic/icons(程序打开图标文件时需要用到)
  3. 执行.digitpic,得到以下的效果:
    程序一开始进入main_page主界面按下与松开3个图标的位置,图标的颜色会反转
    在这里插入图片描述
    按下设置按钮时,进入setting_main设置界
    在这里插入图片描述
    在这里插入图片描述按下返回按键后可以返回main_page主页面
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42813232/article/details/107320871