13

傻孩子(GorgonMeducer) · 2022年10月08日 · 北京市

【玩转Arm-2D】七、制作酷炫环形进度条

image.png

之前我们讲过一个矩形进度条的制作,忘记的可以看下面这篇文章

用Arm-2D制作炫酷的进度条

今天我们再讲一下怎么制作炫酷的圆环进度条,效果如下所示:

image.png

制作前的准备知识


一、子Tile

与矩形进度条一样,我们需要用到子Tile,之前我们讲过怎么用_arm_2d_tile_generate_child_函数生成一个子Tile,如下


arm_2d_tile_t c_tileChild;
arm_2d_tile_generate_child(
  &Parenttile,//父tile
  &rotate_region,//子tile的region
  &ChildTile,//子tile
  false);//默认为false

今天我们补充一个用宏函数_impl_child_tile_来初始化一个子tile,如下:

static
const arm_2d_tile_t c_tileChild = 
    impl_child_tile(
        ParentTile,//父tile
        0,//子tile的region.tLocation.iX
        0,//子tile的region.tLocation.iY
        50,//子tile的region.tSize.iWidth
        50,//子tile的region.tSize.iHeight
    );

这个是不是很简单,只需要知道父Tile和子Tile的区域就可以了(* ̄︶ ̄)

二、旋转函数

之前我们也讲过Arm-2D的旋转功能,如下

【玩转Arm-2D】四、旋转与抗锯齿(美颜功能)

使用了arm_2dp_tile_rotationarm_2dp_tile_rotation_with_opaicty两个函数

arm_2dp_tile_rotation(  (arm_2d_op_trans_t *)&(ptItem->tOP),
  ptItem->ptTile,         //!< source tile
  ptTile,                 //!< target tile
  ptItem->ptRegion,       //!< target region
  ptItem->tCentre,        //!< center point
  ptItem->fAngle,         //!< rotation angle
  GLCD_COLOR_BLACK,       //!< masking colour
  ptItem->ptTargetCentre);//!< Target center point
  
arm_2dp_tile_rotation_with_opacity(   
  (arm_2d_op_trans_opa_t *)&(ptItem->tOP),
  ptItem->ptTile,         //!< source tile
  ptTile,                 //!< target tile
  ptItem->ptRegion,       //!< target region
  ptItem->tCentre,        //!< center point
  ptItem->fAngle,         //!< rotation angle
  GLCD_COLOR_BLACK,       //!< masking colour
  ptItem->chOpacity,      //!< Opacity
  ptItem->ptTargetCentre);//!< Target center point

今天我们在补充两个旋转函数arm_2dp_tile_rotation_with_src_maskarm_2dp_tile_rotation_with_src_mask_and_opacity,如下:

arm_2dp_tile_rotation_with_src_mask(  
  (arm_2d_op_trans_msk_t *)&(ptItem->tOP),                    //!< control block
  ptItem->ptTile,          //!< source tile
  ptItem->ptMask,          //!< source mask
  ptTile,                  //!< target tile
  ptItem->ptRegion,        //!< target region
  ptItem->tCentre,         //!< pivot on source
  ptItem->fAngle ,         //!< rotation angle 
  ptItem->ptTargetCentre   //!< Target center point
);
        
arm_2dp_tile_rotation_with_src_mask_and_opacity(   
  &(ptItem->tOP),         //!< control block
  ptItem->ptTile,         //!< source tile
  ptItem->ptMask,         //!< source mask
  ptTile,                 //!< target tile
  ptItem->ptRegion,       //!< target region
  ptItem->tCentre,        //!< pivot on source
  ptItem->fAngle,         //!< rotation angle 
  ptItem->chOpacity,      //!< opacity
  ptItem->ptTargetCentre  //!< Target center point
); 
  • 从名字我们就知道比上面两个函数多了一个src_mask,也就是source mask,那这个source mask有什么用呢?              有了它,可以使我们的抗锯齿效果变得更好,是不是很心动,赶快动手试试吧。

三、切图

这个功能大家都不陌生吧,Arm-2D自带的。
image.png
上面的旋转函数都有一个target region,只要我们旋转超出这个区域,Arm-2D就会给我们把超出去的部分裁剪掉,如下图所示:

image.png

像这种tile copy和fill_colour函数,都有一个target region,如下


arm_2d_fill_colour_with_mask_and_opacity(
   ptTarget,  /*   target tile address*/
   &pTregion, /*   target region address*/
   &ChildTile,/*   alpha tile address */
   (__arm_2d_color_t){Colour},/*   colour */ 
   OPACITY); 

只要超出_target region,_Arm-2D都会帮我们剪切掉,也就是说有target region的地方就有切图功能,如下图所示:

image.png

其实子Tile的功能就相当于是对父Tile的剪切了。

看到了吧,Arm-2D无处不在的切图功能是不是被你们忽略了(* ̄︶ ̄)

四、圆环素材的制作

其实这个也很简单,就是需要一个圆环的png图片,如下

image.png

不过这里需要强调一点,就是我们的圆环素材的像素边长要为奇数,比如像素为55*55的素材要比54*54的效果好一些,因为奇数个像素圆心为整数(方便旋转),下面我以世界上最小的像素圆环3*3举例来计算圆心,如下图所示

image.png
圆环旋转的原理


制作旋转的圆环进度条,其原理也很简单,就是用到了旋转和切图这两个功能。首先我们把圆环分成左右两个部分,如下图:
image.png
这样用Arm-2D子tile的切图功能很容易就制作出左右两个半圆环了,程序如下


// 图片素材71*71像素的圆环
extern const arm_2d_tile_t c_tilegreen71RGB565;

// 圆环左半圈
const arm_2d_tile_t   c_tileBigGreenCircleLeftHalf = 
  impl_child_tile(
    c_tilegreen71RGB565,  /*parent tile*/
    0,              /*iX*/
    0,              /*iY*/
    36,              /*iWidth*/
    71              /*iHeight*/
  );
// 圆环右半圈 
const arm_2d_tile_t   c_tileBigGreenCircleRightHalf = 
  impl_child_tile(
    c_tilegreen71RGB565,  /*parent tile*/
    35,              /*iX*/
    0,              /*iY*/
    36,              /*iWidth*/
    71              /*iHeight*/
  );

当我们的旋转角度小于180度的时候,只需要旋转右半圆就可以了,把旋转区域设置成右半圆的矩形区域,旋转超出区域的圆环部分Arm-2D就会帮我们裁剪掉(这样旋转右半圈就不会影响到左半圈,同样的旋转左半圈也不会影响到右半圈),如下图所示,圆环旋转45度角:

image.png

程序也很简单,我们使用抗锯齿效果更好arm_2dp_tile_rotation_with_src_mask_and_opacity函数来进行旋转,如下所示:

if(ptItem->fAngle < ARM_2D_ANGLE(180)){
  // 旋转右半圈
  arm_2dp_tile_rotation_with_src_mask_and_opacity(
    &(ptItem->tOP),          //!< control block
    ptItem->ptTile,          //!< source tile
    ptItem->ptMask,          //!< source mask
    ptTile,                  //!< target tile
    ptItem->ptRegion,       //!< target region
    ptItem->tCentre,        //!< pivot on source
    ptItem->fAngle,         //!< rotation angle 
    ptItem->chOpacity ,     //!< opacity
    ptItem->ptTargetCentre  //!< Target center point
  );
  // copy左半圈的图片
  arm_2d_rgb16_tile_copy_with_colour_keying( 
    &c_tileBigGreenCircleLeftHalf , 
    ptTile, 
    &ptRegion, 
    GLCD_COLOR_BLACK,
    ARM_2D_CP_MODE_COPY);
}
  • 这里需要注意的是角度180需要转换成弧度,用ARM_2D_ANGLE宏就可以。

同样的道理,当旋转角度大于180度时,右半圈的圆环已经旋转到左半圈,此时把左半圈的贴图去掉就可以了(注意旋转区域设置成左半圈),这样一个旋转圆环的原理就讲完了,是不是很简单(* ̄︶ ̄)

炫酷圆环进度条的制作

制作之前我们要再讲一个知识点,用来制作颜色可变的圆环,这样就可以动态调整颜色了,是不是很酷。

下面我们就讲一下今天的主角arm_2dp_gray8_tile_rotation这个函数,他是对8位色图进行旋转的,我们待会用这个函数旋转我们的mask Tile,然后把旋转后的Tile保存到RAM中,这样我们就可以给旋转后的mask Tile填充颜色,从而实现改变圆环的颜色。先看下他的原型长什么样,如下

arm_2dp_gray8_tile_rotation(
  arm_2d_op_trans_t *ptOP,    //!< control block
  arm_2d_tile_t *ptSource,    //!< source tile
  arm_2d_tile_t *ptTarget,    //!< target tile 
  arm_2d_region_t *ptRegion,  //!< target region
  arm_2d_location_t tCentre,  //!< pivot on source
  float fAngle,               //!< rotation angle 
  uint_fast8_t chMskColour);  //!< masking colour 
  • 第1个参数ptOP为Arm-2D旋转需要的控制块
  • 第2个参数ptSource为源Tile(即要旋转的图片)
  • 第3个参数ptTarget为目标tile(即吧旋转后的图片放到哪)
  • 第4个参数ptRegion为目标区域
  • 第5个参数tCentre为source tile的圆心
  • 第6个参数fAngle为旋转角度
  • 第7个参数chMskColour为masking colour (即抠图功能)

上面我们讲了怎么用左右两个半圆环制作圆环进度条,那能不能用1/4圆环进行制作呢?

答案是肯定的,接下来我们就用旋转1/4圆环来制作圆环进度条,原理也是很简单,如下图所示:

image.png

这个旋转区域是0~90度的,其他区域也是同样的原理(也就是说我们只要旋转1/4圆环,其他区域贴图就可以)。好,现在就看看代码怎么实现。

首先,我们制作一个圆环素材的mask,素材大小为71*71,如下

extern const arm_2d_tile_t c_tilecolor71Mask;
const uint8_t c_bmpcolor71GRAY8[71*71] = {
/* -0- */
0x00, 0x00, 0x00, 0x00, 0xa4, 0xac, 0xac,
...
}
__attribute__((section("arm2d.tile.c_tilecolor71Mask")))
const arm_2d_tile_t c_tilecolor71Mask = {
    .tRegion = {
        .tSize = {
            .iWidth = 71,
            .iHeight = 71,
        },
    },
    .tInfo = {
        .bIsRoot = true,
        .bHasEnforcedColour = true,
        .tColourInfo = {
            .chScheme = ARM_2D_COLOUR_8BIT,
        },
    },
    .pchBuffer = (uint8_t *)c_bmpcolor71GRAY8,
};

接着用宏函数impl_fb定义一块RAM,用来存放旋转后的mask

impl_fb(s_tileSpinWheelMask2, 
        71, 
        71, 
        arm_2d_color_gray8_t,
        
        .tInfo.tColourInfo.chScheme = ARM_2D_COLOUR_8BIT,
        .tInfo.bHasEnforcedColour = true,
        );

用mask填充一个背景圆环


arm_2d_fill_colour_with_mask_and_opacity(  
      ptTarget,
      &__centre_region,
      &c_tile_myCIRCLE_MASK,
      BG_color,//(__arm_2d_color_t){GLCD_COLOR_LIGHT_GREY},
      OPACITY);  

然后用子Tile取圆环的右上角(1/4圆环),准备旋转


arm_2d_tile_t c_tileGreenCircleQuaterMask;
rotate_region.tLocation.iX = CIRCLE_RADIUS-1;
rotate_region.tLocation.iY = 0;
rotate_region.tSize.iWidth = CIRCLE_RADIUS;
rotate_region.tSize.iHeight = CIRCLE_RADIUS;
arm_2d_tile_generate_child(
  &c_tile_myCIRCLE_MASK,//父tile
  &rotate_region,//子tile的region
  &c_tileGreenCircleQuaterMask,//子tile
  false);//默认为false

调用arm_2dp_gray8_tile_rotation函数把圆环右上角的mask旋转角度存放到RAM中,如下


memset(s_tileSpinWheelMask2.pchBuffer, 0, sizeof(s_tileSpinWheelMask2Buffer));
arm_2d_tile_t tileMaskFB = s_tileSpinWheelMask2;
static arm_2d_op_rotate_t s_tMaskRotateCB = {0};

const arm_2d_location_t c_tCentre = {
    .iX = 0,
    .iY = CIRCLE_RADIUS-1,
};
  
tileMaskFB.tRegion.tSize.iWidth = CIRCLE_DIAMETER;
tileMaskFB.tRegion.tSize.iHeight = CIRCLE_DIAMETER;

if (bIsNewFrame) {
    s_fAngle += ARM_2D_ANGLE(6.0f);
    s_fAngle = fmodf(s_fAngle,ARM_2D_ANGLE(360));
}

arm_2dp_gray8_tile_rotation(
    &s_tMaskRotateCB,
    &c_tileGreenCircleQuaterMask,
    &tileMaskFB,
    NULL,
    c_tCentre,
    s_fAngle,
    0x00);

然后把旋转后的mask用子Tile剪切后填充颜色

if(s_fAngle < ARM_2D_ANGLE(90)){
      rotate_region.tLocation.iX = CIRCLE_RADIUS-1;
      rotate_region.tLocation.iY = 0;   
}
else if(s_fAngle < ARM_2D_ANGLE(180))  {
...
}else if(s_fAngle < ARM_2D_ANGLE(270))  {
  ...
}else{
  ...
}
arm_2d_tile_generate_child( &tileMaskFB,
                            &rotate_region,
                            &ChildTile,
                            false);
arm_2d_fill_colour_with_mask_and_opacity( ptTarget,
    &rotate_region,
    &ChildTile,
    (__arm_2d_color_t){Colour},
    OPACITY);
  • 注意:此时子Tile的区域要根据旋转角度(90、180、270、360四个区域)进行调整。
  • 然后在用arm_2d_fill_colour_with_mask_and_opacity函数填充颜色,此时我们把Colour设置成变量就可以修改颜色了(* ̄︶ ̄)

剩下的区域分别用子Tile填充就可以了,到这里可以改变颜色(其实就是改变填充颜色)的圆环进度条就做好了。

当然,为了使圆环更好看,我们也可以用一张彩色的图片作为背景圆环,如下图旋转320度的效果
image.png
小结

image.png

从表中我们可以看到,旋转mask的圆环虽然可以改变圆环颜色,但是也需要一个RAM空间,大家可以根据需求进行取舍。

备注:这个1/4旋转圆环程序参考了官方的代码,感兴趣的也可以去看看,代码地址如下:

| https://github.com/ARM-softwa... |

从上面所讲的圆环进度条来看,都需要使用整个圆环的素材来制作,但能不能只使用半个圆环或者1/4圆环素材进行制作呢?

当然也是可以的,此时需要使用Arm-2D为我们提供的镜像拷贝函数,如下


enum __arm_2d_copy_mode_t {
    ARM_2D_CP_MODE_COPY ,
    ARM_2D_CP_MODE_FILL ,
    ARM_2D_CP_MODE_Y_MIRROR ,
    ARM_2D_CP_MODE_X_MIRROR ,
    ARM_2D_CP_MODE_XY_MIRROR ,
};
arm_2d_rgb16_tile_copy_with_colour_keying( 
  c_tile_QuarterCIRCLE, //1/4圆环
  ptTarget, 
  &rotate_region, 
  GLCD_COLOR_BLACK,
  ARM_2D_CP_MODE_X_MIRROR);//X镜像
  • 此函数的最后一个参数就可以设置镜像拷贝,可以设置的枚举值我也贴到了函数上面。

下面我就用1/2圆环素材简单实现了一个旋转圆环,程序贴到下面供大家参考:

void spinning_wheel_half_show(
    const arm_2d_tile_t   *ptTarget,           //!< target tile 
    const arm_2d_tile_t   *c_tile_myHalfCircle,//!< source tile
    const arm_2d_region_t alignment_region,    //!< target region
    float                 fAngle,              //!< rotation angle                     
    const char            CircleRadius,        //!< circle radius
    bool                  bIsNewFrame)
{
  static arm_2d_op_trans_t   s_tRotateCB = {0};
  static float s_fAngle = 0.0f;
  const arm_2d_location_t c_tCentre = {
            .iX = 0,
            .iY = CircleRadius-1,
        };
  const arm_2d_location_t c_tTargetCentre = {
            .iX = alignment_region.tLocation.iX + CircleRadius -1,
            .iY = alignment_region.tLocation.iY +CircleRadius-1,
        };
  arm_2d_region_t rotate_region = alignment_region;
      
  if (bIsNewFrame) {           
    s_fAngle = ARM_2D_ANGLE(fAngle);
  }
  rotate_region.tSize.iWidth = CircleRadius;
  if(s_fAngle < ARM_2D_ANGLE(180)){
    //小于180,用X镜像贴左边的半圆环
    arm_2d_rgb16_tile_copy_with_colour_masking( 
      c_tile_myHalfCircle, 
      ptTarget, 
      &rotate_region, 
      GLCD_COLOR_BLACK,
      ARM_2D_CP_MODE_X_MIRROR);
    //旋转区域设置成右半圆  
    rotate_region.tLocation.iX += CircleRadius-1;    
  }
  
  //旋转半圆环  
  arm_2dp_tile_rotation(  &s_tRotateCB,
          c_tile_myHalfCircle, //!< source tile
          ptTarget,           //!< target tile
          &rotate_region,    //!< target region
          c_tCentre,        //!< center point
          s_fAngle,         //!< rotation angle
          GLCD_COLOR_BLACK, //!< masking colour
          &c_tTargetCentre);//!< Target center point  
      
}

有了这个圆环旋转函数,我们使用起来也很简单,如下


/*
__arm_2d_align_top_left(__region, __width, __height) {
    code body that can use __top_left_region
}
*/
arm_2d_align_top_left(ptTile->tRegion, 69, 69) {
   spinning_wheel_half_show(  
       ptTile,
       &c_tilehalfCircleRGB565, 
       __top_left_region,
       s_fAngle,
       35, 
       bIsNewFrame);//           
}
  • 注意:我们使用了Arm-2D左上角对齐的宏arm_2d_align_top_left,在给target region传参数的时候就可以使用___top_left_region了。

同样的,在使用1/4圆环制作时,最好用圆环的右上角,这样方便旋转,如下图所示:
image.png
我们只要旋转1/4圆环,其他用镜像拷贝就可以了,是不是很简单。

对了,镜像拷贝还有一个函数,需要讲一下,即arm_2d_rgb565_tile_copy_with_src_mask,如下所示


arm_2d_rgb565_tile_copy_with_src_mask( 
  SRC_ADDR,     /*source tile address */ 
  SRC_MSK_ADDR, /*source mask address */ 
  DES_ADDR,     /*target tile address */ 
  REGION,       /*region address */      
  MODE)         /*copy mode */ 
  • 这个函数比上面那个copy_with_colour_masking函数要多开销一个mask的FLASH,但是适合获得通用性,切换背景颗粒感也不会很强(即抗锯齿效果更好)。
  • 最后一个参数_MODE_就是设置镜像模式的。

说在后面的话


我们一直在使用mask,但是这个图片的mask怎么制作呢?其实Arm-2D也给我们提供了Python工具【img2c.py】,此文件在\RTE\Acceleration目录下,其使用方法也在【README.md】文件中有介绍。

众所周知,Arm-2D的旋转是可以设置抗锯齿效果的,其设置方法也很简单,如下图所示:

image.png

有了mask加抗锯齿功能,在使用arm_2dp_tile_rotation_with_src_mask_and_opacity旋转函数和arm_2d_rgb565_tile_copy_with_src_mask镜像拷贝函数,制作通用性强,抗锯齿效果好的圆环进度条就非常方便了,大家赶快动手试试吧。

原文:嵌入式小书虫
作者:FledgingSu 支离苏

专栏推荐文章

如果你喜欢我的思维,欢迎订阅裸机思维欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。
推荐阅读
关注数
1466
内容数
108
探讨嵌入式系统开发的相关思维、方法、技巧。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息