This commit is contained in:
Wiesław Šoltés 2015-08-02 20:07:08 +02:00
Родитель 926dbcca1c
Коммит 7378baa794
9 изменённых файлов: 337 добавлений и 22 удалений

Просмотреть файл

@ -23,17 +23,41 @@ using System;
namespace SpiroNet
{
/// <summary>
/// The band matrix.
/// </summary>
internal struct BandMatrix
{
public double[] a; // new double[11];
public double[] al; // new double[5];
/// <summary>
/// The band-diagonal matrix.
/// A double's array of size 11.
/// </summary>
public double[] a;
/// <summary>
/// Lower part of band-diagonal decomposition.
/// A double's array of size 5.
/// </summary>
public double[] al;
/// <summary>
/// Copy band matrix from source band matrix to current instance of band matrix.
/// </summary>
/// <param name="from">The source band matrix.</param>
private void CopyFrom(ref BandMatrix from)
{
Array.Copy(from.a, 0, a, 0, 11);
Array.Copy(from.al, 0, al, 0, 5);
}
/// <summary>
/// The source band matrix.
/// </summary>
/// <param name="src">The source band matrix.</param>
/// <param name="srcIndex">The source band matrix start element index.</param>
/// <param name="dst">The destination band matrix.</param>
/// <param name="dstIndex">The destination band matrix start element index.</param>
/// <param name="length">Number of elements to copy from source band matrix.</param>
public static void Copy(BandMatrix[] src, int srcIndex, BandMatrix[] dst, int dstIndex, int length)
{
for (int i = 0; i < length; ++i)

Просмотреть файл

@ -23,12 +23,57 @@ using System;
namespace SpiroNet
{
/// <summary>
/// Abstract type that handles the creation of particular representation of bézier splines.
///
/// Spiro will convert a set of spiro control points into a set of bézier curves.
///
/// As it does so it will call the appropriate routine in your bézier context with this information
/// – this should allow you to create your own internal representation of those curves.
/// </summary>
public interface IBezierContext
{
/// <summary>
/// Called by spiro to start a contour.
/// </summary>
/// <param name="x">The X coordinate of the new start point.</param>
/// <param name="y">The Y coordinate of the new start point.</param>
/// <param name="isOpen">An boolean flag indicating wheter spline is open (True) or closed (False).</param>
void MoveTo(double x, double y, bool isOpen);
/// <summary>
/// Called by spiro to move from the last point to the next one on a straight line.
/// </summary>
/// <param name="x">The X coordinate of the new end point.</param>
/// <param name="y">The Y coordinate of the new end point.</param>
void LineTo(double x, double y);
/// <summary>
/// Called by spiro to move from the last point to the next along a quadratic bezier spline
/// (x1,y1) is the quadratic bezier control point and (x2,y2) will be the new end point.
/// </summary>
/// <param name="x1">The X coordinate of quadratic bezier bezier control point.</param>
/// <param name="y1">The Y coordinate of quadratic bezier bezier control point.</param>
/// <param name="x2">The X coordinate of the new end point.</param>
/// <param name="y2">The Y coordinate of the new end point.</param>
void QuadTo(double x1, double y1, double x2, double y2);
/// <summary>
/// Called by spiro to move from the last point to the next along a cubic bezier spline
/// (x1,y1) and (x2,y2) are the two off-curve control point and (x3,y3) will be the new end point.
/// </summary>
/// <param name="x1">The X coordinate of first cubic bezier spline off-curve control point.</param>
/// <param name="y1">The Y coordinate of first cubic bezier spline off-curve control point.</param>
/// <param name="x2">The X coordinate of second cubic bezier spline off-curve control point.</param>
/// <param name="y2">The Y coordinate of second cubic bezier spline off-curve control point.</param>
/// <param name="x3">The X coordinate of the new end point.</param>
/// <param name="y3">The Y coordinate of the new end point.</param>
void CurveTo(double x1, double y1, double x2, double y2, double x3, double y3);
/// <summary>
/// Called by spiro to mark current control point knot.
/// </summary>
/// <param name="knotIndex">The current spiros control point knot index.</param>
void MarkKnot(int knotIndex);
}
}

Просмотреть файл

@ -24,16 +24,31 @@ using System.Text;
namespace SpiroNet
{
/// <summary>
/// Bezier context implementation that handles the creation of Path data representation of bézier splines.
/// </summary>
/// <remarks>
/// Internally class used StringBuilder object to append generated Path data.
/// </remarks>
public class PathBezierContext : IBezierContext
{
private bool _needToClose = false;
private StringBuilder _sb = new StringBuilder();
/// <summary>
/// Format double value using en-GB culture info.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private static string Format(double value)
{
return value.ToString(CultureInfo.GetCultureInfo("en-GB"));
}
/// <summary>
/// Get output Path data string format.
/// </summary>
/// <returns>The Path data string format.</returns>
public string GetData()
{
if (_needToClose)
@ -45,11 +60,21 @@ namespace SpiroNet
return _sb.ToString();
}
/// <summary>
/// Get output Path data string format.
/// </summary>
/// <returns>The Path data string format.</returns>
public override string ToString()
{
return GetData();
}
/// <summary>
/// Start a contour.
/// </summary>
/// <param name="x">The X coordinate of the new start point.</param>
/// <param name="y">The Y coordinate of the new start point.</param>
/// <param name="isOpen">An boolean flag indicating wheter spline is open (True) or closed (False).</param>
public void MoveTo(double x, double y, bool isOpen)
{
if (_needToClose)
@ -62,25 +87,52 @@ namespace SpiroNet
_sb.AppendLine(move);
_needToClose = !isOpen;
}
/// <summary>
/// Move from the last point to the next one on a straight line.
/// </summary>
/// <param name="x">The X coordinate of the new end point.</param>
/// <param name="y">The Y coordinate of the new end point.</param>
public void LineTo(double x, double y)
{
var line = string.Format("L {0},{1}", Format(x), Format(y));
_sb.AppendLine(line);
}
/// <summary>
/// Move from the last point to the next along a quadratic bezier spline.
/// </summary>
/// <param name="x1">The X coordinate of quadratic bezier bezier control point.</param>
/// <param name="y1">The Y coordinate of quadratic bezier bezier control point.</param>
/// <param name="x2">The X coordinate of the new end point.</param>
/// <param name="y2">The Y coordinate of the new end point.</param>
public void QuadTo(double x1, double y1, double x2, double y2)
{
var quad = string.Format("Q {0},{1} {2},{3}", Format(x1), Format(y1), Format(x2), Format(y2));
_sb.AppendLine(quad);
}
/// <summary>
/// Move from the last point to the next along a cubic bezier spline.
/// </summary>
/// <param name="x1">The X coordinate of first cubic bezier spline off-curve control point.</param>
/// <param name="y1">The Y coordinate of first cubic bezier spline off-curve control point.</param>
/// <param name="x2">The X coordinate of second cubic bezier spline off-curve control point.</param>
/// <param name="y2">The Y coordinate of second cubic bezier spline off-curve control point.</param>
/// <param name="x3">The X coordinate of the new end point.</param>
/// <param name="y3">The Y coordinate of the new end point.</param>
public void CurveTo(double x1, double y1, double x2, double y2, double x3, double y3)
{
var curve = string.Format("C {0},{1} {2},{3} {4},{5}", Format(x1), Format(y1), Format(x2), Format(y2), Format(x3), Format(y3));
_sb.AppendLine(curve);
}
public void MarkKnot(int knot_idx) { }
/// <summary>
/// Mark current control point knot. Currenlty not implemented, may be usefull for marking generated curves to original spiro code points.
/// </summary>
/// <param name="knotIndex">The current spiros control point knot index.</param>
public void MarkKnot(int knot_idx)
{
}
}
}

Просмотреть файл

@ -24,14 +24,40 @@ using System.Linq;
namespace SpiroNet
{
/// <summary>
/// The spiro shape used to generate Path data.
/// </summary>
public class PathShape
{
/// <summary>
/// Spiro control points array.
/// </summary>
public IList<SpiroControlPoint> Points { get; set; }
public bool IsClosed { get; set; }
public bool IsTagged { get; set; }
public string Source { get; set; }
public bool UpdateSource()
/// <summary>
/// Is closed spiro shape.
/// Whether points describe a closed (True) or open (False) contour.
/// </summary>
public bool IsClosed { get; set; }
/// <summary>
/// Is tagged spiro shape.
/// This requires that spiro control points be tagged according to convention. A closed curve will have an extra control point attached to the end of it with a type of 'End'.
/// The location of this last point is irrelevant.
/// In an open contour the point types of the first and last control points are going to be ignored.
/// </summary>
public bool IsTagged { get; set; }
/// <summary>
/// The generated Path data.
/// </summary>
public string Data { get; set; }
/// <summary>
/// Generate Path shape data using path bezier context implementation.
/// </summary>
/// <returns>True when Data was generated successfully.</returns>
public bool UpdateData()
{
var points = this.Points.ToArray();
var bc = new PathBezierContext();
@ -40,9 +66,9 @@ namespace SpiroNet
{
var success = Spiro.TaggedSpiroCPsToBezier0(points, bc);
if (success)
this.Source = bc.ToString();
this.Data = bc.ToString();
else
this.Source = string.Empty;
this.Data = string.Empty;
return success;
}
@ -50,9 +76,9 @@ namespace SpiroNet
{
var success = Spiro.SpiroCPsToBezier0(points, points.Length, this.IsClosed, bc);
if (success)
this.Source = bc.ToString();
this.Data = bc.ToString();
else
this.Source = string.Empty;
this.Data = string.Empty;
return success;
}

Просмотреть файл

@ -24,15 +24,23 @@ using System;
namespace SpiroNet
{
/// <summary>
/// C# implementation of third-order polynomial spirals.
/// Interface routines for Raph's spiro package.
/// </summary>
public static class Spiro
{
/// <summary>
/// Convert a set of spiro control points into a set of bézier curves.
///
/// As it does so it will call the appropriate routine in your bézier context with this information
/// – this should allow you to create your own internal representation of those curves.
/// This function is kept for backwards compatibility.
///
/// Open contours do not need to start with '{', nor to end with '}'.
///
/// Close contours do not need to end with 'z'.
///
/// This function is kept for backwards compatibility for older programs.
/// Please use the function that return success/failure replies when done.
/// </summary>
/// <param name="spiros">An array of input spiros.</param>
/// <param name="n">The number of elements in the spiros array.</param>
@ -47,7 +55,11 @@ namespace SpiroNet
/// Convert a tagged set of spiro control points into a set of bézier curves.
/// As it does so it will call the appropriate routine in your bézier context with this information
/// – this should allow you to create your own internal representation of those curves.
/// This function is kept for backwards compatibility.
/// The spiros array should indicate it's own end.
/// Open contours must have the ty field of the first cp set to '{' and have the ty field of the last cp set to '}'.
/// Closed contours must have an extra cp at the end whose ty is 'z' the x&y values of this extra cp are ignored.
/// This function is kept for backwards compatibility for older programs.
/// Please use the functions that return success/failure replies when done.
/// </summary>
/// <param name="spiros">An array of input spiros.</param>
/// <param name="bc">A bézier results output context.</param>
@ -58,8 +70,16 @@ namespace SpiroNet
/// <summary>
/// Convert a set of spiro control points into a set of bézier curves.
///
/// As it does so it will call the appropriate routine in your bézier context with this information
/// – this should allow you to create your own internal representation of those curves.
///
/// Open contours do not need to start with '{', nor to end with '}'.
///
/// Close contours do not need to end with 'z'.
///
/// This function is kept for backwards compatibility for older programs.
/// Please use the function that return success/failure replies when done.
/// </summary>
/// <param name="spiros">An array of input spiros.</param>
/// <param name="n">The number of elements in the spiros array.</param>
@ -107,8 +127,15 @@ namespace SpiroNet
/// <summary>
/// Convert a tagged set of spiro control points into a set of bézier curves.
///
/// As it does so it will call the appropriate routine in your bézier context with this information
/// – this should allow you to create your own internal representation of those curves.
///
/// The spiros array should indicate it's own end.
///
/// Open contours must have the ty field of the first cp set to '{' and have the ty field of the last cp set to '}'.
///
/// Closed contours must have an extra cp at the end whose ty is 'z' the x&y values of this extra cp are ignored.
/// </summary>
/// <param name="spiros">An array of input spiros.</param>
/// <param name="bc">A bézier results output context.</param>
@ -162,8 +189,16 @@ namespace SpiroNet
/// <summary>
/// Convert a set of spiro control points into a set of bézier curves.
///
/// As it does so it will call the appropriate routine in your bézier context with this information
/// – this should allow you to create your own internal representation of those curves.
///
/// Open contours do not need to start with '{', nor to end with '}'.
///
/// Close contours do not need to end with 'z'.
///
/// If you can't use SpiroCPsToBezier0() this function is enhanced version of the original function,
/// where spiro success/failure replies are passd back through done output parameter.
/// </summary>
/// <param name="spiros">An array of input spiros.</param>
/// <param name="n">The number of elements in the spiros array.</param>
@ -177,8 +212,18 @@ namespace SpiroNet
/// <summary>
/// Convert a tagged set of spiro control points into a set of bézier curves.
///
/// As it does so it will call the appropriate routine in your bézier context with this information
/// – this should allow you to create your own internal representation of those curves.
///
/// The spiros array should indicate it's own end.
///
/// Open contours must have the ty field of the first cp set to '{' and have the ty field of the last cp set to '}'.
///
/// Closed contours must have an extra cp at the end whose ty is 'z' the x&y values of this extra cp are ignored.
///
/// If you can't use TaggedSpiroCPsToBezier0() this function is enhanced version of the original function,
/// where spiro success/failure replies are passd back through done output parameter.
/// </summary>
/// <param name="spiros">An array of input spiros.</param>
/// <param name="bc">A bézier results output context.</param>

Просмотреть файл

@ -23,10 +23,24 @@ using System;
namespace SpiroNet
{
/// <summary>
/// User passes an array of spiros control points in this format for Spiro to solve.
/// </summary>
public struct SpiroControlPoint
{
/// <summary>
/// Spiro code point X location.
/// </summary>
public double X;
/// <summary>
/// Spiro code point Y location.
/// </summary>
public double Y;
/// <summary>
/// Spiro code point Type.
/// </summary>
public SpiroPointType Type;
}
}

Просмотреть файл

@ -25,15 +25,35 @@ using System;
namespace SpiroNet
{
/// <summary>
/// Internal implementation of spiro.
/// C# implementation of third-order polynomial spirals.
/// Internal implementation of spiro using ORDER equal to 12.
/// </summary>
internal static class SpiroImpl
{
/// <summary>
/// Compute hypotenuse. The function returns what would be the square root of the sum of the squares of x and y (as per the Pythagorean theorem), but without incurring in undue overflow or underflow of intermediate values.
/// </summary>
/// <param name="x">The X floating point value corresponding to the legs of a right-angled triangle for which the hypotenuse is computed.</param>
/// <param name="y">The Y floating point value corresponding to the legs of a right-angled triangle for which the hypotenuse is computed.</param>
/// <returns>Returns the hypotenuse of a right-angled triangle whose legs are x and y.</returns>
public static double hypot(double x, double y)
{
return Math.Sqrt(x * x + y * y);
}
/// <summary>
/// Returns whether x is a finite value.
/// A finite value is any floating-point value that is neither infinite nor NaN (Not-A-Number).
///
/// IsFinite() equivalent:
/// http://stackoverflow.com/questions/10030070/isfinite-equivalent
/// References:
/// http://pubs.opengroup.org/onlinepubs/009604499/functions/isfinite.html
/// http://msdn.microsoft.com/en-us/library/system.double.isinfinity.aspx
/// http://msdn.microsoft.com/en-us/library/system.double.isnan.aspx
/// </summary>
/// <param name="x">A floating-point value.</param>
/// <returns>A non-zero value (true) if x is finite; and zero (false) otherwise.</returns>
public static int IsFinite(double x)
{
return !double.IsInfinity(x) && !double.IsNaN(x) ? 1 : 0;
@ -41,6 +61,7 @@ namespace SpiroNet
public const int N = 4;
// Integrate polynomial spiral curve over range -.5 .. .5.
public static void integrate_spiro(double[] ks, double[] xy, int n)
{
double th1 = ks[0];
@ -226,6 +247,7 @@ namespace SpiroNet
SpiroSegment[] r;
#if CHECK_INPUT_FINITENESS
// Verify that input values are within realistic limits
for (i = 0; i < n; i++)
{
if (IsFinite(src[i].X) == 0 || IsFinite(src[i].Y) == 0)
@ -292,6 +314,7 @@ namespace SpiroNet
int i, j, k, l, pivot;
double pivot_val, pivot_scale, tmp, x;
// pack top triangle to the left.
for (i = 0; i < 5; i++)
{
for (j = 0; j < i + 6; j++)
@ -352,6 +375,7 @@ namespace SpiroNet
int i, k, l;
double tmp, x;
// forward substitution
l = 5;
for (k = 0; k < n; k++)
@ -372,6 +396,7 @@ namespace SpiroNet
v[i] -= m[k].al[i - k - 1] * v[k];
}
// back substitution
l = 1;
for (i = n - 1; i >= 0; i--)
@ -484,6 +509,7 @@ namespace SpiroNet
compute_pderivs(ref s[i], ends, derivs, jinc);
// constraints crossing left
if (ty0 == SpiroPointType.G4 || ty0 == SpiroPointType.G2 || ty0 == SpiroPointType.Left || ty0 == SpiroPointType.Right)
{
jthl = jj++;
@ -498,6 +524,7 @@ namespace SpiroNet
}
}
// constraints on left
if ((ty0 == SpiroPointType.Left || ty0 == SpiroPointType.Corner || ty0 == SpiroPointType.OpenContour || ty0 == SpiroPointType.G2) && jinc == 4)
{
if (ty0 != SpiroPointType.G2)
@ -506,6 +533,7 @@ namespace SpiroNet
jk2l = jj++;
}
// constraints on right
if ((ty1 == SpiroPointType.Right || ty1 == SpiroPointType.Corner || ty1 == SpiroPointType.EndOpenContour || ty1 == SpiroPointType.G2) && jinc == 4)
{
if (ty1 != SpiroPointType.G2)
@ -514,6 +542,7 @@ namespace SpiroNet
jk2r = jj++;
}
// constraints crossing right
if (ty1 == SpiroPointType.G4 || ty1 == SpiroPointType.G2 || ty1 == SpiroPointType.Left || ty1 == SpiroPointType.Right)
{
jthr = jj;
@ -583,6 +612,7 @@ namespace SpiroNet
public static bool check_finiteness(SpiroSegment[] segs, int num_segs)
{
// Check if all values are "finite", return true, else return fail=false
int i, j;
for (i = 0; i < num_segs; ++i)
@ -605,7 +635,7 @@ namespace SpiroNet
n_alloc = nmat;
if (nmat == 0)
return 1;
return 1; // just means no convergence problems
if (s[0].Type != SpiroPointType.OpenContour && s[0].Type != SpiroPointType.Corner)
n_alloc *= 3;
if (n_alloc < 5)
@ -621,7 +651,7 @@ namespace SpiroNet
v = new double[n_alloc];
perm = new int[n_alloc];
i = converged = 0;
i = converged = 0; // not solved (yet)
if (m != null && v != null && perm != null)
{
while (i++ < 60)
@ -677,6 +707,7 @@ namespace SpiroNet
}
else
{
// subdivide
ksub[0] = .5 * ks[0] - .125 * ks[1] + (1.0 / 64) * ks[2] - (1.0 / 768) * ks[3];
ksub[1] = .25 * ks[1] - (1.0 / 16) * ks[2] + (1.0 / 128) * ks[3];
ksub[2] = .125 * ks[2] - (1.0 / 32) * ks[3];
@ -709,7 +740,7 @@ namespace SpiroNet
if (s != null)
{
nseg = src[0].Type == SpiroPointType.OpenContour ? n - 1 : n;
converged = 1;
converged = 1; // this value is for when nseg == 1; else actual value determined below
if (nseg > 1)
converged = solve_spiro(s, nseg);

Просмотреть файл

@ -23,15 +23,58 @@ using System;
namespace SpiroNet
{
/// <summary>
/// Possible values of the spiro control point Type property.
/// </summary>
public enum SpiroPointType
{
/// <summary>
/// A corner point.
/// Where the slopes and curvatures of the incoming and outgoing splines are unconstrained.
/// </summary>
Corner = 'v',
/// <summary>
/// A G4 curve point.
/// Continuous up to the fourth derivative.
/// </summary>
G4 = 'o',
/// <summary>
/// A G2 curve point.
/// Continuous up to the second derivative.
/// </summary>
G2 = 'c',
/// <summary>
/// A left constraint point.
/// Used to connect a curved line to a straight one.
/// </summary>
Left = '[',
/// <summary>
/// A right constraint point.
/// Used to connect a straight line to a curved one.
/// If you have a contour which is drawn clockwise, and you have a straight segment at the top, then the left point of that straight segment should be a left constraint, and the right point should be a right constraint.
/// </summary>
Right = ']',
/// <summary>
/// End point.
/// For a closed contour add an extra cp with a ty set to 'end'.
/// </summary>
End = 'z',
/// <summary>
/// Open contour.
/// For an open contour the first cp must have a ty set to 'open contour'.
/// </summary>
OpenContour = '{',
/// <summary>
/// End open contour.
/// For an open contour the last cp must have a ty set to 'end open contour'.
/// </summary>
EndOpenContour = '}'
}
}

Просмотреть файл

@ -23,15 +23,50 @@ using System;
namespace SpiroNet
{
/// <summary>
/// The run_spiro() uses array of information given in the spiro control point structure
/// and creates an array in this structure format to use by spiro_to_bpath for building bezier curves.
/// </summary>
public struct SpiroSegment
{
/// <summary>
/// Spiro code point segment_chord startX.
/// </summary>
public double X;
/// <summary>
/// Spiro code point segment_chord startY.
/// </summary>
public double Y;
/// <summary>
/// Spiro code point Type.
/// </summary>
public SpiroPointType Type;
/// <summary>
/// Bend theta between this vector and next vector.
/// </summary>
public double bend_th;
public double[] ks; // new double[4]
/// <summary>
/// A double's array of size 4.
/// </summary>
public double[] ks;
/// <summary>
/// The segment_chord distance from xy to next spiro code point.
/// </summary>
public double seg_ch;
/// <summary>
/// The segment_theta angle for this spiro code point.
/// </summary>
public double seg_th;
/// <summary>
/// Unused.
/// </summary>
public double l;
}
}