This commit is contained in:
PrefacedCorg
2026-04-26 12:25:43 +08:00
6 changed files with 172 additions and 58 deletions
+9 -38
View File
@@ -11,7 +11,6 @@ namespace Ink_Canvas.Helpers
private readonly object _initSync = new object(); private readonly object _initSync = new object();
private ModernInkProcessor _modernProcessor; private ModernInkProcessor _modernProcessor;
private ModernInkAnalyzer _modernAnalyzer;
private bool _isModernSystemAvailable; private bool _isModernSystemAvailable;
private bool _isInitialized; private bool _isInitialized;
@@ -41,7 +40,7 @@ namespace Ink_Canvas.Helpers
try try
{ {
// 启动阶段只做能力探测,不做 WinRT 组件实例化(避免冷启动延迟) // 启动阶段只做能力探测,不做 WinRT 组件实例化(避免冷启动延迟)
_isModernSystemAvailable = WinRtInkShapeRecognizer.IsApiAvailable && Environment.Is64BitProcess; _isModernSystemAvailable = WinRtInkShapeRecognizer.IsApiAvailable;
_isInitialized = true; _isInitialized = true;
} }
catch (Exception ex) catch (Exception ex)
@@ -63,22 +62,20 @@ namespace Ink_Canvas.Helpers
private void EnsureModernAnalyzerInitialized() private void EnsureModernAnalyzerInitialized()
{ {
if (_modernAnalyzer != null || !_isModernSystemAvailable) return; if (_modernProcessor != null || !_isModernSystemAvailable) return;
lock (_initSync) lock (_initSync)
{ {
if (_modernAnalyzer != null || !_isModernSystemAvailable) return; if (_modernProcessor != null || !_isModernSystemAvailable) return;
try try
{ {
_modernProcessor ??= new ModernInkProcessor(); _modernProcessor ??= new ModernInkProcessor();
_modernAnalyzer = new ModernInkAnalyzer();
} }
catch (Exception ex) catch (Exception ex)
{ {
LogHelper.WriteLogToFile("WinRT 墨迹模块懒加载失败: " + ex.Message, LogHelper.LogType.Warning); LogHelper.WriteLogToFile("WinRT 墨迹模块懒加载失败: " + ex.Message, LogHelper.LogType.Warning);
_isModernSystemAvailable = false; _isModernSystemAvailable = false;
_modernProcessor = null; _modernProcessor = null;
_modernAnalyzer = null;
} }
} }
} }
@@ -156,19 +153,11 @@ namespace Ink_Canvas.Helpers
return Task.FromResult(strokes); return Task.FromResult(strokes);
} }
if (!Environment.Is64BitProcess)
{
LogHelper.WriteLogToFile(
"[手写体] CorrectInkAsync 跳过:非 64 位进程,WinRT 手写体替换不可用。笔画数=" + strokes.Count,
LogHelper.LogType.Info);
return Task.FromResult(strokes);
}
EnsureModernAnalyzerInitialized(); EnsureModernAnalyzerInitialized();
if (_modernAnalyzer == null) if (_modernProcessor == null)
{ {
LogHelper.WriteLogToFile( LogHelper.WriteLogToFile(
"[手写体] CorrectInkAsync 跳过:ModernInkAnalyzer 未就绪(WinRT 初始化失败?)。笔画数=" + "[手写体] CorrectInkAsync 跳过:ModernInkProcessor 未就绪(WinRT 初始化失败?)。笔画数=" +
strokes.Count, strokes.Count,
LogHelper.LogType.Warning); LogHelper.LogType.Warning);
return Task.FromResult(strokes); return Task.FromResult(strokes);
@@ -178,7 +167,7 @@ namespace Ink_Canvas.Helpers
"[手写体] CorrectInkAsync 开始:笔画数=" + strokes.Count + "[手写体] CorrectInkAsync 开始:笔画数=" + strokes.Count +
",字体=" + (string.IsNullOrWhiteSpace(handwritingFontFamilyList) ? "(默认)" : handwritingFontFamilyList.Trim()), ",字体=" + (string.IsNullOrWhiteSpace(handwritingFontFamilyList) ? "(默认)" : handwritingFontFamilyList.Trim()),
LogHelper.LogType.Info); LogHelper.LogType.Info);
return _modernAnalyzer.AnalyzeAndCorrectAsync(strokes, handwritingFontFamilyList); return WinRtHandwritingRecognizer.ConvertRecognizedTextToHandwritingInkAsync(strokes, handwritingFontFamilyList);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -188,7 +177,7 @@ namespace Ink_Canvas.Helpers
} }
/// <summary> /// <summary>
/// WinRT 手写体识别(需 64 位进程、Windows 10+ 及系统手写识别组件)。返回分词候选与包围框,供剪贴板或插件使用。 /// WinRT 手写体识别(需 Windows 10+ 及系统手写识别组件)。返回分词候选与包围框,供剪贴板或插件使用。
/// </summary> /// </summary>
public Task<HandwritingRecognitionResult> RecognizeHandwritingAsync( public Task<HandwritingRecognitionResult> RecognizeHandwritingAsync(
StrokeCollection strokes, StrokeCollection strokes,
@@ -200,8 +189,7 @@ namespace Ink_Canvas.Helpers
try try
{ {
if (!Environment.Is64BitProcess if (!ShapeRecognitionRouter.ResolveUseWinRt(mode)
|| !ShapeRecognitionRouter.ResolveUseWinRt(mode)
|| !WinRtHandwritingRecognizer.IsApiAvailable) || !WinRtHandwritingRecognizer.IsApiAvailable)
return Task.FromResult(HandwritingRecognitionResult.Empty); return Task.FromResult(HandwritingRecognitionResult.Empty);
@@ -227,14 +215,13 @@ namespace Ink_Canvas.Helpers
public string GetSystemInfo() public string GetSystemInfo()
{ {
return _isModernSystemAvailable return _isModernSystemAvailable
? $"现代化64位墨迹识别系统 (Windows Runtime API) - 进程架构: {Environment.Is64BitProcess}" ? $"现代化墨迹识别系统 (Windows Runtime API) - 进程架构: {Environment.Is64BitProcess}"
: $"传统墨迹识别系统 (IACore) - 进程架构: {Environment.Is64BitProcess}"; : $"传统墨迹识别系统 (IACore) - 进程架构: {Environment.Is64BitProcess}";
} }
public void Dispose() public void Dispose()
{ {
_modernProcessor?.Dispose(); _modernProcessor?.Dispose();
_modernAnalyzer?.Dispose();
_isInitialized = false; _isInitialized = false;
} }
} }
@@ -256,20 +243,4 @@ namespace Ink_Canvas.Helpers
{ {
} }
} }
internal sealed class ModernInkAnalyzer : IDisposable
{
public Task<StrokeCollection> AnalyzeAndCorrectAsync(
StrokeCollection strokes,
string handwritingFontFamilyList)
{
return WinRtHandwritingRecognizer.ConvertRecognizedTextToHandwritingInkAsync(
strokes,
handwritingFontFamilyList);
}
public void Dispose()
{
}
}
} }
+1 -1
View File
@@ -117,7 +117,7 @@ namespace Ink_Canvas.Helpers
} }
} }
/// <summary>WinRT 手写识别(64 位 + Windows 10+)。</summary> /// <summary>WinRT 手写识别(Windows 10+)。</summary>
public static Task<HandwritingRecognitionResult> RecognizeHandwritingUnifiedAsync( public static Task<HandwritingRecognitionResult> RecognizeHandwritingUnifiedAsync(
StrokeCollection strokes, StrokeCollection strokes,
ShapeRecognitionEngineMode mode) => ShapeRecognitionEngineMode mode) =>
+3 -3
View File
@@ -17,13 +17,13 @@ namespace Ink_Canvas.Helpers
public static class ShapeRecognitionRouter public static class ShapeRecognitionRouter
{ {
/// <summary> /// <summary>
/// 自动模式:按当前进程位数选择——<c>64</c> 位进程用 WinRT<c>32</c> 位进程(含 x86 目标在 WOW64 下运行)用 IACore。 /// 自动模式:在 Windows 10 及以上系统默认使用 WinRT,否则使用 IACore。
/// </summary> /// </summary>
public static bool ResolveUseWinRt(ShapeRecognitionEngineMode mode) public static bool ResolveUseWinRt(ShapeRecognitionEngineMode mode)
{ {
if (mode == ShapeRecognitionEngineMode.WinRT) return true; if (mode == ShapeRecognitionEngineMode.WinRT) return true;
if (mode == ShapeRecognitionEngineMode.IACore) return false; if (mode == ShapeRecognitionEngineMode.IACore) return false;
return Environment.Is64BitProcess; return OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows10;
} }
public static bool ShouldRunShapeRecognition(bool inkToShapeEnabled, ShapeRecognitionEngineMode mode) public static bool ShouldRunShapeRecognition(bool inkToShapeEnabled, ShapeRecognitionEngineMode mode)
@@ -31,7 +31,7 @@ namespace Ink_Canvas.Helpers
if (!inkToShapeEnabled) return false; if (!inkToShapeEnabled) return false;
if (ResolveUseWinRt(mode)) if (ResolveUseWinRt(mode))
return OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows10; return OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows10;
return !Environment.Is64BitProcess; return true;
} }
public static ShapeRecognitionEngineMode FromSettingsInt(int value) public static ShapeRecognitionEngineMode FromSettingsInt(int value)
+129 -6
View File
@@ -1,6 +1,7 @@
using OSVersionExtension; using OSVersionExtension;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Ink; using System.Windows.Ink;
@@ -11,6 +12,128 @@ using WinRtInkAnalyzer = global::Windows.UI.Input.Inking.Analysis.InkAnalyzer;
namespace Ink_Canvas.Helpers 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<Stroke, uint> _strokeIdMap = new Dictionary<Stroke, uint>();
private readonly Dictionary<uint, Stroke> _reverseIdMap = new Dictionary<uint, Stroke>();
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<InkShapeRecognitionResult> 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<StrokeCollection> AnalyzeAndCorrectAsync(
StrokeCollection strokes,
string handwritingFontFamilyList)
{
return WinRtHandwritingRecognizer.ConvertRecognizedTextToHandwritingInkAsync(
strokes,
handwritingFontFamilyList);
}
public void Dispose()
{
_internalAnalyzer = null;
}
}
/// <summary>基于 Windows.UI.Input.Inking.Analysis 的形状识别(适用于 64 位进程等场景)。</summary> /// <summary>基于 Windows.UI.Input.Inking.Analysis 的形状识别(适用于 64 位进程等场景)。</summary>
internal static class WinRtInkShapeRecognizer internal static class WinRtInkShapeRecognizer
{ {
@@ -150,8 +273,8 @@ namespace Ink_Canvas.Helpers
return builder.CreateStroke(points); return builder.CreateStroke(points);
} }
private static global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing FindPrimaryDrawing( internal static global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing FindPrimaryDrawing(
WinRtInkAnalyzer analyzer) global::Windows.UI.Input.Inking.Analysis.InkAnalyzer analyzer)
{ {
global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing best = null; global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing best = null;
double bestArea = -1; double bestArea = -1;
@@ -190,7 +313,7 @@ namespace Ink_Canvas.Helpers
return w * h; 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) global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing drawing)
{ {
var src = drawing?.Points; var src = drawing?.Points;
@@ -207,7 +330,7 @@ namespace Ink_Canvas.Helpers
return arr; return arr;
} }
private static void BoundsFromPoints( internal static void BoundsFromPoints(
System.Collections.Generic.IReadOnlyList<global::Windows.Foundation.Point> points, System.Collections.Generic.IReadOnlyList<global::Windows.Foundation.Point> points,
out double w, out double w,
out double h) out double h)
@@ -232,7 +355,7 @@ namespace Ink_Canvas.Helpers
h = Math.Max(0, maxY - minY); h = Math.Max(0, maxY - minY);
} }
private static PointCollection ToWpfPointCollection( internal static PointCollection ToWpfPointCollection(
System.Collections.Generic.IReadOnlyList<global::Windows.Foundation.Point> points) System.Collections.Generic.IReadOnlyList<global::Windows.Foundation.Point> points)
{ {
var hot = new PointCollection(); var hot = new PointCollection();
@@ -246,7 +369,7 @@ namespace Ink_Canvas.Helpers
return hot; return hot;
} }
private static string MapDrawingKindToShapeName( internal static string MapDrawingKindToShapeName(
global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind kind) global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind kind)
{ {
switch (kind) switch (kind)
@@ -30,6 +30,10 @@ namespace Ink_Canvas
/// </remarks> /// </remarks>
public partial class MainWindow : Window public partial class MainWindow : Window
{ {
private Helpers.ModernInkAnalyzer _modernInkAnalyzer;
private Helpers.ModernInkAnalyzer ModernInkAnalyzer =>
_modernInkAnalyzer ??= new Helpers.ModernInkAnalyzer();
/// <summary> /// <summary>
/// 存储新的笔画集合,用于形状识别 /// 存储新的笔画集合,用于形状识别
/// </summary> /// </summary>
@@ -564,6 +568,7 @@ namespace Ink_Canvas
{ {
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
}; };
straightStroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true);
// Replace the original stroke with the straightened one // Replace the original stroke with the straightened one
SetNewBackupOfStroke(); SetNewBackupOfStroke();
@@ -617,17 +622,26 @@ namespace Ink_Canvas
ProcessRectangleGuideLines(e.Stroke); ProcessRectangleGuideLines(e.Stroke);
var shapeMode = ShapeRecognitionRouter.FromSettingsInt(Settings.InkToShape.ShapeRecognitionEngine); var shapeMode = ShapeRecognitionRouter.FromSettingsInt(Settings.InkToShape.ShapeRecognitionEngine);
var strokeReco = new StrokeCollection(); InkShapeRecognitionResult result = InkShapeRecognitionResult.Empty;
var result = await InkRecognizeHelper.RecognizeShapeUnifiedAsync(newStrokes, shapeMode);
for (var i = newStrokes.Count - 1; i >= 0; i--) if (ShapeRecognitionRouter.ResolveUseWinRt(shapeMode) && Helpers.WinRtInkShapeRecognizer.IsApiAvailable)
{ {
strokeReco.Add(newStrokes[i]); result = await ModernInkAnalyzer.AnalyzeAsync(newStrokes);
var newResult = await InkRecognizeHelper.RecognizeShapeUnifiedAsync(strokeReco, shapeMode); }
if (newResult.IsSuccess && else
(newResult.ShapeName == "Circle" || newResult.ShapeName == "Ellipse")) {
var strokeReco = new StrokeCollection();
result = await InkRecognizeHelper.RecognizeShapeUnifiedAsync(newStrokes, shapeMode);
for (var i = newStrokes.Count - 1; i >= 0; i--)
{ {
result = newResult; strokeReco.Add(newStrokes[i]);
break; 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() DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
}; };
stroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true);
circles.Add(new Circle(result.Centroid, result.ShapeWidth / 2.0, stroke)); circles.Add(new Circle(result.Centroid, result.ShapeWidth / 2.0, stroke));
SetNewBackupOfStroke(); SetNewBackupOfStroke();
_currentCommitType = CommitReason.ShapeRecognition; _currentCommitType = CommitReason.ShapeRecognition;
@@ -790,6 +805,7 @@ namespace Ink_Canvas
{ {
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
}; };
_stroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true);
var _dashedLineStroke = var _dashedLineStroke =
GenerateDashedLineEllipseStrokeCollection(iniP, endP, true, false); GenerateDashedLineEllipseStrokeCollection(iniP, endP, true, false);
var strokes = new StrokeCollection { var strokes = new StrokeCollection {
@@ -836,6 +852,7 @@ namespace Ink_Canvas
{ {
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
}; };
stroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true);
if (needRotation) if (needRotation)
{ {
@@ -883,6 +900,7 @@ namespace Ink_Canvas
{ {
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
}; };
stroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true);
SetNewBackupOfStroke(); SetNewBackupOfStroke();
_currentCommitType = CommitReason.ShapeRecognition; _currentCommitType = CommitReason.ShapeRecognition;
inkCanvas.Strokes.Remove(result.StrokesToRemove); inkCanvas.Strokes.Remove(result.StrokesToRemove);
@@ -928,6 +946,7 @@ namespace Ink_Canvas
{ {
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
}; };
stroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true);
SetNewBackupOfStroke(); SetNewBackupOfStroke();
_currentCommitType = CommitReason.ShapeRecognition; _currentCommitType = CommitReason.ShapeRecognition;
inkCanvas.Strokes.Remove(result.StrokesToRemove); inkCanvas.Strokes.Remove(result.StrokesToRemove);
@@ -2835,6 +2854,7 @@ namespace Ink_Canvas
{ {
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
}; };
rectangleStroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true);
// 移除原有的四条直线 // 移除原有的四条直线
SetNewBackupOfStroke(); SetNewBackupOfStroke();
+1 -1
View File
@@ -568,7 +568,7 @@
<value>WinRT识别转手写体字形</value> <value>WinRT识别转手写体字形</value>
</data> </data>
<data name="InkRecog_HandwritingBeautifyHint" xml:space="preserve"> <data name="InkRecog_HandwritingBeautifyHint" xml:space="preserve">
<value>#开启后,调用墨迹纠正API时:先WinRT识别手写词,再将识别成功的文字用手写风格字体(默认Ink Free/楷体等,可在设置JSON的handwritingCorrectionFontFamily调整)转成字形轮廓墨迹替换原笔画。需64位与WinRT。</value> <value>#开启后,调用墨迹纠正API时:先WinRT识别手写词,再将识别成功的文字用手写风格字体(默认Ink Free/楷体等,可在设置JSON的handwritingCorrectionFontFamily调整)转成字形轮廓墨迹替换原笔画。需WinRT。</value>
</data> </data>
<data name="InkRecog_ShapeEngine" xml:space="preserve"> <data name="InkRecog_ShapeEngine" xml:space="preserve">
<value>识别引擎</value> <value>识别引擎</value>