diff --git a/Ink Canvas/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs index 0f179263..6b93c545 100644 --- a/Ink Canvas/AssemblyInfo.cs +++ b/Ink Canvas/AssemblyInfo.cs @@ -49,5 +49,5 @@ using System.Windows; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.7.1.3")] -[assembly: AssemblyFileVersion("1.7.1.3")] +[assembly: AssemblyVersion("1.7.1.6")] +[assembly: AssemblyFileVersion("1.7.1.6")] diff --git a/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs b/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs new file mode 100644 index 00000000..16ac88c8 --- /dev/null +++ b/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Media; +using Point = System.Windows.Point; + +namespace Ink_Canvas.Helpers +{ + /// + /// 适合手写/触摸的墨迹平滑方案:指数平滑+等距重采样+Catmull-Rom样条插值,防止自交和异常填充 + /// + public class AdvancedBezierSmoothing + { + public double SmoothingStrength { get; set; } = 0.8; + public double ResampleInterval { get; set; } = 0.8; + public int InterpolationSteps { get; set; } = 64; + + public Stroke SmoothStroke(Stroke stroke) + { + if (stroke == null || stroke.StylusPoints.Count < 2) + return stroke; + var originalPoints = stroke.StylusPoints.ToList(); + var smoothedPoints = ApplyExponentialSmoothing(originalPoints, SmoothingStrength); + var resampledPoints = ResampleEquidistant(smoothedPoints, ResampleInterval); + var interpolatedPoints = SlidingBezierFit(resampledPoints, 4, 24); + var finalPoints = ApplyExponentialSmoothing(interpolatedPoints, 0.5); // 二次平滑 + var ultraSmoothPoints = SlidingWindowSmooth(finalPoints, 7); // 滑动窗口平滑 + var smoothedStroke = new Stroke(new StylusPointCollection(ultraSmoothPoints)) + { + DrawingAttributes = stroke.DrawingAttributes.Clone() + }; + return smoothedStroke; + } + + private List ApplyExponentialSmoothing(List points, double alpha) + { + var result = new List(); + if (points.Count == 0) return result; + result.Add(points[0]); + double lastX = points[0].X; + double lastY = points[0].Y; + float lastPressure = points[0].PressureFactor; + for (int i = 1; i < points.Count; i++) + { + var p = points[i]; + lastX = alpha * p.X + (1 - alpha) * lastX; + lastY = alpha * p.Y + (1 - alpha) * lastY; + lastPressure = (float)(alpha * p.PressureFactor + (1 - alpha) * lastPressure); + if (lastPressure < 0.1f) lastPressure = 0.1f; + result.Add(new StylusPoint(lastX, lastY, lastPressure)); + } + return result; + } + + private List ResampleEquidistant(List points, double interval = 2.0) + { + var result = new List(); + if (points.Count == 0) return result; + result.Add(points[0]); + double accumulated = 0; + for (int i = 1; i < points.Count; i++) + { + var prev = result.Last(); + var curr = points[i]; + double dx = curr.X - prev.X; + double dy = curr.Y - prev.Y; + double dist = Math.Sqrt(dx * dx + dy * dy); + if (dist + accumulated >= interval) + { + double t = (interval - accumulated) / dist; + double x = prev.X + t * dx; + double y = prev.Y + t * dy; + float pressure = (float)(prev.PressureFactor * (1 - t) + curr.PressureFactor * t); + if (pressure < 0.1f) pressure = 0.1f; + var newPoint = new StylusPoint(x, y, pressure); + result.Add(newPoint); + accumulated = 0; + i--; // 重新处理当前点 + } + else + { + accumulated += dist; + } + } + return result; + } + + private List SlidingBezierFit(List points, int window = 4, int steps = 24) + { + var result = new List(); + if (points.Count < window) return points; + for (int i = 0; i <= points.Count - window; i++) + { + var p0 = points[i]; + var p1 = points[i + 1]; + var p2 = points[i + 2]; + var p3 = points[i + 3]; + for (int j = 0; j < steps; j++) + { + double t = (double)j / steps; + var pt = CubicBezier(p0, p1, p2, p3, t); + result.Add(pt); + } + } + // 保证最后一个点被包含 + result.Add(points.Last()); + return result; + } + + private StylusPoint CubicBezier(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3, double t) + { + double u = 1 - t; + double tt = t * t; + double uu = u * u; + double uuu = uu * u; + double ttt = tt * t; + double x = uuu * p0.X + 3 * uu * t * p1.X + 3 * u * tt * p2.X + ttt * p3.X; + double y = uuu * p0.Y + 3 * uu * t * p1.Y + 3 * u * tt * p2.Y + ttt * p3.Y; + float pressure = (float)(p1.PressureFactor * (1 - t) + p2.PressureFactor * t); + if (pressure < 0.1f) pressure = 0.1f; + return new StylusPoint(x, y, pressure); + } + + private List SlidingWindowSmooth(List points, int window = 5) + { + var result = new List(); + int half = window / 2; + for (int i = 0; i < points.Count; i++) + { + double sumX = 0, sumY = 0, sumP = 0; + int count = 0; + for (int j = Math.Max(0, i - half); j <= Math.Min(points.Count - 1, i + half); j++) + { + sumX += points[j].X; + sumY += points[j].Y; + sumP += points[j].PressureFactor; + count++; + } + result.Add(new StylusPoint(sumX / count, sumY / count, (float)(sumP / count))); + } + return result; + } + } +} \ No newline at end of file diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index 1b5bb979..d4040534 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -210,8 +210,18 @@ +