Штриховые кривые на Html5 Canvas Bezier - PullRequest
8 голосов
/ 08 сентября 2011

Для одного из моих приложений мне нужно было бы нарисовать пунктирные кривые на пути Безье в канале Html5 ... Длина тире и промежутки между ними должны быть переменными ... Это достижимо в JavaFx, см. эта ссылка ... Я хотел бы добиться того же эффекта, используя Html5 canvas. Я знаю, как рисовать пунктирные прямые линии, но не изогнутые линии вдоль Безье ...

Хотя я не эксперт, я знаю алгоритм рисования Безье , проблема, с которой я сталкиваюсь, заключается в том, что он позволяет определять координаты на Безье с помощью параметра времени, который находится в диапазоне от 0 до 1 ...

Этого недостаточно, потому что для рисования пунктирной линией мне нужно было бы нарисовать много маленьких линеек с заданным параметром длины и с заданным расстоянием между зазорами на главной траектории Безье. Должен быть какой-то алгоритм, который используется JavaFx. Если кто-нибудь может мне помочь, это было бы здорово.

Ответы [ 2 ]

5 голосов
/ 02 мая 2012

В будущем мы можем использовать context.setLineDash(segments): http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#line-styles

4 голосов
/ 09 сентября 2011

Я бы предположил, что 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() с для точек, которые находятся на расстоянии менее одного пикселя друг от друга, иногда делая артефакты АА. Фильтрация субпиксельных координат расстояния не сложна, но неэффективно генерировать больше «вершин», чем вам действительно нужно. Придумать лучший шаг - это то, что я бы посчитал более критичным для атаки.

Есть один бонус в использовании этого подхода: если вы замените bezier(controlPoints, t) на что-либо еще, что будет соответствовать кривой, вы будете рисовать пунктирную белизну! - снова с теми же потенциальными проблемами, перечисленными в предыдущем абзаце. Но действительно хорошее решение проблемы гранулярности может работать для всех кривых с хорошим поведением.

...