必要なのは、カーディナル スプラインが描画する実際のポイントを通過するカーディナル スプラインです。
注: プロフェッショナルな結果を得るには、短いしきい値に対して移動平均を実装し、大きなしきい値に対してカーディナル スプラインを使用し、ニー値を使用して鋭い角で線を分割して、線全体を滑らかにしないようにする必要もあります。移動平均やニー (テーパーも) は範囲外であるため、ここでは扱いませんが、カーディナル スプラインの使用方法を示します。
補足事項として、アプリが線を変更しているように見える効果は、平滑化がポストで行われるため、避けられません。描画中に滑らかにするアルゴリズムがありますが、それらはニー値を保持せず、描画中に線が「ぐらつく」ように見えます。それは好みの問題だと思います。
以下は、次のことを示すためのフィドルです。
ONLINE DEMO
最初にいくつかの前提条件があります (私はeasyCanvasライブラリを使用してデモで環境をセットアップしていますが、これは多くの作業を節約しますが、これはこのソリューションが機能するための要件ではありません):
- メインのキャンバスの上にある別のキャンバスに新しいストロークを描くことをお勧めします。
- ストロークが終了したら (マウスを上げて)、それをスムーザーに渡し、ストローク スタックに保存します。
- 次に、滑らかな線をメインに引きます。
X / Y (つまり[x1, y1, x2, y2, ... xn, yn]
) の配列順序でポイントがある場合、この関数を使用して滑らかにすることができます。
張力値 ( 、ts
デフォルト 0.5) は、曲線を滑らかにするものです。数値が大きいほど曲線が丸くなります。通常の間隔 [0, 1] の外に出て、カールを作ることができます。
セグメント( nos
、またはセグメント数) は、各ポイント間の解像度です。ほとんどの場合、9 ~ 10 を超える値は必要ないでしょう。ただし、低速のコンピューターや高速で描画する場所では、より高い値が必要です。
関数 (最適化):
/// cardinal spline by Ken Fyrstenberg, CC-attribute
function smoothCurve(pts, ts, nos) {
// use input value if provided, or use a default value
ts = (typeof ts === 'undefined') ? 0.5 : ts;
nos = (typeof nos === 'undefined') ? 16 : nos;
var _pts = [], res = [], // clone array
x, y, // our x,y coords
t1x, t2x, t1y, t2y, // tension vectors
c1, c2, c3, c4, // cardinal points
st, st2, st3, st23, st32, // steps
t, i, r = 0,
len = pts.length,
pt1, pt2, pt3, pt4;
_pts.push(pts[0]); //copy 1. point and insert at beginning
_pts.push(pts[1]);
_pts = _pts.concat(pts);
_pts.push(pts[len - 2]); //copy last point and append
_pts.push(pts[len - 1]);
for (i = 2; i < len; i+=2) {
pt1 = _pts[i];
pt2 = _pts[i+1];
pt3 = _pts[i+2];
pt4 = _pts[i+3];
t1x = (pt3 - _pts[i-2]) * ts;
t2x = (_pts[i+4] - pt1) * ts;
t1y = (pt4 - _pts[i-1]) * ts;
t2y = (_pts[i+5] - pt2) * ts;
for (t = 0; t <= nos; t++) {
// pre-calc steps
st = t / nos;
st2 = st * st;
st3 = st2 * st;
st23 = st3 * 2;
st32 = st2 * 3;
// calc cardinals
c1 = st23 - st32 + 1;
c2 = st32 - st23;
c3 = st3 - 2 * st2 + st;
c4 = st3 - st2;
res.push(c1 * pt1 + c2 * pt3 + c3 * t1x + c4 * t2x);
res.push(c1 * pt2 + c2 * pt4 + c3 * t1y + c4 * t2y);
} //for t
} //for i
return res;
}
mouseup
次に、ポイントが保存された後にイベントから呼び出すだけです。
stroke = smoothCurve(stroke, 0.5, 16);
strokes.push(stroke);
膝の値に関する短いコメント:
このコンテキストでのニー値は、ライン内の (ライン セグメントの一部としての) ポイント間の角度が特定のしきい値 (通常は 45 ~ 60 度) より大きい場合です。膝が発生すると、線は新しい線に分割されるため、それらの間の角度がしきい値よりも小さい点で構成される線のみが使用されます (膝を使用しない結果として、デモで小さなカールが表示されます)。
移動平均についての簡単なコメント:
移動平均は通常、統計目的で使用されますが、描画アプリケーションにも非常に役立ちます。それらの間の距離が短い多くのポイントのクラスターがある場合、スプラインはうまく機能しません。したがって、ここでは MA を使用してポイントを滑らかにすることができます。
Ramer/Douglas/Peuckerなどの使用できるポイント削減アルゴリズムもありますが、データ量を削減するためのストレージ目的でより多くの用途があります。