improve:墨迹平滑方案
This commit is contained in:
@@ -10,20 +10,24 @@ using Point = System.Windows.Point;
|
|||||||
namespace Ink_Canvas.Helpers
|
namespace Ink_Canvas.Helpers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 高级贝塞尔曲线平滑算法
|
/// 高级贝塞尔曲线平滑算法 - 优化版本
|
||||||
/// 用于解决墨迹闪烁问题,提供更平滑的笔迹效果
|
/// 用于解决墨迹闪烁问题,提供更平滑的笔迹效果
|
||||||
|
/// 优化特性:
|
||||||
|
/// 1. 更平滑的墨迹:改进的贝塞尔曲线算法
|
||||||
|
/// 2. 更平滑的拐点:优化的控制点计算
|
||||||
|
/// 3. 1像素级别插点:精确到像素级别的曲线生成
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AdvancedBezierSmoothing
|
public class AdvancedBezierSmoothing
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 平滑强度 (0.0 - 1.0)
|
/// 平滑强度 (0.0 - 1.0) - 优化为更平滑的默认值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double SmoothingStrength { get; set; } = 0.6;
|
public double SmoothingStrength { get; set; } = 0.7;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 张力参数 (0.0 - 1.0)
|
/// 张力参数 (0.0 - 1.0) - 优化为更平滑的默认值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double Tension { get; set; } = 0.5;
|
public double Tension { get; set; } = 0.4;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否启用自适应平滑
|
/// 是否启用自适应平滑
|
||||||
@@ -31,29 +35,39 @@ namespace Ink_Canvas.Helpers
|
|||||||
public bool EnableAdaptiveSmoothing { get; set; } = true;
|
public bool EnableAdaptiveSmoothing { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 最小点间距阈值
|
/// 最小点间距阈值 - 优化为1像素级别
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double MinPointDistance { get; set; } = 3.0; // 增加最小间距,减少毛刺
|
public double MinPointDistance { get; set; } = 1.0; // 降低到1像素级别
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 最大点间距阈值
|
/// 最大点间距阈值 - 优化为更精细的控制
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double MaxPointDistance { get; set; } = 30.0; // 减少最大间距,提高平滑度
|
public double MaxPointDistance { get; set; } = 15.0; // 进一步减少最大间距,提高平滑度
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 手抖修正强度 (0.0 - 1.0)
|
/// 手抖修正强度 (0.0 - 1.0) - 优化为更平滑的默认值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double ShakeCorrectionStrength { get; set; } = 0.6;
|
public double ShakeCorrectionStrength { get; set; } = 0.8;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 速度加权平滑强度 (0.0 - 1.0)
|
/// 速度加权平滑强度 (0.0 - 1.0) - 优化为更平滑的默认值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double VelocityWeightedSmoothingStrength { get; set; } = 0.7;
|
public double VelocityWeightedSmoothingStrength { get; set; } = 0.8;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 时间加权平滑强度 (0.0 - 1.0)
|
/// 时间加权平滑强度 (0.0 - 1.0) - 优化为更平滑的默认值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double TimeWeightedSmoothingStrength { get; set; } = 0.5;
|
public double TimeWeightedSmoothingStrength { get; set; } = 0.6;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 拐点平滑强度 (0.0 - 1.0) - 新增参数,优化为更平滑的默认值
|
||||||
|
/// </summary>
|
||||||
|
public double CornerSmoothingStrength { get; set; } = 0.8;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 1像素级别插点精度 - 新增参数
|
||||||
|
/// </summary>
|
||||||
|
public double PixelLevelPrecision { get; set; } = 1.0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 对笔画进行高级贝塞尔曲线平滑处理
|
/// 对笔画进行高级贝塞尔曲线平滑处理
|
||||||
@@ -83,19 +97,22 @@ namespace Ink_Canvas.Helpers
|
|||||||
// 第二步:基于速度和时间的加权平滑
|
// 第二步:基于速度和时间的加权平滑
|
||||||
var velocityTimeWeightedPoints = ApplyVelocityTimeWeightedSmoothing(shakeCorrectedPoints);
|
var velocityTimeWeightedPoints = ApplyVelocityTimeWeightedSmoothing(shakeCorrectedPoints);
|
||||||
|
|
||||||
// 第三步:点过滤和重采样
|
// 第三步:拐点检测和平滑
|
||||||
var filteredPoints = FilterAndResamplePoints(velocityTimeWeightedPoints);
|
var cornerSmoothedPoints = ApplyCornerSmoothing(velocityTimeWeightedPoints);
|
||||||
|
|
||||||
// 第四步:计算控制点
|
// 第四步:点过滤和重采样(1像素级别)
|
||||||
var controlPoints = CalculateControlPoints(filteredPoints);
|
var filteredPoints = FilterAndResamplePoints(cornerSmoothedPoints);
|
||||||
|
|
||||||
// 第五步:生成平滑曲线点
|
// 第五步:计算优化的控制点
|
||||||
var curvePoints = GenerateCurvePoints(filteredPoints, controlPoints);
|
var controlPoints = CalculateOptimizedControlPoints(filteredPoints);
|
||||||
|
|
||||||
// 第六步:修正收尾相连问题
|
// 第六步:生成1像素级别的平滑曲线点
|
||||||
|
var curvePoints = GeneratePixelLevelCurvePoints(filteredPoints, controlPoints);
|
||||||
|
|
||||||
|
// 第七步:修正收尾相连问题
|
||||||
var fixedStylusPoints = FixEndToEndConnection(new StylusPointCollection(curvePoints));
|
var fixedStylusPoints = FixEndToEndConnection(new StylusPointCollection(curvePoints));
|
||||||
|
|
||||||
// 第七步:创建新的笔画
|
// 第八步:创建新的笔画
|
||||||
var smoothedStroke = new Stroke(fixedStylusPoints)
|
var smoothedStroke = new Stroke(fixedStylusPoints)
|
||||||
{
|
{
|
||||||
DrawingAttributes = stroke.DrawingAttributes.Clone()
|
DrawingAttributes = stroke.DrawingAttributes.Clone()
|
||||||
@@ -125,7 +142,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
double endToEndDistance = GetDistance(firstPoint.ToPoint(), lastPoint.ToPoint());
|
double endToEndDistance = GetDistance(firstPoint.ToPoint(), lastPoint.ToPoint());
|
||||||
|
|
||||||
// 如果首尾距离太近,可能是收尾相连问题
|
// 如果首尾距离太近,可能是收尾相连问题
|
||||||
if (endToEndDistance < 5.0)
|
if (endToEndDistance < 3.0) // 降低阈值到3像素
|
||||||
{
|
{
|
||||||
// 移除最后一个点,避免收尾相连
|
// 移除最后一个点,避免收尾相连
|
||||||
if (resultPoints.Count > 1)
|
if (resultPoints.Count > 1)
|
||||||
@@ -138,7 +155,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 过滤和重采样点
|
/// 过滤和重采样点(1像素级别优化)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<StylusPoint> FilterAndResamplePoints(List<StylusPoint> points)
|
private List<StylusPoint> FilterAndResamplePoints(List<StylusPoint> points)
|
||||||
{
|
{
|
||||||
@@ -149,9 +166,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
// 添加第一个点
|
// 添加第一个点
|
||||||
filteredPoints.Add(points[0]);
|
filteredPoints.Add(points[0]);
|
||||||
|
|
||||||
// 使用移动平均来减少毛刺
|
// 使用改进的移动平均来减少毛刺
|
||||||
var smoothedPoints = new List<StylusPoint>();
|
var smoothedPoints = new List<StylusPoint>();
|
||||||
int windowSize = 3; // 移动平均窗口大小
|
int windowSize = 5; // 增加窗口大小以获得更平滑的效果
|
||||||
|
|
||||||
for (int i = 0; i < points.Count; i++)
|
for (int i = 0; i < points.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -160,33 +177,41 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
double distance = GetDistance(lastPoint.ToPoint(), currentPoint.ToPoint());
|
double distance = GetDistance(lastPoint.ToPoint(), currentPoint.ToPoint());
|
||||||
|
|
||||||
// 如果距离太近,跳过
|
// 如果距离太近,跳过(1像素级别)
|
||||||
if (distance < MinPointDistance)
|
if (distance < MinPointDistance)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// 应用移动平均平滑
|
// 应用改进的移动平均平滑
|
||||||
if (i >= windowSize - 1)
|
if (i >= windowSize - 1)
|
||||||
{
|
{
|
||||||
double avgX = 0, avgY = 0, avgPressure = 0;
|
double avgX = 0, avgY = 0, avgPressure = 0;
|
||||||
|
double totalWeight = 0;
|
||||||
|
|
||||||
|
// 使用加权移动平均,中心点权重更高
|
||||||
for (int j = 0; j < windowSize; j++)
|
for (int j = 0; j < windowSize; j++)
|
||||||
{
|
{
|
||||||
var point = points[i - j];
|
var point = points[i - j];
|
||||||
avgX += point.X;
|
double weight = 1.0 - Math.Abs(j - (windowSize - 1) / 2.0) / (windowSize / 2.0);
|
||||||
avgY += point.Y;
|
weight = Math.Max(0.1, weight); // 确保最小权重
|
||||||
avgPressure += point.PressureFactor;
|
|
||||||
|
avgX += point.X * weight;
|
||||||
|
avgY += point.Y * weight;
|
||||||
|
avgPressure += point.PressureFactor * weight;
|
||||||
|
totalWeight += weight;
|
||||||
}
|
}
|
||||||
avgX /= windowSize;
|
|
||||||
avgY /= windowSize;
|
avgX /= totalWeight;
|
||||||
avgPressure /= windowSize;
|
avgY /= totalWeight;
|
||||||
|
avgPressure /= totalWeight;
|
||||||
|
|
||||||
var smoothedPoint = new StylusPoint(avgX, avgY, (float)avgPressure);
|
var smoothedPoint = new StylusPoint(avgX, avgY, (float)avgPressure);
|
||||||
currentPoint = smoothedPoint;
|
currentPoint = smoothedPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果距离太远,插入中间点
|
// 如果距离太远,插入中间点(1像素级别精度)
|
||||||
if (distance > MaxPointDistance)
|
if (distance > MaxPointDistance)
|
||||||
{
|
{
|
||||||
int segments = (int)(distance / MaxPointDistance) + 1;
|
int segments = Math.Max(2, (int)(distance / PixelLevelPrecision));
|
||||||
for (int j = 1; j < segments; j++)
|
for (int j = 1; j < segments; j++)
|
||||||
{
|
{
|
||||||
double ratio = (double)j / segments;
|
double ratio = (double)j / segments;
|
||||||
@@ -202,9 +227,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 计算贝塞尔曲线的控制点
|
/// 计算优化的控制点(改进拐点平滑)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<Point> CalculateControlPoints(List<StylusPoint> points)
|
private List<Point> CalculateOptimizedControlPoints(List<StylusPoint> points)
|
||||||
{
|
{
|
||||||
var controlPoints = new List<Point>();
|
var controlPoints = new List<Point>();
|
||||||
|
|
||||||
@@ -212,8 +237,8 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
// 检查点密度,如果点太稀疏则使用更保守的控制点计算
|
// 检查点密度,如果点太稀疏则使用更保守的控制点计算
|
||||||
bool isSparsePoints = points.Count < 5;
|
bool isSparsePoints = points.Count < 5;
|
||||||
double tensionMultiplier = isSparsePoints ? 0.1 : 0.3; // 稀疏点时使用更小的张力
|
double tensionMultiplier = isSparsePoints ? 0.05 : 0.2; // 进一步减少张力
|
||||||
double maxOffset = isSparsePoints ? 5.0 : 10.0; // 稀疏点时使用更小的最大偏移
|
double maxOffset = isSparsePoints ? 3.0 : 8.0; // 减少最大偏移
|
||||||
|
|
||||||
for (int i = 0; i < points.Count; i++)
|
for (int i = 0; i < points.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -227,15 +252,17 @@ namespace Ink_Canvas.Helpers
|
|||||||
double distance = GetDistance(currentPoint, nextPoint);
|
double distance = GetDistance(currentPoint, nextPoint);
|
||||||
|
|
||||||
// 如果距离太远,使用更保守的控制点
|
// 如果距离太远,使用更保守的控制点
|
||||||
if (distance > 50.0)
|
if (distance > 30.0) // 降低阈值
|
||||||
{
|
{
|
||||||
controlPoint = currentPoint; // 直接使用当前点作为控制点
|
controlPoint = currentPoint; // 直接使用当前点作为控制点
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// 使用更平滑的控制点计算
|
||||||
|
double tension = Tension * tensionMultiplier * CornerSmoothingStrength;
|
||||||
controlPoint = new Point(
|
controlPoint = new Point(
|
||||||
currentPoint.X + (nextPoint.X - currentPoint.X) * Tension * tensionMultiplier,
|
currentPoint.X + (nextPoint.X - currentPoint.X) * tension,
|
||||||
currentPoint.Y + (nextPoint.Y - currentPoint.Y) * Tension * tensionMultiplier
|
currentPoint.Y + (nextPoint.Y - currentPoint.Y) * tension
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,21 +273,23 @@ namespace Ink_Canvas.Helpers
|
|||||||
double distance = GetDistance(currentPoint, prevPoint);
|
double distance = GetDistance(currentPoint, prevPoint);
|
||||||
|
|
||||||
// 如果距离太远,使用更保守的控制点
|
// 如果距离太远,使用更保守的控制点
|
||||||
if (distance > 50.0)
|
if (distance > 30.0) // 降低阈值
|
||||||
{
|
{
|
||||||
controlPoint = currentPoint; // 直接使用当前点作为控制点
|
controlPoint = currentPoint; // 直接使用当前点作为控制点
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// 使用更平滑的控制点计算
|
||||||
|
double tension = Tension * tensionMultiplier * CornerSmoothingStrength;
|
||||||
controlPoint = new Point(
|
controlPoint = new Point(
|
||||||
currentPoint.X + (currentPoint.X - prevPoint.X) * Tension * tensionMultiplier,
|
currentPoint.X + (currentPoint.X - prevPoint.X) * tension,
|
||||||
currentPoint.Y + (currentPoint.Y - prevPoint.Y) * Tension * tensionMultiplier
|
currentPoint.Y + (currentPoint.Y - prevPoint.Y) * tension
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 中间点的控制点
|
// 中间点的控制点(改进拐点平滑)
|
||||||
Point prevPoint = points[i - 1].ToPoint();
|
Point prevPoint = points[i - 1].ToPoint();
|
||||||
Point nextPoint = points[i + 1].ToPoint();
|
Point nextPoint = points[i + 1].ToPoint();
|
||||||
|
|
||||||
@@ -268,7 +297,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
double prevDistance = GetDistance(currentPoint, prevPoint);
|
double prevDistance = GetDistance(currentPoint, prevPoint);
|
||||||
double nextDistance = GetDistance(currentPoint, nextPoint);
|
double nextDistance = GetDistance(currentPoint, nextPoint);
|
||||||
|
|
||||||
if (prevDistance > 50.0 || nextDistance > 50.0)
|
if (prevDistance > 30.0 || nextDistance > 30.0) // 降低阈值
|
||||||
{
|
{
|
||||||
// 距离太远,使用线性插值作为控制点
|
// 距离太远,使用线性插值作为控制点
|
||||||
controlPoint = new Point(
|
controlPoint = new Point(
|
||||||
@@ -278,13 +307,13 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 计算切线方向,使用更保守的方法
|
// 计算改进的切线方向,使用更平滑的方法
|
||||||
double tangentX = (nextPoint.X - prevPoint.X) * tensionMultiplier;
|
double tangentX = (nextPoint.X - prevPoint.X) * tensionMultiplier;
|
||||||
double tangentY = (nextPoint.Y - prevPoint.Y) * tensionMultiplier;
|
double tangentY = (nextPoint.Y - prevPoint.Y) * tensionMultiplier;
|
||||||
|
|
||||||
// 应用张力参数,但限制最大偏移
|
// 应用张力参数和拐点平滑,但限制最大偏移
|
||||||
double offsetX = tangentX * Tension;
|
double offsetX = tangentX * Tension * CornerSmoothingStrength;
|
||||||
double offsetY = tangentY * Tension;
|
double offsetY = tangentY * Tension * CornerSmoothingStrength;
|
||||||
|
|
||||||
// 限制偏移距离
|
// 限制偏移距离
|
||||||
double offsetDistance = Math.Sqrt(offsetX * offsetX + offsetY * offsetY);
|
double offsetDistance = Math.Sqrt(offsetX * offsetX + offsetY * offsetY);
|
||||||
@@ -308,9 +337,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 生成曲线点
|
/// 生成1像素级别的平滑曲线点
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<StylusPoint> GenerateCurvePoints(List<StylusPoint> points, List<Point> controlPoints)
|
private List<StylusPoint> GeneratePixelLevelCurvePoints(List<StylusPoint> points, List<Point> controlPoints)
|
||||||
{
|
{
|
||||||
var curvePoints = new List<StylusPoint>();
|
var curvePoints = new List<StylusPoint>();
|
||||||
|
|
||||||
@@ -324,19 +353,14 @@ namespace Ink_Canvas.Helpers
|
|||||||
var startControl = controlPoints[i];
|
var startControl = controlPoints[i];
|
||||||
var endControl = controlPoints[i + 1];
|
var endControl = controlPoints[i + 1];
|
||||||
|
|
||||||
// 计算自适应步长,减少步数以避免毛刺
|
// 计算1像素级别的步长
|
||||||
double distance = GetDistance(startPoint.ToPoint(), endPoint.ToPoint());
|
double distance = GetDistance(startPoint.ToPoint(), endPoint.ToPoint());
|
||||||
|
|
||||||
// 根据点密度调整步数
|
// 根据距离计算精确的步数,确保1像素级别精度
|
||||||
int steps;
|
int steps = Math.Max(1, (int)(distance / PixelLevelPrecision));
|
||||||
if (points.Count < 5) // 低密度点
|
|
||||||
{
|
// 限制最大步数以避免过度细分
|
||||||
steps = Math.Max(1, Math.Min(3, (int)(distance / 15.0))); // 更少的步数
|
steps = Math.Min(steps, 50);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
steps = Math.Max(2, Math.Min(15, (int)(distance / 8.0))); // 正常步数
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成贝塞尔曲线点
|
// 生成贝塞尔曲线点
|
||||||
for (int j = 0; j <= steps; j++)
|
for (int j = 0; j <= steps; j++)
|
||||||
@@ -362,6 +386,53 @@ namespace Ink_Canvas.Helpers
|
|||||||
return curvePoints;
|
return curvePoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用拐点平滑
|
||||||
|
/// </summary>
|
||||||
|
private List<StylusPoint> ApplyCornerSmoothing(List<StylusPoint> points)
|
||||||
|
{
|
||||||
|
if (points.Count < 3) return points;
|
||||||
|
|
||||||
|
var smoothedPoints = new List<StylusPoint>();
|
||||||
|
smoothedPoints.Add(points[0]); // 添加第一个点
|
||||||
|
|
||||||
|
for (int i = 1; i < points.Count - 1; i++)
|
||||||
|
{
|
||||||
|
var prev = points[i - 1];
|
||||||
|
var curr = points[i];
|
||||||
|
var next = points[i + 1];
|
||||||
|
|
||||||
|
// 计算角度变化
|
||||||
|
double angle1 = Math.Atan2(curr.Y - prev.Y, curr.X - prev.X);
|
||||||
|
double angle2 = Math.Atan2(next.Y - curr.Y, next.X - curr.X);
|
||||||
|
double angleDiff = Math.Abs(angle2 - angle1);
|
||||||
|
|
||||||
|
// 标准化角度差
|
||||||
|
if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff;
|
||||||
|
|
||||||
|
// 如果角度变化太大,认为是拐点
|
||||||
|
if (angleDiff > Math.PI / 4) // 45度阈值
|
||||||
|
{
|
||||||
|
// 应用拐点平滑
|
||||||
|
double smoothingFactor = CornerSmoothingStrength;
|
||||||
|
double smoothedX = curr.X * (1.0 - smoothingFactor) +
|
||||||
|
(prev.X + next.X) * 0.5 * smoothingFactor;
|
||||||
|
double smoothedY = curr.Y * (1.0 - smoothingFactor) +
|
||||||
|
(prev.Y + next.Y) * 0.5 * smoothingFactor;
|
||||||
|
|
||||||
|
var smoothedPoint = new StylusPoint(smoothedX, smoothedY, curr.PressureFactor);
|
||||||
|
smoothedPoints.Add(smoothedPoint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
smoothedPoints.Add(curr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
smoothedPoints.Add(points[points.Count - 1]); // 添加最后一个点
|
||||||
|
return smoothedPoints;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 计算贝塞尔曲线上的点
|
/// 计算贝塞尔曲线上的点
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -445,7 +516,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
double deviation = Math.Sqrt(deviationX * deviationX + deviationY * deviationY);
|
double deviation = Math.Sqrt(deviationX * deviationX + deviationY * deviationY);
|
||||||
|
|
||||||
// 如果偏差超过阈值,认为是手抖
|
// 如果偏差超过阈值,认为是手抖
|
||||||
double shakeThreshold = 5.0; // 手抖检测阈值
|
double shakeThreshold = 3.0; // 降低手抖检测阈值
|
||||||
if (deviation > shakeThreshold)
|
if (deviation > shakeThreshold)
|
||||||
{
|
{
|
||||||
// 应用手抖修正
|
// 应用手抖修正
|
||||||
@@ -505,11 +576,11 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
// 如果点过于密集,增加时间权重
|
// 如果点过于密集,增加时间权重
|
||||||
double avgDistance = velocity;
|
double avgDistance = velocity;
|
||||||
if (avgDistance < 2.0)
|
if (avgDistance < 1.0) // 降低阈值
|
||||||
{
|
{
|
||||||
timeWeight = 1.5; // 增加权重
|
timeWeight = 1.5; // 增加权重
|
||||||
}
|
}
|
||||||
else if (avgDistance > 20.0)
|
else if (avgDistance > 15.0) // 降低阈值
|
||||||
{
|
{
|
||||||
timeWeight = 0.5; // 减少权重
|
timeWeight = 0.5; // 减少权重
|
||||||
}
|
}
|
||||||
@@ -530,7 +601,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
double velocity = velocities[i - 1];
|
double velocity = velocities[i - 1];
|
||||||
// 速度越快,权重越大(更平滑)
|
// 速度越快,权重越大(更平滑)
|
||||||
velocityWeight = Math.Min(2.0, velocity / 10.0 + 0.5);
|
velocityWeight = Math.Min(2.0, velocity / 8.0 + 0.5); // 调整参数
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算加速度权重
|
// 计算加速度权重
|
||||||
@@ -539,7 +610,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
double acceleration = Math.Abs(accelerations[i - 1]);
|
double acceleration = Math.Abs(accelerations[i - 1]);
|
||||||
// 加速度越大,权重越大(更平滑)
|
// 加速度越大,权重越大(更平滑)
|
||||||
accelerationWeight = Math.Min(2.0, acceleration / 5.0 + 0.5);
|
accelerationWeight = Math.Min(2.0, acceleration / 3.0 + 0.5); // 调整参数
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取时间权重
|
// 获取时间权重
|
||||||
@@ -557,7 +628,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
if (totalWeight > 1.0)
|
if (totalWeight > 1.0)
|
||||||
{
|
{
|
||||||
// 向相邻点加权平均
|
// 向相邻点加权平均
|
||||||
double weight = (totalWeight - 1.0) * 0.3; // 限制最大影响
|
double weight = (totalWeight - 1.0) * 0.2; // 减少最大影响
|
||||||
smoothedX = curr.X * (1.0 - weight) + (prev.X + next.X) * 0.5 * weight;
|
smoothedX = curr.X * (1.0 - weight) + (prev.X + next.X) * 0.5 * weight;
|
||||||
smoothedY = curr.Y * (1.0 - weight) + (prev.Y + next.Y) * 0.5 * weight;
|
smoothedY = curr.Y * (1.0 - weight) + (prev.Y + next.Y) * 0.5 * weight;
|
||||||
}
|
}
|
||||||
@@ -590,10 +661,10 @@ namespace Ink_Canvas.Helpers
|
|||||||
// 计算两点间距离
|
// 计算两点间距离
|
||||||
double distance = GetDistance(currentPoint.ToPoint(), nextPoint.ToPoint());
|
double distance = GetDistance(currentPoint.ToPoint(), nextPoint.ToPoint());
|
||||||
|
|
||||||
// 如果距离太远,插入中间点
|
// 如果距离太远,插入中间点(1像素级别)
|
||||||
if (distance > 20.0) // 低采样率下使用更大的阈值
|
if (distance > 15.0) // 降低阈值
|
||||||
{
|
{
|
||||||
int segments = Math.Max(2, Math.Min(5, (int)(distance / 10.0))); // 限制插值段数
|
int segments = Math.Max(2, Math.Min(8, (int)(distance / PixelLevelPrecision))); // 使用像素级别精度
|
||||||
for (int j = 1; j < segments; j++)
|
for (int j = 1; j < segments; j++)
|
||||||
{
|
{
|
||||||
double ratio = (double)j / segments;
|
double ratio = (double)j / segments;
|
||||||
|
|||||||
@@ -799,7 +799,7 @@
|
|||||||
<TextBlock Foreground="#fafafa" Text="平滑强度" VerticalAlignment="Center"
|
<TextBlock Foreground="#fafafa" Text="平滑强度" VerticalAlignment="Center"
|
||||||
FontSize="14" Margin="0,0,16,0" />
|
FontSize="14" Margin="0,0,16,0" />
|
||||||
<Slider Name="AdvancedSmoothingStrengthSlider" Width="150" Minimum="0.1" Maximum="1.0"
|
<Slider Name="AdvancedSmoothingStrengthSlider" Width="150" Minimum="0.1" Maximum="1.0"
|
||||||
Value="0.4" TickFrequency="0.1" IsSnapToTickEnabled="True"
|
Value="0.7" TickFrequency="0.1" IsSnapToTickEnabled="True"
|
||||||
ValueChanged="AdvancedSmoothingStrengthSlider_ValueChanged" />
|
ValueChanged="AdvancedSmoothingStrengthSlider_ValueChanged" />
|
||||||
<TextBlock Foreground="#fafafa" Text="{Binding ElementName=AdvancedSmoothingStrengthSlider, Path=Value, StringFormat={}{0:F1}}"
|
<TextBlock Foreground="#fafafa" Text="{Binding ElementName=AdvancedSmoothingStrengthSlider, Path=Value, StringFormat={}{0:F1}}"
|
||||||
VerticalAlignment="Center" FontSize="14" Margin="16,0,0,0" />
|
VerticalAlignment="Center" FontSize="14" Margin="16,0,0,0" />
|
||||||
@@ -809,7 +809,7 @@
|
|||||||
<TextBlock Foreground="#fafafa" Text="张力参数" VerticalAlignment="Center"
|
<TextBlock Foreground="#fafafa" Text="张力参数" VerticalAlignment="Center"
|
||||||
FontSize="14" Margin="0,0,16,0" />
|
FontSize="14" Margin="0,0,16,0" />
|
||||||
<Slider Name="AdvancedSmoothingTensionSlider" Width="150" Minimum="0.1" Maximum="1.0"
|
<Slider Name="AdvancedSmoothingTensionSlider" Width="150" Minimum="0.1" Maximum="1.0"
|
||||||
Value="0.3" TickFrequency="0.1" IsSnapToTickEnabled="True"
|
Value="0.4" TickFrequency="0.1" IsSnapToTickEnabled="True"
|
||||||
ValueChanged="AdvancedSmoothingTensionSlider_ValueChanged" />
|
ValueChanged="AdvancedSmoothingTensionSlider_ValueChanged" />
|
||||||
<TextBlock Foreground="#fafafa" Text="{Binding ElementName=AdvancedSmoothingTensionSlider, Path=Value, StringFormat={}{0:F1}}"
|
<TextBlock Foreground="#fafafa" Text="{Binding ElementName=AdvancedSmoothingTensionSlider, Path=Value, StringFormat={}{0:F1}}"
|
||||||
VerticalAlignment="Center" FontSize="14" Margin="16,0,0,0" />
|
VerticalAlignment="Center" FontSize="14" Margin="16,0,0,0" />
|
||||||
@@ -827,7 +827,7 @@
|
|||||||
<TextBlock Foreground="#fafafa" Text="手抖修正强度" VerticalAlignment="Center"
|
<TextBlock Foreground="#fafafa" Text="手抖修正强度" VerticalAlignment="Center"
|
||||||
FontSize="14" Margin="0,0,16,0" />
|
FontSize="14" Margin="0,0,16,0" />
|
||||||
<Slider Name="ShakeCorrectionStrengthSlider" Width="150" Minimum="0.0" Maximum="1.0"
|
<Slider Name="ShakeCorrectionStrengthSlider" Width="150" Minimum="0.0" Maximum="1.0"
|
||||||
Value="0.6" TickFrequency="0.1" IsSnapToTickEnabled="True"
|
Value="0.8" TickFrequency="0.1" IsSnapToTickEnabled="True"
|
||||||
ValueChanged="ShakeCorrectionStrengthSlider_ValueChanged" />
|
ValueChanged="ShakeCorrectionStrengthSlider_ValueChanged" />
|
||||||
<TextBlock Foreground="#fafafa" Text="{Binding ElementName=ShakeCorrectionStrengthSlider, Path=Value, StringFormat={}{0:F1}}"
|
<TextBlock Foreground="#fafafa" Text="{Binding ElementName=ShakeCorrectionStrengthSlider, Path=Value, StringFormat={}{0:F1}}"
|
||||||
VerticalAlignment="Center" FontSize="14" Margin="16,0,0,0" />
|
VerticalAlignment="Center" FontSize="14" Margin="16,0,0,0" />
|
||||||
@@ -837,7 +837,7 @@
|
|||||||
<TextBlock Foreground="#fafafa" Text="速度加权强度" VerticalAlignment="Center"
|
<TextBlock Foreground="#fafafa" Text="速度加权强度" VerticalAlignment="Center"
|
||||||
FontSize="14" Margin="0,0,16,0" />
|
FontSize="14" Margin="0,0,16,0" />
|
||||||
<Slider Name="VelocityWeightedSmoothingStrengthSlider" Width="150" Minimum="0.0" Maximum="1.0"
|
<Slider Name="VelocityWeightedSmoothingStrengthSlider" Width="150" Minimum="0.0" Maximum="1.0"
|
||||||
Value="0.7" TickFrequency="0.1" IsSnapToTickEnabled="True"
|
Value="0.8" TickFrequency="0.1" IsSnapToTickEnabled="True"
|
||||||
ValueChanged="VelocityWeightedSmoothingStrengthSlider_ValueChanged" />
|
ValueChanged="VelocityWeightedSmoothingStrengthSlider_ValueChanged" />
|
||||||
<TextBlock Foreground="#fafafa" Text="{Binding ElementName=VelocityWeightedSmoothingStrengthSlider, Path=Value, StringFormat={}{0:F1}}"
|
<TextBlock Foreground="#fafafa" Text="{Binding ElementName=VelocityWeightedSmoothingStrengthSlider, Path=Value, StringFormat={}{0:F1}}"
|
||||||
VerticalAlignment="Center" FontSize="14" Margin="16,0,0,0" />
|
VerticalAlignment="Center" FontSize="14" Margin="16,0,0,0" />
|
||||||
@@ -847,11 +847,21 @@
|
|||||||
<TextBlock Foreground="#fafafa" Text="时间加权强度" VerticalAlignment="Center"
|
<TextBlock Foreground="#fafafa" Text="时间加权强度" VerticalAlignment="Center"
|
||||||
FontSize="14" Margin="0,0,16,0" />
|
FontSize="14" Margin="0,0,16,0" />
|
||||||
<Slider Name="TimeWeightedSmoothingStrengthSlider" Width="150" Minimum="0.0" Maximum="1.0"
|
<Slider Name="TimeWeightedSmoothingStrengthSlider" Width="150" Minimum="0.0" Maximum="1.0"
|
||||||
Value="0.5" TickFrequency="0.1" IsSnapToTickEnabled="True"
|
Value="0.6" TickFrequency="0.1" IsSnapToTickEnabled="True"
|
||||||
ValueChanged="TimeWeightedSmoothingStrengthSlider_ValueChanged" />
|
ValueChanged="TimeWeightedSmoothingStrengthSlider_ValueChanged" />
|
||||||
<TextBlock Foreground="#fafafa" Text="{Binding ElementName=TimeWeightedSmoothingStrengthSlider, Path=Value, StringFormat={}{0:F1}}"
|
<TextBlock Foreground="#fafafa" Text="{Binding ElementName=TimeWeightedSmoothingStrengthSlider, Path=Value, StringFormat={}{0:F1}}"
|
||||||
VerticalAlignment="Center" FontSize="14" Margin="16,0,0,0" />
|
VerticalAlignment="Center" FontSize="14" Margin="16,0,0,0" />
|
||||||
</ui:SimpleStackPanel>
|
</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="CornerSmoothingStrengthSlider" Width="150" Minimum="0.0" Maximum="1.0"
|
||||||
|
Value="0.8" TickFrequency="0.1" IsSnapToTickEnabled="True"
|
||||||
|
ValueChanged="CornerSmoothingStrengthSlider_ValueChanged" />
|
||||||
|
<TextBlock Foreground="#fafafa" Text="{Binding ElementName=CornerSmoothingStrengthSlider, Path=Value, StringFormat={}{0:F1}}"
|
||||||
|
VerticalAlignment="Center" FontSize="14" Margin="16,0,0,0" />
|
||||||
|
</ui:SimpleStackPanel>
|
||||||
<TextBlock Text="# 高级贝塞尔曲线平滑(推荐):使用自定义算法替代系统默认的FitToCurve,提供更平滑的笔迹效果并解决墨迹闪烁、毛刺和低采样率收尾相连问题。启用高级平滑时会自动禁用系统默认平滑。手抖修正可自动检测并修正手抖造成的偏差,速度加权平滑根据笔迹速度调整平滑强度,时间加权平滑根据点密度调整平滑参数,自适应平滑会根据笔迹速度自动调整平滑参数。低采样率时自动使用线性插值避免收尾相连。" TextWrapping="Wrap" Foreground="#a1a1aa" />
|
<TextBlock Text="# 高级贝塞尔曲线平滑(推荐):使用自定义算法替代系统默认的FitToCurve,提供更平滑的笔迹效果并解决墨迹闪烁、毛刺和低采样率收尾相连问题。启用高级平滑时会自动禁用系统默认平滑。手抖修正可自动检测并修正手抖造成的偏差,速度加权平滑根据笔迹速度调整平滑强度,时间加权平滑根据点密度调整平滑参数,自适应平滑会根据笔迹速度自动调整平滑参数。低采样率时自动使用线性插值避免收尾相连。" TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||||
</ui:SimpleStackPanel>
|
</ui:SimpleStackPanel>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
|
|||||||
@@ -1347,6 +1347,12 @@ namespace Ink_Canvas {
|
|||||||
SaveSettingsToFile();
|
SaveSettingsToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CornerSmoothingStrengthSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
|
||||||
|
if (!isLoaded) return;
|
||||||
|
Settings.Canvas.CornerSmoothingStrength = CornerSmoothingStrengthSlider.Value;
|
||||||
|
SaveSettingsToFile();
|
||||||
|
}
|
||||||
|
|
||||||
private void ToggleSwitchAutoSaveStrokesInPowerPoint_Toggled(object sender, RoutedEventArgs e) {
|
private void ToggleSwitchAutoSaveStrokesInPowerPoint_Toggled(object sender, RoutedEventArgs e) {
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint = ToggleSwitchAutoSaveStrokesInPowerPoint.IsOn;
|
Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint = ToggleSwitchAutoSaveStrokesInPowerPoint.IsOn;
|
||||||
|
|||||||
@@ -543,6 +543,7 @@ namespace Ink_Canvas {
|
|||||||
ShakeCorrectionStrengthSlider.Value = Settings.Canvas.ShakeCorrectionStrength;
|
ShakeCorrectionStrengthSlider.Value = Settings.Canvas.ShakeCorrectionStrength;
|
||||||
VelocityWeightedSmoothingStrengthSlider.Value = Settings.Canvas.VelocityWeightedSmoothingStrength;
|
VelocityWeightedSmoothingStrengthSlider.Value = Settings.Canvas.VelocityWeightedSmoothingStrength;
|
||||||
TimeWeightedSmoothingStrengthSlider.Value = Settings.Canvas.TimeWeightedSmoothingStrength;
|
TimeWeightedSmoothingStrengthSlider.Value = Settings.Canvas.TimeWeightedSmoothingStrength;
|
||||||
|
CornerSmoothingStrengthSlider.Value = Settings.Canvas.CornerSmoothingStrength;
|
||||||
|
|
||||||
// 初始化直线自动拉直相关设置
|
// 初始化直线自动拉直相关设置
|
||||||
ToggleSwitchAutoStraightenLine.IsOn = Settings.Canvas.AutoStraightenLine;
|
ToggleSwitchAutoStraightenLine.IsOn = Settings.Canvas.AutoStraightenLine;
|
||||||
|
|||||||
@@ -1602,7 +1602,9 @@ namespace Ink_Canvas {
|
|||||||
EnableAdaptiveSmoothing = Settings.Canvas.EnableAdaptiveSmoothing,
|
EnableAdaptiveSmoothing = Settings.Canvas.EnableAdaptiveSmoothing,
|
||||||
ShakeCorrectionStrength = Settings.Canvas.ShakeCorrectionStrength,
|
ShakeCorrectionStrength = Settings.Canvas.ShakeCorrectionStrength,
|
||||||
VelocityWeightedSmoothingStrength = Settings.Canvas.VelocityWeightedSmoothingStrength,
|
VelocityWeightedSmoothingStrength = Settings.Canvas.VelocityWeightedSmoothingStrength,
|
||||||
TimeWeightedSmoothingStrength = Settings.Canvas.TimeWeightedSmoothingStrength
|
TimeWeightedSmoothingStrength = Settings.Canvas.TimeWeightedSmoothingStrength,
|
||||||
|
CornerSmoothingStrength = Settings.Canvas.CornerSmoothingStrength,
|
||||||
|
PixelLevelPrecision = Settings.Canvas.PixelLevelPrecision
|
||||||
};
|
};
|
||||||
|
|
||||||
// 对临时笔画应用平滑
|
// 对临时笔画应用平滑
|
||||||
|
|||||||
@@ -594,7 +594,9 @@ namespace Ink_Canvas {
|
|||||||
EnableAdaptiveSmoothing = Settings.Canvas.EnableAdaptiveSmoothing,
|
EnableAdaptiveSmoothing = Settings.Canvas.EnableAdaptiveSmoothing,
|
||||||
ShakeCorrectionStrength = Settings.Canvas.ShakeCorrectionStrength,
|
ShakeCorrectionStrength = Settings.Canvas.ShakeCorrectionStrength,
|
||||||
VelocityWeightedSmoothingStrength = Settings.Canvas.VelocityWeightedSmoothingStrength,
|
VelocityWeightedSmoothingStrength = Settings.Canvas.VelocityWeightedSmoothingStrength,
|
||||||
TimeWeightedSmoothingStrength = Settings.Canvas.TimeWeightedSmoothingStrength
|
TimeWeightedSmoothingStrength = Settings.Canvas.TimeWeightedSmoothingStrength,
|
||||||
|
CornerSmoothingStrength = Settings.Canvas.CornerSmoothingStrength,
|
||||||
|
PixelLevelPrecision = Settings.Canvas.PixelLevelPrecision
|
||||||
};
|
};
|
||||||
|
|
||||||
var smoothedStroke = advancedSmoothing.SmoothStroke(e.Stroke);
|
var smoothedStroke = advancedSmoothing.SmoothStroke(e.Stroke);
|
||||||
|
|||||||
@@ -51,17 +51,21 @@ namespace Ink_Canvas
|
|||||||
[JsonProperty("useAdvancedBezierSmoothing")]
|
[JsonProperty("useAdvancedBezierSmoothing")]
|
||||||
public bool UseAdvancedBezierSmoothing { get; set; } = true; // 默认启用高级贝塞尔曲线平滑
|
public bool UseAdvancedBezierSmoothing { get; set; } = true; // 默认启用高级贝塞尔曲线平滑
|
||||||
[JsonProperty("advancedSmoothingStrength")]
|
[JsonProperty("advancedSmoothingStrength")]
|
||||||
public double AdvancedSmoothingStrength { get; set; } = 0.4; // 高级平滑强度 (0.0 - 1.0),降低以减少毛刺
|
public double AdvancedSmoothingStrength { get; set; } = 0.7; // 高级平滑强度 (0.0 - 1.0),提高以获得更平滑的效果
|
||||||
[JsonProperty("advancedSmoothingTension")]
|
[JsonProperty("advancedSmoothingTension")]
|
||||||
public double AdvancedSmoothingTension { get; set; } = 0.3; // 高级平滑张力 (0.0 - 1.0),降低以减少毛刺
|
public double AdvancedSmoothingTension { get; set; } = 0.4; // 高级平滑张力 (0.0 - 1.0),适度提高以获得更平滑的曲线
|
||||||
[JsonProperty("enableAdaptiveSmoothing")]
|
[JsonProperty("enableAdaptiveSmoothing")]
|
||||||
public bool EnableAdaptiveSmoothing { get; set; } = true; // 是否启用自适应平滑
|
public bool EnableAdaptiveSmoothing { get; set; } = true; // 是否启用自适应平滑
|
||||||
[JsonProperty("shakeCorrectionStrength")]
|
[JsonProperty("shakeCorrectionStrength")]
|
||||||
public double ShakeCorrectionStrength { get; set; } = 0.6; // 手抖修正强度 (0.0 - 1.0)
|
public double ShakeCorrectionStrength { get; set; } = 0.8; // 手抖修正强度 (0.0 - 1.0),提高以减少手抖
|
||||||
[JsonProperty("velocityWeightedSmoothingStrength")]
|
[JsonProperty("velocityWeightedSmoothingStrength")]
|
||||||
public double VelocityWeightedSmoothingStrength { get; set; } = 0.7; // 速度加权平滑强度 (0.0 - 1.0)
|
public double VelocityWeightedSmoothingStrength { get; set; } = 0.8; // 速度加权平滑强度 (0.0 - 1.0),提高以获得更平滑的效果
|
||||||
[JsonProperty("timeWeightedSmoothingStrength")]
|
[JsonProperty("timeWeightedSmoothingStrength")]
|
||||||
public double TimeWeightedSmoothingStrength { get; set; } = 0.5; // 时间加权平滑强度 (0.0 - 1.0)
|
public double TimeWeightedSmoothingStrength { get; set; } = 0.6; // 时间加权平滑强度 (0.0 - 1.0),提高以获得更平滑的效果
|
||||||
|
[JsonProperty("cornerSmoothingStrength")]
|
||||||
|
public double CornerSmoothingStrength { get; set; } = 0.8; // 拐点平滑强度 (0.0 - 1.0),新增参数用于更平滑的拐点
|
||||||
|
[JsonProperty("pixelLevelPrecision")]
|
||||||
|
public double PixelLevelPrecision { get; set; } = 1.0; // 1像素级别插点精度,新增参数用于精确控制
|
||||||
[JsonProperty("clearCanvasAndClearTimeMachine")]
|
[JsonProperty("clearCanvasAndClearTimeMachine")]
|
||||||
public bool ClearCanvasAndClearTimeMachine { get; set; } = false;
|
public bool ClearCanvasAndClearTimeMachine { get; set; } = false;
|
||||||
[JsonProperty("enablePressureTouchMode")]
|
[JsonProperty("enablePressureTouchMode")]
|
||||||
|
|||||||
Reference in New Issue
Block a user