@@ -484,7 +484,7 @@ namespace Ink_Canvas
|
||||
// 在应用启动时自动释放IACore相关DLL
|
||||
try
|
||||
{
|
||||
Helpers.IACoreDllExtractor.ExtractIACoreDlls();
|
||||
IACoreDllExtractor.ExtractIACoreDlls();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -508,7 +508,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
// 检查是否存在更新标记文件
|
||||
string updateMarkerFile = Path.Combine(App.RootPath, "update_in_progress.tmp");
|
||||
string updateMarkerFile = Path.Combine(RootPath, "update_in_progress.tmp");
|
||||
bool isUpdateInProgress = false;
|
||||
|
||||
// 检查是否以更新模式启动
|
||||
|
||||
@@ -49,5 +49,5 @@ using System.Windows;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.7.8.3")]
|
||||
[assembly: AssemblyFileVersion("1.7.8.3")]
|
||||
[assembly: AssemblyVersion("1.7.8.4")]
|
||||
[assembly: AssemblyFileVersion("1.7.8.4")]
|
||||
|
||||
@@ -7,11 +7,12 @@ using System.Threading.Tasks;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步硬件加速的墨迹平滑处理器
|
||||
/// 改进的异步硬件加速墨迹平滑处理器,使用优化的三次贝塞尔曲线拟合
|
||||
/// </summary>
|
||||
public class AsyncAdvancedBezierSmoothing
|
||||
{
|
||||
@@ -26,11 +27,13 @@ namespace Ink_Canvas.Helpers
|
||||
_processingTasks = new ConcurrentDictionary<Stroke, CancellationTokenSource>();
|
||||
}
|
||||
|
||||
public double SmoothingStrength { get; set; } = 0.3; // 大幅降低强度
|
||||
public double ResampleInterval { get; set; } = 3.0; // 大幅增加间隔减少点数
|
||||
public int InterpolationSteps { get; set; } = 8; // 从4增加到8,提高插值步数
|
||||
public double SmoothingStrength { get; set; } = 0.4; // 适中的平滑强度
|
||||
public double ResampleInterval { get; set; } = 2.5; // 适中的重采样间隔
|
||||
public int InterpolationSteps { get; set; } = 12; // 增加插值步数提高精度
|
||||
public bool UseHardwareAcceleration { get; set; } = true;
|
||||
public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount;
|
||||
public bool UseAdaptiveInterpolation { get; set; } = true; // 自适应插值
|
||||
public double CurveTension { get; set; } = 0.3; // 曲线张力参数
|
||||
|
||||
/// <summary>
|
||||
/// 异步平滑笔画
|
||||
@@ -89,15 +92,22 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 简化处理:只进行轻度平滑,避免点数爆炸
|
||||
var smoothedPoints = ApplyLightSmoothing(originalPoints);
|
||||
// 使用改进的贝塞尔曲线拟合
|
||||
var smoothedPoints = ApplyImprovedBezierSmoothing(originalPoints);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 确保点数不会过多
|
||||
// 严格控制点数,避免产生过多点
|
||||
if (smoothedPoints.Length > originalPoints.Length * 2)
|
||||
{
|
||||
// 如果点数增加太多,回退到原始笔画
|
||||
// 如果点数增加太多,进行重采样
|
||||
smoothedPoints = ResampleEquidistantOptimized(smoothedPoints, ResampleInterval);
|
||||
}
|
||||
|
||||
// 最终检查:确保点数不会过多
|
||||
if (smoothedPoints.Length > originalPoints.Length * 1.5)
|
||||
{
|
||||
// 如果仍然太多点,使用原始笔画
|
||||
return stroke;
|
||||
}
|
||||
|
||||
@@ -111,34 +121,188 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
/// <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>();
|
||||
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 - 3; i += 3) // 每次移动3个点,避免重叠
|
||||
{
|
||||
var prev = points[i - 1];
|
||||
var curr = points[i];
|
||||
var next = points[i + 1];
|
||||
var p0 = points[i];
|
||||
var p1 = points[Math.Min(i + 1, points.Length - 1)];
|
||||
var p2 = points[Math.Min(i + 2, points.Length - 1)];
|
||||
var p3 = points[Math.Min(i + 3, points.Length - 1)];
|
||||
|
||||
// 3点平均
|
||||
double x = (prev.X + curr.X + next.X) / 3.0;
|
||||
double y = (prev.Y + curr.Y + next.Y) / 3.0;
|
||||
float pressure = (prev.PressureFactor + curr.PressureFactor + next.PressureFactor) / 3.0f;
|
||||
// 计算改进的控制点
|
||||
var controlPoints = CalculateImprovedControlPoints(p0, p1, p2, p3);
|
||||
|
||||
// 限制插值步数,避免点数爆炸
|
||||
int steps = Math.Min(UseAdaptiveInterpolation ?
|
||||
CalculateAdaptiveSteps(p0, p1, p2, p3) : InterpolationSteps, 16);
|
||||
|
||||
result.Add(new StylusPoint(x, y, Math.Max(pressure, 0.1f)));
|
||||
// 生成贝塞尔曲线点,但跳过第一个点避免重复
|
||||
for (int j = 1; j <= steps; j++)
|
||||
{
|
||||
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 RemoveDuplicatePoints(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[] RemoveDuplicatePoints(StylusPoint[] points)
|
||||
{
|
||||
if (points.Length < 2) return points;
|
||||
|
||||
var result = new List<StylusPoint>();
|
||||
result.Add(points[0]);
|
||||
|
||||
double minDistance = ResampleInterval * 0.5; // 最小距离阈值
|
||||
|
||||
for (int i = 1; i < points.Length; i++)
|
||||
{
|
||||
var lastPoint = result[result.Count - 1];
|
||||
var currentPoint = points[i];
|
||||
|
||||
// 计算距离
|
||||
double distance = Math.Sqrt(
|
||||
(currentPoint.X - lastPoint.X) * (currentPoint.X - lastPoint.X) +
|
||||
(currentPoint.Y - lastPoint.Y) * (currentPoint.Y - lastPoint.Y));
|
||||
|
||||
// 如果距离足够大,添加这个点
|
||||
if (distance >= minDistance)
|
||||
{
|
||||
result.Add(currentPoint);
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <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>
|
||||
|
||||
@@ -12,7 +12,6 @@ using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -28,8 +27,6 @@ namespace Ink_Canvas.Helpers
|
||||
private static readonly string updatesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AutoUpdate");
|
||||
private static string statusFilePath;
|
||||
|
||||
// GitHub Token认证
|
||||
private static readonly string GitHubToken = "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";
|
||||
|
||||
// 线路组结构体(包含版本、下载、日志地址)
|
||||
public class UpdateLineGroup
|
||||
@@ -416,8 +413,7 @@ namespace Ink_Canvas.Helpers
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "ICC-CE Auto Updater");
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"token {GitHubToken}");
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 使用GitHub Token进行API调用");
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 使用GitHub API调用");
|
||||
var response = await client.GetStringAsync(apiUrl);
|
||||
var releases = JArray.Parse(response);
|
||||
|
||||
@@ -459,8 +455,7 @@ namespace Ink_Canvas.Helpers
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "ICC-CE Auto Updater");
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"token {GitHubToken}");
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 使用GitHub Token进行API调用");
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 使用GitHub API调用");
|
||||
var response = await client.GetStringAsync(apiUrl);
|
||||
var json = JObject.Parse(response);
|
||||
string version = json["tag_name"]?.ToString();
|
||||
@@ -1900,8 +1895,7 @@ namespace Ink_Canvas.Helpers
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "ICC-CE Auto Updater");
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"token {GitHubToken}");
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 使用GitHub Token进行API调用");
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 使用GitHub API调用");
|
||||
var response = await client.GetStringAsync(apiUrl);
|
||||
var arr = JArray.Parse(response);
|
||||
foreach (var item in arr)
|
||||
|
||||
+289
-1783
File diff suppressed because it is too large
Load Diff
@@ -238,7 +238,7 @@ namespace Ink_Canvas.Helpers
|
||||
RegisterHotkey("DrawTool", Key.D, ModifierKeys.Alt, () => _mainWindow.PenIcon_Click(null, null));
|
||||
RegisterHotkey("EraserTool", Key.E, ModifierKeys.Alt, () => _mainWindow.EraserIcon_Click(null, null));
|
||||
RegisterHotkey("BlackboardTool", Key.B, ModifierKeys.Alt, () => _mainWindow.ImageBlackboard_MouseUp(null, null));
|
||||
RegisterHotkey("QuitDrawTool", Key.Q, ModifierKeys.Alt, () => _mainWindow.CursorIcon_Click(null, null));
|
||||
RegisterHotkey("QuitDrawTool", Key.Q, ModifierKeys.Alt, () => _mainWindow.KeyChangeToQuitDrawTool(null, null));
|
||||
|
||||
// 画笔快捷键 - 使用反射访问penType字段
|
||||
RegisterHotkey("Pen1", Key.D1, ModifierKeys.Alt, () => SwitchToPenType(0));
|
||||
@@ -472,7 +472,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
// 通过反射访问主窗口的penType字段
|
||||
var penTypeField = _mainWindow.GetType().GetField("penType",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (penTypeField != null)
|
||||
{
|
||||
@@ -480,7 +480,7 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
// 调用CheckPenTypeUIState方法更新UI状态
|
||||
var checkPenTypeMethod = _mainWindow.GetType().GetMethod("CheckPenTypeUIState",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (checkPenTypeMethod != null)
|
||||
{
|
||||
@@ -647,7 +647,7 @@ namespace Ink_Canvas.Helpers
|
||||
case "BlackboardTool":
|
||||
return () => _mainWindow.ImageBlackboard_MouseUp(null, null);
|
||||
case "QuitDrawTool":
|
||||
return () => _mainWindow.CursorIcon_Click(null, null);
|
||||
return () => _mainWindow.KeyChangeToQuitDrawTool(null, null);
|
||||
case "Pen1":
|
||||
return () => SwitchToPenType(0);
|
||||
case "Pen2":
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Threading;
|
||||
@@ -107,7 +106,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
// 将墨迹添加到 inkCanvas 的父容器中,而不是 inkCanvas.Children
|
||||
// 这样可以避免坐标系统问题
|
||||
var parent = _mainWindow.inkCanvas.Parent as System.Windows.Controls.Panel;
|
||||
var parent = _mainWindow.inkCanvas.Parent as Panel;
|
||||
if (parent != null)
|
||||
{
|
||||
parent.Children.Add(strokeVisual);
|
||||
@@ -154,7 +153,7 @@ namespace Ink_Canvas.Helpers
|
||||
try
|
||||
{
|
||||
// 从父容器中移除墨迹
|
||||
var parent = _mainWindow.inkCanvas?.Parent as System.Windows.Controls.Panel;
|
||||
var parent = _mainWindow.inkCanvas?.Parent as Panel;
|
||||
if (parent != null && parent.Children.Contains(visual))
|
||||
{
|
||||
parent.Children.Remove(visual);
|
||||
@@ -202,7 +201,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
if (_mainWindow.inkCanvas != null)
|
||||
{
|
||||
var parent = _mainWindow.inkCanvas.Parent as System.Windows.Controls.Panel;
|
||||
var parent = _mainWindow.inkCanvas.Parent as Panel;
|
||||
foreach (var visual in _strokeVisuals.Values)
|
||||
{
|
||||
if (parent != null && parent.Children.Contains(visual))
|
||||
@@ -336,7 +335,7 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
return path;
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -454,7 +453,7 @@ namespace Ink_Canvas.Helpers
|
||||
originalVisual.Visibility = Visibility.Hidden;
|
||||
|
||||
var segments = new List<UIElement>();
|
||||
var parent = _mainWindow.inkCanvas?.Parent as System.Windows.Controls.Panel;
|
||||
var parent = _mainWindow.inkCanvas?.Parent as Panel;
|
||||
if (parent == null)
|
||||
{
|
||||
// 如果父容器不是Panel,直接使用InkCanvas
|
||||
@@ -498,7 +497,7 @@ namespace Ink_Canvas.Helpers
|
||||
// 开始分段渐隐动画
|
||||
StartSegmentedFadeAnimation(segments, stroke, originalVisual, duration);
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
StartSimpleFadeAnimation(originalVisual, stroke, opacity, duration);
|
||||
}
|
||||
@@ -552,7 +551,7 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
return path;
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -676,7 +675,7 @@ namespace Ink_Canvas.Helpers
|
||||
try
|
||||
{
|
||||
// 移除所有分段
|
||||
var parent = _mainWindow.inkCanvas?.Parent as System.Windows.Controls.Panel;
|
||||
var parent = _mainWindow.inkCanvas?.Parent as Panel;
|
||||
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
@@ -811,7 +810,7 @@ namespace Ink_Canvas.Helpers
|
||||
try
|
||||
{
|
||||
// 从父容器中移除墨迹
|
||||
var parent = _mainWindow.inkCanvas?.Parent as System.Windows.Controls.Panel;
|
||||
var parent = _mainWindow.inkCanvas?.Parent as Panel;
|
||||
if (parent != null && parent.Children.Contains(visual))
|
||||
{
|
||||
parent.Children.Remove(visual);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
// 降低高质量模式的门槛,4核以上且支持硬件加速就使用高质量
|
||||
config.Quality = InkSmoothingQuality.HighQuality;
|
||||
config.Quality = (InkSmoothingConfig.SmoothingQuality)InkSmoothingConfig.InkSmoothingQuality.HighQuality;
|
||||
config.UseHardwareAcceleration = true;
|
||||
config.UseAsyncProcessing = true;
|
||||
config.MaxConcurrentTasks = Math.Min(processorCount, 8);
|
||||
@@ -205,7 +205,7 @@ namespace Ink_Canvas.Helpers
|
||||
else if (processorCount >= 2)
|
||||
{
|
||||
// 2核以上使用平衡模式
|
||||
config.Quality = InkSmoothingQuality.Balanced;
|
||||
config.Quality = (InkSmoothingConfig.SmoothingQuality)InkSmoothingConfig.InkSmoothingQuality.Balanced;
|
||||
config.UseHardwareAcceleration = isHardwareAccelerated;
|
||||
config.UseAsyncProcessing = true;
|
||||
config.MaxConcurrentTasks = Math.Min(processorCount, 4);
|
||||
@@ -213,7 +213,7 @@ namespace Ink_Canvas.Helpers
|
||||
else
|
||||
{
|
||||
// 单核或性能较低的设备使用高性能模式
|
||||
config.Quality = InkSmoothingQuality.HighPerformance;
|
||||
config.Quality = (InkSmoothingConfig.SmoothingQuality)InkSmoothingConfig.InkSmoothingQuality.HighPerformance;
|
||||
config.UseHardwareAcceleration = false;
|
||||
config.UseAsyncProcessing = false;
|
||||
config.MaxConcurrentTasks = 1;
|
||||
|
||||
@@ -340,7 +340,7 @@ namespace Ink_Canvas.Helpers
|
||||
LogHelper.WriteLogToFile($"PPT事件注册失败: {ex}", LogHelper.LogType.Error);
|
||||
throw; // 重新抛出异常,让外层处理
|
||||
}
|
||||
}, DispatcherPriority.Normal, System.Threading.CancellationToken.None, TimeSpan.FromSeconds(2));
|
||||
}, DispatcherPriority.Normal, CancellationToken.None, TimeSpan.FromSeconds(2));
|
||||
|
||||
// 获取当前演示文稿信息
|
||||
UpdateCurrentPresentationInfo();
|
||||
@@ -407,7 +407,7 @@ namespace Ink_Canvas.Helpers
|
||||
// COM对象类型转换失败,通常是因为对象已经被释放
|
||||
LogHelper.WriteLogToFile("PPT COM对象已被释放,跳过事件注册取消", LogHelper.LogType.Trace);
|
||||
}
|
||||
}, DispatcherPriority.Normal, System.Threading.CancellationToken.None, TimeSpan.FromSeconds(1));
|
||||
}, DispatcherPriority.Normal, CancellationToken.None, TimeSpan.FromSeconds(1));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Ink_Canvas.Windows;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Media;
|
||||
using System.Linq;
|
||||
|
||||
|
||||
@@ -3445,10 +3445,10 @@
|
||||
|
||||
<!-- 图片选择工具栏 -->
|
||||
<Border Name="BorderImageSelectionControl"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Top" Margin="800,900,0,0"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,0,0,0"
|
||||
CornerRadius="5" Height="80"
|
||||
Background="{DynamicResource FloatBarBackground}"
|
||||
Visibility="{Binding ElementName=GridInkCanvasSelectionCover, Path=Visibility}"
|
||||
Visibility="Collapsed"
|
||||
BorderThickness="1" BorderBrush="{DynamicResource FloatBarBorderBrush}">
|
||||
<Viewbox Margin="0,-2.5">
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" Spacing="10" Height="60">
|
||||
@@ -3549,6 +3549,7 @@
|
||||
</ui:SimpleStackPanel>
|
||||
</Viewbox>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid Visibility="Collapsed">
|
||||
|
||||
@@ -237,8 +237,8 @@ namespace Ink_Canvas
|
||||
ApplyAlwaysOnTop();
|
||||
|
||||
// 添加窗口激活事件处理,确保置顶状态在窗口重新激活时得到保持
|
||||
this.Activated += Window_Activated;
|
||||
this.Deactivated += Window_Deactivated;
|
||||
Activated += Window_Activated;
|
||||
Deactivated += Window_Deactivated;
|
||||
|
||||
// 为浮动栏按钮添加触摸事件支持
|
||||
AddTouchSupportToFloatingBarButtons();
|
||||
@@ -509,7 +509,7 @@ namespace Ink_Canvas
|
||||
ApplyAlwaysOnTop();
|
||||
|
||||
// 初始化UIElement选择系统
|
||||
InitializeUIElementSelection();
|
||||
|
||||
|
||||
// 初始化剪贴板监控
|
||||
InitializeClipboardMonitoring();
|
||||
@@ -1003,19 +1003,7 @@ namespace Ink_Canvas
|
||||
// 如果点击的不是图片或其他UI元素,则取消选择
|
||||
if (!(hitTest is Image) && !(hitTest is MediaElement))
|
||||
{
|
||||
// 检查是否点击在已选择的UI元素上
|
||||
bool clickedOnSelectedElement = false;
|
||||
if (selectedUIElement != null)
|
||||
{
|
||||
var elementBounds = GetUIElementBounds(selectedUIElement);
|
||||
var clickPoint = e.GetPosition(inkCanvas);
|
||||
clickedOnSelectedElement = elementBounds.Contains(clickPoint);
|
||||
}
|
||||
|
||||
if (!clickedOnSelectedElement)
|
||||
{
|
||||
DeselectUIElement();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1372,7 +1360,7 @@ namespace Ink_Canvas
|
||||
// 直接设置滚动位置,不使用动画
|
||||
SettingsPanelScrollViewer.ScrollToVerticalOffset(targetPosition);
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
// 如果出现异常,恢复到原来的滚动位置
|
||||
SettingsPanelScrollViewer.ScrollToVerticalOffset(originalOffset);
|
||||
@@ -1911,7 +1899,7 @@ namespace Ink_Canvas
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
ApplyAlwaysOnTop();
|
||||
}), System.Windows.Threading.DispatcherPriority.Loaded);
|
||||
}), DispatcherPriority.Loaded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1927,83 +1915,11 @@ namespace Ink_Canvas
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
ApplyAlwaysOnTop();
|
||||
}), System.Windows.Threading.DispatcherPriority.Loaded);
|
||||
}), DispatcherPriority.Loaded);
|
||||
}
|
||||
}
|
||||
|
||||
#region Image Toolbar Event Handlers
|
||||
|
||||
private void BorderImageClone_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
|
||||
if (selectedUIElement is Image image)
|
||||
{
|
||||
CloneImage(image);
|
||||
}
|
||||
}
|
||||
|
||||
private void BorderImageCloneToNewBoard_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
|
||||
if (selectedUIElement is Image image)
|
||||
{
|
||||
CloneImageToNewBoard(image);
|
||||
}
|
||||
}
|
||||
|
||||
private void BorderImageRotateLeft_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
|
||||
if (selectedUIElement is Image image)
|
||||
{
|
||||
RotateImage(image, -90);
|
||||
}
|
||||
}
|
||||
|
||||
private void BorderImageRotateRight_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
|
||||
if (selectedUIElement is Image image)
|
||||
{
|
||||
RotateImage(image, 90);
|
||||
}
|
||||
}
|
||||
|
||||
private void GridImageScaleIncrease_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
|
||||
if (selectedUIElement is Image image)
|
||||
{
|
||||
ScaleImage(image, 1.25);
|
||||
}
|
||||
}
|
||||
|
||||
private void GridImageScaleDecrease_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
|
||||
if (selectedUIElement is Image image)
|
||||
{
|
||||
ScaleImage(image, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
private void BorderImageDelete_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
|
||||
if (selectedUIElement is Image image)
|
||||
{
|
||||
DeleteImage(image);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 全局快捷键管理
|
||||
/// <summary>
|
||||
@@ -2114,7 +2030,10 @@ namespace Ink_Canvas
|
||||
try
|
||||
{
|
||||
Settings.Canvas.InkFadeTime = (int)e.NewValue;
|
||||
_inkFadeManager.UpdateFadeTime(Settings.Canvas.InkFadeTime);
|
||||
if (_inkFadeManager != null)
|
||||
{
|
||||
_inkFadeManager.UpdateFadeTime(Settings.Canvas.InkFadeTime);
|
||||
}
|
||||
LogHelper.WriteLogToFile($"墨迹渐隐时间已更新为 {Settings.Canvas.InkFadeTime}ms", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -2187,7 +2106,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
// 如果直接发送失败,回退到原来的方法
|
||||
if (isPrevious)
|
||||
|
||||
@@ -116,8 +116,7 @@ namespace Ink_Canvas
|
||||
_currentCommitType = CommitReason.ClearingCanvas;
|
||||
if (isErasedByCode) _currentCommitType = CommitReason.CodeInput;
|
||||
|
||||
// 取消任何UI元素的选择,隐藏拉伸控件
|
||||
DeselectUIElement();
|
||||
|
||||
|
||||
// 只清除笔画,不清除图片元素
|
||||
// 图片元素的清除由调用方决定
|
||||
@@ -159,11 +158,7 @@ namespace Ink_Canvas
|
||||
foreach (var item in TimeMachineHistories[CurrentWhiteboardIndex]) ApplyHistoryToCanvas(item);
|
||||
}
|
||||
|
||||
// 确保选中状态被清除,因为我们切换了页面
|
||||
if (selectedUIElement != null)
|
||||
{
|
||||
DeselectUIElement();
|
||||
}
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -214,8 +209,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
if (CurrentWhiteboardIndex <= 1) return;
|
||||
|
||||
// 取消任何UI元素的选择
|
||||
DeselectUIElement();
|
||||
|
||||
|
||||
SaveStrokes();
|
||||
|
||||
@@ -239,9 +233,7 @@ namespace Ink_Canvas
|
||||
BtnWhiteBoardAdd_Click(sender, e);
|
||||
return;
|
||||
}
|
||||
|
||||
// 取消任何UI元素的选择
|
||||
DeselectUIElement();
|
||||
|
||||
|
||||
SaveStrokes();
|
||||
|
||||
@@ -258,10 +250,7 @@ namespace Ink_Canvas
|
||||
if (WhiteboardTotalCount >= 99) return;
|
||||
if (Settings.Automation.IsAutoSaveStrokesAtClear &&
|
||||
inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber) SaveScreenShot(true);
|
||||
|
||||
// 取消任何UI元素的选择
|
||||
DeselectUIElement();
|
||||
|
||||
|
||||
SaveStrokes();
|
||||
ClearStrokes(true);
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
@@ -129,6 +131,16 @@ namespace Ink_Canvas
|
||||
string timestamp = "img_clipboard_" + 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;
|
||||
}
|
||||
|
||||
// 设置位置
|
||||
if (position.HasValue)
|
||||
{
|
||||
@@ -142,12 +154,40 @@ namespace Ink_Canvas
|
||||
CenterAndScaleElement(image);
|
||||
}
|
||||
|
||||
// 设置图片属性,避免被InkCanvas选择系统处理
|
||||
image.IsHitTestVisible = true;
|
||||
image.Focusable = false;
|
||||
|
||||
// 初始化InkCanvas选择设置
|
||||
if (inkCanvas != null)
|
||||
{
|
||||
// 清除当前选择,避免显示控制点
|
||||
inkCanvas.Select(new StrokeCollection());
|
||||
// 设置编辑模式为Ink模式,这样可以保持图片的交互功能
|
||||
// 同时通过图片的IsHitTestVisible和Focusable属性来避免InkCanvas选择系统的干扰
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
}
|
||||
|
||||
// 添加到画布
|
||||
inkCanvas.Children.Add(image);
|
||||
|
||||
// 添加鼠标事件处理
|
||||
image.MouseDown += UIElement_MouseDown;
|
||||
image.IsManipulationEnabled = true;
|
||||
// 绑定事件处理器
|
||||
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);
|
||||
|
||||
@@ -6,11 +6,23 @@ using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Threading;
|
||||
using Ink_Canvas.Helpers;
|
||||
using System.Windows.Input;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Ink;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
// 当前选中的可操作元素
|
||||
private FrameworkElement currentSelectedElement;
|
||||
private bool isDragging = false;
|
||||
private Point dragStartPoint;
|
||||
private bool isElementSelected = false;
|
||||
|
||||
#region Image
|
||||
private async void BtnImageInsert_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
@@ -28,18 +40,516 @@ namespace Ink_Canvas
|
||||
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||
image.Name = timestamp;
|
||||
|
||||
CenterAndScaleElement(image);
|
||||
// 设置图片属性,避免被InkCanvas选择系统处理
|
||||
image.IsHitTestVisible = true;
|
||||
image.Focusable = false;
|
||||
|
||||
// 初始化InkCanvas选择设置
|
||||
InitializeInkCanvasSelectionSettings();
|
||||
|
||||
// 先添加到画布
|
||||
inkCanvas.Children.Add(image);
|
||||
|
||||
// 添加鼠标事件处理,使图片可以被选择
|
||||
image.MouseDown += UIElement_MouseDown;
|
||||
image.IsManipulationEnabled = true;
|
||||
// 等待图片加载完成后再进行后续处理
|
||||
image.Loaded += (s, args) =>
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
// 初始化TransformGroup
|
||||
InitializeElementTransform(image);
|
||||
|
||||
// 居中缩放
|
||||
CenterAndScaleElement(image);
|
||||
|
||||
// 最后绑定事件处理器
|
||||
BindElementEvents(image);
|
||||
|
||||
LogHelper.WriteLogToFile($"图片插入完成: {image.Name}");
|
||||
}), System.Windows.Threading.DispatcherPriority.Loaded);
|
||||
};
|
||||
|
||||
timeMachine.CommitElementInsertHistory(image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化元素的TransformGroup
|
||||
private void InitializeElementTransform(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;
|
||||
}
|
||||
|
||||
// 绑定元素事件处理器
|
||||
private void BindElementEvents(FrameworkElement element)
|
||||
{
|
||||
// 鼠标事件
|
||||
element.MouseLeftButtonDown += Element_MouseLeftButtonDown;
|
||||
element.MouseLeftButtonUp += Element_MouseLeftButtonUp;
|
||||
element.MouseMove += Element_MouseMove;
|
||||
element.MouseWheel += Element_MouseWheel;
|
||||
|
||||
// 触摸事件
|
||||
element.IsManipulationEnabled = true;
|
||||
element.ManipulationDelta += Element_ManipulationDelta;
|
||||
element.ManipulationCompleted += Element_ManipulationCompleted;
|
||||
|
||||
// 设置光标
|
||||
element.Cursor = Cursors.Hand;
|
||||
|
||||
// 禁用InkCanvas对图片的选择处理
|
||||
element.IsHitTestVisible = true;
|
||||
element.Focusable = false;
|
||||
}
|
||||
|
||||
// 鼠标左键按下事件
|
||||
private void Element_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement element)
|
||||
{
|
||||
|
||||
|
||||
// 取消之前选中的元素
|
||||
if (currentSelectedElement != null && currentSelectedElement != element)
|
||||
{
|
||||
UnselectElement(currentSelectedElement);
|
||||
}
|
||||
|
||||
// 选中当前元素
|
||||
SelectElement(element);
|
||||
|
||||
// 开始拖动
|
||||
isDragging = true;
|
||||
dragStartPoint = e.GetPosition(inkCanvas);
|
||||
element.CaptureMouse();
|
||||
element.Cursor = Cursors.SizeAll;
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标左键释放事件
|
||||
private void Element_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement element)
|
||||
{
|
||||
isDragging = false;
|
||||
element.ReleaseMouseCapture();
|
||||
element.Cursor = Cursors.Hand;
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标移动事件
|
||||
private void Element_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement element && isDragging && element.IsMouseCaptured)
|
||||
{
|
||||
var currentPoint = e.GetPosition(inkCanvas);
|
||||
|
||||
// 使用鼠标拖动的完整实现机制
|
||||
ApplyMouseDragTransform(element, currentPoint, dragStartPoint);
|
||||
|
||||
// 如果是图片元素,更新工具栏位置
|
||||
if (element is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateImageSelectionToolbarPosition(element);
|
||||
}
|
||||
|
||||
dragStartPoint = currentPoint;
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标滚轮事件 - 缩放
|
||||
private void Element_MouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement element)
|
||||
{
|
||||
|
||||
|
||||
// 使用滚轮缩放的核心机制
|
||||
ApplyWheelScaleTransform(element, e);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 触摸操作事件
|
||||
private void Element_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement element)
|
||||
{
|
||||
// 使用触摸拖动的完整实现
|
||||
ApplyTouchManipulationTransform(element, e);
|
||||
|
||||
// 如果是图片元素,更新工具栏位置
|
||||
if (element is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateImageSelectionToolbarPosition(element);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 触摸操作完成事件
|
||||
private void Element_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
|
||||
{
|
||||
// 可以在这里添加操作完成后的处理逻辑
|
||||
}
|
||||
|
||||
// 应用平移变换
|
||||
private void ApplyTranslateTransform(FrameworkElement element, double deltaX, double deltaY)
|
||||
{
|
||||
if (element.RenderTransform is TransformGroup transformGroup)
|
||||
{
|
||||
var translateTransform = transformGroup.Children.OfType<TranslateTransform>().FirstOrDefault();
|
||||
if (translateTransform != null)
|
||||
{
|
||||
translateTransform.X += deltaX;
|
||||
translateTransform.Y += deltaY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 应用缩放变换
|
||||
private void ApplyScaleTransform(FrameworkElement element, double scaleFactor, Point center)
|
||||
{
|
||||
if (element.RenderTransform is TransformGroup transformGroup)
|
||||
{
|
||||
var scaleTransform = transformGroup.Children.OfType<ScaleTransform>().FirstOrDefault();
|
||||
if (scaleTransform != null)
|
||||
{
|
||||
// 设置缩放中心
|
||||
scaleTransform.CenterX = center.X;
|
||||
scaleTransform.CenterY = center.Y;
|
||||
|
||||
// 应用缩放
|
||||
scaleTransform.ScaleX *= scaleFactor;
|
||||
scaleTransform.ScaleY *= scaleFactor;
|
||||
|
||||
// 限制缩放范围
|
||||
scaleTransform.ScaleX = Math.Max(0.1, Math.Min(scaleTransform.ScaleX, 5.0));
|
||||
scaleTransform.ScaleY = Math.Max(0.1, Math.Min(scaleTransform.ScaleY, 5.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 应用旋转变换
|
||||
private void ApplyRotateTransform(FrameworkElement element, double angle)
|
||||
{
|
||||
if (element.RenderTransform is TransformGroup transformGroup)
|
||||
{
|
||||
var rotateTransform = transformGroup.Children.OfType<RotateTransform>().FirstOrDefault();
|
||||
if (rotateTransform != null)
|
||||
{
|
||||
rotateTransform.Angle += angle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 选中元素
|
||||
private void SelectElement(FrameworkElement element)
|
||||
{
|
||||
currentSelectedElement = element;
|
||||
isElementSelected = true;
|
||||
|
||||
// 根据元素类型显示不同的选择工具栏
|
||||
if (element is Image)
|
||||
{
|
||||
// 显示图片选择工具栏并设置位置
|
||||
if (BorderImageSelectionControl != null)
|
||||
{
|
||||
// 计算工具栏位置
|
||||
UpdateImageSelectionToolbarPosition(element);
|
||||
BorderImageSelectionControl.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
// 隐藏笔画选择工具栏
|
||||
if (BorderStrokeSelectionControl != null)
|
||||
{
|
||||
BorderStrokeSelectionControl.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 显示笔画选择工具栏
|
||||
if (BorderStrokeSelectionControl != null)
|
||||
{
|
||||
BorderStrokeSelectionControl.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
// 隐藏图片选择工具栏
|
||||
if (BorderImageSelectionControl != null)
|
||||
{
|
||||
BorderImageSelectionControl.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
// 确保选择框不显示,避免蓝色边框
|
||||
if (GridInkCanvasSelectionCover != null)
|
||||
{
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
// 禁用InkCanvas的选择功能,去除控制点
|
||||
if (inkCanvas != null)
|
||||
{
|
||||
// 清除当前选择
|
||||
inkCanvas.Select(new StrokeCollection());
|
||||
// 设置编辑模式为非选择模式
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
}
|
||||
}
|
||||
|
||||
// 取消选中元素
|
||||
private void UnselectElement(FrameworkElement element)
|
||||
{
|
||||
// 去除选中效果
|
||||
isElementSelected = false;
|
||||
|
||||
// 隐藏所有选择工具栏
|
||||
if (BorderImageSelectionControl != null)
|
||||
{
|
||||
BorderImageSelectionControl.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
if (BorderStrokeSelectionControl != null)
|
||||
{
|
||||
BorderStrokeSelectionControl.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
// 确保选择框隐藏
|
||||
if (GridInkCanvasSelectionCover != null)
|
||||
{
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
// 恢复InkCanvas的编辑模式
|
||||
if (inkCanvas != null)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
}
|
||||
}
|
||||
|
||||
// 应用矩阵变换到元素
|
||||
private void ApplyElementMatrixTransform(FrameworkElement element, Matrix matrix)
|
||||
{
|
||||
if (element.RenderTransform is TransformGroup transformGroup)
|
||||
{
|
||||
// 创建MatrixTransform
|
||||
var matrixTransform = new MatrixTransform(matrix);
|
||||
|
||||
// 将MatrixTransform添加到TransformGroup
|
||||
transformGroup.Children.Add(matrixTransform);
|
||||
}
|
||||
}
|
||||
|
||||
// 滚轮缩放的核心机制
|
||||
private void ApplyWheelScaleTransform(FrameworkElement element, MouseWheelEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 根据滚轮方向确定缩放比例(向上1.1倍,向下0.9倍)
|
||||
double scaleFactor = e.Delta > 0 ? 1.1 : 0.9;
|
||||
|
||||
// 计算选中元素的中心点作为缩放中心
|
||||
var elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
|
||||
|
||||
// 创建 Matrix 对象并应用 ScaleAt 变换
|
||||
var matrix = new Matrix();
|
||||
matrix.ScaleAt(scaleFactor, scaleFactor, elementCenter.X, elementCenter.Y);
|
||||
|
||||
// 对选中的图片元素调用 ApplyElementMatrixTransform
|
||||
ApplyElementMatrixTransform(element, matrix);
|
||||
|
||||
// 对选中的笔画应用 Transform 方法(如果有选中的笔画)
|
||||
var selectedStrokes = inkCanvas.GetSelectedStrokes();
|
||||
foreach (var stroke in selectedStrokes)
|
||||
{
|
||||
stroke.Transform(matrix, false);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"滚轮缩放失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 矩阵变换的完整实现
|
||||
private void ApplyMatrixTransformToElement(FrameworkElement element, Matrix matrix, bool saveHistory = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取元素的 RenderTransform,如果不存在则创建新的 TransformGroup
|
||||
TransformGroup transformGroup = element.RenderTransform as TransformGroup;
|
||||
if (transformGroup == null)
|
||||
{
|
||||
transformGroup = new TransformGroup();
|
||||
element.RenderTransform = transformGroup;
|
||||
}
|
||||
|
||||
// 保存初始变换状态用于历史记录
|
||||
var initialTransform = transformGroup.Clone() as TransformGroup;
|
||||
|
||||
// 创建新的 TransformGroup 并添加 MatrixTransform
|
||||
var newTransformGroup = new TransformGroup();
|
||||
newTransformGroup.Children.Add(new MatrixTransform(matrix));
|
||||
|
||||
// 将新的变换组添加到现有的变换组中
|
||||
transformGroup.Children.Add(newTransformGroup);
|
||||
|
||||
// 如果启用了历史记录,提交变换历史
|
||||
if (saveHistory)
|
||||
{
|
||||
CommitTransformHistory(element, initialTransform, transformGroup);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"矩阵变换失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标拖动的完整实现机制
|
||||
private void ApplyMouseDragTransform(FrameworkElement element, Point currentPoint, Point startPoint)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 计算鼠标移动的位移向量
|
||||
var delta = currentPoint - startPoint;
|
||||
|
||||
// 创建 Matrix 对象并应用 Translate 变换
|
||||
var matrix = new Matrix();
|
||||
matrix.Translate(delta.X, delta.Y);
|
||||
|
||||
// 对选中的图片元素应用矩阵变换
|
||||
ApplyMatrixTransformToElement(element, matrix, false);
|
||||
|
||||
// 对选中的笔画应用变换
|
||||
var selectedStrokes = inkCanvas.GetSelectedStrokes();
|
||||
foreach (var stroke in selectedStrokes)
|
||||
{
|
||||
stroke.Transform(matrix, false);
|
||||
}
|
||||
|
||||
// 更新选择框的位置(如果有选择框)
|
||||
UpdateSelectionBorderPosition(delta);
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"鼠标拖动失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新选择框位置
|
||||
private void UpdateSelectionBorderPosition(Vector delta)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 这里可以添加更新选择框位置的逻辑
|
||||
// 例如更新 BorderStrokeSelectionControl 的位置
|
||||
if (BorderStrokeSelectionControl != null)
|
||||
{
|
||||
var currentMargin = BorderStrokeSelectionControl.Margin;
|
||||
BorderStrokeSelectionControl.Margin = new Thickness(
|
||||
currentMargin.Left + delta.X,
|
||||
currentMargin.Top + delta.Y,
|
||||
currentMargin.Right,
|
||||
currentMargin.Bottom
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新选择框位置失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 提交变换历史
|
||||
private void CommitTransformHistory(FrameworkElement element, TransformGroup initialTransform, TransformGroup finalTransform)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 这里可以添加提交变换历史到时间机器的逻辑
|
||||
// 例如记录变换前后的状态
|
||||
LogHelper.WriteLogToFile($"变换历史已记录: 元素={element.Name}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"提交变换历史失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 触摸拖动的完整实现
|
||||
private void ApplyTouchManipulationTransform(FrameworkElement element, ManipulationDeltaEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var md = e.DeltaManipulation;
|
||||
var matrix = new Matrix();
|
||||
|
||||
// 支持单指拖动和多指手势
|
||||
// 可以同时进行平移、旋转和缩放
|
||||
|
||||
// 通过 ManipulationDelta 获取手势变化信息
|
||||
var translation = md.Translation;
|
||||
var rotation = md.Rotation;
|
||||
var scale = md.Scale;
|
||||
|
||||
// 应用平移
|
||||
if (translation.X != 0 || translation.Y != 0)
|
||||
{
|
||||
matrix.Translate(translation.X, translation.Y);
|
||||
}
|
||||
|
||||
// 支持两指缩放和旋转操作
|
||||
if (e.Manipulators.Count() >= 2)
|
||||
{
|
||||
var center = e.ManipulationOrigin;
|
||||
|
||||
// 应用缩放
|
||||
if (scale.X != 1.0 || scale.Y != 1.0)
|
||||
{
|
||||
matrix.ScaleAt(scale.X, scale.Y, center.X, center.Y);
|
||||
}
|
||||
|
||||
// 应用旋转
|
||||
if (rotation != 0)
|
||||
{
|
||||
matrix.RotateAt(rotation, center.X, center.Y);
|
||||
}
|
||||
}
|
||||
|
||||
// 应用变换到元素
|
||||
ApplyMatrixTransformToElement(element, matrix, false);
|
||||
|
||||
// 应用变换到选中的笔画
|
||||
var selectedStrokes = inkCanvas.GetSelectedStrokes();
|
||||
foreach (var stroke in selectedStrokes)
|
||||
{
|
||||
stroke.Transform(matrix, false);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"触摸操作失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Image> CreateAndCompressImageAsync(string filePath)
|
||||
{
|
||||
string savePath = Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "File Dependency");
|
||||
@@ -115,10 +625,6 @@ namespace Ink_Canvas
|
||||
InkCanvas.SetTop(mediaElement, 0);
|
||||
inkCanvas.Children.Add(mediaElement);
|
||||
|
||||
// 添加鼠标事件处理,使媒体元素可以被选择
|
||||
mediaElement.MouseDown += UIElement_MouseDown;
|
||||
mediaElement.IsManipulationEnabled = true;
|
||||
|
||||
mediaElement.LoadedBehavior = MediaState.Manual;
|
||||
mediaElement.UnloadedBehavior = MediaState.Manual;
|
||||
mediaElement.Loaded += async (_, args) =>
|
||||
@@ -222,9 +728,9 @@ namespace Ink_Canvas
|
||||
/// 克隆图片
|
||||
/// </summary>
|
||||
/// <param name="image">要克隆的图片</param>
|
||||
private void CloneImage(Image image)
|
||||
private Image CloneImage(Image image)
|
||||
{
|
||||
if (image == null) return;
|
||||
if (image == null) return null;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -242,17 +748,9 @@ namespace Ink_Canvas
|
||||
InkCanvas.SetLeft(clonedImage, InkCanvas.GetLeft(image) + 20);
|
||||
InkCanvas.SetTop(clonedImage, InkCanvas.GetTop(image) + 20);
|
||||
|
||||
// 添加鼠标事件处理,使图片可以被选择
|
||||
clonedImage.MouseDown += UIElement_MouseDown;
|
||||
clonedImage.IsManipulationEnabled = true;
|
||||
|
||||
// 添加到画布
|
||||
inkCanvas.Children.Add(clonedImage);
|
||||
|
||||
// 选择新克隆的图片
|
||||
DeselectUIElement();
|
||||
SelectUIElement(clonedImage);
|
||||
|
||||
// 提交到时间机器以支持撤销
|
||||
timeMachine.CommitElementInsertHistory(clonedImage);
|
||||
}
|
||||
@@ -261,6 +759,8 @@ namespace Ink_Canvas
|
||||
// 记录错误但不中断程序
|
||||
System.Diagnostics.Debug.WriteLine($"克隆图片时发生错误: {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -287,20 +787,12 @@ namespace Ink_Canvas
|
||||
InkCanvas.SetLeft(clonedImage, InkCanvas.GetLeft(image) + 20);
|
||||
InkCanvas.SetTop(clonedImage, InkCanvas.GetTop(image) + 20);
|
||||
|
||||
// 添加鼠标事件处理,使图片可以被选择
|
||||
clonedImage.MouseDown += UIElement_MouseDown;
|
||||
clonedImage.IsManipulationEnabled = true;
|
||||
|
||||
// 创建新页面
|
||||
BtnWhiteBoardAdd_Click(null, null);
|
||||
|
||||
// 添加到新页面的画布
|
||||
inkCanvas.Children.Add(clonedImage);
|
||||
|
||||
// 选择新克隆的图片
|
||||
DeselectUIElement();
|
||||
SelectUIElement(clonedImage);
|
||||
|
||||
// 提交到时间机器以支持撤销
|
||||
timeMachine.CommitElementInsertHistory(clonedImage);
|
||||
}
|
||||
@@ -379,9 +871,6 @@ namespace Ink_Canvas
|
||||
{
|
||||
inkCanvas.Children.Remove(image);
|
||||
|
||||
// 取消选择
|
||||
DeselectUIElement();
|
||||
|
||||
// 提交到时间机器以支持撤销
|
||||
timeMachine.CommitElementRemoveHistory(image);
|
||||
}
|
||||
@@ -397,32 +886,349 @@ namespace Ink_Canvas
|
||||
|
||||
private void CenterAndScaleElement(FrameworkElement element)
|
||||
{
|
||||
double maxWidth = SystemParameters.PrimaryScreenWidth / 2;
|
||||
double maxHeight = SystemParameters.PrimaryScreenHeight / 2;
|
||||
try
|
||||
{
|
||||
// 确保元素已加载且有有效尺寸
|
||||
if (element == null || element.ActualWidth <= 0 || element.ActualHeight <= 0)
|
||||
{
|
||||
// 如果元素尺寸无效,等待加载完成后再处理
|
||||
element.Loaded += (sender, e) =>
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
CenterAndScaleElement(element);
|
||||
}), System.Windows.Threading.DispatcherPriority.Loaded);
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
double scaleX = maxWidth / element.Width;
|
||||
double scaleY = maxHeight / element.Height;
|
||||
double scale = Math.Min(scaleX, scaleY);
|
||||
// 获取画布的实际尺寸
|
||||
double canvasWidth = inkCanvas.ActualWidth;
|
||||
double canvasHeight = inkCanvas.ActualHeight;
|
||||
|
||||
// 直接设置元素的大小,而不使用RenderTransform
|
||||
double newWidth = element.Width * scale;
|
||||
double newHeight = element.Height * scale;
|
||||
// 如果画布尺寸为0,使用窗口尺寸作为备选
|
||||
if (canvasWidth <= 0 || canvasHeight <= 0)
|
||||
{
|
||||
canvasWidth = this.ActualWidth;
|
||||
canvasHeight = this.ActualHeight;
|
||||
}
|
||||
|
||||
element.Width = newWidth;
|
||||
element.Height = newHeight;
|
||||
// 如果仍然为0,使用屏幕尺寸
|
||||
if (canvasWidth <= 0 || canvasHeight <= 0)
|
||||
{
|
||||
canvasWidth = SystemParameters.PrimaryScreenWidth;
|
||||
canvasHeight = SystemParameters.PrimaryScreenHeight;
|
||||
}
|
||||
|
||||
// 计算居中位置
|
||||
double canvasWidth = inkCanvas.ActualWidth;
|
||||
double canvasHeight = inkCanvas.ActualHeight;
|
||||
double centerX = (canvasWidth - newWidth) / 2;
|
||||
double centerY = (canvasHeight - newHeight) / 2;
|
||||
// 计算最大允许尺寸(画布的70%)
|
||||
double maxWidth = canvasWidth * 0.7;
|
||||
double maxHeight = canvasHeight * 0.7;
|
||||
|
||||
// 直接设置位置,而不使用RenderTransform
|
||||
InkCanvas.SetLeft(element, centerX);
|
||||
InkCanvas.SetTop(element, centerY);
|
||||
// 获取元素的当前尺寸
|
||||
double elementWidth = element.ActualWidth;
|
||||
double elementHeight = element.ActualHeight;
|
||||
|
||||
// 清除任何现有的RenderTransform
|
||||
element.RenderTransform = Transform.Identity;
|
||||
// 计算缩放比例
|
||||
double scaleX = maxWidth / elementWidth;
|
||||
double scaleY = maxHeight / elementHeight;
|
||||
double scale = Math.Min(scaleX, scaleY);
|
||||
|
||||
// 如果元素本身比最大尺寸小,不进行缩放
|
||||
if (scale > 1.0)
|
||||
{
|
||||
scale = 1.0;
|
||||
}
|
||||
|
||||
// 计算新的尺寸
|
||||
double newWidth = elementWidth * scale;
|
||||
double newHeight = elementHeight * scale;
|
||||
|
||||
// 设置元素尺寸
|
||||
element.Width = newWidth;
|
||||
element.Height = newHeight;
|
||||
|
||||
// 计算居中位置
|
||||
double centerX = (canvasWidth - newWidth) / 2;
|
||||
double centerY = (canvasHeight - newHeight) / 2;
|
||||
|
||||
// 确保位置不为负数
|
||||
centerX = Math.Max(0, centerX);
|
||||
centerY = Math.Max(0, centerY);
|
||||
|
||||
// 设置位置
|
||||
InkCanvas.SetLeft(element, centerX);
|
||||
InkCanvas.SetTop(element, centerY);
|
||||
|
||||
// 保持TransformGroup,不清除RenderTransform
|
||||
// 这样可以保持滚轮缩放和拖动功能
|
||||
if (element.RenderTransform == null || element.RenderTransform == Transform.Identity)
|
||||
{
|
||||
// 只有在没有TransformGroup时才创建
|
||||
InitializeElementTransform(element);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"元素居中完成: 位置({centerX}, {centerY}), 尺寸({newWidth}x{newHeight})");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"元素居中失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化InkCanvas选择设置
|
||||
private void InitializeInkCanvasSelectionSettings()
|
||||
{
|
||||
if (inkCanvas != null)
|
||||
{
|
||||
// 清除当前选择,避免显示控制点
|
||||
inkCanvas.Select(new StrokeCollection());
|
||||
// 设置编辑模式为非选择模式
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新图片选择工具栏位置
|
||||
private void UpdateImageSelectionToolbarPosition(FrameworkElement element)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (BorderImageSelectionControl == null || element == null) return;
|
||||
|
||||
// 获取元素在画布中的位置
|
||||
double elementLeft = InkCanvas.GetLeft(element);
|
||||
double elementTop = InkCanvas.GetTop(element);
|
||||
double elementWidth = element.ActualWidth;
|
||||
double elementHeight = element.ActualHeight;
|
||||
|
||||
// 如果元素位置未设置,使用默认值
|
||||
if (double.IsNaN(elementLeft)) elementLeft = 0;
|
||||
if (double.IsNaN(elementTop)) elementTop = 0;
|
||||
|
||||
// 计算工具栏位置(显示在图片下方)
|
||||
double toolbarLeft = elementLeft + (elementWidth / 2) - (BorderImageSelectionControl.ActualWidth / 2);
|
||||
double toolbarTop = elementTop + elementHeight + 10; // 图片下方10像素
|
||||
|
||||
// 确保工具栏不超出画布边界
|
||||
double maxLeft = inkCanvas.ActualWidth - BorderImageSelectionControl.ActualWidth;
|
||||
double maxTop = inkCanvas.ActualHeight - BorderImageSelectionControl.ActualHeight;
|
||||
|
||||
toolbarLeft = Math.Max(0, Math.Min(toolbarLeft, maxLeft));
|
||||
toolbarTop = Math.Max(0, Math.Min(toolbarTop, maxTop));
|
||||
|
||||
// 设置工具栏位置
|
||||
BorderImageSelectionControl.Margin = new Thickness(toolbarLeft, toolbarTop, 0, 0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新图片选择工具栏位置失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#region Image Selection Toolbar Event Handlers
|
||||
|
||||
// 图片克隆功能
|
||||
private void BorderImageClone_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (currentSelectedElement is Image originalImage)
|
||||
{
|
||||
// 创建克隆图片
|
||||
Image clonedImage = CloneImage(originalImage);
|
||||
|
||||
// 添加到画布
|
||||
inkCanvas.Children.Add(clonedImage);
|
||||
|
||||
// 初始化变换
|
||||
InitializeElementTransform(clonedImage);
|
||||
|
||||
// 绑定事件
|
||||
BindElementEvents(clonedImage);
|
||||
|
||||
// 记录历史
|
||||
timeMachine.CommitElementInsertHistory(clonedImage);
|
||||
|
||||
LogHelper.WriteLogToFile($"图片克隆完成: {clonedImage.Name}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"图片克隆失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 图片克隆到新页面
|
||||
private void BorderImageCloneToNewBoard_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (currentSelectedElement is Image originalImage)
|
||||
{
|
||||
// 创建克隆图片
|
||||
Image clonedImage = CloneImage(originalImage);
|
||||
|
||||
// 这里可以添加切换到新页面的逻辑
|
||||
// 暂时先添加到当前页面
|
||||
inkCanvas.Children.Add(clonedImage);
|
||||
|
||||
// 初始化变换
|
||||
InitializeElementTransform(clonedImage);
|
||||
|
||||
// 绑定事件
|
||||
BindElementEvents(clonedImage);
|
||||
|
||||
// 记录历史
|
||||
timeMachine.CommitElementInsertHistory(clonedImage);
|
||||
|
||||
LogHelper.WriteLogToFile($"图片克隆到新页面完成: {clonedImage.Name}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"图片克隆到新页面失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 图片左旋转
|
||||
private void BorderImageRotateLeft_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (currentSelectedElement != null)
|
||||
{
|
||||
ApplyRotateTransform(currentSelectedElement, -45);
|
||||
LogHelper.WriteLogToFile($"图片左旋转完成");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"图片左旋转失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 图片右旋转
|
||||
private void BorderImageRotateRight_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (currentSelectedElement != null)
|
||||
{
|
||||
ApplyRotateTransform(currentSelectedElement, 45);
|
||||
LogHelper.WriteLogToFile($"图片右旋转完成");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"图片右旋转失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 图片缩放减小
|
||||
private void GridImageScaleDecrease_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (currentSelectedElement != null)
|
||||
{
|
||||
var elementCenter = new Point(currentSelectedElement.ActualWidth / 2, currentSelectedElement.ActualHeight / 2);
|
||||
ApplyScaleTransform(currentSelectedElement, 0.9, elementCenter);
|
||||
LogHelper.WriteLogToFile($"图片缩放减小完成");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"图片缩放减小失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 图片缩放增大
|
||||
private void GridImageScaleIncrease_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (currentSelectedElement != null)
|
||||
{
|
||||
var elementCenter = new Point(currentSelectedElement.ActualWidth / 2, currentSelectedElement.ActualHeight / 2);
|
||||
ApplyScaleTransform(currentSelectedElement, 1.1, elementCenter);
|
||||
LogHelper.WriteLogToFile($"图片缩放增大完成");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"图片缩放增大失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 图片删除
|
||||
private void BorderImageDelete_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (currentSelectedElement != null)
|
||||
{
|
||||
// 记录删除历史
|
||||
timeMachine.CommitElementRemoveHistory(currentSelectedElement);
|
||||
|
||||
// 从画布中移除
|
||||
inkCanvas.Children.Remove(currentSelectedElement);
|
||||
|
||||
// 清除选中状态
|
||||
UnselectElement(currentSelectedElement);
|
||||
currentSelectedElement = null;
|
||||
|
||||
LogHelper.WriteLogToFile($"图片删除完成");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"图片删除失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 克隆图片的辅助方法
|
||||
private Image CreateClonedImage(Image originalImage)
|
||||
{
|
||||
try
|
||||
{
|
||||
Image clonedImage = new Image();
|
||||
|
||||
// 复制图片源
|
||||
if (originalImage.Source is BitmapSource bitmapSource)
|
||||
{
|
||||
clonedImage.Source = bitmapSource;
|
||||
}
|
||||
|
||||
// 复制属性
|
||||
clonedImage.Width = originalImage.Width;
|
||||
clonedImage.Height = originalImage.Height;
|
||||
clonedImage.Stretch = originalImage.Stretch;
|
||||
clonedImage.StretchDirection = originalImage.StretchDirection;
|
||||
|
||||
// 复制位置
|
||||
double left = InkCanvas.GetLeft(originalImage);
|
||||
double top = InkCanvas.GetTop(originalImage);
|
||||
InkCanvas.SetLeft(clonedImage, left + 20); // 稍微偏移位置
|
||||
InkCanvas.SetTop(clonedImage, top + 20);
|
||||
|
||||
// 复制变换
|
||||
if (originalImage.RenderTransform is TransformGroup originalTransformGroup)
|
||||
{
|
||||
clonedImage.RenderTransform = originalTransformGroup.Clone();
|
||||
}
|
||||
|
||||
// 设置名称
|
||||
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||
clonedImage.Name = timestamp;
|
||||
|
||||
return clonedImage;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"克隆图片失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Threading;
|
||||
using Application = System.Windows.Application;
|
||||
using Button = System.Windows.Controls.Button;
|
||||
using Cursors = System.Windows.Input.Cursors;
|
||||
using HorizontalAlignment = System.Windows.HorizontalAlignment;
|
||||
using Image = System.Windows.Controls.Image;
|
||||
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
||||
@@ -238,6 +238,9 @@ namespace Ink_Canvas
|
||||
BoardBorderLeftPageListView.Visibility = Visibility.Collapsed;
|
||||
BoardBorderRightPageListView.Visibility = Visibility.Collapsed;
|
||||
BoardImageOptionsPanel.Visibility = Visibility.Collapsed;
|
||||
// 添加隐藏图形工具的二级菜单面板
|
||||
BorderDrawShape.Visibility = Visibility.Collapsed;
|
||||
BoardBorderDrawShape.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -2501,8 +2504,6 @@ namespace Ink_Canvas
|
||||
AnimationsHelper.HideWithSlideAndFade(BlackboardCenterSide);
|
||||
AnimationsHelper.HideWithSlideAndFade(BlackboardRightSide);
|
||||
|
||||
DeselectUIElement();
|
||||
|
||||
// 在PPT模式下隐藏手势面板和手势按钮
|
||||
AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder);
|
||||
@@ -2550,9 +2551,6 @@ namespace Ink_Canvas
|
||||
AnimationsHelper.HideWithSlideAndFade(BlackboardCenterSide);
|
||||
AnimationsHelper.HideWithSlideAndFade(BlackboardRightSide);
|
||||
|
||||
// 取消任何UI元素的选择
|
||||
DeselectUIElement();
|
||||
|
||||
// 在PPT模式下隐藏手势面板和手势按钮
|
||||
AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder);
|
||||
@@ -2593,9 +2591,6 @@ namespace Ink_Canvas
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BlackboardCenterSide);
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BlackboardRightSide);
|
||||
|
||||
// 取消任何UI元素的选择
|
||||
DeselectUIElement();
|
||||
|
||||
SaveStrokes(true);
|
||||
ClearStrokes(true);
|
||||
|
||||
@@ -2776,17 +2771,20 @@ namespace Ink_Canvas
|
||||
|
||||
private void InsertImageOptions_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// Hide other sub-panels first
|
||||
HideSubPanelsImmediately();
|
||||
|
||||
// Show the image options panel
|
||||
if (BoardImageOptionsPanel.Visibility == Visibility.Collapsed)
|
||||
// Check if the image options panel is currently visible
|
||||
bool isImagePanelVisible = BoardImageOptionsPanel.Visibility == Visibility.Visible;
|
||||
|
||||
// Toggle the image options panel
|
||||
if (isImagePanelVisible)
|
||||
{
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardImageOptionsPanel);
|
||||
// Panel was visible, so hide it with animation
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardImageOptionsPanel);
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardImageOptionsPanel);
|
||||
// Panel was hidden, so hide other panels and show this one
|
||||
HideSubPanelsImmediately();
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardImageOptionsPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2803,8 +2801,8 @@ namespace Ink_Canvas
|
||||
// Wait a bit for the panel to hide
|
||||
await Task.Delay(100);
|
||||
|
||||
// Capture screenshot and copy to clipboard
|
||||
await CaptureScreenshotToClipboard();
|
||||
// Capture screenshot and insert to canvas
|
||||
await CaptureScreenshotAndInsert();
|
||||
}
|
||||
|
||||
private async void ImageOptionSelectFile_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
@@ -2826,12 +2824,116 @@ namespace Ink_Canvas
|
||||
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());
|
||||
// 同时通过图片的IsHitTestVisible和Focusable属性来避免InkCanvas选择系统的干扰
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
}
|
||||
|
||||
inkCanvas.Children.Add(image);
|
||||
|
||||
// 添加鼠标事件处理,使图片可以被选择
|
||||
image.MouseDown += UIElement_MouseDown;
|
||||
image.IsManipulationEnabled = true;
|
||||
// 绑定事件处理器
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:插入图片方法
|
||||
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);
|
||||
}
|
||||
@@ -2854,12 +2956,50 @@ namespace Ink_Canvas
|
||||
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);
|
||||
|
||||
// 添加鼠标事件处理,使图片可以被选择
|
||||
image.MouseDown += UIElement_MouseDown;
|
||||
image.IsManipulationEnabled = true;
|
||||
// 绑定事件处理器
|
||||
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);
|
||||
}
|
||||
@@ -2971,13 +3111,13 @@ namespace Ink_Canvas
|
||||
quickColorPaletteMode = "single";
|
||||
}
|
||||
|
||||
// 获取实际按钮宽度,如果获取不到则使用默认值
|
||||
double cursorWidth = Cursor_Icon?.ActualWidth > 0 ? Cursor_Icon.ActualWidth : buttonWidth;
|
||||
double penWidth = Pen_Icon?.ActualWidth > 0 ? Pen_Icon.ActualWidth : buttonWidth;
|
||||
double deleteWidth = SymbolIconDelete?.ActualWidth > 0 ? SymbolIconDelete.ActualWidth : buttonWidth;
|
||||
double eraserWidth = Eraser_Icon?.ActualWidth > 0 ? Eraser_Icon.ActualWidth : buttonWidth;
|
||||
double eraserByStrokesWidth = EraserByStrokes_Icon?.ActualWidth > 0 ? EraserByStrokes_Icon.ActualWidth : buttonWidth;
|
||||
double selectWidth = SymbolIconSelect?.ActualWidth > 0 ? SymbolIconSelect.ActualWidth : buttonWidth;
|
||||
// 获取实际按钮宽度,如果获取不到则使用默认值,同时考虑按钮的可见性
|
||||
double cursorWidth = (Cursor_Icon?.Visibility == Visibility.Visible && Cursor_Icon?.ActualWidth > 0) ? Cursor_Icon.ActualWidth : 0;
|
||||
double penWidth = (Pen_Icon?.Visibility == Visibility.Visible && Pen_Icon?.ActualWidth > 0) ? Pen_Icon.ActualWidth : 0;
|
||||
double deleteWidth = (SymbolIconDelete?.Visibility == Visibility.Visible && SymbolIconDelete?.ActualWidth > 0) ? SymbolIconDelete.ActualWidth : 0;
|
||||
double eraserWidth = (Eraser_Icon?.Visibility == Visibility.Visible && Eraser_Icon?.ActualWidth > 0) ? Eraser_Icon.ActualWidth : 0;
|
||||
double eraserByStrokesWidth = (EraserByStrokes_Icon?.Visibility == Visibility.Visible && EraserByStrokes_Icon?.ActualWidth > 0) ? EraserByStrokes_Icon.ActualWidth : 0;
|
||||
double selectWidth = (SymbolIconSelect?.Visibility == Visibility.Visible && SymbolIconSelect?.ActualWidth > 0) ? SymbolIconSelect.ActualWidth : 0;
|
||||
|
||||
// 获取高光的实际宽度
|
||||
double actualHighlightWidth = FloatingbarSelectionBG.ActualWidth > 0 ? FloatingbarSelectionBG.ActualWidth : highlightWidth;
|
||||
@@ -3074,6 +3214,60 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前选中的模式
|
||||
/// </summary>
|
||||
/// <returns>当前选中的模式名称</returns>
|
||||
public string GetCurrentSelectedMode()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查当前编辑模式
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.Select)
|
||||
{
|
||||
return "select";
|
||||
}
|
||||
else if (inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
|
||||
{
|
||||
// 检查是否是荧光笔模式
|
||||
if (drawingAttributes != null && drawingAttributes.IsHighlighter)
|
||||
{
|
||||
return "color";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "pen";
|
||||
}
|
||||
}
|
||||
else if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
|
||||
{
|
||||
// 检查是面积擦还是线擦
|
||||
if (Eraser_Icon != null && Eraser_Icon.Visibility == Visibility.Visible)
|
||||
{
|
||||
return "eraser";
|
||||
}
|
||||
else if (EraserByStrokes_Icon != null && EraserByStrokes_Icon.Visibility == Visibility.Visible)
|
||||
{
|
||||
return "eraserByStrokes";
|
||||
}
|
||||
}
|
||||
else if (inkCanvas.EditingMode == InkCanvasEditingMode.None)
|
||||
{
|
||||
return "cursor";
|
||||
}
|
||||
else if (drawingShapeMode != 0)
|
||||
{
|
||||
return "shape";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取当前选中模式失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 触摸事件支持
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Diagnostics;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
@@ -81,10 +77,18 @@ namespace Ink_Canvas
|
||||
PenIcon_Click(lastBorderMouseDownObject, null);
|
||||
}
|
||||
|
||||
private void KeyChangeToQuitDrawTool(object sender, ExecutedRoutedEventArgs e)
|
||||
internal void KeyChangeToQuitDrawTool(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
if (currentMode != 0) ImageBlackboard_MouseUp(lastBorderMouseDownObject, null);
|
||||
CursorIcon_Click(lastBorderMouseDownObject, null);
|
||||
if (currentMode != 0)
|
||||
{
|
||||
// 在白板模式下,alt+q 退出白板模式
|
||||
ImageBlackboard_MouseUp(lastBorderMouseDownObject, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 在非白板模式下,alt+q 切换到鼠标模式
|
||||
CursorIcon_Click(lastBorderMouseDownObject, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void KeyChangeToSelect(object sender, ExecutedRoutedEventArgs e)
|
||||
|
||||
@@ -0,0 +1,460 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Application = System.Windows.Application;
|
||||
using PixelFormat = System.Drawing.Imaging.PixelFormat;
|
||||
using Size = System.Drawing.Size;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
// 截图结果结构体
|
||||
public struct ScreenshotResult
|
||||
{
|
||||
public Rectangle Area;
|
||||
public List<System.Windows.Point> Path;
|
||||
|
||||
public ScreenshotResult(Rectangle area, List<System.Windows.Point> path = null)
|
||||
{
|
||||
Area = area;
|
||||
Path = path;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
// 截图并插入到画布
|
||||
private async Task CaptureScreenshotAndInsert()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 隐藏主窗口以避免截图包含窗口本身
|
||||
var originalVisibility = Visibility;
|
||||
Visibility = Visibility.Hidden;
|
||||
|
||||
// 等待窗口隐藏
|
||||
await Task.Delay(200);
|
||||
|
||||
// 启动区域选择截图
|
||||
var screenshotResult = await ShowScreenshotSelector();
|
||||
|
||||
// 恢复窗口显示
|
||||
Visibility = originalVisibility;
|
||||
|
||||
if (screenshotResult.HasValue && screenshotResult.Value.Area.Width > 0 && screenshotResult.Value.Area.Height > 0)
|
||||
{
|
||||
// 截取选定区域
|
||||
using (var originalBitmap = CaptureScreenArea(screenshotResult.Value.Area))
|
||||
{
|
||||
if (originalBitmap != null)
|
||||
{
|
||||
Bitmap finalBitmap = originalBitmap;
|
||||
bool needDisposeFinalBitmap = false;
|
||||
|
||||
try
|
||||
{
|
||||
// 如果有路径信息,应用形状遮罩
|
||||
if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0)
|
||||
{
|
||||
finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
|
||||
needDisposeFinalBitmap = true; // 标记需要释放新创建的位图
|
||||
}
|
||||
|
||||
// 将截图转换为WPF Image并插入到画布
|
||||
await InsertScreenshotToCanvas(finalBitmap);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 如果创建了新的位图,需要释放它
|
||||
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
|
||||
{
|
||||
finalBitmap.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowNotification("截图已取消");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowNotification($"截图失败: {ex.Message}");
|
||||
Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示截图区域选择器
|
||||
private async Task<ScreenshotResult?> ShowScreenshotSelector()
|
||||
{
|
||||
ScreenshotResult? result = null;
|
||||
|
||||
try
|
||||
{
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var selectorWindow = new ScreenshotSelectorWindow();
|
||||
if (selectorWindow.ShowDialog() == true)
|
||||
{
|
||||
result = new ScreenshotResult(
|
||||
selectorWindow.SelectedArea.Value,
|
||||
selectorWindow.SelectedPath
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"显示截图选择器失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 截取指定屏幕区域
|
||||
private Bitmap CaptureScreenArea(Rectangle area)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保区域在有效范围内
|
||||
var virtualScreen = SystemInformation.VirtualScreen;
|
||||
|
||||
// 调整区域边界,确保不超出屏幕范围
|
||||
int x = Math.Max(area.X, virtualScreen.X);
|
||||
int y = Math.Max(area.Y, virtualScreen.Y);
|
||||
int right = Math.Min(area.Right, virtualScreen.Right);
|
||||
int bottom = Math.Min(area.Bottom, virtualScreen.Bottom);
|
||||
|
||||
int width = Math.Max(1, right - x);
|
||||
int height = Math.Max(1, bottom - y);
|
||||
|
||||
// 创建支持透明度的位图
|
||||
var bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
|
||||
using (var graphics = Graphics.FromImage(bitmap))
|
||||
{
|
||||
// 设置高质量渲染
|
||||
graphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
graphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||
graphics.CompositingMode = CompositingMode.SourceOver;
|
||||
|
||||
// 截取屏幕区域
|
||||
graphics.CopyFromScreen(x, y, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"成功截取区域: X={x}, Y={y}, Width={width}, Height={height}");
|
||||
return bitmap;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"截取屏幕区域失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 将截图插入到画布
|
||||
private async Task InsertScreenshotToCanvas(Bitmap bitmap)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 将Bitmap转换为WPF BitmapSource
|
||||
var bitmapSource = ConvertBitmapToBitmapSource(bitmap);
|
||||
|
||||
// 创建WPF Image控件
|
||||
var image = new System.Windows.Controls.Image
|
||||
{
|
||||
Source = bitmapSource,
|
||||
Stretch = Stretch.Uniform
|
||||
};
|
||||
RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.HighQuality);
|
||||
|
||||
// 生成唯一名称
|
||||
string timestamp = "screenshot_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||
image.Name = timestamp;
|
||||
|
||||
// 初始化TransformGroup
|
||||
InitializeScreenshotTransform(image);
|
||||
|
||||
// 设置截图属性,避免被InkCanvas选择系统处理
|
||||
image.IsHitTestVisible = true;
|
||||
image.Focusable = false;
|
||||
|
||||
// 初始化InkCanvas选择设置
|
||||
InitializeInkCanvasSelectionSettings();
|
||||
|
||||
// 等待图片加载完成后再进行居中处理
|
||||
image.Loaded += (sender, e) =>
|
||||
{
|
||||
// 确保在UI线程中执行
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
CenterAndScaleScreenshot(image);
|
||||
// 绑定事件处理器
|
||||
BindScreenshotEvents(image);
|
||||
}), System.Windows.Threading.DispatcherPriority.Loaded);
|
||||
};
|
||||
|
||||
// 添加到画布
|
||||
inkCanvas.Children.Add(image);
|
||||
|
||||
// 提交历史记录
|
||||
timeMachine.CommitElementInsertHistory(image);
|
||||
|
||||
ShowNotification("截图已插入到画布");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowNotification($"插入截图失败: {ex.Message}");
|
||||
LogHelper.WriteLogToFile($"插入截图失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化截图的TransformGroup
|
||||
private void InitializeScreenshotTransform(System.Windows.Controls.Image image)
|
||||
{
|
||||
var transformGroup = new TransformGroup();
|
||||
transformGroup.Children.Add(new ScaleTransform(1, 1));
|
||||
transformGroup.Children.Add(new TranslateTransform(0, 0));
|
||||
transformGroup.Children.Add(new RotateTransform(0));
|
||||
image.RenderTransform = transformGroup;
|
||||
}
|
||||
|
||||
// 绑定截图事件处理器
|
||||
private void BindScreenshotEvents(System.Windows.Controls.Image image)
|
||||
{
|
||||
// 鼠标事件
|
||||
image.MouseLeftButtonDown += Element_MouseLeftButtonDown;
|
||||
image.MouseLeftButtonUp += Element_MouseLeftButtonUp;
|
||||
image.MouseMove += Element_MouseMove;
|
||||
image.MouseWheel += Element_MouseWheel;
|
||||
|
||||
// 触摸事件
|
||||
image.IsManipulationEnabled = true;
|
||||
image.ManipulationDelta += Element_ManipulationDelta;
|
||||
image.ManipulationCompleted += Element_ManipulationCompleted;
|
||||
|
||||
// 设置光标
|
||||
image.Cursor = System.Windows.Input.Cursors.Hand;
|
||||
|
||||
// 禁用InkCanvas对截图的选择处理
|
||||
image.IsHitTestVisible = true;
|
||||
image.Focusable = false;
|
||||
}
|
||||
|
||||
// 专门为截图优化的居中缩放方法
|
||||
private void CenterAndScaleScreenshot(System.Windows.Controls.Image image)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保图片已加载
|
||||
if (image.Source == null || image.ActualWidth == 0 || image.ActualHeight == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取画布的实际尺寸
|
||||
double canvasWidth = inkCanvas.ActualWidth;
|
||||
double canvasHeight = inkCanvas.ActualHeight;
|
||||
|
||||
// 如果画布尺寸为0,使用窗口尺寸作为备选
|
||||
if (canvasWidth <= 0 || canvasHeight <= 0)
|
||||
{
|
||||
canvasWidth = this.ActualWidth;
|
||||
canvasHeight = this.ActualHeight;
|
||||
}
|
||||
|
||||
// 如果仍然为0,使用屏幕尺寸
|
||||
if (canvasWidth <= 0 || canvasHeight <= 0)
|
||||
{
|
||||
canvasWidth = SystemParameters.PrimaryScreenWidth;
|
||||
canvasHeight = SystemParameters.PrimaryScreenHeight;
|
||||
}
|
||||
|
||||
// 计算最大允许尺寸(画布的80%)
|
||||
double maxWidth = canvasWidth * 0.8;
|
||||
double maxHeight = canvasHeight * 0.8;
|
||||
|
||||
// 获取图片的原始尺寸
|
||||
double originalWidth = image.Source.Width;
|
||||
double originalHeight = image.Source.Height;
|
||||
|
||||
// 计算缩放比例
|
||||
double scaleX = maxWidth / originalWidth;
|
||||
double scaleY = maxHeight / originalHeight;
|
||||
double scale = Math.Min(scaleX, scaleY);
|
||||
|
||||
// 如果图片本身比最大尺寸小,不进行缩放
|
||||
if (scale > 1.0)
|
||||
{
|
||||
scale = 1.0;
|
||||
}
|
||||
|
||||
// 计算新的尺寸
|
||||
double newWidth = originalWidth * scale;
|
||||
double newHeight = originalHeight * scale;
|
||||
|
||||
// 设置图片尺寸
|
||||
image.Width = newWidth;
|
||||
image.Height = newHeight;
|
||||
|
||||
// 计算居中位置
|
||||
double centerX = (canvasWidth - newWidth) / 2;
|
||||
double centerY = (canvasHeight - newHeight) / 2;
|
||||
|
||||
// 确保位置不为负数
|
||||
centerX = Math.Max(0, centerX);
|
||||
centerY = Math.Max(0, centerY);
|
||||
|
||||
// 设置位置
|
||||
InkCanvas.SetLeft(image, centerX);
|
||||
InkCanvas.SetTop(image, centerY);
|
||||
|
||||
// 清除任何现有的RenderTransform
|
||||
image.RenderTransform = Transform.Identity;
|
||||
|
||||
LogHelper.WriteLogToFile($"截图居中完成: 位置({centerX}, {centerY}), 尺寸({newWidth}x{newHeight})");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"截图居中失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
// 如果居中失败,使用默认的居中方法作为备选
|
||||
CenterAndScaleElement(image);
|
||||
}
|
||||
}
|
||||
|
||||
// 应用形状遮罩到截图
|
||||
private Bitmap ApplyShapeMask(Bitmap bitmap, List<System.Windows.Point> path, Rectangle area)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 验证路径参数
|
||||
if (path == null || path.Count < 3)
|
||||
{
|
||||
LogHelper.WriteLogToFile("路径点数不足,无法应用形状遮罩", LogHelper.LogType.Warning);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
// 获取DPI缩放比例
|
||||
var dpiScale = GetDpiScale();
|
||||
var virtualScreen = SystemInformation.VirtualScreen;
|
||||
|
||||
// 创建结果位图,确保支持透明度
|
||||
var resultBitmap = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format32bppArgb);
|
||||
|
||||
// 首先将整个位图设置为透明
|
||||
using (var resultGraphics = Graphics.FromImage(resultBitmap))
|
||||
{
|
||||
// 清除位图,设置为完全透明
|
||||
resultGraphics.Clear(System.Drawing.Color.Transparent);
|
||||
|
||||
// 设置高质量渲染
|
||||
resultGraphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
resultGraphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||
resultGraphics.CompositingMode = CompositingMode.SourceOver;
|
||||
|
||||
// 创建路径
|
||||
using (var pathGraphics = new GraphicsPath())
|
||||
{
|
||||
// 转换WPF坐标到GDI+坐标,考虑DPI缩放和屏幕偏移
|
||||
var points = new PointF[path.Count];
|
||||
for (int i = 0; i < path.Count; i++)
|
||||
{
|
||||
// 将WPF坐标转换为实际屏幕坐标,然后相对于截图区域计算偏移
|
||||
double screenX = (path[i].X * dpiScale) + virtualScreen.Left;
|
||||
double screenY = (path[i].Y * dpiScale) + virtualScreen.Top;
|
||||
|
||||
// 计算相对于截图区域的坐标
|
||||
float relativeX = (float)(screenX - area.X);
|
||||
float relativeY = (float)(screenY - area.Y);
|
||||
|
||||
// 确保坐标在有效范围内
|
||||
relativeX = Math.Max(0, Math.Min(relativeX, bitmap.Width - 1));
|
||||
relativeY = Math.Max(0, Math.Min(relativeY, bitmap.Height - 1));
|
||||
|
||||
points[i] = new PointF(relativeX, relativeY);
|
||||
}
|
||||
|
||||
// 添加路径 - 使用FillMode.Winding确保路径正确填充
|
||||
pathGraphics.FillMode = FillMode.Winding;
|
||||
pathGraphics.AddPolygon(points);
|
||||
|
||||
// 验证路径是否有效
|
||||
if (!pathGraphics.IsVisible(0, 0) && pathGraphics.GetBounds().Width > 0 && pathGraphics.GetBounds().Height > 0)
|
||||
{
|
||||
// 设置裁剪区域为路径内部
|
||||
resultGraphics.SetClip(pathGraphics);
|
||||
|
||||
// 在裁剪区域内绘制原始图像
|
||||
resultGraphics.DrawImage(bitmap, 0, 0);
|
||||
|
||||
// 重置裁剪区域,确保后续操作不受影响
|
||||
resultGraphics.ResetClip();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("生成的路径无效,返回透明图像", LogHelper.LogType.Warning);
|
||||
// 如果路径无效,返回透明图像
|
||||
return resultBitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resultBitmap;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用形状遮罩失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
// 将System.Drawing.Bitmap转换为WPF BitmapSource
|
||||
private BitmapSource ConvertBitmapToBitmapSource(Bitmap bitmap)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var memory = new MemoryStream())
|
||||
{
|
||||
bitmap.Save(memory, ImageFormat.Png);
|
||||
memory.Position = 0;
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapImage.StreamSource = memory;
|
||||
bitmapImage.EndInit();
|
||||
bitmapImage.Freeze();
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"转换位图失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取DPI缩放比例
|
||||
private double GetDpiScale()
|
||||
{
|
||||
var source = PresentationSource.FromVisual(this);
|
||||
if (source?.CompositionTarget != null)
|
||||
{
|
||||
return source.CompositionTarget.TransformToDevice.M11;
|
||||
}
|
||||
return 1.0; // 默认DPI
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,8 +86,8 @@ namespace Ink_Canvas
|
||||
private DispatcherTimer _longPressTimer;
|
||||
private bool _isLongPressActive = false;
|
||||
private bool _isLongPressNext = true; // true为下一页,false为上一页
|
||||
private const int LongPressDelay = 50; // 长按延迟时间(毫秒)
|
||||
private const int LongPressInterval = 50; // 长按翻页间隔(毫秒)
|
||||
private const int LongPressDelay = 15; // 长按延迟时间(毫秒)
|
||||
private const int LongPressInterval = 15; // 长按翻页间隔(毫秒)
|
||||
#endregion
|
||||
|
||||
#region PPT Managers
|
||||
|
||||
@@ -86,9 +86,6 @@ namespace Ink_Canvas
|
||||
// 只有当选择的页面与当前页面不同时才进行切换
|
||||
if (index + 1 != CurrentWhiteboardIndex)
|
||||
{
|
||||
// 取消任何UI元素的选择(只在真正切换页面时)
|
||||
DeselectUIElement();
|
||||
|
||||
SaveStrokes();
|
||||
ClearStrokes(true);
|
||||
CurrentWhiteboardIndex = index + 1;
|
||||
@@ -111,9 +108,6 @@ namespace Ink_Canvas
|
||||
// 只有当选择的页面与当前页面不同时才进行切换
|
||||
if (index + 1 != CurrentWhiteboardIndex)
|
||||
{
|
||||
// 取消任何UI元素的选择(只在真正切换页面时)
|
||||
DeselectUIElement();
|
||||
|
||||
SaveStrokes();
|
||||
ClearStrokes(true);
|
||||
CurrentWhiteboardIndex = index + 1;
|
||||
|
||||
@@ -1,33 +1,12 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Application = System.Windows.Application;
|
||||
using Clipboard = System.Windows.Clipboard;
|
||||
using Size = System.Drawing.Size;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
// 截图结果结构体
|
||||
public struct ScreenshotResult
|
||||
{
|
||||
public System.Drawing.Rectangle Area;
|
||||
public List<System.Windows.Point> Path;
|
||||
|
||||
public ScreenshotResult(System.Drawing.Rectangle area, List<System.Windows.Point> path = null)
|
||||
{
|
||||
Area = area;
|
||||
Path = path;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private void SaveScreenShot(bool isHideNotification, string fileName = null)
|
||||
@@ -120,312 +99,5 @@ namespace Ink_Canvas
|
||||
screenshotsFolder,
|
||||
$"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.png");
|
||||
}
|
||||
|
||||
// 截图并复制到剪贴板
|
||||
private async Task CaptureScreenshotToClipboard()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 隐藏主窗口以避免截图包含窗口本身
|
||||
var originalVisibility = this.Visibility;
|
||||
this.Visibility = Visibility.Hidden;
|
||||
|
||||
// 等待窗口隐藏
|
||||
await Task.Delay(200);
|
||||
|
||||
// 启动区域选择截图
|
||||
var screenshotResult = await ShowScreenshotSelector();
|
||||
|
||||
// 恢复窗口显示
|
||||
this.Visibility = originalVisibility;
|
||||
|
||||
if (screenshotResult.HasValue && screenshotResult.Value.Area.Width > 0 && screenshotResult.Value.Area.Height > 0)
|
||||
{
|
||||
// 截取选定区域
|
||||
using (var originalBitmap = CaptureScreenArea(screenshotResult.Value.Area))
|
||||
{
|
||||
if (originalBitmap != null)
|
||||
{
|
||||
Bitmap finalBitmap = originalBitmap;
|
||||
bool needDisposeFinalBitmap = false;
|
||||
|
||||
try
|
||||
{
|
||||
// 如果有路径信息,应用形状遮罩
|
||||
if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0)
|
||||
{
|
||||
finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
|
||||
needDisposeFinalBitmap = true; // 标记需要释放新创建的位图
|
||||
}
|
||||
|
||||
// 将截图复制到剪贴板
|
||||
CopyBitmapToClipboard(finalBitmap);
|
||||
|
||||
// 等待窗口完全显示后自动粘贴
|
||||
await Task.Delay(100);
|
||||
await AutoPasteScreenshot();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 如果创建了新的位图,需要释放它
|
||||
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
|
||||
{
|
||||
finalBitmap.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowNotification("截图已取消");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowNotification($"截图失败: {ex.Message}");
|
||||
this.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示截图区域选择器
|
||||
private async Task<ScreenshotResult?> ShowScreenshotSelector()
|
||||
{
|
||||
ScreenshotResult? result = null;
|
||||
|
||||
try
|
||||
{
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var selectorWindow = new ScreenshotSelectorWindow();
|
||||
if (selectorWindow.ShowDialog() == true)
|
||||
{
|
||||
result = new ScreenshotResult(
|
||||
selectorWindow.SelectedArea.Value,
|
||||
selectorWindow.SelectedPath
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"显示截图选择器失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 截取指定屏幕区域
|
||||
private Bitmap CaptureScreenArea(System.Drawing.Rectangle area)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保区域在有效范围内
|
||||
var virtualScreen = SystemInformation.VirtualScreen;
|
||||
|
||||
// 调整区域边界,确保不超出屏幕范围
|
||||
int x = Math.Max(area.X, virtualScreen.X);
|
||||
int y = Math.Max(area.Y, virtualScreen.Y);
|
||||
int right = Math.Min(area.Right, virtualScreen.Right);
|
||||
int bottom = Math.Min(area.Bottom, virtualScreen.Bottom);
|
||||
|
||||
int width = Math.Max(1, right - x);
|
||||
int height = Math.Max(1, bottom - y);
|
||||
|
||||
// 创建支持透明度的位图
|
||||
var bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
|
||||
using (var graphics = Graphics.FromImage(bitmap))
|
||||
{
|
||||
// 设置高质量渲染
|
||||
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
|
||||
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
|
||||
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
|
||||
graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
|
||||
|
||||
// 截取屏幕区域
|
||||
graphics.CopyFromScreen(x, y, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"成功截取区域: X={x}, Y={y}, Width={width}, Height={height}", LogHelper.LogType.Info);
|
||||
return bitmap;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"截取屏幕区域失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 自动粘贴截图到画布
|
||||
private async Task AutoPasteScreenshot()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 只在白板模式下自动粘贴
|
||||
if (currentMode == 1)
|
||||
{
|
||||
await PasteImageFromClipboard();
|
||||
ShowNotification("截图已自动插入到画布");
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowNotification("截图已复制到剪贴板,可在白板模式下粘贴");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowNotification($"自动粘贴截图失败: {ex.Message}");
|
||||
LogHelper.WriteLogToFile($"自动粘贴截图失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 将Bitmap复制到剪贴板
|
||||
private void CopyBitmapToClipboard(Bitmap bitmap)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 将System.Drawing.Bitmap转换为WPF BitmapSource
|
||||
var bitmapSource = ConvertBitmapToBitmapSource(bitmap);
|
||||
|
||||
// 复制到剪贴板
|
||||
Clipboard.SetImage(bitmapSource);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowNotification($"复制到剪贴板失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// 应用形状遮罩到截图
|
||||
private Bitmap ApplyShapeMask(Bitmap bitmap, List<System.Windows.Point> path, System.Drawing.Rectangle area)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 验证路径参数
|
||||
if (path == null || path.Count < 3)
|
||||
{
|
||||
LogHelper.WriteLogToFile("路径点数不足,无法应用形状遮罩", LogHelper.LogType.Warning);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
// 获取DPI缩放比例
|
||||
var dpiScale = GetDpiScale();
|
||||
var virtualScreen = SystemInformation.VirtualScreen;
|
||||
|
||||
// 创建结果位图,确保支持透明度
|
||||
var resultBitmap = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format32bppArgb);
|
||||
|
||||
// 首先将整个位图设置为透明
|
||||
using (var resultGraphics = Graphics.FromImage(resultBitmap))
|
||||
{
|
||||
// 清除位图,设置为完全透明
|
||||
resultGraphics.Clear(System.Drawing.Color.Transparent);
|
||||
|
||||
// 设置高质量渲染
|
||||
resultGraphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
resultGraphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||
resultGraphics.CompositingMode = CompositingMode.SourceOver;
|
||||
|
||||
// 创建路径
|
||||
using (var pathGraphics = new GraphicsPath())
|
||||
{
|
||||
// 转换WPF坐标到GDI+坐标,考虑DPI缩放和屏幕偏移
|
||||
var points = new PointF[path.Count];
|
||||
for (int i = 0; i < path.Count; i++)
|
||||
{
|
||||
// 将WPF坐标转换为实际屏幕坐标,然后相对于截图区域计算偏移
|
||||
double screenX = (path[i].X * dpiScale) + virtualScreen.Left;
|
||||
double screenY = (path[i].Y * dpiScale) + virtualScreen.Top;
|
||||
|
||||
// 计算相对于截图区域的坐标
|
||||
float relativeX = (float)(screenX - area.X);
|
||||
float relativeY = (float)(screenY - area.Y);
|
||||
|
||||
// 确保坐标在有效范围内
|
||||
relativeX = Math.Max(0, Math.Min(relativeX, bitmap.Width - 1));
|
||||
relativeY = Math.Max(0, Math.Min(relativeY, bitmap.Height - 1));
|
||||
|
||||
points[i] = new PointF(relativeX, relativeY);
|
||||
}
|
||||
|
||||
// 添加路径 - 使用FillMode.Winding确保路径正确填充
|
||||
pathGraphics.FillMode = FillMode.Winding;
|
||||
pathGraphics.AddPolygon(points);
|
||||
|
||||
// 验证路径是否有效
|
||||
if (!pathGraphics.IsVisible(0, 0) && pathGraphics.GetBounds().Width > 0 && pathGraphics.GetBounds().Height > 0)
|
||||
{
|
||||
// 设置裁剪区域为路径内部
|
||||
resultGraphics.SetClip(pathGraphics);
|
||||
|
||||
// 在裁剪区域内绘制原始图像
|
||||
resultGraphics.DrawImage(bitmap, 0, 0);
|
||||
|
||||
// 重置裁剪区域,确保后续操作不受影响
|
||||
resultGraphics.ResetClip();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("生成的路径无效,返回原始图像", LogHelper.LogType.Warning);
|
||||
// 如果路径无效,返回透明图像
|
||||
return resultBitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"成功应用形状遮罩,路径点数: {path.Count}", LogHelper.LogType.Info);
|
||||
return resultBitmap;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用形状遮罩失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
// 返回完全透明的图像而不是原始图像
|
||||
var transparentBitmap = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format32bppArgb);
|
||||
using (var g = Graphics.FromImage(transparentBitmap))
|
||||
{
|
||||
g.Clear(System.Drawing.Color.Transparent);
|
||||
}
|
||||
return transparentBitmap;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取DPI缩放比例
|
||||
private double GetDpiScale()
|
||||
{
|
||||
var source = PresentationSource.FromVisual(this);
|
||||
if (source?.CompositionTarget != null)
|
||||
{
|
||||
return source.CompositionTarget.TransformToDevice.M11;
|
||||
}
|
||||
return 1.0; // 默认DPI
|
||||
}
|
||||
|
||||
// 将System.Drawing.Bitmap转换为WPF BitmapSource
|
||||
private BitmapSource ConvertBitmapToBitmapSource(Bitmap bitmap)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var memory = new MemoryStream())
|
||||
{
|
||||
// 使用PNG格式保存,确保透明度信息不丢失
|
||||
bitmap.Save(memory, ImageFormat.Png);
|
||||
memory.Position = 0;
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.StreamSource = memory;
|
||||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapImage.EndInit();
|
||||
bitmapImage.Freeze();
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"转换位图失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Threading;
|
||||
using Point = System.Windows.Point;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
@@ -29,6 +30,7 @@ namespace Ink_Canvas
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
lastBorderMouseDownObject = sender;
|
||||
}
|
||||
|
||||
@@ -93,6 +95,7 @@ namespace Ink_Canvas
|
||||
stroke.DrawingAttributes.Width = newWidth;
|
||||
stroke.DrawingAttributes.Height = newHeight;
|
||||
}
|
||||
|
||||
if (DrawingAttributesHistory.Count > 0)
|
||||
{
|
||||
|
||||
@@ -240,6 +243,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
collecion.Add(item.Key);
|
||||
}
|
||||
|
||||
timeMachine.CommitStrokeDrawingAttributesHistory(DrawingAttributesHistory);
|
||||
DrawingAttributesHistory = new Dictionary<Stroke, Tuple<DrawingAttributes, DrawingAttributes>>();
|
||||
foreach (var item in DrawingAttributesHistoryFlag)
|
||||
@@ -277,9 +281,8 @@ namespace Ink_Canvas
|
||||
if (inkCanvas.GetSelectedStrokes().Count == inkCanvas.Strokes.Count)
|
||||
{
|
||||
// 使用集中化的工具模式切换方法
|
||||
SetCurrentToolMode(InkCanvasEditingMode.Ink, () => {
|
||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||
});
|
||||
SetCurrentToolMode(InkCanvasEditingMode.Ink,
|
||||
() => { SetCurrentToolMode(InkCanvasEditingMode.Select); });
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -304,11 +307,21 @@ namespace Ink_Canvas
|
||||
private void inkCanvas_SelectionChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (isProgramChangeStrokeSelection) return;
|
||||
|
||||
// 检查是否有图片元素被选中
|
||||
var selectedElements = inkCanvas.GetSelectedElements();
|
||||
bool hasImageElement = selectedElements.Any(element => element is System.Windows.Controls.Image);
|
||||
|
||||
// 如果有图片元素被选中,不显示选择框
|
||||
if (hasImageElement)
|
||||
{
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
|
||||
if (inkCanvas.GetSelectedStrokes().Count == 0)
|
||||
{
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||
// 当没有选中笔画时,检查是否有选中的UIElement
|
||||
CheckUIElementSelection();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -316,25 +329,10 @@ namespace Ink_Canvas
|
||||
BorderStrokeSelectionClone.Background = Brushes.Transparent;
|
||||
isStrokeSelectionCloneOn = false;
|
||||
updateBorderStrokeSelectionControlLocation();
|
||||
// 当选中笔画时,取消UIElement选择
|
||||
DeselectUIElement();
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckUIElementSelection()
|
||||
{
|
||||
// 检查InkCanvas中的UIElement是否被选中
|
||||
var selectedElements = inkCanvas.GetSelectedElements();
|
||||
if (selectedElements.Count > 0)
|
||||
{
|
||||
var element = selectedElements[0];
|
||||
SelectUIElement(element);
|
||||
}
|
||||
else
|
||||
{
|
||||
DeselectUIElement();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void updateBorderStrokeSelectionControlLocation()
|
||||
{
|
||||
@@ -366,8 +364,10 @@ namespace Ink_Canvas
|
||||
{
|
||||
StrokeInitialHistory[item.Key] = item.Value.Item2;
|
||||
}
|
||||
|
||||
StrokeManipulationHistory = null;
|
||||
}
|
||||
|
||||
if (DrawingAttributesHistory.Count > 0)
|
||||
{
|
||||
timeMachine.CommitStrokeDrawingAttributesHistory(DrawingAttributesHistory);
|
||||
@@ -419,18 +419,26 @@ namespace Ink_Canvas
|
||||
stroke.DrawingAttributes.Width *= md.Scale.X;
|
||||
stroke.DrawingAttributes.Height *= md.Scale.Y;
|
||||
}
|
||||
catch { }
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
updateBorderStrokeSelectionControlLocation();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void GridInkCanvasSelectionCover_TouchDown(object sender, TouchEventArgs e) { }
|
||||
private void GridInkCanvasSelectionCover_TouchDown(object sender, TouchEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
private void GridInkCanvasSelectionCover_TouchUp(object sender, TouchEventArgs e) { }
|
||||
private void GridInkCanvasSelectionCover_TouchUp(object sender, TouchEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
private Point lastTouchPointOnGridInkCanvasCover = new Point(0, 0);
|
||||
|
||||
@@ -514,225 +522,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
#region UIElement Selection and Resize
|
||||
|
||||
private UIElement selectedUIElement;
|
||||
private System.Windows.Controls.Canvas resizeHandlesCanvas;
|
||||
private readonly List<Rectangle> resizeHandles = new List<Rectangle>();
|
||||
private bool isResizing;
|
||||
private ResizeDirection currentResizeDirection = ResizeDirection.None;
|
||||
private Point resizeStartPoint;
|
||||
private Rect originalElementBounds;
|
||||
|
||||
// 图片工具栏相关
|
||||
private Border borderImageSelectionControl;
|
||||
private double BorderImageSelectionControlWidth = 490.0; // 6个按钮 + 分隔线的实际宽度
|
||||
private double BorderImageSelectionControlHeight = 80.0;
|
||||
|
||||
// 元素变化监听相关
|
||||
private DispatcherTimer elementUpdateTimer;
|
||||
private Rect lastElementBounds;
|
||||
|
||||
private enum ResizeDirection
|
||||
{
|
||||
None,
|
||||
TopLeft,
|
||||
TopCenter,
|
||||
TopRight,
|
||||
MiddleLeft,
|
||||
MiddleRight,
|
||||
BottomLeft,
|
||||
BottomCenter,
|
||||
BottomRight
|
||||
}
|
||||
|
||||
private void InitializeUIElementSelection()
|
||||
{
|
||||
// 创建拖拽手柄画布
|
||||
if (resizeHandlesCanvas == null)
|
||||
{
|
||||
resizeHandlesCanvas = new System.Windows.Controls.Canvas
|
||||
{
|
||||
Background = Brushes.Transparent,
|
||||
IsHitTestVisible = true,
|
||||
Visibility = Visibility.Collapsed
|
||||
};
|
||||
|
||||
// 将手柄画布添加到主网格中,确保它在InkCanvas之上
|
||||
var mainGrid = inkCanvas.Parent as Grid;
|
||||
if (mainGrid != null)
|
||||
{
|
||||
mainGrid.Children.Add(resizeHandlesCanvas);
|
||||
Panel.SetZIndex(resizeHandlesCanvas, 1000); // 确保在最上层
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化图片工具栏引用
|
||||
if (borderImageSelectionControl == null)
|
||||
{
|
||||
borderImageSelectionControl = FindName("BorderImageSelectionControl") as Border;
|
||||
}
|
||||
|
||||
// 创建8个拖拽手柄
|
||||
CreateResizeHandles();
|
||||
}
|
||||
|
||||
private void CreateResizeHandles()
|
||||
{
|
||||
resizeHandles.Clear();
|
||||
resizeHandlesCanvas.Children.Clear();
|
||||
|
||||
var directions = new[]
|
||||
{
|
||||
ResizeDirection.TopLeft, ResizeDirection.TopCenter, ResizeDirection.TopRight,
|
||||
ResizeDirection.MiddleLeft, ResizeDirection.MiddleRight,
|
||||
ResizeDirection.BottomLeft, ResizeDirection.BottomCenter, ResizeDirection.BottomRight
|
||||
};
|
||||
|
||||
foreach (var direction in directions)
|
||||
{
|
||||
var handle = new Rectangle
|
||||
{
|
||||
Width = 12,
|
||||
Height = 12,
|
||||
Fill = Brushes.White,
|
||||
Stroke = Brushes.DodgerBlue,
|
||||
StrokeThickness = 2,
|
||||
Cursor = GetCursorForDirection(direction),
|
||||
Tag = direction
|
||||
};
|
||||
|
||||
handle.MouseDown += ResizeHandle_MouseDown;
|
||||
handle.MouseMove += ResizeHandle_MouseMove;
|
||||
handle.MouseUp += ResizeHandle_MouseUp;
|
||||
|
||||
resizeHandles.Add(handle);
|
||||
resizeHandlesCanvas.Children.Add(handle);
|
||||
}
|
||||
}
|
||||
|
||||
private Cursor GetCursorForDirection(ResizeDirection direction)
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case ResizeDirection.TopLeft:
|
||||
case ResizeDirection.BottomRight:
|
||||
return Cursors.SizeNWSE;
|
||||
case ResizeDirection.TopRight:
|
||||
case ResizeDirection.BottomLeft:
|
||||
return Cursors.SizeNESW;
|
||||
case ResizeDirection.TopCenter:
|
||||
case ResizeDirection.BottomCenter:
|
||||
return Cursors.SizeNS;
|
||||
case ResizeDirection.MiddleLeft:
|
||||
case ResizeDirection.MiddleRight:
|
||||
return Cursors.SizeWE;
|
||||
default:
|
||||
return Cursors.Arrow;
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectUIElement(UIElement element)
|
||||
{
|
||||
if (selectedUIElement == element) return;
|
||||
|
||||
// 取消之前的选择
|
||||
DeselectUIElement();
|
||||
|
||||
// 清除笔画选择
|
||||
if (inkCanvas.GetSelectedStrokes().Count > 0)
|
||||
{
|
||||
isProgramChangeStrokeSelection = true;
|
||||
inkCanvas.Select(new StrokeCollection());
|
||||
isProgramChangeStrokeSelection = false;
|
||||
}
|
||||
|
||||
selectedUIElement = element;
|
||||
|
||||
if (element != null)
|
||||
{
|
||||
// 初始化选择系统(如果还没有初始化)
|
||||
if (resizeHandlesCanvas == null)
|
||||
{
|
||||
InitializeUIElementSelection();
|
||||
}
|
||||
|
||||
// 显示拖拽手柄(所有UI元素都需要)
|
||||
ShowResizeHandles();
|
||||
|
||||
// 根据元素类型显示特定的工具栏
|
||||
if (element is Image)
|
||||
{
|
||||
ShowImageToolbar();
|
||||
}
|
||||
|
||||
// 监听元素的布局变化,以便实时更新手柄位置
|
||||
StartMonitoringElementChanges(element);
|
||||
}
|
||||
}
|
||||
|
||||
private void DeselectUIElement()
|
||||
{
|
||||
// 停止监听之前选中元素的变化
|
||||
StopMonitoringElementChanges();
|
||||
|
||||
selectedUIElement = null;
|
||||
HideResizeHandles();
|
||||
HideImageToolbar();
|
||||
}
|
||||
|
||||
private void ShowResizeHandles()
|
||||
{
|
||||
if (selectedUIElement == null || resizeHandlesCanvas == null) return;
|
||||
|
||||
var bounds = GetUIElementBounds(selectedUIElement);
|
||||
UpdateResizeHandlesPosition(bounds);
|
||||
resizeHandlesCanvas.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void HideResizeHandles()
|
||||
{
|
||||
if (resizeHandlesCanvas != null)
|
||||
{
|
||||
resizeHandlesCanvas.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowImageToolbar()
|
||||
{
|
||||
if (selectedUIElement == null || borderImageSelectionControl == null) return;
|
||||
|
||||
var bounds = GetUIElementBounds(selectedUIElement);
|
||||
UpdateImageToolbarPosition(bounds);
|
||||
borderImageSelectionControl.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void HideImageToolbar()
|
||||
{
|
||||
if (borderImageSelectionControl != null)
|
||||
{
|
||||
borderImageSelectionControl.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateImageToolbarPosition(Rect bounds)
|
||||
{
|
||||
if (borderImageSelectionControl == null) return;
|
||||
|
||||
// 计算工具栏位置,类似于墨迹选择工具栏的逻辑
|
||||
var toolbarX = bounds.X + bounds.Width / 2 - BorderImageSelectionControlWidth / 2;
|
||||
var toolbarY = bounds.Y + bounds.Height + 10; // 在图片下方10像素处
|
||||
|
||||
// 确保工具栏不会超出画布边界
|
||||
if (toolbarX < 0) toolbarX = 0;
|
||||
if (toolbarX + BorderImageSelectionControlWidth > inkCanvas.ActualWidth)
|
||||
toolbarX = inkCanvas.ActualWidth - BorderImageSelectionControlWidth;
|
||||
|
||||
if (toolbarY + BorderImageSelectionControlHeight > inkCanvas.ActualHeight)
|
||||
toolbarY = bounds.Y - BorderImageSelectionControlHeight - 10; // 如果下方空间不够,显示在上方
|
||||
|
||||
borderImageSelectionControl.Margin = new Thickness(toolbarX, toolbarY, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
private Rect GetUIElementBounds(UIElement element)
|
||||
{
|
||||
if (element is FrameworkElement fe)
|
||||
@@ -772,270 +562,7 @@ namespace Ink_Canvas
|
||||
|
||||
return new Rect(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
private void UpdateResizeHandlesPosition(Rect bounds)
|
||||
{
|
||||
if (resizeHandles.Count != 8) return;
|
||||
|
||||
var handleSize = 12.0;
|
||||
var halfHandle = handleSize / 2;
|
||||
|
||||
// 计算手柄位置
|
||||
var positions = new[]
|
||||
{
|
||||
new Point(bounds.Left - halfHandle, bounds.Top - halfHandle), // TopLeft
|
||||
new Point(bounds.Left + bounds.Width / 2 - halfHandle, bounds.Top - halfHandle), // TopCenter
|
||||
new Point(bounds.Right - halfHandle, bounds.Top - halfHandle), // TopRight
|
||||
new Point(bounds.Left - halfHandle, bounds.Top + bounds.Height / 2 - halfHandle), // MiddleLeft
|
||||
new Point(bounds.Right - halfHandle, bounds.Top + bounds.Height / 2 - halfHandle), // MiddleRight
|
||||
new Point(bounds.Left - halfHandle, bounds.Bottom - halfHandle), // BottomLeft
|
||||
new Point(bounds.Left + bounds.Width / 2 - halfHandle, bounds.Bottom - halfHandle), // BottomCenter
|
||||
new Point(bounds.Right - halfHandle, bounds.Bottom - halfHandle) // BottomRight
|
||||
};
|
||||
|
||||
for (int i = 0; i < resizeHandles.Count && i < positions.Length; i++)
|
||||
{
|
||||
System.Windows.Controls.Canvas.SetLeft(resizeHandles[i], positions[i].X);
|
||||
System.Windows.Controls.Canvas.SetTop(resizeHandles[i], positions[i].Y);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResizeHandle_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (selectedUIElement == null) return;
|
||||
|
||||
var handle = sender as Rectangle;
|
||||
if (handle?.Tag is ResizeDirection direction)
|
||||
{
|
||||
isResizing = true;
|
||||
currentResizeDirection = direction;
|
||||
resizeStartPoint = e.GetPosition(inkCanvas);
|
||||
originalElementBounds = GetUIElementBounds(selectedUIElement);
|
||||
|
||||
handle.CaptureMouse();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResizeHandle_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!isResizing || selectedUIElement == null) return;
|
||||
|
||||
var currentPoint = e.GetPosition(inkCanvas);
|
||||
var deltaX = currentPoint.X - resizeStartPoint.X;
|
||||
var deltaY = currentPoint.Y - resizeStartPoint.Y;
|
||||
|
||||
ResizeUIElement(deltaX, deltaY);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ResizeHandle_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (isResizing)
|
||||
{
|
||||
isResizing = false;
|
||||
currentResizeDirection = ResizeDirection.None;
|
||||
|
||||
var handle = sender as Rectangle;
|
||||
handle?.ReleaseMouseCapture();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResizeUIElement(double deltaX, double deltaY)
|
||||
{
|
||||
if (selectedUIElement == null) return;
|
||||
|
||||
var newBounds = originalElementBounds;
|
||||
const double minSize = 20.0;
|
||||
|
||||
switch (currentResizeDirection)
|
||||
{
|
||||
case ResizeDirection.TopLeft:
|
||||
var newWidth = originalElementBounds.Width - deltaX;
|
||||
var newHeight = originalElementBounds.Height - deltaY;
|
||||
if (newWidth >= minSize && newHeight >= minSize)
|
||||
{
|
||||
newBounds.X = originalElementBounds.X + deltaX;
|
||||
newBounds.Y = originalElementBounds.Y + deltaY;
|
||||
newBounds.Width = newWidth;
|
||||
newBounds.Height = newHeight;
|
||||
}
|
||||
break;
|
||||
|
||||
case ResizeDirection.TopCenter:
|
||||
var newHeightTC = originalElementBounds.Height - deltaY;
|
||||
if (newHeightTC >= minSize)
|
||||
{
|
||||
newBounds.Y = originalElementBounds.Y + deltaY;
|
||||
newBounds.Height = newHeightTC;
|
||||
}
|
||||
break;
|
||||
|
||||
case ResizeDirection.TopRight:
|
||||
var newWidthTR = originalElementBounds.Width + deltaX;
|
||||
var newHeightTR = originalElementBounds.Height - deltaY;
|
||||
if (newWidthTR >= minSize && newHeightTR >= minSize)
|
||||
{
|
||||
newBounds.Y = originalElementBounds.Y + deltaY;
|
||||
newBounds.Width = newWidthTR;
|
||||
newBounds.Height = newHeightTR;
|
||||
}
|
||||
break;
|
||||
|
||||
case ResizeDirection.MiddleLeft:
|
||||
var newWidthML = originalElementBounds.Width - deltaX;
|
||||
if (newWidthML >= minSize)
|
||||
{
|
||||
newBounds.X = originalElementBounds.X + deltaX;
|
||||
newBounds.Width = newWidthML;
|
||||
}
|
||||
break;
|
||||
|
||||
case ResizeDirection.MiddleRight:
|
||||
var newWidthMR = originalElementBounds.Width + deltaX;
|
||||
if (newWidthMR >= minSize)
|
||||
{
|
||||
newBounds.Width = newWidthMR;
|
||||
}
|
||||
break;
|
||||
|
||||
case ResizeDirection.BottomLeft:
|
||||
var newWidthBL = originalElementBounds.Width - deltaX;
|
||||
var newHeightBL = originalElementBounds.Height + deltaY;
|
||||
if (newWidthBL >= minSize && newHeightBL >= minSize)
|
||||
{
|
||||
newBounds.X = originalElementBounds.X + deltaX;
|
||||
newBounds.Width = newWidthBL;
|
||||
newBounds.Height = newHeightBL;
|
||||
}
|
||||
break;
|
||||
|
||||
case ResizeDirection.BottomCenter:
|
||||
var newHeightBC = originalElementBounds.Height + deltaY;
|
||||
if (newHeightBC >= minSize)
|
||||
{
|
||||
newBounds.Height = newHeightBC;
|
||||
}
|
||||
break;
|
||||
|
||||
case ResizeDirection.BottomRight:
|
||||
var newWidthBR = originalElementBounds.Width + deltaX;
|
||||
var newHeightBR = originalElementBounds.Height + deltaY;
|
||||
if (newWidthBR >= minSize && newHeightBR >= minSize)
|
||||
{
|
||||
newBounds.Width = newWidthBR;
|
||||
newBounds.Height = newHeightBR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// 应用新的尺寸和位置
|
||||
ApplyUIElementBounds(selectedUIElement, newBounds);
|
||||
|
||||
// 更新手柄位置
|
||||
UpdateResizeHandlesPosition(newBounds);
|
||||
|
||||
// 如果是图片,也更新工具栏位置
|
||||
if (selectedUIElement is Image)
|
||||
{
|
||||
UpdateImageToolbarPosition(newBounds);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyUIElementBounds(UIElement element, Rect bounds)
|
||||
{
|
||||
if (element is FrameworkElement fe)
|
||||
{
|
||||
// 清除RenderTransform,避免与直接设置Width/Height冲突
|
||||
fe.RenderTransform = Transform.Identity;
|
||||
|
||||
// 直接设置位置和大小
|
||||
InkCanvas.SetLeft(element, bounds.X);
|
||||
InkCanvas.SetTop(element, bounds.Y);
|
||||
fe.Width = bounds.Width;
|
||||
fe.Height = bounds.Height;
|
||||
}
|
||||
}
|
||||
|
||||
private void UIElement_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.Select)
|
||||
{
|
||||
var element = sender as UIElement;
|
||||
if (element != null)
|
||||
{
|
||||
// 切换到选择模式并选择这个元素
|
||||
inkCanvas.Select(new[] { element });
|
||||
SelectUIElement(element);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void StartMonitoringElementChanges(UIElement element)
|
||||
{
|
||||
// 停止之前的监听
|
||||
StopMonitoringElementChanges();
|
||||
|
||||
if (element == null) return;
|
||||
|
||||
// 记录初始边界
|
||||
lastElementBounds = GetUIElementBounds(element);
|
||||
|
||||
// 创建定时器,定期检查元素边界变化
|
||||
elementUpdateTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromMilliseconds(16) // 约60FPS的更新频率
|
||||
};
|
||||
|
||||
elementUpdateTimer.Tick += (sender, e) =>
|
||||
{
|
||||
if (selectedUIElement == null)
|
||||
{
|
||||
StopMonitoringElementChanges();
|
||||
return;
|
||||
}
|
||||
|
||||
var currentBounds = GetUIElementBounds(selectedUIElement);
|
||||
|
||||
// 检查边界是否发生变化
|
||||
if (!AreRectsEqual(lastElementBounds, currentBounds))
|
||||
{
|
||||
lastElementBounds = currentBounds;
|
||||
|
||||
// 更新手柄位置
|
||||
UpdateResizeHandlesPosition(currentBounds);
|
||||
|
||||
// 如果是图片,也更新工具栏位置
|
||||
if (selectedUIElement is Image)
|
||||
{
|
||||
UpdateImageToolbarPosition(currentBounds);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
elementUpdateTimer.Start();
|
||||
}
|
||||
|
||||
private void StopMonitoringElementChanges()
|
||||
{
|
||||
if (elementUpdateTimer != null)
|
||||
{
|
||||
elementUpdateTimer.Stop();
|
||||
elementUpdateTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool AreRectsEqual(Rect rect1, Rect rect2)
|
||||
{
|
||||
const double tolerance = 0.1; // 允许的误差范围
|
||||
return Math.Abs(rect1.X - rect2.X) < tolerance &&
|
||||
Math.Abs(rect1.Y - rect2.Y) < tolerance &&
|
||||
Math.Abs(rect1.Width - rect2.Width) < tolerance &&
|
||||
Math.Abs(rect1.Height - rect2.Height) < tolerance;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -2675,6 +2675,41 @@ namespace Ink_Canvas
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 在按钮可见性更新后,重新计算当前高光位置
|
||||
// 延迟执行以确保UI更新完成
|
||||
Dispatcher.BeginInvoke(new Action(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 等待UI完全更新
|
||||
await Task.Delay(100);
|
||||
|
||||
// 获取当前选中的模式并重新设置高光位置
|
||||
string currentMode = GetCurrentSelectedMode();
|
||||
if (!string.IsNullOrEmpty(currentMode))
|
||||
{
|
||||
SetFloatingBarHighlightPosition(currentMode);
|
||||
}
|
||||
|
||||
// 重新计算浮动栏位置,因为按钮可见性变化会影响浮动栏宽度
|
||||
if (!isFloatingBarFolded)
|
||||
{
|
||||
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
|
||||
{
|
||||
ViewboxFloatingBarMarginAnimation(60);
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewboxFloatingBarMarginAnimation(100, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"重新计算高光位置和浮动栏位置失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}), System.Windows.Threading.DispatcherPriority.Loaded);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -190,21 +190,33 @@ namespace Ink_Canvas
|
||||
{
|
||||
if (item.InsertedElement is Image img)
|
||||
{
|
||||
img.MouseDown -= UIElement_MouseDown;
|
||||
img.MouseDown += UIElement_MouseDown;
|
||||
img.IsManipulationEnabled = true;
|
||||
|
||||
// 重新应用CenterAndScaleElement变换
|
||||
CenterAndScaleElement(img);
|
||||
// 检查图片是否有位置信息,如果没有则应用居中
|
||||
double left = InkCanvas.GetLeft(img);
|
||||
double top = InkCanvas.GetTop(img);
|
||||
|
||||
if (double.IsNaN(left) || double.IsNaN(top))
|
||||
{
|
||||
// 图片没有位置信息,应用居中
|
||||
CenterAndScaleElement(img);
|
||||
}
|
||||
|
||||
// 重新绑定事件处理器
|
||||
BindElementEvents(img);
|
||||
}
|
||||
else if (item.InsertedElement is MediaElement media)
|
||||
{
|
||||
media.MouseDown -= UIElement_MouseDown;
|
||||
media.MouseDown += UIElement_MouseDown;
|
||||
media.IsManipulationEnabled = true;
|
||||
|
||||
// 重新应用CenterAndScaleElement变换
|
||||
CenterAndScaleElement(media);
|
||||
// 检查媒体元素是否有位置信息,如果没有则应用居中
|
||||
double left = InkCanvas.GetLeft(media);
|
||||
double top = InkCanvas.GetTop(media);
|
||||
|
||||
if (double.IsNaN(left) || double.IsNaN(top))
|
||||
{
|
||||
// 媒体元素没有位置信息,应用居中
|
||||
CenterAndScaleElement(media);
|
||||
}
|
||||
|
||||
// 重新绑定事件处理器
|
||||
BindElementEvents(media);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
@@ -49,5 +49,5 @@ using System.Windows;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.7.8.3")]
|
||||
[assembly: AssemblyFileVersion("1.7.8.3")]
|
||||
[assembly: AssemblyVersion("1.7.8.4")]
|
||||
[assembly: AssemblyFileVersion("1.7.8.4")]
|
||||
|
||||
@@ -101,8 +101,8 @@
|
||||
DefaultKey="B"
|
||||
DefaultModifiers="Alt"/>
|
||||
<local:HotkeyItem x:Name="QuitDrawToolHotkey"
|
||||
Title="退出绘图"
|
||||
Description="退出绘图模式"
|
||||
Title="退出绘图/白板"
|
||||
Description="退出绘图模式或白板模式"
|
||||
DefaultKey="Q"
|
||||
DefaultModifiers="Alt"/>
|
||||
</ui:SimpleStackPanel>
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Ink_Canvas.Windows
|
||||
InitializeHotkeyItems();
|
||||
|
||||
// 延迟加载快捷键,确保快捷键管理器已完全初始化
|
||||
this.Loaded += (s, e) =>
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -48,7 +48,7 @@ namespace Ink_Canvas.Windows
|
||||
};
|
||||
|
||||
// 注册窗口关闭事件
|
||||
this.Closed += HotkeySettingsWindow_Closed;
|
||||
Closed += HotkeySettingsWindow_Closed;
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -224,7 +224,7 @@ namespace Ink_Canvas.Windows
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
// 设置默认快捷键时出错,忽略
|
||||
}
|
||||
@@ -440,7 +440,7 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
if (settingsBorder != null)
|
||||
{
|
||||
settingsBorder.Visibility = System.Windows.Visibility.Collapsed;
|
||||
settingsBorder.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
// 隐藏设置蒙版
|
||||
@@ -449,7 +449,7 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
if (settingsMask != null)
|
||||
{
|
||||
settingsMask.Visibility = System.Windows.Visibility.Collapsed;
|
||||
settingsMask.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -471,7 +471,7 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
if (settingsBorder != null)
|
||||
{
|
||||
settingsBorder.Visibility = System.Windows.Visibility.Visible;
|
||||
settingsBorder.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
// 显示设置蒙版
|
||||
@@ -480,7 +480,7 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
if (settingsMask != null)
|
||||
{
|
||||
settingsMask.Visibility = System.Windows.Visibility.Visible;
|
||||
settingsMask.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -16,13 +16,13 @@ namespace Ink_Canvas
|
||||
{
|
||||
private bool _isSelecting = false;
|
||||
private bool _isFreehandMode = false;
|
||||
private System.Windows.Point _startPoint;
|
||||
private System.Windows.Point _currentPoint;
|
||||
private List<System.Windows.Point> _freehandPoints;
|
||||
private Point _startPoint;
|
||||
private Point _currentPoint;
|
||||
private List<Point> _freehandPoints;
|
||||
private Polyline _freehandPolyline;
|
||||
|
||||
public DrawingRectangle? SelectedArea { get; private set; }
|
||||
public List<System.Windows.Point> SelectedPath { get; private set; }
|
||||
public List<Point> SelectedPath { get; private set; }
|
||||
|
||||
public ScreenshotSelectorWindow()
|
||||
{
|
||||
@@ -47,7 +47,7 @@ namespace Ink_Canvas
|
||||
|
||||
private void InitializeFreehandMode()
|
||||
{
|
||||
_freehandPoints = new List<System.Windows.Point>();
|
||||
_freehandPoints = new List<Point>();
|
||||
_freehandPolyline = new Polyline
|
||||
{
|
||||
Stroke = Brushes.Red,
|
||||
@@ -65,10 +65,10 @@ namespace Ink_Canvas
|
||||
// 转换为WPF坐标系统
|
||||
var dpiScale = GetDpiScale();
|
||||
|
||||
this.Left = virtualScreen.Left / dpiScale;
|
||||
this.Top = virtualScreen.Top / dpiScale;
|
||||
this.Width = virtualScreen.Width / dpiScale;
|
||||
this.Height = virtualScreen.Height / dpiScale;
|
||||
Left = virtualScreen.Left / dpiScale;
|
||||
Top = virtualScreen.Top / dpiScale;
|
||||
Width = virtualScreen.Width / dpiScale;
|
||||
Height = virtualScreen.Height / dpiScale;
|
||||
}
|
||||
|
||||
private double GetDpiScale()
|
||||
@@ -186,7 +186,7 @@ namespace Ink_Canvas
|
||||
if (_freehandPoints.Count > 3) // 至少需要3个点形成有效路径
|
||||
{
|
||||
// 创建路径的副本,避免修改原始列表
|
||||
var pathPoints = new List<System.Windows.Point>(_freehandPoints);
|
||||
var pathPoints = new List<Point>(_freehandPoints);
|
||||
|
||||
// 确保路径闭合(如果最后一个点不是起始点,则添加起始点)
|
||||
if (pathPoints.Count > 0 &&
|
||||
@@ -284,7 +284,7 @@ namespace Ink_Canvas
|
||||
return new Rect(x, y, width, height);
|
||||
}
|
||||
|
||||
private Rect CalculatePathBounds(List<System.Windows.Point> points)
|
||||
private Rect CalculatePathBounds(List<Point> points)
|
||||
{
|
||||
if (points == null || points.Count == 0)
|
||||
return new Rect();
|
||||
@@ -306,12 +306,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
// 优化路径:移除重复点和过于接近的点,提高路径质量
|
||||
private List<System.Windows.Point> OptimizePath(List<System.Windows.Point> originalPath)
|
||||
private List<Point> OptimizePath(List<Point> originalPath)
|
||||
{
|
||||
if (originalPath == null || originalPath.Count < 3)
|
||||
return originalPath;
|
||||
|
||||
var optimizedPath = new List<System.Windows.Point>();
|
||||
var optimizedPath = new List<Point>();
|
||||
const double minDistance = 2.0; // 最小距离阈值
|
||||
|
||||
// 添加第一个点
|
||||
|
||||
Reference in New Issue
Block a user