// To recognize a scribble we require the simplified line to reverse // direction at least three times. There are separate criteria for // erasing a shape currently being drawn and for erasing existing // shapes. // // The key difference between an "erase scribble" and a "cancel // scribble" is that an erase scribble starts out as such, while // a cancel scribble indicates that the user changed his mind, so // the line will not appear to be a scribble at the beginning. // The difference is detected by timestamps. For example, the // following diagram represents an "erase" operation and a "cancel" // operation. Assume the input points are evenly spaced in time, // and that the dots represent points where the input reversed // direction. // // Input points .......................... // Reversals (erase) . . . . . . // Reversals (cancel) . . . . // // So, a scribble is considered an erasure if it satisfies t0 < t1, // where t0 is the time between mouse-down and the first reversal, // and t1 is the time between the first and third reversals. A cancel // operation satisfies t0 > t1 instead. // // Both kinds of scribble need to satisfy the formula LL*c > CHA, // where c is a constant factor in pixels, LL is the drawn line // length and CHA is the area of the Convex Hull that outlines the // drawn figure. This formula basically detects that the user // is convering the same ground repeatedly; if the pen reverses // direction repeatedly but goes to new places each time, it's not // considered an erasure scribble. For a cancel scribble, LL is // computed starting from the first reversal. IEnumerable <Shape> RecognizeScribbleForEraseOrCancel(DragState state, out bool cancel, out List <PointT> simplifiedSS) { cancel = false; simplifiedSS = null; var tolerance = state._inputTransform.Transform(new VectorT(0, 10)).Length(); var simplifiedMP = LineMath.SimplifyPolyline( state.UnfilteredMousePoints.Select(p => p.Point), tolerance); List <int> reversals = FindReversals(simplifiedMP, 3); if (reversals.Count >= 3) { simplifiedSS = simplifiedMP.Select(p => state._inputTransform.Transform(p)).ToList(); // 3 reversals confirmed. Now decide: erase or cancel? int[] timeStampsMs = FindTimeStamps(state.UnfilteredMousePoints, simplifiedMP); int t0 = timeStampsMs[reversals[0]], t1 = timeStampsMs[reversals[2]] - t0; cancel = t0 > t1 + 500; // Now test the formula LL*c > CHA as explained above IListSource <PointT> simplifiedMP_ = cancel ? simplifiedMP.Slice(reversals[0]) : simplifiedMP.AsListSource(); float LL = simplifiedMP_.AdjacentPairs().Sum(pair => pair.A.Sub(pair.B).Length()); var hull = PointMath.ComputeConvexHull(simplifiedMP); float CHA = PolygonMath.PolygonArea(hull); if (LL * EraseNubWidth > CHA) { // Erasure confirmed. if (cancel) { return(EmptyList <Shape> .Value); } // Figure out which shapes to erase. To do this, we compute for // each shape the amount of the scribble that overlaps that shape. var simplifiedSS_ = simplifiedSS; return(_doc.Shapes.Where(s => ShouldErase(s, simplifiedSS_)).ToList()); } } return(null); }