我在canvas中使用typescript / javascript绘制图像。 我得到的坐标来自直线和圆弧,我从数据库中读取这些数据(直线和圆弧的顺序是随机的)。 图片的绘图没问题,但我需要用颜色填充绘制的形状。使用moveTo和lineTo执行此操作然后调用fill不起作用。因为线和弧的随机顺序。还有两个,因为弧可以向内,所以它会在房间的外面上色。
示例图片:
let canvas = <HTMLCanvasElement>document.getElementById("canvas-view");
canvas.setAttribute('width', '500');
canvas.setAttribute('height', '500');
let ctx = canvas.getContext("2d");
if (ctx != null)
{
for (let count = 0; count < image.lines.length; count++)
{
ctx.beginPath();
ctx.moveTo(image.lines[count].startPoint.x, image.lines[count].startPoint.y);
ctx.lineTo(image.lines[count].endPoint.x, image.lines[count].endPoint.y);
ctx.stroke();
}
for (let count = 0; count < image.arcs.length; count++)
{
ctx.beginPath();
ctx.arc(image.arcs[count].center.x, image.arcs[count].center.y, image.arcs[count].radius, this.DegreesToRadians(image.arcs[count].startAngle), this.DegreesToRadians(image.arcs[count].endAngle));
ctx.stroke();
}
}
有没有办法对这个形状的内部进行着色?
答案 0 :(得分:1)
如何从线和弧段的随机列表创建连续路径,每条线和弧沿着随机方向。
要填充形状,您需要等到拥有所有线条和弧线。然后你必须订购它们,以便它们形成一个连续的轮廓。
为此您需要起点和终点,它们对于线而不是弧的坐标,因此您需要计算它们。
此功能将起点和终点添加到弧。
function arcEnds(arc){
const d2r = d => d * Math.PI / 180; // deg to rad
arc.startPoint = {
x : Math.cos(d2r(arc.startAngle)) * arc.radius + arc.center.x,
y : Math.sin(d2r(arc.startAngle)) * arc.radius + arc.center.y
}
arc.endPoint = {
x : Math.cos(d2r(arc.endAngle)) * arc.radius + arc.center.x,
y : Math.sin(d2r(arc.endAngle)) * arc.radius + arc.center.y
}
return arc;
}
要优化线的构造,您需要创建一个包含线和弧的单个数组。当你这样做时,你也可以计算弧终点。
function createSegmentArray(lines, arcs) {
return [...lines, ...arcs.map(arcEnds)]; // add lines and end point calculated arcs
}
正如你可以预期的那样,在终点计算中会出现一些浮点误差,你需要有一个公差,这样就可以确定2点是否相等。
有两种方法可以查看两个点是否位于同一位置。我将使用距离的平方,因为它有点整洁。
// 1 pixel tolerance
const isSame = (p1,p2) => (((p1.x - p2.x) ** 2) + ((p1.y - p2.y) ** 2)) < 1;
现在你创建数组pf segments
const segs = createSegmentArray(image.lines, image.arcs);
创建第二个数组以保存有序段。
const orderedSegs = [];
然后从第一个段开始,从segs数组中删除它,找到具有匹配结束或开始的段(我们还不知道方向)。如果未找到匹配的终点,则表示形状未关闭且无法填充,因此抛出错误以指示此情况。您将不得不添加一个catch处理程序。或者您可能更喜欢从循环中断,只是标记错误
您还需要反转错误方向的细分。对于一条线,您只需交换端点,交换端点所需的圆和端角,并添加一个标记以指示方向已反转。
var current = segs.shift();
while(seg.length > 0){
let reverse; // if true the segment needs to be reversed
const index = segs.findIndex(seg => { // find segment with matching end or start
if(isSame(current.endPoint, seg.startPoint)){
reverse = false;
return true
}
if(isSame(current.endPoint, seg.endPoint)){
reverse = true;
return true
}
return false;
})
if(index === -1){ throw new Error("The shape is not closed and can not be filled") }
orderedSegs.push(current);
current = segs.splice(index,1)[0]; // get the connected seg
if(reverse){
if(current.center){ // is a circle
const t = current.endPoint;
current.endPoint = current.startPoint;
current.startPoint = t;
const t1 = current.endAngle;
current.endAngle= current.startAngle;
current.startAngle = t1;
current.reversed = true;
}else{
const t = current.endPoint;
current.endPoint = current.startPoint;
current.startPoint = t;
}
}
// loop until no more segments
}
// push the last seg onto the array
orderedSegs.push(current);
现在你按照它们连接的顺序得到了所有的点,你可以按顺序渲染它们,但你必须反转弧的方向
const d2r = d => d * Math.PI / 180; // deg to rad
var i;
ctx.beginPath();
// arcs have start points now so dont have to check type for first point
ctx.moveTo(orderedSegs[0].startPoint.x, orderedSegs[0].startPoint.y);
for(i = 0; i < orderedSegs.length; i++){
var seg = orderedSegs[i];
if(seg.center){ // is a arc
ctx.arc(
seg.center.x, seg.center.y, seg.radius,
d2r(seg.startAngle), d2r(seg.endAngle), seg.reverse
);
}else{
ctx.lineTo(seg.endPoint.x, seg.endPoint.y);
}
}
ctx.fill();
ctx.stroke();
这就是过程。
如果形状上有孔,则无效。对于每个细分,必须有一个具有匹配结束或起点的细分。
如果第一个和最后一个之间没有段,它将起作用。它假定它们之间存在线段,并不表示路径未关闭。您需要测试orderedSegs
数组中第一个分段的起点与isSame
数组
orderedSegs
如果存在两个以上段加入的点,则无效。如果是这样,则创建的路径将沿着第一个连接的段移动,这可能不是正确的段。它不会完成排序,即使它找到了正确的封闭路径也会抛出错误。
这会忽略弧形方向,并假设弧线在方向上统一(全部CW或全部CCW)。如果不是这样,那么在渲染路径时你必须进行适当的修正。
上面创建的弧的起点和终点可能与您对该线的终点不同。它们只是具有x,y的对象。如果您希望它们是同一类型的对象,则需要在arcEnds
函数中执行此操作。
由于我没有数据集,因此我无法测试上述代码,因此很可能存在任何数量的拼写错误。该代码仅作为流程逻辑的指南。您应该使用它来创建自己的版本,而不仅仅是复制和粘贴,因为它可能会因拼写错误而导致错误。