/// <summary>
        /// Checks if (p.x+i,p.y+j) is in the same direction as the stroke is headed.
        /// </summary>
        /// <param name="s">the stroke</param>
        /// <param name="ind">the index of the point in the stroke</param>
        /// <param name="context">the number of points to use as context</param>
        /// <param name="i">x translation</param>
        /// <param name="j">y translation</param>
        /// <returns></returns>
        private bool checkDirection(Substroke s, int ind, int context, int i, int j)
        {
            double strokeDir = direction(s, ind, context);

            Sketch.Point p = s.PointsL[ind];

            double connectionDir = direction(p.Y, p.Y + j, p.X, p.X + i);

            double diff = Math.Abs(strokeDir - connectionDir);

            return(diff < Math.PI / 6d /*&& (curvature(s,ind,1)-curvature(s, ind, 20)) < 1*/);
        }
        private void readXY(string filename)
        {
            string name    = "";
            string strLine = "";

            string[] strs      = new string[0];
            int      numPoints = 0;

            Sketch.Point q = new Sketch.Point();

            StreamReader reader = new StreamReader(filename);

            name      = reader.ReadLine();
            strLine   = reader.ReadLine();
            numPoints = Convert.ToInt32(strLine);
            List <Sketch.Point> points = new List <Sketch.Point>(numPoints);
            float X = 0;
            float Y = 0;
            ulong T = 0;

            for (int i = 0; i < numPoints; i++)
            {
                strLine = reader.ReadLine();
                strs    = strLine.Split(new char[] { ',' }, 3);
                X       = (float)Convert.ToInt32(strs[0]);
                Y       = (float)Convert.ToInt32(strs[1]);
                T       = Convert.ToUInt64(strs[2]);
                Sketch.Point p = new Sketch.Point(X, Y, null, T);
                points.Add(p);
            }

            bool newClass = true;

            for (int i = 0; i < this.classes.Count; i++)
            {
                if (name == this.classes[i].Name)
                {
                    newClass = false;
                }
            }

            if (newClass)
            {
                addAsNewClass(name, points);
            }
            else
            {
                addToClass(name, points);
            }

            reader.Close();
        }
Beispiel #3
0
        /// <summary>
        /// an attempt to find labels based on what didn't have connections in certain directions.
        /// didn't work very well.
        /// </summary>
        /// <param name="sketch"></param>
        /// <param name="graph"></param>
        private static void AddLabelShapes(ref Sketch.Sketch sketch, NeighborhoodMap graph)
        {
            foreach (Substroke s in graph.Substrokes)
            {
                Sketch.Point a = s.PointsL[0];
                Sketch.Point b = s.PointsL[s.PointsL.Count - 1];
                Sketch.Point m = s.PointsL[s.PointsL.Count / 2];

                double dir   = 0d;
                int    x     = 0;
                bool   found = true;
                if (graph[s].Count > 1)
                {
                    bool N, E, W, S;
                    N = E = W = S = false;

                    foreach (Neighbor n in graph[s])
                    {
                        //dir = direction(m, n.neighbor.PointsL[n.neighbor.PointsL.Count/2]);
                        dir = NeighborhoodMap.direction(m, n.dest);
                        if (dir > -Math.PI / 3 && dir < Math.PI / 3)
                        {
                            E = true;
                        }
                        if (dir > Math.PI / 6 && dir < 5 * Math.PI / 6)
                        {
                            N = true;
                        }
                        if (dir > 2 * Math.PI / 3 || dir < -2 * Math.PI / 3)
                        {
                            W = true;
                        }
                        if (dir > -5 * Math.PI / 6 && dir < Math.PI / 6)
                        {
                            S = true;
                        }
                    }
                    found = !((N && S) || (E && W));
                }

                if (found)
                {
                    Shape label = new Shape(new List <Substroke>(new Substroke[] { s }),
                                            new XmlStructs.XmlShapeAttrs(true));
                    label.XmlAttrs.Type = "Label";
                    label.XmlAttrs.Name = "shape";
                    label.XmlAttrs.Time = 1;
                    sketch.AddShape(label);
                }
            }
        }
        private double feature2(List <Sketch.Point> points)
        {
            double featureValue = 0.0;

            if (points.Count > 2)
            {
                Sketch.Point p1 = points[0];
                int          n  = 2;
                Sketch.Point p2 = points[n];
                while (p1.X == p2.X && p1.Y == p2.Y)
                {
                    n++;
                    if (n < points.Count)
                    {
                        p2 = points[n];
                    }
                    else
                    {
                        break;
                    }
                }

                double numer = (double)p2.Y - (double)p1.Y;
                double denom = Math.Sqrt(Math.Pow(p2.X - p1.X, 2.0) + Math.Pow(p2.Y - p1.Y, 2.0));

                if (denom != 0)
                {
                    featureValue = numer / denom;
                }
                else
                {
                    featureValue = 0.0;
                }
            }

            return(featureValue);
        }
Beispiel #5
0
        /// <summary>
        /// Takes an Ink.Stroke and outputs an internal representation that can
        /// then be output to an XML file
        /// </summary>
        /// <param name="inkStroke">Ink.Stroke which will have extracted from it the information necessary to store a Stroke in MIT XML.</param>
        /// <param name="transf">Transformation matrix to shift the stroke by</param>
        /// <param name="shift">Boolean for if we should shift by the transformation matrix</param>
        /// <returns>PointsAndShape object that stores the extracted information.</returns>
        public static Sketch.Stroke StripStrokeData(Microsoft.Ink.Stroke inkStroke, Matrix transf, bool shift)
        {
            int i;
            int length;

            // Get the timestamp for the function using an undocumented GUID (Microsoft.Ink.StrokeProperty.TimeID) to
            // get the time as a byte array, which we then convert to a long
            ulong theTime;

            if (inkStroke.ExtendedProperties.Contains(Microsoft.Ink.StrokeProperty.TimeID) &&
                (inkStroke.ExtendedProperties[Microsoft.Ink.StrokeProperty.TimeID] != null))
            {
                byte[] timeBits = inkStroke.ExtendedProperties[Microsoft.Ink.StrokeProperty.TimeID].Data as byte[];

                // Filetime format
                ulong fileTime = BitConverter.ToUInt64(timeBits, 0);

                // MIT time format
                theTime = (fileTime - 116444736000000000) / 10000;
                //string time = theTime.ToString();
            }
            else
            {
                //theTime = (ulong)System.DateTime.Now.Ticks;
                //theTime = ((ulong)System.DateTime.Now.Ticks - 116444736000000000) / 10000;
                theTime = ((ulong)DateTime.Now.ToFileTime() - 116444736000000000) / 10000;
            }

            // Grab X and Y
            int[] xData = inkStroke.GetPacketValuesByProperty(Microsoft.Ink.PacketProperty.X);
            int[] yData = inkStroke.GetPacketValuesByProperty(Microsoft.Ink.PacketProperty.Y);

            if (shift)
            {
                // Shift X and Y according to transformation matrix
                System.Drawing.Point[] pointData = new System.Drawing.Point[xData.Length];
                length = xData.Length;
                for (i = 0; i < length; i++)
                {
                    pointData[i] = new System.Drawing.Point(xData[i], yData[i]);
                }

                transf.TransformPoints(pointData);

                //Console.WriteLine("x: " + (pointData[0].X - xData[0]) + " y: " + (pointData[0].Y - yData[0]));

                for (i = 0; i < length; i++)
                {
                    xData[i] = pointData[i].X;
                    yData[i] = pointData[i].Y;
                }
            }

            Microsoft.Ink.DrawingAttributes drawingAttributes = inkStroke.DrawingAttributes;
            System.Drawing.Rectangle        boundingBox       = inkStroke.GetBoundingBox();

            // Grab the color
            int color = drawingAttributes.Color.ToArgb();

            // THIS IS DRAWING POINT (PENTIP) HEIGHT AND WIDTH... WE DONT HAVE ANYWHERE TO PUT IT...
            //string height = inkStroke.DrawingAttributes.Height.ToString();
            //string width = inkStroke.DrawingAttributes.Width.ToString();
            float penWidth  = drawingAttributes.Width;
            float penHeight = drawingAttributes.Height;

            // Grab height and width of total stroke
            //float height = boundingBox.Height;
            //float width = boundingBox.Width;

            // Grab penTip
            string penTip = drawingAttributes.PenTip.ToString();

            // Grab raster
            string raster = drawingAttributes.RasterOperation.ToString();


            //float x = Convert.ToSingle(boundingBox.X);
            //float y = Convert.ToSingle(boundingBox.Y);

            //float leftx = Convert.ToSingle(boundingBox.Left);
            //float topy = Convert.ToSingle(boundingBox.Top);

            // If the pressure data is included take it, otherwise set to 255/2's
            // Not all pressure data is in the range of 0 - 255. Some tablets
            // register pressure in the range 0 - 1023, while others are 0 - 32768
            int[] pressureData;
            if (ReadJnt.IsPropertyIncluded(inkStroke, Microsoft.Ink.PacketProperty.NormalPressure))
            {
                pressureData = inkStroke.GetPacketValuesByProperty(Microsoft.Ink.PacketProperty.NormalPressure);
                int max = 0;
                foreach (int p in pressureData)
                {
                    max = Math.Max(max, p);
                }

                if (max > 1024) // Tablet reading in range 0 - 32767
                {
                    double     conversion = 255.0 / 32767.0;
                    List <int> temp       = new List <int>(pressureData.Length);
                    foreach (int p in pressureData)
                    {
                        temp.Add((int)(p * conversion));
                    }

                    pressureData = temp.ToArray();
                }
                else if (max > 255) // Tablet reading in range 0 - 1023
                {
                    double     conversion = 255.0 / 1023.0;
                    List <int> temp       = new List <int>(pressureData.Length);
                    foreach (int p in pressureData)
                    {
                        temp.Add((int)(p * conversion));
                    }

                    pressureData = temp.ToArray();
                }
            }
            else
            {
                pressureData = new int[xData.Length];
                length       = pressureData.Length;
                for (i = 0; i < length; ++i)
                {
                    pressureData[i] = (255 - 0) / 2;
                }
            }

            // If the time data is included take it, otherwise set to the timestamp plus and increment
            ulong[] adjustedTime = new ulong[xData.Length];
            int[]   timerTick;
            if (ReadJnt.IsPropertyIncluded(inkStroke, Microsoft.Ink.PacketProperty.TimerTick))
            {
                timerTick = inkStroke.GetPacketValuesByProperty(Microsoft.Ink.PacketProperty.TimerTick);
                length    = timerTick.Length;
                for (i = 0; i < length; i++)
                {
                    // This may be incorrect, we never encountered this because Microsoft Journal doesn't save TimerTick data.
                    // We know that the timestamp of a stroke is made when the pen is lifted.

                    // Add the time of the stroke to each offset
                    adjustedTime[i] = theTime + (ulong)timerTick[i] * 10000;
                }
            }
            else
            {
                timerTick = new int[xData.Length];
                length    = timerTick.Length;
                for (i = 0; i < length; i++)
                {
                    // We believe this to be the standard sample rate.  The multiplication by 1,000 is to convert from
                    // seconds to milliseconds.
                    //
                    // Our time is in the form of milliseconds since Jan 1, 1970
                    //
                    // NOTE: The timestamp for the stroke is made WHEN THE PEN IS LIFTED
                    adjustedTime[i] = theTime - (ulong)((1 / SAMPLE_RATE * 1000) * (length - i));
                }
            }

            // Create the array of ID's as new Guid's
            Guid[] idData = new Guid[xData.Length];
            length = idData.Length;
            for (i = 0; i < length; i++)
            {
                idData[i] = Guid.NewGuid();
            }

            // Create the internal representation
            Sketch.Stroke converterStroke = new Sketch.Stroke();
            converterStroke.Name   = "stroke";
            converterStroke.Time   = (ulong)theTime;
            converterStroke.Source = "Converter";


            // Add all of the points to the stroke

            length = inkStroke.PacketCount;
            List <Point> pointsToAdd = new List <Point>();

            for (i = 0; i < length; i++)
            {
                float currX = Convert.ToSingle(xData[i]);
                float currY = Convert.ToSingle(yData[i]);

                XmlStructs.XmlPointAttrs attrs = new XmlStructs.XmlPointAttrs(
                    currX, currY,
                    Convert.ToUInt16(pressureData[i]),
                    Convert.ToUInt64(adjustedTime[i]),
                    "point", idData[i]);

                Sketch.Point toAdd = new Sketch.Point(attrs);
            }


            //NOTE: X, Y, WIDTH, HEIGHT done later... boundingbox seems to be off
            Sketch.Substroke substroke = new Sketch.Substroke(pointsToAdd);
            substroke.Name      = "substroke";
            substroke.Color     = color;
            substroke.PenTip    = penTip;
            substroke.PenWidth  = penWidth;
            substroke.PenHeight = penHeight;
            substroke.Raster    = raster;
            substroke.Source    = "ConverterJnt";
            substroke.Start     = idData[0];
            substroke.End       = idData[idData.Length - 1];

            converterStroke.AddSubstroke(substroke);
            return(converterStroke);
        }
Beispiel #6
0
        public static Bitmap createFromShape(Shape shape, int width, int height, bool preserveAspect)
        {
            Rect bounds = shape.Bounds;
            int  x      = (int)bounds.X;
            int  y      = (int)bounds.Y;
            int  w      = (int)bounds.Width;
            int  h      = (int)bounds.Height;

            if (preserveAspect)
            {
                float nPercent  = 0;
                float nPercentW = 0;
                float nPercentH = 0;

                nPercentW = ((float)width / (float)w);
                nPercentH = ((float)height / (float)h);

                if (nPercentH < nPercentW)
                {
                    nPercent = nPercentH;
                }
                else
                {
                    nPercent = nPercentW;
                }

                width  = (int)(w * nPercent);
                height = (int)(h * nPercent);
            }

            float scaleX = (float)width / w;
            float scaleY = (float)height / h;

            Bitmap   result = new Bitmap(width, height);
            Graphics g      = Graphics.FromImage(result);

            // transparent background
            g.CompositingMode = CompositingMode.SourceCopy;
            Brush clear = new SolidBrush(Color.Transparent);

            g.FillRectangle(clear, 0, 0, w, h);

            // draw over transparent background
            g.CompositingMode = CompositingMode.SourceOver;
            Pen pen = new Pen(Color.Black, 3);

            foreach (Substroke stroke in shape.Substrokes)
            {
                int count = stroke.Points.Length;

                if (count <= 1)
                {
                    continue;
                }

                Sketch.Point previous = stroke.Points[0];

                for (int i = 1; i < count; i++)
                {
                    Sketch.Point current = stroke.Points[i];
                    g.DrawLine(
                        pen,
                        (previous.X - x) * scaleX,
                        (previous.Y - y) * scaleY,
                        (current.X - x) * scaleX,
                        (current.Y - y) * scaleY);
                    previous = current;
                }
            }

            return(result);
        }
        /// <summary>
        /// Generate the neighborhood graph.
        /// </summary>
        /// <param name="sketch">the sketch to generate the graph for</param>
        /// <param name="radius">the allowable distance one stroke to another</param>
        /// <param name="directed">if the graph should be directed</param>
        /// <param name="context">how many points of context should be used for direction</param>
        /// <returns>a populated NeighborhoodMap</returns>
        private void generateNeighborhood
            (Sketch.Sketch sketch, double radius, bool directed, int context)
        {
            long maxX = 0;

            // points indexed by Y*maxX+X
            Dictionary <long, List <Substroke> > intPoints = new Dictionary <long, List <Substroke> >();

            map    = new Dictionary <Substroke, NeighborList>();
            points = new List <LinkedPoint>();

            /*
             * First we find the maximum X value, which we use to index into
             * the points dictionary.
             */
            foreach (Substroke s in sketch.Substrokes)
            {
                for (int i = 0; i < s.PointsL.Count; i++)
                {
                    Sketch.Point p = s.PointsL[i];
                    if (p.X > maxX)
                    {
                        maxX = (long)p.X;
                    }
                    points.Add(new LinkedPoint(s, i));
                }
            }
            maxX += 1;

            /*
             * Store the location of each point currently in the sketch
             */
            foreach (Substroke s in sketch.Substrokes)
            {
                foreach (Sketch.Point p in s.PointsL)
                {
                    long key = (long)p.Y * maxX;
                    key += (long)p.X;
                    if (!intPoints.ContainsKey(key))
                    {
                        intPoints.Add(key, new List <Substroke>());
                    }
                    intPoints[key].Add(s);
                }
            }

            /*
             * For each substroke, check if any of its points are close to
             * any other points from the sketch.
             */
            foreach (Substroke s in sketch.Substrokes)
            {
                map.Add(s, new NeighborList());

                for (int index = 0; index < s.PointsL.Count; index++)
                {
                    if (index > 10 && index < s.PointsL.Count - 10)
                    {
                        continue;
                    }
                    if ((index == 0 || index == s.PointsL.Count - 1) && directed)
                    {
                        continue;
                    }
                    Sketch.Point p = s.PointsL[index];
                    if (directed && curvature(s, index, 1) - curvature(s, index, 20) > 1)
                    {
                        continue;
                    }

                    for (int i = 0; i < (int)radius; i++)
                    {
                        for (int j = 0; j < (int)radius && Math.Sqrt(i * i + j * j) < radius; j++)
                        {
                            if (i == 0 & j == 0)
                            {
                                continue;
                            }

                            /*
                             * if p is the origin, we need to check the point translated by |i|
                             * in the X-direction and |j| in the Y-direction in each of the 4
                             * quadrants.
                             */
                            long key1 = (long)(p.Y + j) * maxX + (long)(p.X + i);
                            long key2 = (long)(p.Y + j) * maxX + (long)(p.X - i);
                            long key3 = (long)(p.Y - j) * maxX + (long)(p.X + i);
                            long key4 = (long)(p.Y - j) * maxX + (long)(p.X - i);

                            if (intPoints.ContainsKey(key1))
                            {
                                if (!directed || checkDirection(s, index, context, i, j))
                                {
                                    foreach (Substroke test in intPoints[key1])
                                    {
                                        if (test != s)
                                        {
                                            map[s].addPoint(test,
                                                            new System.Drawing.Point((int)(p.X + i), (int)(p.Y + j)),
                                                            new System.Drawing.Point((int)(p.X), (int)(p.Y)));
                                        }
                                    }
                                }
                            }
                            if (intPoints.ContainsKey(key2))
                            {
                                if (!directed || checkDirection(s, index, context, -i, j))
                                {
                                    foreach (Substroke test in intPoints[key2])
                                    {
                                        if (test != s)
                                        {
                                            map[s].addPoint(test,
                                                            new System.Drawing.Point((int)(p.X - i), (int)(p.Y + j)),
                                                            new System.Drawing.Point((int)(p.X), (int)(p.Y)));
                                        }
                                    }
                                }
                            }
                            if (intPoints.ContainsKey(key3))
                            {
                                if (!directed || checkDirection(s, index, context, i, -j))
                                {
                                    foreach (Substroke test in intPoints[key3])
                                    {
                                        if (test != s)
                                        {
                                            map[s].addPoint(test,
                                                            new System.Drawing.Point((int)(p.X + i), (int)(p.Y - j)),
                                                            new System.Drawing.Point((int)(p.X), (int)(p.Y)));
                                        }
                                    }
                                }
                            }
                            if (intPoints.ContainsKey(key4))
                            {
                                if (!directed || checkDirection(s, index, context, -i, -j))
                                {
                                    foreach (Substroke test in intPoints[key4])
                                    {
                                        if (test != s)
                                        {
                                            map[s].addPoint(test,
                                                            new System.Drawing.Point((int)(p.X - i), (int)(p.Y - j)),
                                                            new System.Drawing.Point((int)(p.X), (int)(p.Y)));
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            /*
             * Once we have constructed the Neighbor objects above,
             * the avg Point has the sum of all the points' X- and Y-values.
             * Now that we know the total, we can divide to find the true average.
             */
            foreach (Substroke s in map.Keys)
            {
                for (int j = 0; j < map[s].Count; j++)
                {
                    map[s][j].src.X /= map[s][j].num;
                    map[s][j].src.Y /= map[s][j].num;

                    map[s][j].dest.X /= map[s][j].num;
                    map[s][j].dest.Y /= map[s][j].num;
                }
            }
        }
 internal static double direction(Sketch.Point p1, System.Drawing.Point p2)
 {
     return(direction(p1.Y, p2.Y, p1.X, p2.X));
 }
Beispiel #9
0
        /// <summary>
        /// Takes an Ink.Stroke and outputs an internal representation that can
        /// then be output to an XML file
        /// </summary>
        /// <param name="inkStroke">Ink.Stroke which will have extracted from it the information necessary to store a Stroke in MIT XML.</param>
        /// <param name="transf">Transformation matrix to shift the stroke by</param>
        /// <param name="shift">Boolean for if we should shift by the transformation matrix</param>
        /// <returns>PointsAndShape object that stores the extracted information.</returns>
        private Sketch.Stroke StripStrokeData(Microsoft.Ink.Stroke inkStroke, Matrix transf, bool shift)
        {
            int i;
            int length;

            // Get the timestamp for the function using an undocumented GUID (Microsoft.Ink.StrokeProperty.TimeID) to
            // get the time as a byte array, which we then convert to a long
            long theTime;

            if (this.IsPropertyIncluded(inkStroke, Microsoft.Ink.StrokeProperty.TimeID))
            {
                byte[] timeBits = inkStroke.ExtendedProperties[Microsoft.Ink.StrokeProperty.TimeID].Data as byte[];
                long   fileTime = BitConverter.ToInt64(timeBits, 0);               //filetime format
                theTime = (fileTime - 116444736000000000) / 10000;                 //MIT time format
                // string time = theTime.ToString();
            }
            else
            {
                theTime = DateTime.Now.Ticks;
            }

            // Grab X and Y
            int[] xData = inkStroke.GetPacketValuesByProperty(Microsoft.Ink.PacketProperty.X);
            int[] yData = inkStroke.GetPacketValuesByProperty(Microsoft.Ink.PacketProperty.Y);

            if (shift)
            {
                // Shift X and Y according to transformation matrix
                System.Drawing.Point[] pointData = new System.Drawing.Point [xData.Length];
                length = xData.Length;
                for (i = 0; i < length; i++)
                {
                    pointData[i] = new System.Drawing.Point(xData[i], yData[i]);
                }

                transf.TransformPoints(pointData);
                //Console.WriteLine("x: " + (pointData[0].X - xData[0]) + " y: " + (pointData[0].Y - yData[0]));

                for (i = 0; i < length; i++)
                {
                    xData[i] = pointData[i].X;
                    yData[i] = pointData[i].Y;
                }
            }

            Microsoft.Ink.DrawingAttributes drawingAttributes = inkStroke.DrawingAttributes;
            System.Drawing.Rectangle        boundingBox       = inkStroke.GetBoundingBox();

            // Grab the color
            int color = drawingAttributes.Color.ToArgb();

            // THIS IS DRAWING POINT (PENTIP) HEIGHT AND WIDTH... WE DONT HAVE ANYWHERE TO PUT IT...
            //string height = inkStroke.DrawingAttributes.Height.ToString();
            //string width = inkStroke.DrawingAttributes.Width.ToString();

            // Grab height and width of total stroke
            float height = boundingBox.Height;
            float width  = boundingBox.Width;

            // Grab penTip
            string penTip = drawingAttributes.PenTip.ToString();

            // Grab raster
            string raster = drawingAttributes.RasterOperation.ToString();


            float x = Convert.ToSingle(boundingBox.X);
            float y = Convert.ToSingle(boundingBox.Y);

            float leftx = Convert.ToSingle(boundingBox.Left);
            float topy  = Convert.ToSingle(boundingBox.Top);



            // If the pressure data is included take it, otherwise set to 255/2's
            int[] pressureData;
            if (this.IsPropertyIncluded(inkStroke, Microsoft.Ink.PacketProperty.NormalPressure))
            {
                pressureData = inkStroke.GetPacketValuesByProperty(Microsoft.Ink.PacketProperty.NormalPressure);
            }
            else
            {
                pressureData = new int[xData.Length];
                length       = pressureData.Length;
                for (i = 0; i < length; ++i)
                {
                    pressureData[i] = (255 - 0) / 2;
                }
            }
            // If the time data is included take it, otherwise set to the timestamp plus and increment
            long[] adjustedTime = new long[xData.Length];
            int[]  timerTick;
            if (this.IsPropertyIncluded(inkStroke, Microsoft.Ink.PacketProperty.TimerTick))
            {
                timerTick = inkStroke.GetPacketValuesByProperty(Microsoft.Ink.PacketProperty.TimerTick);
                length    = timerTick.Length;
                for (i = 0; i < length; i++)
                {
                    // This may be incorrect, we never encountered this because Microsoft Journal doesn't save TimerTick data.
                    // We know that the timestamp of a stroke is made when the pen is lifted.
                    adjustedTime[i] = theTime + (long)timerTick[i] * 10000;                   //add the time of the stroke to each offset
                }
            }
            else
            {
                timerTick = new int[xData.Length];
                length    = timerTick.Length;
                for (i = 0; i < length; i++)
                {
                    // We believe this to be the standard sample rate.  The multiplication by 1,000 is to convert from
                    // seconds to milliseconds.
                    //
                    // Our time is in the form of milliseconds since Jan 1, 1970
                    //
                    // NOTE: The timestamp for the stroke is made WHEN THE PEN IS LIFTED
                    adjustedTime[i] = theTime - (long)((1 / SAMPLE_RATE * 1000) * (length - i));
                }
            }

            // Create the array of ID's as new Guid's
            Guid[] idData = new Guid[xData.Length];
            length = idData.Length;
            for (i = 0; i < length; i++)
            {
                idData[i] = Guid.NewGuid();
            }

            // Create the internal representation
            Sketch.Stroke converterStroke = new Sketch.Stroke();
            converterStroke.XmlAttrs.Id     = System.Guid.NewGuid();
            converterStroke.XmlAttrs.Name   = "stroke";
            converterStroke.XmlAttrs.Time   = (ulong)theTime;
            converterStroke.XmlAttrs.Type   = "stroke";
            converterStroke.XmlAttrs.Source = "Converter";

            Sketch.Substroke substroke = new Sketch.Substroke();
            substroke.XmlAttrs.Id     = System.Guid.NewGuid();
            substroke.XmlAttrs.Name   = "substroke";
            substroke.XmlAttrs.Time   = (ulong)theTime;
            substroke.XmlAttrs.Type   = "substroke";
            substroke.XmlAttrs.Color  = color;
            substroke.XmlAttrs.Height = height;
            substroke.XmlAttrs.Width  = width;
            substroke.XmlAttrs.PenTip = penTip;
            substroke.XmlAttrs.Raster = raster;
            substroke.XmlAttrs.X      = x;
            substroke.XmlAttrs.Y      = y;
            substroke.XmlAttrs.LeftX  = leftx;
            substroke.XmlAttrs.TopY   = topy;
            substroke.XmlAttrs.Source = "Converter";
            substroke.XmlAttrs.Start  = idData[0];
            substroke.XmlAttrs.End    = idData[idData.Length - 1];


            // Add all of the points to the stroke
            length = inkStroke.PacketCount;
            for (i = 0; i < length; i++)
            {
                Sketch.Point toAdd = new Sketch.Point();
                toAdd.XmlAttrs.Name     = "point";
                toAdd.XmlAttrs.X        = Convert.ToSingle(xData[i]);
                toAdd.XmlAttrs.Y        = Convert.ToSingle(yData[i]);
                toAdd.XmlAttrs.Pressure = Convert.ToUInt16(pressureData[i]);
                toAdd.XmlAttrs.Time     = Convert.ToUInt64(adjustedTime[i]);
                toAdd.XmlAttrs.Id       = idData[i];

                substroke.AddPoint(toAdd);
            }
            converterStroke.AddSubstroke(substroke);
            return(converterStroke);
        }
        /// <summary>
        /// Takes a wire and returns the shape that is closest to the end of the wire
        /// along the line shooting off of the end.
        /// </summary>
        /// <param name="beginning">Which end we're looking at.</param>
        /// <param name="wire">The wire we're considering.</param>
        /// <param name="sketch">The sketch providing the context for the wire.</param>
        /// <returns>The shape that's closest to the tip of the wire.</returns>
        private Sketch.Shape findClosest(bool beginning, Sketch.Substroke wire, Sketch.Sketch sketch)
        {
            List <Sketch.Substroke> intersects = new List <Sketch.Substroke>();

            // One method of removing hooks (in situations where the substroke loops around to
            // get closer to its endpoint.
            List <Sketch.Point> points = Featurefy.Compute.DeHook(wire).PointsL;

            points.Sort();
            if (!beginning)
            {
                points.Reverse();
            }

            // First we will generate the line segments shooting off the endpoints of the wires.
            Sketch.LineSegment segment1;
            int count = 20;

            if (points.Count < 2 * count)
            {
                segment1 = new Sketch.LineSegment(points[0], points[points.Count - 1]);
            }
            else
            {
                segment1 = new Sketch.LineSegment(points.GetRange(0, count).ToArray());
            }

            Sketch.Substroke closestSubstroke         = null;
            Sketch.Substroke closestLinestroke        = null;
            double           closestSubstrokeDistance = Double.PositiveInfinity;
            double           closestLineDistance      = Double.PositiveInfinity;

            // Now, consider every substroke in the sketch.
            foreach (Sketch.Substroke substroke in sketch.SubstrokesL)
            {
                if (substroke.Id != wire.Id)
                {
                    // We want this stroke if it is close to the line shooting out of the end of the wire
                    // Or if it is close enough to the endpoint.
                    foreach (Sketch.Point point in substroke.Points)
                    {
                        double linedistance  = point.distance(segment1.getClosestPointOnLine(point));
                        double pointdistance = point.distance(points[0]);
                        if (pointdistance < closestSubstrokeDistance)
                        {
                            closestSubstrokeDistance = point.distance(points[0]);
                            closestSubstroke         = substroke;
                        }
                        if (2 * linedistance + pointdistance < closestLineDistance)
                        {
                            // We want to make sure it's on the correct side of the line.
                            Sketch.Point linepoint = segment1.getClosestPointOnLine(point);
                            if ((segment1.StartPoint.X < segment1.EndPoint.X && linepoint.X < segment1.StartPoint.X) ||
                                (segment1.StartPoint.X > segment1.EndPoint.X && linepoint.X > segment1.StartPoint.X))
                            {
                                closestLineDistance = 2 * linedistance + pointdistance;
                                closestLinestroke   = substroke;
                            }
                        }
                    }
                }
            }


            // Returning the right substroke:

            double thresholdLineDistance      = 50.0; // MAGIC NUMBER! A somewhat arbitrary number pulled out of the air.
            double thresholdSubstrokeDistance = 20.0; // MAGIC NUMBER! A somewhat arbitrary number pulled out of the air.

            // The threshold is stricter on substrokes that are nearby but
            // are not in the right direction

            // Substrokes that are close to the projected line get priority.
            if ((closestLineDistance < thresholdLineDistance) &&
                (closestLinestroke != null) &&
                (closestLinestroke.ParentShape != null))
            {
                return(closestLinestroke.ParentShape);
            }

            // If the Linestroke wasn't good enough, try the closest substroke.
            else if ((closestSubstrokeDistance < thresholdSubstrokeDistance) &&
                     (closestSubstroke != null) &&
                     (closestSubstroke.ParentShape != null))
            {
                return(closestSubstroke.ParentShape);
            }

            // We get here if none of the substrokes were close enough
            else
            {
                return(null);
            }


            //// Now we need to decide whether we want the closest substroke, or the one closest to the line...
            //if (closestLineDistance / 5 > closestSubstrokeDistance)
            //    if (closestSubstroke == null)
            //        return null;
            //    else
            //        return closestSubstroke.ParentShapes[0];
            //else
            //    if (closestLinestroke == null)
            //        return null;
            //    else
            //        return closestLinestroke.ParentShapes[0];
        }