using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
namespace jSignature.Tools
{
class Vector
{
public float x;
public float y;
public Vector(int x, int y){
this.x = x;
this.y = y;
}
public Vector(float x, float y){
this.x = x;
this.y = y;
}
///
/// Returns NEW Vector object with reversed (multiplied by -1) coords.
///
///
public Vector Reversed
{
get {
return new Vector(
this.x * -1
, this.y * -1
);
}
}
private float? _length;
///
/// Applies Pithagoras theorem to find the length of the vector.
///
///
public float Length
{
get
{
if (this._length == null){
this._length = (float)Math.Sqrt( Math.Pow(this.x, 2) + Math.Pow(this.y, 2) );
}
return (float)this._length;
}
}
///
/// Returns either +1 or -1 indicating the polarity of the input number
///
/// some number
///
private int polarity(float value){
return (int)Math.Round(value / Math.Abs(value));
}
///
/// Returns NEW Vector object that has same directionality as this one, but with length of the vector scaled to stated size.
///
///
///
public Vector GetResizedTo(float length){
// proportionally changes x,y such that the hypotenuse (vector length) is = new length
if (this.x == 0 && this.y == 0){
return new Vector(0, 0);
} else if (this.x == 0){
return new Vector(0, length * polarity(this.y));
} else if(this.y == 0){
return new Vector(length * polarity(this.x), 0);
} else {
var proportion = Math.Abs(this.y / this.x);
var _x = Math.Sqrt(Math.Pow(length, 2) / (1 + Math.Pow(proportion, 2)));
var _y = proportion * _x;
return new Vector((float)(_x * polarity(this.x)), (float)(_y * polarity(this.y)));
}
}
/**
* Calculates the angle between 'this' vector and another.
* @public
* @function
* @returns {Number} The angle between the two vectors as measured in PI.
*/
public float AngleTo(Vector vectorB) {
var divisor = this.Length * vectorB.Length;
if (divisor == 0) {
return 0;
} else {
// JavaScript floating point math is screwed up.
// because of it, the core of the formula can, on occasion, have values
// over 1.0 and below -1.0.
return (float)( Math.Acos(
Math.Min(
Math.Max(
( this.x * vectorB.x + this.y * vectorB.y ) / divisor
, -1.0
)
, 1.0
)
) / Math.PI );
}
}
}
public class SVGConverter
{
protected static string segmentToCurve(int[][] stroke, int positionInStroke, float lineCurveThreshold)
{
// long lines (ones with many pixels between them) do not look good when they are part of a large curvy stroke.
// You know, the jaggedy crocodile spine instead of a pretty, smooth curve. Yuck!
// We want to approximate pretty curves in-place of those ugly lines.
// To approximate a very nice curve we need to know the direction of line before and after.
// Hence, on long lines we actually wait for another point beyond it to come back from
// mousemoved before we draw this curve.
// So for "prior curve" to be calc'ed we need 4 points
// A, B, C, D (we are on D now, A is 3 points in the past.)
// and 3 lines:
// pre-line (from points A to B),
// this line (from points B to C), (we call it "this" because if it was not yet, it's the only one we can draw for sure.)
// post-line (from points C to D) (even through D point is 'current' we don't know how we can draw it yet)
//
// Well, actually, we don't need to *know* the point A, just the vector A->B
// Again, we can only derive curve between points positionInStroke-1 and positionInStroke
// Thus, since we can only draw a line if we know one point ahead of it, we need to shift our focus one point ahead.
positionInStroke += 1;
// Let's hope the code that calls us knows we do that and does not call us with positionInStroke = index of last point.
var CDvector = new Vector(stroke[positionInStroke][0], stroke[positionInStroke][1]);
// Again, we have a chance here to draw only PREVIOUS line segment - BC
// So, let's start with BC curve.
// if there is only 2 points in stroke array (C, D), we don't have "history" long enough to have point B, let alone point A.
// so positionInStroke should start with 2, ie
// we are here when there are at least 3 points in stroke array.
var BCvector = new Vector(stroke[positionInStroke - 1][0], stroke[positionInStroke - 1][1]);
Vector ABvector;
var rounding = 2;
string curvetemplate = "c {0} {1} {2} {3} {4} {5}";
string linetemplate = "l {0} {1}";
if ( BCvector.Length > lineCurveThreshold ){
// Yey! Pretty curves, here we come!
if(positionInStroke > 2) {
ABvector = new Vector(stroke[positionInStroke - 2][0], stroke[positionInStroke - 2][1]);
} else {
ABvector = new Vector(0,0);
}
var minlenfraction = 0.05f;
var maxlen = BCvector.Length * 0.35;
var ABCangle = BCvector.AngleTo(ABvector.Reversed);
var BCDangle = CDvector.AngleTo(BCvector.Reversed);
var BtoCP1vector = new Vector(
ABvector.x + BCvector.x
, ABvector.y + BCvector.y
).GetResizedTo(
(float)(Math.Max(minlenfraction, ABCangle) * maxlen)
);
var CtoCP2vector = new Vector(
BCvector.x + CDvector.x
, BCvector.y + CDvector.y
).Reversed.GetResizedTo(
(float)(Math.Max(minlenfraction, BCDangle) * maxlen)
);
var BtoCP2vector = new Vector(BCvector.x + CtoCP2vector.x, BCvector.y + CtoCP2vector.y);
// returing curve for BC segment
// all coords are vectors against Bpoint
return String.Format(
curvetemplate
, Math.Round( BtoCP1vector.x, rounding )
, Math.Round( BtoCP1vector.y, rounding )
, Math.Round( BtoCP2vector.x, rounding )
, Math.Round( BtoCP2vector.y, rounding )
, Math.Round( BCvector.x, rounding )
, Math.Round( BCvector.y, rounding )
);
} else {
return String.Format(
linetemplate
, Math.Round( BCvector.x, rounding )
, Math.Round( BCvector.y, rounding )
);
}
}
protected static string lastSegmentToCurve(int[][] stroke, float lineCurveThreshold)
{
// Here we tidy up things left unfinished
// What's left unfinished there is the curve between the last points
// in the stroke
// We can also be called when there is only one point in the stroke (meaning, the
// stroke was just a dot), in which case there is nothing for us to do.
// So for "this curve" to be calc'ed we need 3 points
// A, B, C
// and 2 lines:
// pre-line (from points A to B),
// this line (from points B to C)
// Well, actually, we don't need to *know* the point A, just the vector A->B
// so, we really need points B, C and AB vector.
var positionInStroke = stroke.Length - 1;
// there must be at least 2 points in the stroke.for us to work. Hope calling code checks for that.
var BCvector = new Vector(stroke[positionInStroke][0], stroke[positionInStroke][1]);
var rounding = 2;
string curvetemplate = "c {0} {1} {2} {3} {4} {5}";
string linetemplate = "l {0} {1}";
if (positionInStroke > 1 && BCvector.Length > lineCurveThreshold){
// we have at least 3 elems in stroke
var ABvector = new Vector(stroke[positionInStroke - 1][0], stroke[positionInStroke - 1][1]);
var ABCangle = BCvector.AngleTo(ABvector.Reversed);
var minlenfraction = 0.05;
var maxlen = BCvector.Length * 0.35;
var BtoCP1vector = new Vector(
ABvector.x + BCvector.x
, ABvector.y + BCvector.y
).GetResizedTo(
(float)(Math.Max(minlenfraction, ABCangle) * maxlen)
);
return String.Format(
curvetemplate
, Math.Round( BtoCP1vector.x, rounding )
, Math.Round( BtoCP1vector.y, rounding )
, Math.Round( BCvector.x, rounding ) // CP2 is same as Cpoint
, Math.Round( BCvector.y, rounding ) // CP2 is same as Cpoint
, Math.Round( BCvector.x, rounding )
, Math.Round( BCvector.y, rounding )
);
} else {
// Since there is no AB leg, there is no curve to draw. This is just line
return String.Format(
linetemplate
, Math.Round( BCvector.x, rounding )
, Math.Round( BCvector.y, rounding )
);
}
}
public static string GetPathsSVGFragment(int[][][] data, int shiftx, int shifty)
{
// I was contemplating going the