Пунктирные кривые на холсте Html5 Безье

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

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

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


person Software Enthusiastic    schedule 08.09.2011    source источник
comment
Существует умная реализация чего-то подобного, которую вы можете адаптировать к пунктирным кривым здесь: в холсте">stackoverflow.com/questions/4576724/dotted-stroke-in-canvas Живой пример здесь: phrogz.net/tmp/canvas_dashed_line.html   -  person unmounted    schedule 08.09.2011
comment
Как я уже сказал, я знаю, как нарисовать пунктирную линию, проблема в том, как нарисовать пунктирные кривые на пути Безье...   -  person Software Enthusiastic    schedule 08.09.2011
comment
Я думаю, вы могли бы использовать mod op (%) в своем алгоритме без рисования. Установите альфа на ноль для четного положения и обычную альфа для нечетного положения на кривой относительно ее длины. Если вы можете предоставить мне свой алгоритм Безье, я не против подключить эту математику. :)   -  person Juho Vepsäläinen    schedule 08.09.2011


Ответы (2)


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

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

person ellisbben    schedule 08.09.2011
comment
Вы правильно определили, что, предполагая, что шаги до шага = 0,001, это большой риск, потому что вы можете не знать размер Безье заранее. Было бы лучше, если бы шаги вычислялись рекурсивно путем нахождения средней точки до тех пор, пока расстояние между двумя точками не станет равным нулю или кривая не станет прямой... - person Software Enthusiastic; 09.09.2011
comment
Другой, более простой способ сделать это — установить step равным приблизительной длине дуги кривой Безье, деленной на наименьшую длину штриха. Тем не менее, фиксированный step будет работать нормально, если ваши кривые таковы, что 1/step намного больше, чем длина дуги, деленная на наименьшую длину штриха. - person ellisbben; 09.09.2011

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

person Damien    schedule 02.05.2012