以下文章来源于嵌入式小书虫 ,作者FledgingSu 支离苏
【说在前面的话】
上一篇我们利用脏矩阵在m0的单片机上制作了酷炫的汽车仪表盘界面,今天我们继续压榨M0的性能,看看播放动画的效果怎么样。
什么?播放动画你有点不信?
那先看看下面的视频。
视频中的小孩走来走去,你知道这个是怎么实现的吗?
哈哈,下面我们就从原理开始讲起,一步一步实现这个小孩运动的效果(* ̄︶ ̄)
当然,我们还是先简单说一下这个板子的软硬件配置,如下
配置讲完接下来就看看这个动画效果是怎么实现的。
【动画原理】
首先,视频中的动画效果其实是一张gif图转换而来的。
网上有工具可以把 GIF图 转化为 平铺开的PNG图片(叫 sprites),平铺工具连接如下
https://ezgif.com/gif-to-sprite
打开 此网站如下
上传完成,点击Upload按钮,如下
在跳转到的新页面选择to sprite,如下
然后点击蓝色按钮就可以生成图片,如下
最后,点击鼠标右键选择图片另存为即可。
把GIF展开后平铺的效果如下图所示
你是不是瞬间就明白了,原来播放动画其实就是不停地切换图片而已(* ̄︶ ̄)
【Arm-2D中的film】
播放动画的原理很简单,那我们接着看看Arm-2D中专门用来处理图片类动画的arm\_2d\_helper\_film\_t是怎么使用的。
首先,要把GIF转化后的PNG图片取模生成tile(制作tile之前的文章有讲,这里就不展开讲了)
extern const arm_2d_tile_t c_tileqiuRGB565;
接着用impl\_film宏生成arm\_2d\_helper\_film\_t,如下
static arm_2d_helper_film_t s_tileqiuRGB565Film =
impl_film(c_tileqiuRGB565, 80, 80, 6, 12, 100);
- 第1个参数为上面的平铺 tile
- 第2个参数为每帧的 width(每张图片为80*80像素)
- 第3个参数为每帧的height
- 第4个参数为平铺 tile里的一行有几个帧
- 第5个参数为总帧数
- 第6个参数为一个参考的帧持续时间,(这里是100,代表100ms切换一帧)
好,有了这个arm\_2d\_helper\_film\_t
类型的变量s\_tileqiuRGB565Film ,我们就可以用它来显示图片了,它的使用和普通的tile是一样的,也就是tile能出现的地方,这个s\_tileqiuRGB565Film 就可以出现在那里
接着把这个tile显示出来就可以了,如下
arm_2d_rgb16_tile_copy_with_colour_keying_only(
(arm_2d_tile_t *)&s_tileqiuRGB565Film,
ptTile,
&myRegion,
GLCD_COLOR_BLACK
) ;
对了,这个只是显示一帧图片,那要显示下一帧该怎么办呢?
其实,这个也简单,在 frame\_start 事件处理程序里,调用 arm\_2d\_helper\_file\_next\_frame() 来换帧就行了,程序如下
static void __on_scene1_frame_start(arm_2d_scene_t *ptScene)
{
if (arm_2d_helper_is_time_out( s_tileqiuRGB565Film.hwPeriodPerFrame,
&this.lTimestamp[3])) {
arm_2d_helper_film_next_frame(&s_tileqiuRGB565Film);
}
}
- 这个s\_tileqiuRGB565Film.hwPeriodPerFrame就是刚才我们设置的第6个参数,超过100ms就切换成下一帧
- 这里就是用 arm\_2d\_helper\_is\_timeout 来实现的锁帧换帧的
当然,这里只是按顺序切换到下一帧,那能不能跳到指定的帧去显示呢?
答案是肯定的,
使用 arm\_2d\_helper\_film\_set\_frame()函数就可以了,他的使用如下
arm_2d_helper_film_set_frame(&s_tileqiuRGB565Film, 3);
聪明的你是不是也发现,开始视频里的小孩是左右移动的,而现在我们只是切换图片并没有左右移动。别急,接下来我们就讲讲小孩的移动。
【小孩的左右移动】
其实,左右移动也很简单。因为在显示每一帧图片时,我们会传入一个arm\_2d\_region\_t类型的变量,只要修改它的值,小孩的位置就不一样了,程序如下
arm_2d_region_t myRegion = {
.tLocation = {.iX = 104,.iY = 101,},
.tSize = {.iWidth = 80,.iHeight = 80,},
};
//修改x方向的偏移量
myRegion.tLocation.iX = iX_offset;
arm_2d_rgb16_tile_copy_with_colour_keying_only(
(arm_2d_tile_t *)&s_tileqiuRGB565Film,
ptTile,
&myRegion,
GLCD_COLOR_BLACK
) ;
- 第6行,左右移动就是修改X方向的坐标值,那iX\_offset是怎么确定的呢?
这个值也是在frame\_start 事件处理程序里设置的,如下
static void __on_scene1_frame_start(arm_2d_scene_t *ptScene)
{
if (arm_2d_helper_is_time_out( s_tileqiuRGB565Film.hwPeriodPerFrame,
&this.lTimestamp[3])) {
int32_t iResult;
arm_2d_helper_time_cos_slider(10, 150, 1200, 0, &iResult, &this.lTimestamp[1]);
iX_offset = iResult;
arm_2d_helper_film_next_frame(&s_tileqiuRGB565Film);
}
}
- 这次我们使用了arm\_2d\_helper\_time\_cos\_slider函数,这样我们的运动轨迹就符合cos函数,使得移动看起来明显有加减速的感觉。
- 他的用法也很简单,cos slider是以cos函数的形式让一个值在 from 和to之间随着时间进行变化。例如
arm\_2d\_helper\_time\_cos\_slider(10, 150, 1200, 0, &iResult, &this.lTimestamp\[1\]);
就是让你以1200ms为周期,在10~150之间以cos函数为参考进行变化,并把计算好的值传给iResult,是不是很简单。
好了,到这里视频中的动画效果就实现了,大家可以赶快动手试试了,此程序参考了官方的代码,地址如下
当然,如果你之前还没有移植过Arm-2D,那也可以看这篇最新的移植教程哦,如下
等等,不是说好的要对帧率进行优化吗?怎么就结束了呢?
接下来我们就看看怎么对帧率进行优化。
【帧率优化】
说到帧率优化,那肯定还是得用脏矩阵(局部刷新)了。上一篇也讲了一下脏矩阵,不过脏矩阵的计算官方已经帮我们计算好了,我们只是调用了一下,带着对脏矩阵计算的好奇心,这次我们手动对脏矩阵进行修改,看看里面到底是什么(* ̄︶ ̄)
首先,我们这次还是需要两块脏矩阵区域,如下图所示
当小球和小孩从位置1移动到位置2时,我们在位置1的区域擦除掉小孩并在位置2的区域重新绘制一帧图片就可以了,所以需要两块脏矩阵区域。那我们就先定义两块脏矩阵区域,如下
IMPL_ARM_2D_REGION_LIST(s_tDirtyRegions, static)
/* a dirty region to be specified at runtime*/
//添加两个脏矩阵区域
ADD_REGION_TO_LIST(s_tDirtyRegions,
.tLocation = {.iX = 0,.iY = 101,},
.tSize = {.iWidth = 80,.iHeight = 80,},
),
ADD_REGION_TO_LIST(s_tDirtyRegions),
- 我们在\_\_arm\_2d\_scene1\_init函数中添加两个脏矩阵区域,第一个区域我们赋了初值,因为小球最开始的位置为最左边。
那问题又来了,怎么在运行时修改脏矩阵的区域呢?
哈哈,这个贴心的官方也为我们提供了修改脏矩阵的方法哦。其实脏矩阵区域说白了就是一个变量,我们只要获取到这个变量然后修改它的值就可以了,获取到脏矩阵变量也很简单,如下
this.use_as__arm_2d_scene_t.ptDirtyRegion[0].tRegion
- 注意,这里ptDirtyRegion[0]数组下标为0,代表我们第一个添加的脏矩阵元素(即赋初值的那个),以此类推,下标为1就是第二个添加的脏矩阵元素,我们今天就修改这两个脏矩阵区域就可以了。
接下来,我们还是在frame\_start 事件处理程序里修改脏矩阵区域,程序如下
if (arm_2d_helper_is_time_out( s_tileqiuRGB565Film.hwPeriodPerFrame,
&this.lTimestamp[3])) {
int32_t iResult;
arm_2d_helper_time_cos_slider(10, 150, 1200, 0, &iResult, &this.lTimestamp[1]);
iX_num = iResult;
arm_2d_helper_film_next_frame(&s_tileqiuRGB565Film);
//更新脏矩阵区域
DirtyRegionUpdata( ptScene, iResult);
}
- 这次我们定义了一个函数来更新脏矩阵区域,DirtyRegionUpdata函数如下
void DirtyRegionUpdata2(arm_2d_scene_t *ptScene,int32_t iResult){
user_scene_1_t *ptThis = (user_scene_1_t *)ptScene;
//定义两个区域
static arm_2d_region_t myRegion_old = {
.tLocation = {.iX = 0,.iY = 101,},
.tSize = {.iWidth = 80,.iHeight = 80,},
};
static arm_2d_region_t myRegion_new = {
.tLocation = {.iX = 0,.iY = 101,},
.tSize = {.iWidth = 80,.iHeight = 80,},
};
//保存需要擦除的区域
myRegion_old.tLocation.iX = myRegion_new.tLocation.iX;
//保存需要绘制的区域
myRegion_new.tLocation.iX = iResult;
//更新脏矩阵
this.use_as__arm_2d_scene_t.ptDirtyRegion[0].tRegion = myRegion_old;
this.use_as__arm_2d_scene_t.ptDirtyRegion[1].tRegion = myRegion_new;
}
- 首先我们定义两个区域,一个用来保存需要擦除的区域一个用来保存需要绘制图片的区域,然后更新到脏矩阵区域就可以了,程序运行效果如下
FPS为55,到这里,你以为我们的帧率就优化完了吗?
no!no!no!
我们还可以继续优化
我们再来看看视频中小孩的运动,由于我们一帧图片为80*80,而小孩每帧横向移动的距离不会超过80,如下图所示
小孩由位置1(蓝色区域)移动到位置2(红色区域),我们设置了两块脏矩阵区域,而这两块区域是有重合的,也就是我们的程序刷了重复的内容,所以我们就可以把重复的区域优化掉
其实这个也简单,我们把两块区域合并成一块就可以了,如下图所示
我们把脏矩阵区域设置成绿色的区域就可以了,修改后的程序如下
void DirtyRegionUpdata(arm_2d_scene_t *ptScene,int32_t iResult){
user_scene_1_t *ptThis = (user_scene_1_t *)ptScene;
//定义两个区域
static arm_2d_region_t myRegion_old = {
.tLocation = {.iX = 0,.iY = 101,},
.tSize = {.iWidth = 80,.iHeight = 80,},
};
static arm_2d_region_t myRegion_new = {
.tLocation = {.iX = 0,.iY = 101,},
.tSize = {.iWidth = 80,.iHeight = 80,},
};
//保存需要擦除的区域
myRegion_old.tLocation.iX = myRegion_new.tLocation.iX;
//保存需要绘制的区域
myRegion_new.tLocation.iX = iResult;
//把两个区域合并成一个区域
if( myRegion_new.tLocation.iX > myRegion_old.tLocation.iX){
myRegion_old.tSize.iWidth = 80 + myRegion_new.tLocation.iX - myRegion_old.tLocation.iX;
this.use_as__arm_2d_scene_t.ptDirtyRegion[0].tRegion = myRegion_old;
}else{
myRegion_new.tSize.iWidth = 80 + myRegion_old.tLocation.iX - myRegion_new.tLocation.iX;
this.use_as__arm_2d_scene_t.ptDirtyRegion[0].tRegion = myRegion_new;
}
}
- 只需要在第17~24行,把两个区域合并成一个就可以了(* ̄︶ ̄)
修改后的程序运行效果如下
优化效果还可以,FPS达到了60(增加了5)。这个就是开始视频里显示的效果。怎么样,大家get到脏矩阵的用法了吧。这里只是起到一个抛砖引玉的效果,大家也可以用脏矩阵的思路优化帧率,比如只局部刷新gif变化的部分,这样也可以有效提升帧率哦。期待大家动手尝试脏矩阵,和我一起玩转Arm-2D,大家一起玩才更好玩。
原文:裸机思维
作者:GorgonMeducer 傻孩子
专栏推荐文章
- 【玩转Arm-2D】Arm-2D应用开发入门
- 【玩转Arm-2D】入门和移植从未如此简单
- 【玩转Arm-2D】十一、酷炫汽车仪表盘是怎么实现的
- 【例说Arm-2D界面设计】还在手算坐标?试试Layout Assistant吧!
- 【喂到嘴边了的模块】不服?跑个分看看!——Coremark篇
如果你喜欢我的思维,欢迎订阅裸机思维欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。