圆角边框的绘制方法

2019年07月14日  ·  1726 字  ·  阅读

需求

LCUI 的圆角边框渲染一直没有实现,因为在这之前觉得这个功能需要花费大量的连续时间来开发和调试,长时间集中精力搞一个功能有点困难,所以就搁置了,现在准备完善它,毕竟一个 GUI 开发库连圆角边框这种常见效果都不能实现那确实有点水。

分析

首先,以浏览器上的各种圆角效果作为参考:

Border

从上图可以看出以下规律:

  1. 外边界呈圆形,当两条相交的边框宽度不同时,内边界呈椭圆形。
  2. 两条边框在一条分割线上贴合,这条线经过内容框(content box)顶点和边界框(border box)顶点。

由规律可得出以下绘制步骤:

  1. 根据当前 y 轴坐标计算出外边界、分割线、内边界的 x 轴坐标。
  2. 填充圆外的像素为透明色。
  3. 分割线两侧分别填充对应边框色,直到内边界为止。

怎么计算这些坐标?套用数学题的表达方式,需要解决的问题有以下三个:

  1. 已知圆心坐标为 (0,0),半径为 r,圆上点 P 的 y 轴坐标为 y,求点 P 的 x 轴坐标

     ∵ x² + y² = r²
     ∴ x = sqrt(r² - y²)
    
  2. 已知两点 A(x1, y1)、B(x2, y2),点 C 的 y 轴坐标为 y3,且位于直线 AB 上,求点 C 的 x 轴坐标

     ∵ (x2 - x1) / (y2 - y1) == (x2 - x3) / (y2 - y3)
     ∴ x3 = (x2 - x1) / (y2 - y1) * (y2 - y3)
    
  3. 已知椭圆长轴长度为 2a,短轴长度为 2b, 圆上点 P 的 y 轴坐标为 y,求点 P 的 x 轴坐标

     ∵ x² / a² + y² / b² = 1
     ∴ x = sqrt((1 - y² / b²) * a²)
    

绘制

如果直接套用平面坐标系来转换像素坐标的话,实际绘制出来的圆并不圆,而且仔细看会发现右边界被裁剪掉了 1px,显然是坐标计算方式有问题。

图形绘制问题只凭脑子想效率会很低,可以使用画图工具画个示例图来辅助思考:

Border pixels

上图是 10x10 像素点阵,可以看出红色部分就是圆占用的像素点,填充像素点时使用的是屏幕坐标系,那么,需要解决的问题有以下四个:

  1. 如何计算实际绘制的圆的半径?

     radius -= 0.5
    
  2. 已知平面直角坐标系的原点 O 在屏幕坐标系上的坐标 (x, y),如何转换屏幕坐标系中的坐标?

     x = x - O.x
     y = O.y - y
    
  3. 在屏幕坐标系中,已知圆心 O 的坐标 (4.5, 4.5),半径 r 为 4.5,圆上点的 y 轴坐标为 4.5,求该点的 x 轴坐标

     ∵ (x - O.x)² + (O.y - y)² = r²
     ∴ x = O.x + sqrt(r² - (O.y - y)²)
     ∴ x = 4.5 + sqrt(4.5² - (4.5 - 4.5)²)
     ∴ x = 9
    
  4. 在屏幕坐标系中,已知圆心 O 的坐标 (4.5, 4.5),半径 r 为 4.5,圆上点在区域 rect (5, 0, 5, 5) 中的 y 轴相对坐标为 4,求该点的 x 轴相对坐标

     ∵ (rect.x + x - O.x)² + (O.y - rect.y - y)² = r²
     ∴ x = sqrt(r² - (O.y - rect.y - y)²) + O.x - rect.x
     ∴ x = sqrt(4.5² - (4.5 - 0 - 4)²) + 4.5 - 5
     ∴ x = sqrt(4.5² - 0.5²) - 0.5
     ∴ x = 3.97213
    

抗锯齿

上述绘制方法只是解决某个坐标上的像素点要不要填充颜色的问题,实际绘制出来的圆的边界会有锯齿,那么如何做抗锯齿处理?稍微花点时间思考后,最容易想到的方法是让与圆心距离越接近半径的像素点的颜色接近边框色,即:

  1. 与圆心的距离大于等于半径 + 1,则像素点填充透明色。
  2. 与圆心的距离大于等于半径,则将差值作为颜色的透明度来填充,例如:距离 d 为 5,圆半径为 4.5,那么就将色值乘以 0.5 再填充。
  3. 像素点到圆心的距离小于半径,不处理。

伪代码:

double d = sqrt(x * x + y * y);

if (d >= r + 1.0) {
    pixel.alpha = 0;
} else if (d >= r) {
    d -= r;
    pixel.red = color.red * d;
    pixel.green = color.green * d;
    pixel.blue = color.blue * d;
    pixel.alpha = color.alpha * d;
}

LCUI 中用的圆角绘制方法大致如下,具体实现代码可参考 src/draw/border.c 文件。

  1. 计算各个边界框:
    • 半径 + 1 后的边框外边界 outer_smooth,用于抗锯齿处理。
    • 边框外边界 outer
    • 边框内边界 inner
  2. 对位于 outer 之外、outer_smooth 之内的像素点,计算其与圆心的距离,然后根据差值填充合适的颜色。

文章版权归作者所有,未经许可不得转载。

问题反馈

对此文章有疑问?你可以点击 这里 反馈