专栏首页裸机思维【Arm-2D】不整活儿玩啥GUI?

【Arm-2D】不整活儿玩啥GUI?

上回我们说到:Arm-2D是小资源单片机

之前,我们说过Arm-2D虽然本意是在底层默默的为各类商用和开源GUI软件协议栈提供加速服务,但考虑到在资源受限的深度嵌入式系统环境下,仍然有一大批贫下中农不辞辛劳的在 32~64K Flash、4~32K SRAM的单片机里“螺蛳壳里做道场”——“妄图染指”一般只有高端处理器才能触碰的“华丽”图形界面,Arm-2D也为这些享受不起哪怕是起码LVGL恩惠的资源难民,提供了一系列享受浪漫的机会

既然是“嵌入式工程师的浪漫”,哪有不整活儿的呢?

今天我们就在上一篇文章移植好的平台上为大家“正经”的介绍一下“某些”常用API的使用和技巧

【从“正事儿”开始】


说起Arm-2D的正事儿,那自然是贴图(Tile)了。其实在Arm-2D眼中,一切似乎都是贴图:

  • 保存在ROM里的图片是贴图;
  • RAM里的作图缓存(Frame buffer)也是贴图;
  • 甚至还可以从一个已有的贴图里派生出无数的子贴图(Child-Tile)。

为此,Arm-2D专门引入了一个数据结构 arm_2d_tile_t,它定义在 arm_2d_tile_types.h 中,具体内容如下:

typedef struct arm_2d_tile_t arm_2d_tile_t;
struct arm_2d_tile_t {
    implement_ex(struct {
        uint8_t    bIsRoot              : 1; 
        uint8_t    bHasEnforcedColour   : 1;
        uint8_t                         : 6;
        uint8_t                         : 8;
        uint8_t                         : 8;
        arm_2d_color_info_t    tColourInfo;
    }, tInfo);

    implement_ex(arm_2d_region_t, tRegion);

    union {
        /*! when bIsRoot is true, phwBuffer is available,
         *! otherwise ptParent is available
         */
        arm_2d_tile_t       *ptParent;
        uint16_t            *phwBuffer;
        uint32_t            *pwBuffer;
        uint8_t             *pchBuffer;
    };
};

如果看着比较晕,不要紧,其实它就只有三大部分而已:

  • 贴图的各类属性描述信息:tInfo
  • 贴图的尺寸和位置信息: tRegion
  • 贴图的指针或引用

为了方便大家理解,我们不妨举个例子。

假设我们想在屏幕上显示一个让人“印象深刻”的图片,比如这个:

由于傻孩子小学六年级暑假花300RMB学的PS已经忘得差不多了,所以只能祭出“简历上人均精通”的工具——PowerPoint——在空白页面上插入以上图片,截取“灵魂”,并缩放到普通LCD看起来尺寸适中的大小:

使用工具将其转化为C语言数组:

#include <stdint.h>

//! 没有这个,有些编译器会产生 warning,神烦!
__attribute__((aligned(2)))
extern const uint8_t c_bmpDodgy[];

__attribute__((aligned(2)))
const uint8_t c_bmpDoge[] = {
    ...
};

由于我的屏幕使用的是RGB565的颜色格式,因此保存在数组 c_bmpDoge[]里的像素是16位的,需要2字节对齐;同理,如果你使用的颜色格式是32位的,则需要使用__attribute__((aligned(4)))来确保一个不低于4字节的对齐。


关于转化工具,我推荐LVGL的在线工具:https://lvgl.io/tools/imageconverter 非常好用。


接下来,我们就可以使用 arm_2d_tile_t 来描述这一图片资源:

extern const uint8_t c_bmpDoge[];

const arm_2d_tile_t c_tileDoge = {
    .tRegion = {
        .tSize = {
            .iWidth = 112,  //!< 素材的宽度
            .iHeight = 108  //!< 素材d额高度
        },
    },
    .tInfo.bIsRoot = true,  //!< 说明这个贴图拥有资源
    .phwBuffer = (uint16_t *)c_bmpDoge,  //!< 指向资源的指针
};

这里,我们使用了C99标准引入的结构体初始化方式(可惜这一方式C++并不认可),并分别提供了以下信息:

  • 素材的长宽尺寸;
  • 当前的贴图是一个“根”贴图(bIsRoot = true),意思是说,这个贴图真正拥有自己的像素数组;与之相对,有些贴图是从别的贴图那里派生出来的,因此并不拥有自己的像素数组;
  • 由于当前贴图是一个根贴图,因此“指针引用区”的phwBuffer被赋值——指向了我们此前建立的常量数组 c_bmpDoge[]

值得说明的是,由于c_bmpDoge[]和新建立的贴图对象 c_tileDoge 都使用了 const 进行修饰,因而在编译时都算作 RO数据(Read Only Data),一般来说会被放置在Flash中,因而并不占用宝贵的RAM资源,当然也不可以在运行时刻对其内容进行修改——否则会立即导致 Busfault(或者Hardfault)。


对大部分Arm-2D的API来说,其操作的最基本单位是贴图(tile),现在我们的手中已经有了一个专门的贴图对象 c_tileDoge,剩下的事情就变得非常简单了。

比如,我们可以借助 arm_2d_rgb16_tile_copy() 函数将其拷贝到显示缓冲区中(显示缓冲区自然也是用 arm_2d_tile_t 数据结构来表示的):

#include "arm_2d.h"
#include "arm_2d_helper.h"
...
static arm_fsm_rt_t __pfb_draw_background_handler( void *pTarget,
                                          const arm_2d_tile_t *ptTile)
{
    ARM_2D_UNUSED(pTarget);
    
    arm_2d_rgb16_fill_colour(
        ptTile,      //!< 目标缓冲区
        NULL,        //!< 填充目标缓冲区的哪个区域
        GLCD_COLOR_WHITE); //!< 白色
    
    arm_2d_rgb16_tile_copy(
        &c_tileDoge,  //!< 我们的素材
        ptTile,       //!< 目标缓冲区
        NULL,         //!< 拷贝到目标缓冲区的那个区域
        ARM_2D_CP_MODE_COPY); //!< 就是单纯的拷贝,不做作

}
...
static ARM_NOINIT arm_2d_helper_pfb_t s_tExamplePFB;
...
void main(void)
{
    lcd_init();
    arm_2d_init();

    //! initialise FPB helper
    if (ARM_2D_HELPER_PFB_INIT( 
            &s_tExamplePFB,                 //!< FPB Helper object
            ...
            {
            ...
                .evtOnDrawing = {
                    //! callback for drawing GUI 
                    .fnHandler = &__pfb_draw_background_handler, 
                },
            }
        ) < 0) {
        //! error detected
        assert(false);
    }
    
    //! call partial framebuffer helper service to draw background
    while(arm_fsm_rt_cpl != arm_2d_helper_pfb_task(&s_tExamplePFB,NULL));
  
}

这里 __pfb_draw_background_handler() 就是我们的界面绘制函数,其中我们做了以下操作:

  • 通过 arm_2d_rgb16_fill_colour() 在整个目标缓冲区内填充白色;
  • 通过 arm_2d_rgb16_tile_copy() 将贴图拷贝到目标缓冲区中。

效果如下:


值得说明的是,两个函数都在涉及“目标缓冲区中具体哪个位置”的地方给了NULL,这个意思就是:“我也不指定目标缓冲区具体哪里了,你就默认为是目标缓冲区的整个区域好了”。


看起来效果不错——这小眼神怎么看都是那么的有“神韵”——然而,为了把节目效果拉满,我们完全可以这样整活儿:

怎么样,是不是血压一下就上来了?这里我们只是简单的使用了贴图填充功能(也就是大家熟悉的纹理填充),代码如下:

static arm_fsm_rt_t __pfb_draw_background_handler( void *pTarget,
                                          const arm_2d_tile_t *ptTile)
{
    ARM_2D_UNUSED(pTarget);
    
    arm_2d_rgb16_tile_copy(
        &c_tileDoge,  //!< 我们的素材
        ptTile,       //!< 目标缓冲区
        NULL,         //!< 拷贝到目标缓冲区的那个区域
        ARM_2D_CP_MODE_FILL); //!< 就是单纯的拷贝,不做作

}

容易注意到两点:

  1. 既然已经是全屏幕无缝填充了,自然不需要背景色了;
  2. ARM_2D_CP_MODE_COPY 改为 ARM_2D_CP_MODE_FILL 就可以实现填充功能——非常简单。

考虑到,如果你真的把这样的效果作为界面背景呈现给你的用户,我估计屏幕的损坏率可能会出奇的高:

为了安抚一下观众们的情绪,我们不妨用一个万能的背景美化效果——加半透明蒙版:

static arm_fsm_rt_t __pfb_draw_background_handler( void *pTarget,
                                          const arm_2d_tile_t *ptTile)
{
    ARM_2D_UNUSED(pTarget);
    
    arm_2d_rgb16_tile_copy(
        &c_tileDoge,  //!< 我们的素材
        ptTile,       //!< 目标缓冲区
        NULL,         //!< 拷贝到目标缓冲区的那个区域
        ARM_2D_CP_MODE_FILL); //!< 就是单纯的拷贝,不做作

    arm_2d_rgb565_fill_colour_with_alpha(
        ptTile,       //!< 目标缓冲区
        NULL,         //!< 填充目标缓冲区的哪个区域
        //!< 特别指明是 rgb565 的白色
        (arm_2d_color_rgb565_t){GLCD_COLOR_WHITE}, 
        200);         //!< 不透明度 (200/255) * 100%

}

实际效果如下:

是不是突然就觉得好像还挺不错的?

认真说起来,其实同样的方法也可以用来给界面打公司水印——即用公司logo填充背景,然后加上白色蒙版——完美。

【“整活儿要有技术含量”】


前面的整活儿贴图,虽然效果不错,但总觉得过于缺乏技术含量,比如最简单的问题,如果我想做个“水边的神烦狗”,应该如何实现呢?

说到水边,其关键就在于一种意境,具体说就是一种若有似无的倒影。要做到这一点其实并不难。首先,我们要将狗头居中,并留下倒影的位置:

代码如下:

static arm_fsm_rt_t __pfb_draw_background_handler( void *pTarget,
                                          const arm_2d_tile_t *ptTile)
{
    arm_2d_rgb16_fill_colour(ptTile, NULL, GLCD_COLOR_WHITE);
    
    arm_2d_region_t tDogRegion = {
        .tLocation = {
            .iX = (APP_SCREEN_WIDTH - c_tileDoge.tRegion.tSize.iWidth) >> 1,
            .iY = (APP_SCREEN_HEIGHT - c_tileDoge.tRegion.tSize.iHeight * 2) >> 1,
        },
        .tSize = c_tileDoge.tRegion.tSize,
    };
    
    arm_2d_rgb16_tile_copy(
        &c_tileDoge,      //!< 我们的素材
        ptTile,           //!< 目标缓冲区
        &tDogRegion,      //!< 拷贝到目标缓冲区的那个区域
        ARM_2D_CP_MODE_COPY);
}

与此前不同的是,这次我们通过必要的计算,1)将狗头居中;2)并预留出倒影的位置。计算的结果保存在 arm_2d_region_t 类型的结构 tDogRegion中。很容易注意到,类型 arm_2d_region_t 由尺寸和位置两部分信息组成,它们的定义如下:

typedef struct arm_2d_location_t {
    int16_t iX;
    int16_t iY;
} arm_2d_location_t;

typedef struct arm_2d_point_float_t {
    float fX;
    float fY;
} arm_2d_point_float_t;

typedef struct arm_2d_size_t {
    int16_t iWidth;
    int16_t iHeight;
} arm_2d_size_t;

typedef struct arm_2d_region_t {
    implement_ex(arm_2d_location_t, tLocation);
    implement_ex(arm_2d_size_t, tSize);
} arm_2d_region_t;

这里OOPC辅助宏 implement_ex() 的意思是:arm_2d_region_t 继承了 arm_2d_location_t 和 arm_2d_size_t,并分配给与它们对应的名字 tLocation和tSize。


接下来是重头戏,翻转狗头——这次必须要请出 arm_2d_rgb16_tile_copy()的特殊模式:Y轴镜像(ARM_2D_CP_MODE_Y_MIRROR)——加入代码如下:

static arm_fsm_rt_t __pfb_draw_background_handler( void *pTarget,
                                          const arm_2d_tile_t *ptTile)
{
    //! 填充白色背景
    arm_2d_rgb16_fill_colour(ptTile, NULL, GLCD_COLOR_WHITE);
    //! 计算狗头的位置(居中,并预留倒影)
    arm_2d_region_t tDogRegion = {
        .tLocation = {
            .iX = (APP_SCREEN_WIDTH - c_tileDoge.tRegion.tSize.iWidth) >> 1,
            .iY = (APP_SCREEN_HEIGHT - c_tileDoge.tRegion.tSize.iHeight * 2) >> 1,
        },
        .tSize = c_tileDoge.tRegion.tSize,
    };
    //! 画狗头
    arm_2d_rgb16_tile_copy(
        &c_tileDoge,      //!< 我们的素材
        ptTile,           //!< 目标缓冲区
        &tDogRegion,      //!< 拷贝到目标缓冲区的那个区域
        ARM_2D_CP_MODE_COPY);
        
    //! 更新要拷贝狗头的位置
    tDogRegion.tLocation.iY += c_tileDoge.tRegion.tSize.iHeight;
    
    //! 以镜像的方式拷贝狗头
    arm_2d_rgb16_tile_copy(
        &c_tileDoge,      //!< 我们的素材
        ptTile,           //!< 目标缓冲区
        &tDogRegion,      //!< 拷贝到倒影的位置
        ARM_2D_CP_MODE_Y_MIRROR);
}

嗯!嗯!

我知道!

是的……是的……

我知道很魔性……

“我们受过专业的训练,无论到好笑,我们都不会笑……除非忍不住……”

接下来,我们继续严肃认真地解决“倒影不够有意境的问题”——加上半透明蒙版:

static arm_fsm_rt_t __pfb_draw_background_handler( void *pTarget,
                                          const arm_2d_tile_t *ptTile)
{
    //! 填充白色背景
    arm_2d_rgb16_fill_colour(ptTile, NULL, GLCD_COLOR_WHITE);
    //! 计算狗头的位置(居中,并预留倒影)
    arm_2d_region_t tDogRegion = {
        .tLocation = {
            .iX = (APP_SCREEN_WIDTH - c_tileDoge.tRegion.tSize.iWidth) >> 1,
            .iY = (APP_SCREEN_HEIGHT - c_tileDoge.tRegion.tSize.iHeight * 2) >> 1,
        },
        .tSize = c_tileDoge.tRegion.tSize,
    };
    //! 画狗头
    arm_2d_rgb16_tile_copy(
        &c_tileDoge,      //!< 我们的素材
        ptTile,           //!< 目标缓冲区
        &tDogRegion,      //!< 拷贝到目标缓冲区的那个区域
        ARM_2D_CP_MODE_COPY);
        
    //! 更新要拷贝狗头的位置
    tDogRegion.tLocation.iY += c_tileDoge.tRegion.tSize.iHeight;
    
    //! 以镜像的方式拷贝狗头
    arm_2d_rgb16_tile_copy(
        &c_tileDoge,      //!< 我们的素材
        ptTile,           //!< 目标缓冲区
        &tDogRegion,      //!< 拷贝到倒影的位置
        ARM_2D_CP_MODE_Y_MIRROR);
        
    //! 给倒影加半透明蒙版
    arm_2d_rgb565_fill_colour_with_alpha(
        ptTile,       //!< 目标缓冲区
        &tDogRegion,      //!< 拷贝到倒影的位置
        //! 特别指明是 rgb565 的白色
        (arm_2d_color_rgb565_t){GLCD_COLOR_WHITE}, 
        200);         //!< 不透明度 (200/255) * 100%
}

最终效果如下:

【It's Only Begin!】


这一篇,我们通过“怒刷狗头”的方式介绍了Arm-2D一些常用API的使用,并给出了这些方法在日常应用中如何“狗头保命”的建议——比如如何实现水影背景。

然而,整活儿的步伐怎么能说停就停呢?下一篇,我们将详细介绍一种实现“一狗映月进度条”的方式:

感兴趣的小伙伴、还没搭建平台的小伙伴,赶快动起手来,一起来Arm-2D追更吧

本文分享自微信公众号 - 裸机思维(bare-metal),作者:GorgonMeducer 傻孩子

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-05-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【例说Arm-2D界面设计】从不规则图标的显示说起

    Arm-2D是Arm公司为Cortex-M处理器平台量身打造的一款2D图形处理方案。针对已有的经典Cortex-M内核,诸如Cortex-M0/M0+/M3/M...

    GorgonMeducer 傻孩子
  • 活久见!Arm居然为Cortex-M发布了专属显卡驱动

    (2D图形处理的技术其实早在红白机时代就成熟了,6502能做到的事情,Cortex-M自然也不在话下)

    GorgonMeducer 傻孩子
  • 为什么说Arm-2D是小资源单片机的GUI人权卡!

    实际上,不光很多明眼人看得出来,Arm-2D自己也在仓库的README里写的明明白白:

    GorgonMeducer 傻孩子
  • 懒人玩Arm-2D究竟有几种姿势

    如果你是第一次接触Arm-2D,可以单击这篇文章《为什么说Arm-2D是小资源单片机的GUI人权卡!》来了解大概、或是观看公开课(https://aijishu...

    GorgonMeducer 傻孩子
  • 【教程更新】Arm-2D的公开课你错过了么?

    最近受到极客社区的邀请,我有幸为大家献上了一期名为“Arm-2D初探——填补空白还是屋上架屋”的公开课。原本计划是1个小时,无奈说的太嗨了,一不小心就讲了3个小...

    GorgonMeducer 傻孩子
  • 【教程更新】一网打尽Arm-2D的资料和傻瓜部署教程

    随着时间的推移,从4月份更新第一个版本以来,Arm-2D也逐渐走入成熟期,截止到我编写这篇文稿的时间,其版本已经来到了0.9.8,而Github开发分支上的版本...

    GorgonMeducer 傻孩子
  • 3D拓扑自动布局之Web Workers篇

    2D拓扑的应用在电信网管和电力SCADA领域早已习以为常了,随着OpenGL特别是WebGL技术的普及,3D方式的数据可视化也慢慢从佛殿神堂步入了寻常百姓家,似...

    HT for Web
  • 原 3D拓扑自动布局之Web Worker

    HT_hightopo
  • 【例说Arm-2D界面设计】任意尺寸的圆角矩形(上)

    在上篇文章《【例说Arm-2D界面设计】做剪影风也太简单了8!》中我们介绍了使用透明蒙版的方法来实现“性冷淡风”图标显示的方法。其中,我们提到了使用透明蒙版的三...

    GorgonMeducer 傻孩子
  • 2019,Go GUI项目爆发的一年?

    目前Go语言主要活跃在区块链、云计算、命令行工具和后端服务等领域。这些领域基本上和GUI关系不大。近来出现了很多跨平台的Go GUI项目。虽说用井喷之势形容有些...

    刘老貘
  • 【Rust 日报】2021-08-29 Embedded Rust 第一步:选择一块板子

    内容整理自 robyoung (Rob Young) 的文章:First steps with Embedded Rust: Selecting a board

    MikeLoveRust
  • 源创库 | Python GUI初步认识与C/S端发展之我见

    今天翻了翻《Python编程(第四版)》,这本书其实买来还是有点久的,毕竟现在Python 3.9都出来了,这书是Python 3.1/3.2 Beta的示例。...

    ZNing
  • 【例说Arm-2D界面设计】做剪影风也太简单了8!

    在上一篇文章《【例说Arm-2D界面设计】从不规则图标的显示说起》的最后,我们展示了如何使用Arm-2D在RGB565环境下显示带有Alpha通道的图片的(比如...

    GorgonMeducer 傻孩子
  • 无限想象空间,用Python玩转3D人体姿态估计

    通过摄像头捕捉追踪人体的动作变化,根据肢体动作或变化角度判断人体动作行为,可用于无人车、机器人、视频监控等行为分析需求场景。

    AI科技大本营
  • 客户端软件GUI开发技术漫谈:原生与跨平台解决方案分析

    如果你想深入的美化UI,需要耗费很大的力气,对于目前主流的CSS样式表来讲,美化Winform的界面以及自定义控件是需要耗费更多的时间的。

    周陆军
  • 暖暖系列第四弹《闪耀暖暖》上线服务器即崩盘!台服开放VR功能超吸睛~

    说起暖暖系列,其实小编也曾入过坑,包括小编周围的许多朋友也都非常喜欢。从《暖暖环游世界》、《奇迹暖暖》到《暖暖的换装物语》,各色不一样的服装,真的满满都是少女心...

    VRPinea
  • 一种为 Linux ARM 设备构建跨平台 UI 的新方法

    为应用程序创建良好的用户体验(UX)是一项艰巨的任务,尤其是在开发嵌入式应用程序时。今天,有两种图形用户界面(GUI)工具通常用于开发嵌入式软件:它们要么涉及复...

    用户1880875
  • 一种为 Linux ARM 设备构建跨平台 UI 的新方法

    为应用程序创建良好的用户体验(UX)是一项艰巨的任务,尤其是在开发嵌入式应用程序时。今天,有两种图形用户界面(GUI)工具通常用于开发嵌入式软件:它们要么涉及复...

    用户8639654
  • 精灵4RTK 一览无余 不差毫厘(简单机内航线规划试用)

    有人可能会说,你又看说明书,你倒是整活儿啊!整啥整,几万块的东西你啥也不知道能玩明白?尤其还是航测这种事情。

    云深无际

扫码关注云+社区

领取腾讯云代金券