From f7802ba4d0dd10bc3b1a67fa37d204df07349618 Mon Sep 17 00:00:00 2001 From: CJKmkp Date: Sun, 26 Apr 2026 03:01:17 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20WinRT=20Ink=20Analysis=20=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E4=BC=98=E5=8C=96=E9=87=8D=E6=9E=84=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- Ink Canvas/Helpers/InkRecognitionManager.cs | 30 +--- Ink Canvas/Helpers/WinRtInkShapeRecognizer.cs | 156 +++++++++++++++++- .../MW_SimulatePressure&InkToShape.cs | 38 ++++- 3 files changed, 184 insertions(+), 40 deletions(-) diff --git a/Ink Canvas/Helpers/InkRecognitionManager.cs b/Ink Canvas/Helpers/InkRecognitionManager.cs index 881b7ed6..111aefd9 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; @@ -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; } } } @@ -165,10 +162,10 @@ namespace Ink_Canvas.Helpers } 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 +175,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) { @@ -234,7 +231,6 @@ namespace Ink_Canvas.Helpers public void Dispose() { _modernProcessor?.Dispose(); - _modernAnalyzer?.Dispose(); _isInitialized = false; } } @@ -256,20 +252,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/WinRtInkShapeRecognizer.cs b/Ink Canvas/Helpers/WinRtInkShapeRecognizer.cs index 687d6980..93a70ae5 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,149 @@ 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 StrokeCollection _strokeContainer; + private readonly Dictionary _strokeIdMap = new Dictionary(); + private readonly Dictionary _reverseIdMap = new Dictionary(); + private readonly object _syncLock = new object(); + + public ModernInkAnalyzer(StrokeCollection container) + { + if (!WinRtInkShapeRecognizer.IsApiAvailable) + return; + + _internalAnalyzer = new global::Windows.UI.Input.Inking.Analysis.InkAnalyzer(); + _strokeContainer = container; + _strokeContainer.StrokesChanged += OnStrokesChanged; + + // Initial sync + foreach (var stroke in _strokeContainer) + { + AddStrokeInternal(stroke); + } + } + + private void OnStrokesChanged(object sender, StrokeCollectionChangedEventArgs e) + { + if (_internalAnalyzer == null) return; + + lock (_syncLock) + { + foreach (var stroke in e.Added) + { + AddStrokeInternal(stroke); + } + + foreach (var stroke in e.Removed) + { + if (_strokeIdMap.TryGetValue(stroke, out var id)) + { + _internalAnalyzer.RemoveDataForStroke(id); + _strokeIdMap.Remove(stroke); + _reverseIdMap.Remove(id); + } + } + } + } + + 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() + { + if (_internalAnalyzer == null) + return InkShapeRecognitionResult.Empty; + + _cts?.Cancel(); + _cts = new CancellationTokenSource(); + var token = _cts.Token; + + try + { + 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() + { + if (_strokeContainer != null) + { + _strokeContainer.StrokesChanged -= OnStrokesChanged; + } + _internalAnalyzer = null; + } + } + /// 基于 Windows.UI.Input.Inking.Analysis 的形状识别(适用于 64 位进程等场景)。 internal static class WinRtInkShapeRecognizer { @@ -150,8 +294,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 +334,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 +351,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 +376,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 +390,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..e12d2299 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(inkCanvas.Strokes); + /// /// 存储新的笔画集合,用于形状识别 /// @@ -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(); + } + 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();