HTML5画布上的虚线曲线贝塞尔

9
对于我的一个应用程序,我需要在Html5画布上的bezier路径上绘制虚线曲线... 虚线的长度和间隔应该是可变的... 在JavaFx中可以实现,请看这个链接... 我想使用Html5画布实现相同的效果。我知道如何绘制虚线直线,但不知道如何沿着bezier绘制曲线...

虽然我不是专家,但我知道bezier绘图算法,我发现这个算法的问题在于,它允许您使用时间参数(范围从0到1)来识别bezier上的坐标...

这还不够,因为要绘制虚线bezier,我需要在主bezier路径上绘制许多小的bezier,并指定长度参数和给定间隔距离。JavaFx使用了某些算法。如果有人能帮我解决这个问题,那就太好了。


这里有一个类似的东西,你可以在这里找到一个聪明的实现方法来适应虚线曲线:https://dev59.com/bm455IYBdhLWcg3wCPjh。这里有一个实时示例:http://phrogz.net/tmp/canvas_dashed_line.html。 - unmounted
正如我所说,我知道如何画虚线,问题是如何在贝塞尔路径上绘制虚线曲线... - Software Enthusiastic
我猜你可以在你的贝塞尔曲线绘制算法中使用模运算符(%)。对于曲线长度上的偶数位置,将alpha设置为零,对于奇数位置,则设置常规alpha。如果您可以向我提供您的贝塞尔算法,我不介意将这个数学公式插入其中。 :) - Juho Vepsäläinen
2个回答

5

我认为JavaFX使用的是一般绘制虚线曲线的技术,只是在那个例子中恰好使用了贝塞尔曲线。

难点在于确定每个虚线段的起点和终点,这需要在贝塞尔曲线上知道各个点的弧长

有一种分析方法,但我建议采用以下方法:

var bezier = function(controlPoints, t) {
  /* your code here, I'll presume it returns a 2-element array of x and y. */
};

//just figure out the coordinates of all the points in each dash, don't draw.
//returns an array of arrays, each sub-array will have an even number of nu-
//merical elements, to wit, x and y pairs.

//Argument dashPattern should be an array of alternating dash and space
//lengths, e.g., [10, 10] would be dots, [30, 10] would be dashes,
//[30, 10, 10, 10] would be 30-length dash, 10-length spaces, 10-length dash
// and 10-length space.
var calculateDashedBezier = function(controlPoints, dashPattern) {
  var step = 0.001; //this really should be set by an intelligent method,
                    //rather than using a constant, but it serves as an
                    //example.

  //possibly gratuitous helper functions
  var delta = function(p0, p1) {
    return [p1[0] - p0[0], p1[1] - p0[1]];
  };
  var arcLength = function(p0, p1) {
    var d = delta(p0, p1);
    return Math.sqrt(d[0]*d[0] + d[1] * d[1]);
  };

  var subPaths = [];
  var loc = bezier(controlPoints, 0);
  var lastLoc = loc;

  var dashIndex = 0;
  var length = 0;
  var thisPath = [];
  for(var t = step; t <= 1; t += step) {
    loc = bezier(controlPoints, t);
    length += arcLength(lastLoc, loc);
    lastLoc = loc;

    //detect when we come to the end of a dash or space
    if(length >= dashPattern[dashIndex]) {

      //if we are on a dash, we need to record the path.
      if(dashIndex % 2 == 0)
        subPaths.push(thisPath);

      //go to the next dash or space in the pattern
      dashIndex = (dashIndex + 1) % dashPattern.length;

      //clear the arclength and path.
      thisPath = [];
      length = 0;
    }

    //if we are on a dash and not a space, add a point to the path.
    if(dashIndex % 2 == 0) {
      thisPath.push(loc[0], loc[1]);
    }
  }
  if(thisPath.length > 0)
    subPaths.push(thisPath);
  return subPaths;
};

//take output of the previous function and build an appropriate path
var pathParts = function(ctx, pathParts) {
  for(var i = 0; i < pathParts.length; i++) {
    var part = pathParts[i];
    if(part.length > 0)
      ctx.moveTo(part[0], part[1]);
    for(var j = 1; j < part.length / 2; j++) {
      ctx.lineTo(part[2*j], part[2*j+1]);
    }
  }
};

//combine the above two functions to actually draw a dashed curve.
var drawDashedBezier = function(ctx, controlPoints, dashPattern) {
  var dashes = calculateDashedBezier(controlPoints, dashPattern);
  ctx.beginPath();
  ctx.strokeStyle = /* ... */
  ctx.lineWidth = /* ... */
  pathParts(ctx, dashes);
  ctx.stroke();
};

这种方法的主要问题在于它的粒度不够智能化。当步长对于您的(小型)虚线或(大型)曲线来说太大时,步长将无法正常工作,并且短划线边界将不会完全落在您希望它们落的地方。当步长太小时,您可能会在相距亚像素距离的点上进行lineTo(),有时会产生AA伪影。过滤子像素距离坐标并不难,但生成比您实际需要的“顶点”更多是低效的。设计出更好的步长实际上是我认为可以更加分析地解决的问题。
使用这种方法有一个额外的好处:如果您用任何其他评估为曲线的东西替换bezier(controlPoints, t),则会绘制虚线!- 再次存在前一段中列出的潜在问题。但是解决颗粒度问题的真正好方法可能适用于所有“行为良好”的曲线。

1
你说得对,假设步长为0.001是一个很大的风险,因为你可能不知道贝塞尔曲线的大小。更好的方法是通过找到中点递归地计算步数,直到两个点之间的距离变为零或曲线变成直线... - Software Enthusiastic
另一种更简单的方法是将“步长”设置为贝塞尔曲线弧长的近似值,然后将其除以最小虚线长度。但是,如果您的曲线使得“1/步长”远大于弧长除以最小虚线长度,则固定的“步长”也可以正常工作。 - ellisbben

5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接