lvgl源码分析5(圆角矩形绘制算法)--Apple的学习笔记

一,前言

之前的4篇littlevgl源码分析已经完成一个按钮的显示的流程的分析。但是littlevgl要比我之前看的5K行的guilite做的控件要漂亮,比如这个按钮,他有各种样式,包括圆角矩形按钮,因为我看了下guilite源码好像是不支持绘制圆角矩形的。之前是分析框架,今天则分析细节的特色功能。

二,分析lvgl是如何绘制带圆角的矩形

首先我要提下mask的概念,在一副图片上,加上100%的mask遮盖后,你再给他绘制原色,那么遮盖的部分是不会改变颜色的。而把mask的概念理解为透明度叠加也可以。2个颜色若是透明的,那么就是2个颜色的rgb相加,否则就是后面绘制的颜色。比如先画蓝色,后画白色,不透明的话就是白色,若是透明的话,加入白色后的效果就是蓝色变淡蓝色。

好了,那么lvgl中若颜色完全覆盖,则用0xff来mask这个像素点,若完全透明就是0,在0~0xff间的就代表存在不同层度的透明度。而绘制圆角矩形的原理就是利用圆角mask。

之前学习游戏中的刚体碰撞了解过圆形和矩形碰撞,可以计算圆点到矩形边界的距离。大于r则无碰撞,否则有碰撞。那么要构造一个圆角矩阵,也是可以理解为每个圆角都是一个圆,见下图,那么要把下图的4个白色区间为透明,其它涂色,则变成了一个圆角矩形。


image.png

圆角矩形绘制的方法就是描点法。来看代码,要绘制矩形,就是一行行绘制。然后每行设置mask,当mask为0则为透明,就等于上图的白色区间。我现在将源码修改了下,屏蔽了绘制矩形轮廓等,仅绘制矩形背景,背景色为白色。此时周期刷新函数会调用draw_bg函数进行绘制。

LV_ATTRIBUTE_FAST_MEM static void draw_bg(const lv_area_t * coords, const lv_area_t * clip,
                                          const lv_draw_rect_dsc_t * dsc)
{
            。。。。。。
            // mask数据准备,为每一行准备mask
            for(h = draw_area.y1; h <= draw_area.y2; h++) {
            int32_t y = h + vdb->area.y1;

            opa2 = opa;

            /*In not corner areas apply the mask only if required*/
            if(y > coords_bg.y1 + rout + 1 &&
               y < coords_bg.y2 - rout - 1) {
                mask_res = LV_DRAW_MASK_RES_FULL_COVER;
                if(simple_mode == false) {
                    _lv_memset(mask_buf, opa, draw_area_w);
                    mask_res = lv_draw_mask_apply(mask_buf, vdb->area.x1 + draw_area.x1, vdb->area.y1 + h, draw_area_w);
                }
            }
            /*In corner areas apply the mask anyway*/
            // 若有圆角,色申请mask空间。mask空间为这一行的长度
            else {
                _lv_memset(mask_buf, opa, draw_area_w);
                mask_res = lv_draw_mask_apply(mask_buf, vdb->area.x1 + draw_area.x1, vdb->area.y1 + h, draw_area_w);
            }
            。。。。。。
            _lv_blend_fill(xxx)  //rgb数据混合及渲染
}

然后看看lv_draw_mask_apply算法的使用,它会调用res = dsc->cb(mask_buf, abs_x, abs_y, len, (void *)m->param);等于进入lv_draw_mask_radius函数。lv_draw_mask_radius里面做的事情就是先计算点的坐标,通过半径及这一个行y和y-1行的坐标计算x和x-1的坐标。如下

            y = radius - (h - abs_y) + 1;

            /* Get the x intersection points for `abs_y` and `abs_y-1`
             * Use the circle's equation x = sqrt(r^2 - y^2)
             * Try to use the values from the previous run*/
            if((y - 1) == p->y_prev) {
                x1.f = p->y_prev_x.f;
                x1.i = p->y_prev_x.i;
            }
            else {
                _lv_sqrt(r2 - ((y - 1) * (y - 1)), &x1, sqrt_mask);
            }

            _lv_sqrt(r2 - (y * y), &x0, sqrt_mask);
            p->y_prev = y;
            p->y_prev_x.f = x0.f;
            p->y_prev_x.i = x0.i;

然后为x和x-1的坐标添加mask渐变过滤。Kl和Kr就是左边和右边的像素位置,所以用mask_buf[kl]和mask_buf[kr]来表示,mask填充的值为m。

            /*Set all points which are crossed by the circle*/
            for(; i <= x1.i; i++) {
                /* These values are very close to each other. It's enough to approximate sqrt
                 * The non-approximated version is lv_sqrt(r2 - (i * i), &y_next, sqrt_mask); */
                sqrt_approx(&y_next, &y_prev, r2 - (i * i));

                m = (y_prev.f + y_next.f) >> 1;
                if(outer) m = 255 - m;
                if(kl >= 0 && kl < len) mask_buf[kl] = mask_mix(mask_buf[kl], m);
                if(kr >= 0 && kr < len) mask_buf[kr] = mask_mix(mask_buf[kr], m);
                kl--;
                kr++;
                y_prev.f = y_next.f;
            }

调试截图如下,x-1和x相差6。


image.png

然后除了2边对称需要设置mask,中间点也要设置mask。

            if(y_prev.f) {
                m = (y_prev.f * x1.f) >> 9;
                if(outer) m = 255 - m;
                if(kl >= 0 && kl < len) mask_buf[kl] = mask_mix(mask_buf[kl], m);
                if(kr >= 0 && kr < len) mask_buf[kr] = mask_mix(mask_buf[kr], m);
                kl--;
                kr++;
            }

最后再设置x和x-1没有交叉的部分,直接设置为0。

            if(outer == 0) {
                kl++;
                if(kl > len) {
                    return LV_DRAW_MASK_RES_TRANSP;
                }
                if(kl >= 0) _lv_memset_00(&mask_buf[0], kl);

                if(kr < 0) {
                    return LV_DRAW_MASK_RES_TRANSP;
                }
                if(kr < len) _lv_memset_00(&mask_buf[kr], len - kr);
            }

交叉的部分可以理解为是要描点绘制曲线的,所以用了渐变算法,否则的话会看到明显的锯齿。outer应该理解为是绘制里面或者绘制外部吧!见下图,对每一行矩形其实都是这样kl和kr的对称mask赋值。分为3个阶段赋值,一个是曲线段,一个是中间点,最后是曲线外。


image.png

我把在绘图板中画的一个圆,进行了5被扩大。让大家可以理解下描点与锯齿。下图第一行y和下一行y-1对应的的x和x-1也差距6。看坐标一个是30,一个是24。


image.png

我把lvgl绘制出的按钮背景的左上角矩形圆角截图,然后把截图放大8倍,也可以看到渐变的锯齿效果。其实就是这段代码的作用啦~标准尺寸的时候看上去就很平滑了。
image.png

三,总结

主要了解了下圆角矩形的绘制思路,简单来说就是用行扫描法加上形状mask,用渐变的mask值让描点的曲线效果更平滑。

你可能感兴趣的:(lvgl源码分析5(圆角矩形绘制算法)--Apple的学习笔记)