diff --git a/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs b/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs new file mode 100644 index 00000000..c495af4d --- /dev/null +++ b/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs @@ -0,0 +1,291 @@ +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 +{ + /// + /// 高级贝塞尔曲线平滑算法 + /// 用于解决墨迹闪烁问题,提供更平滑的笔迹效果 + /// + public class AdvancedBezierSmoothing + { + /// + /// 平滑强度 (0.0 - 1.0) + /// + public double SmoothingStrength { get; set; } = 0.6; + + /// + /// 张力参数 (0.0 - 1.0) + /// + public double Tension { get; set; } = 0.5; + + /// + /// 是否启用自适应平滑 + /// + public bool EnableAdaptiveSmoothing { get; set; } = true; + + /// + /// 最小点间距阈值 + /// + public double MinPointDistance { get; set; } = 2.0; + + /// + /// 最大点间距阈值 + /// + public double MaxPointDistance { get; set; } = 50.0; + + /// + /// 对笔画进行高级贝塞尔曲线平滑处理 + /// + /// 原始笔画 + /// 平滑后的笔画 + public Stroke SmoothStroke(Stroke stroke) + { + if (stroke == null || stroke.StylusPoints.Count < 3) + return stroke; + + var originalPoints = stroke.StylusPoints.ToList(); + var smoothedPoints = new List(); + + // 第一步:点过滤和重采样 + var filteredPoints = FilterAndResamplePoints(originalPoints); + + // 第二步:计算控制点 + var controlPoints = CalculateControlPoints(filteredPoints); + + // 第三步:生成平滑曲线点 + var curvePoints = GenerateCurvePoints(filteredPoints, controlPoints); + + // 第四步:创建新的笔画 + var newStylusPoints = new StylusPointCollection(curvePoints); + var smoothedStroke = new Stroke(newStylusPoints) + { + DrawingAttributes = stroke.DrawingAttributes.Clone() + }; + + return smoothedStroke; + } + + /// + /// 过滤和重采样点 + /// + private List FilterAndResamplePoints(List points) + { + var filteredPoints = new List(); + + if (points.Count == 0) return filteredPoints; + + // 添加第一个点 + filteredPoints.Add(points[0]); + + for (int i = 1; i < points.Count; i++) + { + var currentPoint = points[i]; + var lastPoint = filteredPoints[filteredPoints.Count - 1]; + + double distance = GetDistance(lastPoint.ToPoint(), currentPoint.ToPoint()); + + // 如果距离太近,跳过 + if (distance < MinPointDistance) + continue; + + // 如果距离太远,插入中间点 + if (distance > MaxPointDistance) + { + int segments = (int)(distance / MaxPointDistance) + 1; + for (int j = 1; j < segments; j++) + { + double ratio = (double)j / segments; + var interpolatedPoint = InterpolatePoint(lastPoint, currentPoint, ratio); + filteredPoints.Add(interpolatedPoint); + } + } + + filteredPoints.Add(currentPoint); + } + + return filteredPoints; + } + + /// + /// 计算贝塞尔曲线的控制点 + /// + private List CalculateControlPoints(List points) + { + var controlPoints = new List(); + + if (points.Count < 2) return controlPoints; + + for (int i = 0; i < points.Count; i++) + { + Point currentPoint = points[i].ToPoint(); + Point controlPoint; + + if (i == 0) + { + // 第一个点的控制点 + Point nextPoint = points[i + 1].ToPoint(); + controlPoint = new Point( + currentPoint.X + (nextPoint.X - currentPoint.X) * Tension * 0.5, + currentPoint.Y + (nextPoint.Y - currentPoint.Y) * Tension * 0.5 + ); + } + else if (i == points.Count - 1) + { + // 最后一个点的控制点 + Point prevPoint = points[i - 1].ToPoint(); + controlPoint = new Point( + currentPoint.X + (currentPoint.X - prevPoint.X) * Tension * 0.5, + currentPoint.Y + (currentPoint.Y - prevPoint.Y) * Tension * 0.5 + ); + } + else + { + // 中间点的控制点 + Point prevPoint = points[i - 1].ToPoint(); + Point nextPoint = points[i + 1].ToPoint(); + + // 计算切线方向 + double tangentX = (nextPoint.X - prevPoint.X) * 0.5; + double tangentY = (nextPoint.Y - prevPoint.Y) * 0.5; + + // 应用张力参数 + controlPoint = new Point( + currentPoint.X + tangentX * Tension, + currentPoint.Y + tangentY * Tension + ); + } + + controlPoints.Add(controlPoint); + } + + return controlPoints; + } + + /// + /// 生成曲线点 + /// + private List GenerateCurvePoints(List points, List controlPoints) + { + var curvePoints = new List(); + + if (points.Count < 2) return curvePoints; + + // 为每个线段生成贝塞尔曲线点 + for (int i = 0; i < points.Count - 1; i++) + { + var startPoint = points[i]; + var endPoint = points[i + 1]; + var startControl = controlPoints[i]; + var endControl = controlPoints[i + 1]; + + // 计算自适应步长 + double distance = GetDistance(startPoint.ToPoint(), endPoint.ToPoint()); + int steps = Math.Max(3, Math.Min(20, (int)(distance / 5.0))); + + // 生成贝塞尔曲线点 + for (int j = 0; j <= steps; j++) + { + double t = (double)j / steps; + var curvePoint = CalculateBezierPoint(startPoint.ToPoint(), startControl, endControl, endPoint.ToPoint(), t); + + // 插值压感值 + float pressure = InterpolatePressure(startPoint.PressureFactor, endPoint.PressureFactor, t); + + var stylusPoint = new StylusPoint(curvePoint.X, curvePoint.Y, pressure); + curvePoints.Add(stylusPoint); + } + } + + return curvePoints; + } + + /// + /// 计算贝塞尔曲线上的点 + /// + private Point CalculateBezierPoint(Point p0, Point p1, Point p2, Point p3, double t) + { + double u = 1 - t; + double tt = t * t; + double uu = u * u; + double uuu = uu * u; + double ttt = tt * t; + + Point point = new Point(); + point.X = uuu * p0.X + 3 * uu * t * p1.X + 3 * u * tt * p2.X + ttt * p3.X; + point.Y = uuu * p0.Y + 3 * uu * t * p1.Y + 3 * u * tt * p2.Y + ttt * p3.Y; + + return point; + } + + /// + /// 计算两点间距离 + /// + private double GetDistance(Point p1, Point p2) + { + return Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y)); + } + + /// + /// 插值两点间的点 + /// + private StylusPoint InterpolatePoint(StylusPoint p1, StylusPoint p2, double ratio) + { + return new StylusPoint( + p1.X + (p2.X - p1.X) * ratio, + p1.Y + (p2.Y - p1.Y) * ratio, + InterpolatePressure(p1.PressureFactor, p2.PressureFactor, ratio) + ); + } + + /// + /// 插值压感值 + /// + private float InterpolatePressure(float p1, float p2, double ratio) + { + return (float)(p1 + (p2 - p1) * ratio); + } + + /// + /// 应用自适应平滑 + /// + private void ApplyAdaptiveSmoothing(List points) + { + if (!EnableAdaptiveSmoothing || points.Count < 3) + return; + + // 计算笔迹的速度变化 + var speeds = new List(); + for (int i = 1; i < points.Count - 1; i++) + { + var prev = points[i - 1].ToPoint(); + var curr = points[i].ToPoint(); + var next = points[i + 1].ToPoint(); + + double speed1 = GetDistance(prev, curr); + double speed2 = GetDistance(curr, next); + double avgSpeed = (speed1 + speed2) / 2.0; + + speeds.Add(avgSpeed); + } + + // 根据速度调整平滑强度 + if (speeds.Count > 0) + { + double avgSpeed = speeds.Average(); + double maxSpeed = speeds.Max(); + double minSpeed = speeds.Min(); + + // 速度变化越大,平滑强度越小 + double speedVariation = (maxSpeed - minSpeed) / avgSpeed; + SmoothingStrength = Math.Max(0.1, Math.Min(0.9, SmoothingStrength * (1.0 - speedVariation * 0.3))); + } + } + } +} \ No newline at end of file diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index f663fae4..6a165a37 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -781,12 +781,48 @@ - + + + + + + + + + + + + + + + + + + + + diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs index 29ada085..16e045e6 100644 --- a/Ink Canvas/MainWindow.xaml.cs +++ b/Ink Canvas/MainWindow.xaml.cs @@ -143,7 +143,15 @@ namespace Ink_Canvas { drawingAttributes.Height = 2.5; drawingAttributes.Width = 2.5; drawingAttributes.IsHighlighter = false; - drawingAttributes.FitToCurve = Settings.Canvas.FitToCurve; + // 默认使用高级贝塞尔曲线平滑,如果未启用则使用原来的FitToCurve + if (Settings.Canvas.UseAdvancedBezierSmoothing) + { + drawingAttributes.FitToCurve = false; + } + else + { + drawingAttributes.FitToCurve = Settings.Canvas.FitToCurve; + } inkCanvas.EditingMode = InkCanvasEditingMode.Ink; inkCanvas.Gesture += InkCanvas_Gesture; diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs index 81084903..c330aff4 100644 --- a/Ink Canvas/MainWindow_cs/MW_Settings.cs +++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs @@ -1285,6 +1285,47 @@ namespace Ink_Canvas { if (!isLoaded) return; drawingAttributes.FitToCurve = ToggleSwitchFitToCurve.IsOn; Settings.Canvas.FitToCurve = ToggleSwitchFitToCurve.IsOn; + + // 启用原来的FitToCurve时自动禁用高级贝塞尔平滑 + if (ToggleSwitchFitToCurve.IsOn) + { + ToggleSwitchAdvancedBezierSmoothing.IsOn = false; + Settings.Canvas.UseAdvancedBezierSmoothing = false; + } + + SaveSettingsToFile(); + } + + private void ToggleSwitchAdvancedBezierSmoothing_Toggled(object sender, RoutedEventArgs e) { + if (!isLoaded) return; + Settings.Canvas.UseAdvancedBezierSmoothing = ToggleSwitchAdvancedBezierSmoothing.IsOn; + + // 启用高级贝塞尔平滑时自动禁用原来的FitToCurve + if (ToggleSwitchAdvancedBezierSmoothing.IsOn) + { + ToggleSwitchFitToCurve.IsOn = false; + Settings.Canvas.FitToCurve = false; + drawingAttributes.FitToCurve = false; + } + + SaveSettingsToFile(); + } + + private void AdvancedSmoothingStrengthSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { + if (!isLoaded) return; + Settings.Canvas.AdvancedSmoothingStrength = AdvancedSmoothingStrengthSlider.Value; + SaveSettingsToFile(); + } + + private void AdvancedSmoothingTensionSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { + if (!isLoaded) return; + Settings.Canvas.AdvancedSmoothingTension = AdvancedSmoothingTensionSlider.Value; + SaveSettingsToFile(); + } + + private void ToggleSwitchEnableAdaptiveSmoothing_Toggled(object sender, RoutedEventArgs e) { + if (!isLoaded) return; + Settings.Canvas.EnableAdaptiveSmoothing = ToggleSwitchEnableAdaptiveSmoothing.IsOn; SaveSettingsToFile(); } @@ -1588,7 +1629,11 @@ namespace Ink_Canvas { Settings.Canvas.EraserShapeType = 1; Settings.Canvas.HideStrokeWhenSelecting = false; Settings.Canvas.ClearCanvasAndClearTimeMachine = false; - Settings.Canvas.FitToCurve = true; + Settings.Canvas.FitToCurve = false; + Settings.Canvas.UseAdvancedBezierSmoothing = true; + Settings.Canvas.AdvancedSmoothingStrength = 0.6; + Settings.Canvas.AdvancedSmoothingTension = 0.5; + Settings.Canvas.EnableAdaptiveSmoothing = true; Settings.Canvas.EnablePressureTouchMode = false; Settings.Canvas.DisablePressure = false; Settings.Canvas.AutoStraightenLine = true; diff --git a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs index cf2531a4..e1dd2960 100644 --- a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs +++ b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs @@ -515,13 +515,31 @@ namespace Ink_Canvas { ToggleSwitchHideStrokeWhenSelecting.IsOn = Settings.Canvas.HideStrokeWhenSelecting; - if (Settings.Canvas.FitToCurve) { - ToggleSwitchFitToCurve.IsOn = true; - drawingAttributes.FitToCurve = true; - } else { + // 初始化贝塞尔曲线平滑设置 + if (Settings.Canvas.UseAdvancedBezierSmoothing) + { + // 如果启用高级贝塞尔平滑,则禁用原来的FitToCurve + ToggleSwitchAdvancedBezierSmoothing.IsOn = true; ToggleSwitchFitToCurve.IsOn = false; drawingAttributes.FitToCurve = false; } + else if (Settings.Canvas.FitToCurve) + { + // 如果启用原来的FitToCurve,则禁用高级贝塞尔平滑 + ToggleSwitchFitToCurve.IsOn = true; + ToggleSwitchAdvancedBezierSmoothing.IsOn = false; + drawingAttributes.FitToCurve = true; + } + else + { + // 两者都禁用 + ToggleSwitchFitToCurve.IsOn = false; + ToggleSwitchAdvancedBezierSmoothing.IsOn = false; + drawingAttributes.FitToCurve = false; + } + AdvancedSmoothingStrengthSlider.Value = Settings.Canvas.AdvancedSmoothingStrength; + AdvancedSmoothingTensionSlider.Value = Settings.Canvas.AdvancedSmoothingTension; + ToggleSwitchEnableAdaptiveSmoothing.IsOn = Settings.Canvas.EnableAdaptiveSmoothing; // 初始化直线自动拉直相关设置 ToggleSwitchAutoStraightenLine.IsOn = Settings.Canvas.AutoStraightenLine; diff --git a/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs b/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs index 35aba96f..46037236 100644 --- a/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs +++ b/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs @@ -433,6 +433,7 @@ namespace Ink_Canvas { #region 形状绘制主函数 private void MouseTouchMove(Point endP) { + // 禁用原有的FitToCurve,使用新的高级贝塞尔曲线平滑 if (Settings.Canvas.FitToCurve == true) drawingAttributes.FitToCurve = false; ViewboxFloatingBar.IsHitTestVisible = false; BlackboardUIGridForInkReplay.IsHitTestVisible = false; @@ -1589,7 +1590,36 @@ namespace Ink_Canvas { } } - if (Settings.Canvas.FitToCurve == true) drawingAttributes.FitToCurve = true; + // 应用高级贝塞尔曲线平滑 + if (Settings.Canvas.UseAdvancedBezierSmoothing) + { + try + { + var advancedSmoothing = new Helpers.AdvancedBezierSmoothing + { + SmoothingStrength = Settings.Canvas.AdvancedSmoothingStrength, + Tension = Settings.Canvas.AdvancedSmoothingTension, + EnableAdaptiveSmoothing = Settings.Canvas.EnableAdaptiveSmoothing + }; + + // 对临时笔画应用平滑 + if (lastTempStroke != null) + { + var smoothedStroke = advancedSmoothing.SmoothStroke(lastTempStroke); + inkCanvas.Strokes.Remove(lastTempStroke); + lastTempStroke = smoothedStroke; + inkCanvas.Strokes.Add(smoothedStroke); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"形状绘制高级贝塞尔曲线平滑失败: {ex.Message}"); + } + } + else if (Settings.Canvas.FitToCurve == true) + { + drawingAttributes.FitToCurve = true; + } } private bool NeedUpdateIniP() { diff --git a/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs b/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs index f66ed808..9cc49768 100644 --- a/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs +++ b/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs @@ -16,6 +16,7 @@ namespace Ink_Canvas { private const double LINE_STRAIGHTEN_THRESHOLD = 0.20; // 默认灵敏度阈值,与UI默认值对应 private void inkCanvas_StrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e) { + // 禁用原有的FitToCurve,使用新的高级贝塞尔曲线平滑 if (Settings.Canvas.FitToCurve == true) drawingAttributes.FitToCurve = false; try { @@ -572,7 +573,37 @@ namespace Ink_Canvas { } catch { } - if (Settings.Canvas.FitToCurve == true) drawingAttributes.FitToCurve = true; + // 应用高级贝塞尔曲线平滑 + if (Settings.Canvas.UseAdvancedBezierSmoothing) + { + try + { + var advancedSmoothing = new Helpers.AdvancedBezierSmoothing + { + SmoothingStrength = Settings.Canvas.AdvancedSmoothingStrength, + Tension = Settings.Canvas.AdvancedSmoothingTension, + EnableAdaptiveSmoothing = Settings.Canvas.EnableAdaptiveSmoothing + }; + + var smoothedStroke = advancedSmoothing.SmoothStroke(e.Stroke); + + // 替换原始笔画 + SetNewBackupOfStroke(); + _currentCommitType = CommitReason.ShapeRecognition; + inkCanvas.Strokes.Remove(e.Stroke); + inkCanvas.Strokes.Add(smoothedStroke); + _currentCommitType = CommitReason.UserInput; + } + catch (Exception ex) + { + // 如果高级平滑失败,回退到原始笔画 + System.Diagnostics.Debug.WriteLine($"高级贝塞尔曲线平滑失败: {ex.Message}"); + } + } + else if (Settings.Canvas.FitToCurve == true) + { + drawingAttributes.FitToCurve = true; + } } // New method: Checks if a stroke is potentially a straight line diff --git a/Ink Canvas/Resources/Settings.cs b/Ink Canvas/Resources/Settings.cs index da490d31..01d44acb 100644 --- a/Ink Canvas/Resources/Settings.cs +++ b/Ink Canvas/Resources/Settings.cs @@ -47,7 +47,15 @@ namespace Ink_Canvas [JsonProperty("hideStrokeWhenSelecting")] public bool HideStrokeWhenSelecting { get; set; } = true; [JsonProperty("fitToCurve")] - public bool FitToCurve { get; set; } = true; + public bool FitToCurve { get; set; } = false; // 默认关闭原来的贝塞尔平滑 + [JsonProperty("useAdvancedBezierSmoothing")] + public bool UseAdvancedBezierSmoothing { get; set; } = true; // 默认启用高级贝塞尔曲线平滑 + [JsonProperty("advancedSmoothingStrength")] + public double AdvancedSmoothingStrength { get; set; } = 0.6; // 高级平滑强度 (0.0 - 1.0) + [JsonProperty("advancedSmoothingTension")] + public double AdvancedSmoothingTension { get; set; } = 0.5; // 高级平滑张力 (0.0 - 1.0) + [JsonProperty("enableAdaptiveSmoothing")] + public bool EnableAdaptiveSmoothing { get; set; } = true; // 是否启用自适应平滑 [JsonProperty("clearCanvasAndClearTimeMachine")] public bool ClearCanvasAndClearTimeMachine { get; set; } = false; [JsonProperty("enablePressureTouchMode")]