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

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

相关文章

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Python Invoke自动化任务库的使用

《PythonInvoke自动化任务库的使用》Invoke是一个强大的Python库,用于编写自动化脚本,本文就来介绍一下PythonInvoke自动化任务库的使用,具有一定的参考价值,感兴趣的可以... 目录什么是 Invoke?如何安装 Invoke?Invoke 基础1. 运行测试2. 构建文档3.

解决Cron定时任务中Pytest脚本无法发送邮件的问题

《解决Cron定时任务中Pytest脚本无法发送邮件的问题》文章探讨解决在Cron定时任务中运行Pytest脚本时邮件发送失败的问题,先优化环境变量,再检查Pytest邮件配置,接着配置文件确保SMT... 目录引言1. 环境变量优化:确保Cron任务可以正确执行解决方案:1.1. 创建一个脚本1.2. 修

Redis主从复制的原理分析

《Redis主从复制的原理分析》Redis主从复制通过将数据镜像到多个从节点,实现高可用性和扩展性,主从复制包括初次全量同步和增量同步两个阶段,为优化复制性能,可以采用AOF持久化、调整复制超时时间、... 目录Redis主从复制的原理主从复制概述配置主从复制数据同步过程复制一致性与延迟故障转移机制监控与维

Java实现任务管理器性能网络监控数据的方法详解

《Java实现任务管理器性能网络监控数据的方法详解》在现代操作系统中,任务管理器是一个非常重要的工具,用于监控和管理计算机的运行状态,包括CPU使用率、内存占用等,对于开发者和系统管理员来说,了解这些... 目录引言一、背景知识二、准备工作1. Maven依赖2. Gradle依赖三、代码实现四、代码详解五

Redis连接失败:客户端IP不在白名单中的问题分析与解决方案

《Redis连接失败:客户端IP不在白名单中的问题分析与解决方案》在现代分布式系统中,Redis作为一种高性能的内存数据库,被广泛应用于缓存、消息队列、会话存储等场景,然而,在实际使用过程中,我们可能... 目录一、问题背景二、错误分析1. 错误信息解读2. 根本原因三、解决方案1. 将客户端IP添加到Re

如何使用celery进行异步处理和定时任务(django)

《如何使用celery进行异步处理和定时任务(django)》文章介绍了Celery的基本概念、安装方法、如何使用Celery进行异步任务处理以及如何设置定时任务,通过Celery,可以在Web应用中... 目录一、celery的作用二、安装celery三、使用celery 异步执行任务四、使用celery

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

什么是cron? Linux系统下Cron定时任务使用指南

《什么是cron?Linux系统下Cron定时任务使用指南》在日常的Linux系统管理和维护中,定时执行任务是非常常见的需求,你可能需要每天执行备份任务、清理系统日志或运行特定的脚本,而不想每天... 在管理 linux 服务器的过程中,总有一些任务需要我们定期或重复执行。就比如备份任务,通常会选在服务器资

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专