Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / wpf / src / Core / CSharp / System / Windows / Ink / Stroke2.cs / 1 / Stroke2.cs
//#define DEBUG_RENDERING_FEEDBACK //------------------------------------------------------------------------ //// Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------- using MS.Utility; using System; using System.ComponentModel; using System.Windows; using System.Windows.Input; using System.Windows.Media; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using MS.Internal; using MS.Internal.Ink; using SR=MS.Internal.PresentationCore.SR; using SRID=MS.Internal.PresentationCore.SRID; using MS.Internal.PresentationCore; // Primary root namespace for TabletPC/Ink/Handwriting/Recognition in .NET namespace System.Windows.Ink { ////// The hit-testing API of Stroke /// public partial class Stroke : INotifyPropertyChanged { #region Public APIs #region Public Methods ////// Computes the bounds of the stroke in the default rendering context /// ///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; } /// /// Render the Stroke under the specified DrawingContext. The draw method is a /// batch operationg that uses the rendering methods exposed off of DrawingContext /// /// public void Draw(DrawingContext context) { if (null == context) { throw new System.ArgumentNullException("context"); } //our code never calls this public API so we can assume that opacity //has not been set up //call our public Draw method with the strokes.DA this.Draw(context, this.DrawingAttributes); } ////// Render the StrokeCollection under the specified DrawingContext. This draw method uses the /// passing in drawing attribute to override that on the stroke. /// /// /// public void Draw(DrawingContext drawingContext, DrawingAttributes drawingAttributes) { if (null == drawingContext) { throw new System.ArgumentNullException("context"); } if (null == drawingAttributes) { throw new System.ArgumentNullException("drawingAttributes"); } // context.VerifyAccess(); //our code never calls this public API so we can assume that opacity //has not been set up if (drawingAttributes.IsHighlighter) { drawingContext.PushOpacity(StrokeRenderer.HighlighterOpacity); try { this.DrawInternal(drawingContext, StrokeRenderer.GetHighlighterAttributes(this, this.DrawingAttributes), false); } finally { drawingContext.Pop(); } } else { this.DrawInternal(drawingContext, drawingAttributes, false); } } ////// Clip with rect. Calculate the after-clipping Strokes. Only the "in-segments" are left after this operation. /// /// A Rect to clip with ///The after-clipping strokes. public StrokeCollection GetClipResult(Rect bounds) { return this.GetClipResult(new Point[4] { bounds.TopLeft, bounds.TopRight, bounds.BottomRight, bounds.BottomLeft }); } ////// Clip with lasso. Calculate the after-clipping Strokes. Only the "in-segments" are left after this operation. /// /// The lasso points to clip with ///The after-clipping strokes public StrokeCollection GetClipResult(IEnumerablelassoPoints) { // Check the input parameters if (lassoPoints == null) { throw new System.ArgumentNullException("lassoPoints"); } if (IEnumerablePointHelper.GetCount(lassoPoints) == 0) { throw new ArgumentException(SR.Get(SRID.EmptyArray)); } Lasso lasso = new SingleLoopLasso(); lasso.AddPoints(lassoPoints); return this.Clip(this.HitTest(lasso)); } /// /// Erase with a rect. Calculate the after-erasing Strokes. Only the "out-segments" are left after this operation. /// /// A Rect to clip with ///The after-erasing strokes public StrokeCollection GetEraseResult(Rect bounds) { return this.GetEraseResult(new Point[4] { bounds.TopLeft, bounds.TopRight, bounds.BottomRight, bounds.BottomLeft }); } ////// Erase with lasso points. /// /// Lasso points to erase with ///The after-erasing strokes public StrokeCollection GetEraseResult(IEnumerablelassoPoints) { // Check the input parameters if (lassoPoints == null) { throw new System.ArgumentNullException("lassoPoints"); } if (IEnumerablePointHelper.GetCount(lassoPoints) == 0) { throw new ArgumentException(SR.Get(SRID.EmptyArray)); } Lasso lasso = new SingleLoopLasso(); lasso.AddPoints(lassoPoints); return this.Erase(this.HitTest(lasso)); } /// /// Erase with an eraser with passed in shape /// /// The path to erase /// Shape of the eraser ///public StrokeCollection GetEraseResult(IEnumerable eraserPath, StylusShape eraserShape) { // Check the input parameters if (eraserShape == null) { throw new System.ArgumentNullException("eraserShape"); } if (eraserPath == null) { throw new System.ArgumentNullException("eraserPath"); } return this.Erase(this.EraseTest(eraserPath, eraserShape)); } /// /// Tap-hit. Hit tests with a point. Internally does Stroke.HitTest(Point, 1pxlRectShape). /// /// The location to do the hitest ///True is this stroke is hit, false otherwise public bool HitTest(Point point) { return HitTest(new Point[]{point}, new EllipseStylusShape(TapHitPointSize, TapHitPointSize, TapHitRotation)); } ////// Tap-hit. Hit tests with a point. /// /// The location to do the hittest /// diameter of the tip ///true if hit, false otherwise public bool HitTest(Point point, double diameter) { if (Double.IsNaN(diameter) || diameter < DrawingAttributes.MinWidth || diameter > DrawingAttributes.MaxWidth) { throw new ArgumentOutOfRangeException("diameter", SR.Get(SRID.InvalidDiameter)); } return HitTest(new Point[]{point}, new EllipseStylusShape(diameter, diameter, TapHitRotation)); } ////// Check whether a certain percentage of the stroke is within the Rect passed in. /// /// /// ///public bool HitTest(Rect bounds, int percentageWithinBounds) { if ((percentageWithinBounds < 0) || (percentageWithinBounds > 100)) { throw new System.ArgumentOutOfRangeException("percentageWithinBounds"); } if (percentageWithinBounds == 0) { return true; } StrokeInfo strokeInfo = null; try { strokeInfo = new StrokeInfo(this); StylusPointCollection stylusPoints = strokeInfo.StylusPoints; double target = strokeInfo.TotalWeight * percentageWithinBounds / 100.0f - PercentageTolerance; for (int i = 0; i < stylusPoints.Count; i++) { if (true == bounds.Contains((Point)stylusPoints[i])) { target -= strokeInfo.GetPointWeight(i); if (DoubleUtil.LessThanOrClose(target, 0d)) { return true; } } } return false; } finally { if (strokeInfo != null) { //detach from event handlers, or else we leak. strokeInfo.Detach(); } } } /// /// Check whether a certain percentage of the stroke is within the lasso /// /// /// ///public bool HitTest(IEnumerable lassoPoints, int percentageWithinLasso) { if (lassoPoints == null) { throw new System.ArgumentNullException("lassoPoints"); } if ((percentageWithinLasso < 0) || (percentageWithinLasso > 100)) { throw new System.ArgumentOutOfRangeException("percentageWithinLasso"); } if (percentageWithinLasso == 0) { return true; } StrokeInfo strokeInfo = null; try { strokeInfo = new StrokeInfo(this); StylusPointCollection stylusPoints = strokeInfo.StylusPoints; double target = strokeInfo.TotalWeight * percentageWithinLasso / 100.0f - PercentageTolerance; Lasso lasso = new SingleLoopLasso(); lasso.AddPoints(lassoPoints); for (int i = 0; i < stylusPoints.Count; i++) { if (true == lasso.Contains((Point)stylusPoints[i])) { target -= strokeInfo.GetPointWeight(i); if (DoubleUtil.LessThan(target, 0f)) { return true; } } } return false; } finally { if (strokeInfo != null) { //detach from event handlers, or else we leak. strokeInfo.Detach(); } } } /// /// /// /// /// ///public bool HitTest(IEnumerable 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; } #endregion #endregion #region Protected APIs /// /// 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 /// /// DrawingContext to draw on /// DrawingAttributes to draw with 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 } } ////// Returns the Geometry of this stroke. /// ///public Geometry GetGeometry() { return GetGeometry(this.DrawingAttributes); } /// /// Get the Geometry of the Stroke /// /// ///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; } #endregion #region Internal APIs /// /// our code - StrokeVisual.OnRender and StrokeCollection.Draw - always calls this /// so we can assume the correct opacity has already been pushed on dc. The flag drawAsHollow is set /// to true when this function is called from Renderer and this.IsSelected == true. /// [FriendAccessAllowed] // Built into Core, also used by Framework. internal void DrawInternal(DrawingContext dc, DrawingAttributes DrawingAttributes, bool drawAsHollow) { if (drawAsHollow == true) { // The Stroke.DrawCore may be overriden in the 3rd party code. // The out-side code could throw exception. We use try/finally block to protect our status. try { _drawAsHollow = true; // temporarily set the flag to be true this.DrawCore(dc, DrawingAttributes); } finally { _drawAsHollow = false; // reset _drawAsHollow } } else { // IsSelected can be true or false, but _drawAsHollow must be false System.Diagnostics.Debug.Assert(false == _drawAsHollow); this.DrawCore(dc, DrawingAttributes); } } ////// Used by Inkcanvas to draw selected stroke as hollow. /// [FriendAccessAllowed] // Built into Core, also used by Framework. internal bool IsSelected { get { return _isSelected; } set { if (value != _isSelected) { _isSelected = value; // Raise Invalidated event. This will cause Renderer to repaint and call back DrawCore OnInvalidated(EventArgs.Empty); } } } ////// Set the path geometry /// internal void SetGeometry(Geometry geometry) { System.Diagnostics.Debug.Assert(geometry != null); _cachedGeometry = geometry; } ////// Set the bounds /// internal void SetBounds(Rect newBounds) { System.Diagnostics.Debug.Assert(newBounds.IsEmpty == false); _cachedBounds = newBounds; } ///Hit tests all segments within a contour generated with shape and path /// /// ///StrokeIntersection array for these segments internal StrokeIntersection[] EraseTest(IEnumerablepath, StylusShape shape) { System.Diagnostics.Debug.Assert(shape != null); System.Diagnostics.Debug.Assert(path != null); if (IEnumerablePointHelper.GetCount(path) == 0) { return new StrokeIntersection[0]; } ErasingStroke erasingStroke = new ErasingStroke(shape, path); List intersections = new List (); erasingStroke.EraseTest(StrokeNodeIterator.GetIterator(this, this.DrawingAttributes), intersections); return intersections.ToArray(); } /// /// Hit tests all segments within the lasso loops /// ///a StrokeIntersection array for these segments internal StrokeIntersection[] HitTest(Lasso lasso) { // Check the input parameters System.Diagnostics.Debug.Assert(lasso != null); if (lasso.IsEmpty) { return new StrokeIntersection[0]; } // 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 new StrokeIntersection[0]; } return lasso.HitTest(StrokeNodeIterator.GetIterator(this, this.DrawingAttributes)); } ////// Calculate the after-erasing Strokes. Only the "out-segments" are left after this operation. /// /// Array of intersections indicating the erasing locations ///internal StrokeCollection Erase(StrokeIntersection[] cutAt) { System.Diagnostics.Debug.Assert(cutAt != null); // Nothing needs to be erased if(cutAt.Length == 0) { StrokeCollection strokes = new StrokeCollection(); strokes.Add(this.Clone()); //clip and erase always return clones for this condition return strokes; } // Two assertions are deferred to the private erase function to avoid duplicate code. // 1. AssertSortedNoOverlap // 2. Check whether the insegments are out of range with the packets StrokeFIndices[] hitSegments = StrokeIntersection.GetHitSegments(cutAt); return this.Erase(hitSegments); } /// /// Calculate the after-clipping Strokes. Only the "in-segments" are left after this operation. /// /// Array of intersections indicating the clipping locations ///The resulting StrokeCollection internal StrokeCollection Clip(StrokeIntersection[] cutAt) { System.Diagnostics.Debug.Assert(cutAt != null); // Nothing is inside if (cutAt.Length == 0) { return new StrokeCollection(); } // Get the "in-segments" StrokeFIndices[] inSegments = StrokeIntersection.GetInSegments(cutAt); // For special case like cutAt is {BF, AL, BF, 0.67}, the inSegments are empty if (inSegments.Length == 0) { return new StrokeCollection(); } // Two other validations are deferred to the private clip function to avoid duplicate code. // 1. ValidateSortedNoOverlap // 2. Check whether the insegments are out of range with the packets return this.Clip(inSegments); } internal double TapHitPointSize = 1.0; internal double TapHitRotation = 0; #endregion #region Private APIs ////// Calculate the two transforms for two-pass rendering used to draw as hollow. The resulting outerTransform will make the /// first-pass-rendering 1 avalon-unit wider/heigher. The resulting innerTransform will make the second-pass-rendering 1 avalon-unit /// narrower/shorter. /// private static void CalcHollowTransforms(DrawingAttributes originalDa, out Matrix innerTransform, out Matrix outerTransform) { System.Diagnostics.Debug.Assert(DoubleUtil.IsZero(originalDa.StylusTipTransform.OffsetX) && DoubleUtil.IsZero(originalDa.StylusTipTransform.OffsetY)); innerTransform = outerTransform = Matrix.Identity; Point w = originalDa.StylusTipTransform.Transform(new Point(originalDa.Width, 0)); Point h = originalDa.StylusTipTransform.Transform(new Point(0, originalDa.Height)); // the newWidth and newHeight are the actual width/height of the stylus shape considering StylusTipTransform. // The assumption is TylusTipTransform has no translation component. double newWidth = Math.Sqrt(w.X * w.X + w.Y * w.Y); double newHeight = Math.Sqrt(h.X * h.X + h.Y * h.Y); double xTransform = DoubleUtil.GreaterThan(newWidth, HollowLineSize) ? (newWidth - HollowLineSize) / newWidth : 1.0f; double yTransform = DoubleUtil.GreaterThan(newHeight, HollowLineSize) ? (newHeight - HollowLineSize) / newHeight : 1.0f; innerTransform.Scale(xTransform, yTransform); innerTransform *= originalDa.StylusTipTransform; outerTransform.Scale((newWidth + HollowLineSize) / newWidth, (newHeight + HollowLineSize) / newHeight); outerTransform *= originalDa.StylusTipTransform; } #region Private fields private Geometry _cachedGeometry = null; private bool _isSelected = false; private bool _drawAsHollow = false; private bool _cloneStylusPoints = true; private bool _delayRaiseInvalidated = false; private static readonly double HollowLineSize = 1.0f; private Rect _cachedBounds = Rect.Empty; // The private PropertyChanged event private PropertyChangedEventHandler _propertyChanged; private const string DrawingAttributesName = "DrawingAttributes"; private const string StylusPointsName = "StylusPoints"; #endregion internal static readonly double PercentageTolerance = 0.0001d; #endregion } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. //#define DEBUG_RENDERING_FEEDBACK //------------------------------------------------------------------------ //// Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------- using MS.Utility; using System; using System.ComponentModel; using System.Windows; using System.Windows.Input; using System.Windows.Media; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using MS.Internal; using MS.Internal.Ink; using SR=MS.Internal.PresentationCore.SR; using SRID=MS.Internal.PresentationCore.SRID; using MS.Internal.PresentationCore; // Primary root namespace for TabletPC/Ink/Handwriting/Recognition in .NET namespace System.Windows.Ink { ////// The hit-testing API of Stroke /// public partial class Stroke : INotifyPropertyChanged { #region Public APIs #region Public Methods ////// Computes the bounds of the stroke in the default rendering context /// ///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; } /// /// Render the Stroke under the specified DrawingContext. The draw method is a /// batch operationg that uses the rendering methods exposed off of DrawingContext /// /// public void Draw(DrawingContext context) { if (null == context) { throw new System.ArgumentNullException("context"); } //our code never calls this public API so we can assume that opacity //has not been set up //call our public Draw method with the strokes.DA this.Draw(context, this.DrawingAttributes); } ////// Render the StrokeCollection under the specified DrawingContext. This draw method uses the /// passing in drawing attribute to override that on the stroke. /// /// /// public void Draw(DrawingContext drawingContext, DrawingAttributes drawingAttributes) { if (null == drawingContext) { throw new System.ArgumentNullException("context"); } if (null == drawingAttributes) { throw new System.ArgumentNullException("drawingAttributes"); } // context.VerifyAccess(); //our code never calls this public API so we can assume that opacity //has not been set up if (drawingAttributes.IsHighlighter) { drawingContext.PushOpacity(StrokeRenderer.HighlighterOpacity); try { this.DrawInternal(drawingContext, StrokeRenderer.GetHighlighterAttributes(this, this.DrawingAttributes), false); } finally { drawingContext.Pop(); } } else { this.DrawInternal(drawingContext, drawingAttributes, false); } } ////// Clip with rect. Calculate the after-clipping Strokes. Only the "in-segments" are left after this operation. /// /// A Rect to clip with ///The after-clipping strokes. public StrokeCollection GetClipResult(Rect bounds) { return this.GetClipResult(new Point[4] { bounds.TopLeft, bounds.TopRight, bounds.BottomRight, bounds.BottomLeft }); } ////// Clip with lasso. Calculate the after-clipping Strokes. Only the "in-segments" are left after this operation. /// /// The lasso points to clip with ///The after-clipping strokes public StrokeCollection GetClipResult(IEnumerablelassoPoints) { // Check the input parameters if (lassoPoints == null) { throw new System.ArgumentNullException("lassoPoints"); } if (IEnumerablePointHelper.GetCount(lassoPoints) == 0) { throw new ArgumentException(SR.Get(SRID.EmptyArray)); } Lasso lasso = new SingleLoopLasso(); lasso.AddPoints(lassoPoints); return this.Clip(this.HitTest(lasso)); } /// /// Erase with a rect. Calculate the after-erasing Strokes. Only the "out-segments" are left after this operation. /// /// A Rect to clip with ///The after-erasing strokes public StrokeCollection GetEraseResult(Rect bounds) { return this.GetEraseResult(new Point[4] { bounds.TopLeft, bounds.TopRight, bounds.BottomRight, bounds.BottomLeft }); } ////// Erase with lasso points. /// /// Lasso points to erase with ///The after-erasing strokes public StrokeCollection GetEraseResult(IEnumerablelassoPoints) { // Check the input parameters if (lassoPoints == null) { throw new System.ArgumentNullException("lassoPoints"); } if (IEnumerablePointHelper.GetCount(lassoPoints) == 0) { throw new ArgumentException(SR.Get(SRID.EmptyArray)); } Lasso lasso = new SingleLoopLasso(); lasso.AddPoints(lassoPoints); return this.Erase(this.HitTest(lasso)); } /// /// Erase with an eraser with passed in shape /// /// The path to erase /// Shape of the eraser ///public StrokeCollection GetEraseResult(IEnumerable eraserPath, StylusShape eraserShape) { // Check the input parameters if (eraserShape == null) { throw new System.ArgumentNullException("eraserShape"); } if (eraserPath == null) { throw new System.ArgumentNullException("eraserPath"); } return this.Erase(this.EraseTest(eraserPath, eraserShape)); } /// /// Tap-hit. Hit tests with a point. Internally does Stroke.HitTest(Point, 1pxlRectShape). /// /// The location to do the hitest ///True is this stroke is hit, false otherwise public bool HitTest(Point point) { return HitTest(new Point[]{point}, new EllipseStylusShape(TapHitPointSize, TapHitPointSize, TapHitRotation)); } ////// Tap-hit. Hit tests with a point. /// /// The location to do the hittest /// diameter of the tip ///true if hit, false otherwise public bool HitTest(Point point, double diameter) { if (Double.IsNaN(diameter) || diameter < DrawingAttributes.MinWidth || diameter > DrawingAttributes.MaxWidth) { throw new ArgumentOutOfRangeException("diameter", SR.Get(SRID.InvalidDiameter)); } return HitTest(new Point[]{point}, new EllipseStylusShape(diameter, diameter, TapHitRotation)); } ////// Check whether a certain percentage of the stroke is within the Rect passed in. /// /// /// ///public bool HitTest(Rect bounds, int percentageWithinBounds) { if ((percentageWithinBounds < 0) || (percentageWithinBounds > 100)) { throw new System.ArgumentOutOfRangeException("percentageWithinBounds"); } if (percentageWithinBounds == 0) { return true; } StrokeInfo strokeInfo = null; try { strokeInfo = new StrokeInfo(this); StylusPointCollection stylusPoints = strokeInfo.StylusPoints; double target = strokeInfo.TotalWeight * percentageWithinBounds / 100.0f - PercentageTolerance; for (int i = 0; i < stylusPoints.Count; i++) { if (true == bounds.Contains((Point)stylusPoints[i])) { target -= strokeInfo.GetPointWeight(i); if (DoubleUtil.LessThanOrClose(target, 0d)) { return true; } } } return false; } finally { if (strokeInfo != null) { //detach from event handlers, or else we leak. strokeInfo.Detach(); } } } /// /// Check whether a certain percentage of the stroke is within the lasso /// /// /// ///public bool HitTest(IEnumerable lassoPoints, int percentageWithinLasso) { if (lassoPoints == null) { throw new System.ArgumentNullException("lassoPoints"); } if ((percentageWithinLasso < 0) || (percentageWithinLasso > 100)) { throw new System.ArgumentOutOfRangeException("percentageWithinLasso"); } if (percentageWithinLasso == 0) { return true; } StrokeInfo strokeInfo = null; try { strokeInfo = new StrokeInfo(this); StylusPointCollection stylusPoints = strokeInfo.StylusPoints; double target = strokeInfo.TotalWeight * percentageWithinLasso / 100.0f - PercentageTolerance; Lasso lasso = new SingleLoopLasso(); lasso.AddPoints(lassoPoints); for (int i = 0; i < stylusPoints.Count; i++) { if (true == lasso.Contains((Point)stylusPoints[i])) { target -= strokeInfo.GetPointWeight(i); if (DoubleUtil.LessThan(target, 0f)) { return true; } } } return false; } finally { if (strokeInfo != null) { //detach from event handlers, or else we leak. strokeInfo.Detach(); } } } /// /// /// /// /// ///public bool HitTest(IEnumerable 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; } #endregion #endregion #region Protected APIs /// /// 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 /// /// DrawingContext to draw on /// DrawingAttributes to draw with 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 } } ////// Returns the Geometry of this stroke. /// ///public Geometry GetGeometry() { return GetGeometry(this.DrawingAttributes); } /// /// Get the Geometry of the Stroke /// /// ///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; } #endregion #region Internal APIs /// /// our code - StrokeVisual.OnRender and StrokeCollection.Draw - always calls this /// so we can assume the correct opacity has already been pushed on dc. The flag drawAsHollow is set /// to true when this function is called from Renderer and this.IsSelected == true. /// [FriendAccessAllowed] // Built into Core, also used by Framework. internal void DrawInternal(DrawingContext dc, DrawingAttributes DrawingAttributes, bool drawAsHollow) { if (drawAsHollow == true) { // The Stroke.DrawCore may be overriden in the 3rd party code. // The out-side code could throw exception. We use try/finally block to protect our status. try { _drawAsHollow = true; // temporarily set the flag to be true this.DrawCore(dc, DrawingAttributes); } finally { _drawAsHollow = false; // reset _drawAsHollow } } else { // IsSelected can be true or false, but _drawAsHollow must be false System.Diagnostics.Debug.Assert(false == _drawAsHollow); this.DrawCore(dc, DrawingAttributes); } } ////// Used by Inkcanvas to draw selected stroke as hollow. /// [FriendAccessAllowed] // Built into Core, also used by Framework. internal bool IsSelected { get { return _isSelected; } set { if (value != _isSelected) { _isSelected = value; // Raise Invalidated event. This will cause Renderer to repaint and call back DrawCore OnInvalidated(EventArgs.Empty); } } } ////// Set the path geometry /// internal void SetGeometry(Geometry geometry) { System.Diagnostics.Debug.Assert(geometry != null); _cachedGeometry = geometry; } ////// Set the bounds /// internal void SetBounds(Rect newBounds) { System.Diagnostics.Debug.Assert(newBounds.IsEmpty == false); _cachedBounds = newBounds; } ///Hit tests all segments within a contour generated with shape and path /// /// ///StrokeIntersection array for these segments internal StrokeIntersection[] EraseTest(IEnumerablepath, StylusShape shape) { System.Diagnostics.Debug.Assert(shape != null); System.Diagnostics.Debug.Assert(path != null); if (IEnumerablePointHelper.GetCount(path) == 0) { return new StrokeIntersection[0]; } ErasingStroke erasingStroke = new ErasingStroke(shape, path); List intersections = new List (); erasingStroke.EraseTest(StrokeNodeIterator.GetIterator(this, this.DrawingAttributes), intersections); return intersections.ToArray(); } /// /// Hit tests all segments within the lasso loops /// ///a StrokeIntersection array for these segments internal StrokeIntersection[] HitTest(Lasso lasso) { // Check the input parameters System.Diagnostics.Debug.Assert(lasso != null); if (lasso.IsEmpty) { return new StrokeIntersection[0]; } // 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 new StrokeIntersection[0]; } return lasso.HitTest(StrokeNodeIterator.GetIterator(this, this.DrawingAttributes)); } ////// Calculate the after-erasing Strokes. Only the "out-segments" are left after this operation. /// /// Array of intersections indicating the erasing locations ///internal StrokeCollection Erase(StrokeIntersection[] cutAt) { System.Diagnostics.Debug.Assert(cutAt != null); // Nothing needs to be erased if(cutAt.Length == 0) { StrokeCollection strokes = new StrokeCollection(); strokes.Add(this.Clone()); //clip and erase always return clones for this condition return strokes; } // Two assertions are deferred to the private erase function to avoid duplicate code. // 1. AssertSortedNoOverlap // 2. Check whether the insegments are out of range with the packets StrokeFIndices[] hitSegments = StrokeIntersection.GetHitSegments(cutAt); return this.Erase(hitSegments); } /// /// Calculate the after-clipping Strokes. Only the "in-segments" are left after this operation. /// /// Array of intersections indicating the clipping locations ///The resulting StrokeCollection internal StrokeCollection Clip(StrokeIntersection[] cutAt) { System.Diagnostics.Debug.Assert(cutAt != null); // Nothing is inside if (cutAt.Length == 0) { return new StrokeCollection(); } // Get the "in-segments" StrokeFIndices[] inSegments = StrokeIntersection.GetInSegments(cutAt); // For special case like cutAt is {BF, AL, BF, 0.67}, the inSegments are empty if (inSegments.Length == 0) { return new StrokeCollection(); } // Two other validations are deferred to the private clip function to avoid duplicate code. // 1. ValidateSortedNoOverlap // 2. Check whether the insegments are out of range with the packets return this.Clip(inSegments); } internal double TapHitPointSize = 1.0; internal double TapHitRotation = 0; #endregion #region Private APIs ////// Calculate the two transforms for two-pass rendering used to draw as hollow. The resulting outerTransform will make the /// first-pass-rendering 1 avalon-unit wider/heigher. The resulting innerTransform will make the second-pass-rendering 1 avalon-unit /// narrower/shorter. /// private static void CalcHollowTransforms(DrawingAttributes originalDa, out Matrix innerTransform, out Matrix outerTransform) { System.Diagnostics.Debug.Assert(DoubleUtil.IsZero(originalDa.StylusTipTransform.OffsetX) && DoubleUtil.IsZero(originalDa.StylusTipTransform.OffsetY)); innerTransform = outerTransform = Matrix.Identity; Point w = originalDa.StylusTipTransform.Transform(new Point(originalDa.Width, 0)); Point h = originalDa.StylusTipTransform.Transform(new Point(0, originalDa.Height)); // the newWidth and newHeight are the actual width/height of the stylus shape considering StylusTipTransform. // The assumption is TylusTipTransform has no translation component. double newWidth = Math.Sqrt(w.X * w.X + w.Y * w.Y); double newHeight = Math.Sqrt(h.X * h.X + h.Y * h.Y); double xTransform = DoubleUtil.GreaterThan(newWidth, HollowLineSize) ? (newWidth - HollowLineSize) / newWidth : 1.0f; double yTransform = DoubleUtil.GreaterThan(newHeight, HollowLineSize) ? (newHeight - HollowLineSize) / newHeight : 1.0f; innerTransform.Scale(xTransform, yTransform); innerTransform *= originalDa.StylusTipTransform; outerTransform.Scale((newWidth + HollowLineSize) / newWidth, (newHeight + HollowLineSize) / newHeight); outerTransform *= originalDa.StylusTipTransform; } #region Private fields private Geometry _cachedGeometry = null; private bool _isSelected = false; private bool _drawAsHollow = false; private bool _cloneStylusPoints = true; private bool _delayRaiseInvalidated = false; private static readonly double HollowLineSize = 1.0f; private Rect _cachedBounds = Rect.Empty; // The private PropertyChanged event private PropertyChangedEventHandler _propertyChanged; private const string DrawingAttributesName = "DrawingAttributes"; private const string StylusPointsName = "StylusPoints"; #endregion internal static readonly double PercentageTolerance = 0.0001d; #endregion } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- RemotingAttributes.cs
- ResourceDefaultValueAttribute.cs
- TextDecorations.cs
- EUCJPEncoding.cs
- BasicViewGenerator.cs
- HwndSourceParameters.cs
- BrowserDefinition.cs
- XmlSchemaImporter.cs
- StoreContentChangedEventArgs.cs
- AttributeCallbackBuilder.cs
- Collection.cs
- MdbDataFileEditor.cs
- CurrentTimeZone.cs
- DataGridViewHitTestInfo.cs
- Mappings.cs
- HighContrastHelper.cs
- CachingHintValidation.cs
- EntityDataSourceQueryBuilder.cs
- RijndaelManagedTransform.cs
- ModelVisual3D.cs
- ChineseLunisolarCalendar.cs
- AccessibilityApplicationManager.cs
- BitmapSourceSafeMILHandle.cs
- SmiMetaData.cs
- LocatorPart.cs
- FileAuthorizationModule.cs
- MenuItem.cs
- CLRBindingWorker.cs
- ColorMatrix.cs
- Thumb.cs
- IndexedGlyphRun.cs
- precedingsibling.cs
- ImageList.cs
- HttpCacheParams.cs
- ConnectionPointConverter.cs
- TimeSpanMinutesConverter.cs
- MappingModelBuildProvider.cs
- Overlapped.cs
- X500Name.cs
- SizeLimitedCache.cs
- OptimisticConcurrencyException.cs
- XPathNodeHelper.cs
- EditCommandColumn.cs
- KeyValueConfigurationCollection.cs
- ProviderIncompatibleException.cs
- SiteMapNodeItem.cs
- OrderByQueryOptionExpression.cs
- ExpressionEditorAttribute.cs
- AttributeSetAction.cs
- OutputCacheSettings.cs
- SchemaImporterExtensionElement.cs
- HtmlEmptyTagControlBuilder.cs
- KnownTypeAttribute.cs
- RangeExpression.cs
- FtpRequestCacheValidator.cs
- SymLanguageVendor.cs
- PasswordDeriveBytes.cs
- TypeInfo.cs
- NameObjectCollectionBase.cs
- EventLogQuery.cs
- WinEventQueueItem.cs
- UrlAuthorizationModule.cs
- NameValuePermission.cs
- URLIdentityPermission.cs
- SchemaImporterExtensionElementCollection.cs
- DataColumnPropertyDescriptor.cs
- DecoratedNameAttribute.cs
- URIFormatException.cs
- AppDomainManager.cs
- ADMembershipProvider.cs
- MenuItem.cs
- ErrorRuntimeConfig.cs
- TextEditorThreadLocalStore.cs
- CompareInfo.cs
- Scene3D.cs
- SQLConvert.cs
- XamlFilter.cs
- DataTable.cs
- BufferModeSettings.cs
- StateFinalizationActivity.cs
- TableRowGroup.cs
- Errors.cs
- prompt.cs
- Missing.cs
- CssStyleCollection.cs
- SaveFileDialog.cs
- HtmlShim.cs
- DbDataSourceEnumerator.cs
- LocatorGroup.cs
- StrokeCollectionConverter.cs
- XmlArrayAttribute.cs
- Transform3DCollection.cs
- ImageAutomationPeer.cs
- FontFamilyIdentifier.cs
- WebPartEditorApplyVerb.cs
- GridErrorDlg.cs
- EntitySqlQueryCacheKey.cs
- MemberRelationshipService.cs
- EntityWrapper.cs
- EditorPartCollection.cs