improve:插入图片及墨迹平滑
This commit is contained in:
@@ -7,11 +7,12 @@ using System.Threading.Tasks;
|
|||||||
using System.Windows.Ink;
|
using System.Windows.Ink;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers
|
namespace Ink_Canvas.Helpers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步硬件加速的墨迹平滑处理器
|
/// 改进的异步硬件加速墨迹平滑处理器,使用优化的三次贝塞尔曲线拟合
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AsyncAdvancedBezierSmoothing
|
public class AsyncAdvancedBezierSmoothing
|
||||||
{
|
{
|
||||||
@@ -26,11 +27,13 @@ namespace Ink_Canvas.Helpers
|
|||||||
_processingTasks = new ConcurrentDictionary<Stroke, CancellationTokenSource>();
|
_processingTasks = new ConcurrentDictionary<Stroke, CancellationTokenSource>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public double SmoothingStrength { get; set; } = 0.3; // 大幅降低强度
|
public double SmoothingStrength { get; set; } = 0.4; // 适中的平滑强度
|
||||||
public double ResampleInterval { get; set; } = 3.0; // 大幅增加间隔减少点数
|
public double ResampleInterval { get; set; } = 2.5; // 适中的重采样间隔
|
||||||
public int InterpolationSteps { get; set; } = 8; // 从4增加到8,提高插值步数
|
public int InterpolationSteps { get; set; } = 12; // 增加插值步数提高精度
|
||||||
public bool UseHardwareAcceleration { get; set; } = true;
|
public bool UseHardwareAcceleration { get; set; } = true;
|
||||||
public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount;
|
public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount;
|
||||||
|
public bool UseAdaptiveInterpolation { get; set; } = true; // 自适应插值
|
||||||
|
public double CurveTension { get; set; } = 0.3; // 曲线张力参数
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步平滑笔画
|
/// 异步平滑笔画
|
||||||
@@ -89,16 +92,16 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
// 简化处理:只进行轻度平滑,避免点数爆炸
|
// 使用改进的贝塞尔曲线拟合
|
||||||
var smoothedPoints = ApplyLightSmoothing(originalPoints);
|
var smoothedPoints = ApplyImprovedBezierSmoothing(originalPoints);
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
// 确保点数不会过多
|
// 确保点数合理
|
||||||
if (smoothedPoints.Length > originalPoints.Length * 2)
|
if (smoothedPoints.Length > originalPoints.Length * 3)
|
||||||
{
|
{
|
||||||
// 如果点数增加太多,回退到原始笔画
|
// 如果点数增加太多,进行重采样
|
||||||
return stroke;
|
smoothedPoints = ResampleEquidistantOptimized(smoothedPoints, ResampleInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建平滑后的笔画
|
// 创建平滑后的笔画
|
||||||
@@ -111,34 +114,155 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 轻度平滑处理,避免点数爆炸
|
/// 改进的贝塞尔曲线平滑处理
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private StylusPoint[] ApplyLightSmoothing(StylusPoint[] points)
|
private StylusPoint[] ApplyImprovedBezierSmoothing(StylusPoint[] points)
|
||||||
{
|
{
|
||||||
if (points.Length < 3) return points;
|
if (points.Length < 4) return points;
|
||||||
|
|
||||||
var result = new List<StylusPoint>();
|
var result = new List<StylusPoint>();
|
||||||
result.Add(points[0]); // 保持第一个点
|
|
||||||
|
// 添加第一个点
|
||||||
|
result.Add(points[0]);
|
||||||
|
|
||||||
// 简单的3点平均平滑
|
// 使用滑动窗口进行贝塞尔曲线拟合
|
||||||
for (int i = 1; i < points.Length - 1; i++)
|
for (int i = 0; i <= points.Length - 4; i++)
|
||||||
{
|
{
|
||||||
var prev = points[i - 1];
|
var p0 = points[i];
|
||||||
var curr = points[i];
|
var p1 = points[i + 1];
|
||||||
var next = points[i + 1];
|
var p2 = points[i + 2];
|
||||||
|
var p3 = points[i + 3];
|
||||||
|
|
||||||
// 3点平均
|
// 计算改进的控制点
|
||||||
double x = (prev.X + curr.X + next.X) / 3.0;
|
var controlPoints = CalculateImprovedControlPoints(p0, p1, p2, p3);
|
||||||
double y = (prev.Y + curr.Y + next.Y) / 3.0;
|
|
||||||
float pressure = (prev.PressureFactor + curr.PressureFactor + next.PressureFactor) / 3.0f;
|
// 自适应插值步数
|
||||||
|
int steps = UseAdaptiveInterpolation ?
|
||||||
|
CalculateAdaptiveSteps(p0, p1, p2, p3) : InterpolationSteps;
|
||||||
|
|
||||||
result.Add(new StylusPoint(x, y, Math.Max(pressure, 0.1f)));
|
// 生成贝塞尔曲线点
|
||||||
|
for (int j = 1; j <= steps; j++) // 从1开始避免重复第一个点
|
||||||
|
{
|
||||||
|
double t = (double)j / steps;
|
||||||
|
var bezierPoint = CubicBezierWithControlPoints(controlPoints, t, p0, p3);
|
||||||
|
result.Add(bezierPoint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Add(points[points.Length - 1]); // 保持最后一个点
|
// 添加最后一个点
|
||||||
|
result.Add(points[points.Length - 1]);
|
||||||
|
|
||||||
return result.ToArray();
|
return result.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算改进的控制点
|
||||||
|
/// </summary>
|
||||||
|
private (Point cp1, Point cp2) CalculateImprovedControlPoints(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
|
||||||
|
{
|
||||||
|
// 计算切线方向
|
||||||
|
var tangent1 = new Vector(p1.X - p0.X, p1.Y - p0.Y);
|
||||||
|
var tangent2 = new Vector(p3.X - p2.X, p3.Y - p2.Y);
|
||||||
|
|
||||||
|
// 归一化切线
|
||||||
|
if (tangent1.Length > 0) tangent1.Normalize();
|
||||||
|
if (tangent2.Length > 0) tangent2.Normalize();
|
||||||
|
|
||||||
|
// 计算控制点距离(基于点间距离)
|
||||||
|
double dist1 = Math.Sqrt((p1.X - p0.X) * (p1.X - p0.X) + (p1.Y - p0.Y) * (p1.Y - p0.Y));
|
||||||
|
double dist2 = Math.Sqrt((p3.X - p2.X) * (p3.X - p2.X) + (p3.Y - p2.Y) * (p3.Y - p2.Y));
|
||||||
|
|
||||||
|
double controlDist1 = dist1 * CurveTension;
|
||||||
|
double controlDist2 = dist2 * CurveTension;
|
||||||
|
|
||||||
|
// 计算控制点
|
||||||
|
var cp1 = new Point(
|
||||||
|
p1.X + tangent1.X * controlDist1,
|
||||||
|
p1.Y + tangent1.Y * controlDist1
|
||||||
|
);
|
||||||
|
|
||||||
|
var cp2 = new Point(
|
||||||
|
p2.X - tangent2.X * controlDist2,
|
||||||
|
p2.Y - tangent2.Y * controlDist2
|
||||||
|
);
|
||||||
|
|
||||||
|
return (cp1, cp2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 自适应插值步数计算
|
||||||
|
/// </summary>
|
||||||
|
private int CalculateAdaptiveSteps(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
|
||||||
|
{
|
||||||
|
// 基于曲线长度和复杂度计算步数
|
||||||
|
double totalLength = 0;
|
||||||
|
totalLength += Math.Sqrt((p1.X - p0.X) * (p1.X - p0.X) + (p1.Y - p0.Y) * (p1.Y - p0.Y));
|
||||||
|
totalLength += Math.Sqrt((p2.X - p1.X) * (p2.X - p1.X) + (p2.Y - p1.Y) * (p2.Y - p1.Y));
|
||||||
|
totalLength += Math.Sqrt((p3.X - p2.X) * (p3.X - p2.X) + (p3.Y - p2.Y) * (p3.Y - p2.Y));
|
||||||
|
|
||||||
|
// 计算曲率(简化版本)
|
||||||
|
double curvature = CalculateCurvature(p0, p1, p2, p3);
|
||||||
|
|
||||||
|
// 基于长度和曲率计算步数
|
||||||
|
int baseSteps = Math.Max(8, Math.Min(20, (int)(totalLength / 10)));
|
||||||
|
int curvatureSteps = (int)(curvature * 10);
|
||||||
|
|
||||||
|
return Math.Max(InterpolationSteps, Math.Min(24, baseSteps + curvatureSteps));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算曲率(简化版本)
|
||||||
|
/// </summary>
|
||||||
|
private double CalculateCurvature(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
|
||||||
|
{
|
||||||
|
// 计算三个向量的角度变化
|
||||||
|
var v1 = new Vector(p1.X - p0.X, p1.Y - p0.Y);
|
||||||
|
var v2 = new Vector(p2.X - p1.X, p2.Y - p1.Y);
|
||||||
|
var v3 = new Vector(p3.X - p2.X, p3.Y - p2.Y);
|
||||||
|
|
||||||
|
if (v1.Length == 0 || v2.Length == 0 || v3.Length == 0) return 0;
|
||||||
|
|
||||||
|
v1.Normalize();
|
||||||
|
v2.Normalize();
|
||||||
|
v3.Normalize();
|
||||||
|
|
||||||
|
// 计算角度变化
|
||||||
|
double angle1 = Math.Acos(Math.Max(-1, Math.Min(1, Vector.Multiply(v1, v2))));
|
||||||
|
double angle2 = Math.Acos(Math.Max(-1, Math.Min(1, Vector.Multiply(v2, v3))));
|
||||||
|
|
||||||
|
return (angle1 + angle2) / Math.PI; // 归一化到0-1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用控制点的三次贝塞尔曲线计算
|
||||||
|
/// </summary>
|
||||||
|
private StylusPoint CubicBezierWithControlPoints((Point cp1, Point cp2) controlPoints, double t, StylusPoint p0, StylusPoint p3)
|
||||||
|
{
|
||||||
|
var p1 = controlPoints.cp1;
|
||||||
|
var p2 = controlPoints.cp2;
|
||||||
|
|
||||||
|
double u = 1 - t;
|
||||||
|
double tt = t * t;
|
||||||
|
double uu = u * u;
|
||||||
|
double uuu = uu * u;
|
||||||
|
double ttt = tt * t;
|
||||||
|
|
||||||
|
// 预计算系数
|
||||||
|
double c0 = uuu;
|
||||||
|
double c1 = 3 * uu * t;
|
||||||
|
double c2 = 3 * u * tt;
|
||||||
|
double c3 = ttt;
|
||||||
|
|
||||||
|
double x = c0 * p0.X + c1 * p1.X + c2 * p2.X + c3 * p3.X;
|
||||||
|
double y = c0 * p0.Y + c1 * p1.Y + c2 * p2.Y + c3 * p3.Y;
|
||||||
|
|
||||||
|
// 插值压力值
|
||||||
|
float pressure = (float)(p0.PressureFactor * u + p3.PressureFactor * t);
|
||||||
|
pressure = Math.Max(pressure, 0.1f);
|
||||||
|
|
||||||
|
return new StylusPoint(x, y, pressure);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 硬件加速的向量化指数平滑
|
/// 硬件加速的向量化指数平滑
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -197,61 +197,5 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 质量配置枚举
|
|
||||||
/// </summary>
|
|
||||||
public enum InkSmoothingQuality
|
|
||||||
{
|
|
||||||
HighPerformance = 0, // 高性能低质量
|
|
||||||
Balanced = 1, // 平衡
|
|
||||||
HighQuality = 2 // 高质量低性能
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 墨迹平滑配置
|
|
||||||
/// </summary>
|
|
||||||
public class InkSmoothingConfig
|
|
||||||
{
|
|
||||||
public InkSmoothingQuality Quality { get; set; } = InkSmoothingQuality.HighQuality;
|
|
||||||
public bool UseHardwareAcceleration { get; set; } = true;
|
|
||||||
public bool UseAsyncProcessing { get; set; } = true;
|
|
||||||
public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount;
|
|
||||||
public double SmoothingStrength { get; set; } = 0.8; // 高质量模式的平滑强度
|
|
||||||
public double ResampleInterval { get; set; } = 0.8; // 高质量模式的重采样间隔
|
|
||||||
public int InterpolationSteps { get; set; } = 64; // 高质量模式的插值步数
|
|
||||||
|
|
||||||
public static InkSmoothingConfig FromSettings()
|
|
||||||
{
|
|
||||||
return new InkSmoothingConfig
|
|
||||||
{
|
|
||||||
Quality = (InkSmoothingQuality)MainWindow.Settings.Canvas.InkSmoothingQuality,
|
|
||||||
UseHardwareAcceleration = MainWindow.Settings.Canvas.UseHardwareAcceleration,
|
|
||||||
UseAsyncProcessing = MainWindow.Settings.Canvas.UseAsyncInkSmoothing,
|
|
||||||
MaxConcurrentTasks = MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks > 0 ?
|
|
||||||
MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks : Environment.ProcessorCount
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ApplyQualitySettings()
|
|
||||||
{
|
|
||||||
switch (Quality)
|
|
||||||
{
|
|
||||||
case InkSmoothingQuality.HighPerformance:
|
|
||||||
SmoothingStrength = 0.4;
|
|
||||||
ResampleInterval = 2.0;
|
|
||||||
InterpolationSteps = 16;
|
|
||||||
break;
|
|
||||||
case InkSmoothingQuality.Balanced:
|
|
||||||
SmoothingStrength = 0.6;
|
|
||||||
ResampleInterval = 1.2;
|
|
||||||
InterpolationSteps = 32;
|
|
||||||
break;
|
|
||||||
case InkSmoothingQuality.HighQuality:
|
|
||||||
SmoothingStrength = 0.8;
|
|
||||||
ResampleInterval = 0.8;
|
|
||||||
InterpolationSteps = 64;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,325 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Ink;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Helpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 改进的三次贝塞尔曲线平滑算法
|
||||||
|
/// </summary>
|
||||||
|
public class ImprovedBezierSmoothing
|
||||||
|
{
|
||||||
|
private readonly InkSmoothingConfig _config;
|
||||||
|
|
||||||
|
public ImprovedBezierSmoothing(InkSmoothingConfig config = null)
|
||||||
|
{
|
||||||
|
_config = config ?? new InkSmoothingConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用改进的贝塞尔曲线算法平滑笔画
|
||||||
|
/// </summary>
|
||||||
|
public Stroke SmoothStroke(Stroke originalStroke)
|
||||||
|
{
|
||||||
|
if (originalStroke == null || originalStroke.StylusPoints.Count < 3)
|
||||||
|
return originalStroke;
|
||||||
|
|
||||||
|
var originalPoints = originalStroke.StylusPoints.ToArray();
|
||||||
|
|
||||||
|
// 预处理:去除噪声点
|
||||||
|
var cleanedPoints = RemoveNoisePoints(originalPoints);
|
||||||
|
|
||||||
|
// 使用改进的贝塞尔曲线拟合
|
||||||
|
var smoothedPoints = ApplyCubicBezierSmoothing(cleanedPoints);
|
||||||
|
|
||||||
|
// 后处理:重采样和优化
|
||||||
|
var finalPoints = PostProcessPoints(smoothedPoints);
|
||||||
|
|
||||||
|
return new Stroke(new StylusPointCollection(finalPoints))
|
||||||
|
{
|
||||||
|
DrawingAttributes = originalStroke.DrawingAttributes.Clone()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 去除噪声点
|
||||||
|
/// </summary>
|
||||||
|
private StylusPoint[] RemoveNoisePoints(StylusPoint[] points)
|
||||||
|
{
|
||||||
|
if (points.Length < 3) return points;
|
||||||
|
|
||||||
|
var result = new List<StylusPoint> { points[0] };
|
||||||
|
double minDistance = _config.ResampleInterval * 0.5;
|
||||||
|
|
||||||
|
for (int i = 1; i < points.Length - 1; i++)
|
||||||
|
{
|
||||||
|
var prev = result[result.Count - 1];
|
||||||
|
var curr = points[i];
|
||||||
|
var next = points[i + 1];
|
||||||
|
|
||||||
|
// 计算到前一个点的距离
|
||||||
|
double distToPrev = Math.Sqrt((curr.X - prev.X) * (curr.X - prev.X) +
|
||||||
|
(curr.Y - prev.Y) * (curr.Y - prev.Y));
|
||||||
|
|
||||||
|
// 如果距离太近,跳过这个点
|
||||||
|
if (distToPrev < minDistance)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// 检查是否为异常点(与前后点形成锐角)
|
||||||
|
if (IsOutlierPoint(prev, curr, next))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
result.Add(curr);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add(points[points.Length - 1]);
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查是否为异常点
|
||||||
|
/// </summary>
|
||||||
|
private bool IsOutlierPoint(StylusPoint prev, StylusPoint curr, StylusPoint next)
|
||||||
|
{
|
||||||
|
var v1 = new Vector(curr.X - prev.X, curr.Y - prev.Y);
|
||||||
|
var v2 = new Vector(next.X - curr.X, next.Y - curr.Y);
|
||||||
|
|
||||||
|
if (v1.Length == 0 || v2.Length == 0) return false;
|
||||||
|
|
||||||
|
v1.Normalize();
|
||||||
|
v2.Normalize();
|
||||||
|
|
||||||
|
double dotProduct = Vector.Multiply(v1, v2);
|
||||||
|
double angle = Math.Acos(Math.Max(-1, Math.Min(1, dotProduct)));
|
||||||
|
|
||||||
|
// 如果角度小于30度,认为是异常点
|
||||||
|
return angle < Math.PI / 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用三次贝塞尔曲线平滑
|
||||||
|
/// </summary>
|
||||||
|
private StylusPoint[] ApplyCubicBezierSmoothing(StylusPoint[] points)
|
||||||
|
{
|
||||||
|
if (points.Length < 4) return points;
|
||||||
|
|
||||||
|
var result = new List<StylusPoint>();
|
||||||
|
result.Add(points[0]);
|
||||||
|
|
||||||
|
// 使用滑动窗口进行贝塞尔曲线拟合
|
||||||
|
for (int i = 0; i <= points.Length - 4; i++)
|
||||||
|
{
|
||||||
|
var p0 = points[i];
|
||||||
|
var p1 = points[i + 1];
|
||||||
|
var p2 = points[i + 2];
|
||||||
|
var p3 = points[i + 3];
|
||||||
|
|
||||||
|
// 计算控制点
|
||||||
|
var controlPoints = CalculateOptimalControlPoints(p0, p1, p2, p3);
|
||||||
|
|
||||||
|
// 计算插值步数
|
||||||
|
int steps = CalculateInterpolationSteps(p0, p1, p2, p3);
|
||||||
|
|
||||||
|
// 生成贝塞尔曲线点
|
||||||
|
for (int j = 1; j <= steps; j++)
|
||||||
|
{
|
||||||
|
double t = (double)j / steps;
|
||||||
|
var bezierPoint = CalculateBezierPoint(p0, controlPoints.cp1, controlPoints.cp2, p3, t);
|
||||||
|
result.Add(bezierPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add(points[points.Length - 1]);
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算最优控制点
|
||||||
|
/// </summary>
|
||||||
|
private (Point cp1, Point cp2) CalculateOptimalControlPoints(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
|
||||||
|
{
|
||||||
|
// 计算切线方向
|
||||||
|
var tangent1 = CalculateTangent(p0, p1, p2);
|
||||||
|
var tangent2 = CalculateTangent(p1, p2, p3);
|
||||||
|
|
||||||
|
// 计算控制点距离
|
||||||
|
double dist1 = CalculateDistance(p0, p1);
|
||||||
|
double dist2 = CalculateDistance(p2, p3);
|
||||||
|
|
||||||
|
double controlDist1 = dist1 * _config.CurveTension;
|
||||||
|
double controlDist2 = dist2 * _config.CurveTension;
|
||||||
|
|
||||||
|
// 计算控制点
|
||||||
|
var cp1 = new Point(
|
||||||
|
p1.X + tangent1.X * controlDist1,
|
||||||
|
p1.Y + tangent1.Y * controlDist1
|
||||||
|
);
|
||||||
|
|
||||||
|
var cp2 = new Point(
|
||||||
|
p2.X - tangent2.X * controlDist2,
|
||||||
|
p2.Y - tangent2.Y * controlDist2
|
||||||
|
);
|
||||||
|
|
||||||
|
return (cp1, cp2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算切线方向
|
||||||
|
/// </summary>
|
||||||
|
private Vector CalculateTangent(StylusPoint p0, StylusPoint p1, StylusPoint p2)
|
||||||
|
{
|
||||||
|
var v1 = new Vector(p1.X - p0.X, p1.Y - p0.Y);
|
||||||
|
var v2 = new Vector(p2.X - p1.X, p2.Y - p1.Y);
|
||||||
|
|
||||||
|
// 如果向量长度为零,返回零向量
|
||||||
|
if (v1.Length == 0 || v2.Length == 0)
|
||||||
|
return new Vector(0, 0);
|
||||||
|
|
||||||
|
v1.Normalize();
|
||||||
|
v2.Normalize();
|
||||||
|
|
||||||
|
// 返回平均方向
|
||||||
|
var tangent = (v1 + v2) / 2;
|
||||||
|
if (tangent.Length > 0)
|
||||||
|
tangent.Normalize();
|
||||||
|
|
||||||
|
return tangent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算两点间距离
|
||||||
|
/// </summary>
|
||||||
|
private double CalculateDistance(StylusPoint p1, StylusPoint p2)
|
||||||
|
{
|
||||||
|
double dx = p2.X - p1.X;
|
||||||
|
double dy = p2.Y - p1.Y;
|
||||||
|
return Math.Sqrt(dx * dx + dy * dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算插值步数
|
||||||
|
/// </summary>
|
||||||
|
private int CalculateInterpolationSteps(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
|
||||||
|
{
|
||||||
|
if (!_config.UseAdaptiveInterpolation)
|
||||||
|
return _config.InterpolationSteps;
|
||||||
|
|
||||||
|
// 计算曲线长度
|
||||||
|
double totalLength = CalculateDistance(p0, p1) + CalculateDistance(p1, p2) + CalculateDistance(p2, p3);
|
||||||
|
|
||||||
|
// 计算曲率
|
||||||
|
double curvature = CalculateCurvature(p0, p1, p2, p3);
|
||||||
|
|
||||||
|
// 基于长度和曲率计算步数
|
||||||
|
int baseSteps = Math.Max(8, Math.Min(20, (int)(totalLength / 10)));
|
||||||
|
int curvatureSteps = (int)(curvature * 15);
|
||||||
|
|
||||||
|
return Math.Max(_config.InterpolationSteps, Math.Min(30, baseSteps + curvatureSteps));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算曲率
|
||||||
|
/// </summary>
|
||||||
|
private double CalculateCurvature(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
|
||||||
|
{
|
||||||
|
var v1 = new Vector(p1.X - p0.X, p1.Y - p0.Y);
|
||||||
|
var v2 = new Vector(p2.X - p1.X, p2.Y - p1.Y);
|
||||||
|
var v3 = new Vector(p3.X - p2.X, p3.Y - p2.Y);
|
||||||
|
|
||||||
|
if (v1.Length == 0 || v2.Length == 0 || v3.Length == 0) return 0;
|
||||||
|
|
||||||
|
v1.Normalize();
|
||||||
|
v2.Normalize();
|
||||||
|
v3.Normalize();
|
||||||
|
|
||||||
|
// 计算角度变化
|
||||||
|
double angle1 = Math.Acos(Math.Max(-1, Math.Min(1, Vector.Multiply(v1, v2))));
|
||||||
|
double angle2 = Math.Acos(Math.Max(-1, Math.Min(1, Vector.Multiply(v2, v3))));
|
||||||
|
|
||||||
|
return (angle1 + angle2) / Math.PI; // 归一化到0-1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算贝塞尔曲线上的点
|
||||||
|
/// </summary>
|
||||||
|
private StylusPoint CalculateBezierPoint(StylusPoint p0, Point cp1, Point cp2, StylusPoint p3, double t)
|
||||||
|
{
|
||||||
|
double u = 1 - t;
|
||||||
|
double tt = t * t;
|
||||||
|
double uu = u * u;
|
||||||
|
double uuu = uu * u;
|
||||||
|
double ttt = tt * t;
|
||||||
|
|
||||||
|
// 预计算系数
|
||||||
|
double c0 = uuu;
|
||||||
|
double c1 = 3 * uu * t;
|
||||||
|
double c2 = 3 * u * tt;
|
||||||
|
double c3 = ttt;
|
||||||
|
|
||||||
|
double x = c0 * p0.X + c1 * cp1.X + c2 * cp2.X + c3 * p3.X;
|
||||||
|
double y = c0 * p0.Y + c1 * cp1.Y + c2 * cp2.Y + c3 * p3.Y;
|
||||||
|
|
||||||
|
// 插值压力值
|
||||||
|
float pressure = (float)(p0.PressureFactor * u + p3.PressureFactor * t);
|
||||||
|
pressure = Math.Max(pressure, 0.1f);
|
||||||
|
|
||||||
|
return new StylusPoint(x, y, pressure);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 后处理点集
|
||||||
|
/// </summary>
|
||||||
|
private StylusPoint[] PostProcessPoints(StylusPoint[] points)
|
||||||
|
{
|
||||||
|
if (points.Length == 0) return points;
|
||||||
|
|
||||||
|
// 如果点数过多,进行重采样
|
||||||
|
if (points.Length > _config.MaxPointsPerStroke)
|
||||||
|
{
|
||||||
|
return ResamplePoints(points, _config.ResampleInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重采样点集
|
||||||
|
/// </summary>
|
||||||
|
private StylusPoint[] ResamplePoints(StylusPoint[] points, double interval)
|
||||||
|
{
|
||||||
|
var result = new List<StylusPoint> { points[0] };
|
||||||
|
double accumulated = 0;
|
||||||
|
|
||||||
|
for (int i = 1; i < points.Length; i++)
|
||||||
|
{
|
||||||
|
var prev = result[result.Count - 1];
|
||||||
|
var curr = points[i];
|
||||||
|
double dx = curr.X - prev.X;
|
||||||
|
double dy = curr.Y - prev.Y;
|
||||||
|
double dist = Math.Sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
if (dist + accumulated >= interval)
|
||||||
|
{
|
||||||
|
double t = (interval - accumulated) / dist;
|
||||||
|
double x = prev.X + t * dx;
|
||||||
|
double y = prev.Y + t * dy;
|
||||||
|
float pressure = (float)(prev.PressureFactor * (1 - t) + curr.PressureFactor * t);
|
||||||
|
pressure = Math.Max(pressure, 0.1f);
|
||||||
|
|
||||||
|
result.Add(new StylusPoint(x, y, pressure));
|
||||||
|
accumulated = 0;
|
||||||
|
i--; // 重新处理当前点
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
accumulated += dist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
using System;
|
||||||
|
using System.Configuration;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Helpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 墨迹平滑配置类
|
||||||
|
/// </summary>
|
||||||
|
public class InkSmoothingConfig
|
||||||
|
{
|
||||||
|
// 基本平滑参数
|
||||||
|
public double SmoothingStrength { get; set; } = 0.4;
|
||||||
|
public double ResampleInterval { get; set; } = 2.5;
|
||||||
|
public int InterpolationSteps { get; set; } = 12;
|
||||||
|
|
||||||
|
// 贝塞尔曲线参数
|
||||||
|
public bool UseAdaptiveInterpolation { get; set; } = true;
|
||||||
|
public double CurveTension { get; set; } = 0.3;
|
||||||
|
public double MinCurvatureThreshold { get; set; } = 0.1;
|
||||||
|
public double MaxCurvatureThreshold { get; set; } = 0.8;
|
||||||
|
|
||||||
|
// 性能参数
|
||||||
|
public bool UseHardwareAcceleration { get; set; } = true;
|
||||||
|
public bool UseAsyncProcessing { get; set; } = true;
|
||||||
|
public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount;
|
||||||
|
public int MaxPointsPerStroke { get; set; } = 10000;
|
||||||
|
|
||||||
|
// 质量设置
|
||||||
|
public SmoothingQuality Quality { get; set; } = SmoothingQuality.Balanced;
|
||||||
|
|
||||||
|
public enum SmoothingQuality
|
||||||
|
{
|
||||||
|
Performance, // 性能优先
|
||||||
|
Balanced, // 平衡
|
||||||
|
Quality // 质量优先
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容性枚举
|
||||||
|
public enum InkSmoothingQuality
|
||||||
|
{
|
||||||
|
HighPerformance = 0, // 高性能低质量
|
||||||
|
Balanced = 1, // 平衡
|
||||||
|
HighQuality = 2 // 高质量低性能
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从设置中加载配置
|
||||||
|
/// </summary>
|
||||||
|
public static InkSmoothingConfig FromSettings()
|
||||||
|
{
|
||||||
|
var config = new InkSmoothingConfig();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 尝试从MainWindow.Settings加载配置(兼容性)
|
||||||
|
if (MainWindow.Settings?.Canvas != null)
|
||||||
|
{
|
||||||
|
config.Quality = (SmoothingQuality)MainWindow.Settings.Canvas.InkSmoothingQuality;
|
||||||
|
config.UseHardwareAcceleration = MainWindow.Settings.Canvas.UseHardwareAcceleration;
|
||||||
|
config.UseAsyncProcessing = MainWindow.Settings.Canvas.UseAsyncInkSmoothing;
|
||||||
|
config.MaxConcurrentTasks = MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks > 0 ?
|
||||||
|
MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks : Environment.ProcessorCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"加载平滑配置失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用质量设置
|
||||||
|
/// </summary>
|
||||||
|
public void ApplyQualitySettings()
|
||||||
|
{
|
||||||
|
switch (Quality)
|
||||||
|
{
|
||||||
|
case SmoothingQuality.Performance:
|
||||||
|
SmoothingStrength = 0.2;
|
||||||
|
ResampleInterval = 4.0;
|
||||||
|
InterpolationSteps = 6;
|
||||||
|
UseAdaptiveInterpolation = false;
|
||||||
|
CurveTension = 0.2;
|
||||||
|
MaxConcurrentTasks = Math.Max(1, Environment.ProcessorCount / 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SmoothingQuality.Balanced:
|
||||||
|
SmoothingStrength = 0.4;
|
||||||
|
ResampleInterval = 2.5;
|
||||||
|
InterpolationSteps = 12;
|
||||||
|
UseAdaptiveInterpolation = true;
|
||||||
|
CurveTension = 0.3;
|
||||||
|
MaxConcurrentTasks = Environment.ProcessorCount;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SmoothingQuality.Quality:
|
||||||
|
SmoothingStrength = 0.6;
|
||||||
|
ResampleInterval = 1.5;
|
||||||
|
InterpolationSteps = 20;
|
||||||
|
UseAdaptiveInterpolation = true;
|
||||||
|
CurveTension = 0.4;
|
||||||
|
MaxConcurrentTasks = Environment.ProcessorCount;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 保存配置到设置
|
||||||
|
/// </summary>
|
||||||
|
public void SaveToSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 尝试保存到MainWindow.Settings(兼容性)
|
||||||
|
if (MainWindow.Settings?.Canvas != null)
|
||||||
|
{
|
||||||
|
MainWindow.Settings.Canvas.InkSmoothingQuality = (int)Quality;
|
||||||
|
MainWindow.Settings.Canvas.UseHardwareAcceleration = UseHardwareAcceleration;
|
||||||
|
MainWindow.Settings.Canvas.UseAsyncInkSmoothing = UseAsyncProcessing;
|
||||||
|
MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks = MaxConcurrentTasks;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"保存平滑配置失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证配置参数
|
||||||
|
/// </summary>
|
||||||
|
public bool Validate()
|
||||||
|
{
|
||||||
|
return SmoothingStrength >= 0.0 && SmoothingStrength <= 1.0 &&
|
||||||
|
ResampleInterval > 0.0 &&
|
||||||
|
InterpolationSteps > 0 && InterpolationSteps <= 50 &&
|
||||||
|
CurveTension >= 0.0 && CurveTension <= 1.0 &&
|
||||||
|
MaxConcurrentTasks > 0 &&
|
||||||
|
MaxPointsPerStroke > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取配置摘要
|
||||||
|
/// </summary>
|
||||||
|
public string GetSummary()
|
||||||
|
{
|
||||||
|
return $"质量: {Quality}, 强度: {SmoothingStrength:F2}, 间隔: {ResampleInterval:F1}, " +
|
||||||
|
$"步数: {InterpolationSteps}, 自适应: {UseAdaptiveInterpolation}, " +
|
||||||
|
$"张力: {CurveTension:F2}, 硬件加速: {UseHardwareAcceleration}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -197,7 +197,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
if (processorCount >= 4 && isHardwareAccelerated)
|
if (processorCount >= 4 && isHardwareAccelerated)
|
||||||
{
|
{
|
||||||
// 降低高质量模式的门槛,4核以上且支持硬件加速就使用高质量
|
// 降低高质量模式的门槛,4核以上且支持硬件加速就使用高质量
|
||||||
config.Quality = InkSmoothingQuality.HighQuality;
|
config.Quality = (InkSmoothingConfig.SmoothingQuality)InkSmoothingConfig.InkSmoothingQuality.HighQuality;
|
||||||
config.UseHardwareAcceleration = true;
|
config.UseHardwareAcceleration = true;
|
||||||
config.UseAsyncProcessing = true;
|
config.UseAsyncProcessing = true;
|
||||||
config.MaxConcurrentTasks = Math.Min(processorCount, 8);
|
config.MaxConcurrentTasks = Math.Min(processorCount, 8);
|
||||||
@@ -205,7 +205,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
else if (processorCount >= 2)
|
else if (processorCount >= 2)
|
||||||
{
|
{
|
||||||
// 2核以上使用平衡模式
|
// 2核以上使用平衡模式
|
||||||
config.Quality = InkSmoothingQuality.Balanced;
|
config.Quality = (InkSmoothingConfig.SmoothingQuality)InkSmoothingConfig.InkSmoothingQuality.Balanced;
|
||||||
config.UseHardwareAcceleration = isHardwareAccelerated;
|
config.UseHardwareAcceleration = isHardwareAccelerated;
|
||||||
config.UseAsyncProcessing = true;
|
config.UseAsyncProcessing = true;
|
||||||
config.MaxConcurrentTasks = Math.Min(processorCount, 4);
|
config.MaxConcurrentTasks = Math.Min(processorCount, 4);
|
||||||
@@ -213,7 +213,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 单核或性能较低的设备使用高性能模式
|
// 单核或性能较低的设备使用高性能模式
|
||||||
config.Quality = InkSmoothingQuality.HighPerformance;
|
config.Quality = (InkSmoothingConfig.SmoothingQuality)InkSmoothingConfig.InkSmoothingQuality.HighPerformance;
|
||||||
config.UseHardwareAcceleration = false;
|
config.UseHardwareAcceleration = false;
|
||||||
config.UseAsyncProcessing = false;
|
config.UseAsyncProcessing = false;
|
||||||
config.MaxConcurrentTasks = 1;
|
config.MaxConcurrentTasks = 1;
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ using System;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Ink;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
namespace Ink_Canvas
|
namespace Ink_Canvas
|
||||||
{
|
{
|
||||||
@@ -129,6 +131,16 @@ namespace Ink_Canvas
|
|||||||
string timestamp = "img_clipboard_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
string timestamp = "img_clipboard_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||||
image.Name = timestamp;
|
image.Name = timestamp;
|
||||||
|
|
||||||
|
// 初始化TransformGroup
|
||||||
|
if (image is FrameworkElement element)
|
||||||
|
{
|
||||||
|
var transformGroup = new TransformGroup();
|
||||||
|
transformGroup.Children.Add(new ScaleTransform(1, 1));
|
||||||
|
transformGroup.Children.Add(new TranslateTransform(0, 0));
|
||||||
|
transformGroup.Children.Add(new RotateTransform(0));
|
||||||
|
element.RenderTransform = transformGroup;
|
||||||
|
}
|
||||||
|
|
||||||
// 设置位置
|
// 设置位置
|
||||||
if (position.HasValue)
|
if (position.HasValue)
|
||||||
{
|
{
|
||||||
@@ -142,9 +154,40 @@ namespace Ink_Canvas
|
|||||||
CenterAndScaleElement(image);
|
CenterAndScaleElement(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置图片属性,避免被InkCanvas选择系统处理
|
||||||
|
image.IsHitTestVisible = true;
|
||||||
|
image.Focusable = false;
|
||||||
|
|
||||||
|
// 初始化InkCanvas选择设置
|
||||||
|
if (inkCanvas != null)
|
||||||
|
{
|
||||||
|
// 清除当前选择,避免显示控制点
|
||||||
|
inkCanvas.Select(new StrokeCollection());
|
||||||
|
// 设置编辑模式为非选择模式
|
||||||
|
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||||
|
}
|
||||||
|
|
||||||
// 添加到画布
|
// 添加到画布
|
||||||
inkCanvas.Children.Add(image);
|
inkCanvas.Children.Add(image);
|
||||||
|
|
||||||
|
// 绑定事件处理器
|
||||||
|
if (image is FrameworkElement elementForEvents)
|
||||||
|
{
|
||||||
|
// 鼠标事件
|
||||||
|
elementForEvents.MouseLeftButtonDown += Element_MouseLeftButtonDown;
|
||||||
|
elementForEvents.MouseLeftButtonUp += Element_MouseLeftButtonUp;
|
||||||
|
elementForEvents.MouseMove += Element_MouseMove;
|
||||||
|
elementForEvents.MouseWheel += Element_MouseWheel;
|
||||||
|
|
||||||
|
// 触摸事件
|
||||||
|
elementForEvents.IsManipulationEnabled = true;
|
||||||
|
elementForEvents.ManipulationDelta += Element_ManipulationDelta;
|
||||||
|
elementForEvents.ManipulationCompleted += Element_ManipulationCompleted;
|
||||||
|
|
||||||
|
// 设置光标
|
||||||
|
elementForEvents.Cursor = Cursors.Hand;
|
||||||
|
}
|
||||||
|
|
||||||
// 提交到历史记录
|
// 提交到历史记录
|
||||||
timeMachine.CommitElementInsertHistory(image);
|
timeMachine.CommitElementInsertHistory(image);
|
||||||
|
|
||||||
|
|||||||
@@ -906,15 +906,10 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
if (inkCanvas != null)
|
if (inkCanvas != null)
|
||||||
{
|
{
|
||||||
// 隐藏选择控制点 - 通过清除选择和设置编辑模式
|
// 清除当前选择,避免显示控制点
|
||||||
inkCanvas.Select(new StrokeCollection());
|
inkCanvas.Select(new StrokeCollection());
|
||||||
|
// 设置编辑模式为非选择模式
|
||||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||||
|
|
||||||
// 隐藏选择框
|
|
||||||
if (GridInkCanvasSelectionCover != null)
|
|
||||||
{
|
|
||||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ using System.Windows.Media.Animation;
|
|||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using Application = System.Windows.Application;
|
using Application = System.Windows.Application;
|
||||||
using Button = System.Windows.Controls.Button;
|
using Button = System.Windows.Controls.Button;
|
||||||
|
using Cursors = System.Windows.Input.Cursors;
|
||||||
using HorizontalAlignment = System.Windows.HorizontalAlignment;
|
using HorizontalAlignment = System.Windows.HorizontalAlignment;
|
||||||
using Image = System.Windows.Controls.Image;
|
using Image = System.Windows.Controls.Image;
|
||||||
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
||||||
@@ -2828,6 +2829,72 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增:插入图片方法
|
||||||
|
private async void InsertImage_MouseUp_New(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
var dialog = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif"
|
||||||
|
};
|
||||||
|
if (dialog.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
string filePath = dialog.FileName;
|
||||||
|
Image image = await CreateAndCompressImageAsync(filePath);
|
||||||
|
if (image != null)
|
||||||
|
{
|
||||||
|
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||||
|
image.Name = timestamp;
|
||||||
|
|
||||||
|
// 初始化TransformGroup
|
||||||
|
if (image is FrameworkElement element)
|
||||||
|
{
|
||||||
|
var transformGroup = new TransformGroup();
|
||||||
|
transformGroup.Children.Add(new ScaleTransform(1, 1));
|
||||||
|
transformGroup.Children.Add(new TranslateTransform(0, 0));
|
||||||
|
transformGroup.Children.Add(new RotateTransform(0));
|
||||||
|
element.RenderTransform = transformGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
CenterAndScaleElement(image);
|
||||||
|
|
||||||
|
// 设置图片属性,避免被InkCanvas选择系统处理
|
||||||
|
image.IsHitTestVisible = true;
|
||||||
|
image.Focusable = false;
|
||||||
|
|
||||||
|
// 初始化InkCanvas选择设置
|
||||||
|
if (inkCanvas != null)
|
||||||
|
{
|
||||||
|
// 清除当前选择,避免显示控制点
|
||||||
|
inkCanvas.Select(new StrokeCollection());
|
||||||
|
// 设置编辑模式为非选择模式
|
||||||
|
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
inkCanvas.Children.Add(image);
|
||||||
|
|
||||||
|
// 绑定事件处理器
|
||||||
|
if (image is FrameworkElement elementForEvents)
|
||||||
|
{
|
||||||
|
// 鼠标事件
|
||||||
|
elementForEvents.MouseLeftButtonDown += Element_MouseLeftButtonDown;
|
||||||
|
elementForEvents.MouseLeftButtonUp += Element_MouseLeftButtonUp;
|
||||||
|
elementForEvents.MouseMove += Element_MouseMove;
|
||||||
|
elementForEvents.MouseWheel += Element_MouseWheel;
|
||||||
|
|
||||||
|
// 触摸事件
|
||||||
|
elementForEvents.IsManipulationEnabled = true;
|
||||||
|
elementForEvents.ManipulationDelta += Element_ManipulationDelta;
|
||||||
|
elementForEvents.ManipulationCompleted += Element_ManipulationCompleted;
|
||||||
|
|
||||||
|
// 设置光标
|
||||||
|
elementForEvents.Cursor = Cursors.Hand;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeMachine.CommitElementInsertHistory(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Keep the old method for backward compatibility
|
// Keep the old method for backward compatibility
|
||||||
private async void InsertImage_MouseUp(object sender, MouseButtonEventArgs e)
|
private async void InsertImage_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -2844,9 +2911,51 @@ namespace Ink_Canvas
|
|||||||
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||||
image.Name = timestamp;
|
image.Name = timestamp;
|
||||||
|
|
||||||
|
// 初始化TransformGroup
|
||||||
|
if (image is FrameworkElement element)
|
||||||
|
{
|
||||||
|
var transformGroup = new TransformGroup();
|
||||||
|
transformGroup.Children.Add(new ScaleTransform(1, 1));
|
||||||
|
transformGroup.Children.Add(new TranslateTransform(0, 0));
|
||||||
|
transformGroup.Children.Add(new RotateTransform(0));
|
||||||
|
element.RenderTransform = transformGroup;
|
||||||
|
}
|
||||||
|
|
||||||
CenterAndScaleElement(image);
|
CenterAndScaleElement(image);
|
||||||
|
|
||||||
|
// 设置图片属性,避免被InkCanvas选择系统处理
|
||||||
|
image.IsHitTestVisible = true;
|
||||||
|
image.Focusable = false;
|
||||||
|
|
||||||
|
// 初始化InkCanvas选择设置
|
||||||
|
if (inkCanvas != null)
|
||||||
|
{
|
||||||
|
// 清除当前选择,避免显示控制点
|
||||||
|
inkCanvas.Select(new StrokeCollection());
|
||||||
|
// 设置编辑模式为非选择模式
|
||||||
|
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||||
|
}
|
||||||
|
|
||||||
inkCanvas.Children.Add(image);
|
inkCanvas.Children.Add(image);
|
||||||
|
|
||||||
|
// 绑定事件处理器
|
||||||
|
if (image is FrameworkElement elementForEvents)
|
||||||
|
{
|
||||||
|
// 鼠标事件
|
||||||
|
elementForEvents.MouseLeftButtonDown += Element_MouseLeftButtonDown;
|
||||||
|
elementForEvents.MouseLeftButtonUp += Element_MouseLeftButtonUp;
|
||||||
|
elementForEvents.MouseMove += Element_MouseMove;
|
||||||
|
elementForEvents.MouseWheel += Element_MouseWheel;
|
||||||
|
|
||||||
|
// 触摸事件
|
||||||
|
elementForEvents.IsManipulationEnabled = true;
|
||||||
|
elementForEvents.ManipulationDelta += Element_ManipulationDelta;
|
||||||
|
elementForEvents.ManipulationCompleted += Element_ManipulationCompleted;
|
||||||
|
|
||||||
|
// 设置光标
|
||||||
|
elementForEvents.Cursor = Cursors.Hand;
|
||||||
|
}
|
||||||
|
|
||||||
timeMachine.CommitElementInsertHistory(image);
|
timeMachine.CommitElementInsertHistory(image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user