有一项任务是使用 HTML5 Canvas 实现我自己的 arc() 方法。 它应该与原生 arc(typescript 代码)具有相同的签名:
arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, anticlockwise?: boolean): void;
我知道圆将通过 lineTo() 方法绘制许多短线,所以我需要设置精度(线数。可能由本地常量)。 有一些自己的例子,只适用于某些圈子。但该方法必须与原生 arc() 方法一样通用。 任何帮助/链接将不胜感激。
当前代码(画一个完整的圆)。
private customCircleDraw(center: Point, radius: number, start: number = 0, end: number = 2) {
const p1 = new Point(center.x + radius * Math.cos(start * Math.PI), -center.y + radius * Math.sin(start * Math.PI));
const p2 = new Point(center.x + radius * Math.cos(end * Math.PI), -center.y + radius * Math.sin(end * Math.PI));
const points = 50;
let angle1;
let angle2;
for(let i = 0; i < points; ++i) {
angle1 = i * 2 * Math.PI / points;
angle2 = (i+1) * 2 * Math.PI / points;
let firstPoint = new Point(center.x + radius * Math.cos(angle1), center.y + radius * Math.sin(angle1));
let secondPoint = new Point(center.x + radius * Math.cos(angle2), center.y + radius * Math.sin(angle2));
// if (some advanced condition)
this.drawLine(firstPoint,secondPoint);
}
另外,请注意,中心点的 Y 轴是倒置的。 Canvas 原点移动了。为了更好地了解情况,我已将其部署到临时 link .该示例现在使用 native arc(),我想替换它。
最佳答案
与 Arc 几乎完美匹配。
我假设实现应该尽可能匹配现有功能。
为了绘制圆,我们围绕圆逐步画出线段。我们不想使用太小的步长,因为那只是不必要的工作,我们也不希望步长太大,否则圆看起来会参差不齐。对于此示例,高质量的线条长度约为 6 像素。
步数是周长除以线步长。周长是 = (PI * 2 * radius)
因此编号steps = (PI * 2 * radius) / 6
.但是我们可以稍微作弊。将线的长度设为两个馅饼的长度会使步数等于整个圆的半径。
Arc 的标准行为。
现在是一些标准行为。如果 radius < 0,则 Arc 抛出,如果 radius 为 0,则 arc 的行为类似于 lineTo 函数。可选方向以顺时针 (CW) == false 和 CCW(如果为真)画线。
如果从起点到终点在渲染方向上的 Angular 大于或等于一个完整的圆,则圆弧将绘制一个完整的圆(PI * 2)
两个 Angular ,start
和 end
可以在 > -Infinity
中的任何位置至 < Infinity
.我们需要将 Angular (如果不绘制完整的圆圈)归一化到 0 到 PI * 2 的范围
迭代步长和 Angular 步长。
一旦我们有了正确的开始和结束 Angular ,我们就可以找到弧的步数 steps = (end - start) / PI * radius
根据步数,我们可以计算出步 Angular step = (end - start) / steps
现在只需画出线段即可。 Arc 不使用 moveTo
所以所有线段都标有ctx.lineTo
.
圆上的一点是
x = Math.cos(angle) * radius + centerX
y = Math.sin(angle) * radius + centerY
步数将有小数部分,因此最后一条线段将比其余线段短。在主迭代之后,我们添加最后一条线段以在结束 Angular 处结束。
要完成该功能,需要使用 CanvasRenderingContext2D,因此我们将用新功能覆盖现有的 arc 功能。作为CanvasRenderingContext2D.prototype.arc = //the function
函数
CanvasRenderingContext2D.prototype.arc = function (x, y, radius, start, end, direction) {
const PI = Math.PI; // use PI and PI * 2 a lot so make them constants for easy reading
const PI2 = PI * 2;
// check radius is in range
if (radius < 0) { throw new Error(`Failed to execute 'arc' on 'CanvasRenderingContext2D': The radius provided (${radius}) is negative.`) }
if (radius == 0) { ctx.lineTo(x,y) } // if zero radius just do a lineTo
else {
const angleDist = end - start; // get the angular distance from start to end;
let step, i;
let steps = radius; // number of 6.28 pixel steps is radius
// check for full CW or CCW circle depending on directio
if((direction !== true && angleDist >= PI2)){ // full circle
step = PI2 / steps;
} else if((direction === true && angleDist <= -PI2)){ // full circle
step = -PI2 / steps;
}else{
// normalise start and end angles to the range 0- 2 PI
start = ((start % PI2) + PI2) % PI2;
end = ((end % PI2) + PI2) % PI2;
if(end < start) { end += PI2 } // move end to be infront (CW) of start
if(direction === true){ end -= PI2 } // if CCW move end behind start
steps *= (end - start) / PI2; // get number of 2 pixel steps
step = (end - start) / steps; // convert steps to a step in radians
if(direction === true) { step = -step; } // correct sign of step if CCW
steps = Math.abs(steps); // ensure that the iteration is positive
}
// iterate circle
for (i = 0 ; i < steps; i += 1){
this.lineTo(
Math.cos(start + step * i) * radius + x,
Math.sin(start + step * i) * radius + y
);
}
this.lineTo( // do the last segment
Math.cos(start + step * steps) * radius + x,
Math.sin(start + step * steps) * radius + y
);
}
}
例子
只是因为答案应该有一个可运行的例子。使用新的 arc 函数绘制随机圆。红色圆圈是 CCW,蓝色圆圈是 CW。外面的绿色圆圈是要比较的原始弧函数。
CanvasRenderingContext2D.prototype.arcOld = CanvasRenderingContext2D.prototype.arc;
CanvasRenderingContext2D.prototype.arc = function (x, y, radius, start, end, direction) {
const PI = Math.PI; // use PI and PI * 2 a lot so make them constants for easy reading
const PI2 = PI * 2;
// check radius is in range
if (radius < 0) { throw new Error(`Failed to execute 'arc' on 'CanvasRenderingContext2D': The radius provided (${radius}) is negative.`) }
if (radius == 0) { ctx.lineTo(x,y) } // if zero radius just do a lineTo
else {
const angleDist = end - start; // get the angular distance from start to end;
let step, i;
let steps = radius; // number of 6.28 pixel steps is radius
// check for full CW or CCW circle depending on directio
if((direction !== true && angleDist >= PI2)){ // full circle
step = PI2 / steps;
} else if((direction === true && angleDist <= -PI2)){ // full circle
step = -PI2 / steps;
}else{
// normalise start and end angles to the range 0- 2 PI
start = ((start % PI2) + PI2) % PI2;
end = ((end % PI2) + PI2) % PI2;
if(end < start) { end += PI2 } // move end to be infront (CW) of start
if(direction === true){ end -= PI2 } // if CCW move end behind start
steps *= (end - start) / PI2; // get number of 2 pixel steps
step = (end - start) / steps; // convert steps to a step in radians
if(direction === true) { step = -step; } // correct sign of step if CCW
steps = Math.abs(steps); // ensure that the iteration is positive
}
// iterate circle
for (i = 0 ; i < steps; i += 1){
this.lineTo(
Math.cos(start + step * i) * radius + x,
Math.sin(start + step * i) * radius + y
);
}
this.lineTo( // do the last segment
Math.cos(start + step * steps) * radius + x,
Math.sin(start + step * steps) * radius + y
);
}
}
const rand = (min, max = min + (min = 0)) => Math.random() * (max - min) + min;
// test code
const ctx = canvas.getContext("2d");
canvas.width = innerWidth - 20;
canvas.height = innerHeight - 20;
var count = 0;
(function randomCircle(){
count += 1;
if(count > 50){
ctx.clearRect(0,0,canvas.width,canvas.height);
count = 0;
}
var x = rand(canvas.width);
var y = rand(canvas.height);
var start = rand(-1000,1000);
var end = rand(-1000,1000);
var radius = rand(10,200);
var dir = rand(1) < 0.5;
ctx.strokeStyle = dir ? "red" : "blue";
ctx.beginPath()
ctx.arc(x,y,radius,start,end,dir)
ctx.stroke();
ctx.strokeStyle = "green";
ctx.beginPath()
ctx.arcOld(x,y,radius + 4,start,end,dir)
ctx.stroke();
setTimeout(randomCircle,250);
})();
canvas { position : absolute; top : 0px; left : 0px; }
Red circles CCW, blue CW.
<canvas id="canvas"></canvas>
为了更好的...
除了一件小事外几乎完美。我使用了大约 6 像素长的线段大小。这不适用于<~8px radius 的小圆圈。我把它留给你来解决。
- 提示,一个简单的小半径测试,你可以增加
steps
的数量。 .如果steps
将行的长度加倍
关于javascript - 自定义 Canvas arc() 方法实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46287423/