using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Windows; using System.Windows.Media; using HandyControl.Expression.Drawing; namespace HandyControl.Expression.Media; public sealed class SketchGeometryEffect : GeometryEffect { private readonly long _randomSeed = DateTime.Now.Ticks; protected override GeometryEffect DeepCopy() { return new SketchGeometryEffect(); } private static void DisturbPoints(RandomEngine random, double scale, IList points, IList normals) { var count = points.Count; for (var i = 1; i < count; i++) { var num3 = random.NextGaussian(0.0, 1.0 * scale); var num4 = random.NextUniform(-0.5, 0.5) * scale; var point = points[i]; var vector = normals[i]; var vector2 = normals[i]; var point2 = points[i]; var vector3 = normals[i]; var vector4 = normals[i]; points[i] = new Point(point.X + vector.X * num4 - vector2.Y * num3, point2.Y + vector3.X * num3 + vector4.Y * num4); } } public override bool Equals(GeometryEffect geometryEffect) { return geometryEffect is SketchGeometryEffect; } private IEnumerable GetEffectiveSegments(PathFigure pathFigure) { var startPoint = pathFigure.StartPoint; foreach (var iteratorVariable1 in pathFigure.AllSegments()) foreach (var iteratorVariable2 in iteratorVariable1.PathSegment.GetSimpleSegments( iteratorVariable1.StartPoint)) { yield return iteratorVariable2; startPoint = iteratorVariable2.Points.Last(); } if (pathFigure.IsClosed) yield return SimpleSegment.Create(startPoint, pathFigure.StartPoint); } [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse")] protected override bool UpdateCachedGeometry(Geometry input) { var flag = false; var inputPath = input.AsPathGeometry(); if (inputPath != null) return flag | UpdateSketchGeometry(inputPath); CachedGeometry = input; return flag; } private bool UpdateSketchGeometry(PathGeometry inputPath) { var flag = false; flag |= GeometryHelper.EnsureGeometryType(out var geometry, ref CachedGeometry, () => new PathGeometry()); flag |= geometry.Figures.EnsureListCount(inputPath.Figures.Count, () => new PathFigure()); var random = new RandomEngine(_randomSeed); for (var i = 0; i < inputPath.Figures.Count; i++) { var pathFigure = inputPath.Figures[i]; var isClosed = pathFigure.IsClosed; var isFilled = pathFigure.IsFilled; if (pathFigure.Segments.Count == 0) { flag |= geometry.Figures[i] .SetIfDifferent(PathFigure.StartPointProperty, pathFigure.StartPoint); flag |= geometry.Figures[i].Segments.EnsureListCount(0); } else { var list = new List(pathFigure.Segments.Count * 3); foreach (var segment in GetEffectiveSegments(pathFigure)) { var resultPolyline = new List { segment.Points[0] }; segment.Flatten(resultPolyline, 0.0, null); var polyline = new PolylineData(resultPolyline); if (resultPolyline.Count > 1 && polyline.TotalLength > 4.0) { var a = polyline.TotalLength / 8.0; var sampleCount = (int) Math.Max(2.0, Math.Ceiling(a)); var interval = polyline.TotalLength / sampleCount; var scale = interval / 8.0; var samplePoints = new List(sampleCount); var sampleNormals = new List(sampleCount); var sampleIndex = 0; PolylineHelper.PathMarch(polyline, 0.0, 0.0, delegate (MarchLocation location) { if (location.Reason == MarchStopReason.CompletePolyline) return double.NaN; if (location.Reason != MarchStopReason.CompleteStep) return location.Remain; if (sampleIndex++ == sampleCount) return double.NaN; samplePoints.Add(location.GetPoint(polyline.Points)); sampleNormals.Add(location.GetNormal(polyline)); return interval; }); DisturbPoints(random, scale, samplePoints, sampleNormals); list.AddRange(samplePoints); } else { list.AddRange(resultPolyline); list.RemoveLast(); } } if (!isClosed) list.Add(pathFigure.Segments.Last().GetLastPoint()); flag |= PathFigureHelper.SyncPolylineFigure(geometry.Figures[i], list, isClosed, isFilled); } } if (flag) CachedGeometry = PathGeometryHelper.FixPathGeometryBoundary(CachedGeometry); return flag; } }