在javascript / typescript中绘制形状并填充颜色

时间:2017-11-25 02:11:35

标签: javascript typescript canvas graphics drawing

我在canvas中使用typescript / javascript绘制图像。 我得到的坐标来自直线和圆弧,我从数据库中读取这些数据(直线和圆弧的顺序是随机的)。 图片的绘图没问题,但我需要用颜色填充绘制的形状。使用moveTo和lineTo执行此操作然后调用fill不起作用。因为线和弧的随机顺序。还有两个,因为弧可以向内,所以它会在房间的外面上色。

示例图片:

enter image description here 我的绘图代码片段:

    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();
        }
    }

有没有办法对这个形状的内部进行着色?

1 个答案:

答案 0 :(得分:1)

对随机线和弧进行排序

如何从线和弧段的随机列表创建连续路径,每条线和弧沿着随机方向。

步骤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;
}

步骤2创建单个数组

要优化线的构造,您需要创建一个包含线和弧的单个数组。当你这样做时,你也可以计算弧终点。

function createSegmentArray(lines, arcs) {
     return [...lines, ...arcs.map(arcEnds)]; // add lines and end point calculated arcs
}

步骤3创建匹配点的功能

正如你可以预期的那样,在终点计算中会出现一些浮点误差,你需要有一个公差,这样就可以确定2点是否相等。

有两种方法可以查看两个点是否位于同一位置。我将使用距离的平方,因为它有点整洁。

// 1 pixel tolerance
const isSame = (p1,p2) => (((p1.x - p2.x) ** 2) + ((p1.y - p2.y) ** 2)) < 1; 

步骤4创建未排序和排序的数组

现在你创建数组pf segments

 const segs = createSegmentArray(image.lines, image.arcs);

创建第二个数组以保存有序段。

 const orderedSegs = [];

步骤5按匹配结束点或起点

进行排序

然后从第一个段开始,从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);

步骤6渲染结果。

现在你按照它们连接的顺序得到了所有的点,你可以按顺序渲染它们,但你必须反转弧的方向

  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();

这就是过程。

警告

  1. 如果形状上有孔,则无效。对于每个细分,必须有一个具有匹配结束或起点的细分。

  2. 如果第一个和最后一个之间没有段,它将起作用。它假定它们之间存在线段,并不表示路径未关闭。您需要测试orderedSegs数组中第一个分段的起点与isSame数组

  3. 中最后一个分段的终点相同orderedSegs
  4. 如果存在两个以上段加入的点,则无效。如果是这样,则创建的路径将沿着第一个连接的段移动,这可能不是正确的段。它不会完成排序,即使它找到了正确的封闭路径也会抛出错误。

  5. 这会忽略弧形方向,并假设弧线在方向上统一(全部CW或全部CCW)。如果不是这样,那么在渲染路径时你必须进行适当的修正。

  6. 上面创建的弧的起点和终点可能与您对该线的终点不同。它们只是具有x,y的对象。如果您希望它们是同一类型的对象,则需要在arcEnds函数中执行此操作。

  7. 由于我没有数据集,因此我无法测试上述代码,因此很可能存在任何数量的拼写错误。该代码仅作为流程逻辑的指南。您应该使用它来创建自己的版本,而不仅仅是复制和粘贴,因为它可能会因拼写错误而导致错误。