diff --git a/Ink Canvas/Helpers/InkRecognitionManager.cs b/Ink Canvas/Helpers/InkRecognitionManager.cs index 881b7ed6..8c30d975 100644 --- a/Ink Canvas/Helpers/InkRecognitionManager.cs +++ b/Ink Canvas/Helpers/InkRecognitionManager.cs @@ -11,7 +11,6 @@ namespace Ink_Canvas.Helpers private readonly object _initSync = new object(); private ModernInkProcessor _modernProcessor; - private ModernInkAnalyzer _modernAnalyzer; private bool _isModernSystemAvailable; private bool _isInitialized; @@ -41,7 +40,7 @@ namespace Ink_Canvas.Helpers try { // 启动阶段只做能力探测,不做 WinRT 组件实例化(避免冷启动延迟) - _isModernSystemAvailable = WinRtInkShapeRecognizer.IsApiAvailable && Environment.Is64BitProcess; + _isModernSystemAvailable = WinRtInkShapeRecognizer.IsApiAvailable; _isInitialized = true; } catch (Exception ex) @@ -63,22 +62,20 @@ namespace Ink_Canvas.Helpers private void EnsureModernAnalyzerInitialized() { - if (_modernAnalyzer != null || !_isModernSystemAvailable) return; + if (_modernProcessor != null || !_isModernSystemAvailable) return; lock (_initSync) { - if (_modernAnalyzer != null || !_isModernSystemAvailable) return; + if (_modernProcessor != null || !_isModernSystemAvailable) return; try { _modernProcessor ??= new ModernInkProcessor(); - _modernAnalyzer = new ModernInkAnalyzer(); } catch (Exception ex) { LogHelper.WriteLogToFile("WinRT 墨迹模块懒加载失败: " + ex.Message, LogHelper.LogType.Warning); _isModernSystemAvailable = false; _modernProcessor = null; - _modernAnalyzer = null; } } } @@ -156,19 +153,11 @@ namespace Ink_Canvas.Helpers return Task.FromResult(strokes); } - if (!Environment.Is64BitProcess) - { - LogHelper.WriteLogToFile( - "[手写体] CorrectInkAsync 跳过:非 64 位进程,WinRT 手写体替换不可用。笔画数=" + strokes.Count, - LogHelper.LogType.Info); - return Task.FromResult(strokes); - } - EnsureModernAnalyzerInitialized(); - if (_modernAnalyzer == null) + if (_modernProcessor == null) { LogHelper.WriteLogToFile( - "[手写体] CorrectInkAsync 跳过:ModernInkAnalyzer 未就绪(WinRT 初始化失败?)。笔画数=" + + "[手写体] CorrectInkAsync 跳过:ModernInkProcessor 未就绪(WinRT 初始化失败?)。笔画数=" + strokes.Count, LogHelper.LogType.Warning); return Task.FromResult(strokes); @@ -178,7 +167,7 @@ namespace Ink_Canvas.Helpers "[手写体] CorrectInkAsync 开始:笔画数=" + strokes.Count + ",字体=" + (string.IsNullOrWhiteSpace(handwritingFontFamilyList) ? "(默认)" : handwritingFontFamilyList.Trim()), LogHelper.LogType.Info); - return _modernAnalyzer.AnalyzeAndCorrectAsync(strokes, handwritingFontFamilyList); + return WinRtHandwritingRecognizer.ConvertRecognizedTextToHandwritingInkAsync(strokes, handwritingFontFamilyList); } catch (Exception ex) { @@ -188,7 +177,7 @@ namespace Ink_Canvas.Helpers } /// - /// WinRT 手写体识别(需 64 位进程、Windows 10+ 及系统手写识别组件)。返回分词候选与包围框,供剪贴板或插件使用。 + /// WinRT 手写体识别(需 Windows 10+ 及系统手写识别组件)。返回分词候选与包围框,供剪贴板或插件使用。 /// public Task RecognizeHandwritingAsync( StrokeCollection strokes, @@ -200,8 +189,7 @@ namespace Ink_Canvas.Helpers try { - if (!Environment.Is64BitProcess - || !ShapeRecognitionRouter.ResolveUseWinRt(mode) + if (!ShapeRecognitionRouter.ResolveUseWinRt(mode) || !WinRtHandwritingRecognizer.IsApiAvailable) return Task.FromResult(HandwritingRecognitionResult.Empty); @@ -227,14 +215,13 @@ namespace Ink_Canvas.Helpers public string GetSystemInfo() { return _isModernSystemAvailable - ? $"现代化64位墨迹识别系统 (Windows Runtime API) - 进程架构: {Environment.Is64BitProcess}" + ? $"现代化墨迹识别系统 (Windows Runtime API) - 进程架构: {Environment.Is64BitProcess}" : $"传统墨迹识别系统 (IACore) - 进程架构: {Environment.Is64BitProcess}"; } public void Dispose() { _modernProcessor?.Dispose(); - _modernAnalyzer?.Dispose(); _isInitialized = false; } } @@ -256,20 +243,4 @@ namespace Ink_Canvas.Helpers { } } - - internal sealed class ModernInkAnalyzer : IDisposable - { - public Task AnalyzeAndCorrectAsync( - StrokeCollection strokes, - string handwritingFontFamilyList) - { - return WinRtHandwritingRecognizer.ConvertRecognizedTextToHandwritingInkAsync( - strokes, - handwritingFontFamilyList); - } - - public void Dispose() - { - } - } } diff --git a/Ink Canvas/Helpers/InkRecognizeHelper.cs b/Ink Canvas/Helpers/InkRecognizeHelper.cs index 9f3018c7..ed56e693 100644 --- a/Ink Canvas/Helpers/InkRecognizeHelper.cs +++ b/Ink Canvas/Helpers/InkRecognizeHelper.cs @@ -117,7 +117,7 @@ namespace Ink_Canvas.Helpers } } - /// WinRT 手写识别(64 位 + Windows 10+)。 + /// WinRT 手写识别(Windows 10+)。 public static Task RecognizeHandwritingUnifiedAsync( StrokeCollection strokes, ShapeRecognitionEngineMode mode) => diff --git a/Ink Canvas/Helpers/InkShapeRecognition.cs b/Ink Canvas/Helpers/InkShapeRecognition.cs index 0d04c0cf..987a9aee 100644 --- a/Ink Canvas/Helpers/InkShapeRecognition.cs +++ b/Ink Canvas/Helpers/InkShapeRecognition.cs @@ -17,13 +17,13 @@ namespace Ink_Canvas.Helpers public static class ShapeRecognitionRouter { /// - /// 自动模式:按当前进程位数选择——64 位进程用 WinRT,32 位进程(含 x86 目标在 WOW64 下运行)用 IACore。 + /// 自动模式:在 Windows 10 及以上系统默认使用 WinRT,否则使用 IACore。 /// public static bool ResolveUseWinRt(ShapeRecognitionEngineMode mode) { if (mode == ShapeRecognitionEngineMode.WinRT) return true; if (mode == ShapeRecognitionEngineMode.IACore) return false; - return Environment.Is64BitProcess; + return OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows10; } public static bool ShouldRunShapeRecognition(bool inkToShapeEnabled, ShapeRecognitionEngineMode mode) @@ -31,7 +31,7 @@ namespace Ink_Canvas.Helpers if (!inkToShapeEnabled) return false; if (ResolveUseWinRt(mode)) return OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows10; - return !Environment.Is64BitProcess; + return true; } public static ShapeRecognitionEngineMode FromSettingsInt(int value) diff --git a/Ink Canvas/Helpers/WinRtInkShapeRecognizer.cs b/Ink Canvas/Helpers/WinRtInkShapeRecognizer.cs index 687d6980..8ad74c69 100644 --- a/Ink Canvas/Helpers/WinRtInkShapeRecognizer.cs +++ b/Ink Canvas/Helpers/WinRtInkShapeRecognizer.cs @@ -1,6 +1,7 @@ using OSVersionExtension; using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Ink; @@ -11,6 +12,128 @@ using WinRtInkAnalyzer = global::Windows.UI.Input.Inking.Analysis.InkAnalyzer; namespace Ink_Canvas.Helpers { + internal class ModernInkAnalyzer : IDisposable + { + public static readonly Guid ShapeStrokePropertyGuid = new Guid("11111111-2222-3333-4444-555555555555"); + + private global::Windows.UI.Input.Inking.Analysis.InkAnalyzer _internalAnalyzer; + private readonly Dictionary _strokeIdMap = new Dictionary(); + private readonly Dictionary _reverseIdMap = new Dictionary(); + private readonly object _syncLock = new object(); + + public ModernInkAnalyzer() + { + if (!WinRtInkShapeRecognizer.IsApiAvailable) + return; + + _internalAnalyzer = new global::Windows.UI.Input.Inking.Analysis.InkAnalyzer(); + } + + private void AddStrokeInternal(Stroke stroke) + { + if (stroke.ContainsPropertyData(ShapeStrokePropertyGuid)) + return; + + var inkStroke = WinRtInkShapeRecognizer.CreateInkStrokeFromWpf(stroke); + if (inkStroke == null) return; + + _internalAnalyzer.AddDataForStroke(inkStroke); + _internalAnalyzer.SetStrokeDataKind( + inkStroke.Id, + global::Windows.UI.Input.Inking.Analysis.InkAnalysisStrokeKind.Drawing); + + _strokeIdMap[stroke] = inkStroke.Id; + _reverseIdMap[inkStroke.Id] = stroke; + } + + private CancellationTokenSource _cts; + + public async Task AnalyzeAsync(StrokeCollection strokes) + { + if (_internalAnalyzer == null || strokes == null || strokes.Count == 0) + return InkShapeRecognitionResult.Empty; + + _cts?.Cancel(); + _cts = new CancellationTokenSource(); + var token = _cts.Token; + + try + { + lock (_syncLock) + { + _internalAnalyzer.ClearDataForAllStrokes(); + _strokeIdMap.Clear(); + _reverseIdMap.Clear(); + + foreach (var stroke in strokes) + { + AddStrokeInternal(stroke); + } + } + + if (_strokeIdMap.Count == 0) + return InkShapeRecognitionResult.Empty; + + var result = await _internalAnalyzer.AnalyzeAsync().AsTask(token).ConfigureAwait(true); + + if (token.IsCancellationRequested) return InkShapeRecognitionResult.Empty; + + // Use the internal method from WinRtInkShapeRecognizer to find the primary drawing + var drawing = WinRtInkShapeRecognizer.FindPrimaryDrawing(_internalAnalyzer); + if (drawing == null) + return InkShapeRecognitionResult.Empty; + + if (drawing.DrawingKind == global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind.Drawing) + return InkShapeRecognitionResult.Empty; + + var name = WinRtInkShapeRecognizer.MapDrawingKindToShapeName(drawing.DrawingKind); + if (string.IsNullOrEmpty(name) || name == "Drawing") + return InkShapeRecognitionResult.Empty; + + var winPts = WinRtInkShapeRecognizer.CopyWinRtPoints(drawing); + var hot = WinRtInkShapeRecognizer.ToWpfPointCollection(winPts); + var c = drawing.Center; + var centroid = new SysPoint(c.X, c.Y); + WinRtInkShapeRecognizer.BoundsFromPoints(winPts, out double w, out double h); + + var toRemove = new StrokeCollection(); + lock (_syncLock) + { + foreach (var id in drawing.GetStrokeIds()) + { + if (_reverseIdMap.TryGetValue(id, out var stroke)) + { + toRemove.Add(stroke); + } + } + } + + if (toRemove.Count == 0) + return InkShapeRecognitionResult.Empty; + + return new InkShapeRecognitionResult(name, centroid, hot, w, h, toRemove); + } + catch (Exception) + { + return InkShapeRecognitionResult.Empty; + } + } + + public Task AnalyzeAndCorrectAsync( + StrokeCollection strokes, + string handwritingFontFamilyList) + { + return WinRtHandwritingRecognizer.ConvertRecognizedTextToHandwritingInkAsync( + strokes, + handwritingFontFamilyList); + } + + public void Dispose() + { + _internalAnalyzer = null; + } + } + /// 基于 Windows.UI.Input.Inking.Analysis 的形状识别(适用于 64 位进程等场景)。 internal static class WinRtInkShapeRecognizer { @@ -150,8 +273,8 @@ namespace Ink_Canvas.Helpers return builder.CreateStroke(points); } - private static global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing FindPrimaryDrawing( - WinRtInkAnalyzer analyzer) + internal static global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing FindPrimaryDrawing( + global::Windows.UI.Input.Inking.Analysis.InkAnalyzer analyzer) { global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing best = null; double bestArea = -1; @@ -190,7 +313,7 @@ namespace Ink_Canvas.Helpers return w * h; } - private static global::Windows.Foundation.Point[] CopyWinRtPoints( + internal static global::Windows.Foundation.Point[] CopyWinRtPoints( global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing drawing) { var src = drawing?.Points; @@ -207,7 +330,7 @@ namespace Ink_Canvas.Helpers return arr; } - private static void BoundsFromPoints( + internal static void BoundsFromPoints( System.Collections.Generic.IReadOnlyList points, out double w, out double h) @@ -232,7 +355,7 @@ namespace Ink_Canvas.Helpers h = Math.Max(0, maxY - minY); } - private static PointCollection ToWpfPointCollection( + internal static PointCollection ToWpfPointCollection( System.Collections.Generic.IReadOnlyList points) { var hot = new PointCollection(); @@ -246,7 +369,7 @@ namespace Ink_Canvas.Helpers return hot; } - private static string MapDrawingKindToShapeName( + internal static string MapDrawingKindToShapeName( global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind kind) { switch (kind) diff --git a/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs b/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs index 5cddb40b..a81d2ada 100644 --- a/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs +++ b/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs @@ -30,6 +30,10 @@ namespace Ink_Canvas /// public partial class MainWindow : Window { + private Helpers.ModernInkAnalyzer _modernInkAnalyzer; + private Helpers.ModernInkAnalyzer ModernInkAnalyzer => + _modernInkAnalyzer ??= new Helpers.ModernInkAnalyzer(); + /// /// 存储新的笔画集合,用于形状识别 /// @@ -564,6 +568,7 @@ namespace Ink_Canvas { DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() }; + straightStroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true); // Replace the original stroke with the straightened one SetNewBackupOfStroke(); @@ -617,17 +622,26 @@ namespace Ink_Canvas ProcessRectangleGuideLines(e.Stroke); var shapeMode = ShapeRecognitionRouter.FromSettingsInt(Settings.InkToShape.ShapeRecognitionEngine); - var strokeReco = new StrokeCollection(); - var result = await InkRecognizeHelper.RecognizeShapeUnifiedAsync(newStrokes, shapeMode); - for (var i = newStrokes.Count - 1; i >= 0; i--) + InkShapeRecognitionResult result = InkShapeRecognitionResult.Empty; + + if (ShapeRecognitionRouter.ResolveUseWinRt(shapeMode) && Helpers.WinRtInkShapeRecognizer.IsApiAvailable) { - strokeReco.Add(newStrokes[i]); - var newResult = await InkRecognizeHelper.RecognizeShapeUnifiedAsync(strokeReco, shapeMode); - if (newResult.IsSuccess && - (newResult.ShapeName == "Circle" || newResult.ShapeName == "Ellipse")) + result = await ModernInkAnalyzer.AnalyzeAsync(newStrokes); + } + else + { + var strokeReco = new StrokeCollection(); + result = await InkRecognizeHelper.RecognizeShapeUnifiedAsync(newStrokes, shapeMode); + for (var i = newStrokes.Count - 1; i >= 0; i--) { - result = newResult; - break; + strokeReco.Add(newStrokes[i]); + var newResult = await InkRecognizeHelper.RecognizeShapeUnifiedAsync(strokeReco, shapeMode); + if (newResult.IsSuccess && + (newResult.ShapeName == "Circle" || newResult.ShapeName == "Ellipse")) + { + result = newResult; + break; + } } } @@ -687,6 +701,7 @@ namespace Ink_Canvas { DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() }; + stroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true); circles.Add(new Circle(result.Centroid, result.ShapeWidth / 2.0, stroke)); SetNewBackupOfStroke(); _currentCommitType = CommitReason.ShapeRecognition; @@ -790,6 +805,7 @@ namespace Ink_Canvas { DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() }; + _stroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true); var _dashedLineStroke = GenerateDashedLineEllipseStrokeCollection(iniP, endP, true, false); var strokes = new StrokeCollection { @@ -836,6 +852,7 @@ namespace Ink_Canvas { DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() }; + stroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true); if (needRotation) { @@ -883,6 +900,7 @@ namespace Ink_Canvas { DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() }; + stroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true); SetNewBackupOfStroke(); _currentCommitType = CommitReason.ShapeRecognition; inkCanvas.Strokes.Remove(result.StrokesToRemove); @@ -928,6 +946,7 @@ namespace Ink_Canvas { DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() }; + stroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true); SetNewBackupOfStroke(); _currentCommitType = CommitReason.ShapeRecognition; inkCanvas.Strokes.Remove(result.StrokesToRemove); @@ -2835,6 +2854,7 @@ namespace Ink_Canvas { DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() }; + rectangleStroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true); // 移除原有的四条直线 SetNewBackupOfStroke(); diff --git a/Ink Canvas/Properties/Strings.resx b/Ink Canvas/Properties/Strings.resx index 27aa9e9e..ea3fb82c 100644 --- a/Ink Canvas/Properties/Strings.resx +++ b/Ink Canvas/Properties/Strings.resx @@ -568,7 +568,7 @@ WinRT识别转手写体字形 - #开启后,调用墨迹纠正API时:先WinRT识别手写词,再将识别成功的文字用手写风格字体(默认Ink Free/楷体等,可在设置JSON的handwritingCorrectionFontFamily调整)转成字形轮廓墨迹替换原笔画。需64位与WinRT。 + #开启后,调用墨迹纠正API时:先WinRT识别手写词,再将识别成功的文字用手写风格字体(默认Ink Free/楷体等,可在设置JSON的handwritingCorrectionFontFamily调整)转成字形轮廓墨迹替换原笔画。需WinRT。 识别引擎