将不规则数量的x,y点平滑处理以模拟手写路径。

7
我正在尝试在AS3(纯净版,非Flex)中制作一个“画笔”工具,模拟手写,使笔画变得平滑而不是有角度。然后,轨迹必须被缩减为立方贝塞尔曲线,这些曲线可以被拖动和变形,影响先前绘制的路径(就像Illustrator的钢笔工具一样)。
我正在跟踪鼠标移动以获取一组点来绘制路径。据我所知,我需要使用该点集进行B样条路径。然后,我应该将其缩减为立方贝塞尔曲线(添加到路径的“钢笔工具”功能)。
我已经开发了钢笔工具,使用算法将立方贝塞尔曲线缩减为二次贝塞尔曲线(然后使用Flash curveTo函数)。但我不知道如何创建B样条(或其他简化),然后将其缩减为贝塞尔曲线。
你知道实现这一点的任何方法吗?

我正在寻找相同的东西,但是用JavaScript,你知道有吗? - igor
4个回答

6

jhotdraw 是一个用Java编写的开源绘图项目。它将手绘图形转换为立方贝塞尔曲线。源代码可供下载和翻译。不要被项目的规模吓到:你只需要用到其中的几个类,包括:

org.jhotdraw.geom.Bezier
org.jhotdraw.geom.BezierPath
org.jhotdraw.geom.Geom

在翻译时,将所有集合声明更改为数组(如果仅针对FP10用户,则使用向量)。我有一些正则表达式,你可能会发现在转换中很有用-如果您想要,我可以发布它们。


以下是一些可能有用的正则表达式列表。在每个匹配对中,将第一个粘贴到搜索文本区域中,将第二个粘贴到替换区域中,勾选正则表达式复选框,并使用查找和替换按钮。不要使用Replace All - 这些都不保证是百分百可靠的。

将所有int/double name声明替换为var name:Number

\b(double|int)\s+(\w+)\b

var $2:Number

将所有的Point2D.Double name声明替换为var name:Point

\bPoint2D\.Double\s+(\w+)\b

var $1:Point

将所有函数签名中的 int/double name 声明替换为 name:Number

\(([^)]*)\b(?:double|int)\s+(\w+)\b([^)]*?)\)

($1$2:Number$3)

将所有函数签名中的Point2D.Double name声明替换为name:Point

\(([^)]*)\b(?:Point2D\.Double)\s+(\w+)\b([^)]*?)\) 

($1$2:Point$3)

在更改方法签名之前,请确保所有方法都是静态的:

(public|private)\s+(?!static)

将方法签名替换为AS格式

(public|private)\s+static\s+(\w+)\s+(\w+)\s*\(([^)]*)\)

$1 static function $3($4):$2

使用 array[index] 替换 ArrayList.get(index) //警告:对于 list.get(list.size() - 1) 不适用

(\w+)\.get\(([^)]+)\)

$1[$2]

//avoid the () failure 

(\w+)\.get\(([^)]*(?:\([^)]*\))[^)]*)\)

$1[$2]

ArrayList.set(index, element)替换为array[index] = element //警告:对于list.set(i, list.size())会失败

(\w+)\.set\(([^,]+)\s*,\s*([^)]+)\)

$1[$2] = $3


/*the above regex successfully made the following replacement*/

cleaned.set(cleaned.size() - 1, digitizedPoints[digitizedPoints.size() - 1])

cleaned[cleaned.size() - 1] = digitizedPoints[digitizedPoints.size() - 1]

arraylist.add(object)替换为array.push(object)

//would fail if object contains ')'
//add(index, object) should be done with splice

(\w+)\.add\(([^)]+)\)

$1.push($2)

//too many failures - fail safe version - 
//still fails for nested parenthesis  list.add(new Point(a.first(), a.last())) 
//- only three such cases - the effort to match parenthesis wouldn't be worth it
//works for list.add(new Point(3, 4)) - there were many similar cases

(\w+)\.add\(([^)]*(?:\([^)]*\))[^)]*)\)

$1.push($2)

将方法签名替换为AS格式(非静态方法)

(public|private)\s+(?!function)(\w+)\s+(\w+)\s*\(([^)]*)\)

$1 function $3($4):$2

将函数签名中所有的int/double/point/boolean name声明替换为name:type

\(([^)]*)\b(\w+)\s+(\w+)\b([^)]*?)\)

($1$3:$2$4)

将所有单独一行的变量声明改为AS格式中的=。
^(\s+)(\w+)\s+(\w+)\s*=\s*(.+?)\s*;(\s*)$

$1var $3:$2 = $4;$5

更改大括号的位置。

^(\t)(\s*)([^\n]+)\{\s*(\n)\s+

$1$2$3$4$1$2{$4$1$2

} else 改为 } \n else

^([ \t]+)}[ \t]*else\b([^\n]*)(\n)

$1}$3$1else$2$3

将单行中的4个变量声明拆分成不同行的AS语句

^(\t+)(\w+)\s+(\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*;[ \t]*(\n)

$1var $3:$2;$7$1var $4:$2;$7$1var $5:$2;$7$1var $6:$2;$7

更改数组声明

^(\s+)\w+\[\]\s*(\w+)\b

$1 var $2:Array

移除()转换-因为AS编译器不喜欢它们

(?:\(\w+\)\s*)([^ ,*+;/)><=\-])

$1

将max等替换为Math.max - 因为AS不支持静态导入

(?<!Math\.)\b(max|min|abs|sqrt|PI|cos|sin|atan2)\(

Math.$1(

我已经检查了这个库,它看起来很棒。示例绘图应用程序正好符合我的需求。感谢您提供的参考。 - user214624
这是我刚才提到的正则表达式。 - Amarghosh
太棒了!我已经开始将Java代码移植到AS3,这似乎并不是一项困难的任务,但这将极大地加快进程。非常感谢。 - user214624
请确保将端口上传到Github或Google Code,我今天早些时候正在寻找这个。 :) - timoxley

1

我曾经使用过这个函数一次。


public function multicurve(g: Graphics, args: Array, closed: Boolean): void {
    var mid: Array = args.slice();  //制作副本
    var i: uint;
    var point: Point;
    var nextPoint: Point;
    var numPoints: uint = mid.length;
if (numPoints == 2) { g.moveTo(mid[0].x, mid[0].y); g.lineTo(mid[1].x, mid[1].y); return; }
var Xpoint: Array = new Array(); var Ypoint: Array = new Array();
for (i = 1; i < numPoints - 2; i++) { point = mid[i]; nextPoint = mid[i + 1]; Xpoint[i] = 0.5 * (nextPoint.x + point.x); Ypoint[i] = 0.5 * (nextPoint.y + point.y); }
if (closed) { Xpoint[0] = 0.5 * (mid[1].x + mid[0].x); Ypoint[0] = 0.5 * (mid[1].y + mid[0].y); Xpoint[i] = 0.5 * (mid[i + 1].x + mid[i].x); Ypoint[i] = 0.5 * (mid[i + 1].y + mid[i].y); Xpoint[i + 1] = 0.5 * (mid[i + 1].x + mid[0].x); Ypoint[i + 1] = 0.5 * (mid[i + 1].y + mid[0].y); mid.push(new Point(mid[0].x, mid[0].y)); Xpoint[i + 2] = Xpoint[0]; Ypoint[i + 2] = Ypoint[0]; } else { Xpoint[0] = mid[0].x; Ypoint[0] = mid[0].y; Xpoint[i] = mid[i + 1].x; Ypoint[i] = mid[i + 1].y; mid.pop(); numPoints--; }
g.moveTo(Xpoint[0], Ypoint[0]);
for (i = 1; i < numPoints; i++) { point = mid[i]; g.curveTo(point.x, point.y, Xpoint[i], Ypoint[i]); }
if (closed) { g.curveTo(mid[0].x, mid[0].y, Xpoint[i], Ypoint[i]); } }

0

在C库中有一个算法可以实现您的要求: http://tog.acm.org/resources/GraphicsGems/gems/FitCurves.c

这是一个相当复杂的算法,通过将许多点的列表转换为一些紧密拟合的贝塞尔曲线的列表来简化您的几何图形,从而将涂鸦变成非常平滑的曲线。它具有可调节的松弛度,并通过找到适合您一定松弛度内点集的最少数量的贝塞尔曲线来工作。因此,您设置算法的松弛度越高,您的书写就越平滑(但可能不太准确)。


嘿!谢谢!这看起来很不错!我已经在使用jHotDraw算法(简化版),它的工作方式基本上与你给我链接的代码相似。不过,我也会深入了解其中的区别。网站上似乎还有更多文件源。再次感谢你提供的参考! - user214624
快速更新:FitCurves库起作用了,但如果我再次解决这个问题,我会采用不同的方法。首先,我必须调整库以处理快速书写 - 快速书写似乎是对这种算法进行压力测试的好方法。更重要的是,我会制作一个实时算法,随着您的绘制一次计算一条曲线。我将保留FitCurves的核心部分,即返回最适合一组点的单个曲线的算法,并自定义其余部分。 - Rolf Hendriks
链接已失效。存档在此处:https://web.archive.org/web/20090805204502/http://tog.acm.org/resources/GraphicsGems/gems/FitCurves.c这似乎是新的官方链接: https://github.com/erich666/GraphicsGems/blob/master/gems/FitCurves.c/* 自动拟合数字化曲线的算法 作者:Philip J. Schneider 来源:“图形宝石”,学术出版社,1990年 */ - murkle

0

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