Files
community/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs
T

2137 lines
99 KiB
C#
Raw Normal View History

2025-08-31 11:43:52 +08:00
using Ink_Canvas.Helpers;
using System;
2025-05-25 09:29:48 +08:00
using System.Collections.Generic;
2025-07-28 14:40:44 +08:00
using System.Diagnostics;
2025-05-25 09:29:48 +08:00
using System.Linq;
2025-07-26 19:03:07 +08:00
using System.Threading.Tasks;
2025-05-25 09:29:48 +08:00
using System.Windows;
using System.Windows.Controls;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using Point = System.Windows.Point;
2025-08-03 16:46:33 +08:00
namespace Ink_Canvas
{
public partial class MainWindow : Window
{
2025-05-25 09:29:48 +08:00
private StrokeCollection newStrokes = new StrokeCollection();
private List<Circle> circles = new List<Circle>();
private const double LINE_STRAIGHTEN_THRESHOLD = 0.20; // 默认灵敏度阈值,与UI默认值对应
2025-05-25 09:29:48 +08:00
2025-07-29 23:14:20 +08:00
// 矩形参考线系统
private List<RectangleGuideLine> rectangleGuideLines = new List<RectangleGuideLine>();
private const double RECTANGLE_ENDPOINT_THRESHOLD = 30.0; // 端点相交判断阈值
private const double RECTANGLE_ANGLE_THRESHOLD = 15.0; // 角度判断阈值(度)
// 矩形参考线数据结构
private class RectangleGuideLine
{
public Stroke OriginalStroke { get; set; }
public Point StartPoint { get; set; }
public Point EndPoint { get; set; }
public DateTime CreatedTime { get; set; }
public double Angle { get; set; } // 直线角度(弧度)
public bool IsHorizontal { get; set; }
public bool IsVertical { get; set; }
public RectangleGuideLine(Stroke stroke, Point start, Point end)
{
OriginalStroke = stroke;
StartPoint = start;
EndPoint = end;
CreatedTime = DateTime.Now;
// 计算角度
double deltaX = end.X - start.X;
double deltaY = end.Y - start.Y;
Angle = Math.Atan2(deltaY, deltaX);
// 判断是否为水平或垂直线
double angleDegrees = Math.Abs(Angle * 180.0 / Math.PI);
IsHorizontal = angleDegrees < RECTANGLE_ANGLE_THRESHOLD || angleDegrees > (180 - RECTANGLE_ANGLE_THRESHOLD);
IsVertical = Math.Abs(angleDegrees - 90) < RECTANGLE_ANGLE_THRESHOLD;
}
}
2025-08-03 16:46:33 +08:00
private void inkCanvas_StrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e)
{
2025-08-23 23:13:39 +08:00
// 检查是否启用墨迹渐隐功能
if (Settings.Canvas.EnableInkFade)
{
// 获取墨迹的起点和终点
var startPoint = e.Stroke.StylusPoints.Count > 0 ? e.Stroke.StylusPoints[0].ToPoint() : new Point();
var endPoint = e.Stroke.StylusPoints.Count > 0 ? e.Stroke.StylusPoints[e.Stroke.StylusPoints.Count - 1].ToPoint() : new Point();
2025-08-31 11:43:52 +08:00
2025-08-23 23:13:39 +08:00
// 从InkCanvas中移除墨迹,因为我们要用渐隐管理器来管理它
if (inkCanvas.Strokes.Contains(e.Stroke))
{
inkCanvas.Strokes.Remove(e.Stroke);
}
2025-08-31 11:43:52 +08:00
2025-08-23 23:13:39 +08:00
// 添加到墨迹渐隐管理器
if (_inkFadeManager != null)
{
_inkFadeManager.AddFadingStroke(e.Stroke, startPoint, endPoint);
}
else
{
LogHelper.WriteLogToFile("StrokeCollected: 墨迹渐隐管理器为空,无法添加墨迹", LogHelper.LogType.Error);
}
2025-08-31 11:43:52 +08:00
2025-08-23 23:13:39 +08:00
// 墨迹渐隐模式下不参与墨迹纠正和其他处理,直接返回
return;
}
2025-08-31 11:43:52 +08:00
2025-07-20 15:57:51 +08:00
// 标记是否进行了直线拉直
bool wasStraightened = false;
2025-08-03 16:46:33 +08:00
2025-07-20 15:21:59 +08:00
// 禁用原有的FitToCurve,使用新的高级贝塞尔曲线平滑
2025-07-28 14:40:44 +08:00
if (Settings.Canvas.FitToCurve) drawingAttributes.FitToCurve = false;
2025-05-25 09:29:48 +08:00
2025-08-03 16:46:33 +08:00
try
{
2025-05-25 09:29:48 +08:00
inkCanvas.Opacity = 1;
2025-08-03 16:46:33 +08:00
2025-06-18 13:37:52 +08:00
// 应用屏蔽压感功能 - 如果启用,所有笔画都使用统一粗细
2025-08-03 16:46:33 +08:00
if (Settings.Canvas.DisablePressure)
{
2025-06-18 13:37:52 +08:00
var uniformPoints = new StylusPointCollection();
2025-08-03 16:46:33 +08:00
foreach (StylusPoint point in e.Stroke.StylusPoints)
{
2025-06-18 13:37:52 +08:00
StylusPoint newPoint = new StylusPoint(point.X, point.Y, 0.5f); // 统一压感值为0.5
uniformPoints.Add(newPoint);
}
e.Stroke.StylusPoints = uniformPoints;
}
// 应用压感触屏模式 - 如果启用并且检测到触屏输入
2025-08-03 16:46:33 +08:00
else if (Settings.Canvas.EnablePressureTouchMode)
{
2025-06-18 13:37:52 +08:00
bool isTouchInput = true;
2025-08-03 16:46:33 +08:00
foreach (StylusPoint point in e.Stroke.StylusPoints)
{
2025-06-18 13:37:52 +08:00
// 检测是否为压感笔输入(压感笔的PressureFactor不等于0.5或0
2025-08-03 16:46:33 +08:00
if ((point.PressureFactor > 0.501 || point.PressureFactor < 0.5) && point.PressureFactor != 0)
{
2025-06-18 13:37:52 +08:00
isTouchInput = false;
break;
}
}
// 如果是触屏输入,则应用模拟压感
2025-08-03 16:46:33 +08:00
if (isTouchInput)
{
switch (Settings.Canvas.InkStyle)
{
2025-06-18 13:37:52 +08:00
case 1:
if (penType == 0)
2025-08-03 16:46:33 +08:00
try
{
2025-06-18 13:37:52 +08:00
var stylusPoints = new StylusPointCollection();
var n = e.Stroke.StylusPoints.Count - 1;
2025-08-03 16:46:33 +08:00
for (var i = 0; i <= n; i++)
{
2025-06-18 13:37:52 +08:00
var speed = GetPointSpeed(e.Stroke.StylusPoints[Math.Max(i - 1, 0)].ToPoint(),
e.Stroke.StylusPoints[i].ToPoint(),
e.Stroke.StylusPoints[Math.Min(i + 1, n)].ToPoint());
var point = new StylusPoint();
if (speed >= 0.25)
point.PressureFactor = (float)(0.5 - 0.3 * (Math.Min(speed, 1.5) - 0.3) / 1.2);
else if (speed >= 0.05)
point.PressureFactor = (float)0.5;
else
point.PressureFactor = (float)(0.5 + 0.4 * (0.05 - speed) / 0.05);
point.X = e.Stroke.StylusPoints[i].X;
point.Y = e.Stroke.StylusPoints[i].Y;
stylusPoints.Add(point);
}
e.Stroke.StylusPoints = stylusPoints;
}
catch { }
break;
case 0:
if (penType == 0)
2025-08-03 16:46:33 +08:00
try
{
2025-06-18 13:37:52 +08:00
var stylusPoints = new StylusPointCollection();
var n = e.Stroke.StylusPoints.Count - 1;
var pressure = 0.1;
var x = 10;
if (n == 1) return;
2025-08-03 16:46:33 +08:00
if (n >= x)
{
for (var i = 0; i < n - x; i++)
{
2025-06-18 13:37:52 +08:00
var point = new StylusPoint();
point.PressureFactor = (float)0.5;
point.X = e.Stroke.StylusPoints[i].X;
point.Y = e.Stroke.StylusPoints[i].Y;
stylusPoints.Add(point);
}
2025-08-03 16:46:33 +08:00
for (var i = n - x; i <= n; i++)
{
2025-06-18 13:37:52 +08:00
var point = new StylusPoint();
point.PressureFactor = (float)((0.5 - pressure) * (n - i) / x + pressure);
point.X = e.Stroke.StylusPoints[i].X;
point.Y = e.Stroke.StylusPoints[i].Y;
stylusPoints.Add(point);
}
}
2025-08-03 16:46:33 +08:00
else
{
for (var i = 0; i <= n; i++)
{
2025-06-18 13:37:52 +08:00
var point = new StylusPoint();
point.PressureFactor = (float)(0.4 * (n - i) / n + pressure);
point.X = e.Stroke.StylusPoints[i].X;
point.Y = e.Stroke.StylusPoints[i].Y;
stylusPoints.Add(point);
}
}
e.Stroke.StylusPoints = stylusPoints;
}
catch { }
break;
}
}
}
2025-08-03 16:46:33 +08:00
// Apply line straightening and endpoint snapping if ink-to-shape is enabled
if (Settings.InkToShape.IsInkToShapeEnabled)
{
2025-06-19 14:30:24 +08:00
// 检查是否启用了直线自动拉直功能
2025-08-03 16:46:33 +08:00
if (Settings.Canvas.AutoStraightenLine && IsPotentialStraightLine(e.Stroke))
{
2025-06-18 13:24:50 +08:00
// Get start and end points of the stroke
Point startPoint = e.Stroke.StylusPoints[0].ToPoint();
Point endPoint = e.Stroke.StylusPoints[e.Stroke.StylusPoints.Count - 1].ToPoint();
2025-08-03 16:46:33 +08:00
2025-07-15 18:30:25 +08:00
// 先完成所有直线判定,再考虑端点吸附
// 读取实际的灵敏度设置值
double sensitivity = Settings.InkToShape.LineStraightenSensitivity;
2025-07-28 14:40:44 +08:00
Debug.WriteLine($"当前灵敏度值: {sensitivity}");
2025-08-03 16:46:33 +08:00
2025-07-15 18:30:25 +08:00
// 判断是否应该拉直线条
bool shouldStraighten = ShouldStraightenLine(e.Stroke);
2025-08-03 16:46:33 +08:00
// 输出一些调试信息,帮助理解灵敏度设置的效果
2025-07-28 14:40:44 +08:00
Debug.WriteLine($"LineStraightenSensitivity: {Settings.InkToShape.LineStraightenSensitivity}, ShouldStraighten: {shouldStraighten}");
2025-08-03 16:46:33 +08:00
2025-07-15 18:30:25 +08:00
// 只有当确定要拉直线条时,才检查端点吸附
2025-08-03 16:46:33 +08:00
if (shouldStraighten && Settings.Canvas.LineEndpointSnapping)
{
2025-06-19 14:30:24 +08:00
// 只有在启用了形状识别(矩形或三角形)时才执行端点吸附
2025-08-03 16:46:33 +08:00
if (Settings.InkToShape.IsInkToShapeRectangle || Settings.InkToShape.IsInkToShapeTriangle)
{
2025-06-19 14:30:24 +08:00
Point[] snappedPoints = GetSnappedEndpoints(startPoint, endPoint);
2025-08-03 16:46:33 +08:00
if (snappedPoints != null)
{
2025-06-19 14:30:24 +08:00
startPoint = snappedPoints[0];
endPoint = snappedPoints[1];
}
2025-06-17 22:37:37 +08:00
}
2025-06-18 13:24:50 +08:00
}
2025-07-15 18:30:25 +08:00
// 如果确定要拉直,则创建直线
2025-08-03 16:46:33 +08:00
if (shouldStraighten)
{
2025-06-18 13:24:50 +08:00
StylusPointCollection straightLinePoints = CreateStraightLine(startPoint, endPoint);
2025-08-03 16:46:33 +08:00
Stroke straightStroke = new Stroke(straightLinePoints)
{
2025-06-18 13:24:50 +08:00
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
};
2025-08-03 16:46:33 +08:00
2025-06-18 13:24:50 +08:00
// Replace the original stroke with the straightened one
SetNewBackupOfStroke();
_currentCommitType = CommitReason.ShapeRecognition;
inkCanvas.Strokes.Remove(e.Stroke);
inkCanvas.Strokes.Add(straightStroke);
_currentCommitType = CommitReason.UserInput;
2025-08-03 16:46:33 +08:00
2025-06-18 13:24:50 +08:00
// We can't modify e.Stroke directly, but we need to update newStrokes
// to ensure proper shape recognition for the straightened line
2025-08-03 16:46:33 +08:00
if (newStrokes.Contains(e.Stroke))
{
2025-06-18 13:24:50 +08:00
newStrokes.Remove(e.Stroke);
newStrokes.Add(straightStroke);
}
2025-08-03 16:46:33 +08:00
2025-07-20 15:57:51 +08:00
wasStraightened = true; // 标记已进行直线拉直
2025-06-17 22:37:37 +08:00
}
}
}
2025-06-18 13:24:50 +08:00
2025-08-03 16:46:33 +08:00
if (Settings.InkToShape.IsInkToShapeEnabled && !Environment.Is64BitProcess)
{
void InkToShapeProcess()
{
try
{
2025-05-25 09:29:48 +08:00
newStrokes.Add(e.Stroke);
if (newStrokes.Count > 4) newStrokes.RemoveAt(0);
for (var i = 0; i < newStrokes.Count; i++)
if (!inkCanvas.Strokes.Contains(newStrokes[i]))
newStrokes.RemoveAt(i--);
for (var i = 0; i < circles.Count; i++)
if (!inkCanvas.Strokes.Contains(circles[i].Stroke))
circles.RemoveAt(i);
2025-07-29 23:14:20 +08:00
// 处理矩形参考线系统
ProcessRectangleGuideLines(e.Stroke);
2025-05-25 09:29:48 +08:00
var strokeReco = new StrokeCollection();
var result = InkRecognizeHelper.RecognizeShape(newStrokes);
2025-08-03 16:46:33 +08:00
for (var i = newStrokes.Count - 1; i >= 0; i--)
{
2025-05-25 09:29:48 +08:00
strokeReco.Add(newStrokes[i]);
var newResult = InkRecognizeHelper.RecognizeShape(strokeReco);
if (newResult.InkDrawingNode.GetShapeName() == "Circle" ||
2025-08-03 16:46:33 +08:00
newResult.InkDrawingNode.GetShapeName() == "Ellipse")
{
2025-05-25 09:29:48 +08:00
result = newResult;
break;
}
//Label.Visibility = Visibility.Visible;
//Label.Content = circles.Count.ToString() + "\n" + newResult.InkDrawingNode.GetShapeName();
}
if (result.InkDrawingNode.GetShapeName() == "Circle" &&
2025-08-03 16:46:33 +08:00
Settings.InkToShape.IsInkToShapeRounded)
{
2025-05-25 09:29:48 +08:00
var shape = result.InkDrawingNode.GetShape();
2025-08-03 16:46:33 +08:00
if (shape.Width > 75)
{
2025-05-25 09:29:48 +08:00
foreach (var circle in circles)
//判断是否画同心圆
if (Math.Abs(result.Centroid.X - circle.Centroid.X) / shape.Width < 0.12 &&
2025-08-03 16:46:33 +08:00
Math.Abs(result.Centroid.Y - circle.Centroid.Y) / shape.Width < 0.12)
{
2025-05-25 09:29:48 +08:00
result.Centroid = circle.Centroid;
break;
}
2025-08-03 16:46:33 +08:00
else
{
2025-05-25 09:29:48 +08:00
var d = (result.Centroid.X - circle.Centroid.X) *
(result.Centroid.X - circle.Centroid.X) +
(result.Centroid.Y - circle.Centroid.Y) *
(result.Centroid.Y - circle.Centroid.Y);
d = Math.Sqrt(d);
//判断是否画外切圆
var x = shape.Width / 2.0 + circle.R - d;
2025-08-03 16:46:33 +08:00
if (Math.Abs(x) / shape.Width < 0.1)
{
2025-05-25 09:29:48 +08:00
var sinTheta = (result.Centroid.Y - circle.Centroid.Y) / d;
var cosTheta = (result.Centroid.X - circle.Centroid.X) / d;
var newX = result.Centroid.X + x * cosTheta;
var newY = result.Centroid.Y + x * sinTheta;
result.Centroid = new Point(newX, newY);
}
//判断是否画外切圆
x = Math.Abs(circle.R - shape.Width / 2.0) - d;
2025-08-03 16:46:33 +08:00
if (Math.Abs(x) / shape.Width < 0.1)
{
2025-05-25 09:29:48 +08:00
var sinTheta = (result.Centroid.Y - circle.Centroid.Y) / d;
var cosTheta = (result.Centroid.X - circle.Centroid.X) / d;
var newX = result.Centroid.X + x * cosTheta;
var newY = result.Centroid.Y + x * sinTheta;
result.Centroid = new Point(newX, newY);
}
}
var iniP = new Point(result.Centroid.X - shape.Width / 2,
result.Centroid.Y - shape.Height / 2);
var endP = new Point(result.Centroid.X + shape.Width / 2,
result.Centroid.Y + shape.Height / 2);
var pointList = GenerateEllipseGeometry(iniP, endP);
var point = new StylusPointCollection(pointList);
2025-08-03 16:46:33 +08:00
var stroke = new Stroke(point)
{
2025-05-25 09:29:48 +08:00
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
};
circles.Add(new Circle(result.Centroid, shape.Width / 2.0, stroke));
SetNewBackupOfStroke();
_currentCommitType = CommitReason.ShapeRecognition;
inkCanvas.Strokes.Remove(result.InkDrawingNode.Strokes);
inkCanvas.Strokes.Add(stroke);
_currentCommitType = CommitReason.UserInput;
newStrokes = new StrokeCollection();
}
}
else if (result.InkDrawingNode.GetShapeName().Contains("Ellipse") &&
2025-08-03 16:46:33 +08:00
Settings.InkToShape.IsInkToShapeRounded)
{
2025-05-25 09:29:48 +08:00
var shape = result.InkDrawingNode.GetShape();
//var shape1 = result.InkDrawingNode.GetShape();
//shape1.Fill = Brushes.Gray;
//Canvas.Children.Add(shape1);
var p = result.InkDrawingNode.HotPoints;
var a = GetDistance(p[0], p[2]) / 2; //长半轴
var b = GetDistance(p[1], p[3]) / 2; //短半轴
2025-08-03 16:46:33 +08:00
if (a < b)
{
2025-05-25 09:29:48 +08:00
var t = a;
a = b;
b = t;
}
result.Centroid = new Point((p[0].X + p[2].X) / 2, (p[0].Y + p[2].Y) / 2);
var needRotation = true;
2025-08-03 16:46:33 +08:00
if (shape.Width > 75 || (shape.Height > 75 && p.Count == 4))
{
2025-05-25 09:29:48 +08:00
var iniP = new Point(result.Centroid.X - shape.Width / 2,
result.Centroid.Y - shape.Height / 2);
var endP = new Point(result.Centroid.X + shape.Width / 2,
result.Centroid.Y + shape.Height / 2);
foreach (var circle in circles)
//判断是否画同心椭圆
if (Math.Abs(result.Centroid.X - circle.Centroid.X) / a < 0.2 &&
2025-08-03 16:46:33 +08:00
Math.Abs(result.Centroid.Y - circle.Centroid.Y) / a < 0.2)
{
2025-05-25 09:29:48 +08:00
result.Centroid = circle.Centroid;
iniP = new Point(result.Centroid.X - shape.Width / 2,
result.Centroid.Y - shape.Height / 2);
endP = new Point(result.Centroid.X + shape.Width / 2,
result.Centroid.Y + shape.Height / 2);
//再判断是否与圆相切
2025-08-03 16:46:33 +08:00
if (Math.Abs(a - circle.R) / a < 0.2)
{
if (shape.Width >= shape.Height)
{
2025-05-25 09:29:48 +08:00
iniP.X = result.Centroid.X - circle.R;
endP.X = result.Centroid.X + circle.R;
iniP.Y = result.Centroid.Y - b;
endP.Y = result.Centroid.Y + b;
}
2025-08-03 16:46:33 +08:00
else
{
2025-05-25 09:29:48 +08:00
iniP.Y = result.Centroid.Y - circle.R;
endP.Y = result.Centroid.Y + circle.R;
iniP.X = result.Centroid.X - a;
endP.X = result.Centroid.X + a;
}
}
break;
}
2025-08-03 16:46:33 +08:00
else if (Math.Abs(result.Centroid.X - circle.Centroid.X) / a < 0.2)
{
2025-05-25 09:29:48 +08:00
var sinTheta = Math.Abs(circle.Centroid.Y - result.Centroid.Y) /
circle.R;
var cosTheta = Math.Sqrt(1 - sinTheta * sinTheta);
var newA = circle.R * cosTheta;
if (circle.R * sinTheta / circle.R < 0.9 && a / b > 2 &&
2025-08-03 16:46:33 +08:00
Math.Abs(newA - a) / newA < 0.3)
{
2025-05-25 09:29:48 +08:00
iniP.X = circle.Centroid.X - newA;
endP.X = circle.Centroid.X + newA;
iniP.Y = result.Centroid.Y - newA / 5;
endP.Y = result.Centroid.Y + newA / 5;
var topB = endP.Y - iniP.Y;
SetNewBackupOfStroke();
_currentCommitType = CommitReason.ShapeRecognition;
inkCanvas.Strokes.Remove(result.InkDrawingNode.Strokes);
newStrokes = new StrokeCollection();
2025-07-28 14:40:44 +08:00
var _pointList = GenerateEllipseGeometry(iniP, endP, false);
2025-05-25 09:29:48 +08:00
var _point = new StylusPointCollection(_pointList);
2025-08-03 16:46:33 +08:00
var _stroke = new Stroke(_point)
{
2025-05-25 09:29:48 +08:00
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
};
var _dashedLineStroke =
GenerateDashedLineEllipseStrokeCollection(iniP, endP, true, false);
2025-07-28 14:40:44 +08:00
var strokes = new StrokeCollection {
2025-05-25 09:29:48 +08:00
_stroke,
_dashedLineStroke
};
inkCanvas.Strokes.Add(strokes);
_currentCommitType = CommitReason.UserInput;
return;
}
}
2025-08-03 16:46:33 +08:00
else if (Math.Abs(result.Centroid.Y - circle.Centroid.Y) / a < 0.2)
{
2025-05-25 09:29:48 +08:00
var cosTheta = Math.Abs(circle.Centroid.X - result.Centroid.X) /
circle.R;
var sinTheta = Math.Sqrt(1 - cosTheta * cosTheta);
var newA = circle.R * sinTheta;
if (circle.R * sinTheta / circle.R < 0.9 && a / b > 2 &&
2025-08-03 16:46:33 +08:00
Math.Abs(newA - a) / newA < 0.3)
{
2025-05-25 09:29:48 +08:00
iniP.X = result.Centroid.X - newA / 5;
endP.X = result.Centroid.X + newA / 5;
iniP.Y = circle.Centroid.Y - newA;
endP.Y = circle.Centroid.Y + newA;
needRotation = false;
}
}
//纠正垂直与水平关系
var newPoints = FixPointsDirection(p[0], p[2]);
p[0] = newPoints[0];
p[2] = newPoints[1];
newPoints = FixPointsDirection(p[1], p[3]);
p[1] = newPoints[0];
p[3] = newPoints[1];
var pointList = GenerateEllipseGeometry(iniP, endP);
var point = new StylusPointCollection(pointList);
2025-08-03 16:46:33 +08:00
var stroke = new Stroke(point)
{
2025-05-25 09:29:48 +08:00
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
};
2025-08-03 16:46:33 +08:00
if (needRotation)
{
2025-05-25 09:29:48 +08:00
var m = new Matrix();
var fe = e.Source as FrameworkElement;
var tanTheta = (p[2].Y - p[0].Y) / (p[2].X - p[0].X);
var theta = Math.Atan(tanTheta);
m.RotateAt(theta * 180.0 / Math.PI, result.Centroid.X, result.Centroid.Y);
stroke.Transform(m, false);
}
SetNewBackupOfStroke();
_currentCommitType = CommitReason.ShapeRecognition;
inkCanvas.Strokes.Remove(result.InkDrawingNode.Strokes);
inkCanvas.Strokes.Add(stroke);
_currentCommitType = CommitReason.UserInput;
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
newStrokes = new StrokeCollection();
}
}
else if (result.InkDrawingNode.GetShapeName().Contains("Triangle") &&
2025-08-03 16:46:33 +08:00
Settings.InkToShape.IsInkToShapeTriangle)
{
2025-05-25 09:29:48 +08:00
var shape = result.InkDrawingNode.GetShape();
var p = result.InkDrawingNode.HotPoints;
if ((Math.Max(Math.Max(p[0].X, p[1].X), p[2].X) -
Math.Min(Math.Min(p[0].X, p[1].X), p[2].X) >= 100 ||
Math.Max(Math.Max(p[0].Y, p[1].Y), p[2].Y) -
Math.Min(Math.Min(p[0].Y, p[1].Y), p[2].Y) >= 100) &&
2025-08-03 16:46:33 +08:00
result.InkDrawingNode.HotPoints.Count == 3)
{
2025-05-25 09:29:48 +08:00
//纠正垂直与水平关系
var newPoints = FixPointsDirection(p[0], p[1]);
p[0] = newPoints[0];
p[1] = newPoints[1];
newPoints = FixPointsDirection(p[0], p[2]);
p[0] = newPoints[0];
p[2] = newPoints[1];
newPoints = FixPointsDirection(p[1], p[2]);
p[1] = newPoints[0];
p[2] = newPoints[1];
var pointList = p.ToList();
//pointList.Add(p[0]);
var point = new StylusPointCollection(pointList);
2025-08-03 16:46:33 +08:00
var stroke = new Stroke(GenerateFakePressureTriangle(point))
{
2025-05-25 09:29:48 +08:00
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
};
SetNewBackupOfStroke();
_currentCommitType = CommitReason.ShapeRecognition;
inkCanvas.Strokes.Remove(result.InkDrawingNode.Strokes);
inkCanvas.Strokes.Add(stroke);
_currentCommitType = CommitReason.UserInput;
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
newStrokes = new StrokeCollection();
}
}
else if ((result.InkDrawingNode.GetShapeName().Contains("Rectangle") ||
result.InkDrawingNode.GetShapeName().Contains("Diamond") ||
result.InkDrawingNode.GetShapeName().Contains("Parallelogram") ||
result.InkDrawingNode.GetShapeName().Contains("Square") ||
result.InkDrawingNode.GetShapeName().Contains("Trapezoid")) &&
2025-08-03 16:46:33 +08:00
Settings.InkToShape.IsInkToShapeRectangle)
{
2025-05-25 09:29:48 +08:00
var shape = result.InkDrawingNode.GetShape();
var p = result.InkDrawingNode.HotPoints;
if ((Math.Max(Math.Max(Math.Max(p[0].X, p[1].X), p[2].X), p[3].X) -
Math.Min(Math.Min(Math.Min(p[0].X, p[1].X), p[2].X), p[3].X) >= 100 ||
Math.Max(Math.Max(Math.Max(p[0].Y, p[1].Y), p[2].Y), p[3].Y) -
Math.Min(Math.Min(Math.Min(p[0].Y, p[1].Y), p[2].Y), p[3].Y) >= 100) &&
2025-08-03 16:46:33 +08:00
result.InkDrawingNode.HotPoints.Count == 4)
{
2025-05-25 09:29:48 +08:00
//纠正垂直与水平关系
var newPoints = FixPointsDirection(p[0], p[1]);
p[0] = newPoints[0];
p[1] = newPoints[1];
newPoints = FixPointsDirection(p[1], p[2]);
p[1] = newPoints[0];
p[2] = newPoints[1];
newPoints = FixPointsDirection(p[2], p[3]);
p[2] = newPoints[0];
p[3] = newPoints[1];
newPoints = FixPointsDirection(p[3], p[0]);
p[3] = newPoints[0];
p[0] = newPoints[1];
var pointList = p.ToList();
pointList.Add(p[0]);
var point = new StylusPointCollection(pointList);
2025-08-03 16:46:33 +08:00
var stroke = new Stroke(GenerateFakePressureRectangle(point))
{
2025-05-25 09:29:48 +08:00
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
};
SetNewBackupOfStroke();
_currentCommitType = CommitReason.ShapeRecognition;
inkCanvas.Strokes.Remove(result.InkDrawingNode.Strokes);
inkCanvas.Strokes.Add(stroke);
_currentCommitType = CommitReason.UserInput;
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
newStrokes = new StrokeCollection();
}
}
}
catch { }
}
InkToShapeProcess();
}
2025-06-18 13:24:50 +08:00
foreach (var stylusPoint in e.Stroke.StylusPoints)
//LogHelper.WriteLogToFile(stylusPoint.PressureFactor.ToString(), LogHelper.LogType.Info);
// 检查是否是压感笔书写
//if (stylusPoint.PressureFactor != 0.5 && stylusPoint.PressureFactor != 0)
if ((stylusPoint.PressureFactor > 0.501 || stylusPoint.PressureFactor < 0.5) &&
stylusPoint.PressureFactor != 0)
return;
2025-05-25 09:29:48 +08:00
2025-08-03 16:46:33 +08:00
try
{
if (e.Stroke.StylusPoints.Count > 3)
{
2025-05-25 09:29:48 +08:00
var random = new Random();
var _speed = GetPointSpeed(
e.Stroke.StylusPoints[random.Next(0, e.Stroke.StylusPoints.Count - 1)].ToPoint(),
e.Stroke.StylusPoints[random.Next(0, e.Stroke.StylusPoints.Count - 1)].ToPoint(),
e.Stroke.StylusPoints[random.Next(0, e.Stroke.StylusPoints.Count - 1)].ToPoint());
RandWindow.randSeed = (int)(_speed * 100000 * 1000);
}
}
catch { }
2025-08-03 16:46:33 +08:00
switch (Settings.Canvas.InkStyle)
{
2025-05-25 09:29:48 +08:00
case 1:
if (penType == 0)
2025-08-03 16:46:33 +08:00
try
{
2025-05-25 09:29:48 +08:00
var stylusPoints = new StylusPointCollection();
var n = e.Stroke.StylusPoints.Count - 1;
var s = "";
2025-08-03 16:46:33 +08:00
for (var i = 0; i <= n; i++)
{
2025-05-25 09:29:48 +08:00
var speed = GetPointSpeed(e.Stroke.StylusPoints[Math.Max(i - 1, 0)].ToPoint(),
e.Stroke.StylusPoints[i].ToPoint(),
e.Stroke.StylusPoints[Math.Min(i + 1, n)].ToPoint());
2025-07-28 14:40:44 +08:00
s += speed + "\t";
2025-05-25 09:29:48 +08:00
var point = new StylusPoint();
if (speed >= 0.25)
point.PressureFactor = (float)(0.5 - 0.3 * (Math.Min(speed, 1.5) - 0.3) / 1.2);
else if (speed >= 0.05)
point.PressureFactor = (float)0.5;
else
point.PressureFactor = (float)(0.5 + 0.4 * (0.05 - speed) / 0.05);
point.X = e.Stroke.StylusPoints[i].X;
point.Y = e.Stroke.StylusPoints[i].Y;
stylusPoints.Add(point);
}
e.Stroke.StylusPoints = stylusPoints;
}
catch { }
break;
case 0:
if (penType == 0)
2025-08-03 16:46:33 +08:00
try
{
2025-05-25 09:29:48 +08:00
var stylusPoints = new StylusPointCollection();
var n = e.Stroke.StylusPoints.Count - 1;
var pressure = 0.1;
var x = 10;
if (n == 1) return;
2025-08-03 16:46:33 +08:00
if (n >= x)
{
for (var i = 0; i < n - x; i++)
{
2025-05-25 09:29:48 +08:00
var point = new StylusPoint();
point.PressureFactor = (float)0.5;
point.X = e.Stroke.StylusPoints[i].X;
point.Y = e.Stroke.StylusPoints[i].Y;
stylusPoints.Add(point);
}
2025-08-03 16:46:33 +08:00
for (var i = n - x; i <= n; i++)
{
2025-05-25 09:29:48 +08:00
var point = new StylusPoint();
point.PressureFactor = (float)((0.5 - pressure) * (n - i) / x + pressure);
point.X = e.Stroke.StylusPoints[i].X;
point.Y = e.Stroke.StylusPoints[i].Y;
stylusPoints.Add(point);
}
}
2025-08-03 16:46:33 +08:00
else
{
for (var i = 0; i <= n; i++)
{
2025-05-25 09:29:48 +08:00
var point = new StylusPoint();
point.PressureFactor = (float)(0.4 * (n - i) / n + pressure);
point.X = e.Stroke.StylusPoints[i].X;
point.Y = e.Stroke.StylusPoints[i].Y;
stylusPoints.Add(point);
}
}
e.Stroke.StylusPoints = stylusPoints;
}
catch { }
break;
}
}
catch { }
2025-07-20 15:57:51 +08:00
// 应用高级贝塞尔曲线平滑(仅在未进行直线拉直时)
2025-09-20 13:44:01 +08:00
Debug.WriteLine($"墨迹平滑检查: UseAdvancedBezierSmoothing={Settings.Canvas.UseAdvancedBezierSmoothing}, wasStraightened={wasStraightened}");
Debug.WriteLine($"异步平滑设置: UseAsyncInkSmoothing={Settings.Canvas.UseAsyncInkSmoothing}, _inkSmoothingManager={_inkSmoothingManager != null}");
2025-07-20 15:57:51 +08:00
if (Settings.Canvas.UseAdvancedBezierSmoothing && !wasStraightened)
2025-07-20 15:21:59 +08:00
{
try
{
2025-09-20 13:44:01 +08:00
Debug.WriteLine($"开始墨迹平滑处理: 原始点数={e.Stroke.StylusPoints.Count}, 直线拉直={wasStraightened}");
2025-07-20 15:57:51 +08:00
// 检查原始笔画是否仍然存在于画布中
if (inkCanvas.Strokes.Contains(e.Stroke))
2025-07-20 15:21:59 +08:00
{
2025-07-26 19:03:07 +08:00
// 使用新的异步墨迹平滑管理器
if (Settings.Canvas.UseAsyncInkSmoothing && _inkSmoothingManager != null)
2025-07-20 15:57:51 +08:00
{
2025-09-20 13:44:01 +08:00
Debug.WriteLine("使用异步墨迹平滑");
2025-07-26 19:03:07 +08:00
// 异步处理
_ = ProcessStrokeAsync(e.Stroke);
}
else
{
// 同步处理(向后兼容)
var smoothedStroke = _inkSmoothingManager?.SmoothStroke(e.Stroke) ?? e.Stroke;
if (smoothedStroke != e.Stroke)
{
// 替换原始笔画
SetNewBackupOfStroke();
_currentCommitType = CommitReason.ShapeRecognition;
inkCanvas.Strokes.Remove(e.Stroke);
inkCanvas.Strokes.Add(smoothedStroke);
_currentCommitType = CommitReason.UserInput;
}
}
2025-07-20 15:57:51 +08:00
}
2025-09-20 13:44:01 +08:00
else
{
Debug.WriteLine("原始笔画不在画布中,跳过平滑处理");
}
2025-07-20 15:21:59 +08:00
}
catch (Exception ex)
{
// 如果高级平滑失败,回退到原始笔画
2025-07-28 14:40:44 +08:00
Debug.WriteLine($"高级贝塞尔曲线平滑失败: {ex.Message}");
2025-07-20 15:21:59 +08:00
}
}
2025-08-03 16:46:33 +08:00
else if (Settings.Canvas.FitToCurve && !wasStraightened)
2025-07-20 15:21:59 +08:00
{
drawingAttributes.FitToCurve = true;
}
2025-05-25 09:29:48 +08:00
}
2025-07-26 19:03:07 +08:00
/// <summary>
/// 异步处理笔画平滑
/// </summary>
private async Task ProcessStrokeAsync(Stroke originalStroke)
{
try
{
2025-09-20 13:44:01 +08:00
Debug.WriteLine($"异步平滑开始: 原始点数={originalStroke.StylusPoints.Count}");
2025-07-26 19:03:07 +08:00
await _inkSmoothingManager.SmoothStrokeAsync(originalStroke, (original, smoothed) =>
{
2025-09-20 13:44:01 +08:00
Debug.WriteLine($"异步平滑完成: 原始点数={original.StylusPoints.Count}, 平滑后点数={smoothed.StylusPoints.Count}");
Debug.WriteLine($"墨迹比较: smoothed != original = {smoothed != original}");
Debug.WriteLine($"画布包含原始墨迹: {inkCanvas.Strokes.Contains(original)}");
2025-07-26 19:03:07 +08:00
// 在UI线程上执行笔画替换
if (inkCanvas.Strokes.Contains(original) && smoothed != original)
{
2025-09-20 13:44:01 +08:00
Debug.WriteLine("异步替换原始笔画为平滑后的笔画");
2025-07-26 19:03:07 +08:00
SetNewBackupOfStroke();
_currentCommitType = CommitReason.ShapeRecognition;
inkCanvas.Strokes.Remove(original);
inkCanvas.Strokes.Add(smoothed);
_currentCommitType = CommitReason.UserInput;
}
2025-09-20 13:44:01 +08:00
else
{
Debug.WriteLine($"异步平滑后的笔画与原始笔画相同,未进行替换 (contains={inkCanvas.Strokes.Contains(original)}, different={smoothed != original})");
}
2025-07-26 19:03:07 +08:00
});
}
catch (Exception ex)
{
2025-07-28 14:40:44 +08:00
Debug.WriteLine($"异步墨迹平滑失败: {ex.Message}");
2025-07-26 19:03:07 +08:00
}
}
2025-06-18 13:24:50 +08:00
// New method: Checks if a stroke is potentially a straight line
2025-08-03 16:46:33 +08:00
private bool IsPotentialStraightLine(Stroke stroke)
{
2025-06-18 15:10:33 +08:00
// 确保有足够的点来进行线条分析
2025-07-29 01:40:35 +08:00
if (stroke.StylusPoints.Count < 5)
2025-06-18 13:24:50 +08:00
return false;
2025-07-29 01:40:35 +08:00
2025-06-18 13:24:50 +08:00
Point start = stroke.StylusPoints.First().ToPoint();
Point end = stroke.StylusPoints.Last().ToPoint();
double lineLength = GetDistance(start, end);
2025-07-24 21:02:00 +08:00
// 分辨率自适应阈值
double adaptiveThreshold = Settings.Canvas.AutoStraightenLineThreshold * GetResolutionScale();
// 线条必须足够长才考虑拉直,使用自适应阈值
if (lineLength < adaptiveThreshold)
2025-06-18 15:10:33 +08:00
return false;
2025-07-29 01:40:35 +08:00
// 新增:检查墨迹复杂度,避免将复杂图形拉直
if (IsComplexShape(stroke))
return false;
// 新增:检查是否为明显的曲线
if (IsObviousCurve(stroke))
return false;
2025-08-03 16:46:33 +08:00
// 获取用户设置的灵敏度值,确保使用正确的设置
2025-06-18 15:10:33 +08:00
double sensitivity = Settings.InkToShape.LineStraightenSensitivity;
2025-08-03 16:46:33 +08:00
// 输出当前灵敏度值(调试用)
2025-07-28 14:40:44 +08:00
Debug.WriteLine($"IsPotentialStraightLine - sensitivity: {sensitivity}, length: {lineLength}");
2025-08-03 16:46:33 +08:00
// 根据灵敏度调整快速检查阈值
double quickThreshold;
2025-08-03 16:46:33 +08:00
// 如果灵敏度超过1.0,使用更宽松的快速检查标准
2025-08-03 16:46:33 +08:00
if (sensitivity > 1.0)
{
// 高灵敏度模式 - 使用更宽松的阈值
quickThreshold = Math.Min(0.2 + (sensitivity - 1.0) * 0.3, 0.5); // 映射到0.2-0.5范围
2025-08-03 16:46:33 +08:00
}
else
{
// 常规灵敏度模式
quickThreshold = Math.Min(sensitivity * 1.5, 0.20);
}
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
Debug.WriteLine($"使用快速检查阈值: {quickThreshold}");
2025-08-03 16:46:33 +08:00
2025-06-18 15:10:33 +08:00
// 快速检查:计算几个关键点与直线的距离
2025-08-03 16:46:33 +08:00
if (stroke.StylusPoints.Count >= 10)
{
2025-06-18 15:10:33 +08:00
// 取中点和1/4、3/4位置的点,快速检查偏差
int quarterIdx = stroke.StylusPoints.Count / 4;
int midIdx = stroke.StylusPoints.Count / 2;
int threeQuarterIdx = quarterIdx * 3;
2025-08-03 16:46:33 +08:00
2025-06-18 15:10:33 +08:00
Point quarterPoint = stroke.StylusPoints[quarterIdx].ToPoint();
Point midPoint = stroke.StylusPoints[midIdx].ToPoint();
Point threeQuarterPoint = stroke.StylusPoints[threeQuarterIdx].ToPoint();
2025-08-03 16:46:33 +08:00
2025-06-18 15:10:33 +08:00
double quarterDeviation = DistanceFromLineToPoint(start, end, quarterPoint);
double midDeviation = DistanceFromLineToPoint(start, end, midPoint);
double threeQuarterDeviation = DistanceFromLineToPoint(start, end, threeQuarterPoint);
2025-08-03 16:46:33 +08:00
// 使用相对偏差:偏差与线长的比例,并使用灵敏度进行调整
2025-06-18 15:10:33 +08:00
double quickRelativeThreshold = lineLength * quickThreshold;
2025-08-03 16:46:33 +08:00
// 记录检测到的偏差(调试用)
2025-07-28 14:40:44 +08:00
Debug.WriteLine($"Deviations: q={quarterDeviation}, m={midDeviation}, tq={threeQuarterDeviation}, threshold={quickRelativeThreshold}");
2025-08-03 16:46:33 +08:00
// 如果灵敏度超过1.5,则即使有一个点满足条件也认为可能是直线
2025-08-03 16:46:33 +08:00
if (sensitivity > 1.5)
{
// 超高灵敏度模式:只要有一个关键点偏差小,就认为可能是直线
2025-08-03 16:46:33 +08:00
if (quarterDeviation <= quickRelativeThreshold ||
midDeviation <= quickRelativeThreshold ||
threeQuarterDeviation <= quickRelativeThreshold)
{
return true;
}
2025-08-03 16:46:33 +08:00
}
else
{
// 常规判断:如果任一点偏离太大,直接排除
2025-08-03 16:46:33 +08:00
if (quarterDeviation > quickRelativeThreshold ||
midDeviation > quickRelativeThreshold ||
threeQuarterDeviation > quickRelativeThreshold)
{
return false;
}
2025-06-18 15:10:33 +08:00
}
}
2025-08-03 16:46:33 +08:00
2025-06-18 15:10:33 +08:00
return true;
2025-06-18 13:24:50 +08:00
}
2025-07-29 01:40:35 +08:00
/// <summary>
/// 检查墨迹是否为复杂形状(如一团墨迹、涂鸦等)
/// </summary>
private bool IsComplexShape(Stroke stroke)
{
if (stroke.StylusPoints.Count < 10) return false;
Point start = stroke.StylusPoints.First().ToPoint();
Point end = stroke.StylusPoints.Last().ToPoint();
double lineLength = GetDistance(start, end);
// 计算墨迹的实际路径长度
double actualLength = 0;
for (int i = 1; i < stroke.StylusPoints.Count; i++)
{
Point p1 = stroke.StylusPoints[i - 1].ToPoint();
Point p2 = stroke.StylusPoints[i].ToPoint();
actualLength += GetDistance(p1, p2);
}
// 如果实际路径长度远大于直线距离,说明是复杂形状
double complexityRatio = actualLength / Math.Max(lineLength, 1);
if (complexityRatio > 2.5) // 实际路径是直线距离的2.5倍以上
{
Debug.WriteLine($"检测到复杂形状:复杂度比率 = {complexityRatio:F2}");
return true;
}
// 检查方向变化次数
int directionChanges = CountDirectionChanges(stroke);
int maxAllowedChanges = Math.Max(3, stroke.StylusPoints.Count / 20); // 动态阈值
if (directionChanges > maxAllowedChanges)
{
Debug.WriteLine($"检测到复杂形状:方向变化次数 = {directionChanges},阈值 = {maxAllowedChanges}");
return true;
}
// 检查是否有明显的回环或重叠
if (HasSignificantLoops(stroke))
{
Debug.WriteLine("检测到复杂形状:存在明显回环");
return true;
}
return false;
}
/// <summary>
/// 检查是否为明显的曲线(如圆弧、抛物线等)
/// </summary>
private bool IsObviousCurve(Stroke stroke)
{
if (stroke.StylusPoints.Count < 10) return false;
Point start = stroke.StylusPoints.First().ToPoint();
Point end = stroke.StylusPoints.Last().ToPoint();
double lineLength = GetDistance(start, end);
// 检查曲率一致性
if (HasConsistentCurvature(stroke))
{
Debug.WriteLine("检测到明显曲线:曲率一致");
return true;
}
// 检查中点偏移(对圆弧特别有效)
int midIndex = stroke.StylusPoints.Count / 2;
Point midPoint = stroke.StylusPoints[midIndex].ToPoint();
double midDeviation = DistanceFromLineToPoint(start, end, midPoint);
// 如果中点偏移超过线长的15%,且偏移方向一致,可能是圆弧
if (midDeviation > lineLength * 0.15)
{
// 检查偏移方向的一致性
if (IsConsistentArcDirection(stroke))
{
Debug.WriteLine($"检测到明显曲线:中点偏移 = {midDeviation:F2},线长 = {lineLength:F2}");
return true;
}
}
return false;
}
/// <summary>
/// 计算方向变化次数
/// </summary>
private int CountDirectionChanges(Stroke stroke)
{
if (stroke.StylusPoints.Count < 3) return 0;
int changes = 0;
double lastAngle = 0;
bool hasLastAngle = false;
for (int i = 1; i < stroke.StylusPoints.Count - 1; i++)
{
Point p1 = stroke.StylusPoints[i - 1].ToPoint();
Point p2 = stroke.StylusPoints[i].ToPoint();
Point p3 = stroke.StylusPoints[i + 1].ToPoint();
// 计算角度变化
double angle1 = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);
double angle2 = Math.Atan2(p3.Y - p2.Y, p3.X - p2.X);
double angleDiff = Math.Abs(angle2 - angle1);
// 处理角度跨越问题
if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff;
// 如果角度变化超过30度,认为是方向变化
if (angleDiff > Math.PI / 6) // 30度
{
if (hasLastAngle && Math.Abs(angleDiff - lastAngle) > Math.PI / 12) // 15度
{
changes++;
}
lastAngle = angleDiff;
hasLastAngle = true;
}
}
return changes;
}
/// <summary>
/// 检查是否有明显的回环
/// </summary>
private bool HasSignificantLoops(Stroke stroke)
{
if (stroke.StylusPoints.Count < 20) return false;
// 检查起点和终点是否接近(可能是闭合图形)
Point start = stroke.StylusPoints.First().ToPoint();
Point end = stroke.StylusPoints.Last().ToPoint();
double startEndDistance = GetDistance(start, end);
// 计算平均点间距
double totalDistance = 0;
for (int i = 1; i < stroke.StylusPoints.Count; i++)
{
Point p1 = stroke.StylusPoints[i - 1].ToPoint();
Point p2 = stroke.StylusPoints[i].ToPoint();
totalDistance += GetDistance(p1, p2);
}
double avgPointDistance = totalDistance / (stroke.StylusPoints.Count - 1);
// 如果起点和终点很接近,可能是闭合图形
if (startEndDistance < avgPointDistance * 5)
{
return true;
}
// 检查是否有点重复经过相似区域
int overlapCount = 0;
double overlapThreshold = avgPointDistance * 3;
for (int i = 0; i < stroke.StylusPoints.Count - 10; i += 5)
{
Point p1 = stroke.StylusPoints[i].ToPoint();
for (int j = i + 10; j < stroke.StylusPoints.Count; j += 5)
{
Point p2 = stroke.StylusPoints[j].ToPoint();
if (GetDistance(p1, p2) < overlapThreshold)
{
overlapCount++;
if (overlapCount > 3) return true;
}
}
}
return false;
}
/// <summary>
/// 检查曲率是否一致(用于识别圆弧等规则曲线)
/// </summary>
private bool HasConsistentCurvature(Stroke stroke)
{
if (stroke.StylusPoints.Count < 15) return false;
List<double> curvatures = new List<double>();
// 计算每个点的曲率
for (int i = 2; i < stroke.StylusPoints.Count - 2; i++)
{
Point p1 = stroke.StylusPoints[i - 2].ToPoint();
Point p2 = stroke.StylusPoints[i].ToPoint();
Point p3 = stroke.StylusPoints[i + 2].ToPoint();
double curvature = CalculateCurvature(p1, p2, p3);
if (!double.IsNaN(curvature) && !double.IsInfinity(curvature))
{
curvatures.Add(Math.Abs(curvature));
}
}
if (curvatures.Count < 5) return false;
// 计算曲率的标准差
double avgCurvature = curvatures.Average();
double variance = curvatures.Select(c => Math.Pow(c - avgCurvature, 2)).Average();
double stdDev = Math.Sqrt(variance);
// 如果曲率变化很小且平均曲率不为零,可能是规则曲线
return avgCurvature > 0.001 && stdDev / avgCurvature < 0.5;
}
/// <summary>
/// 检查圆弧方向是否一致
/// </summary>
private bool IsConsistentArcDirection(Stroke stroke)
{
if (stroke.StylusPoints.Count < 10) return false;
Point start = stroke.StylusPoints.First().ToPoint();
Point end = stroke.StylusPoints.Last().ToPoint();
int positiveDeviations = 0;
int negativeDeviations = 0;
// 检查多个点相对于直线的偏移方向
for (int i = 1; i < stroke.StylusPoints.Count - 1; i += Math.Max(1, stroke.StylusPoints.Count / 10))
{
Point p = stroke.StylusPoints[i].ToPoint();
double signedDistance = SignedDistanceFromLineToPoint(start, end, p);
if (Math.Abs(signedDistance) > 5) // 忽略很小的偏移
{
if (signedDistance > 0) positiveDeviations++;
else negativeDeviations++;
}
}
// 如果大部分点都在直线的同一侧,说明是一致的弧形
int totalSignificantDeviations = positiveDeviations + negativeDeviations;
if (totalSignificantDeviations < 3) return false;
double consistency = Math.Max(positiveDeviations, negativeDeviations) / (double)totalSignificantDeviations;
return consistency > 0.8; // 80%的点在同一侧
}
/// <summary>
/// 计算三点的曲率
/// </summary>
private double CalculateCurvature(Point p1, Point p2, Point p3)
{
// 使用三点计算曲率的公式
double a = GetDistance(p1, p2);
double b = GetDistance(p2, p3);
double c = GetDistance(p1, p3);
if (a == 0 || b == 0 || c == 0) return 0;
// 使用海伦公式计算面积
double s = (a + b + c) / 2;
double area = Math.Sqrt(s * (s - a) * (s - b) * (s - c));
// 曲率 = 4 * 面积 / (a * b * c)
return 4 * area / (a * b * c);
}
/// <summary>
/// 计算点到直线的有符号距离
/// </summary>
private double SignedDistanceFromLineToPoint(Point lineStart, Point lineEnd, Point point)
{
// 使用叉积计算有符号距离
double dx = lineEnd.X - lineStart.X;
double dy = lineEnd.Y - lineStart.Y;
double lineLength = Math.Sqrt(dx * dx + dy * dy);
if (lineLength == 0) return 0;
return ((lineEnd.Y - lineStart.Y) * point.X - (lineEnd.X - lineStart.X) * point.Y +
lineEnd.X * lineStart.Y - lineEnd.Y * lineStart.X) / lineLength;
}
2025-06-18 13:24:50 +08:00
// New method: Determines if a stroke should be straightened into a line
2025-08-03 16:46:33 +08:00
private bool ShouldStraightenLine(Stroke stroke)
{
2025-06-18 13:24:50 +08:00
Point start = stroke.StylusPoints.First().ToPoint();
Point end = stroke.StylusPoints.Last().ToPoint();
double maxDeviation = 0;
double lineLength = GetDistance(start, end);
2025-07-24 21:02:00 +08:00
// 分辨率自适应阈值
double adaptiveThreshold = Settings.Canvas.AutoStraightenLineThreshold * GetResolutionScale();
// 如果线条太短,不进行拉直处理,使用自适应阈值
2025-08-03 16:46:33 +08:00
if (lineLength < adaptiveThreshold)
{
2025-07-29 01:40:35 +08:00
Debug.WriteLine($"线条太短: {lineLength} < {adaptiveThreshold}");
return false;
}
// 新增:再次检查复杂度(双重保险)
if (IsComplexShape(stroke))
{
Debug.WriteLine("拒绝拉直:检测到复杂形状");
return false;
}
// 新增:检查线条的直线度评分
double straightnessScore = CalculateStraightnessScore(stroke);
double minStraightnessThreshold = 0.7; // 最低直线度要求
if (straightnessScore < minStraightnessThreshold)
{
Debug.WriteLine($"拒绝拉直:直线度评分过低 {straightnessScore:F3} < {minStraightnessThreshold}");
2025-06-18 15:10:33 +08:00
return false;
}
2025-08-03 16:46:33 +08:00
// 获取用户设置的灵敏度值,确保使用正确的值进行后续判断
2025-06-18 15:10:33 +08:00
double sensitivity = Settings.InkToShape.LineStraightenSensitivity;
2025-08-03 16:46:33 +08:00
// 输出详细的调试信息
2025-07-28 14:40:44 +08:00
Debug.WriteLine($"ShouldStraightenLine - sensitivity: {sensitivity}, length: {lineLength}");
2025-08-03 16:46:33 +08:00
// 临时:显示调试消息框
// MessageBox.Show($"灵敏度值: {sensitivity}", "调试信息");
2025-08-03 16:46:33 +08:00
2025-06-18 15:10:33 +08:00
// 计算点与直线的偏差
double totalDeviation = 0;
int pointCount = 0;
2025-08-03 16:46:33 +08:00
2025-06-29 14:15:20 +08:00
// 检查是否启用了高精度直线拉直
bool useHighPrecision = Settings.Canvas.HighPrecisionLineStraighten;
2025-08-03 16:46:33 +08:00
if (useHighPrecision)
{
2025-07-28 14:40:44 +08:00
Debug.WriteLine("使用高精度直线拉直模式");
2025-08-03 16:46:33 +08:00
2025-06-29 14:15:20 +08:00
// 高精度模式:每隔10像素取一个计数点
double strokeLength = 0;
double sampleInterval = 10.0; // 10像素间隔
2025-08-03 16:46:33 +08:00
2025-06-29 14:15:20 +08:00
// 计算笔画的总长度,用于后续采样
2025-08-03 16:46:33 +08:00
for (int i = 1; i < stroke.StylusPoints.Count; i++)
{
Point p1 = stroke.StylusPoints[i - 1].ToPoint();
2025-06-29 14:15:20 +08:00
Point p2 = stroke.StylusPoints[i].ToPoint();
strokeLength += GetDistance(p1, p2);
}
2025-08-03 16:46:33 +08:00
2025-06-29 14:15:20 +08:00
// 如果笔画太短,直接使用所有点
2025-08-03 16:46:33 +08:00
if (strokeLength < sampleInterval * 5)
{
foreach (StylusPoint sp in stroke.StylusPoints)
{
2025-06-29 14:15:20 +08:00
Point p = sp.ToPoint();
double deviation = DistanceFromLineToPoint(start, end, p);
maxDeviation = Math.Max(maxDeviation, deviation);
totalDeviation += deviation;
pointCount++;
}
2025-08-03 16:46:33 +08:00
}
else
{
2025-06-29 14:15:20 +08:00
// 使用等距采样点
double currentLength = 0;
double nextSampleAt = 0;
2025-08-03 16:46:33 +08:00
2025-06-29 14:15:20 +08:00
// 总是包含起点
Point lastPoint = start;
double deviation = DistanceFromLineToPoint(start, end, lastPoint);
maxDeviation = Math.Max(maxDeviation, deviation);
totalDeviation += deviation;
pointCount++;
2025-08-03 16:46:33 +08:00
2025-06-29 14:15:20 +08:00
// 采样中间点
2025-08-03 16:46:33 +08:00
for (int i = 1; i < stroke.StylusPoints.Count; i++)
{
2025-06-29 14:15:20 +08:00
Point currentPoint = stroke.StylusPoints[i].ToPoint();
double segmentLength = GetDistance(lastPoint, currentPoint);
2025-08-03 16:46:33 +08:00
2025-06-29 14:15:20 +08:00
// 如果这段线段跨越了下一个采样点
2025-08-03 16:46:33 +08:00
while (currentLength + segmentLength >= nextSampleAt)
{
2025-06-29 14:15:20 +08:00
// 计算采样点在线段上的位置
double t = (nextSampleAt - currentLength) / segmentLength;
Point samplePoint = new Point(
lastPoint.X + t * (currentPoint.X - lastPoint.X),
lastPoint.Y + t * (currentPoint.Y - lastPoint.Y)
);
2025-08-03 16:46:33 +08:00
2025-06-29 14:15:20 +08:00
// 计算采样点的偏差
deviation = DistanceFromLineToPoint(start, end, samplePoint);
maxDeviation = Math.Max(maxDeviation, deviation);
totalDeviation += deviation;
pointCount++;
2025-08-03 16:46:33 +08:00
2025-06-29 14:15:20 +08:00
// 设置下一个采样点位置
nextSampleAt += sampleInterval;
2025-08-03 16:46:33 +08:00
2025-06-29 14:15:20 +08:00
// 防止无限循环
if (nextSampleAt > strokeLength) break;
}
2025-08-03 16:46:33 +08:00
2025-06-29 14:15:20 +08:00
currentLength += segmentLength;
lastPoint = currentPoint;
}
2025-08-03 16:46:33 +08:00
2025-06-29 14:15:20 +08:00
// 总是包含终点
deviation = DistanceFromLineToPoint(start, end, end);
maxDeviation = Math.Max(maxDeviation, deviation);
totalDeviation += deviation;
pointCount++;
}
2025-08-03 16:46:33 +08:00
}
else
{
2025-06-29 14:15:20 +08:00
// 原始模式:使用所有点
2025-08-03 16:46:33 +08:00
foreach (StylusPoint sp in stroke.StylusPoints)
{
2025-06-29 14:15:20 +08:00
Point p = sp.ToPoint();
double deviation = DistanceFromLineToPoint(start, end, p);
maxDeviation = Math.Max(maxDeviation, deviation);
totalDeviation += deviation;
pointCount++;
}
2025-06-18 15:10:33 +08:00
}
2025-08-03 16:46:33 +08:00
2025-06-18 15:10:33 +08:00
// 计算平均偏差
double avgDeviation = totalDeviation / pointCount;
2025-08-03 16:46:33 +08:00
// 更详细的调试信息
2025-07-28 14:40:44 +08:00
Debug.WriteLine($"Max deviation: {maxDeviation}, Avg: {avgDeviation}, Threshold: {sensitivity * lineLength}, Points: {pointCount}");
2025-08-03 16:46:33 +08:00
// 支持更广泛的灵敏度范围 (0.05-2.0)
2025-08-03 16:46:33 +08:00
// 如果灵敏度高于1.0,使用更宽松的判断标准
2025-08-03 16:46:33 +08:00
if (sensitivity > 1.0)
{
// 高灵敏度模式 - 允许更大的偏差
double adjustedSensitivity = 0.5 + (sensitivity - 1.0) * 1.5; // 映射到0.5-2.0范围
2025-08-03 16:46:33 +08:00
// 只判断平均偏差和相对偏差
2025-08-03 16:46:33 +08:00
if (maxDeviation / lineLength < adjustedSensitivity && avgDeviation < lineLength * 0.1 * adjustedSensitivity)
{
2025-07-28 14:40:44 +08:00
Debug.WriteLine("接受拉直 (高灵敏度模式)");
return true;
}
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
Debug.WriteLine("拒绝拉直 (高灵敏度模式)");
2025-06-18 15:10:33 +08:00
return false;
}
// 否则使用常规判断标准
2025-07-28 14:40:44 +08:00
// 检查点分布的一致性 - 如果有些点偏离很大而其他点很接近直线,表明线条有明显弯曲
double deviationVariance = 0;
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
// 使用相同的高精度/原始模式来计算方差
2025-08-03 16:46:33 +08:00
if (useHighPrecision)
{
2025-07-28 14:40:44 +08:00
// 高精度模式:重新采样计算方差
double strokeLength = 0;
double sampleInterval = 10.0; // 10像素间隔
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
// 计算笔画的总长度,用于后续采样
2025-08-03 16:46:33 +08:00
for (int i = 1; i < stroke.StylusPoints.Count; i++)
{
Point p1 = stroke.StylusPoints[i - 1].ToPoint();
2025-07-28 14:40:44 +08:00
Point p2 = stroke.StylusPoints[i].ToPoint();
strokeLength += GetDistance(p1, p2);
}
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
// 如果笔画太短,直接使用所有点
2025-08-03 16:46:33 +08:00
if (strokeLength < sampleInterval * 5)
{
foreach (StylusPoint sp in stroke.StylusPoints)
{
2025-07-28 14:40:44 +08:00
Point p = sp.ToPoint();
double deviation = DistanceFromLineToPoint(start, end, p);
2025-06-29 14:15:20 +08:00
deviationVariance += Math.Pow(deviation - avgDeviation, 2);
2025-07-28 14:40:44 +08:00
}
2025-08-03 16:46:33 +08:00
}
else
{
2025-07-28 14:40:44 +08:00
// 使用等距采样点
double currentLength = 0;
double nextSampleAt = 0;
Point lastPoint = start;
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
// 起点方差
double deviation = DistanceFromLineToPoint(start, end, lastPoint);
deviationVariance += Math.Pow(deviation - avgDeviation, 2);
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
// 采样中间点
2025-08-03 16:46:33 +08:00
for (int i = 1; i < stroke.StylusPoints.Count; i++)
{
2025-07-28 14:40:44 +08:00
Point currentPoint = stroke.StylusPoints[i].ToPoint();
double segmentLength = GetDistance(lastPoint, currentPoint);
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
// 如果这段线段跨越了下一个采样点
2025-08-03 16:46:33 +08:00
while (currentLength + segmentLength >= nextSampleAt)
{
2025-07-28 14:40:44 +08:00
// 计算采样点在线段上的位置
double t = (nextSampleAt - currentLength) / segmentLength;
Point samplePoint = new Point(
lastPoint.X + t * (currentPoint.X - lastPoint.X),
lastPoint.Y + t * (currentPoint.Y - lastPoint.Y)
);
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
// 计算采样点的方差
deviation = DistanceFromLineToPoint(start, end, samplePoint);
deviationVariance += Math.Pow(deviation - avgDeviation, 2);
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
// 设置下一个采样点位置
nextSampleAt += sampleInterval;
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
// 防止无限循环
if (nextSampleAt > strokeLength) break;
2025-06-29 14:15:20 +08:00
}
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
currentLength += segmentLength;
lastPoint = currentPoint;
2025-06-29 14:15:20 +08:00
}
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
// 终点方差
deviation = DistanceFromLineToPoint(start, end, end);
deviationVariance += Math.Pow(deviation - avgDeviation, 2);
}
2025-08-03 16:46:33 +08:00
}
else
{
2025-07-28 14:40:44 +08:00
// 原始模式:使用所有点计算方差
2025-08-03 16:46:33 +08:00
foreach (StylusPoint sp in stroke.StylusPoints)
{
2025-07-28 14:40:44 +08:00
Point p = sp.ToPoint();
double deviation = DistanceFromLineToPoint(start, end, p);
deviationVariance += Math.Pow(deviation - avgDeviation, 2);
}
}
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
deviationVariance /= pointCount;
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
// 输出更多调试信息
Debug.WriteLine($"Deviation variance: {deviationVariance}, Threshold: {sensitivity * lineLength * 0.05}");
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
// 如果最大偏差超过线长的阈值比例,或者偏差方差较大(表示不均匀弯曲),则不拉直
// 灵敏度越大,容许的偏差越大,更容易将线条识别为直线
2025-08-03 16:46:33 +08:00
if ((maxDeviation / lineLength) > sensitivity)
{
2025-07-28 14:40:44 +08:00
Debug.WriteLine("拒绝拉直:最大偏差过大");
return false;
}
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
// 如果偏差方差大,说明线条弯曲不均匀
// 灵敏度越大,容许的偏差方差越大
2025-08-03 16:46:33 +08:00
if (deviationVariance > (sensitivity * lineLength * 0.05))
{
2025-07-28 14:40:44 +08:00
Debug.WriteLine("拒绝拉直:偏差方差过大");
return false;
}
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
// 检查中点偏离情况 - 针对弧形线条特别有效
2025-08-03 16:46:33 +08:00
if (stroke.StylusPoints.Count > 10)
{
2025-07-28 14:40:44 +08:00
int midIndex = stroke.StylusPoints.Count / 2;
Point midPoint = stroke.StylusPoints[midIndex].ToPoint();
double midDeviation = DistanceFromLineToPoint(start, end, midPoint);
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
// 输出中点偏差信息
Debug.WriteLine($"Mid deviation: {midDeviation}, Threshold: {lineLength * sensitivity * 0.8}");
2025-08-03 16:46:33 +08:00
2025-07-28 14:40:44 +08:00
// 如果中点偏离过大,不拉直
// 使用灵敏度作为判断基准,灵敏度越大,容许的中点偏离越大
2025-08-03 16:46:33 +08:00
if (midDeviation > (lineLength * sensitivity * 0.8))
{
2025-07-28 14:40:44 +08:00
Debug.WriteLine("拒绝拉直:中点偏差过大");
return false;
}
2025-06-18 13:24:50 +08:00
}
2025-08-03 16:46:33 +08:00
2025-07-29 01:40:35 +08:00
Debug.WriteLine($"接受拉直:直线度评分 = {straightnessScore:F3}");
2025-07-28 14:40:44 +08:00
return true;
2025-06-18 13:24:50 +08:00
}
2025-07-29 01:40:35 +08:00
/// <summary>
/// 计算墨迹的直线度评分(0-1,1表示完美直线)
/// </summary>
private double CalculateStraightnessScore(Stroke stroke)
{
if (stroke.StylusPoints.Count < 3) return 0;
Point start = stroke.StylusPoints.First().ToPoint();
Point end = stroke.StylusPoints.Last().ToPoint();
double lineLength = GetDistance(start, end);
if (lineLength == 0) return 0;
// 1. 计算偏差评分(基于点到直线的距离)
double totalDeviation = 0;
double maxDeviation = 0;
int pointCount = 0;
foreach (StylusPoint sp in stroke.StylusPoints)
{
Point p = sp.ToPoint();
double deviation = DistanceFromLineToPoint(start, end, p);
totalDeviation += deviation;
maxDeviation = Math.Max(maxDeviation, deviation);
pointCount++;
}
double avgDeviation = totalDeviation / pointCount;
// 偏差评分:基于平均偏差和最大偏差
double deviationScore = Math.Max(0, 1 - (avgDeviation / (lineLength * 0.05)) - (maxDeviation / (lineLength * 0.1)));
// 2. 计算方向一致性评分
double directionScore = CalculateDirectionConsistency(stroke);
// 3. 计算路径效率评分(实际路径长度 vs 直线距离)
double actualLength = 0;
for (int i = 1; i < stroke.StylusPoints.Count; i++)
{
Point p1 = stroke.StylusPoints[i - 1].ToPoint();
Point p2 = stroke.StylusPoints[i].ToPoint();
actualLength += GetDistance(p1, p2);
}
double efficiencyScore = Math.Max(0, Math.Min(1, lineLength / actualLength));
// 4. 计算端点连接度评分(起点到终点的直接性)
double endpointScore = 1.0; // 默认满分,因为我们已经有了起点和终点
// 综合评分(加权平均)
double finalScore = (deviationScore * 0.4 + directionScore * 0.3 + efficiencyScore * 0.2 + endpointScore * 0.1);
Debug.WriteLine($"直线度评分详情: 偏差={deviationScore:F3}, 方向={directionScore:F3}, 效率={efficiencyScore:F3}, 综合={finalScore:F3}");
return Math.Max(0, Math.Min(1, finalScore));
}
/// <summary>
/// 计算方向一致性评分
/// </summary>
private double CalculateDirectionConsistency(Stroke stroke)
{
if (stroke.StylusPoints.Count < 5) return 1.0;
Point start = stroke.StylusPoints.First().ToPoint();
Point end = stroke.StylusPoints.Last().ToPoint();
// 目标方向
double targetAngle = Math.Atan2(end.Y - start.Y, end.X - start.X);
double totalAngleDifference = 0;
int segmentCount = 0;
// 计算每个线段与目标方向的角度差
for (int i = 1; i < stroke.StylusPoints.Count; i++)
{
Point p1 = stroke.StylusPoints[i - 1].ToPoint();
Point p2 = stroke.StylusPoints[i].ToPoint();
double segmentLength = GetDistance(p1, p2);
if (segmentLength < 2) continue; // 忽略太短的线段
double segmentAngle = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);
double angleDiff = Math.Abs(segmentAngle - targetAngle);
// 处理角度跨越问题
if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff;
totalAngleDifference += angleDiff;
segmentCount++;
}
if (segmentCount == 0) return 1.0;
double avgAngleDifference = totalAngleDifference / segmentCount;
// 将角度差转换为评分(0-1
// 0度差 = 1分,90度差 = 0分
double directionScore = Math.Max(0, 1 - (avgAngleDifference / (Math.PI / 2)));
return directionScore;
}
2025-08-03 16:46:33 +08:00
2025-06-18 13:24:50 +08:00
// New method: Creates a straight line stroke between two points
2025-08-03 16:46:33 +08:00
private StylusPointCollection CreateStraightLine(Point start, Point end)
{
2025-06-18 13:24:50 +08:00
StylusPointCollection points = new StylusPointCollection();
2025-08-03 16:46:33 +08:00
2025-06-18 18:31:03 +08:00
// 根据是否启用压感触屏模式决定如何设置压感
// 如果未启用压感触屏模式,则使用均匀粗细
2025-08-03 16:46:33 +08:00
if (!Settings.Canvas.EnablePressureTouchMode || Settings.Canvas.DisablePressure ||
Settings.InkToShape.IsInkToShapeNoFakePressureRectangle || penType == 1)
{
2025-06-18 18:31:03 +08:00
// 使用均匀粗细(所有点压感值都是0.5f)
points.Add(new StylusPoint(start.X, start.Y, 0.5f));
2025-08-03 16:46:33 +08:00
2025-06-18 18:31:03 +08:00
// 可以添加一些额外的中间点使线条更平滑(均匀粗细)
double distance = GetDistance(start, end);
2025-08-03 16:46:33 +08:00
if (distance > 100)
{
2025-06-18 18:31:03 +08:00
// 对于较长的线条,添加几个中间点
2025-08-03 16:46:33 +08:00
for (int i = 1; i < 3; i++)
{
2025-06-18 18:31:03 +08:00
double ratio = i / 3.0;
Point midPoint = new Point(
start.X + (end.X - start.X) * ratio,
start.Y + (end.Y - start.Y) * ratio);
points.Add(new StylusPoint(midPoint.X, midPoint.Y, 0.5f));
}
}
2025-08-03 16:46:33 +08:00
2025-06-18 18:31:03 +08:00
points.Add(new StylusPoint(end.X, end.Y, 0.5f));
2025-08-03 16:46:33 +08:00
}
else
{
2025-06-18 18:31:03 +08:00
// 启用了压感触屏模式,使用变化的粗细(原有行为)
2025-06-18 13:24:50 +08:00
points.Add(new StylusPoint(start.X, start.Y, 0.4f));
2025-08-03 16:46:33 +08:00
2025-06-18 18:31:03 +08:00
// 添加中点,压感值较高,使线条中间较粗
2025-06-18 13:24:50 +08:00
Point midPoint = new Point((start.X + end.X) / 2, (start.Y + end.Y) / 2);
points.Add(new StylusPoint(midPoint.X, midPoint.Y, 0.8f));
2025-08-03 16:46:33 +08:00
2025-06-18 13:24:50 +08:00
points.Add(new StylusPoint(end.X, end.Y, 0.4f));
}
2025-08-03 16:46:33 +08:00
2025-06-18 13:24:50 +08:00
return points;
}
2025-08-03 16:46:33 +08:00
2025-06-18 13:24:50 +08:00
// New method: Gets distance from point to a line defined by two points
2025-08-03 16:46:33 +08:00
private double DistanceFromLineToPoint(Point lineStart, Point lineEnd, Point point)
{
2025-06-18 13:24:50 +08:00
// Calculate distance from point to line defined by lineStart and lineEnd
double lineLength = GetDistance(lineStart, lineEnd);
if (lineLength == 0) return GetDistance(point, lineStart);
2025-08-03 16:46:33 +08:00
2025-06-18 13:24:50 +08:00
// Calculate the cross product to get the perpendicular distance
2025-08-03 16:46:33 +08:00
double distance = Math.Abs((lineEnd.Y - lineStart.Y) * point.X -
(lineEnd.X - lineStart.X) * point.Y +
2025-06-18 13:24:50 +08:00
lineEnd.X * lineStart.Y - lineEnd.Y * lineStart.X) / lineLength;
return distance;
}
2025-08-03 16:46:33 +08:00
2025-06-18 13:24:50 +08:00
// New method: Attempts to snap endpoints to existing stroke endpoints
2025-08-03 16:46:33 +08:00
private Point[] GetSnappedEndpoints(Point start, Point end)
{
2025-06-19 14:30:24 +08:00
// 如果端点吸附功能关闭,直接返回null
// 这里不再返回原始点,因为调用此方法的地方会判断返回值是否为null
if (!Settings.Canvas.LineEndpointSnapping)
return null;
2025-08-03 16:46:33 +08:00
2025-06-18 13:24:50 +08:00
bool startSnapped = false;
bool endSnapped = false;
Point snappedStart = start;
Point snappedEnd = end;
2025-08-03 16:46:33 +08:00
2025-06-19 14:30:24 +08:00
// 使用设置中的吸附距离阈值
double snapThreshold = Settings.Canvas.LineEndpointSnappingThreshold;
2025-08-03 16:46:33 +08:00
2025-06-18 13:24:50 +08:00
// Check all strokes in canvas for potential snap points
2025-08-03 16:46:33 +08:00
foreach (Stroke stroke in inkCanvas.Strokes)
{
2025-06-18 13:24:50 +08:00
if (stroke.StylusPoints.Count == 0) continue;
2025-08-03 16:46:33 +08:00
2025-06-18 13:24:50 +08:00
// Get stroke endpoints
Point strokeStart = stroke.StylusPoints.First().ToPoint();
Point strokeEnd = stroke.StylusPoints.Last().ToPoint();
2025-08-03 16:46:33 +08:00
2025-06-18 13:24:50 +08:00
// Check if start point should snap to an endpoint
2025-08-03 16:46:33 +08:00
if (!startSnapped)
{
if (GetDistance(start, strokeStart) < snapThreshold)
{
2025-06-18 13:24:50 +08:00
snappedStart = strokeStart;
startSnapped = true;
2025-08-03 16:46:33 +08:00
}
else if (GetDistance(start, strokeEnd) < snapThreshold)
{
2025-06-18 13:24:50 +08:00
snappedStart = strokeEnd;
startSnapped = true;
}
}
2025-08-03 16:46:33 +08:00
2025-06-18 13:24:50 +08:00
// Check if end point should snap to an endpoint
2025-08-03 16:46:33 +08:00
if (!endSnapped)
{
if (GetDistance(end, strokeStart) < snapThreshold)
{
2025-06-18 13:24:50 +08:00
snappedEnd = strokeStart;
endSnapped = true;
2025-08-03 16:46:33 +08:00
}
else if (GetDistance(end, strokeEnd) < snapThreshold)
{
2025-06-18 13:24:50 +08:00
snappedEnd = strokeEnd;
endSnapped = true;
}
}
2025-08-03 16:46:33 +08:00
2025-06-18 13:24:50 +08:00
// If both endpoints are snapped, we're done
if (startSnapped && endSnapped) break;
}
2025-08-03 16:46:33 +08:00
2025-06-18 13:24:50 +08:00
// Return snapped points if any snapping occurred
2025-08-03 16:46:33 +08:00
if (startSnapped || endSnapped)
{
2025-07-28 14:40:44 +08:00
return new[] { snappedStart, snappedEnd };
2025-06-18 13:24:50 +08:00
}
2025-08-03 16:46:33 +08:00
2025-06-18 13:24:50 +08:00
return null;
}
2025-08-03 16:46:33 +08:00
private void SetNewBackupOfStroke()
{
2025-05-25 09:29:48 +08:00
lastTouchDownStrokeCollection = inkCanvas.Strokes.Clone();
var whiteboardIndex = CurrentWhiteboardIndex;
if (currentMode == 0) whiteboardIndex = 0;
strokeCollections[whiteboardIndex] = lastTouchDownStrokeCollection;
}
2025-08-03 16:46:33 +08:00
public double GetDistance(Point point1, Point point2)
{
2025-05-25 09:29:48 +08:00
return Math.Sqrt((point1.X - point2.X) * (point1.X - point2.X) +
(point1.Y - point2.Y) * (point1.Y - point2.Y));
}
2025-08-03 16:46:33 +08:00
public double GetPointSpeed(Point point1, Point point2, Point point3)
{
2025-05-25 09:29:48 +08:00
return (Math.Sqrt((point1.X - point2.X) * (point1.X - point2.X) +
(point1.Y - point2.Y) * (point1.Y - point2.Y))
+ Math.Sqrt((point3.X - point2.X) * (point3.X - point2.X) +
(point3.Y - point2.Y) * (point3.Y - point2.Y)))
/ 20;
}
2025-08-03 16:46:33 +08:00
public Point[] FixPointsDirection(Point p1, Point p2)
{
if (Math.Abs(p1.X - p2.X) / Math.Abs(p1.Y - p2.Y) > 8)
{
2025-05-25 09:29:48 +08:00
//水平
var x = Math.Abs(p1.Y - p2.Y) / 2;
2025-08-03 16:46:33 +08:00
if (p1.Y > p2.Y)
{
2025-05-25 09:29:48 +08:00
p1.Y -= x;
p2.Y += x;
}
2025-08-03 16:46:33 +08:00
else
{
2025-05-25 09:29:48 +08:00
p1.Y += x;
p2.Y -= x;
}
}
2025-08-03 16:46:33 +08:00
else if (Math.Abs(p1.Y - p2.Y) / Math.Abs(p1.X - p2.X) > 8)
{
2025-05-25 09:29:48 +08:00
//垂直
var x = Math.Abs(p1.X - p2.X) / 2;
2025-08-03 16:46:33 +08:00
if (p1.X > p2.X)
{
2025-05-25 09:29:48 +08:00
p1.X -= x;
p2.X += x;
}
2025-08-03 16:46:33 +08:00
else
{
2025-05-25 09:29:48 +08:00
p1.X += x;
p2.X -= x;
}
}
return new Point[2] { p1, p2 };
}
2025-08-03 16:46:33 +08:00
public StylusPointCollection GenerateFakePressureTriangle(StylusPointCollection points)
{
if (Settings.InkToShape.IsInkToShapeNoFakePressureTriangle || penType == 1)
{
2025-05-25 09:29:48 +08:00
var newPoint = new StylusPointCollection();
newPoint.Add(new StylusPoint(points[0].X, points[0].Y));
var cPoint = GetCenterPoint(points[0], points[1]);
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y));
newPoint.Add(new StylusPoint(points[1].X, points[1].Y));
newPoint.Add(new StylusPoint(points[1].X, points[1].Y));
cPoint = GetCenterPoint(points[1], points[2]);
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y));
newPoint.Add(new StylusPoint(points[2].X, points[2].Y));
newPoint.Add(new StylusPoint(points[2].X, points[2].Y));
cPoint = GetCenterPoint(points[2], points[0]);
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y));
newPoint.Add(new StylusPoint(points[0].X, points[0].Y));
return newPoint;
}
2025-08-03 16:46:33 +08:00
else
{
2025-05-25 09:29:48 +08:00
var newPoint = new StylusPointCollection();
newPoint.Add(new StylusPoint(points[0].X, points[0].Y, (float)0.4));
var cPoint = GetCenterPoint(points[0], points[1]);
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y, (float)0.8));
newPoint.Add(new StylusPoint(points[1].X, points[1].Y, (float)0.4));
newPoint.Add(new StylusPoint(points[1].X, points[1].Y, (float)0.4));
cPoint = GetCenterPoint(points[1], points[2]);
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y, (float)0.8));
newPoint.Add(new StylusPoint(points[2].X, points[2].Y, (float)0.4));
newPoint.Add(new StylusPoint(points[2].X, points[2].Y, (float)0.4));
cPoint = GetCenterPoint(points[2], points[0]);
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y, (float)0.8));
newPoint.Add(new StylusPoint(points[0].X, points[0].Y, (float)0.4));
return newPoint;
}
}
2025-07-28 14:40:44 +08:00
public StylusPointCollection GenerateFakePressureRectangle(StylusPointCollection points)
{
2025-08-03 16:46:33 +08:00
if (Settings.InkToShape.IsInkToShapeNoFakePressureRectangle || penType == 1)
{
2025-05-25 09:29:48 +08:00
return points;
}
2025-07-28 14:40:44 +08:00
var newPoint = new StylusPointCollection();
newPoint.Add(new StylusPoint(points[0].X, points[0].Y, (float)0.4));
var cPoint = GetCenterPoint(points[0], points[1]);
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y, (float)0.8));
newPoint.Add(new StylusPoint(points[1].X, points[1].Y, (float)0.4));
newPoint.Add(new StylusPoint(points[1].X, points[1].Y, (float)0.4));
cPoint = GetCenterPoint(points[1], points[2]);
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y, (float)0.8));
newPoint.Add(new StylusPoint(points[2].X, points[2].Y, (float)0.4));
newPoint.Add(new StylusPoint(points[2].X, points[2].Y, (float)0.4));
cPoint = GetCenterPoint(points[2], points[3]);
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y, (float)0.8));
newPoint.Add(new StylusPoint(points[3].X, points[3].Y, (float)0.4));
newPoint.Add(new StylusPoint(points[3].X, points[3].Y, (float)0.4));
cPoint = GetCenterPoint(points[3], points[0]);
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y, (float)0.8));
newPoint.Add(new StylusPoint(points[0].X, points[0].Y, (float)0.4));
return newPoint;
2025-05-25 09:29:48 +08:00
}
2025-08-03 16:46:33 +08:00
public Point GetCenterPoint(Point point1, Point point2)
{
2025-05-25 09:29:48 +08:00
return new Point((point1.X + point2.X) / 2, (point1.Y + point2.Y) / 2);
}
2025-08-03 16:46:33 +08:00
public StylusPoint GetCenterPoint(StylusPoint point1, StylusPoint point2)
{
2025-05-25 09:29:48 +08:00
return new StylusPoint((point1.X + point2.X) / 2, (point1.Y + point2.Y) / 2);
}
2025-07-24 21:02:00 +08:00
// 分辨率自适应:以1080P为基准,返回当前分辨率下的阈值倍数
private double GetResolutionScale()
{
// 以1920x1080为基准
double baseWidth = 1920.0;
double baseHeight = 1080.0;
double screenWidth = SystemParameters.PrimaryScreenWidth;
double screenHeight = SystemParameters.PrimaryScreenHeight;
// 取宽高平均缩放,防止极端比例
double scaleW = screenWidth / baseWidth;
double scaleH = screenHeight / baseHeight;
return (scaleW + scaleH) / 2.0;
}
2025-07-29 23:14:20 +08:00
#region 线
/// <summary>
/// 处理矩形参考线系统
/// </summary>
private void ProcessRectangleGuideLines(Stroke newStroke)
{
// 只有启用矩形识别时才处理
if (!Settings.InkToShape.IsInkToShapeRectangle) return;
// 检查新笔画是否为直线
if (!IsPotentialStraightLine(newStroke)) return;
Point startPoint = newStroke.StylusPoints[0].ToPoint();
Point endPoint = newStroke.StylusPoints[newStroke.StylusPoints.Count - 1].ToPoint();
// 创建新的参考线
var newGuideLine = new RectangleGuideLine(newStroke, startPoint, endPoint);
// 清理过期的参考线(超过30秒的)
CleanupExpiredGuideLines();
// 添加新参考线
rectangleGuideLines.Add(newGuideLine);
// 检查是否可以构成矩形
CheckForRectangleFormation();
}
/// <summary>
/// 清理过期的参考线
/// </summary>
private void CleanupExpiredGuideLines()
{
var expireTime = DateTime.Now.AddSeconds(-30); // 30秒过期
for (int i = rectangleGuideLines.Count - 1; i >= 0; i--)
{
var guideLine = rectangleGuideLines[i];
if (guideLine.CreatedTime < expireTime || !inkCanvas.Strokes.Contains(guideLine.OriginalStroke))
{
rectangleGuideLines.RemoveAt(i);
}
}
}
/// <summary>
/// 检查是否可以构成矩形
/// </summary>
private void CheckForRectangleFormation()
{
if (rectangleGuideLines.Count < 4) return;
// 尝试找到四条能构成矩形的直线
var rectangleLines = FindRectangleLines();
if (rectangleLines != null && rectangleLines.Count == 4)
{
// 创建矩形并替换原有直线
CreateRectangleFromLines(rectangleLines);
}
}
/// <summary>
/// 寻找能构成矩形的四条直线
/// </summary>
private List<RectangleGuideLine> FindRectangleLines()
{
// 按时间排序,优先考虑最近绘制的直线
var sortedLines = rectangleGuideLines.OrderByDescending(l => l.CreatedTime).ToList();
// 尝试不同的四条直线组合
for (int i = 0; i < sortedLines.Count - 3; i++)
{
for (int j = i + 1; j < sortedLines.Count - 2; j++)
{
for (int k = j + 1; k < sortedLines.Count - 1; k++)
{
for (int l = k + 1; l < sortedLines.Count; l++)
{
var lines = new List<RectangleGuideLine> { sortedLines[i], sortedLines[j], sortedLines[k], sortedLines[l] };
if (CanFormRectangle(lines))
{
return lines;
}
}
}
}
}
return null;
}
/// <summary>
/// 判断四条直线是否能构成矩形
/// </summary>
private bool CanFormRectangle(List<RectangleGuideLine> lines)
{
if (lines.Count != 4) return false;
// 分类水平线和垂直线
var horizontalLines = lines.Where(l => l.IsHorizontal).ToList();
var verticalLines = lines.Where(l => l.IsVertical).ToList();
// 必须有2条水平线和2条垂直线
if (horizontalLines.Count != 2 || verticalLines.Count != 2) return false;
// 检查端点相交关系
return CheckEndpointConnections(horizontalLines, verticalLines);
}
/// <summary>
/// 检查端点相交关系
/// </summary>
private bool CheckEndpointConnections(List<RectangleGuideLine> horizontalLines, List<RectangleGuideLine> verticalLines)
{
// 收集所有端点
var allEndpoints = new List<Point>();
foreach (var line in horizontalLines.Concat(verticalLines))
{
allEndpoints.Add(line.StartPoint);
allEndpoints.Add(line.EndPoint);
}
// 检查是否有4个相交点(允许一定误差)
var intersectionPoints = new List<Point>();
foreach (var hLine in horizontalLines)
{
foreach (var vLine in verticalLines)
{
var intersection = GetLineIntersection(hLine, vLine);
if (intersection.HasValue)
{
// 检查交点是否在两条线段的端点附近
if (IsPointNearLineEndpoints(intersection.Value, hLine) &&
IsPointNearLineEndpoints(intersection.Value, vLine))
{
intersectionPoints.Add(intersection.Value);
}
}
}
}
// 需要有4个交点才能构成矩形
return intersectionPoints.Count >= 4;
}
/// <summary>
/// 计算两条直线的交点
/// </summary>
private Point? GetLineIntersection(RectangleGuideLine line1, RectangleGuideLine line2)
{
double x1 = line1.StartPoint.X, y1 = line1.StartPoint.Y;
double x2 = line1.EndPoint.X, y2 = line1.EndPoint.Y;
double x3 = line2.StartPoint.X, y3 = line2.StartPoint.Y;
double x4 = line2.EndPoint.X, y4 = line2.EndPoint.Y;
double denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (Math.Abs(denom) < 1e-10) return null; // 平行线
double t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom;
double u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom;
double intersectionX = x1 + t * (x2 - x1);
double intersectionY = y1 + t * (y2 - y1);
return new Point(intersectionX, intersectionY);
}
/// <summary>
/// 检查点是否在直线端点附近
/// </summary>
private bool IsPointNearLineEndpoints(Point point, RectangleGuideLine line)
{
double distToStart = GetDistance(point, line.StartPoint);
double distToEnd = GetDistance(point, line.EndPoint);
return distToStart <= RECTANGLE_ENDPOINT_THRESHOLD || distToEnd <= RECTANGLE_ENDPOINT_THRESHOLD;
}
/// <summary>
/// 从四条直线创建矩形
/// </summary>
private void CreateRectangleFromLines(List<RectangleGuideLine> lines)
{
try
{
// 计算矩形的四个角点
var corners = CalculateRectangleCorners(lines);
if (corners == null || corners.Count != 4) return;
// 创建矩形笔画
var pointList = new List<Point>(corners) { corners[0] }; // 闭合矩形
var point = new StylusPointCollection(pointList);
var rectangleStroke = new Stroke(GenerateFakePressureRectangle(point))
{
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
};
// 移除原有的四条直线
SetNewBackupOfStroke();
_currentCommitType = CommitReason.ShapeRecognition;
foreach (var line in lines)
{
if (inkCanvas.Strokes.Contains(line.OriginalStroke))
{
inkCanvas.Strokes.Remove(line.OriginalStroke);
}
}
// 添加新的矩形
inkCanvas.Strokes.Add(rectangleStroke);
_currentCommitType = CommitReason.UserInput;
// 清理参考线
foreach (var line in lines)
{
rectangleGuideLines.Remove(line);
}
// 清空新笔画集合,避免重复处理
newStrokes = new StrokeCollection();
Debug.WriteLine("成功创建矩形参考线矩形");
}
catch (Exception ex)
{
Debug.WriteLine($"创建矩形时出错: {ex.Message}");
}
}
/// <summary>
/// 计算矩形的四个角点
/// </summary>
private List<Point> CalculateRectangleCorners(List<RectangleGuideLine> lines)
{
var horizontalLines = lines.Where(l => l.IsHorizontal).ToList();
var verticalLines = lines.Where(l => l.IsVertical).ToList();
if (horizontalLines.Count != 2 || verticalLines.Count != 2) return null;
var corners = new List<Point>();
// 计算四个交点
foreach (var hLine in horizontalLines)
{
foreach (var vLine in verticalLines)
{
var intersection = GetLineIntersection(hLine, vLine);
if (intersection.HasValue)
{
corners.Add(intersection.Value);
}
}
}
if (corners.Count != 4) return null;
// 按顺序排列角点(顺时针或逆时针)
return SortRectangleCorners(corners);
}
/// <summary>
/// 按顺序排列矩形角点
/// </summary>
private List<Point> SortRectangleCorners(List<Point> corners)
{
if (corners.Count != 4) return corners;
// 计算中心点
double centerX = corners.Average(p => p.X);
double centerY = corners.Average(p => p.Y);
var center = new Point(centerX, centerY);
// 按角度排序
return corners.OrderBy(p => Math.Atan2(p.Y - center.Y, p.X - center.X)).ToList();
}
#endregion
2025-05-25 09:29:48 +08:00
}
}