improve:墨迹平滑方案

This commit is contained in:
2025-07-20 15:21:59 +08:00
parent 428b278c78
commit d5142ad82c
8 changed files with 479 additions and 12 deletions
@@ -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
{
/// <summary>
/// 高级贝塞尔曲线平滑算法
/// 用于解决墨迹闪烁问题,提供更平滑的笔迹效果
/// </summary>
public class AdvancedBezierSmoothing
{
/// <summary>
/// 平滑强度 (0.0 - 1.0)
/// </summary>
public double SmoothingStrength { get; set; } = 0.6;
/// <summary>
/// 张力参数 (0.0 - 1.0)
/// </summary>
public double Tension { get; set; } = 0.5;
/// <summary>
/// 是否启用自适应平滑
/// </summary>
public bool EnableAdaptiveSmoothing { get; set; } = true;
/// <summary>
/// 最小点间距阈值
/// </summary>
public double MinPointDistance { get; set; } = 2.0;
/// <summary>
/// 最大点间距阈值
/// </summary>
public double MaxPointDistance { get; set; } = 50.0;
/// <summary>
/// 对笔画进行高级贝塞尔曲线平滑处理
/// </summary>
/// <param name="stroke">原始笔画</param>
/// <returns>平滑后的笔画</returns>
public Stroke SmoothStroke(Stroke stroke)
{
if (stroke == null || stroke.StylusPoints.Count < 3)
return stroke;
var originalPoints = stroke.StylusPoints.ToList();
var smoothedPoints = new List<StylusPoint>();
// 第一步:点过滤和重采样
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;
}
/// <summary>
/// 过滤和重采样点
/// </summary>
private List<StylusPoint> FilterAndResamplePoints(List<StylusPoint> points)
{
var filteredPoints = new List<StylusPoint>();
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;
}
/// <summary>
/// 计算贝塞尔曲线的控制点
/// </summary>
private List<Point> CalculateControlPoints(List<StylusPoint> points)
{
var controlPoints = new List<Point>();
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;
}
/// <summary>
/// 生成曲线点
/// </summary>
private List<StylusPoint> GenerateCurvePoints(List<StylusPoint> points, List<Point> controlPoints)
{
var curvePoints = new List<StylusPoint>();
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;
}
/// <summary>
/// 计算贝塞尔曲线上的点
/// </summary>
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;
}
/// <summary>
/// 计算两点间距离
/// </summary>
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));
}
/// <summary>
/// 插值两点间的点
/// </summary>
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)
);
}
/// <summary>
/// 插值压感值
/// </summary>
private float InterpolatePressure(float p1, float p2, double ratio)
{
return (float)(p1 + (p2 - p1) * ratio);
}
/// <summary>
/// 应用自适应平滑
/// </summary>
private void ApplyAdaptiveSmoothing(List<StylusPoint> points)
{
if (!EnableAdaptiveSmoothing || points.Count < 3)
return;
// 计算笔迹的速度变化
var speeds = new List<double>();
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)));
}
}
}
}
+39 -3
View File
@@ -781,12 +781,48 @@
<TextBlock Text="# 请注意,若不保留双曲线渐近线可能会有遇到撤回相关的 BUG 影响用。" TextWrapping="Wrap"
Foreground="#a1a1aa" />
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Foreground="#fafafa" Text="让墨迹使用贝塞尔曲线平滑处理"
VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" />
<TextBlock Foreground="#fafafa" Text="使用WPF默认贝塞尔曲线平滑"
VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchFitToCurve"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
IsOn="False" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchFitToCurve_Toggled" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Foreground="#fafafa" Text="使用高级贝塞尔曲线平滑(推荐)"
VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchAdvancedBezierSmoothing"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchAdvancedBezierSmoothing_Toggled" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left"
Visibility="{Binding ElementName=ToggleSwitchAdvancedBezierSmoothing, Path=IsOn, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock Foreground="#fafafa" Text="平滑强度" VerticalAlignment="Center"
FontSize="14" Margin="0,0,16,0" />
<Slider Name="AdvancedSmoothingStrengthSlider" Width="150" Minimum="0.1" Maximum="1.0"
Value="0.6" TickFrequency="0.1" IsSnapToTickEnabled="True"
ValueChanged="AdvancedSmoothingStrengthSlider_ValueChanged" />
<TextBlock Foreground="#fafafa" Text="{Binding ElementName=AdvancedSmoothingStrengthSlider, Path=Value, StringFormat={}{0:F1}}"
VerticalAlignment="Center" FontSize="14" Margin="16,0,0,0" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left"
Visibility="{Binding ElementName=ToggleSwitchAdvancedBezierSmoothing, Path=IsOn, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock Foreground="#fafafa" Text="张力参数" VerticalAlignment="Center"
FontSize="14" Margin="0,0,16,0" />
<Slider Name="AdvancedSmoothingTensionSlider" Width="150" Minimum="0.1" Maximum="1.0"
Value="0.5" TickFrequency="0.1" IsSnapToTickEnabled="True"
ValueChanged="AdvancedSmoothingTensionSlider_ValueChanged" />
<TextBlock Foreground="#fafafa" Text="{Binding ElementName=AdvancedSmoothingTensionSlider, Path=Value, StringFormat={}{0:F1}}"
VerticalAlignment="Center" FontSize="14" Margin="16,0,0,0" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left"
Visibility="{Binding ElementName=ToggleSwitchAdvancedBezierSmoothing, Path=IsOn, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock Foreground="#fafafa" Text="启用自适应平滑" VerticalAlignment="Center"
FontSize="14" Margin="0,0,16,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchEnableAdaptiveSmoothing"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchEnableAdaptiveSmoothing_Toggled" />
</ui:SimpleStackPanel>
<TextBlock Text="# 高级贝塞尔曲线平滑(推荐):使用自定义算法替代系统默认的FitToCurve,提供更平滑的笔迹效果并解决墨迹闪烁问题。启用高级平滑时会自动禁用系统默认平滑。平滑强度控制平滑程度,张力参数控制曲线的紧绷程度,自适应平滑会根据笔迹速度自动调整平滑参数。" TextWrapping="Wrap" Foreground="#a1a1aa" />
</ui:SimpleStackPanel>
</GroupBox>
<!-- 新增:崩溃后操作设置 -->
+9 -1
View File
@@ -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;
+46 -1
View File
@@ -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<double> e) {
if (!isLoaded) return;
Settings.Canvas.AdvancedSmoothingStrength = AdvancedSmoothingStrengthSlider.Value;
SaveSettingsToFile();
}
private void AdvancedSmoothingTensionSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> 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;
+22 -4
View File
@@ -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;
+31 -1
View File
@@ -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() {
@@ -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
+9 -1
View File
@@ -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")]