我认为JavaFX使用的是一般绘制虚线曲线的技术,只是在那个例子中恰好使用了贝塞尔曲线。
难点在于确定每个虚线段的起点和终点,这需要在贝塞尔曲线上知道各个点的弧长。
有一种分析方法,但我建议采用以下方法:
var bezier = function(controlPoints, t) {
};
var calculateDashedBezier = function(controlPoints, dashPattern) {
var step = 0.001;
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;
if(length >= dashPattern[dashIndex]) {
if(dashIndex % 2 == 0)
subPaths.push(thisPath);
dashIndex = (dashIndex + 1) % dashPattern.length;
thisPath = [];
length = 0;
}
if(dashIndex % 2 == 0) {
thisPath.push(loc[0], loc[1]);
}
}
if(thisPath.length > 0)
subPaths.push(thisPath);
return subPaths;
};
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]);
}
}
};
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),则会绘制虚线!- 再次存在前一段中列出的潜在问题。但是解决颗粒度问题的真正好方法可能适用于所有“行为良好”的曲线。