前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Arm-2D】不整活儿玩啥GUI?

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

作者头像
GorgonMeducer 傻孩子
发布2021-05-27 15:57:57
8660
发布2021-05-27 15:57:57
举报
文章被收录于专栏:裸机思维裸机思维
上回我们说到: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 中,具体内容如下:

代码语言:javascript
复制
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语言数组:

代码语言:javascript
复制
#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 来描述这一图片资源:

代码语言:javascript
复制
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 数据结构来表示的):

代码语言:javascript
复制
#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,这个意思就是:“我也不指定目标缓冲区具体哪里了,你就默认为是目标缓冲区的整个区域好了”。


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

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

代码语言:javascript
复制
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 就可以实现填充功能——非常简单。

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

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

代码语言:javascript
复制
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填充背景,然后加上白色蒙版——完美。

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


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

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

代码如下:

代码语言:javascript
复制
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 由尺寸和位置两部分信息组成,它们的定义如下:

代码语言:javascript
复制
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)——加入代码如下:

代码语言:javascript
复制
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);
}

嗯!嗯!

我知道!

是的……是的……

我知道很魔性……

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

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

代码语言:javascript
复制
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追更吧

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-05-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 裸机思维 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档