3

多くの人が知っていたように、HTML5 CanvaslineTo()は各コーナーでギザギザの線を表示します。この時点で、より好ましい解決策は を実装することquadraticCurveTo()です。これは、スムーズな描画を生成するための非常に優れた方法です。ただし、キャンバス HTML5 で滑らかで正確な描画を作成したいと考えています。二次曲線アプローチは、描画を滑らかにするのにうまく機能しますが、すべてのサンプル ポイントを通過するわけではありません。つまり、二次曲線を使用して簡単な曲線を描こうとすると、アプリケーションによって曲線が「修正」されたように見えることがあります。描画パスをたどる代わりに、セグメントの一部が元のパスから曲がって 2 次曲線をたどります。

私のアプリケーションは、HTML5 キャンバスでのプロフェッショナルな描画を目的としているため、描画が滑らかで正確であることが非常に重要です。HTML5 キャンバスを Photoshop やその他のペインター アプリケーション (SAI、painterX など) と同じレベルに置こうとして、不可能なことを求めているのかどうかはわかりません。

ありがとう

4

1 に答える 1

2

必要なのは、カーディナル スプラインが描画する実際のポイントを通過するカーディナル スプラインです。

注: プロフェッショナルな結果を得るには、短いしきい値に対して移動平均を実装し、大きなしきい値に対してカーディナル スプラインを使用し、ニー値を使用して鋭い角で線を分割して、線全体を滑らかにしないようにする必要もあります。移動平均やニー (テーパーも) は範囲外であるため、ここでは扱いませんが、カーディナル スプラインの使用方法を示します。

補足事項として、アプリが線を変更しているように見える効果は、平滑化がポストで行われるため、避けられません。描画中に滑らかにするアルゴリズムがありますが、それらはニー値を保持せず、描画中に線が「ぐらつく」ように見えます。それは好みの問題だと思います。

以下は、次のことを示すためのフィドルです。
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などの使用できるポイント削減アルゴリズムもありますが、データ量を削減するためのストレージ目的でより多くの用途があります。

于 2013-07-26T15:52:09.520 に答える