diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs index 25f5f817..8bcce6b3 100644 --- a/Ink Canvas/App.xaml.cs +++ b/Ink Canvas/App.xaml.cs @@ -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; // 检查是否以更新模式启动 diff --git a/Ink Canvas/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs index 68f1de06..36053414 100644 --- a/Ink Canvas/AssemblyInfo.cs +++ b/Ink Canvas/AssemblyInfo.cs @@ -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")] diff --git a/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs b/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs index 56a2e6f3..ab7aa9fd 100644 --- a/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs +++ b/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs @@ -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 { /// - /// 异步硬件加速的墨迹平滑处理器 + /// 改进的异步硬件加速墨迹平滑处理器,使用优化的三次贝塞尔曲线拟合 /// public class AsyncAdvancedBezierSmoothing { @@ -26,11 +27,13 @@ namespace Ink_Canvas.Helpers _processingTasks = new ConcurrentDictionary(); } - 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; // 曲线张力参数 /// /// 异步平滑笔画 @@ -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 } /// - /// 轻度平滑处理,避免点数爆炸 + /// 改进的贝塞尔曲线平滑处理 /// - 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(); - 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()); + } + + /// + /// 计算改进的控制点 + /// + 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); + } + + /// + /// 自适应插值步数计算 + /// + 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)); + } + + /// + /// 计算曲率(简化版本) + /// + 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 + } + + /// + /// 去除重复和过近的点 + /// + private StylusPoint[] RemoveDuplicatePoints(StylusPoint[] points) + { + if (points.Length < 2) return points; + + var result = new List(); + 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(); } + + /// + /// 使用控制点的三次贝塞尔曲线计算 + /// + 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); + } + /// /// 硬件加速的向量化指数平滑 /// diff --git a/Ink Canvas/Helpers/AutoUpdateHelper.cs b/Ink Canvas/Helpers/AutoUpdateHelper.cs index e24c1df3..fe21d61c 100644 --- a/Ink Canvas/Helpers/AutoUpdateHelper.cs +++ b/Ink Canvas/Helpers/AutoUpdateHelper.cs @@ -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) diff --git a/Ink Canvas/Helpers/DeviceIdentifier.cs b/Ink Canvas/Helpers/DeviceIdentifier.cs index 79140029..04736514 100644 --- a/Ink Canvas/Helpers/DeviceIdentifier.cs +++ b/Ink Canvas/Helpers/DeviceIdentifier.cs @@ -1,4 +1,3 @@ -using Microsoft.Win32; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -16,26 +15,10 @@ namespace Ink_Canvas.Helpers /// internal static class DeviceIdentifier { - // 多重备份路径策略 + // 文件路径策略 private static readonly string DeviceIdFilePath = Path.Combine(App.RootPath, "device_id.dat"); - private static readonly string UsageStatsFilePath = Path.Combine(App.RootPath, "usage_stats.json"); - - // 使用频率数据的多重隐藏备份路径 - private static readonly string BackupDeviceIdPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "ICC", ".sys", "device.dat"); - private static readonly string BackupUsageStatsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "ICC", ".sys", "usage.dat"); - - // 使用频率数据的额外隐藏备份位置 - private static readonly string SecondaryUsageBackupPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - "Microsoft", "Windows", ".icc", "usage_backup.tmp"); - private static readonly string TertiaryUsageBackupPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), - "ICC", ".cache", "usage_cache.dat"); - private static readonly string QuaternaryUsageBackupPath = Path.Combine(Path.GetTempPath(), - ".icc_temp", "usage_temp.dat"); - - // 数据完整性验证密钥 - private static readonly string DataIntegrityKey = "ICC_DEVICE_INTEGRITY_2024"; + private static readonly string UsageStatsFilePath = Path.Combine(App.RootPath, "usage_stats.enc"); + private static readonly string UsageStatsBackupPath = Path.Combine(App.RootPath, "saves", "usage_stats_backup.enc"); private static readonly string DeviceId; private static readonly object fileLock = new object(); @@ -44,16 +27,6 @@ namespace Ink_Canvas.Helpers { // 在静态构造函数中初始化设备ID DeviceId = GetOrCreateDeviceId(); - - // 执行数据完整性检查和自动修复 - try - { - PerformDataIntegrityCheck(); - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 初始化时数据完整性检查失败: {ex.Message}", LogHelper.LogType.Error); - } } /// @@ -66,7 +39,7 @@ namespace Ink_Canvas.Helpers } /// - /// 获取或创建设备ID(内部方法)- 支持多重备份恢复 + /// 获取或创建设备ID /// private static string GetOrCreateDeviceId() { @@ -79,35 +52,15 @@ namespace Ink_Canvas.Helpers if (!string.IsNullOrEmpty(deviceId)) { LogHelper.WriteLogToFile($"DeviceIdentifier | 从主文件读取设备ID: {deviceId}"); - // 确保备份同步 - SaveDeviceIdToAllLocations(deviceId); return deviceId; } - // 2. 尝试从备份文件恢复 - deviceId = LoadDeviceIdFromFile(BackupDeviceIdPath); - if (!string.IsNullOrEmpty(deviceId)) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 从备份文件恢复设备ID: {deviceId}"); - SaveDeviceIdToAllLocations(deviceId); - return deviceId; - } - - // 3. 尝试从注册表恢复 - deviceId = LoadDeviceIdFromRegistry(); - if (!string.IsNullOrEmpty(deviceId)) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 从注册表恢复设备ID: {deviceId}"); - SaveDeviceIdToAllLocations(deviceId); - return deviceId; - } - - // 4. 生成新的设备ID + // 2. 生成新的设备ID string newDeviceId = GenerateDeviceId(); LogHelper.WriteLogToFile($"DeviceIdentifier | 生成新设备ID: {newDeviceId}"); - // 5. 保存到所有位置 - SaveDeviceIdToAllLocations(newDeviceId); + // 3. 保存到主文件 + SaveDeviceIdToFile(DeviceIdFilePath, newDeviceId); return newDeviceId; } @@ -350,47 +303,6 @@ namespace Ink_Canvas.Helpers return null; } - /// - /// 从注册表加载设备ID - /// - private static string LoadDeviceIdFromRegistry() - { - try - { - using (var key = Registry.CurrentUser.OpenSubKey(@"Software\ICC\DeviceInfo")) - { - if (key != null) - { - var value = key.GetValue("DeviceId") as string; - if (IsValidDeviceId(value)) - { - return value; - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 从注册表加载设备ID失败: {ex.Message}", LogHelper.LogType.Error); - } - return null; - } - - /// - /// 保存设备ID到所有位置 - /// - private static void SaveDeviceIdToAllLocations(string deviceId) - { - // 保存到主文件 - SaveDeviceIdToFile(DeviceIdFilePath, deviceId); - - // 保存到备份文件 - SaveDeviceIdToFile(BackupDeviceIdPath, deviceId); - - // 保存到注册表 - SaveDeviceIdToRegistry(deviceId); - } - /// /// 保存设备ID到文件 /// @@ -403,50 +315,19 @@ namespace Ink_Canvas.Helpers if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); - // 设置隐藏属性 - if (filePath.Contains(".sys")) - { - var dirInfo = new DirectoryInfo(directory); - dirInfo.Attributes |= FileAttributes.Hidden | FileAttributes.System; - } } File.WriteAllText(filePath, deviceId); - // 设置文件属性为隐藏和系统文件 - if (filePath.Contains(".sys")) - { - var fileInfo = new FileInfo(filePath); - fileInfo.Attributes |= FileAttributes.Hidden | FileAttributes.System; - } - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 设备ID已保存到: {filePath}"); + LogHelper.WriteLogToFile($"DeviceIdentifier | 设备ID已保存到: {filePath}"); } catch (Exception ex) { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 保存设备ID到文件失败 ({filePath}): {ex.Message}", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"DeviceIdentifier | 保存设备ID到文件失败 ({filePath}): {ex.Message}", LogHelper.LogType.Error); } } - /// - /// 保存设备ID到注册表 - /// - private static void SaveDeviceIdToRegistry(string deviceId) - { - try - { - using (var key = Registry.CurrentUser.CreateSubKey(@"Software\ICC\DeviceInfo")) - { - key?.SetValue("DeviceId", deviceId); - key?.SetValue("LastUpdate", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); - } - // LogHelper.WriteLogToFile("DeviceIdentifier | 设备ID已保存到注册表"); - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 保存设备ID到注册表失败: {ex.Message}", LogHelper.LogType.Error); - } - } + /// /// 使用频率统计数据结构(优化至秒级精度) @@ -487,9 +368,6 @@ namespace Ink_Canvas.Helpers [JsonProperty("usageFrequency")] public UsageFrequency UsageFrequency { get; set; } - [JsonProperty("dataHash")] - public string DataHash { get; set; } - [JsonProperty("lastModified")] public DateTime LastModified { get; set; } @@ -526,28 +404,28 @@ namespace Ink_Canvas.Helpers try { // 如果新字段为空但旧字段有数据,进行迁移 - if (TotalUsageSeconds == 0 && TotalUsageMinutes > 0) + if (TotalUsageSeconds == 0 && TotalUsageSeconds > 0) { - TotalUsageSeconds = TotalUsageMinutes * 60; - LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移总使用时长: {TotalUsageMinutes}分钟 -> {TotalUsageSeconds}秒"); + TotalUsageSeconds = TotalUsageSeconds * 60; + LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移总使用时长: {TotalUsageSeconds}分钟 -> {TotalUsageSeconds}秒"); } - if (AverageSessionSeconds == 0 && AverageSessionMinutes > 0) + if (AverageSessionSeconds == 0 && AverageSessionSeconds > 0) { - AverageSessionSeconds = AverageSessionMinutes * 60; - LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移平均会话时长: {AverageSessionMinutes}分钟 -> {AverageSessionSeconds}秒"); + AverageSessionSeconds = AverageSessionSeconds * 60; + LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移平均会话时长: {AverageSessionSeconds}分钟 -> {AverageSessionSeconds}秒"); } - if (WeeklyUsageSeconds == 0 && WeeklyUsageMinutes > 0) + if (WeeklyUsageSeconds == 0 && WeeklyUsageSeconds > 0) { - WeeklyUsageSeconds = WeeklyUsageMinutes * 60; - LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移每周使用时长: {WeeklyUsageMinutes}分钟 -> {WeeklyUsageSeconds}秒"); + WeeklyUsageSeconds = WeeklyUsageSeconds * 60; + LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移每周使用时长: {WeeklyUsageSeconds}分钟 -> {WeeklyUsageSeconds}秒"); } - if (LastWeekUsageSeconds == 0 && LastWeekUsageMinutes > 0) + if (LastWeekUsageSeconds == 0 && LastWeekUsageSeconds > 0) { - LastWeekUsageSeconds = LastWeekUsageMinutes * 60; - LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移上周使用时长: {LastWeekUsageMinutes}分钟 -> {LastWeekUsageSeconds}秒"); + LastWeekUsageSeconds = LastWeekUsageSeconds * 60; + LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移上周使用时长: {LastWeekUsageSeconds}分钟 -> {LastWeekUsageSeconds}秒"); } } catch (Exception ex) @@ -572,12 +450,12 @@ namespace Ink_Canvas.Helpers LastWeekUsageSeconds = WeeklyUsageSeconds; // 同时更新旧字段以保持兼容性 - LastWeekUsageMinutes = LastWeekUsageSeconds / 60; + LastWeekUsageSeconds = LastWeekUsageSeconds / 60; // 重置本周数据 WeeklyLaunchCount = 0; WeeklyUsageSeconds = 0; - WeeklyUsageMinutes = 0; + WeeklyUsageSeconds = 0; WeekStartDate = currentWeekStart; LogHelper.WriteLogToFile($"DeviceIdentifier | 每周统计重置 - 上周启动: {LastWeekLaunchCount}次, 上周使用: {FormatDuration(LastWeekUsageSeconds)}"); @@ -611,58 +489,7 @@ namespace Ink_Canvas.Helpers CheckAndResetWeeklyStats(); WeeklyUsageSeconds += seconds; // 同时更新旧字段以保持兼容性 - WeeklyUsageMinutes = WeeklyUsageSeconds / 60; - } - - - - /// - /// 计算数据哈希值用于完整性验证(秒级精度) - /// - public void UpdateDataHash() - { - // 使用秒级精度数据计算哈希 - var dataString = $"{DeviceId}|{LaunchCount}|{TotalUsageSeconds}|{LastLaunchTime:yyyyMMddHHmmss}|{WeeklyLaunchCount}|{WeeklyUsageSeconds}|{DataIntegrityKey}"; - using (var sha256 = SHA256.Create()) - { - var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(dataString)); - DataHash = Convert.ToBase64String(hashBytes); - } - LastModified = DateTime.Now; - } - - /// - /// 验证数据完整性(秒级精度) - /// - public bool VerifyDataIntegrity() - { - try - { - // 首先尝试使用秒级精度验证 - var dataString = $"{DeviceId}|{LaunchCount}|{TotalUsageSeconds}|{LastLaunchTime:yyyyMMddHHmmss}|{WeeklyLaunchCount}|{WeeklyUsageSeconds}|{DataIntegrityKey}"; - using (var sha256 = SHA256.Create()) - { - var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(dataString)); - var expectedHash = Convert.ToBase64String(hashBytes); - if (DataHash == expectedHash) - { - return true; - } - } - - // 如果秒级精度验证失败,尝试使用旧的分钟精度验证(向后兼容) - var oldDataString = $"{DeviceId}|{LaunchCount}|{TotalUsageMinutes}|{LastLaunchTime:yyyyMMddHHmmss}|{WeeklyLaunchCount}|{WeeklyUsageMinutes}|{DataIntegrityKey}"; - using (var sha256 = SHA256.Create()) - { - var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(oldDataString)); - var expectedHash = Convert.ToBase64String(hashBytes); - return DataHash == expectedHash; - } - } - catch - { - return false; - } + WeeklyUsageSeconds = WeeklyUsageSeconds / 60; } } @@ -738,9 +565,6 @@ namespace Ink_Canvas.Helpers // 计算使用频率 CalculateUsageFrequency(stats); - // 更新数据完整性哈希 - stats.UpdateDataHash(); - SaveUsageStats(stats); LogHelper.WriteLogToFile($"DeviceIdentifier | 记录应用启动 - 设备ID: {DeviceId}, 总启动: {stats.LaunchCount}次, 本周启动: {stats.WeeklyLaunchCount}次"); @@ -777,7 +601,7 @@ namespace Ink_Canvas.Helpers stats.TotalUsageSeconds += sessionSeconds; // 同时更新旧字段以保持兼容性 - stats.TotalUsageMinutes = stats.TotalUsageSeconds / 60; + stats.TotalUsageSeconds = stats.TotalUsageSeconds / 60; // 记录每周使用时长(秒级精度) stats.RecordWeeklyUsage(sessionSeconds); @@ -787,16 +611,13 @@ namespace Ink_Canvas.Helpers { stats.AverageSessionSeconds = (double)stats.TotalUsageSeconds / stats.LaunchCount; // 同时更新旧字段以保持兼容性 - stats.AverageSessionMinutes = stats.AverageSessionSeconds / 60; + stats.AverageSessionSeconds = stats.AverageSessionSeconds / 60; } } // 重新计算使用频率 CalculateUsageFrequency(stats); - // 更新数据完整性哈希 - stats.UpdateDataHash(); - SaveUsageStats(stats); LogHelper.WriteLogToFile($"DeviceIdentifier | 记录应用退出 - 本次会话: {FormatDuration(sessionSeconds)}, " + @@ -829,9 +650,9 @@ namespace Ink_Canvas.Helpers var currentWeekSeconds = stats.WeeklyUsageSeconds; // 如果秒级数据为空但分钟数据存在,进行转换 - if (currentWeekSeconds == 0 && stats.WeeklyUsageMinutes > 0) + if (currentWeekSeconds == 0 && stats.WeeklyUsageSeconds > 0) { - currentWeekSeconds = stats.WeeklyUsageMinutes * 60; + currentWeekSeconds = stats.WeeklyUsageSeconds * 60; } // 如果本周数据不足,参考上周数据 @@ -839,9 +660,9 @@ namespace Ink_Canvas.Helpers var weeklySeconds = currentWeekSeconds > 0 ? currentWeekSeconds : stats.LastWeekUsageSeconds; // 如果秒级数据仍为空,使用分钟数据转换 - if (weeklySeconds == 0 && stats.LastWeekUsageMinutes > 0) + if (weeklySeconds == 0 && stats.LastWeekUsageSeconds > 0) { - weeklySeconds = stats.LastWeekUsageMinutes * 60; + weeklySeconds = stats.LastWeekUsageSeconds * 60; } // 综合评分系统(0-100分) @@ -910,7 +731,7 @@ namespace Ink_Canvas.Helpers else if (weeklySeconds >= 3600) score += 5; // 1-2小时:轻度使用 // 历史使用深度评分(10分)- 反映用户的长期使用习惯(秒级精度) - var totalSeconds = stats.TotalUsageSeconds > 0 ? stats.TotalUsageSeconds : stats.TotalUsageMinutes * 60; + var totalSeconds = stats.TotalUsageSeconds > 0 ? stats.TotalUsageSeconds : stats.TotalUsageSeconds * 60; if (totalSeconds >= 180000) score += 10; // 50小时以上:资深用户 else if (totalSeconds >= 72000) score += 7; // 20-50小时:中等用户 else if (totalSeconds >= 18000) score += 4; // 5-20小时:新手用户 @@ -992,97 +813,93 @@ namespace Ink_Canvas.Helpers } /// - /// 加载使用统计 - 支持多重备份恢复和智能反篡改 + /// 加载使用统计 /// private static UsageStats LoadUsageStats() { try { - // 智能恢复:收集所有可用的数据源,选择最可信的 - var allDataSources = CollectAllUsageDataSources(); - - // 如果找到有效数据,返回最可信的 - if (allDataSources.Count > 0) + // 1. 尝试从主文件加载 + var stats = LoadUsageStatsFromFile(UsageStatsFilePath); + if (stats != null) { - var bestData = SelectMostTrustedData(allDataSources); - if (bestData != null) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 使用最可信数据源恢复使用统计: {bestData.Source}"); - - // 执行数据迁移(如果需要) - bestData.Stats.MigrateToSecondsPrecision(); - - // 确保备份同步 - SaveUsageStatsToAllLocations(bestData.Stats); - return bestData.Stats; - } - } - - // LogHelper.WriteLogToFile("DeviceIdentifier | 所有数据源都不可用,检查是否有部分可恢复数据", LogHelper.LogType.Warning); - - // 如果没有完全可信的数据,尝试从部分损坏的数据中恢复 - var partiallyRecoveredData = AttemptPartialDataRecovery(allDataSources); - if (partiallyRecoveredData != null) - { - // LogHelper.WriteLogToFile("DeviceIdentifier | 从部分损坏数据中恢复使用统计"); - // 执行数据迁移(如果需要) - partiallyRecoveredData.MigrateToSecondsPrecision(); - - SaveUsageStatsToAllLocations(partiallyRecoveredData); - return partiallyRecoveredData; + stats.MigrateToSecondsPrecision(); + return stats; } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 加载使用统计失败: {ex.Message}", LogHelper.LogType.Error); - } - // 返回新的统计对象(秒级精度) + // 2. 尝试从备份文件加载 + stats = LoadUsageStatsFromFile(UsageStatsBackupPath); + if (stats != null) + { + LogHelper.WriteLogToFile("DeviceIdentifier | 从备份文件恢复使用统计"); + stats.MigrateToSecondsPrecision(); + // 尝试恢复主文件 + try + { + SaveUsageStatsToFile(UsageStatsFilePath, stats); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 恢复主文件失败: {ex.Message}", LogHelper.LogType.Warning); + } + return stats; + } + + + + // 如果所有文件都不存在或损坏,返回新的统计对象 var newStats = new UsageStats { DeviceId = DeviceId, LastLaunchTime = DateTime.Now, LaunchCount = 0, - TotalUsageSeconds = 0, - AverageSessionSeconds = 0, - TotalUsageMinutes = 0, // 保持兼容性 - AverageSessionMinutes = 0, // 保持兼容性 + TotalUsageSeconds = 0, // 保持兼容性 + AverageSessionSeconds = 0, // 保持兼容性 LastUpdateCheck = DateTime.MinValue, UpdatePriority = UpdatePriority.Medium, UsageFrequency = UsageFrequency.Medium }; - // 更新数据完整性哈希 - newStats.UpdateDataHash(); - - // 保存新统计到所有位置 - SaveUsageStatsToAllLocations(newStats); + // 保存新统计到文件 + SaveUsageStatsToFile(UsageStatsFilePath, newStats); return newStats; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 加载使用统计失败: {ex.Message}", LogHelper.LogType.Error); + + // 返回默认统计对象 + return new UsageStats + { + DeviceId = DeviceId, + LastLaunchTime = DateTime.Now, + LaunchCount = 0, + TotalUsageSeconds = 0, + AverageSessionSeconds = 0, + LastUpdateCheck = DateTime.MinValue, + UpdatePriority = UpdatePriority.Medium, + UsageFrequency = UsageFrequency.Medium + }; + } } /// - /// 保存使用统计 - 多重备份 + /// 保存使用统计 /// private static void SaveUsageStats(UsageStats stats) { - SaveUsageStatsToAllLocations(stats); + // 保存到主文件 + SaveUsageStatsToFile(UsageStatsFilePath, stats); + + // 保存到备份文件 + SaveUsageStatsToFile(UsageStatsBackupPath, stats); } - /// - /// 数据源信息结构 - /// - private class DataSourceInfo - { - public UsageStats Stats { get; set; } - public string Source { get; set; } - public bool IsIntegrityValid { get; set; } - public DateTime LastModified { get; set; } - public int TrustScore { get; set; } - } + /// - /// 从文件加载使用统计(带完整性验证,但不丢弃篡改数据) + /// 从文件加载使用统计(解密) /// private static UsageStats LoadUsageStatsFromFile(string filePath) { @@ -1090,28 +907,28 @@ namespace Ink_Canvas.Helpers { if (File.Exists(filePath)) { - string json = File.ReadAllText(filePath); - var stats = JsonConvert.DeserializeObject(json); - if (stats != null && !string.IsNullOrEmpty(stats.DeviceId)) + // 尝试解密文件 + var stats = LoadUsageStatsFromEncryptedFile(filePath); + if (stats != null) { - // 验证数据完整性 - if (!string.IsNullOrEmpty(stats.DataHash)) - { - if (stats.VerifyDataIntegrity()) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 数据完整性验证通过: {filePath}"); - return stats; - } - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 数据完整性验证失败,可能被篡改: {filePath}", LogHelper.LogType.Warning); - return null; // 数据被篡改,不使用 - } - - // 旧版本数据,没有哈希值,更新哈希后返回 - // LogHelper.WriteLogToFile($"DeviceIdentifier | 检测到旧版本数据,正在更新完整性哈希: {filePath}"); - stats.UpdateDataHash(); return stats; } + + // 如果解密失败,尝试作为普通JSON文件读取(向后兼容) + try + { + string json = File.ReadAllText(filePath); + var plainStats = JsonConvert.DeserializeObject(json); + if (plainStats != null && !string.IsNullOrEmpty(plainStats.DeviceId)) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 从普通JSON文件加载使用统计: {filePath}"); + return plainStats; + } + } + catch + { + // 忽略JSON解析错误 + } } } catch (Exception ex) @@ -1122,557 +939,67 @@ namespace Ink_Canvas.Helpers } /// - /// 从文件加载使用统计(包括被篡改的数据,用于恢复分析) + /// 从加密文件加载使用统计 /// - private static DataSourceInfo LoadUsageStatsFromFileWithInfo(string filePath, string sourceName) + private static UsageStats LoadUsageStatsFromEncryptedFile(string filePath) { try { if (File.Exists(filePath)) { - string json = File.ReadAllText(filePath); - var stats = JsonConvert.DeserializeObject(json); - if (stats != null && !string.IsNullOrEmpty(stats.DeviceId)) + byte[] encryptedData = File.ReadAllBytes(filePath); + + if (encryptedData.Length < 32) // SHA256校验和长度为32字节 { - var fileInfo = new FileInfo(filePath); - var dataSource = new DataSourceInfo + LogHelper.WriteLogToFile($"DeviceIdentifier | 加密文件格式错误: {filePath}", LogHelper.LogType.Error); + return null; + } + + // 提取校验和和加密数据 + byte[] checksum = new byte[32]; + byte[] data = new byte[encryptedData.Length - 32]; + Array.Copy(encryptedData, 0, checksum, 0, 32); + Array.Copy(encryptedData, 32, data, 0, data.Length); + + // 使用SHA256生成解密密钥 + using (var sha256 = SHA256.Create()) + { + byte[] keyBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(DeviceId + "ICC_Usage_Stats_Salt")); + + // XOR解密 + byte[] decryptedData = new byte[data.Length]; + for (int i = 0; i < data.Length; i++) { - Stats = stats, - Source = sourceName, - LastModified = stats.LastModified != DateTime.MinValue ? stats.LastModified : fileInfo.LastWriteTime, - IsIntegrityValid = false, - TrustScore = 0 - }; - - // 验证数据完整性 - if (!string.IsNullOrEmpty(stats.DataHash)) - { - dataSource.IsIntegrityValid = stats.VerifyDataIntegrity(); - dataSource.TrustScore = dataSource.IsIntegrityValid ? 100 : 20; // 完整数据100分,篡改数据20分 + decryptedData[i] = (byte)(data[i] ^ keyBytes[i % keyBytes.Length]); } - else + + // 验证校验和 + byte[] computedChecksum = sha256.ComputeHash(decryptedData); + if (!checksum.SequenceEqual(computedChecksum)) { - // 旧版本数据,中等信任度 - dataSource.IsIntegrityValid = true; - dataSource.TrustScore = 60; - stats.UpdateDataHash(); + LogHelper.WriteLogToFile($"DeviceIdentifier | 加密文件校验和验证失败: {filePath}", LogHelper.LogType.Error); + return null; + } + + string json = Encoding.UTF8.GetString(decryptedData); + var stats = JsonConvert.DeserializeObject(json); + if (stats != null && !string.IsNullOrEmpty(stats.DeviceId)) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 从加密文件成功加载使用统计: {filePath}"); + return stats; } - - return dataSource; } } } catch (Exception ex) { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 从文件加载数据源信息失败 ({filePath}): {ex.Message}", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"DeviceIdentifier | 从加密文件加载使用统计失败 ({filePath}): {ex.Message}", LogHelper.LogType.Error); } return null; } - /// - /// 收集所有可用的使用统计数据源 - /// - private static List CollectAllUsageDataSources() - { - var dataSources = new List(); - - try - { - // 1. 收集文件数据源 - var fileSources = new[] - { - new { Path = UsageStatsFilePath, Name = "主文件" }, - new { Path = BackupUsageStatsPath, Name = "第一备份" }, - new { Path = SecondaryUsageBackupPath, Name = "第二备份" }, - new { Path = TertiaryUsageBackupPath, Name = "第三备份" }, - new { Path = QuaternaryUsageBackupPath, Name = "第四备份" } - }; - - foreach (var source in fileSources) - { - var dataSource = LoadUsageStatsFromFileWithInfo(source.Path, source.Name); - if (dataSource != null) - { - dataSources.Add(dataSource); - } - } - - // 2. 收集注册表数据源 - var registrySource = LoadUsageStatsFromRegistryWithInfo(@"Software\ICC\DeviceInfo", "主注册表"); - if (registrySource != null) - { - dataSources.Add(registrySource); - } - - // 3. 收集备用注册表数据源 - var backupRegistryPaths = new[] - { - new { Path = @"Software\Microsoft\Windows\CurrentVersion\ICC", Name = "备用注册表1" }, - new { Path = @"Software\Classes\.icc\UsageData", Name = "备用注册表2" }, - new { Path = @"Software\ICC\Config\Usage", Name = "备用注册表3" } - }; - - foreach (var regPath in backupRegistryPaths) - { - var regSource = LoadUsageStatsFromBackupRegistryWithInfo(regPath.Path, regPath.Name); - if (regSource != null) - { - dataSources.Add(regSource); - } - } - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 收集到 {dataSources.Count} 个数据源"); - return dataSources; - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 收集数据源失败: {ex.Message}", LogHelper.LogType.Error); - return dataSources; - } - } - - /// - /// 选择最可信的数据源 - /// - private static DataSourceInfo SelectMostTrustedData(List dataSources) - { - try - { - // 首先尝试找到完整性验证通过的数据 - var validSources = dataSources.Where(d => d.IsIntegrityValid).ToList(); - - if (validSources.Count > 0) - { - // 在有效数据中选择最新的 - var bestValid = validSources.OrderByDescending(d => d.LastModified).First(); - // LogHelper.WriteLogToFile($"DeviceIdentifier | 选择完整性验证通过的最新数据: {bestValid.Source}"); - return bestValid; - } - - // 如果没有完整性验证通过的数据,选择信任度最高的 - var bestByTrust = dataSources.OrderByDescending(d => d.TrustScore).ThenByDescending(d => d.LastModified).FirstOrDefault(); - if (bestByTrust != null) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 选择信任度最高的数据: {bestByTrust.Source} (信任度: {bestByTrust.TrustScore})", LogHelper.LogType.Warning); - return bestByTrust; - } - - return null; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 选择最可信数据失败: {ex.Message}", LogHelper.LogType.Error); - return null; - } - } - - /// - /// 尝试从部分损坏的数据中恢复 - /// - private static UsageStats AttemptPartialDataRecovery(List dataSources) - { - try - { - if (dataSources.Count == 0) - { - // LogHelper.WriteLogToFile("DeviceIdentifier | 没有可用数据源进行部分恢复"); - return null; - } - - // 从所有数据源中提取可信的字段 - var recoveredStats = new UsageStats - { - DeviceId = DeviceId, - LastLaunchTime = DateTime.Now, - LaunchCount = 0, - TotalUsageMinutes = 0, - AverageSessionMinutes = 0, - LastUpdateCheck = DateTime.MinValue, - UpdatePriority = UpdatePriority.Medium, - UsageFrequency = UsageFrequency.Medium - }; - - // 使用多数投票或最大值策略恢复关键数据(秒级精度) - var launchCounts = dataSources.Where(d => d.Stats.LaunchCount > 0).Select(d => d.Stats.LaunchCount).ToList(); - var usageSeconds = dataSources.Where(d => d.Stats.TotalUsageSeconds > 0).Select(d => d.Stats.TotalUsageSeconds).ToList(); - var usageMinutes = dataSources.Where(d => d.Stats.TotalUsageMinutes > 0).Select(d => d.Stats.TotalUsageMinutes).ToList(); - - if (launchCounts.Count > 0) - { - recoveredStats.LaunchCount = (int)launchCounts.Average(); // 使用平均值 - } - - // 优先使用秒级数据,如果没有则使用分钟数据转换 - if (usageSeconds.Count > 0) - { - recoveredStats.TotalUsageSeconds = (long)usageSeconds.Average(); // 使用平均值 - recoveredStats.TotalUsageMinutes = recoveredStats.TotalUsageSeconds / 60; // 兼容性 - } - else if (usageMinutes.Count > 0) - { - recoveredStats.TotalUsageMinutes = (long)usageMinutes.Average(); // 使用平均值 - recoveredStats.TotalUsageSeconds = recoveredStats.TotalUsageMinutes * 60; // 转换为秒 - } - - // 重新计算平均会话时长(秒级精度) - if (recoveredStats.LaunchCount > 0) - { - recoveredStats.AverageSessionSeconds = (double)recoveredStats.TotalUsageSeconds / recoveredStats.LaunchCount; - recoveredStats.AverageSessionMinutes = recoveredStats.AverageSessionSeconds / 60; // 兼容性 - } - - // 重新计算使用频率 - CalculateUsageFrequency(recoveredStats); - - // 更新数据完整性哈希 - recoveredStats.UpdateDataHash(); - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 部分数据恢复完成 - 启动次数: {recoveredStats.LaunchCount}, 使用时长: {FormatDuration(recoveredStats.TotalUsageSeconds)}"); - return recoveredStats; - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 部分数据恢复失败: {ex.Message}", LogHelper.LogType.Error); - return null; - } - } - - /// - /// 从注册表加载使用统计(带数据源信息) - /// - private static DataSourceInfo LoadUsageStatsFromRegistryWithInfo(string registryPath, string sourceName) - { - try - { - using (var key = Registry.CurrentUser.OpenSubKey(registryPath)) - { - if (key != null) - { - var deviceId = key.GetValue("DeviceId") as string; - var launchCount = key.GetValue("LaunchCount"); - - // 秒级精度数据 - var totalSeconds = key.GetValue("TotalUsageSeconds"); - var avgSessionSeconds = key.GetValue("AverageSessionSeconds"); - - // 兼容性:分钟精度数据 - var totalMinutes = key.GetValue("TotalUsageMinutes"); - var avgSessionMinutes = key.GetValue("AverageSessionMinutes"); - - var lastLaunch = key.GetValue("LastLaunchTime") as string; - var priority = key.GetValue("UpdatePriority"); - var frequency = key.GetValue("UsageFrequency"); - var dataHash = key.GetValue("DataHash") as string; - var lastUpdate = key.GetValue("LastUpdate") as string; - - // 每周统计数据(秒级精度) - var weeklyLaunchCount = key.GetValue("WeeklyLaunchCount"); - var weeklyUsageSeconds = key.GetValue("WeeklyUsageSeconds"); - var lastWeekUsageSeconds = key.GetValue("LastWeekUsageSeconds"); - - // 兼容性:分钟精度数据 - var weeklyUsageMinutes = key.GetValue("WeeklyUsageMinutes"); - var lastWeekUsageMinutes = key.GetValue("LastWeekUsageMinutes"); - - var weekStartDate = key.GetValue("WeekStartDate") as string; - var lastWeekLaunchCount = key.GetValue("LastWeekLaunchCount"); - - if (!string.IsNullOrEmpty(deviceId) && launchCount != null) - { - var stats = new UsageStats - { - DeviceId = deviceId, - LaunchCount = Convert.ToInt32(launchCount), - - // 秒级精度数据 - TotalUsageSeconds = totalSeconds != null ? Convert.ToInt64(totalSeconds) : 0, - AverageSessionSeconds = avgSessionSeconds != null ? Convert.ToDouble(avgSessionSeconds) : 0, - - // 兼容性:分钟精度数据 - TotalUsageMinutes = totalMinutes != null ? Convert.ToInt64(totalMinutes) : 0, - AverageSessionMinutes = avgSessionMinutes != null ? Convert.ToDouble(avgSessionMinutes) : 0, - - LastLaunchTime = DateTime.TryParse(lastLaunch, out var dt) ? dt : DateTime.Now, - UpdatePriority = priority != null ? (UpdatePriority)Convert.ToInt32(priority) : UpdatePriority.Medium, - UsageFrequency = frequency != null ? (UsageFrequency)Convert.ToInt32(frequency) : UsageFrequency.Medium, - DataHash = dataHash, - LastUpdateCheck = DateTime.MinValue, - - // 每周统计数据(秒级精度) - WeeklyLaunchCount = weeklyLaunchCount != null ? Convert.ToInt32(weeklyLaunchCount) : 0, - WeeklyUsageSeconds = weeklyUsageSeconds != null ? Convert.ToInt64(weeklyUsageSeconds) : 0, - LastWeekUsageSeconds = lastWeekUsageSeconds != null ? Convert.ToInt64(lastWeekUsageSeconds) : 0, - - // 兼容性:分钟精度数据 - WeeklyUsageMinutes = weeklyUsageMinutes != null ? Convert.ToInt64(weeklyUsageMinutes) : 0, - LastWeekUsageMinutes = lastWeekUsageMinutes != null ? Convert.ToInt64(lastWeekUsageMinutes) : 0, - - WeekStartDate = DateTime.TryParse(weekStartDate, out var wsd) ? wsd : DateTime.MinValue, - LastWeekLaunchCount = lastWeekLaunchCount != null ? Convert.ToInt32(lastWeekLaunchCount) : 0 - }; - - // 执行数据迁移(如果需要) - stats.MigrateToSecondsPrecision(); - - // 重新计算平均会话时长 - if (stats.LaunchCount > 0 && stats.AverageSessionSeconds == 0) - { - stats.AverageSessionSeconds = (double)stats.TotalUsageSeconds / stats.LaunchCount; - stats.AverageSessionMinutes = stats.AverageSessionSeconds / 60; - } - - var dataSource = new DataSourceInfo - { - Stats = stats, - Source = sourceName, - LastModified = DateTime.TryParse(lastUpdate, out var updateTime) ? updateTime : DateTime.Now, - IsIntegrityValid = false, - TrustScore = 80 // 注册表数据信任度较高 - }; - - // 验证数据完整性 - if (!string.IsNullOrEmpty(stats.DataHash)) - { - dataSource.IsIntegrityValid = stats.VerifyDataIntegrity(); - dataSource.TrustScore = dataSource.IsIntegrityValid ? 100 : 30; - } - - return dataSource; - } - } - } - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 从注册表加载数据源信息失败 ({registryPath}): {ex.Message}", LogHelper.LogType.Error); - } - return null; - } - - /// - /// 从备用注册表位置加载使用统计(带数据源信息) - /// - private static DataSourceInfo LoadUsageStatsFromBackupRegistryWithInfo(string registryPath, string sourceName) - { - try - { - using (var key = Registry.CurrentUser.OpenSubKey(registryPath)) - { - if (key != null) - { - var launchCount = key.GetValue("LC"); - - // 秒级精度数据 - var totalSeconds = key.GetValue("TUS"); - var avgSessionSeconds = key.GetValue("ASS"); - - // 兼容性:分钟精度数据 - var totalMinutes = key.GetValue("TUM"); - var avgSessionMinutes = key.GetValue("ASM"); - - var lastLaunchBinary = key.GetValue("LLT"); - var priority = key.GetValue("UP"); - var frequency = key.GetValue("UF"); - var dataHash = key.GetValue("DH") as string; - var lastUpdateBinary = key.GetValue("LU"); - - // 每周统计数据(秒级精度) - var weeklyLaunchCount = key.GetValue("WLC"); - var weeklyUsageSeconds = key.GetValue("WUS"); - var lastWeekUsageSeconds = key.GetValue("LWUS"); - - // 兼容性:分钟精度数据 - var weeklyUsageMinutes = key.GetValue("WUM"); - var lastWeekUsageMinutes = key.GetValue("LWUM"); - - var weekStartDateBinary = key.GetValue("WSD"); - var lastWeekLaunchCount = key.GetValue("LWLC"); - - if (launchCount != null && (totalSeconds != null || totalMinutes != null)) - { - var stats = new UsageStats - { - DeviceId = DeviceId, - LaunchCount = Convert.ToInt32(launchCount), - - // 秒级精度数据 - TotalUsageSeconds = totalSeconds != null ? Convert.ToInt64(totalSeconds) : 0, - AverageSessionSeconds = avgSessionSeconds != null ? Convert.ToDouble(avgSessionSeconds) : 0, - - // 兼容性:分钟精度数据 - TotalUsageMinutes = totalMinutes != null ? Convert.ToInt64(totalMinutes) : 0, - AverageSessionMinutes = avgSessionMinutes != null ? Convert.ToDouble(avgSessionMinutes) : 0, - - LastLaunchTime = lastLaunchBinary != null ? DateTime.FromBinary(Convert.ToInt64(lastLaunchBinary)) : DateTime.Now, - UpdatePriority = priority != null ? (UpdatePriority)Convert.ToInt32(priority) : UpdatePriority.Medium, - UsageFrequency = frequency != null ? (UsageFrequency)Convert.ToInt32(frequency) : UsageFrequency.Medium, - DataHash = dataHash, - LastUpdateCheck = DateTime.MinValue, - - // 每周统计数据(秒级精度) - WeeklyLaunchCount = weeklyLaunchCount != null ? Convert.ToInt32(weeklyLaunchCount) : 0, - WeeklyUsageSeconds = weeklyUsageSeconds != null ? Convert.ToInt64(weeklyUsageSeconds) : 0, - LastWeekUsageSeconds = lastWeekUsageSeconds != null ? Convert.ToInt64(lastWeekUsageSeconds) : 0, - - // 兼容性:分钟精度数据 - WeeklyUsageMinutes = weeklyUsageMinutes != null ? Convert.ToInt64(weeklyUsageMinutes) : 0, - LastWeekUsageMinutes = lastWeekUsageMinutes != null ? Convert.ToInt64(lastWeekUsageMinutes) : 0, - - WeekStartDate = weekStartDateBinary != null ? DateTime.FromBinary(Convert.ToInt64(weekStartDateBinary)) : DateTime.MinValue, - LastWeekLaunchCount = lastWeekLaunchCount != null ? Convert.ToInt32(lastWeekLaunchCount) : 0 - }; - - // 执行数据迁移(如果需要) - stats.MigrateToSecondsPrecision(); - - // 重新计算平均会话时长 - if (stats.LaunchCount > 0 && stats.AverageSessionSeconds == 0) - { - stats.AverageSessionSeconds = (double)stats.TotalUsageSeconds / stats.LaunchCount; - stats.AverageSessionMinutes = stats.AverageSessionSeconds / 60; - } - - var dataSource = new DataSourceInfo - { - Stats = stats, - Source = sourceName, - LastModified = lastUpdateBinary != null ? DateTime.FromBinary(Convert.ToInt64(lastUpdateBinary)) : DateTime.Now, - IsIntegrityValid = false, - TrustScore = 75 // 备用注册表数据信任度中等 - }; - - // 验证数据完整性 - if (!string.IsNullOrEmpty(stats.DataHash)) - { - dataSource.IsIntegrityValid = stats.VerifyDataIntegrity(); - dataSource.TrustScore = dataSource.IsIntegrityValid ? 100 : 25; - } - - return dataSource; - } - } - } - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 从备用注册表加载数据源信息失败 ({registryPath}): {ex.Message}", LogHelper.LogType.Error); - } - return null; - } - - /// - /// 从注册表加载使用统计(保持向后兼容) - /// - private static UsageStats LoadUsageStatsFromRegistry() - { - var dataSource = LoadUsageStatsFromRegistryWithInfo(@"Software\ICC\DeviceInfo", "主注册表"); - return dataSource?.Stats; - } - - /// - /// 从多个注册表位置加载使用统计(强化恢复) - /// - private static UsageStats LoadUsageStatsFromMultipleRegistryLocations() - { - var registryPaths = new[] - { - @"Software\Microsoft\Windows\CurrentVersion\ICC", - @"Software\Classes\.icc\UsageData", - @"Software\ICC\Config\Usage" - }; - - foreach (var path in registryPaths) - { - try - { - using (var key = Registry.CurrentUser.OpenSubKey(path)) - { - if (key != null) - { - var launchCount = key.GetValue("LC"); - var totalMinutes = key.GetValue("TUM"); - var lastLaunchBinary = key.GetValue("LLT"); - var priority = key.GetValue("UP"); - var frequency = key.GetValue("UF"); - var dataHash = key.GetValue("DH") as string; - - if (launchCount != null && totalMinutes != null) - { - var stats = new UsageStats - { - DeviceId = DeviceId, - LaunchCount = Convert.ToInt32(launchCount), - TotalUsageMinutes = Convert.ToInt64(totalMinutes), - LastLaunchTime = lastLaunchBinary != null ? DateTime.FromBinary(Convert.ToInt64(lastLaunchBinary)) : DateTime.Now, - UpdatePriority = priority != null ? (UpdatePriority)Convert.ToInt32(priority) : UpdatePriority.Medium, - UsageFrequency = frequency != null ? (UsageFrequency)Convert.ToInt32(frequency) : UsageFrequency.Medium, - DataHash = dataHash, - AverageSessionMinutes = 0, - LastUpdateCheck = DateTime.MinValue - }; - - // 重新计算平均会话时长 - if (stats.LaunchCount > 0) - { - stats.AverageSessionMinutes = (double)stats.TotalUsageMinutes / stats.LaunchCount; - } - - // 验证数据完整性(如果有哈希值) - if (!string.IsNullOrEmpty(stats.DataHash)) - { - if (stats.VerifyDataIntegrity()) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 从注册表位置恢复数据并验证完整性通过: {path}"); - return stats; - } - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 注册表位置数据完整性验证失败: {path}", LogHelper.LogType.Warning); - } - else - { - // 没有哈希值的旧数据,更新哈希后返回 - stats.UpdateDataHash(); - // LogHelper.WriteLogToFile($"DeviceIdentifier | 从注册表位置恢复旧版本数据: {path}"); - return stats; - } - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 从注册表位置加载失败 ({path}): {ex.Message}", LogHelper.LogType.Error); - } - } - - return null; - } - - /// - /// 保存使用统计到所有位置(强化版本 - 多重隐藏备份) - /// - private static void SaveUsageStatsToAllLocations(UsageStats stats) - { - // 保存到主文件 - SaveUsageStatsToFile(UsageStatsFilePath, stats); - - // 保存到第一备份文件 - SaveUsageStatsToFile(BackupUsageStatsPath, stats); - - // 保存到多个隐藏备份位置(专门针对使用频率数据保护) - SaveUsageStatsToFile(SecondaryUsageBackupPath, stats); - SaveUsageStatsToFile(TertiaryUsageBackupPath, stats); - SaveUsageStatsToFile(QuaternaryUsageBackupPath, stats); - - // 保存到注册表 - SaveUsageStatsToRegistry(stats); - - // 保存到注册表的多个位置 - SaveUsageStatsToMultipleRegistryLocations(stats); - } - - /// - /// 保存使用统计到文件 + /// + /// 保存使用统计到文件(加密) /// private static void SaveUsageStatsToFile(string filePath, UsageStats stats) { @@ -1683,161 +1010,42 @@ namespace Ink_Canvas.Helpers if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); - // 设置隐藏属性 - if (filePath.Contains(".sys")) - { - var dirInfo = new DirectoryInfo(directory); - dirInfo.Attributes |= FileAttributes.Hidden | FileAttributes.System; - } } string json = JsonConvert.SerializeObject(stats, Formatting.Indented); - File.WriteAllText(filePath, json); - - // 设置文件属性为隐藏和系统文件 - if (filePath.Contains(".sys")) + byte[] data = Encoding.UTF8.GetBytes(json); + + // 使用SHA256生成加密密钥(基于设备ID) + using (var sha256 = SHA256.Create()) { - var fileInfo = new FileInfo(filePath); - fileInfo.Attributes |= FileAttributes.Hidden | FileAttributes.System; - } - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 使用统计已保存到: {filePath}"); - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 保存使用统计到文件失败 ({filePath}): {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 保存使用统计到注册表 - /// - private static void SaveUsageStatsToRegistry(UsageStats stats) - { - try - { - // LogHelper.WriteLogToFile("DeviceIdentifier | 开始保存使用统计到主注册表位置"); - - using (var key = Registry.CurrentUser.CreateSubKey(@"Software\ICC\DeviceInfo")) - { - if (key != null) + byte[] keyBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(DeviceId + "ICC_Usage_Stats_Salt")); + + // 简单的XOR加密 + byte[] encryptedData = new byte[data.Length]; + for (int i = 0; i < data.Length; i++) { - key.SetValue("DeviceId", stats.DeviceId); - key.SetValue("LaunchCount", stats.LaunchCount); - - // 秒级精度数据 - key.SetValue("TotalUsageSeconds", stats.TotalUsageSeconds); - key.SetValue("AverageSessionSeconds", stats.AverageSessionSeconds); - - // 兼容性:分钟精度数据 - key.SetValue("TotalUsageMinutes", stats.TotalUsageMinutes); - key.SetValue("AverageSessionMinutes", stats.AverageSessionMinutes); - - key.SetValue("LastLaunchTime", stats.LastLaunchTime.ToString("yyyy-MM-dd HH:mm:ss")); - key.SetValue("UpdatePriority", (int)stats.UpdatePriority); - key.SetValue("UsageFrequency", (int)stats.UsageFrequency); - key.SetValue("DataHash", stats.DataHash ?? ""); - key.SetValue("LastUpdate", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); - - // 每周统计数据(秒级精度) - key.SetValue("WeeklyLaunchCount", stats.WeeklyLaunchCount); - key.SetValue("WeeklyUsageSeconds", stats.WeeklyUsageSeconds); - key.SetValue("LastWeekUsageSeconds", stats.LastWeekUsageSeconds); - - // 兼容性:分钟精度数据 - key.SetValue("WeeklyUsageMinutes", stats.WeeklyUsageMinutes); - key.SetValue("LastWeekUsageMinutes", stats.LastWeekUsageMinutes); - - key.SetValue("WeekStartDate", stats.WeekStartDate.ToString("yyyy-MM-dd")); - key.SetValue("LastWeekLaunchCount", stats.LastWeekLaunchCount); - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 使用统计已保存到主注册表 - 总启动: {stats.LaunchCount}次, 本周启动: {stats.WeeklyLaunchCount}次, " + - // $"总时长: {FormatDuration(stats.TotalUsageSeconds)}, 本周时长: {FormatDuration(stats.WeeklyUsageSeconds)}"); - } - else - { - // LogHelper.WriteLogToFile("DeviceIdentifier | 创建主注册表键失败", LogHelper.LogType.Error); + encryptedData[i] = (byte)(data[i] ^ keyBytes[i % keyBytes.Length]); } + + // 添加SHA256校验和 + byte[] checksum = sha256.ComputeHash(data); + byte[] finalData = new byte[checksum.Length + encryptedData.Length]; + checksum.CopyTo(finalData, 0); + encryptedData.CopyTo(finalData, checksum.Length); + + File.WriteAllBytes(filePath, finalData); + + LogHelper.WriteLogToFile($"DeviceIdentifier | 加密使用统计已保存到: {filePath}"); } } catch (Exception ex) { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 保存使用统计到主注册表失败: {ex.Message}", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"DeviceIdentifier | 保存使用统计到文件失败 ({filePath}): {ex.Message}", LogHelper.LogType.Error); } } /// - /// 保存使用统计到多个注册表位置(强化保护) - /// - private static void SaveUsageStatsToMultipleRegistryLocations(UsageStats stats) - { - var registryPaths = new[] - { - @"Software\Microsoft\Windows\CurrentVersion\ICC", - @"Software\Classes\.icc\UsageData", - @"Software\ICC\Config\Usage" - }; - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 开始保存使用统计到{registryPaths.Length}个备用注册表位置"); - var successCount = 0; - - foreach (var path in registryPaths) - { - try - { - using (var key = Registry.CurrentUser.CreateSubKey(path)) - { - if (key != null) - { - // 使用编码的键名来隐藏数据(秒级精度) - key.SetValue("LC", stats.LaunchCount); // LaunchCount - - // 秒级精度数据 - key.SetValue("TUS", stats.TotalUsageSeconds); // TotalUsageSeconds - key.SetValue("ASS", stats.AverageSessionSeconds); // AverageSessionSeconds - - // 兼容性:分钟精度数据 - key.SetValue("TUM", stats.TotalUsageMinutes); // TotalUsageMinutes - key.SetValue("ASM", stats.AverageSessionMinutes); // AverageSessionMinutes - - key.SetValue("LLT", stats.LastLaunchTime.ToBinary()); // LastLaunchTime - key.SetValue("UP", (int)stats.UpdatePriority); // UpdatePriority - key.SetValue("UF", (int)stats.UsageFrequency); // UsageFrequency - key.SetValue("DH", stats.DataHash ?? ""); // DataHash - key.SetValue("LU", DateTime.Now.ToBinary()); // LastUpdate - - // 每周统计数据(秒级精度) - key.SetValue("WLC", stats.WeeklyLaunchCount); // WeeklyLaunchCount - key.SetValue("WUS", stats.WeeklyUsageSeconds); // WeeklyUsageSeconds - key.SetValue("LWUS", stats.LastWeekUsageSeconds); // LastWeekUsageSeconds - - // 兼容性:分钟精度数据 - key.SetValue("WUM", stats.WeeklyUsageMinutes); // WeeklyUsageMinutes - key.SetValue("LWUM", stats.LastWeekUsageMinutes); // LastWeekUsageMinutes - - key.SetValue("WSD", stats.WeekStartDate.ToBinary()); // WeekStartDate - key.SetValue("LWLC", stats.LastWeekLaunchCount); // LastWeekLaunchCount - - successCount++; - // LogHelper.WriteLogToFile($"DeviceIdentifier | 成功保存到备用注册表位置: {path}"); - } - else - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 创建备用注册表键失败: {path}", LogHelper.LogType.Error); - } - } - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 保存到备用注册表位置失败 ({path}): {ex.Message}", LogHelper.LogType.Error); - } - } - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 备用注册表保存完成: {successCount}/{registryPaths.Length} 成功"); - } - - /// - /// 记录更新检查时间(同时执行数据保护检查) + /// 记录更新检查时间 /// public static void RecordUpdateCheck() { @@ -1847,14 +1055,7 @@ namespace Ink_Canvas.Helpers { var stats = LoadUsageStats(); stats.LastUpdateCheck = DateTime.Now; - stats.UpdateDataHash(); SaveUsageStats(stats); - - // 定期执行数据保护检查(每10次更新检查执行一次) - if (stats.LaunchCount % 10 == 0) - { - PerformUsageDataProtectionCheck(); - } } } catch (Exception ex) @@ -1864,139 +1065,130 @@ namespace Ink_Canvas.Helpers } /// - /// 执行使用频率数据保护检查和自动修复 + /// 验证使用统计数据的完整性 /// - public static bool PerformUsageDataProtectionCheck() + public static bool ValidateUsageStatsIntegrity() { try { - lock (fileLock) + // 检查主文件 + if (File.Exists(UsageStatsFilePath)) { - LogHelper.WriteLogToFile("DeviceIdentifier | 开始使用频率数据保护检查"); - - var issues = new List(); - var repaired = new List(); - var backupPaths = new[] + var mainStats = LoadUsageStatsFromFile(UsageStatsFilePath); + if (mainStats != null && mainStats.DeviceId == DeviceId) { - new { Path = UsageStatsFilePath, Name = "主文件" }, - new { Path = BackupUsageStatsPath, Name = "第一备份" }, - new { Path = SecondaryUsageBackupPath, Name = "第二备份" }, - new { Path = TertiaryUsageBackupPath, Name = "第三备份" }, - new { Path = QuaternaryUsageBackupPath, Name = "第四备份" } - }; - - // 检查所有备份文件 - UsageStats validStats = null; - var missingFiles = new List(); - - foreach (var backup in backupPaths) - { - if (!File.Exists(backup.Path)) - { - issues.Add($"{backup.Name}丢失"); - missingFiles.Add(backup.Path); - } - else - { - var stats = LoadUsageStatsFromFile(backup.Path); - if (stats != null && stats.VerifyDataIntegrity() && validStats == null) - { - validStats = stats; - } - } + LogHelper.WriteLogToFile("DeviceIdentifier | 主文件数据完整性验证通过"); + return true; } - - // 如果找到有效数据,修复丢失的文件 - if (validStats != null && missingFiles.Count > 0) - { - foreach (var missingFile in missingFiles) - { - SaveUsageStatsToFile(missingFile, validStats); - repaired.Add($"恢复文件: {Path.GetFileName(missingFile)}"); - } - } - - // 检查注册表备份 - var registryPaths = new[] - { - @"Software\ICC\DeviceInfo", - @"Software\Microsoft\Windows\CurrentVersion\ICC", - @"Software\Classes\.icc\UsageData", - @"Software\ICC\Config\Usage" - }; - - var missingRegistryBackups = 0; - foreach (var path in registryPaths) - { - try - { - using (var key = Registry.CurrentUser.OpenSubKey(path)) - { - if (key == null) missingRegistryBackups++; - } - } - catch - { - missingRegistryBackups++; - } - } - - if (missingRegistryBackups > 0) - { - issues.Add($"{missingRegistryBackups}个注册表备份丢失"); - if (validStats != null) - { - SaveUsageStatsToMultipleRegistryLocations(validStats); - repaired.Add("重建注册表备份"); - } - } - - // 如果没有找到任何有效数据,尝试从注册表恢复 - if (validStats == null) - { - validStats = LoadUsageStatsFromRegistry(); - if (validStats == null) - { - validStats = LoadUsageStatsFromMultipleRegistryLocations(); - } - - if (validStats != null) - { - SaveUsageStatsToAllLocations(validStats); - repaired.Add("从注册表完全恢复数据"); - } - else - { - // 最后手段:强制重建 - ForceRebuildUsageDataBackups(); - repaired.Add("强制重建所有备份"); - } - } - - // 记录检查结果 - if (issues.Count > 0) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率数据保护检查发现问题: {string.Join(", ", issues)}", LogHelper.LogType.Warning); - } - - if (repaired.Count > 0) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率数据保护修复: {string.Join(", ", repaired)}"); - } - - var protectionScore = CalculateUsageDataProtectionScore(); - LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率数据保护检查完成 - 问题: {issues.Count}, 修复: {repaired.Count}, 保护强度: {protectionScore}/100"); - - return protectionScore >= 80; } + + // 检查备份文件 + if (File.Exists(UsageStatsBackupPath)) + { + var backupStats = LoadUsageStatsFromFile(UsageStatsBackupPath); + if (backupStats != null && backupStats.DeviceId == DeviceId) + { + LogHelper.WriteLogToFile("DeviceIdentifier | 备份文件数据完整性验证通过"); + return true; + } + } + + LogHelper.WriteLogToFile("DeviceIdentifier | 数据完整性验证失败", LogHelper.LogType.Warning); + return false; } catch (Exception ex) { - LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率数据保护检查失败: {ex.Message}", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"DeviceIdentifier | 数据完整性验证失败: {ex.Message}", LogHelper.LogType.Error); return false; } } + /// + /// 从备份文件恢复使用统计数据 + /// + public static bool RestoreUsageStatsFromBackup() + { + try + { + if (File.Exists(UsageStatsBackupPath)) + { + var backupStats = LoadUsageStatsFromFile(UsageStatsBackupPath); + if (backupStats != null && backupStats.DeviceId == DeviceId) + { + // 保存到主文件 + SaveUsageStatsToFile(UsageStatsFilePath, backupStats); + LogHelper.WriteLogToFile("DeviceIdentifier | 从备份文件成功恢复使用统计数据"); + return true; + } + } + + LogHelper.WriteLogToFile("DeviceIdentifier | 备份文件不存在或损坏,无法恢复", LogHelper.LogType.Warning); + return false; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 从备份文件恢复失败: {ex.Message}", LogHelper.LogType.Error); + return false; + } + } + + /// + /// 获取使用统计文件状态信息 + /// + public static string GetUsageStatsFileStatus() + { + try + { + var status = new List(); + + // 检查主文件 + if (File.Exists(UsageStatsFilePath)) + { + var fileInfo = new FileInfo(UsageStatsFilePath); + var mainStats = LoadUsageStatsFromFile(UsageStatsFilePath); + if (mainStats != null && mainStats.DeviceId == DeviceId) + { + status.Add($"主文件: 正常 ({fileInfo.Length} 字节, 修改时间: {fileInfo.LastWriteTime:yyyy-MM-dd HH:mm:ss})"); + } + else + { + status.Add($"主文件: 损坏 ({fileInfo.Length} 字节)"); + } + } + else + { + status.Add("主文件: 不存在"); + } + + // 检查备份文件 + if (File.Exists(UsageStatsBackupPath)) + { + var fileInfo = new FileInfo(UsageStatsBackupPath); + var backupStats = LoadUsageStatsFromFile(UsageStatsBackupPath); + if (backupStats != null && backupStats.DeviceId == DeviceId) + { + status.Add($"备份文件: 正常 ({fileInfo.Length} 字节, 修改时间: {fileInfo.LastWriteTime:yyyy-MM-dd HH:mm:ss})"); + } + else + { + status.Add($"备份文件: 损坏 ({fileInfo.Length} 字节)"); + } + } + else + { + status.Add("备份文件: 不存在"); + } + + return string.Join("\n", status); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 获取文件状态失败: {ex.Message}", LogHelper.LogType.Error); + return "获取文件状态失败"; + } + } + + /// /// 根据优先级决定是否应该推送更新(仅适用于自动更新,版本修复功能不受影响) /// @@ -2120,7 +1312,7 @@ namespace Ink_Canvas.Helpers double daysBetweenVersions, double daysSinceLastUse, UsageStats stats, UpdateType updateType) { // 考虑用户的总体使用模式 - var isHeavyUser = stats.TotalUsageMinutes > 3000; // 超过50小时的重度用户 + var isHeavyUser = stats.TotalUsageSeconds > 3000; // 超过50小时的重度用户 var isFrequentUser = stats.LaunchCount > 100; // 启动超过100次的频繁用户 // 根据更新类型调整推送策略 @@ -2279,7 +1471,8 @@ namespace Ink_Canvas.Helpers $"使用频率: {frequency}\n" + $"更新优先级: {priority}\n" + $"最后使用: {daysSinceLastUse:F1}天前\n" + - $"用户类型: {GetUserTypeDescription(stats)}"; + $"用户类型: {GetUserTypeDescription(stats)}\n\n" + + $"文件状态:\n{GetUsageStatsFileStatus()}"; } catch (Exception ex) { @@ -2293,7 +1486,7 @@ namespace Ink_Canvas.Helpers /// private static string GetUserTypeDescription(UsageStats stats) { - var isHeavyUser = stats.TotalUsageMinutes > 3000; + var isHeavyUser = stats.TotalUsageSeconds > 3000; var isFrequentUser = stats.LaunchCount > 100; var daysSinceLastUse = (DateTime.Now - stats.LastLaunchTime).TotalDays; @@ -2310,461 +1503,19 @@ namespace Ink_Canvas.Helpers return descriptions.Count > 0 ? string.Join(", ", descriptions) : "普通用户"; } - /// - /// 数据自检和修复 - /// - public static bool PerformDataIntegrityCheck() - { - try - { - lock (fileLock) - { - // LogHelper.WriteLogToFile("DeviceIdentifier | 开始数据完整性检查"); - - var issues = new List(); - var repaired = new List(); - - // 检查设备ID文件 - if (!File.Exists(DeviceIdFilePath)) - { - issues.Add("主设备ID文件丢失"); - if (File.Exists(BackupDeviceIdPath)) - { - var backupId = LoadDeviceIdFromFile(BackupDeviceIdPath); - if (!string.IsNullOrEmpty(backupId)) - { - SaveDeviceIdToFile(DeviceIdFilePath, backupId); - repaired.Add("从备份恢复主设备ID文件"); - } - } - } - - // 检查备份设备ID文件 - if (!File.Exists(BackupDeviceIdPath)) - { - issues.Add("备份设备ID文件丢失"); - var mainId = LoadDeviceIdFromFile(DeviceIdFilePath); - if (!string.IsNullOrEmpty(mainId)) - { - SaveDeviceIdToFile(BackupDeviceIdPath, mainId); - repaired.Add("重建备份设备ID文件"); - } - } - - // 检查使用统计文件 - if (!File.Exists(UsageStatsFilePath)) - { - issues.Add("主使用统计文件丢失"); - var backupStatsForRestore = LoadUsageStatsFromFile(BackupUsageStatsPath); - if (backupStatsForRestore != null) - { - SaveUsageStatsToFile(UsageStatsFilePath, backupStatsForRestore); - repaired.Add("从备份恢复主使用统计文件"); - } - } - - // 检查备份使用统计文件 - if (!File.Exists(BackupUsageStatsPath)) - { - issues.Add("备份使用统计文件丢失"); - var mainStatsForBackup = LoadUsageStatsFromFile(UsageStatsFilePath); - if (mainStatsForBackup != null) - { - SaveUsageStatsToFile(BackupUsageStatsPath, mainStatsForBackup); - repaired.Add("重建备份使用统计文件"); - } - } - - // 验证数据一致性 - var mainStats = LoadUsageStatsFromFile(UsageStatsFilePath); - var backupStats = LoadUsageStatsFromFile(BackupUsageStatsPath); - - if (mainStats != null && backupStats != null) - { - if (mainStats.LaunchCount != backupStats.LaunchCount || - mainStats.TotalUsageMinutes != backupStats.TotalUsageMinutes) - { - issues.Add("主备份数据不一致"); - // 使用最新的数据 - var newerStats = mainStats.LastModified > backupStats.LastModified ? mainStats : backupStats; - SaveUsageStatsToAllLocations(newerStats); - repaired.Add("同步主备份数据"); - } - } - - // 记录检查结果 - if (issues.Count > 0) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 发现问题: {string.Join(", ", issues)}", LogHelper.LogType.Warning); - } - - if (repaired.Count > 0) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 已修复: {string.Join(", ", repaired)}"); - } - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 数据完整性检查完成 - 问题: {issues.Count}, 修复: {repaired.Count}"); - return issues.Count == 0 || repaired.Count > 0; - } - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 数据完整性检查失败: {ex.Message}", LogHelper.LogType.Error); - return false; - } - } - - /// - /// 获取使用频率数据保护状态摘要(强化版本) - /// - public static string GetUsageDataProtectionSummary() - { - try - { - var summary = new StringBuilder(); - summary.AppendLine("使用频率数据保护状态摘要:"); - - // 检查主要文件 - summary.AppendLine($"主使用统计文件: {(File.Exists(UsageStatsFilePath) ? "✓" : "✗")}"); - summary.AppendLine($"第一备份文件: {(File.Exists(BackupUsageStatsPath) ? "✓" : "✗")}"); - - // 检查多重隐藏备份 - summary.AppendLine($"第二备份文件: {(File.Exists(SecondaryUsageBackupPath) ? "✓" : "✗")}"); - summary.AppendLine($"第三备份文件: {(File.Exists(TertiaryUsageBackupPath) ? "✓" : "✗")}"); - summary.AppendLine($"第四备份文件: {(File.Exists(QuaternaryUsageBackupPath) ? "✓" : "✗")}"); - - // 检查注册表备份 - var registryBackups = 0; - var registryPaths = new[] - { - @"Software\ICC\DeviceInfo", - @"Software\Microsoft\Windows\CurrentVersion\ICC", - @"Software\Classes\.icc\UsageData", - @"Software\ICC\Config\Usage" - }; - - foreach (var path in registryPaths) - { - try - { - using (var key = Registry.CurrentUser.OpenSubKey(path)) - { - if (key != null) registryBackups++; - } - } - catch { } - } - - summary.AppendLine($"注册表备份位置: {registryBackups}/4 ✓"); - - // 检查数据完整性和可恢复性 - var stats = LoadUsageStats(); - if (stats != null) - { - summary.AppendLine($"数据完整性: {(stats.VerifyDataIntegrity() ? "✓" : "✗")}"); - summary.AppendLine($"总启动次数: {stats.LaunchCount}"); - summary.AppendLine($"总使用时长: {FormatDuration(stats.TotalUsageSeconds)}"); - summary.AppendLine($"本周启动次数: {stats.WeeklyLaunchCount}"); - summary.AppendLine($"本周使用时长: {FormatDuration(stats.WeeklyUsageSeconds)}"); - summary.AppendLine($"上周启动次数: {stats.LastWeekLaunchCount}"); - summary.AppendLine($"上周使用时长: {FormatDuration(stats.LastWeekUsageSeconds)}"); - summary.AppendLine($"本周开始日期: {(stats.WeekStartDate != DateTime.MinValue ? stats.WeekStartDate.ToString("yyyy-MM-dd") : "未设置")}"); - summary.AppendLine($"使用频率: {stats.UsageFrequency}"); - summary.AppendLine($"更新优先级: {stats.UpdatePriority}"); - summary.AppendLine($"最后修改: {stats.LastModified:yyyy-MM-dd HH:mm:ss}"); - } - - // 计算保护强度评分 - var protectionScore = CalculateUsageDataProtectionScore(); - summary.AppendLine($"保护强度评分: {protectionScore}/100"); - - return summary.ToString(); - } - catch (Exception ex) - { - return $"获取使用频率数据保护状态失败: {ex.Message}"; - } - } - - /// - /// 计算使用频率数据保护强度评分 - /// - private static int CalculateUsageDataProtectionScore() - { - var score = 0; - - try - { - // 文件备份评分(50分) - if (File.Exists(UsageStatsFilePath)) score += 15; - if (File.Exists(BackupUsageStatsPath)) score += 10; - if (File.Exists(SecondaryUsageBackupPath)) score += 8; - if (File.Exists(TertiaryUsageBackupPath)) score += 8; - if (File.Exists(QuaternaryUsageBackupPath)) score += 9; - - // 注册表备份评分(30分) - var registryPaths = new[] - { - @"Software\ICC\DeviceInfo", - @"Software\Microsoft\Windows\CurrentVersion\ICC", - @"Software\Classes\.icc\UsageData", - @"Software\ICC\Config\Usage" - }; - - foreach (var path in registryPaths) - { - try - { - using (var key = Registry.CurrentUser.OpenSubKey(path)) - { - if (key != null) score += 7; - } - } - catch { } - } - - // 数据完整性评分(20分) - var stats = LoadUsageStats(); - if (stats != null) - { - if (!string.IsNullOrEmpty(stats.DataHash)) score += 10; - if (stats.VerifyDataIntegrity()) score += 10; - } - } - catch { } - - return Math.Min(100, score); - } - - /// - /// 强制重建所有使用频率数据备份 - /// - public static bool ForceRebuildUsageDataBackups() - { - try - { - lock (fileLock) - { - // LogHelper.WriteLogToFile("DeviceIdentifier | 开始强制重建使用频率数据备份"); - - var stats = LoadUsageStats(); - if (stats == null) - { - // 如果无法加载任何数据,创建基础数据 - stats = new UsageStats - { - DeviceId = DeviceId, - LastLaunchTime = DateTime.Now, - LaunchCount = 1, - TotalUsageMinutes = 0, - AverageSessionMinutes = 0, - LastUpdateCheck = DateTime.MinValue, - UpdatePriority = UpdatePriority.Medium, - UsageFrequency = UsageFrequency.Medium - }; - stats.UpdateDataHash(); - LogHelper.WriteLogToFile("DeviceIdentifier | 创建新的基础使用数据"); - } - - // 强制保存到所有位置 - SaveUsageStatsToAllLocations(stats); - - // 验证重建结果 - var protectionScore = CalculateUsageDataProtectionScore(); - // LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率数据备份重建完成,保护强度: {protectionScore}/100"); - - return protectionScore >= 80; // 80分以上认为重建成功 - } - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 强制重建使用频率数据备份失败: {ex.Message}", LogHelper.LogType.Error); - return false; - } - } - - /// - /// 获取数据保护状态摘要(保持向后兼容) - /// - public static string GetDataProtectionSummary() - { - return GetUsageDataProtectionSummary(); - } - - - - /// - /// 强制执行一次完整的数据保存操作(包括注册表) - /// - public static bool ForceCompleteDataSave() - { - try - { - lock (fileLock) - { - // LogHelper.WriteLogToFile("DeviceIdentifier | 开始强制完整数据保存"); - - // 保存设备ID到所有位置 - SaveDeviceIdToAllLocations(DeviceId); - - // 加载并保存使用统计到所有位置 - var stats = LoadUsageStats(); - if (stats != null) - { - stats.UpdateDataHash(); - SaveUsageStatsToAllLocations(stats); - - // 验证注册表保存是否成功 - var verificationResult = VerifyRegistryData(); - // LogHelper.WriteLogToFile($"DeviceIdentifier | 注册表数据验证结果: {verificationResult}"); - - // LogHelper.WriteLogToFile("DeviceIdentifier | 强制完整数据保存完成"); - return true; - } - - // LogHelper.WriteLogToFile("DeviceIdentifier | 强制完整数据保存失败: 无法加载使用统计", LogHelper.LogType.Error); - return false; - } - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 强制完整数据保存失败: {ex.Message}", LogHelper.LogType.Error); - return false; - } - } - - /// - /// 验证注册表中的数据是否存在 - /// - public static string VerifyRegistryData() - { - var results = new StringBuilder(); - results.AppendLine("注册表数据验证结果:"); - - try - { - // 验证主注册表位置 - try - { - using (var key = Registry.CurrentUser.OpenSubKey(@"Software\ICC\DeviceInfo")) - { - if (key != null) - { - var deviceId = key.GetValue("DeviceId") as string; - var launchCount = key.GetValue("LaunchCount"); - var totalMinutes = key.GetValue("TotalUsageMinutes"); - var lastUpdate = key.GetValue("LastUpdate") as string; - - results.AppendLine("✓ 主注册表位置存在"); - results.AppendLine($" 设备ID: {deviceId ?? "未找到"}"); - results.AppendLine($" 启动次数: {launchCount ?? "未找到"}"); - results.AppendLine($" 使用时长: {totalMinutes ?? "未找到"}分钟"); - results.AppendLine($" 最后更新: {lastUpdate ?? "未找到"}"); - } - else - { - results.AppendLine("✗ 主注册表位置不存在"); - } - } - } - catch (Exception ex) - { - results.AppendLine($"✗ 主注册表位置访问失败: {ex.Message}"); - } - - // 验证备用注册表位置 - var registryPaths = new[] - { - @"Software\Microsoft\Windows\CurrentVersion\ICC", - @"Software\Classes\.icc\UsageData", - @"Software\ICC\Config\Usage" - }; - - foreach (var path in registryPaths) - { - try - { - using (var key = Registry.CurrentUser.OpenSubKey(path)) - { - if (key != null) - { - var launchCount = key.GetValue("LC"); - var totalMinutes = key.GetValue("TUM"); - var lastUpdate = key.GetValue("LU"); - - results.AppendLine($"✓ 备用注册表位置存在: {path}"); - results.AppendLine($" 启动次数: {launchCount ?? "未找到"}"); - results.AppendLine($" 使用时长: {totalMinutes ?? "未找到"}"); - results.AppendLine($" 最后更新: {(lastUpdate != null ? DateTime.FromBinary(Convert.ToInt64(lastUpdate)).ToString("yyyy-MM-dd HH:mm:ss") : "未找到")}"); - } - else - { - results.AppendLine($"✗ 备用注册表位置不存在: {path}"); - } - } - } - catch (Exception ex) - { - results.AppendLine($"✗ 备用注册表位置访问失败 ({path}): {ex.Message}"); - } - } - - return results.ToString(); - } - catch (Exception ex) - { - return $"注册表数据验证失败: {ex.Message}"; - } - } - - /// - /// 立即执行一次数据保存并验证注册表写入 - /// - public static string SaveAndVerifyRegistryData() - { - try - { - // LogHelper.WriteLogToFile("DeviceIdentifier | 开始保存并验证注册表数据"); - - // 强制保存数据 - var saveSuccess = ForceCompleteDataSave(); - - // 验证注册表数据 - var verificationResult = VerifyRegistryData(); - - var result = $"保存操作: {(saveSuccess ? "成功" : "失败")}\n\n{verificationResult}"; - - // LogHelper.WriteLogToFile("DeviceIdentifier | 保存并验证注册表数据完成"); - return result; - } - catch (Exception ex) - { - var errorMsg = $"保存并验证注册表数据失败: {ex.Message}"; - LogHelper.WriteLogToFile($"DeviceIdentifier | {errorMsg}", LogHelper.LogType.Error); - return errorMsg; - } - } /// /// 关机时保存使用时间数据 /// public static void SaveUsageStatsOnShutdown() { - // 使用超时锁防止死锁 - if (!Monitor.TryEnter(fileLock, TimeSpan.FromSeconds(30))) - { - LogHelper.WriteLogToFile("DeviceIdentifier | 关机保存超时,使用备用保存策略", LogHelper.LogType.Warning); - SaveUsageStatsOnShutdownFallback(); - return; - } - try { LogHelper.WriteLogToFile("DeviceIdentifier | 开始关机时保存使用时间数据", LogHelper.LogType.Info); - // 1. 加载现有使用统计数据(多重恢复策略) - UsageStats stats = LoadUsageStatsWithFallback(); + // 1. 加载现有使用统计数据 + var stats = LoadUsageStats(); if (stats == null) { stats = new UsageStats { DeviceId = DeviceId }; @@ -2788,261 +1539,16 @@ namespace Ink_Canvas.Helpers stats.AverageSessionSeconds = stats.TotalUsageSeconds / (double)Math.Max(1, stats.LaunchCount); stats.LastLaunchTime = DateTime.Now; - // 更新数据哈希值 - stats.UpdateDataHash(); - - // 4. 多重保存策略 - 确保数据不丢失 - var saveResults = new List(); + // 4. 保存数据 + SaveUsageStats(stats); - // 4.1 保存到所有文件位置 - saveResults.Add(SaveUsageStatsToAllFileLocations(stats)); - - // 4.2 保存到所有注册表位置 - saveResults.Add(SaveUsageStatsToAllRegistryLocations(stats)); - - // 4.3 保存到内存缓存(作为最后防线) - SaveUsageStatsToMemoryCache(stats); - - // 4.4 强制刷新文件系统缓存 - ForceFlushFileSystem(); - - // 4.5 验证保存结果 - var verificationResult = VerifyDataSaveResults(stats, saveResults); - - LogHelper.WriteLogToFile($"DeviceIdentifier | 关机保存完成,验证结果: {verificationResult}", LogHelper.LogType.Info); + LogHelper.WriteLogToFile("DeviceIdentifier | 关机保存完成", LogHelper.LogType.Info); } catch (Exception ex) { LogHelper.WriteLogToFile($"DeviceIdentifier | 关机时保存使用时间数据失败: {ex.Message}", LogHelper.LogType.Error); - - // 即使主保存失败,也要尝试备用保存 - try - { - SaveUsageStatsOnShutdownFallback(); - } - catch (Exception fallbackEx) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 备用保存也失败: {fallbackEx.Message}", LogHelper.LogType.Error); - } - } - finally - { - Monitor.Exit(fileLock); } } - - /// - /// 关机保存的备用策略 - /// - private static void SaveUsageStatsOnShutdownFallback() - { - try - { - LogHelper.WriteLogToFile("DeviceIdentifier | 执行关机保存备用策略", LogHelper.LogType.Warning); - - // 使用最基本的保存方式 - var stats = new UsageStats { DeviceId = DeviceId }; - stats.TotalUsageSeconds = 1; // 最小记录 - stats.LaunchCount = 1; - stats.LastLaunchTime = DateTime.Now; - - // 只保存到最可靠的位置 - SaveUsageStatsToFile(BackupUsageStatsPath, stats); - SaveUsageStatsToRegistry(stats); - - LogHelper.WriteLogToFile("DeviceIdentifier | 备用策略执行完成", LogHelper.LogType.Info); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 备用策略执行失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 加载使用统计数据(带多重恢复策略) - /// - private static UsageStats LoadUsageStatsWithFallback() - { - try - { - // 1. 尝试从主文件加载 - var stats = LoadUsageStats(); - if (stats != null) return stats; - - // 2. 尝试从备份文件加载 - stats = LoadUsageStatsFromFile(BackupUsageStatsPath); - if (stats != null) return stats; - - // 3. 尝试从其他备份位置加载 - var backupPaths = new[] { SecondaryUsageBackupPath, TertiaryUsageBackupPath, QuaternaryUsageBackupPath }; - foreach (var path in backupPaths) - { - stats = LoadUsageStatsFromFile(path); - if (stats != null) return stats; - } - - // 4. 尝试从注册表恢复 - stats = LoadUsageStatsFromRegistry(); - if (stats != null) return stats; - - return null; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 多重恢复加载失败: {ex.Message}", LogHelper.LogType.Error); - return null; - } - } - - /// - /// 保存使用统计到所有文件位置 - /// - private static string SaveUsageStatsToAllFileLocations(UsageStats stats) - { - var results = new List(); - var filePaths = new[] - { - UsageStatsFilePath, - BackupUsageStatsPath, - SecondaryUsageBackupPath, - TertiaryUsageBackupPath, - QuaternaryUsageBackupPath - }; - - foreach (var filePath in filePaths) - { - try - { - SaveUsageStatsToFile(filePath, stats); - results.Add($"✓ {Path.GetFileName(filePath)}"); - } - catch (Exception ex) - { - results.Add($"✗ {Path.GetFileName(filePath)}: {ex.Message}"); - } - } - - return string.Join("\n", results); - } - - /// - /// 保存使用统计到所有注册表位置 - /// - private static string SaveUsageStatsToAllRegistryLocations(UsageStats stats) - { - var results = new List(); - - try - { - // 主注册表位置 - SaveUsageStatsToRegistry(stats); - results.Add("✓ 主注册表位置"); - } - catch (Exception ex) - { - results.Add($"✗ 主注册表位置: {ex.Message}"); - } - - try - { - // 备用注册表位置 - SaveUsageStatsToMultipleRegistryLocations(stats); - results.Add("✓ 备用注册表位置"); - } - catch (Exception ex) - { - results.Add($"✗ 备用注册表位置: {ex.Message}"); - } - - return string.Join("\n", results); - } - - /// - /// 保存使用统计到内存缓存 - /// - private static void SaveUsageStatsToMemoryCache(UsageStats stats) - { - try - { - // 将数据保存到静态变量作为内存备份 - _cachedUsageStats = stats; - LogHelper.WriteLogToFile("DeviceIdentifier | 数据已保存到内存缓存", LogHelper.LogType.Info); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 保存到内存缓存失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 强制刷新文件系统缓存 - /// - private static void ForceFlushFileSystem() - { - try - { - // 强制刷新所有相关目录 - var directories = new[] - { - Path.GetDirectoryName(UsageStatsFilePath), - Path.GetDirectoryName(BackupUsageStatsPath), - Path.GetDirectoryName(SecondaryUsageBackupPath), - Path.GetDirectoryName(TertiaryUsageBackupPath), - Path.GetDirectoryName(QuaternaryUsageBackupPath) - }; - - foreach (var dir in directories.Where(d => !string.IsNullOrEmpty(d) && Directory.Exists(d))) - { - try - { - // 创建临时文件来强制刷新 - var tempFile = Path.Combine(dir, ".flush.tmp"); - File.WriteAllText(tempFile, DateTime.Now.ToString()); - File.Delete(tempFile); - } - catch { /* 忽略刷新错误 */ } - } - - LogHelper.WriteLogToFile("DeviceIdentifier | 文件系统缓存刷新完成", LogHelper.LogType.Info); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 文件系统缓存刷新失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 验证数据保存结果 - /// - private static string VerifyDataSaveResults(UsageStats stats, List saveResults) - { - var verification = new StringBuilder(); - verification.AppendLine("数据保存验证结果:"); - verification.AppendLine(string.Join("\n", saveResults)); - - // 验证关键数据是否保存成功 - try - { - var savedStats = LoadUsageStats(); - if (savedStats != null && savedStats.DeviceId == stats.DeviceId) - { - verification.AppendLine("✓ 主数据文件验证成功"); - } - else - { - verification.AppendLine("✗ 主数据文件验证失败"); - } - } - catch (Exception ex) - { - verification.AppendLine($"✗ 主数据文件验证异常: {ex.Message}"); - } - - return verification.ToString(); - } - - // 内存缓存变量 - private static UsageStats _cachedUsageStats; } } diff --git a/Ink Canvas/Helpers/GlobalHotkeyManager.cs b/Ink Canvas/Helpers/GlobalHotkeyManager.cs index 467adec2..dd90ddf7 100644 --- a/Ink Canvas/Helpers/GlobalHotkeyManager.cs +++ b/Ink Canvas/Helpers/GlobalHotkeyManager.cs @@ -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": diff --git a/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs b/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs index ccea253c..1ca4689f 100644 --- a/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs +++ b/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs @@ -197,61 +197,5 @@ namespace Ink_Canvas.Helpers } } - /// - /// 质量配置枚举 - /// - public enum InkSmoothingQuality - { - HighPerformance = 0, // 高性能低质量 - Balanced = 1, // 平衡 - HighQuality = 2 // 高质量低性能 - } - /// - /// 墨迹平滑配置 - /// - 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; - } - } - } } diff --git a/Ink Canvas/Helpers/ImprovedBezierSmoothing.cs b/Ink Canvas/Helpers/ImprovedBezierSmoothing.cs new file mode 100644 index 00000000..f249885d --- /dev/null +++ b/Ink Canvas/Helpers/ImprovedBezierSmoothing.cs @@ -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 +{ + /// + /// 改进的三次贝塞尔曲线平滑算法 + /// + public class ImprovedBezierSmoothing + { + private readonly InkSmoothingConfig _config; + + public ImprovedBezierSmoothing(InkSmoothingConfig config = null) + { + _config = config ?? new InkSmoothingConfig(); + } + + /// + /// 使用改进的贝塞尔曲线算法平滑笔画 + /// + 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() + }; + } + + /// + /// 去除噪声点 + /// + private StylusPoint[] RemoveNoisePoints(StylusPoint[] points) + { + if (points.Length < 3) return points; + + var result = new List { 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(); + } + + /// + /// 检查是否为异常点 + /// + 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; + } + + /// + /// 应用三次贝塞尔曲线平滑 + /// + private StylusPoint[] ApplyCubicBezierSmoothing(StylusPoint[] points) + { + if (points.Length < 4) return points; + + var result = new List(); + 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(); + } + + /// + /// 计算最优控制点 + /// + 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); + } + + /// + /// 计算切线方向 + /// + 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; + } + + /// + /// 计算两点间距离 + /// + 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); + } + + /// + /// 计算插值步数 + /// + 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)); + } + + /// + /// 计算曲率 + /// + 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 + } + + /// + /// 计算贝塞尔曲线上的点 + /// + 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); + } + + /// + /// 后处理点集 + /// + private StylusPoint[] PostProcessPoints(StylusPoint[] points) + { + if (points.Length == 0) return points; + + // 如果点数过多,进行重采样 + if (points.Length > _config.MaxPointsPerStroke) + { + return ResamplePoints(points, _config.ResampleInterval); + } + + return points; + } + + /// + /// 重采样点集 + /// + private StylusPoint[] ResamplePoints(StylusPoint[] points, double interval) + { + var result = new List { 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(); + } + } +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/InkFadeManager.cs b/Ink Canvas/Helpers/InkFadeManager.cs index 1a1e6b49..febc4662 100644 --- a/Ink Canvas/Helpers/InkFadeManager.cs +++ b/Ink Canvas/Helpers/InkFadeManager.cs @@ -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(); - 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); diff --git a/Ink Canvas/Helpers/InkSmoothingConfig.cs b/Ink Canvas/Helpers/InkSmoothingConfig.cs new file mode 100644 index 00000000..e57aad38 --- /dev/null +++ b/Ink Canvas/Helpers/InkSmoothingConfig.cs @@ -0,0 +1,155 @@ +using System; +using System.Configuration; + +namespace Ink_Canvas.Helpers +{ + /// + /// 墨迹平滑配置类 + /// + 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 // 高质量低性能 + } + + /// + /// 从设置中加载配置 + /// + 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; + } + + /// + /// 应用质量设置 + /// + 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; + } + } + + /// + /// 保存配置到设置 + /// + 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}"); + } + } + + /// + /// 验证配置参数 + /// + 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; + } + + /// + /// 获取配置摘要 + /// + public string GetSummary() + { + return $"质量: {Quality}, 强度: {SmoothingStrength:F2}, 间隔: {ResampleInterval:F1}, " + + $"步数: {InterpolationSteps}, 自适应: {UseAdaptiveInterpolation}, " + + $"张力: {CurveTension:F2}, 硬件加速: {UseHardwareAcceleration}"; + } + } +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/InkSmoothingManager.cs b/Ink Canvas/Helpers/InkSmoothingManager.cs index 3d2e5db5..489cdda3 100644 --- a/Ink Canvas/Helpers/InkSmoothingManager.cs +++ b/Ink Canvas/Helpers/InkSmoothingManager.cs @@ -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; diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs index c51fa589..76c3f4e9 100644 --- a/Ink Canvas/Helpers/PPTManager.cs +++ b/Ink Canvas/Helpers/PPTManager.cs @@ -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) diff --git a/Ink Canvas/Helpers/Plugins/EnhancedPluginBase.cs b/Ink Canvas/Helpers/Plugins/EnhancedPluginBase.cs index cf3c0b47..3b62137f 100644 --- a/Ink Canvas/Helpers/Plugins/EnhancedPluginBase.cs +++ b/Ink Canvas/Helpers/Plugins/EnhancedPluginBase.cs @@ -1,4 +1,3 @@ -using System; using System.Windows.Controls; namespace Ink_Canvas.Helpers.Plugins diff --git a/Ink Canvas/Helpers/Plugins/IEnhancedPlugin.cs b/Ink Canvas/Helpers/Plugins/IEnhancedPlugin.cs index 270064db..6ee5e9d5 100644 --- a/Ink Canvas/Helpers/Plugins/IEnhancedPlugin.cs +++ b/Ink Canvas/Helpers/Plugins/IEnhancedPlugin.cs @@ -1,4 +1,3 @@ -using System; using System.Windows.Controls; namespace Ink_Canvas.Helpers.Plugins diff --git a/Ink Canvas/Helpers/Plugins/IPluginService.cs b/Ink Canvas/Helpers/Plugins/IPluginService.cs index 3063f85f..2df665f5 100644 --- a/Ink Canvas/Helpers/Plugins/IPluginService.cs +++ b/Ink Canvas/Helpers/Plugins/IPluginService.cs @@ -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 diff --git a/Ink Canvas/Helpers/Plugins/PluginConfigurationManager.cs b/Ink Canvas/Helpers/Plugins/PluginConfigurationManager.cs index c328b642..dfa342b2 100644 --- a/Ink Canvas/Helpers/Plugins/PluginConfigurationManager.cs +++ b/Ink Canvas/Helpers/Plugins/PluginConfigurationManager.cs @@ -1,4 +1,3 @@ -using Ink_Canvas.Helpers; using Newtonsoft.Json; using System; using System.Collections.Generic; diff --git a/Ink Canvas/Helpers/Plugins/PluginServiceManager.cs b/Ink Canvas/Helpers/Plugins/PluginServiceManager.cs index 1748fc1e..e3e2d3e5 100644 --- a/Ink Canvas/Helpers/Plugins/PluginServiceManager.cs +++ b/Ink Canvas/Helpers/Plugins/PluginServiceManager.cs @@ -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; diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index 1a087381..c00ff977 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -3445,10 +3445,10 @@ @@ -3549,6 +3549,7 @@ + diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs index 043d046f..5e9794d2 100644 --- a/Ink Canvas/MainWindow.xaml.cs +++ b/Ink Canvas/MainWindow.xaml.cs @@ -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 全局快捷键管理 /// @@ -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) diff --git a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs index f011d0c8..8ecc3345 100644 --- a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs +++ b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs @@ -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); diff --git a/Ink Canvas/MainWindow_cs/MW_ClipboardHandler.cs b/Ink Canvas/MainWindow_cs/MW_ClipboardHandler.cs index ee4a6152..fc8eb43e 100644 --- a/Ink Canvas/MainWindow_cs/MW_ClipboardHandler.cs +++ b/Ink Canvas/MainWindow_cs/MW_ClipboardHandler.cs @@ -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); diff --git a/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs b/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs index ea0176cc..cbfb5a81 100644 --- a/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs +++ b/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs @@ -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().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().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().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 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 /// 克隆图片 /// /// 要克隆的图片 - 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; } /// @@ -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 } } diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs index af76a843..ff1b66d6 100644 --- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs +++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs @@ -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; } /// @@ -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 } } + /// + /// 获取当前选中的模式 + /// + /// 当前选中的模式名称 + 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 触摸事件支持 diff --git a/Ink Canvas/MainWindow_cs/MW_Hotkeys.cs b/Ink Canvas/MainWindow_cs/MW_Hotkeys.cs index a0cbc550..81df3421 100644 --- a/Ink Canvas/MainWindow_cs/MW_Hotkeys.cs +++ b/Ink Canvas/MainWindow_cs/MW_Hotkeys.cs @@ -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) diff --git a/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs b/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs new file mode 100644 index 00000000..2ebc2189 --- /dev/null +++ b/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs @@ -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 Path; + + public ScreenshotResult(Rectangle area, List 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 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 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 + } + } +} \ No newline at end of file diff --git a/Ink Canvas/MainWindow_cs/MW_PPT.cs b/Ink Canvas/MainWindow_cs/MW_PPT.cs index 3efc6e72..f3c05cab 100644 --- a/Ink Canvas/MainWindow_cs/MW_PPT.cs +++ b/Ink Canvas/MainWindow_cs/MW_PPT.cs @@ -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 diff --git a/Ink Canvas/MainWindow_cs/MW_PageListView.cs b/Ink Canvas/MainWindow_cs/MW_PageListView.cs index 70521c1d..c82a0113 100644 --- a/Ink Canvas/MainWindow_cs/MW_PageListView.cs +++ b/Ink Canvas/MainWindow_cs/MW_PageListView.cs @@ -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; diff --git a/Ink Canvas/MainWindow_cs/MW_Screenshot.cs b/Ink Canvas/MainWindow_cs/MW_Screenshot.cs index 00e537fd..05b5cf89 100644 --- a/Ink Canvas/MainWindow_cs/MW_Screenshot.cs +++ b/Ink Canvas/MainWindow_cs/MW_Screenshot.cs @@ -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 Path; - - public ScreenshotResult(System.Drawing.Rectangle area, List 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 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 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; - } - } } } diff --git a/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs b/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs index a87dec7b..6c6ab016 100644 --- a/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs +++ b/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs @@ -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>(); 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 resizeHandles = new List(); - 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 } -} \ No newline at end of file +} + +#endregion diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs index 9d7ca62a..723f056e 100644 --- a/Ink Canvas/MainWindow_cs/MW_Settings.cs +++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs @@ -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) { diff --git a/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs b/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs index cc44b98b..c663f058 100644 --- a/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs +++ b/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs @@ -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); } } } diff --git a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs index d24d6f42..9c1a8393 100644 --- a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs +++ b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs @@ -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; diff --git a/Ink Canvas/Properties/AssemblyInfo.cs b/Ink Canvas/Properties/AssemblyInfo.cs index 68f1de06..36053414 100644 --- a/Ink Canvas/Properties/AssemblyInfo.cs +++ b/Ink Canvas/Properties/AssemblyInfo.cs @@ -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")] diff --git a/Ink Canvas/Windows/HotkeySettingsWindow.xaml b/Ink Canvas/Windows/HotkeySettingsWindow.xaml index 68bde7dc..8b008b07 100644 --- a/Ink Canvas/Windows/HotkeySettingsWindow.xaml +++ b/Ink Canvas/Windows/HotkeySettingsWindow.xaml @@ -101,8 +101,8 @@ DefaultKey="B" DefaultModifiers="Alt"/> diff --git a/Ink Canvas/Windows/HotkeySettingsWindow.xaml.cs b/Ink Canvas/Windows/HotkeySettingsWindow.xaml.cs index 31289032..ea00e278 100644 --- a/Ink Canvas/Windows/HotkeySettingsWindow.xaml.cs +++ b/Ink Canvas/Windows/HotkeySettingsWindow.xaml.cs @@ -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) diff --git a/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml.cs b/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml.cs index ee5aa49e..205f7fce 100644 --- a/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml.cs +++ b/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml.cs @@ -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 _freehandPoints; + private Point _startPoint; + private Point _currentPoint; + private List _freehandPoints; private Polyline _freehandPolyline; public DrawingRectangle? SelectedArea { get; private set; } - public List SelectedPath { get; private set; } + public List SelectedPath { get; private set; } public ScreenshotSelectorWindow() { @@ -47,7 +47,7 @@ namespace Ink_Canvas private void InitializeFreehandMode() { - _freehandPoints = new List(); + _freehandPoints = new List(); _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(_freehandPoints); + var pathPoints = new List(_freehandPoints); // 确保路径闭合(如果最后一个点不是起始点,则添加起始点) if (pathPoints.Count > 0 && @@ -284,7 +284,7 @@ namespace Ink_Canvas return new Rect(x, y, width, height); } - private Rect CalculatePathBounds(List points) + private Rect CalculatePathBounds(List points) { if (points == null || points.Count == 0) return new Rect(); @@ -306,12 +306,12 @@ namespace Ink_Canvas } // 优化路径:移除重复点和过于接近的点,提高路径质量 - private List OptimizePath(List originalPath) + private List OptimizePath(List originalPath) { if (originalPath == null || originalPath.Count < 3) return originalPath; - var optimizedPath = new List(); + var optimizedPath = new List(); const double minDistance = 2.0; // 最小距离阈值 // 添加第一个点