韦东山数码相框任务需求分析

2023-10-12 07:59

本文主要是介绍韦东山数码相框任务需求分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言
  • 需求界面
  • 抽象流程
    • 界面分解
    • Page 结构体
      • static void (*Display)();
      • static void (*GetInputEvent)();
    • 第一界面:主界面
    • 第二界面:选择界面
    • 第三界面:浏览界面
    • 第四界面:连播界面
    • 第五界面:设置界面
    • 第六界面:间隔界面
    • 其他过程抽象
      • 显示接口
      • 读取图片/图标
        • GetPicFmts(Icon 图片信息):
      • 输入接口
      • 调试输出接口
    • 优化接口
      • 显示内存管理
    • 后续想法
      • 阅读界面

前言

只是简单分析了下各个结构体的由来,意淫编程

整体框架参考了韦东山数码相框修正调整,另类介绍程序代码结构

需求界面

整个需求如下图
在这里插入图片描述

抽象流程

理解为是各个界面,通过不同的按钮相关切换,所以将界面抽象出来
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

总共分解成六个小界面,针对每个界面,这时可以想到的操作有:

  1. 显示界面内容 ==》显示数据准备
  2. 响应界面上的触摸事件 =》按键位置判断为哪个按钮

针对界面,则有管理问题,是数组,还是链表?

这里所能想到的对应结构体基本结构应为:

Page {char *name;           	        // 页面名字 void (*Display)();              // 页面的运行函数int (*GetInputEvent)();         // 获得输入数据的函数 Page *ptNext;                   // 链表管理
}

界面分解

每个界面又分为类似如下几个图标:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

针对这些图标,想到的可能属性有:

  1. 位置
  2. 使用哪里图片

对应结构体:

Icon{iTopLeftX                       // 左上角坐标 iTopLeftYiBotRightX                      // 右下角坐标 iBotRightYstrIconName                     // 图片位置
}

而图标数目这种明显跟界面强相关,需要保存在界面 Page 结构体中

Page {char *name;           	        // 页面名字 void (*Display)();              // 页面的运行函数int (*GetInputEvent)();         // 获得输入数据的函数 Page *ptNext;                   // 链表管理Icon[]                          // 所有包含的图标
}

再梳理流程:

Main -> Browser -> manual ||---> Auto||--->Setting --> interval

这个时候需要一个更高层次的来调用组织 Page,暂时叫 App 结构体吧,但是抽象了发现,切换到哪个界面跟只有界面自己知道,这个逻辑最简单,高内聚,每个程序管好自己内部就行了,切出去时自己指定切到谁。这里发现就需要在 Page 内部做逻辑切换。为此在 Page 内部增加一个 Run() 函数

Page {char *name;           	        // 页面名字 void (*Run)();                  // 页面运行函数	            int (*GetInputEvent)();         // 获得输入数据的函数 void (*Display)();              // 页面显示函数Page *ptNext;                   // 链表管理Icon[]                          // 所有包含的图标
}

梳理下 Run() 流程大致如下:

Run()Display();			// 显示主界面for(;;)GetInputEvent();				// 获取哪个按钮被点击switch()PageSelect("目的界面")->Run(); 

这里发现 Display() 跟 GetInputEvent() 似乎不会被其他模块调用,属于 Private 内部就好了

Page {char *name;           	        // 页面名字 void (*Run)();                  // 页面运行函数static int (*GetInputEvent)();         // 获得输入数据的函数 static void (*Display)();              // 页面显示函数Page *ptNext;                   // 链表管理Icon[]                          // 所有包含的图标
}

到这里,大的切换框架已经可以实现了,整体程序暂时框架为:

main()PageInit();											// 注册所有界面到链表 PageList 中管理PageSelect("Main")->Run();							// 选择主界面运行

剩下再细究先研究 Page 结构体对应函数功能, 然后再针对每个界面不同重载的特殊处理。

Page 结构体

Page {char *name;           	        // 页面名字 void (*Run)();                  // 页面运行函数static int (*GetInputEvent)();         // 获得输入数据的函数 static void (*Display)();              // 页面显示函数Page *ptNext;                   // 链表管理Icon[]                          // 所有包含的图标
}

static void (*Display)();

在这里插入图片描述
比如像这种怎么显示到界面上

Display() 流程:1. 获取一块内存2. 填充内存2.1 读取图片2.2 加载到显示内存指定位置3. 刷新到显示中

static void (*GetInputEvent)();

static int (*GetInputEvent)();         // 获得输入数据的函数 1. 获取报点2. 判断点是否有在某个图标位置内部,有返回数组下标

第一界面:主界面

在这里插入图片描述
都是些界面跳转,最简单的一页,流程不需要特别改

Page {char *name;           	            // 页面名字 void (*Run)();                      // 页面运行函数Display();			                // 显示主界面for(;;)GetInputEvent();				// 获取哪个按钮被点击switch()case: 浏览按钮PageSelect("选择界面")->Run(); case: 连播按钮PageSelect("连播界面")->Run(); case: 设置按钮PageSelect("设置界面")->Run(); static int (*GetInputEvent)();         // 获得输入数据的函数 1. 获取报点2. 判断点是否有在某个图标位置内部,有返回数组下标static void (*Display)();              // 页面显示函数1. 获取一块内存2. 填充内存2.1 读取图片2.2 加载到显示内存指定位置3. 刷新到显示中Page *ptNext;                       // 链表管理Icon[]                              // 所有包含的图标
}

第二界面:选择界面

在这里插入图片描述
涉及到目录切换与图标显示,而且实际图标只有四个,剩下 9 个图标是可变的

Page {char *name;           	            // 页面名字 void (*Run)();                      // 页面运行函数Display(“当前根目录”,1);			                // 显示主界面1 打开目录2 遍历目录,保存 文件名+是否文件, 目录名+是否目录到缓存中for(;;)GetInputEvent();				// 获取哪个按钮被点击判断是这 13 个按钮哪个被按到,是文件还是目录文件或目录的话,则保存名称及类型switch()case: 向上:当前目录缩短一段1 打开目录2 遍历目录,保存 文件名+是否文件, 目录名+是否目录到缓存中Display(“当前目录”,缓存[index 第几个 9]);case: 选择:目录:当前目录增加一段1 打开目录2 遍历目录,保存 文件名+是否文件, 目录名+是否目录到缓存中Display(“当前目录”,缓存[index 第几个 9]);文件:目前仅支持图片当前路径增加一段PageSelect("浏览界面")->Run("当前图片路径");  # Run() 需要增加参数case: 上一页:index++Display(“当前目录”,缓存[index 第几个 9]);case: 下一页 index--Display(“当前目录”,缓存[index 第几个 9]);static int (*GetInputEvent)();         // 获得输入数据的函数 1. 获取报点2. 判断点是否有在某个图标位置内部,有返回数组下标static void (*Display)("目录路径",缓存[index 第几个 9]);              // 页面显示函数1. 获取一块内存2. 填充内存2.1 根据 缓存[index 第几个 9] 更新下九个图标的名称2.1 读取图标2.2 刷新到显示中3. 刷新到显示中Page *ptNext;                       // 链表管理Icon[]                              // 所有包含的图标
}

第三界面:浏览界面

在这里插入图片描述
Page {
char *name; // 页面名字

    void (*Run)("当前图片路径");        // 页面运行函数Display();			                // 显示主界面1 当前图片路径2 遍历目录,保存 文件名 缓存中for(;;)GetInputEvent();				// 获取哪个按钮被点击switch()case: 返回退出当前 Run()case: 缩小缩放标志--Display();case: 放大缩放标志++Display();case: 上一张 更新当前图片路径为上一张图片Display();case: 下一张 更新当前图片路径为下一张图片Display();case: 连播 PageSelect("连播界面")->Run("当前图片路径");static int (*GetInputEvent)();         // 获得输入数据的函数 1. 获取报点2. 判断点是否有在某个图标位置内部,有返回数组下标static void (*Display)();              // 页面显示函数1. 获取一块内存2. 填充内存2.1 读取图标2.2 读取当前图片路径 + 缩放标志2.2 加载到显示内存指定位置3. 刷新到显示中Page *ptNext;                       // 链表管理Icon[]                              // 所有包含的图标
}

第四界面:连播界面

在这里插入图片描述
Page {
char *name; // 页面名字

    void (*Run)("当前图片路径");        // 页面运行函数Display();			                // 显示主界面1 当前图片路径2 遍历目录,保存 文件名 缓存中for(;;)GetInputEvent();				// 获取哪个按钮被点击switch()case: 返回退出当前 Run()延时指定时间间隔更新当前图片路径为下一张 Display()static int (*GetInputEvent)();         // 获得输入数据的函数 1. 获取报点2. 判断点是否有在某个图标位置内部,有返回数组下标static void (*Display)();              // 页面显示函数1. 获取一块内存2. 填充内存2.1 读取图标2.2 读取当前图片路径 + 缩放标志2.3 加载到显示内存指定位置3. 刷新到显示中Page *ptNext;                       // 链表管理Icon[]                              // 所有包含的图标
}

第五界面:设置界面

在这里插入图片描述
Page {
char *name; // 页面名字

    void (*Run)("当前图片路径");        // 页面运行函数Display();			                // 显示主界面当前图片路径 = 根目录for(;;)GetInputEvent();				// 获取哪个按钮被点击switch()case: 选择目录PageSelect("选择界面")->Run("当前图片路径");case: 设置间隔PageSelect("连播界面")->Run("当前图片路径");static int (*GetInputEvent)();         // 获得输入数据的函数 1. 获取报点2. 判断点是否有在某个图标位置内部,有返回数组下标static void (*Display)();              // 页面显示函数1. 获取一块内存2. 填充内存2.1 读取图标2.2 加载到显示内存指定位置3. 刷新到显示中Page *ptNext;                       // 链表管理Icon[]                              // 所有包含的图标
}

第六界面:间隔界面

在这里插入图片描述
这里发现需要在添加一个时间间隔全局变量

Page {char *name;           	            // 页面名字 void (*Run)("当前图片路径");        // 页面运行函数Display();			                // 显示主界面当前图片路径 = 根目录for(;;)GetInputEvent();				// 获取哪个按钮被点击switch()case: 增加时间间隔++Display()case: 减小时间间隔--Display()static int (*GetInputEvent)();         // 获得输入数据的函数 1. 获取报点2. 判断点是否有在某个图标位置内部,有返回数组下标static void (*Display)();              // 页面显示函数1. 获取一块内存2. 填充内存            2.1 根据 时间间隔 选择中间图标用哪张图2.2 读取图标2.3 加载到显示内存指定位置3. 刷新到显示中Page *ptNext;                       // 链表管理Icon[]                              // 所有包含的图标
}

到这里,感觉程序整体框架已经搭完了,下面思考下各个页面中使用到的模块抽象

其他过程抽象

显示接口

显示在哪里使用呢?在 Display() 流程有使用

Display() 流程:1. 获取一块内存2. 填充内存2.1 读取图片2.2 加载到显示内存指定位置3. 刷新到显示中

针对 Linux 的话,显示就是将显存映射为一块内存,然后往内存里面填东西就能显示

// 显示接口
Display{char *name;			// 显示接口名称void (*Init)();     // 显示接口初始化流程,比如打开,映射显示设备void (*Flush)(“包含显示的缓存”);    // 刷新显示     
}

这些都是根据上面流程想到的比较直接的接口定义

读取图片/图标

  1. 图片图标使用位置

     Display() 流程:1. 获取一块内存2. 填充内存2.1 读取图片2.2 加载到显示内存指定位置3. 刷新到显示中
    
  2. 图片,图标那肯定有不同的格式的,所以会需要不同的格式解析模块

  3. 不同图片格式那也是需要管理的,链表吧,就用 g_PicFmtsList

     PicFmt{char *name;								        // 图片解析模块名称,比如 Bmp, Pngvoid (*Read)(显示缓存,Icon 图片信息);			// 读取图片到显示缓存中指定位置ptNext		                                    // 下一个模块}
    

这里还有个问题,程序还需要判断这是什么图片类型后,才好调用具体的 PicFmts 格式处理的,所以 PicFmts 还需要有个判断本模块是否支持的功能

PicFmt{char *name;								        // 图片解析模块名称void (*Read)(显示缓存,Icon 图片信息);		    // 读取图片到显示缓存中指定位置1. 针对 Icon 所有图片,打开图片2. 解析图片内容放进显示缓存指定位置void (*isSupport)(Icon 图片信息)1. 打开图片2. 判断格式是否是本模块支持的ptNext		                                    // 下一个模块
}

这样针对每个传入的 Icon 图片,需要先遍历链表 g_PicFmtsList 通过 isSupport() 找到对应模块,再调用读入函数

GetPicFmts(Icon 图片信息):

需要先遍历链表 g_PicFmtsList 通过 isSupport() 找到对应模块,再调用读入函数

1. 遍历 g_PicFmtsList 链表,调用 PicFmt->isSupport() 判断是否有模块支持 
2. 返回支持的 PicFmt 结构体

所以 Display 流程会更新类似如下:

	Display() 流程:1. 获取一块内存2. 填充内存2.1 遍历当前页面 Icon[]2.2 GetPicFmts(Icon):获取对应处理格式模块2.3 PicFmt->Read(显示缓存,Icon ):读入显存中3. 刷新到显示中 	

输入接口

输入接口使用位置:

static int (*GetInputEvent)();         // 获得输入数据的函数 1. 获取报点2. 判断点是否有在某个图标位置内部,有返回数组下标

在第一步获取报点处使用,输入接口相对于六个界面是独立存在的,所以可以用个独立的循环线程存在

Input{char *name;                 // 输入模块名称void (*Init)()              // 输入设备初始化1. 打开设备,创建线程轮询等待事件上报2. 在线程中,有数据上报就唤醒 GetInputData() 上的睡眠进程void (*GetInputData)()      // 获取输入数据等待输入事件并上报
}

当然感觉输入设备不应该只有触摸屏,想以后也可以响应按键,响应网络,响应终端等输入设备,所以这个结构体还需要再改改
需要用链表管理

Input{char *name;                 // 输入模块名称void (*Init)()              // 输入设备初始化1. 打开设备,创建线程轮询等待事件上报2. 在线程中,有数据上报就唤醒 GetInputData() 上的睡眠进程void (*GetInputData)()      // 获取输入数据检查是否有事件上报,有则上报,无则睡眠ptNext                      // 下一个模块 
}

也需要修改界面的 GetInputEvent() 函数,以及 Run() 函数因为每个界面响应的按键方式可能不一定

static int (*GetInputEvent)();         // 获得输入数据的函数 1. 获取报点2. 判断点是否有在某个图标位置内部,有返回数组下标3. 获取按键void (*Run)("当前图片路径");        // 页面运行函数Display();			                // 显示主界面当前图片路径 = 根目录for(;;)GetInputEvent();				// 获取哪个按钮被点击switch()case: 选择目录PageSelect("选择界面")->Run("当前图片路径");case: 设置间隔PageSelect("连播界面")->Run("当前图片路径");################添加按键等响应处理

调试输出接口

调试输出接口嘛,就是支持各种 log 输出,可以从 文件输出、标准输出、网络输出、串口输出, 屏上输出等等

Debug{char *name;             // 输出名称void (*Init)()          // 调试模块初始化 1. 打开输出模块2. 创建线程,等待 DebugPrint() 函数输入,再转发输出模块输出void (*DebugPrint)(格式化字符串)    // 输出函数唤醒线程,让其通过特定模块输出 logptNext;                 // 下一个模块 
}

优化接口

显示内存管理

显示过程中发现还有获取一块内存的操作,像这种也可以使用缓冲池管理

Display() 流程:1. 获取一块内存2. 填充内存2.1 遍历当前页面 Icon[]2.2 GetPicFmts(Icon):获取对应处理格式模块2.3 PicFmt->Read(显示缓存,Icon ):读入显存中3. 刷新到显示中 		

可用如下结构体管理:

VideoMem{int count;void (*Init)(内存大小,内存块数)         // 建池void (*Get)()                            // 从缓冲池获取数据void (*Set)()                            // 释放到缓冲池中
}

后续想法

阅读界面

比如想在浏览界面,支持文本阅读,那要怎么实现呢?
阅读界面逻辑如下:

Page {char *name;           	            // 页面名字 void (*Run)("当前文件路径");        // 页面运行函数Display();			                // 显示主界面for(;;)GetInputEvent();				// 获取哪个按钮被点击switch()case: 上一页根据屏大小,及字体大小,更新上一页开始文件中位置Display()case: 下一页根据屏大小,及字体大小,更新下一页开始文件中位置Display()static int (*GetInputEvent)();         // 获得输入数据的函数 1. 获取报点2. 判断点位置,左半屏上翻,右半屏下翻static void (*Display)();              // 页面显示函数1. 获取一块内存2. 填充内存2.1 读取文件2.2 加载到显示内存指定位置3. 刷新到显示中Page *ptNext;                       // 链表管理Icon[]                              // 所有包含的图标
}

根据之前学习过程可知道

	怎样在 LCD 上显示文件:1. 根据文件获得字符编码 {ASCII, GBK【这一行是大陆用户默认的】UTF-8,UTF16LE,UTF16BE,}2. 根据编码从字体文件中得到 字体数据【包括点阵图】{ASCII字体数组,HZK16,GBK字体文件,freetype,}3. 把 点阵图 在 LCD 上显示出来

字库:主要是将不同编码的字符,转换成对应的点阵图,所以
一个字库可以支持多种编码方式

freetype: 支持 ASCII/GBK/UTF-8/UTF16LE/UTF16BE
HZK16: 支持 ASCII/GBK
ASCII: 支持 ASCII /UTF-16LE/UTF-16BE/UTF-8 			# ? 有这么多?参考程序提取

针对浏览界面读取 txt 场景,处理流程大致如下:

static void (*Display)();              // 页面显示函数1. 获取一块内存2. 填充内存2.1 读取文件,判断文本字符编码2.2 根据字符字符获取让对应模块处理,获取其点阵图2.3 将获取的点阵图显示在显示缓存合适位置3. 刷新到显示中

故可以针对上面的字符编码,字库进行抽象如下:

Encoding{char *name;                     // 编码名称 void (*isSupport)(文件路径)     // 是否是某字符文件1. 打开文件,读取到内存中2. 判断是否支持此种编码文件void (*DisplayTxt)(显示缓存,文件路径,文件内部位置,字体大小)1. 打开文件,2. 根据屏幕尺寸及字体大小,更新可从指定位置读取多少字符3. 将读入的字符通过支持此编码的字库的 GetBmpData() 获得位图4. 将获得的位图填充到显示缓存中ptNext                          // 链表管理 ptNextFont						// 会有个绑定支持字符操作,放到这个链表中
}Fonts{char *name;                     // 字库名称void (*Init)()                  // 字库初始化void (*GetBmpData)(字符,返回位图)        // 根据字符,返回对应的位图ptNext                          // 指向下一个字库}

更新显示场景逻辑:

static void (*Display)();              // 页面显示函数1. 获取一块内存2. 填充内存2.1 遍历编码表, 调用 isSupport() 判断是否有支持的2.2 对应编码的 DisplayTxt() 显示字符3. 刷新到显示中

这篇关于韦东山数码相框任务需求分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/194433

相关文章

[职场] 公务员的利弊分析 #知识分享#经验分享#其他

公务员的利弊分析     公务员作为一种稳定的职业选择,一直备受人们的关注。然而,就像任何其他职业一样,公务员职位也有其利与弊。本文将对公务员的利弊进行分析,帮助读者更好地了解这一职业的特点。 利: 1. 稳定的职业:公务员职位通常具有较高的稳定性,一旦进入公务员队伍,往往可以享受到稳定的工作环境和薪资待遇。这对于那些追求稳定的人来说,是一个很大的优势。 2. 薪资福利优厚:公务员的薪资和

高度内卷下,企业如何通过VOC(客户之声)做好竞争分析?

VOC,即客户之声,是一种通过收集和分析客户反馈、需求和期望,来洞察市场趋势和竞争对手动态的方法。在高度内卷的市场环境下,VOC不仅能够帮助企业了解客户的真实需求,还能为企业提供宝贵的竞争情报,助力企业在竞争中占据有利地位。 那么,企业该如何通过VOC(客户之声)做好竞争分析呢?深圳天行健企业管理咨询公司解析如下: 首先,要建立完善的VOC收集机制。这包括通过线上渠道(如社交媒体、官网留言

如何设置windows计划任务

如何设置windows计划任务 前言:在工作过程中写了一个python脚本,用于调用jira接口查询bug单数量,想要在本地定时任务执行,每天发送到钉钉群提醒,写下操作步骤用于记录。 1. 准备 Python 脚本 确保你的 Python 脚本已经保存到一个文件,比如 jira_reminder.py。 2. 创建批处理文件 为了方便任务计划程序运行 Python 脚本,创建一个批处理文

打包体积分析和优化

webpack分析工具:webpack-bundle-analyzer 1. 通过<script src="./vue.js"></script>方式引入vue、vuex、vue-router等包(CDN) // webpack.config.jsif(process.env.NODE_ENV==='production') {module.exports = {devtool: 'none

Java中的大数据处理与分析架构

Java中的大数据处理与分析架构 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们来讨论Java中的大数据处理与分析架构。随着大数据时代的到来,海量数据的存储、处理和分析变得至关重要。Java作为一门广泛使用的编程语言,在大数据领域有着广泛的应用。本文将介绍Java在大数据处理和分析中的关键技术和架构设计。 大数据处理与

段,页,段页,三种内存(RAM)管理机制分析

段,页,段页         是为实现虚拟内存而产生的技术。直接使用物理内存弊端:地址空间不隔离,内存使用效率低。 段 段:就是按照二进制文件的格式,在内存给进程分段(包括堆栈、数据段、代码段)。通过段寄存器中的段表来进行虚拟地址和物理地址的转换。 段实现的虚拟地址 = 段号+offset 物理地址:被分为很多个有编号的段,每个进程的虚拟地址都有段号,这样可以实现虚实地址之间的转换。其实所谓的地

mediasoup 源码分析 (八)分析PlainTransport

mediasoup 源码分析 (六)分析PlainTransport 一、接收裸RTP流二、mediasoup 中udp建立过程 tips 一、接收裸RTP流 PlainTransport 可以接收裸RTP流,也可以接收AES加密的RTP流。源码中提供了一个通过ffmpeg发送裸RTP流到mediasoup的脚本,具体地址为:mediasoup-demo/broadcaste

Java并发编程—阻塞队列源码分析

在前面几篇文章中,我们讨论了同步容器(Hashtable、Vector),也讨论了并发容器(ConcurrentHashMap、CopyOnWriteArrayList),这些工具都为我们编写多线程程序提供了很大的方便。今天我们来讨论另外一类容器:阻塞队列。   在前面我们接触的队列都是非阻塞队列,比如PriorityQueue、LinkedList(LinkedList是双向链表,它实现了D

线程池ThreadPoolExecutor类源码分析

Java并发编程:线程池的使用   在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:   如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。   那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?

ConcurrentHashMap之源码分析

集合是编程中最常用的数据结构。而谈到并发,几乎总是离不开集合这类高级数据结构的支持。比如两个线程需要同时访问一个中间临界区(Queue),比如常会用缓存作为外部文件的副本(HashMap)。这篇文章主要分析jdk1.5的3种并发集合类型(concurrent,copyonright,queue)中的ConcurrentHashMap,让我们从原理上细致的了解它们,能够让我们在深度项目开发中获益非浅