add:基于IPC的IACore在net6的实现
This commit is contained in:
@@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvas.PluginSdk", "InkC
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvas.Controls", "InkCanvas.Controls\InkCanvas.Controls.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvas.Controls", "InkCanvas.Controls\InkCanvas.Controls.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvasForClass.IACoreHelper", "InkCanvasForClass.IACoreHelper\InkCanvasForClass.IACoreHelper.csproj", "{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -83,6 +85,26 @@ Global
|
|||||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|Any CPU
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|Any CPU
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|Any CPU
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|Any CPU.ActiveCfg = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|Any CPU.Build.0 = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|ARM.ActiveCfg = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|ARM.Build.0 = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|ARM64.ActiveCfg = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|ARM64.Build.0 = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|x64.ActiveCfg = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|x64.Build.0 = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|Any CPU.ActiveCfg = Release|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|Any CPU.Build.0 = Release|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|ARM.ActiveCfg = Release|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|ARM.Build.0 = Release|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|ARM64.ActiveCfg = Release|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|ARM64.Build.0 = Release|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|x64.ActiveCfg = Release|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|x64.Build.0 = Release|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|x86.Build.0 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -1202,6 +1202,17 @@ namespace Ink_Canvas
|
|||||||
LogHelper.WriteLogToFile($"释放IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error);
|
LogHelper.WriteLogToFile($"释放IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("启动 IACore IPC 辅助进程");
|
||||||
|
bool ipcStarted = IpcIACoreClient.Instance.Start();
|
||||||
|
LogHelper.WriteLogToFile($"IACore IPC 辅助进程{(ipcStarted ? "启动成功" : "启动失败(将使用本地 IACore 回退)")}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"启动 IACore IPC 辅助进程时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile("开始注册.icstk文件关联");
|
LogHelper.WriteLogToFile("开始注册.icstk文件关联");
|
||||||
@@ -1577,6 +1588,13 @@ namespace Ink_Canvas
|
|||||||
private void App_Exit(object sender, ExitEventArgs e)
|
private void App_Exit(object sender, ExitEventArgs e)
|
||||||
{
|
{
|
||||||
CleanupTerminationMonitoring();
|
CleanupTerminationMonitoring();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IpcIACoreClient.Instance.Dispose();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
// 卸载所有插件
|
// 卸载所有插件
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -96,8 +96,10 @@ namespace Ink_Canvas.Helpers
|
|||||||
return RecognizeShapeWinRtOnDispatcherContext(strokes);
|
return RecognizeShapeWinRtOnDispatcherContext(strokes);
|
||||||
}
|
}
|
||||||
|
|
||||||
var legacy = InkRecognizeHelper.RecognizeShapeIACore(strokes);
|
// IACore 必须走 IPC 辅助进程(x86/.NET 4.7.2)。
|
||||||
return Task.FromResult(InkRecognizeHelper.FromIACoreOrEmpty(legacy));
|
// 在 .NET 6 x64 主进程中本地加载 IAWinFX 会失败,故不再本地回退。
|
||||||
|
var ipcResult = IpcIACoreClient.Instance.Recognize(strokes);
|
||||||
|
return Task.FromResult(ipcResult);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -214,9 +216,11 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
public string GetSystemInfo()
|
public string GetSystemInfo()
|
||||||
{
|
{
|
||||||
return _isModernSystemAvailable
|
if (_isModernSystemAvailable)
|
||||||
? $"现代化墨迹识别系统 (Windows Runtime API) - 进程架构: {Environment.Is64BitProcess}"
|
return $"现代化墨迹识别系统 (Windows Runtime API) - 进程架构: {Environment.Is64BitProcess}";
|
||||||
: $"传统墨迹识别系统 (IACore) - 进程架构: {Environment.Is64BitProcess}";
|
if (IpcIACoreClient.Instance.IsAvailable)
|
||||||
|
return $"传统墨迹识别系统 (IACore via IPC) - 进程架构: {Environment.Is64BitProcess}";
|
||||||
|
return $"传统墨迹识别系统 (IACore 本地) - 进程架构: {Environment.Is64BitProcess}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
@@ -1,79 +1,15 @@
|
|||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Ink;
|
using System.Windows.Ink;
|
||||||
using System.Windows.Media;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers
|
namespace Ink_Canvas.Helpers
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 墨迹形状/手写识别的对外门面。
|
||||||
|
/// IACore 路径通过 IPC 调用 x86 辅助进程;WinRT 路径在主进程内直接调用。
|
||||||
|
/// 主进程 (.NET 6 x64) 不再直接引用 IAWinFX 类型。
|
||||||
|
/// </summary>
|
||||||
public class InkRecognizeHelper
|
public class InkRecognizeHelper
|
||||||
{
|
{
|
||||||
/// <summary>IACore / IAWinFX 形状识别(典型用于 32 位进程)。</summary>
|
|
||||||
public static ShapeRecognizeResult RecognizeShapeIACore(StrokeCollection strokes)
|
|
||||||
{
|
|
||||||
if (strokes == null || strokes.Count == 0)
|
|
||||||
return default;
|
|
||||||
|
|
||||||
var analyzer = new InkAnalyzer();
|
|
||||||
analyzer.AddStrokes(strokes);
|
|
||||||
analyzer.SetStrokesType(strokes, StrokeType.Drawing);
|
|
||||||
|
|
||||||
AnalysisAlternate analysisAlternate = null;
|
|
||||||
int strokesCount = strokes.Count;
|
|
||||||
var sfsaf = analyzer.Analyze();
|
|
||||||
if (sfsaf.Successful)
|
|
||||||
{
|
|
||||||
var alternates = analyzer.GetAlternates();
|
|
||||||
if (alternates.Count > 0)
|
|
||||||
{
|
|
||||||
while (strokesCount >= 2)
|
|
||||||
{
|
|
||||||
var alt0 = alternates[0];
|
|
||||||
if (alt0?.AlternateNodes == null || alt0.AlternateNodes.Count == 0)
|
|
||||||
break;
|
|
||||||
var drawNode = alt0.AlternateNodes[0] as InkDrawingNode;
|
|
||||||
if (drawNode == null)
|
|
||||||
break;
|
|
||||||
var shapeOk = IsContainShapeType(drawNode.GetShapeName());
|
|
||||||
if (alt0.Strokes.Contains(strokes.Last()) && shapeOk)
|
|
||||||
break;
|
|
||||||
analyzer.RemoveStroke(strokes[strokes.Count - strokesCount]);
|
|
||||||
strokesCount--;
|
|
||||||
sfsaf = analyzer.Analyze();
|
|
||||||
if (sfsaf.Successful)
|
|
||||||
alternates = analyzer.GetAlternates();
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
if (alternates.Count == 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (alternates.Count > 0)
|
|
||||||
{
|
|
||||||
var altFinal = alternates[0];
|
|
||||||
if (altFinal?.AlternateNodes != null && altFinal.AlternateNodes.Count > 0)
|
|
||||||
analysisAlternate = altFinal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
analyzer.Dispose();
|
|
||||||
|
|
||||||
if (analysisAlternate != null && analysisAlternate.AlternateNodes != null && analysisAlternate.AlternateNodes.Count > 0)
|
|
||||||
{
|
|
||||||
var node = analysisAlternate.AlternateNodes[0] as InkDrawingNode;
|
|
||||||
if (node == null)
|
|
||||||
return default;
|
|
||||||
return new ShapeRecognizeResult(node.Centroid, node.HotPoints, analysisAlternate, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>兼容旧调用:等价于 <see cref="RecognizeShapeIACore"/>。</summary>
|
|
||||||
public static ShapeRecognizeResult RecognizeShape(StrokeCollection strokes) =>
|
|
||||||
RecognizeShapeIACore(strokes);
|
|
||||||
|
|
||||||
/// <summary>按设置选择 WinRT(<see cref="InkRecognitionManager"/>)或 IACore;WinRT 请用 <see cref="RecognizeShapeUnifiedAsync"/>。</summary>
|
|
||||||
public static InkShapeRecognitionResult RecognizeShapeUnified(
|
public static InkShapeRecognitionResult RecognizeShapeUnified(
|
||||||
StrokeCollection strokes,
|
StrokeCollection strokes,
|
||||||
ShapeRecognitionEngineMode mode)
|
ShapeRecognitionEngineMode mode)
|
||||||
@@ -84,11 +20,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
if (ShapeRecognitionRouter.ResolveUseWinRt(mode))
|
if (ShapeRecognitionRouter.ResolveUseWinRt(mode))
|
||||||
return InkShapeRecognitionResult.Empty;
|
return InkShapeRecognitionResult.Empty;
|
||||||
|
|
||||||
var legacy = RecognizeShapeIACore(strokes);
|
return IpcIACoreClient.Instance.Recognize(strokes);
|
||||||
return FromIACoreOrEmpty(legacy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>与 CE 反编译版 <c>InkRecognitionManager.RecognizeShapeAsync</c> 对齐的统一入口。</summary>
|
|
||||||
public static Task<InkShapeRecognitionResult> RecognizeShapeUnifiedAsync(
|
public static Task<InkShapeRecognitionResult> RecognizeShapeUnifiedAsync(
|
||||||
StrokeCollection strokes,
|
StrokeCollection strokes,
|
||||||
ShapeRecognitionEngineMode mode)
|
ShapeRecognitionEngineMode mode)
|
||||||
@@ -109,7 +43,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
WinRtHandwritingRecognizer.Warmup();
|
WinRtHandwritingRecognizer.Warmup();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
RecognizeShapeIACore(new StrokeCollection());
|
{
|
||||||
|
IpcIACoreClient.Instance.Start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -117,13 +53,11 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>WinRT 手写识别(Windows 10+)。</summary>
|
|
||||||
public static Task<HandwritingRecognitionResult> RecognizeHandwritingUnifiedAsync(
|
public static Task<HandwritingRecognitionResult> RecognizeHandwritingUnifiedAsync(
|
||||||
StrokeCollection strokes,
|
StrokeCollection strokes,
|
||||||
ShapeRecognitionEngineMode mode) =>
|
ShapeRecognitionEngineMode mode) =>
|
||||||
InkRecognitionManager.Instance.RecognizeHandwritingAsync(strokes, mode);
|
InkRecognitionManager.Instance.RecognizeHandwritingAsync(strokes, mode);
|
||||||
|
|
||||||
/// <summary>WinRT 下将识别成功的词替换为手写体字形墨迹;是否应用由设置「WinRT 识别转手写体字形」控制。</summary>
|
|
||||||
public static Task<StrokeCollection> CorrectHandwritingStrokesUnifiedAsync(
|
public static Task<StrokeCollection> CorrectHandwritingStrokesUnifiedAsync(
|
||||||
StrokeCollection strokes,
|
StrokeCollection strokes,
|
||||||
ShapeRecognitionEngineMode mode) =>
|
ShapeRecognitionEngineMode mode) =>
|
||||||
@@ -133,7 +67,6 @@ namespace Ink_Canvas.Helpers
|
|||||||
MainWindow.Settings?.InkToShape?.EnableWinRtHandwritingStrokeBeautify ?? false,
|
MainWindow.Settings?.InkToShape?.EnableWinRtHandwritingStrokeBeautify ?? false,
|
||||||
MainWindow.Settings?.InkToShape?.HandwritingCorrectionFontFamily);
|
MainWindow.Settings?.InkToShape?.HandwritingCorrectionFontFamily);
|
||||||
|
|
||||||
/// <summary>显式指定是否应用手写体字形替换(忽略开关);字体仍从设置读取。</summary>
|
|
||||||
public static Task<StrokeCollection> CorrectHandwritingStrokesUnifiedAsync(
|
public static Task<StrokeCollection> CorrectHandwritingStrokesUnifiedAsync(
|
||||||
StrokeCollection strokes,
|
StrokeCollection strokes,
|
||||||
ShapeRecognitionEngineMode mode,
|
ShapeRecognitionEngineMode mode,
|
||||||
@@ -144,53 +77,18 @@ namespace Ink_Canvas.Helpers
|
|||||||
applyHandwritingBeautify,
|
applyHandwritingBeautify,
|
||||||
MainWindow.Settings?.InkToShape?.HandwritingCorrectionFontFamily);
|
MainWindow.Settings?.InkToShape?.HandwritingCorrectionFontFamily);
|
||||||
|
|
||||||
internal static InkShapeRecognitionResult FromIACoreOrEmpty(ShapeRecognizeResult legacy)
|
|
||||||
{
|
|
||||||
if (legacy?.InkDrawingNode == null)
|
|
||||||
return InkShapeRecognitionResult.Empty;
|
|
||||||
|
|
||||||
var node = legacy.InkDrawingNode;
|
|
||||||
var shape = node.GetShape();
|
|
||||||
if (shape == null)
|
|
||||||
return InkShapeRecognitionResult.Empty;
|
|
||||||
|
|
||||||
var hot = ClonePointCollection(node.HotPoints);
|
|
||||||
return new InkShapeRecognitionResult(
|
|
||||||
node.GetShapeName(),
|
|
||||||
legacy.Centroid,
|
|
||||||
hot,
|
|
||||||
shape.Width,
|
|
||||||
shape.Height,
|
|
||||||
node.Strokes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static PointCollection ClonePointCollection(PointCollection src)
|
|
||||||
{
|
|
||||||
var dst = new PointCollection();
|
|
||||||
if (src == null) return dst;
|
|
||||||
foreach (System.Windows.Point p in src)
|
|
||||||
dst.Add(p);
|
|
||||||
return dst;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsContainShapeType(string name)
|
public static bool IsContainShapeType(string name)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(name))
|
if (string.IsNullOrEmpty(name))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (name.Contains("Triangle") || name.Contains("Circle") ||
|
return name.Contains("Triangle") || name.Contains("Circle") ||
|
||||||
name.Contains("Rectangle") || name.Contains("Diamond") ||
|
name.Contains("Rectangle") || name.Contains("Diamond") ||
|
||||||
name.Contains("Parallelogram") || name.Contains("Square")
|
name.Contains("Parallelogram") || name.Contains("Square") ||
|
||||||
|| name.Contains("Ellipse"))
|
name.Contains("Ellipse");
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Recognizer 的实现
|
|
||||||
|
|
||||||
public enum RecognizeLanguage
|
public enum RecognizeLanguage
|
||||||
{
|
{
|
||||||
SimplifiedChinese = 0x0804,
|
SimplifiedChinese = 0x0804,
|
||||||
@@ -198,127 +96,17 @@ namespace Ink_Canvas.Helpers
|
|||||||
English = 0x0809
|
English = 0x0809
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ShapeRecognizeResult
|
|
||||||
{
|
|
||||||
public ShapeRecognizeResult(Point centroid, PointCollection hotPoints, AnalysisAlternate analysisAlternate, InkDrawingNode node)
|
|
||||||
{
|
|
||||||
Centroid = centroid;
|
|
||||||
HotPoints = hotPoints;
|
|
||||||
AnalysisAlternate = analysisAlternate;
|
|
||||||
InkDrawingNode = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AnalysisAlternate AnalysisAlternate { get; }
|
|
||||||
|
|
||||||
public Point Centroid { get; set; }
|
|
||||||
|
|
||||||
public PointCollection HotPoints { get; }
|
|
||||||
|
|
||||||
public InkDrawingNode InkDrawingNode { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 图形识别类
|
|
||||||
/// </summary>
|
|
||||||
//public class ShapeRecogniser
|
|
||||||
//{
|
|
||||||
// public InkAnalyzer _inkAnalyzer = null;
|
|
||||||
|
|
||||||
// private ShapeRecogniser()
|
|
||||||
// {
|
|
||||||
// this._inkAnalyzer = new InkAnalyzer
|
|
||||||
// {
|
|
||||||
// AnalysisModes = AnalysisModes.AutomaticReconciliationEnabled
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// 根据笔迹集合返回图形名称字符串
|
|
||||||
// /// </summary>
|
|
||||||
// /// <param name="strokeCollection"></param>
|
|
||||||
// /// <returns></returns>
|
|
||||||
// public InkDrawingNode Recognition(StrokeCollection strokeCollection)
|
|
||||||
// {
|
|
||||||
// if (strokeCollection == null)
|
|
||||||
// {
|
|
||||||
// //MessageBox.Show("dddddd");
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// InkDrawingNode result = null;
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// this._inkAnalyzer.AddStrokes(strokeCollection);
|
|
||||||
// if (this._inkAnalyzer.Analyze().Successful)
|
|
||||||
// {
|
|
||||||
// result = _internalAnalyzer(this._inkAnalyzer);
|
|
||||||
// this._inkAnalyzer.RemoveStrokes(strokeCollection);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// catch (System.Exception ex)
|
|
||||||
// {
|
|
||||||
// //result = ex.Message;
|
|
||||||
// System.Diagnostics.Debug.WriteLine(ex.Message);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return result;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// 实现笔迹的分析,返回图形对应的字符串
|
|
||||||
// /// 你在实际的应用中根据返回的字符串来生成对应的Shape
|
|
||||||
// /// </summary>
|
|
||||||
// /// <param name="ink"></param>
|
|
||||||
// /// <returns></returns>
|
|
||||||
// private InkDrawingNode _internalAnalyzer(InkAnalyzer ink)
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// ContextNodeCollection nodecollections = ink.FindNodesOfType(ContextNodeType.InkDrawing);
|
|
||||||
// foreach (ContextNode node in nodecollections)
|
|
||||||
// {
|
|
||||||
// InkDrawingNode drawingNode = node as InkDrawingNode;
|
|
||||||
// if (drawingNode != null)
|
|
||||||
// {
|
|
||||||
// return drawingNode;//.GetShapeName();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// catch (System.Exception ex)
|
|
||||||
// {
|
|
||||||
// System.Diagnostics.Debug.WriteLine(ex.Message);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// private static ShapeRecogniser instance = null;
|
|
||||||
// public static ShapeRecogniser Instance
|
|
||||||
// {
|
|
||||||
// get
|
|
||||||
// {
|
|
||||||
// return instance == null ? (instance = new ShapeRecogniser()) : instance;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
//用于自动控制其他形状相对于圆的位置
|
|
||||||
|
|
||||||
public class Circle
|
public class Circle
|
||||||
{
|
{
|
||||||
public Circle(Point centroid, double r, Stroke stroke)
|
public Circle(System.Windows.Point centroid, double r, Stroke stroke)
|
||||||
{
|
{
|
||||||
Centroid = centroid;
|
Centroid = centroid;
|
||||||
R = r;
|
R = r;
|
||||||
Stroke = stroke;
|
Stroke = stroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Point Centroid { get; set; }
|
public System.Windows.Point Centroid { get; set; }
|
||||||
|
|
||||||
public double R { get; set; }
|
public double R { get; set; }
|
||||||
|
|
||||||
public Stroke Stroke { get; set; }
|
public Stroke Stroke { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,302 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Ink;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Helpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 通过 Named Pipe IPC 调用 32 位辅助进程 InkCanvasForClass.IACoreHelper.exe 进行 IACore 形状识别。
|
||||||
|
/// 单例,线程安全,辅助进程崩溃后自动重启。
|
||||||
|
/// </summary>
|
||||||
|
public sealed class IpcIACoreClient : IDisposable
|
||||||
|
{
|
||||||
|
// ── 单例 ──────────────────────────────────────────────────────────────
|
||||||
|
private static IpcIACoreClient _instance;
|
||||||
|
private static readonly object _instanceLock = new object();
|
||||||
|
|
||||||
|
public static IpcIACoreClient Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_instance == null)
|
||||||
|
lock (_instanceLock)
|
||||||
|
if (_instance == null)
|
||||||
|
_instance = new IpcIACoreClient();
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 状态 ──────────────────────────────────────────────────────────────
|
||||||
|
private Process _helperProcess;
|
||||||
|
private readonly object _pipeLock = new object();
|
||||||
|
private bool _disposed;
|
||||||
|
private bool _available;
|
||||||
|
|
||||||
|
// 辅助进程 EXE 的路径(与主 EXE 同目录)
|
||||||
|
private static string HelperExePath =>
|
||||||
|
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "InkCanvasForClass.IACoreHelper.exe");
|
||||||
|
|
||||||
|
// Named Pipe 名称(含主进程 PID,保证唯一)
|
||||||
|
private string PipeName =>
|
||||||
|
string.Format("ICC_IACoreHelper_{0}", Process.GetCurrentProcess().Id);
|
||||||
|
|
||||||
|
private IpcIACoreClient() { }
|
||||||
|
|
||||||
|
// ── 公开 API ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>启动辅助进程,返回是否成功。</summary>
|
||||||
|
public bool Start()
|
||||||
|
{
|
||||||
|
if (_disposed) return false;
|
||||||
|
|
||||||
|
// 已运行则无需重启,避免 Warmup + App 两处调用导致重复启动
|
||||||
|
if (IsAvailable) return true;
|
||||||
|
|
||||||
|
if (!File.Exists(HelperExePath))
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"[IACoreIPC] 辅助进程不存在: {HelperExePath}", LogHelper.LogType.Warning);
|
||||||
|
_available = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LaunchHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>是否就绪(辅助进程运行中)。</summary>
|
||||||
|
public bool IsAvailable => _available && _helperProcess != null && !_helperProcess.HasExited;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送笔画到辅助进程进行 IACore 形状识别。
|
||||||
|
/// 超时或失败时返回 <see cref="InkShapeRecognitionResult.Empty"/>。
|
||||||
|
/// </summary>
|
||||||
|
public InkShapeRecognitionResult Recognize(StrokeCollection strokes)
|
||||||
|
{
|
||||||
|
if (strokes == null || strokes.Count == 0)
|
||||||
|
return InkShapeRecognitionResult.Empty;
|
||||||
|
|
||||||
|
EnsureHelperAlive();
|
||||||
|
if (!IsAvailable)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"[IACoreIPC] Recognize 跳过:辅助进程不可用(strokes={strokes.Count})", LogHelper.LogType.Warning);
|
||||||
|
return InkShapeRecognitionResult.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_pipeLock)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"[IACoreIPC] Recognize 请求:strokes={strokes.Count}");
|
||||||
|
var result = SendRecognizeRequest(strokes);
|
||||||
|
LogHelper.WriteLogToFile(
|
||||||
|
$"[IACoreIPC] Recognize 响应:IsSuccess={result.IsSuccess}, Shape={result.ShapeName}, StrokesToRemove={result.StrokesToRemove?.Count ?? 0}");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"[IACoreIPC] 识别失败: {ex.GetType().Name}: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
KillHelper();
|
||||||
|
return InkShapeRecognitionResult.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 内部实现 ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private bool LaunchHelper()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
KillHelper();
|
||||||
|
|
||||||
|
var psi = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = HelperExePath,
|
||||||
|
Arguments = Process.GetCurrentProcess().Id.ToString(),
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory
|
||||||
|
};
|
||||||
|
_helperProcess = Process.Start(psi);
|
||||||
|
if (_helperProcess == null)
|
||||||
|
{
|
||||||
|
_available = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_helperProcess.EnableRaisingEvents = true;
|
||||||
|
_helperProcess.Exited += OnHelperExited;
|
||||||
|
|
||||||
|
// 等待 Pipe 就绪(最多 3 s)
|
||||||
|
bool pipeReady = WaitForPipe(3000);
|
||||||
|
_available = pipeReady;
|
||||||
|
|
||||||
|
LogHelper.WriteLogToFile($"[IACoreIPC] 辅助进程启动{(pipeReady ? "成功" : "失败(Pipe 未就绪)")},PID={_helperProcess?.Id}");
|
||||||
|
return pipeReady;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"[IACoreIPC] 启动辅助进程失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
_available = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool WaitForPipe(int timeoutMs)
|
||||||
|
{
|
||||||
|
int elapsed = 0;
|
||||||
|
while (elapsed < timeoutMs)
|
||||||
|
{
|
||||||
|
if (_helperProcess == null || _helperProcess.HasExited)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var probe = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut))
|
||||||
|
{
|
||||||
|
probe.Connect(200);
|
||||||
|
// 连接成功后立即断开,下次 Recognize 会重新连接
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Thread.Sleep(100);
|
||||||
|
elapsed += 300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private InkShapeRecognitionResult SendRecognizeRequest(StrokeCollection strokes)
|
||||||
|
{
|
||||||
|
using (var client = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut))
|
||||||
|
{
|
||||||
|
client.Connect(IpcTimeoutMs);
|
||||||
|
|
||||||
|
using (var writer = new BinaryWriter(client, System.Text.Encoding.UTF8, leaveOpen: true))
|
||||||
|
using (var reader = new BinaryReader(client, System.Text.Encoding.UTF8, leaveOpen: true))
|
||||||
|
{
|
||||||
|
// 序列化请求
|
||||||
|
writer.Write(CmdRecognize);
|
||||||
|
writer.Write(strokes.Count);
|
||||||
|
foreach (var stroke in strokes)
|
||||||
|
{
|
||||||
|
var pts = stroke.StylusPoints;
|
||||||
|
writer.Write(pts.Count);
|
||||||
|
foreach (var pt in pts)
|
||||||
|
{
|
||||||
|
writer.Write((float)pt.X);
|
||||||
|
writer.Write((float)pt.Y);
|
||||||
|
writer.Write(pt.PressureFactor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.Flush();
|
||||||
|
|
||||||
|
// 读取响应
|
||||||
|
bool success = reader.ReadBoolean();
|
||||||
|
string shape = reader.ReadString();
|
||||||
|
float cx = reader.ReadSingle();
|
||||||
|
float cy = reader.ReadSingle();
|
||||||
|
float width = reader.ReadSingle();
|
||||||
|
float height = reader.ReadSingle();
|
||||||
|
|
||||||
|
int hotLen = reader.ReadInt32();
|
||||||
|
var hotPoints = new PointCollection();
|
||||||
|
for (int i = 0; i < hotLen; i++)
|
||||||
|
hotPoints.Add(new Point(reader.ReadSingle(), reader.ReadSingle()));
|
||||||
|
|
||||||
|
int idxLen = reader.ReadInt32();
|
||||||
|
var indices = new int[idxLen];
|
||||||
|
for (int i = 0; i < idxLen; i++)
|
||||||
|
indices[i] = reader.ReadInt32();
|
||||||
|
|
||||||
|
if (!success || string.IsNullOrEmpty(shape))
|
||||||
|
return InkShapeRecognitionResult.Empty;
|
||||||
|
|
||||||
|
// 根据下标还原参与识别的笔画子集
|
||||||
|
var recognized = new StrokeCollection();
|
||||||
|
foreach (int idx in indices)
|
||||||
|
if (idx >= 0 && idx < strokes.Count)
|
||||||
|
recognized.Add(strokes[idx]);
|
||||||
|
|
||||||
|
return new InkShapeRecognitionResult(
|
||||||
|
shape,
|
||||||
|
new Point(cx, cy),
|
||||||
|
hotPoints,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
recognized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureHelperAlive()
|
||||||
|
{
|
||||||
|
if (!IsAvailable)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("[IACoreIPC] 辅助进程不可用,尝试重启...", LogHelper.LogType.Warning);
|
||||||
|
LaunchHelper();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnHelperExited(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_available = false;
|
||||||
|
LogHelper.WriteLogToFile("[IACoreIPC] 辅助进程意外退出", LogHelper.LogType.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void KillHelper()
|
||||||
|
{
|
||||||
|
if (_helperProcess == null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 先解除事件订阅,避免主动 Kill 时触发 OnHelperExited 误报
|
||||||
|
try { _helperProcess.Exited -= OnHelperExited; } catch { }
|
||||||
|
|
||||||
|
if (!_helperProcess.HasExited)
|
||||||
|
{
|
||||||
|
// 发送优雅关闭命令
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var client = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut))
|
||||||
|
{
|
||||||
|
client.Connect(500);
|
||||||
|
using (var w = new BinaryWriter(client))
|
||||||
|
w.Write(CmdShutdown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { /* 忽略,下面直接 Kill */ }
|
||||||
|
|
||||||
|
if (!_helperProcess.WaitForExit(800))
|
||||||
|
_helperProcess.Kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_helperProcess?.Dispose();
|
||||||
|
_helperProcess = null;
|
||||||
|
_available = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed) return;
|
||||||
|
_disposed = true;
|
||||||
|
KillHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 协议常量(与 IACoreHelper/IpcProtocol.cs 保持一致) ─────────────
|
||||||
|
private const int IpcTimeoutMs = 5000;
|
||||||
|
private const byte CmdRecognize = 0x01;
|
||||||
|
private const byte CmdShutdown = 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,20 +101,6 @@
|
|||||||
<PlatformTarget>x64</PlatformTarget>
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
<Prefer32Bit>false</Prefer32Bit>
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="IACore">
|
|
||||||
<HintPath>.\IACore.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="IALoader">
|
|
||||||
<HintPath>.\IALoader.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="IAWinFX">
|
|
||||||
<HintPath>.\IAWinFX.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="app.manifest" />
|
<None Include="app.manifest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -153,6 +139,10 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\InkCanvas.PluginSdk\InkCanvas.PluginSdk.csproj" />
|
<ProjectReference Include="..\InkCanvas.PluginSdk\InkCanvas.PluginSdk.csproj" />
|
||||||
<ProjectReference Include="..\InkCanvas.Controls\InkCanvas.Controls.csproj" />
|
<ProjectReference Include="..\InkCanvas.Controls\InkCanvas.Controls.csproj" />
|
||||||
|
<ProjectReference Include="..\InkCanvasForClass.IACoreHelper\InkCanvasForClass.IACoreHelper.csproj">
|
||||||
|
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||||
|
<Private>false</Private>
|
||||||
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Condition="'$(MSBuildRuntimeType)' == 'Full'">
|
<ItemGroup Condition="'$(MSBuildRuntimeType)' == 'Full'">
|
||||||
<COMReference Include="IWshRuntimeLibrary">
|
<COMReference Include="IWshRuntimeLibrary">
|
||||||
@@ -668,4 +658,11 @@
|
|||||||
<Target Name="CleanTelemetryDsn" AfterTargets="Build;Clean" Condition="Exists('$(MSBuildProjectDirectory)\telemetry_dsn.generated.txt')">
|
<Target Name="CleanTelemetryDsn" AfterTargets="Build;Clean" Condition="Exists('$(MSBuildProjectDirectory)\telemetry_dsn.generated.txt')">
|
||||||
<Delete Files="$(MSBuildProjectDirectory)\telemetry_dsn.generated.txt" />
|
<Delete Files="$(MSBuildProjectDirectory)\telemetry_dsn.generated.txt" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
|
<Target Name="CopyIACoreHelper" AfterTargets="Build">
|
||||||
|
<ItemGroup>
|
||||||
|
<IACoreHelperFiles Include="$(MSBuildProjectDirectory)\..\InkCanvasForClass.IACoreHelper\bin\$(Configuration)\*.*" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Copy SourceFiles="@(IACoreHelperFiles)" DestinationFolder="$(OutDir)" SkipUnchangedFiles="true" Condition="'@(IACoreHelperFiles)' != ''" />
|
||||||
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<startup useLegacyV2RuntimeActivationPolicy="true">
|
||||||
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
|
||||||
|
</startup>
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||||
|
<ProjectGuid>{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}</ProjectGuid>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<RootNamespace>InkCanvasForClass.IACoreHelper</RootNamespace>
|
||||||
|
<AssemblyName>InkCanvasForClass.IACoreHelper</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
|
||||||
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
|
||||||
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="PresentationCore" />
|
||||||
|
<Reference Include="PresentationFramework" />
|
||||||
|
<Reference Include="WindowsBase" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
<Reference Include="IAWinFX">
|
||||||
|
<HintPath>..\Ink Canvas\Resources\IACore\IAWinFX.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="IpcProtocol.cs" />
|
||||||
|
<Compile Include="Program.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="App.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace InkCanvasForClass.IACoreHelper
|
||||||
|
{
|
||||||
|
// Named Pipe 名称,主进程和辅助进程共用
|
||||||
|
internal static class IpcConstants
|
||||||
|
{
|
||||||
|
public const string PipeName = "ICC_IACoreHelper_{0}"; // {0} = 主进程 PID
|
||||||
|
public const int RequestTimeout = 5000; // ms
|
||||||
|
public const byte CmdRecognize = 0x01;
|
||||||
|
public const byte CmdShutdown = 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单个 StylusPoint 的轻量传输结构
|
||||||
|
internal struct StylusPointDto
|
||||||
|
{
|
||||||
|
public float X;
|
||||||
|
public float Y;
|
||||||
|
public float Pressure;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单条笔画
|
||||||
|
internal class StrokeDto
|
||||||
|
{
|
||||||
|
public StylusPointDto[] Points;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 识别请求(主进程 → 辅助进程)
|
||||||
|
internal class RecognizeRequest
|
||||||
|
{
|
||||||
|
public StrokeDto[] Strokes;
|
||||||
|
|
||||||
|
public void WriteTo(BinaryWriter w)
|
||||||
|
{
|
||||||
|
w.Write(IpcConstants.CmdRecognize);
|
||||||
|
w.Write(Strokes.Length);
|
||||||
|
foreach (var stroke in Strokes)
|
||||||
|
{
|
||||||
|
w.Write(stroke.Points.Length);
|
||||||
|
foreach (var pt in stroke.Points)
|
||||||
|
{
|
||||||
|
w.Write(pt.X);
|
||||||
|
w.Write(pt.Y);
|
||||||
|
w.Write(pt.Pressure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RecognizeRequest ReadFrom(BinaryReader r)
|
||||||
|
{
|
||||||
|
int strokeCount = r.ReadInt32();
|
||||||
|
var strokes = new StrokeDto[strokeCount];
|
||||||
|
for (int i = 0; i < strokeCount; i++)
|
||||||
|
{
|
||||||
|
int ptCount = r.ReadInt32();
|
||||||
|
var pts = new StylusPointDto[ptCount];
|
||||||
|
for (int j = 0; j < ptCount; j++)
|
||||||
|
pts[j] = new StylusPointDto { X = r.ReadSingle(), Y = r.ReadSingle(), Pressure = r.ReadSingle() };
|
||||||
|
strokes[i] = new StrokeDto { Points = pts };
|
||||||
|
}
|
||||||
|
return new RecognizeRequest { Strokes = strokes };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 识别响应(辅助进程 → 主进程)
|
||||||
|
internal class RecognizeResponse
|
||||||
|
{
|
||||||
|
public bool Success;
|
||||||
|
public string ShapeName; // e.g. "Circle", "Rectangle", "Triangle" ...
|
||||||
|
public float CentroidX;
|
||||||
|
public float CentroidY;
|
||||||
|
public float ShapeWidth;
|
||||||
|
public float ShapeHeight;
|
||||||
|
public float[] HotPointsX;
|
||||||
|
public float[] HotPointsY;
|
||||||
|
public int[] StrokeIndices; // 参与识别的笔画在原始数组中的下标
|
||||||
|
|
||||||
|
public void WriteTo(BinaryWriter w)
|
||||||
|
{
|
||||||
|
w.Write(Success);
|
||||||
|
w.Write(ShapeName ?? string.Empty);
|
||||||
|
w.Write(CentroidX);
|
||||||
|
w.Write(CentroidY);
|
||||||
|
w.Write(ShapeWidth);
|
||||||
|
w.Write(ShapeHeight);
|
||||||
|
|
||||||
|
int hotLen = HotPointsX != null ? HotPointsX.Length : 0;
|
||||||
|
w.Write(hotLen);
|
||||||
|
for (int i = 0; i < hotLen; i++)
|
||||||
|
{
|
||||||
|
w.Write(HotPointsX[i]);
|
||||||
|
w.Write(HotPointsY[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
int idxLen = StrokeIndices != null ? StrokeIndices.Length : 0;
|
||||||
|
w.Write(idxLen);
|
||||||
|
for (int i = 0; i < idxLen; i++)
|
||||||
|
w.Write(StrokeIndices[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RecognizeResponse ReadFrom(BinaryReader r)
|
||||||
|
{
|
||||||
|
var resp = new RecognizeResponse
|
||||||
|
{
|
||||||
|
Success = r.ReadBoolean(),
|
||||||
|
ShapeName = r.ReadString(),
|
||||||
|
CentroidX = r.ReadSingle(),
|
||||||
|
CentroidY = r.ReadSingle(),
|
||||||
|
ShapeWidth = r.ReadSingle(),
|
||||||
|
ShapeHeight = r.ReadSingle()
|
||||||
|
};
|
||||||
|
|
||||||
|
int hotLen = r.ReadInt32();
|
||||||
|
resp.HotPointsX = new float[hotLen];
|
||||||
|
resp.HotPointsY = new float[hotLen];
|
||||||
|
for (int i = 0; i < hotLen; i++)
|
||||||
|
{
|
||||||
|
resp.HotPointsX[i] = r.ReadSingle();
|
||||||
|
resp.HotPointsY[i] = r.ReadSingle();
|
||||||
|
}
|
||||||
|
|
||||||
|
int idxLen = r.ReadInt32();
|
||||||
|
resp.StrokeIndices = new int[idxLen];
|
||||||
|
for (int i = 0; i < idxLen; i++)
|
||||||
|
resp.StrokeIndices[i] = r.ReadInt32();
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Ink;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace InkCanvasForClass.IACoreHelper
|
||||||
|
{
|
||||||
|
internal static class Program
|
||||||
|
{
|
||||||
|
private static readonly string LogPath = Path.Combine(
|
||||||
|
AppDomain.CurrentDomain.BaseDirectory, "IACoreHelper.log");
|
||||||
|
|
||||||
|
private static void Log(string msg)
|
||||||
|
{
|
||||||
|
try { File.AppendAllText(LogPath, $"{DateTime.Now:HH:mm:ss.fff} {msg}{Environment.NewLine}"); }
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
[STAThread]
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
Log($"=== Helper started, args=[{string.Join(",", args)}], cwd={Environment.CurrentDirectory}, baseDir={AppDomain.CurrentDomain.BaseDirectory} ===");
|
||||||
|
|
||||||
|
if (args.Length < 1 || !int.TryParse(args[0], out int parentPid))
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("Usage: IACoreHelper.exe <parentPid>");
|
||||||
|
Log("Missing parentPid argument, exiting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string pipeName = string.Format(IpcConstants.PipeName, parentPid);
|
||||||
|
Log($"Pipe name: {pipeName}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RunPipeServer(pipeName);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log($"FATAL: {ex}");
|
||||||
|
Console.Error.WriteLine("IACoreHelper fatal: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RunPipeServer(string pipeName)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
using (var server = new NamedPipeServerStream(
|
||||||
|
pipeName,
|
||||||
|
PipeDirection.InOut,
|
||||||
|
1,
|
||||||
|
PipeTransmissionMode.Byte,
|
||||||
|
PipeOptions.WriteThrough))
|
||||||
|
{
|
||||||
|
server.WaitForConnection();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var reader = new BinaryReader(server, System.Text.Encoding.UTF8, leaveOpen: true))
|
||||||
|
using (var writer = new BinaryWriter(server, System.Text.Encoding.UTF8, leaveOpen: true))
|
||||||
|
{
|
||||||
|
byte cmd = reader.ReadByte();
|
||||||
|
|
||||||
|
if (cmd == IpcConstants.CmdShutdown)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (cmd == IpcConstants.CmdRecognize)
|
||||||
|
{
|
||||||
|
var request = RecognizeRequest.ReadFrom(reader);
|
||||||
|
var response = HandleRecognize(request);
|
||||||
|
response.WriteTo(writer);
|
||||||
|
writer.Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("IACoreHelper pipe error: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 每次连接后重新监听,支持多次调用
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RecognizeResponse HandleRecognize(RecognizeRequest request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Log($"HandleRecognize: strokes={request.Strokes?.Length ?? 0}");
|
||||||
|
var strokes = BuildStrokeCollection(request);
|
||||||
|
Log($"Built StrokeCollection: count={strokes.Count}");
|
||||||
|
if (strokes.Count == 0)
|
||||||
|
return new RecognizeResponse { Success = false, ShapeName = string.Empty };
|
||||||
|
|
||||||
|
var result = RecognizeCore(strokes);
|
||||||
|
Log($"RecognizeCore result: success={result.Success}, shape={result.ShapeName}");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log($"HandleRecognize EXCEPTION: {ex}");
|
||||||
|
Console.Error.WriteLine("IACoreHelper recognize error: " + ex.Message);
|
||||||
|
return new RecognizeResponse { Success = false, ShapeName = string.Empty };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StrokeCollection BuildStrokeCollection(RecognizeRequest request)
|
||||||
|
{
|
||||||
|
var sc = new StrokeCollection();
|
||||||
|
foreach (var strokeDto in request.Strokes)
|
||||||
|
{
|
||||||
|
if (strokeDto.Points == null || strokeDto.Points.Length == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var stylusPoints = new StylusPointCollection();
|
||||||
|
foreach (var pt in strokeDto.Points)
|
||||||
|
stylusPoints.Add(new StylusPoint(pt.X, pt.Y, pt.Pressure));
|
||||||
|
|
||||||
|
sc.Add(new Stroke(stylusPoints));
|
||||||
|
}
|
||||||
|
return sc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RecognizeResponse RecognizeCore(StrokeCollection strokes)
|
||||||
|
{
|
||||||
|
var analyzer = new InkAnalyzer();
|
||||||
|
analyzer.AddStrokes(strokes);
|
||||||
|
analyzer.SetStrokesType(strokes, StrokeType.Drawing);
|
||||||
|
|
||||||
|
AnalysisAlternate analysisAlternate = null;
|
||||||
|
int strokesCount = strokes.Count;
|
||||||
|
var analysisStatus = analyzer.Analyze();
|
||||||
|
|
||||||
|
if (analysisStatus.Successful)
|
||||||
|
{
|
||||||
|
var alternates = analyzer.GetAlternates();
|
||||||
|
if (alternates.Count > 0)
|
||||||
|
{
|
||||||
|
while (strokesCount >= 2)
|
||||||
|
{
|
||||||
|
var alt0 = alternates[0];
|
||||||
|
if (alt0?.AlternateNodes == null || alt0.AlternateNodes.Count == 0)
|
||||||
|
break;
|
||||||
|
var drawNode = alt0.AlternateNodes[0] as InkDrawingNode;
|
||||||
|
if (drawNode == null)
|
||||||
|
break;
|
||||||
|
bool shapeOk = IsContainShapeType(drawNode.GetShapeName());
|
||||||
|
if (alt0.Strokes.Contains(strokes.Last()) && shapeOk)
|
||||||
|
break;
|
||||||
|
analyzer.RemoveStroke(strokes[strokes.Count - strokesCount]);
|
||||||
|
strokesCount--;
|
||||||
|
analysisStatus = analyzer.Analyze();
|
||||||
|
if (analysisStatus.Successful)
|
||||||
|
alternates = analyzer.GetAlternates();
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
if (alternates.Count == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alternates.Count > 0)
|
||||||
|
{
|
||||||
|
var altFinal = alternates[0];
|
||||||
|
if (altFinal?.AlternateNodes != null && altFinal.AlternateNodes.Count > 0)
|
||||||
|
analysisAlternate = altFinal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
analyzer.Dispose();
|
||||||
|
|
||||||
|
if (analysisAlternate?.AlternateNodes == null || analysisAlternate.AlternateNodes.Count == 0)
|
||||||
|
return new RecognizeResponse { Success = false, ShapeName = string.Empty };
|
||||||
|
|
||||||
|
var node = analysisAlternate.AlternateNodes[0] as InkDrawingNode;
|
||||||
|
if (node == null)
|
||||||
|
return new RecognizeResponse { Success = false, ShapeName = string.Empty };
|
||||||
|
|
||||||
|
var shape = node.GetShape();
|
||||||
|
var center = node.Centroid;
|
||||||
|
var hot = node.HotPoints;
|
||||||
|
|
||||||
|
float[] hotX = new float[hot?.Count ?? 0];
|
||||||
|
float[] hotY = new float[hot?.Count ?? 0];
|
||||||
|
if (hot != null)
|
||||||
|
for (int i = 0; i < hot.Count; i++) { hotX[i] = (float)hot[i].X; hotY[i] = (float)hot[i].Y; }
|
||||||
|
|
||||||
|
// 计算参与识别的笔画在原始集合中的下标
|
||||||
|
var participatingStrokes = analysisAlternate.Strokes;
|
||||||
|
int[] strokeIndices = new int[participatingStrokes?.Count ?? 0];
|
||||||
|
if (participatingStrokes != null)
|
||||||
|
for (int i = 0; i < participatingStrokes.Count; i++)
|
||||||
|
strokeIndices[i] = strokes.IndexOf(participatingStrokes[i]);
|
||||||
|
|
||||||
|
return new RecognizeResponse
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
ShapeName = node.GetShapeName() ?? string.Empty,
|
||||||
|
CentroidX = (float)center.X,
|
||||||
|
CentroidY = (float)center.Y,
|
||||||
|
ShapeWidth = shape != null ? (float)shape.Width : 0f,
|
||||||
|
ShapeHeight = shape != null ? (float)shape.Height : 0f,
|
||||||
|
HotPointsX = hotX,
|
||||||
|
HotPointsY = hotY,
|
||||||
|
StrokeIndices = strokeIndices
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsContainShapeType(string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(name)) return false;
|
||||||
|
return name.Contains("Triangle") || name.Contains("Circle") ||
|
||||||
|
name.Contains("Rectangle") || name.Contains("Diamond") ||
|
||||||
|
name.Contains("Parallelogram") || name.Contains("Square") ||
|
||||||
|
name.Contains("Ellipse");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
[assembly: AssemblyTitle("InkCanvasForClass.IACoreHelper")]
|
||||||
|
[assembly: AssemblyDescription("IACore 32-bit ink shape recognition helper process")]
|
||||||
|
[assembly: AssemblyCompany("ICC CE")]
|
||||||
|
[assembly: AssemblyProduct("InkCanvasForClass.IACoreHelper")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © ICC CE")]
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||||
Reference in New Issue
Block a user