diff --git a/samples/AvalonDraw/MainWindow.axaml b/samples/AvalonDraw/MainWindow.axaml index 850c437742..54461f007a 100644 --- a/samples/AvalonDraw/MainWindow.axaml +++ b/samples/AvalonDraw/MainWindow.axaml @@ -64,6 +64,7 @@ + diff --git a/samples/AvalonDraw/MainWindow.axaml.cs b/samples/AvalonDraw/MainWindow.axaml.cs index 23df361bcf..4ba2ca1f76 100644 --- a/samples/AvalonDraw/MainWindow.axaml.cs +++ b/samples/AvalonDraw/MainWindow.axaml.cs @@ -2836,6 +2836,29 @@ private void ApplyPathOp(SK.SKPathOp op) SelectNodeFromElement(result); } + private void BlendSelected() + { + if (_document is null || _multiSelected.Count != 2) + return; + + if (_multiSelected[0] is not SvgPath from || _multiSelected[1] is not SvgPath to) + return; + if (from.Parent is not SvgElement parent) + return; + + SaveUndoState(); + + var blends = _pathService.Blend(from, to, 5); + var index = parent.Children.IndexOf(to); + foreach (var p in blends) + { + parent.Children.Insert(index++, p); + } + + SvgView.SkSvg!.FromSvgDocument(_document); + BuildTree(); + } + private void SmoothPointMenuItem_Click(object? sender, RoutedEventArgs e) { if (_pathService.IsEditing && _pathService.ActivePoint >= 0 && _document is { }) @@ -2863,6 +2886,7 @@ private void CornerPointMenuItem_Click(object? sender, RoutedEventArgs e) private void UniteMenuItem_Click(object? sender, RoutedEventArgs e) => ApplyPathOp(SK.SKPathOp.Union); private void SubtractMenuItem_Click(object? sender, RoutedEventArgs e) => ApplyPathOp(SK.SKPathOp.Difference); private void IntersectMenuItem_Click(object? sender, RoutedEventArgs e) => ApplyPathOp(SK.SKPathOp.Intersect); + private void BlendMenuItem_Click(object? sender, RoutedEventArgs e) => BlendSelected(); private void AlignLeftMenuItem_Click(object? sender, RoutedEventArgs e) => AlignSelected(AlignService.AlignType.Left); private void AlignHCenterMenuItem_Click(object? sender, RoutedEventArgs e) => AlignSelected(AlignService.AlignType.HCenter); diff --git a/samples/AvalonDraw/Services/PathService.cs b/samples/AvalonDraw/Services/PathService.cs index e2e8d6f1a6..3087aecd68 100644 --- a/samples/AvalonDraw/Services/PathService.cs +++ b/samples/AvalonDraw/Services/PathService.cs @@ -513,4 +513,75 @@ public static string ToSvgPathData(SK.SKPath skPath) return path; } + + private static float Lerp(float a, float b, float t) => a + (b - a) * t; + + private static System.Drawing.PointF Lerp(System.Drawing.PointF a, System.Drawing.PointF b, float t) + => new System.Drawing.PointF(Lerp(a.X, b.X, t), Lerp(a.Y, b.Y, t)); + + private static SvgPathSegment? LerpSegment(SvgPathSegment s1, SvgPathSegment s2, float t) + { + if (s1.GetType() != s2.GetType()) + return null; + return s1 switch + { + SvgMoveToSegment mv1 when s2 is SvgMoveToSegment mv2 => new SvgMoveToSegment(false, Lerp(mv1.End, mv2.End, t)), + SvgLineSegment ln1 when s2 is SvgLineSegment ln2 => new SvgLineSegment(false, Lerp(ln1.End, ln2.End, t)), + SvgCubicCurveSegment c1 when s2 is SvgCubicCurveSegment c2 => new SvgCubicCurveSegment(false, + Lerp(c1.FirstControlPoint, c2.FirstControlPoint, t), + Lerp(c1.SecondControlPoint, c2.SecondControlPoint, t), + Lerp(c1.End, c2.End, t)), + SvgQuadraticCurveSegment q1 when s2 is SvgQuadraticCurveSegment q2 => new SvgQuadraticCurveSegment(false, + Lerp(q1.ControlPoint, q2.ControlPoint, t), + Lerp(q1.End, q2.End, t)), + SvgArcSegment a1 when s2 is SvgArcSegment a2 => new SvgArcSegment( + Lerp(a1.RadiusX, a2.RadiusX, t), + Lerp(a1.RadiusY, a2.RadiusY, t), + Lerp(a1.Angle, a2.Angle, t), + a1.Size, + a1.Sweep, + false, + Lerp(a1.End, a2.End, t)), + SvgClosePathSegment _ when s2 is SvgClosePathSegment => new SvgClosePathSegment(false), + _ => null, + }; + } + + private static SvgPathSegmentList? LerpPath(SvgPathSegmentList from, SvgPathSegmentList to, float t) + { + if (from.Count != to.Count) + return null; + var list = new SvgPathSegmentList(); + for (int i = 0; i < from.Count; i++) + { + var seg = LerpSegment(from[i], to[i], t); + if (seg is null) + return null; + list.Add(seg); + } + return list; + } + + public IList Blend(SvgPath? from, SvgPath? to, int steps) + { + var list = new List(); + if (from is null || to is null || steps <= 0) + return list; + + var d1 = from.PathData; + var d2 = to.PathData; + if (d1 is null || d2 is null) + return list; + + for (int i = 1; i <= steps; i++) + { + var t = i / (float)(steps + 1); + var segs = LerpPath(d1, d2, t); + if (segs is null) + continue; + list.Add(new SvgPath { PathData = segs }); + } + + return list; + } }