/// <summary> /// The implementation behind the public methods AddPoint/AddPoints /// </summary> /// <param name="points">a set of points representing the last increment /// in the moving of the erasing shape</param> protected override void AddPointsCore(IEnumerable <Point> points) { System.Diagnostics.Debug.Assert((points != null) && (IEnumerablePointHelper.GetCount(points) != 0)); System.Diagnostics.Debug.Assert(_erasingStroke != null); // Move the shape through the new points and build the contour of the move. _erasingStroke.MoveTo(points); Rect erasingBounds = _erasingStroke.Bounds; if (erasingBounds.IsEmpty) { return; } List <StrokeHitEventArgs> strokeHitEventArgCollection = null; // Do nothing if there's nobody listening to the events if (StrokeHit != null) { List <StrokeIntersection> eraseAt = new List <StrokeIntersection>(); // Test stroke by stroke and collect the results. for (int x = 0; x < this.StrokeInfos.Count; x++) { StrokeInfo strokeInfo = this.StrokeInfos[x]; // Skip the stroke if its bounding box doesn't intersect with the one of the hitting shape. if ((erasingBounds.IntersectsWith(strokeInfo.StrokeBounds) == false) || (_erasingStroke.EraseTest(StrokeNodeIterator.GetIterator(strokeInfo.Stroke, strokeInfo.Stroke.DrawingAttributes), eraseAt) == false)) { continue; } // Create an event args to raise after done with hit-testing // We don't fire these events right away because user is expected to // modify the stroke collection in her event handler, and that would // invalidate this foreach loop. if (strokeHitEventArgCollection == null) { strokeHitEventArgCollection = new List <StrokeHitEventArgs>(); } strokeHitEventArgCollection.Add(new StrokeHitEventArgs(strokeInfo.Stroke, eraseAt.ToArray())); // We must clear eraseAt or it will contain invalid results for the next strokes eraseAt.Clear(); } } // Raise StrokeHit event if needed. if (strokeHitEventArgCollection != null) { System.Diagnostics.Debug.Assert(strokeHitEventArgCollection.Count != 0); for (int x = 0; x < strokeHitEventArgCollection.Count; x++) { StrokeHitEventArgs eventArgs = strokeHitEventArgCollection[x]; System.Diagnostics.Debug.Assert(eventArgs.HitStroke != null); OnStrokeHit(eventArgs); } } }
/// <summary> /// /// </summary> /// <param name="path"></param> /// <param name="stylusShape"></param> /// <returns></returns> public bool HitTest(IEnumerable <Point> path, StylusShape stylusShape) { // Check the input parameters if (path == null) { throw new System.ArgumentNullException("path"); } if (stylusShape == null) { throw new System.ArgumentNullException("stylusShape"); } if (IEnumerablePointHelper.GetCount(path) == 0) { return(false); } ErasingStroke erasingStroke = new ErasingStroke(stylusShape); erasingStroke.MoveTo(path); Rect erasingBounds = erasingStroke.Bounds; if (erasingBounds.IsEmpty) { return(false); } if (erasingBounds.IntersectsWith(this.GetBounds())) { return(erasingStroke.HitTest(StrokeNodeIterator.GetIterator(this, this.DrawingAttributes))); } return(false); }
/// <summary> /// Erases all ink hit by the contour of an erasing stroke /// </summary> /// <param name="eraserShape">Shape of the eraser</param> /// <param name="eraserPath">a path making the spine of the erasing stroke </param> public void Erase(IEnumerable <Point> eraserPath, StylusShape eraserShape) { // Check the input parameters if (eraserShape == null) { throw new System.ArgumentNullException(SR.Get(SRID.SCEraseShape)); } if (eraserPath == null) { throw new System.ArgumentNullException(SR.Get(SRID.SCErasePath)); } if (IEnumerablePointHelper.GetCount(eraserPath) == 0) { return; } ErasingStroke erasingStroke = new ErasingStroke(eraserShape, eraserPath); for (int i = 0; i < this.Count; i++) { Stroke stroke = this[i]; List <StrokeIntersection> intersections = new List <StrokeIntersection>(); erasingStroke.EraseTest(StrokeNodeIterator.GetIterator(stroke, stroke.DrawingAttributes), intersections); StrokeCollection eraseResult = stroke.Erase(intersections.ToArray()); UpdateStrokeCollection(stroke, eraseResult, ref i); } }
internal static Context GetContext(Point[] pointList) { var drawingAttribute = new DrawingAttributes(); var strokeNodeIterator = StrokeNodeIterator.GetIterator(new StylusPointCollection(pointList), drawingAttribute); return(new Context() { StrokeNodeIterator = strokeNodeIterator, DrawingAttributes = drawingAttribute }); }
/// <summary> /// Computes the bounds of the stroke in the default rendering context /// </summary> /// <returns></returns> public virtual Rect GetBounds() { if (_cachedBounds.IsEmpty) { StrokeNodeIterator iterator = StrokeNodeIterator.GetIterator(this, this.DrawingAttributes); for (int i = 0; i < iterator.Count; i++) { StrokeNode strokeNode = iterator[i]; _cachedBounds.Union(strokeNode.GetBounds()); } } return(_cachedBounds); }
/// <summary>Hit tests all segments within a contour generated with shape and path</summary> /// <param name="shape"></param> /// <param name="path"></param> /// <returns>StrokeIntersection array for these segments</returns> internal StrokeIntersection[] EraseTest(IEnumerable <Point> path, StylusShape shape) { System.Diagnostics.Debug.Assert(shape != null); System.Diagnostics.Debug.Assert(path != null); if (IEnumerablePointHelper.GetCount(path) == 0) { return(Array.Empty <StrokeIntersection>()); } ErasingStroke erasingStroke = new ErasingStroke(shape, path); List <StrokeIntersection> intersections = new List <StrokeIntersection>(); erasingStroke.EraseTest(StrokeNodeIterator.GetIterator(this, this.DrawingAttributes), intersections); return(intersections.ToArray()); }
public TextContext GetErasingStroke(Point[] pointList) { var erasingStroke = new ErasingStroke(new RectangleStylusShape(10, 10)); erasingStroke.MoveTo(pointList); var strokeNodeIterator = StrokeNodeIterator.GetIterator(new StylusPointCollection(pointList.Select(temp => new Point(temp.X, temp.Y))), new DrawingAttributes() { Width = 5, Height = 5 }); return(new TextContext(strokeNodeIterator, erasingStroke, new List <StrokeIntersection>(4096), this)); }
/// <summary> /// Hit tests all segments within the lasso loops /// </summary> /// <returns> a StrokeIntersection array for these segments</returns> internal StrokeIntersection[] HitTest(Lasso lasso) { // Check the input parameters System.Diagnostics.Debug.Assert(lasso != null); if (lasso.IsEmpty) { return(Array.Empty <StrokeIntersection>()); } // The following will check whether all the points are within the lasso. // If yes, return the whole stroke as being hit. if (!lasso.Bounds.IntersectsWith(this.GetBounds())) { return(Array.Empty <StrokeIntersection>()); } return(lasso.HitTest(StrokeNodeIterator.GetIterator(this, this.DrawingAttributes))); }
/// <summary> /// Get the Geometry of the Stroke /// </summary> /// <param name="drawingAttributes"></param> /// <returns></returns> public Geometry GetGeometry(DrawingAttributes drawingAttributes) { if (drawingAttributes == null) { throw new ArgumentNullException("drawingAttributes"); } bool geometricallyEqual = DrawingAttributes.GeometricallyEqual(drawingAttributes, this.DrawingAttributes); // need to recalculate the PathGemetry if the DA passed in is "geometrically" different from // this DA, or if the cached PathGeometry is dirty. if (false == geometricallyEqual || (true == geometricallyEqual && null == _cachedGeometry)) { //Recalculate _pathGeometry; StrokeNodeIterator iterator = StrokeNodeIterator.GetIterator(this, drawingAttributes); Geometry geometry; Rect bounds; StrokeRenderer.CalcGeometryAndBounds(iterator, drawingAttributes, #if DEBUG_RENDERING_FEEDBACK null, 0d, false, #endif true, //calc bounds out geometry, out bounds); // return the calculated value directly. We cannot cache the result since the DA passed in // is "geometrically" different from this.DrawingAttributes. if (false == geometricallyEqual) { return(geometry); } // Cache the value and set _isPathGeometryDirty to false; SetGeometry(geometry); SetBounds(bounds); return(geometry); } // return a ref to our _cachedGeometry System.Diagnostics.Debug.Assert(_cachedGeometry != null && _cachedGeometry.IsFrozen); return(_cachedGeometry); }
/// <summary> /// Issue: what's the return value /// </summary> /// <param name="path"></param> /// <param name="stylusShape"></param> /// <returns></returns> public StrokeCollection HitTest(IEnumerable <Point> path, StylusShape stylusShape) { // Check the input parameters if (stylusShape == null) { throw new System.ArgumentNullException("stylusShape"); } if (path == null) { throw new System.ArgumentNullException("path"); } if (IEnumerablePointHelper.GetCount(path) == 0) { return(new StrokeCollection()); } // validate input ErasingStroke erasingStroke = new ErasingStroke(stylusShape, path); Rect erasingBounds = erasingStroke.Bounds; if (erasingBounds.IsEmpty) { return(new StrokeCollection()); } StrokeCollection hits = new StrokeCollection(); foreach (Stroke stroke in this) { // samgeo - Presharp issue // Presharp gives a warning when get methods might deref a null. It's complaining // here that 'stroke'' could be null, but StrokeCollection never allows nulls to be added // so this is not possible #pragma warning disable 1634, 1691 #pragma warning suppress 6506 if (erasingBounds.IntersectsWith(stroke.GetBounds()) && erasingStroke.HitTest(StrokeNodeIterator.GetIterator(stroke, stroke.DrawingAttributes))) { hits.Add(stroke); } #pragma warning restore 1634, 1691 } return(hits); }
public static void CalcGeometryAndBoundsWithTransform() { var drawingAttribute = new DrawingAttributes(); var strokeNodeIterator = StrokeNodeIterator.GetIterator(new StylusPointCollection(new Point[] { new Point(10, 10), new Point(11, 10), new Point(12, 10), new Point(13, 10), new Point(14, 10), new Point(15, 10), new Point(15, 11), new Point(15, 12), new Point(15, 13), new Point(15, 14), new Point(15, 15), new Point(25, 35), new Point(35, 15), new Point(55, 25), }), drawingAttribute); StrokeRenderer.CalcGeometryAndBoundsWithTransform(strokeNodeIterator, drawingAttribute, MatrixTypes.TRANSFORM_IS_IDENTITY, true, out var geometry, out var bounds); }
/// <summary> /// The core functionality to draw a stroke. The function can be called from the following code paths. /// i) From StrokeVisual.OnRender /// a. Highlighter strokes have been grouped and the correct opacity has been set on the container visual. /// b. For a highlighter stroke with color.A != 255, the DA passed in is a copy with color.A set to 255. /// c. _drawAsHollow can be true, i.e., Selected stroke is drawn as hollow /// ii) From StrokeCollection.Draw. /// a. Highlighter strokes have been grouped and the correct opacity has been pushed. /// b. For a highlighter stroke with color.A != 255, the DA passed in is a copy with color.A set to 255. /// c. _drawAsHollow is always false, i.e., Selected stroke is not drawn as hollow /// iii) From Stroke.Draw /// a. The correct opacity has been pushed for a highlighter stroke /// b. For a highlighter stroke with color.A != 255, the DA passed in is a copy with color.A set to 255. /// c. _drawAsHollow is always false, i.e., Selected stroke is not drawn as hollow /// We need to document the following: /// 1) our default implementation so developers can see what we've done here - /// including how we handle IsHollow /// 2) the fact that opacity has already been set up correctly for the call. /// 3) that developers should not call base.DrawCore if they override this /// </summary> /// <param name="drawingContext">DrawingContext to draw on</param> /// <param name="drawingAttributes">DrawingAttributes to draw with</param> protected virtual void DrawCore(DrawingContext drawingContext, DrawingAttributes drawingAttributes) { if (null == drawingContext) { throw new System.ArgumentNullException("drawingContext"); } if (null == drawingAttributes) { throw new System.ArgumentNullException("drawingAttributes"); } if (_drawAsHollow == true) { // Draw as hollow. Our profiler result shows that the two-pass-rendering approach is about 5 times // faster that using GetOutlinePathGeometry. // also, the minimum display size for selected ink is our default width / height Matrix innerTransform, outerTransform; DrawingAttributes selectedDA = drawingAttributes.Clone(); selectedDA.Height = Math.Max(selectedDA.Height, DrawingAttributes.DefaultHeight); selectedDA.Width = Math.Max(selectedDA.Width, DrawingAttributes.DefaultWidth); CalcHollowTransforms(selectedDA, out innerTransform, out outerTransform); // First pass drawing. Use drawingAttributes.Color to create a solid color brush. The stroke will be drawn as // 1 avalon-unit higher and wider (HollowLineSize = 1.0f) selectedDA.StylusTipTransform = outerTransform; SolidColorBrush brush = new SolidColorBrush(drawingAttributes.Color); brush.Freeze(); drawingContext.DrawGeometry(brush, null, GetGeometry(selectedDA)); //Second pass drawing with a white color brush. The stroke will be drawn as // 1 avalon-unit shorter and narrower (HollowLineSize = 1.0f) if the actual-width/height (considering StylusTipTransform) // is larger than HollowLineSize. Otherwise the same size stroke is drawn. selectedDA.StylusTipTransform = innerTransform; drawingContext.DrawGeometry(Brushes.White, null, GetGeometry(selectedDA)); } else { #if DEBUG_RENDERING_FEEDBACK //render debug feedback? Guid guid = new Guid("52053C24-CBDD-4547-AAA1-DEFEBF7FD1E1"); if (this.ContainsPropertyData(guid)) { double thickness = (double)this.GetPropertyData(guid); //first, draw the outline of the stroke drawingContext.DrawGeometry(null, new Pen(Brushes.Black, thickness), GetGeometry()); Geometry g2; Rect b2; //next, overlay the connecting quad points StrokeRenderer.CalcGeometryAndBounds(StrokeNodeIterator.GetIterator(this, drawingAttributes), drawingAttributes, drawingContext, thickness, true, true, //calc bounds out g2, out b2); } else { #endif SolidColorBrush brush = new SolidColorBrush(drawingAttributes.Color); brush.Freeze(); drawingContext.DrawGeometry(brush, null, GetGeometry(drawingAttributes)); #if DEBUG_RENDERING_FEEDBACK } #endif } }