圆是图形中经常使用的元素,大部分图形学库中都提供画圆的api,本文介绍不使用任何图形库直接使用TypeScript语言实现在浏览器中画圆的方法。

概述

html页面使用canvas元素进行绘制,TypeScript对canvas操作进行了简单的封装,这里主要用到设置单个像素点的颜色,代码如下app.ts

class WebGL { canvas: HTMLCanvasElement; ctx: CanvasRenderingContext2D; // 构造函数 constructor(canvas: HTMLCanvasElement) { this.canvas = canvas; this.ctx = canvas.getContext("2d"); this.vertices = [] } // 设置颜色值 glColor3f(r: number, g: number, b: number) { this.ctx.fillStyle = "rgba(" r*255 "," g*255 "," b*255 "," 1.0 ")"; } // 填充1x1像素的矩形(即一个像素大小)来实现单个像素颜色设置 setPixel(x: number, y: number) { this.ctx.fillRect(x, y, 1, 1); } // rgba指定的颜色清空整个canvas实现刷新canvas背景 glClearColor(r: number, g: number, b: number, a:number) { this.ctx.fillStyle = "rgba(" r*255 "," g*255 "," b*255 "," a ")"; this.ctx.fillRect(0, 0, this.canvas.clientWidth, this.canvas.clientHeight); } } var canvas = <HTMLCanvasElement>document.getElementById("canvas001"); var webgl = new WebGL(canvas); // 设置为黑色背景 webgl.glClearColor(0, 0, 0, 1);

注:为了突出重点,这里没有列出完整代码,稍有基础完全可以参考自行实现。

勾股定理法画圆

圆上的点到圆心的距离相等,已知圆心坐标(xc,yc)和半径r,根据勾股定理可以得出

圆的角度计算公式图解(圆生成算法)(1)

x的坐标范围为[xc-r xc r],可以遍历x,根据上面的公式求出y值(有两个值y1, y2),逐个设置像素坐标(x, y1)、(x,y2)的颜色值就可以绘制出圆了。

代码参考如下

// 续app.ts let start = Date.now(); webgl.glColor3f(1.0, 0, 0); let [xc, yc, r] = [110, 110, 100]; for (let x = yc - r; x < yc r; x ) { let tmp = Math.sqrt(r ** 2 - (x - xc) ** 2); let y1 = yc tmp; let y2 = yc - tmp; webgl.setPixel(x, y1); webgl.setPixel(x, y2); } let end = Date.now(); console.log("1-勾股定理法画圆耗时: " (end - start) "ms");

绘制效果见下图,可以明显看出在圆的左右两侧出现了明显的断点(由于斜率两侧较大,y值的变化较大),显然是不理想的,另外该方法中涉及平方、平方根运算,计算量相对是比较大的。

圆的角度计算公式图解(圆生成算法)(2)

极坐标法画圆

解决圆出现断点的问题可以使用极坐标法,可以根据角度计算圆上的坐标点,这样就可以利用沿圆周的等距点来绘制出圆了。

圆的角度计算公式图解(圆生成算法)(3)

圆的角度计算公式图解(圆生成算法)(4)

代码如下

// 续app.ts start = Date.now(); webgl.glColor3f(1.0, 0, 0); [xc, yc, r] = [400, 110, 100]; //把一个圆周分成360份,也就是用360个点来画圆 for (let i = 0; i < 360; i ) { let x = xc r * Math.cos(i); let y = yc r * Math.sin(i); webgl.setPixel(x, y); } end = Date.now(); console.log("2-极坐标法画圆耗时: " (end - start) "ms");

绘制效果见下图,可以看出还是有一些零散的断点,但分布是均匀的,可以通过把一个圆周分成更多的份数来解决这个问题,这个算法需要三角运算,同样存在运算量相对较大的问题。

圆的角度计算公式图解(圆生成算法)(5)

Bresenham中点画圆算法

之前的文章《Bresenham画线算法及实践》介绍了Bresenham中点算法进行线段的绘制,该算法还适合圆和其他曲线,关键是该算法以决策参数增量为依据,可以做到仅涉简单的整数处理,具有非常高的性能,下面介绍该算法实现的原理。

圆的角度计算公式图解(圆生成算法)(6)

圆的角度计算公式图解(圆生成算法)(7)

圆的角度计算公式图解(圆生成算法)(8)

圆的角度计算公式图解(圆生成算法)(9)

圆的角度计算公式图解(圆生成算法)(10)

根据上面的思路实现的代码参考如下

// 续app.ts // circlePlot为自定义WebGL类中的方法,实现了Bresenham画圆算法,(x_c, y_c)为圆心坐标,r为半径 circlePlot(x_c: number, y_c: number, r: number) { let [x0, y0] = [0, r]; let p = 1 - r; let y = y0; for (let x = x0; x < y; x ) { if (p < 0) { // 中点在园内 p = 2 * x 1; } else { // 中点在圆外或圆上 y -= 1; p = 2 * (x - y) 1; } this.setPixel(x_c x, y_c y); // 利用对称性绘制其他圆弧 this.setPixel(x_c y, y_c x); this.setPixel(x_c y, y_c - x); this.setPixel(x_c x, y_c - y); this.setPixel(x_c - x, y_c y); this.setPixel(x_c - y, y_c x); this.setPixel(x_c - x, y_c - y); this.setPixel(x_c - y, y_c - x); } } // Bresenham算法绘制圆 start = Date.now(); webgl.glColor3f(1.0, 0, 0); webgl.circlePlot(690, 110, 100); end = Date.now(); console.log("3-Bresenham中点画圆耗时: " (end - start) "ms");

下图是将三种方法绘制在一起的三个圆的效果,最右边的为Bresenham算法绘制的圆,效果更好些,根据记录的时间来看其算法耗时也是最小的(勾股定理法、极坐标法也可以利用对称性仅计算1/8圆弧就可以了,画一个圆性能上比较估计并不明显,当大量进行绘制时才能体现出Bresenham算法的优势)。

圆的角度计算公式图解(圆生成算法)(11)

参考文献

[1]. 《计算机图形学》(第三版)Donald Hearn、M.PaulineBaker著,3.9 圆生成算法,P80

,