Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6058cb9cff | |||
| 6b20d3e268 | |||
| ce037a437a | |||
| 1349dab6d4 | |||
| d3d31925ee | |||
| d9fa77c86b | |||
| dad01235b0 | |||
| 52d9c26076 | |||
| 1a0236237d | |||
| 58bb7a5ebc | |||
| efbab58bca | |||
| eedbc7a863 | |||
| 626cda63ff | |||
| 7b7a9d93aa | |||
| f5c68dac61 | |||
| 2b6b106771 | |||
| 047586883e | |||
| 0c1a25dd6b | |||
| 7840a9a713 | |||
| 28f96ffcd3 | |||
| 04b2663183 | |||
| 1a11027871 | |||
| 854a00803e | |||
| 11ae5f157f | |||
| ecfe05139e | |||
| 4a392e03a7 | |||
| a66037f886 | |||
| 58c399dcbe | |||
| f33e617f44 | |||
| 3e976c1026 | |||
| c15c75075c | |||
| bf8d988c75 | |||
| 6fb7af3d46 | |||
| 1c2860c180 | |||
| fc41f10c37 | |||
| dedf366866 | |||
| edb10096d6 | |||
| ba42a1e6c9 |
@@ -36,6 +36,49 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "2-2-3-trimethylpentane",
|
||||
"name": "2,2,3-三甲基戊烷",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/141403762?v=4",
|
||||
"profile": "https://github.com/2-2-3-trimethylpentane",
|
||||
"contributions": [
|
||||
"blog",
|
||||
"doc",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Alan-CRL",
|
||||
"name": "Alan-CRL",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/92425617?v=4",
|
||||
"profile": "https://github.com/Alan-CRL",
|
||||
"contributions": [
|
||||
"code",
|
||||
"infra",
|
||||
"doc",
|
||||
"financial"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "MKStoler1024",
|
||||
"name": "MKStoler1024",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/158786854?v=4",
|
||||
"profile": "https://github.com/MKStoler1024",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "awesome-iwb",
|
||||
"name": "Awesome Iwb",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/184760810?v=4",
|
||||
"profile": "https://github.com/awesome-iwb",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.7.2.0
|
||||
1.7.3.0
|
||||
|
||||
@@ -432,6 +432,12 @@ namespace Ink_Canvas
|
||||
|
||||
LogHelper.NewLog(string.Format("Ink Canvas Starting (Version: {0})", Assembly.GetExecutingAssembly().GetName().Version.ToString()));
|
||||
|
||||
// 记录应用启动(设备标识符)
|
||||
DeviceIdentifier.RecordAppLaunch();
|
||||
LogHelper.WriteLogToFile($"App | 设备ID: {DeviceIdentifier.GetDeviceId()}");
|
||||
LogHelper.WriteLogToFile($"App | 使用频率: {DeviceIdentifier.GetUsageFrequency()}");
|
||||
LogHelper.WriteLogToFile($"App | 更新优先级: {DeviceIdentifier.GetUpdatePriority()}");
|
||||
|
||||
bool ret;
|
||||
mutex = new System.Threading.Mutex(true, "InkCanvasForClass", out ret);
|
||||
|
||||
@@ -710,6 +716,17 @@ namespace Ink_Canvas
|
||||
string exitType = IsAppExitByUser ? "用户主动退出" : "应用程序退出";
|
||||
WriteCrashLog($"{exitType},退出代码: {e.ApplicationExitCode}");
|
||||
|
||||
// 记录应用退出(设备标识符)
|
||||
try
|
||||
{
|
||||
DeviceIdentifier.RecordAppExit();
|
||||
LogHelper.WriteLogToFile($"App | 应用运行时长: {(DateTime.Now - appStartTime).TotalMinutes:F1}分钟");
|
||||
}
|
||||
catch (Exception deviceEx)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"记录设备标识符退出信息失败: {deviceEx.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
if (IsAppExitByUser)
|
||||
{
|
||||
// 写入退出信号文件,通知看门狗正常退出
|
||||
|
||||
@@ -49,5 +49,5 @@ using System.Windows;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.7.2.0")]
|
||||
[assembly: AssemblyFileVersion("1.7.2.0")]
|
||||
[assembly: AssemblyVersion("1.7.3.0")]
|
||||
[assembly: AssemblyFileVersion("1.7.3.0")]
|
||||
|
||||
@@ -1,37 +1,409 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 适合手写/触摸的墨迹平滑方案:指数平滑+等距重采样+Catmull-Rom样条插值,防止自交和异常填充
|
||||
/// 异步硬件加速的墨迹平滑处理器
|
||||
/// </summary>
|
||||
public class AsyncAdvancedBezierSmoothing
|
||||
{
|
||||
private readonly SemaphoreSlim _processingSemaphore;
|
||||
private readonly ConcurrentDictionary<Stroke, CancellationTokenSource> _processingTasks;
|
||||
private readonly Dispatcher _uiDispatcher;
|
||||
|
||||
public AsyncAdvancedBezierSmoothing(Dispatcher uiDispatcher)
|
||||
{
|
||||
_uiDispatcher = uiDispatcher;
|
||||
_processingSemaphore = new SemaphoreSlim(Environment.ProcessorCount, Environment.ProcessorCount);
|
||||
_processingTasks = new ConcurrentDictionary<Stroke, CancellationTokenSource>();
|
||||
}
|
||||
|
||||
public double SmoothingStrength { get; set; } = 0.3; // 大幅降低强度
|
||||
public double ResampleInterval { get; set; } = 3.0; // 大幅增加间隔减少点数
|
||||
public int InterpolationSteps { get; set; } = 4; // 极大减少插值步数
|
||||
public bool UseHardwareAcceleration { get; set; } = true;
|
||||
public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount;
|
||||
|
||||
/// <summary>
|
||||
/// 异步平滑笔画
|
||||
/// </summary>
|
||||
public async Task<Stroke> SmoothStrokeAsync(Stroke originalStroke,
|
||||
Action<Stroke, Stroke> onCompleted = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (originalStroke == null || originalStroke.StylusPoints.Count < 2)
|
||||
return originalStroke;
|
||||
|
||||
// 取消之前对同一笔画的处理
|
||||
if (_processingTasks.TryGetValue(originalStroke, out var existingCts))
|
||||
{
|
||||
existingCts.Cancel();
|
||||
_processingTasks.TryRemove(originalStroke, out _);
|
||||
}
|
||||
|
||||
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
_processingTasks[originalStroke] = cts;
|
||||
|
||||
try
|
||||
{
|
||||
await _processingSemaphore.WaitAsync(cts.Token);
|
||||
|
||||
var smoothedStroke = await Task.Run(() =>
|
||||
ProcessStrokeInternal(originalStroke, cts.Token), cts.Token);
|
||||
|
||||
// 在UI线程上执行回调
|
||||
if (onCompleted != null && !cts.Token.IsCancellationRequested)
|
||||
{
|
||||
await _uiDispatcher.InvokeAsync(() => onCompleted(originalStroke, smoothedStroke));
|
||||
}
|
||||
|
||||
return smoothedStroke;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return originalStroke;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_processingSemaphore.Release();
|
||||
_processingTasks.TryRemove(originalStroke, out _);
|
||||
cts.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private Stroke ProcessStrokeInternal(Stroke stroke, CancellationToken cancellationToken)
|
||||
{
|
||||
var originalPoints = stroke.StylusPoints.ToArray();
|
||||
|
||||
// 如果点数太少,直接返回原始笔画
|
||||
if (originalPoints.Length < 3)
|
||||
return stroke;
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 简化处理:只进行轻度平滑,避免点数爆炸
|
||||
var smoothedPoints = ApplyLightSmoothing(originalPoints);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 确保点数不会过多
|
||||
if (smoothedPoints.Length > originalPoints.Length * 2)
|
||||
{
|
||||
// 如果点数增加太多,回退到原始笔画
|
||||
return stroke;
|
||||
}
|
||||
|
||||
// 创建平滑后的笔画
|
||||
var smoothedStroke = new Stroke(new StylusPointCollection(smoothedPoints))
|
||||
{
|
||||
DrawingAttributes = stroke.DrawingAttributes.Clone()
|
||||
};
|
||||
|
||||
return smoothedStroke;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轻度平滑处理,避免点数爆炸
|
||||
/// </summary>
|
||||
private StylusPoint[] ApplyLightSmoothing(StylusPoint[] points)
|
||||
{
|
||||
if (points.Length < 3) return points;
|
||||
|
||||
var result = new List<StylusPoint>();
|
||||
result.Add(points[0]); // 保持第一个点
|
||||
|
||||
// 简单的3点平均平滑
|
||||
for (int i = 1; i < points.Length - 1; i++)
|
||||
{
|
||||
var prev = points[i - 1];
|
||||
var curr = points[i];
|
||||
var next = points[i + 1];
|
||||
|
||||
// 3点平均
|
||||
double x = (prev.X + curr.X + next.X) / 3.0;
|
||||
double y = (prev.Y + curr.Y + next.Y) / 3.0;
|
||||
float pressure = (prev.PressureFactor + curr.PressureFactor + next.PressureFactor) / 3.0f;
|
||||
|
||||
result.Add(new StylusPoint(x, y, Math.Max(pressure, 0.1f)));
|
||||
}
|
||||
|
||||
result.Add(points[points.Length - 1]); // 保持最后一个点
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
/// <summary>
|
||||
/// 硬件加速的向量化指数平滑
|
||||
/// </summary>
|
||||
private StylusPoint[] ApplyExponentialSmoothingVectorized(StylusPoint[] points, double alpha)
|
||||
{
|
||||
if (points.Length == 0) return points;
|
||||
|
||||
var result = new StylusPoint[points.Length];
|
||||
result[0] = points[0];
|
||||
|
||||
double lastX = points[0].X;
|
||||
double lastY = points[0].Y;
|
||||
float lastPressure = points[0].PressureFactor;
|
||||
double oneMinusAlpha = 1.0 - alpha;
|
||||
|
||||
// 向量化处理,减少分支预测失败
|
||||
for (int i = 1; i < points.Length; i++)
|
||||
{
|
||||
var p = points[i];
|
||||
lastX = alpha * p.X + oneMinusAlpha * lastX;
|
||||
lastY = alpha * p.Y + oneMinusAlpha * lastY;
|
||||
lastPressure = (float)(alpha * p.PressureFactor + oneMinusAlpha * lastPressure);
|
||||
lastPressure = Math.Max(lastPressure, 0.1f); // 避免分支
|
||||
result[i] = new StylusPoint(lastX, lastY, lastPressure);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 优化的等距重采样
|
||||
/// </summary>
|
||||
private StylusPoint[] ResampleEquidistantOptimized(StylusPoint[] points, double interval)
|
||||
{
|
||||
if (points.Length == 0) return points;
|
||||
|
||||
var result = new List<StylusPoint>(points.Length) { points[0] };
|
||||
double accumulated = 0;
|
||||
|
||||
for (int i = 1; i < points.Length; i++)
|
||||
{
|
||||
var prev = result[result.Count - 1];
|
||||
var curr = points[i];
|
||||
double dx = curr.X - prev.X;
|
||||
double dy = curr.Y - prev.Y;
|
||||
double dist = Math.Sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (dist + accumulated >= interval)
|
||||
{
|
||||
double t = (interval - accumulated) / dist;
|
||||
double x = prev.X + t * dx;
|
||||
double y = prev.Y + t * dy;
|
||||
float pressure = (float)(prev.PressureFactor * (1 - t) + curr.PressureFactor * t);
|
||||
pressure = Math.Max(pressure, 0.1f);
|
||||
|
||||
result.Add(new StylusPoint(x, y, pressure));
|
||||
accumulated = 0;
|
||||
i--; // 重新处理当前点
|
||||
}
|
||||
else
|
||||
{
|
||||
accumulated += dist;
|
||||
}
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 硬件加速的贝塞尔曲线拟合
|
||||
/// </summary>
|
||||
private StylusPoint[] SlidingBezierFitHardwareAccelerated(StylusPoint[] points, int window, int steps)
|
||||
{
|
||||
if (points.Length < window) return points;
|
||||
|
||||
var result = new List<StylusPoint>(points.Length * steps / window);
|
||||
|
||||
// 使用并行处理加速计算
|
||||
var segments = new List<StylusPoint[]>();
|
||||
|
||||
Parallel.For(0, points.Length - window + 1, i =>
|
||||
{
|
||||
var segmentPoints = new StylusPoint[steps];
|
||||
var p0 = points[i];
|
||||
var p1 = points[i + 1];
|
||||
var p2 = points[i + 2];
|
||||
var p3 = points[i + 3];
|
||||
|
||||
for (int j = 0; j < steps; j++)
|
||||
{
|
||||
double t = (double)j / steps;
|
||||
segmentPoints[j] = CubicBezierOptimized(p0, p1, p2, p3, t);
|
||||
}
|
||||
|
||||
lock (segments)
|
||||
{
|
||||
segments.Add(segmentPoints);
|
||||
}
|
||||
});
|
||||
|
||||
// 合并结果
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
result.AddRange(segment);
|
||||
}
|
||||
|
||||
result.Add(points[points.Length - 1]);
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 优化的单线程贝塞尔拟合
|
||||
/// </summary>
|
||||
private StylusPoint[] SlidingBezierFitOptimized(StylusPoint[] points, int window, int steps)
|
||||
{
|
||||
if (points.Length < window) return points;
|
||||
|
||||
var result = new List<StylusPoint>(points.Length * steps / window);
|
||||
|
||||
for (int i = 0; i <= points.Length - window; i++)
|
||||
{
|
||||
var p0 = points[i];
|
||||
var p1 = points[i + 1];
|
||||
var p2 = points[i + 2];
|
||||
var p3 = points[i + 3];
|
||||
|
||||
for (int j = 0; j < steps; j++)
|
||||
{
|
||||
double t = (double)j / steps;
|
||||
result.Add(CubicBezierOptimized(p0, p1, p2, p3, t));
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(points[points.Length - 1]);
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 优化的三次贝塞尔曲线计算
|
||||
/// </summary>
|
||||
private StylusPoint CubicBezierOptimized(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3, double t)
|
||||
{
|
||||
double u = 1 - t;
|
||||
double tt = t * t;
|
||||
double uu = u * u;
|
||||
double uuu = uu * u;
|
||||
double ttt = tt * t;
|
||||
|
||||
// 预计算系数
|
||||
double c0 = uuu;
|
||||
double c1 = 3 * uu * t;
|
||||
double c2 = 3 * u * tt;
|
||||
double c3 = ttt;
|
||||
|
||||
double x = c0 * p0.X + c1 * p1.X + c2 * p2.X + c3 * p3.X;
|
||||
double y = c0 * p0.Y + c1 * p1.Y + c2 * p2.Y + c3 * p3.Y;
|
||||
float pressure = (float)(p1.PressureFactor * u + p2.PressureFactor * t);
|
||||
pressure = Math.Max(pressure, 0.1f);
|
||||
|
||||
return new StylusPoint(x, y, pressure);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 兼容性方法:传统指数平滑
|
||||
/// </summary>
|
||||
private StylusPoint[] ApplyExponentialSmoothing(StylusPoint[] points, double alpha)
|
||||
{
|
||||
if (points.Length == 0) return points;
|
||||
|
||||
var result = new StylusPoint[points.Length];
|
||||
result[0] = points[0];
|
||||
|
||||
double lastX = points[0].X;
|
||||
double lastY = points[0].Y;
|
||||
float lastPressure = points[0].PressureFactor;
|
||||
|
||||
for (int i = 1; i < points.Length; i++)
|
||||
{
|
||||
var p = points[i];
|
||||
lastX = alpha * p.X + (1 - alpha) * lastX;
|
||||
lastY = alpha * p.Y + (1 - alpha) * lastY;
|
||||
lastPressure = (float)(alpha * p.PressureFactor + (1 - alpha) * lastPressure);
|
||||
lastPressure = Math.Max(lastPressure, 0.1f);
|
||||
result[i] = new StylusPoint(lastX, lastY, lastPressure);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消所有正在进行的处理任务
|
||||
/// </summary>
|
||||
public void CancelAllTasks()
|
||||
{
|
||||
foreach (var kvp in _processingTasks)
|
||||
{
|
||||
kvp.Value.Cancel();
|
||||
}
|
||||
_processingTasks.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
CancelAllTasks();
|
||||
_processingSemaphore?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 原有的同步版本(保持向后兼容)
|
||||
/// </summary>
|
||||
public class AdvancedBezierSmoothing
|
||||
{
|
||||
public double SmoothingStrength { get; set; } = 0.8;
|
||||
public double ResampleInterval { get; set; } = 0.8;
|
||||
public int InterpolationSteps { get; set; } = 64;
|
||||
public double SmoothingStrength { get; set; } = 0.3;
|
||||
public double ResampleInterval { get; set; } = 3.0;
|
||||
public int InterpolationSteps { get; set; } = 4;
|
||||
|
||||
public Stroke SmoothStroke(Stroke stroke)
|
||||
{
|
||||
if (stroke == null || stroke.StylusPoints.Count < 2)
|
||||
if (stroke == null || stroke.StylusPoints.Count < 3)
|
||||
return stroke;
|
||||
|
||||
var originalPoints = stroke.StylusPoints.ToList();
|
||||
var smoothedPoints = ApplyExponentialSmoothing(originalPoints, SmoothingStrength);
|
||||
var resampledPoints = ResampleEquidistant(smoothedPoints, ResampleInterval);
|
||||
var interpolatedPoints = SlidingBezierFit(resampledPoints, 4, 24);
|
||||
var finalPoints = ApplyExponentialSmoothing(interpolatedPoints, 0.5); // 二次平滑
|
||||
var ultraSmoothPoints = SlidingWindowSmooth(finalPoints, 7); // 滑动窗口平滑
|
||||
var smoothedStroke = new Stroke(new StylusPointCollection(ultraSmoothPoints))
|
||||
|
||||
// 简化处理:只进行轻度平滑
|
||||
var smoothedPoints = ApplyLightExponentialSmoothing(originalPoints, 0.2); // 很轻的平滑
|
||||
|
||||
// 检查点数是否合理
|
||||
if (smoothedPoints.Count > originalPoints.Count * 1.5)
|
||||
{
|
||||
return stroke; // 如果点数增加太多,返回原始笔画
|
||||
}
|
||||
|
||||
var smoothedStroke = new Stroke(new StylusPointCollection(smoothedPoints))
|
||||
{
|
||||
DrawingAttributes = stroke.DrawingAttributes.Clone()
|
||||
};
|
||||
return smoothedStroke;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轻度指数平滑
|
||||
/// </summary>
|
||||
private List<StylusPoint> ApplyLightExponentialSmoothing(List<StylusPoint> points, double alpha)
|
||||
{
|
||||
var result = new List<StylusPoint>();
|
||||
if (points.Count == 0) return result;
|
||||
|
||||
result.Add(points[0]);
|
||||
|
||||
for (int i = 1; i < points.Count; i++)
|
||||
{
|
||||
var prev = result[result.Count - 1];
|
||||
var curr = points[i];
|
||||
|
||||
double x = alpha * curr.X + (1 - alpha) * prev.X;
|
||||
double y = alpha * curr.Y + (1 - alpha) * prev.Y;
|
||||
float pressure = (float)(alpha * curr.PressureFactor + (1 - alpha) * prev.PressureFactor);
|
||||
pressure = Math.Max(pressure, 0.1f);
|
||||
|
||||
result.Add(new StylusPoint(x, y, pressure));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<StylusPoint> ApplyExponentialSmoothing(List<StylusPoint> points, double alpha)
|
||||
{
|
||||
var result = new List<StylusPoint>();
|
||||
@@ -141,4 +513,50 @@ namespace Ink_Canvas.Helpers
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 性能监控器
|
||||
/// </summary>
|
||||
public class InkSmoothingPerformanceMonitor
|
||||
{
|
||||
private readonly Queue<TimeSpan> _processingTimes = new Queue<TimeSpan>();
|
||||
private readonly object _lock = new object();
|
||||
private const int MaxSamples = 100;
|
||||
|
||||
public void RecordProcessingTime(TimeSpan time)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_processingTimes.Enqueue(time);
|
||||
if (_processingTimes.Count > MaxSamples)
|
||||
_processingTimes.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
public double GetAverageProcessingTimeMs()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _processingTimes.Count > 0 ?
|
||||
_processingTimes.Average(t => t.TotalMilliseconds) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public double GetMaxProcessingTimeMs()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _processingTimes.Count > 0 ?
|
||||
_processingTimes.Max(t => t.TotalMilliseconds) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetSampleCount()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _processingTimes.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
// 定义超时时间为10秒
|
||||
private static readonly TimeSpan RequestTimeout = TimeSpan.FromSeconds(10);
|
||||
private static string updatesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AutoUpdate");
|
||||
private static readonly string updatesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AutoUpdate");
|
||||
private static string statusFilePath = null;
|
||||
|
||||
// 线路组结构体(包含版本、下载、日志地址)
|
||||
@@ -62,6 +62,11 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
GroupName = "智教联盟",
|
||||
DownloadUrlFormat = "https://get.smart-teach.cn/d/Ningbo-S3/shared/jiangling/community/InkCanvasForClass.CE.{0}.zip",
|
||||
},
|
||||
new UpdateLineGroup
|
||||
{
|
||||
GroupName = "inkeys",
|
||||
DownloadUrlFormat = "https://iccce.inkeys.top/Release/InkCanvasForClass.CE.{0}.zip",
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -92,6 +97,11 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
GroupName = "智教联盟",
|
||||
DownloadUrlFormat = "https://get.smart-teach.cn/d/Ningbo-S3/shared/jiangling/community-beta/InkCanvasForClass.CE.{0}.zip",
|
||||
},
|
||||
new UpdateLineGroup
|
||||
{
|
||||
GroupName = "inkeys",
|
||||
DownloadUrlFormat = "https://iccce.inkeys.top/Beta/InkCanvasForClass.CE.{0}.zip",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,10 +179,10 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
// 跳过“智教联盟”线路组,不参与延迟检测和排序
|
||||
if (group.GroupName == "智教联盟")
|
||||
// 跳过"智教联盟"和"inkeys"线路组,不参与延迟检测和排序
|
||||
if (group.GroupName == "智教联盟" || group.GroupName == "inkeys")
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 跳过智教联盟线路组延迟检测");
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 跳过{group.GroupName}线路组延迟检测");
|
||||
continue;
|
||||
}
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 检测线路组: {group.GroupName} ({group.VersionUrl})");
|
||||
@@ -194,7 +204,7 @@ namespace Ink_Canvas.Helpers
|
||||
.Select(x => x.group)
|
||||
.ToList();
|
||||
|
||||
// 将“智教联盟”线路组插入到最前面(如果存在)
|
||||
// 将"智教联盟"线路组插入到最前面(如果存在)
|
||||
var zhiJiaoGroup = groups.FirstOrDefault(g => g.GroupName == "智教联盟");
|
||||
if (zhiJiaoGroup != null)
|
||||
{
|
||||
@@ -202,6 +212,14 @@ namespace Ink_Canvas.Helpers
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 智教联盟线路组已插入到首位");
|
||||
}
|
||||
|
||||
// 将"inkeys"线路组插入到第二位(如果存在)
|
||||
var inkeysGroup = groups.FirstOrDefault(g => g.GroupName == "inkeys");
|
||||
if (inkeysGroup != null)
|
||||
{
|
||||
orderedGroups.Insert(1, inkeysGroup);
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | inkeys线路组已插入到第二位");
|
||||
}
|
||||
|
||||
if (orderedGroups.Count > 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 找到 {orderedGroups.Count} 个可用线路组,按延迟排序:");
|
||||
@@ -377,8 +395,49 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
// 通过GitHub API获取指定版本的Release信息
|
||||
private static async Task<(string version, string downloadUrl, string releaseNotes, DateTime? releaseTime)> GetGithubReleaseByVersion(string targetVersion, UpdateChannel channel)
|
||||
{
|
||||
try
|
||||
{
|
||||
string apiUrl = channel == UpdateChannel.Beta
|
||||
? "https://api.github.com/repos/InkCanvasForClass/community-beta/releases"
|
||||
: "https://api.github.com/repos/InkCanvasForClass/community/releases";
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "ICC-CE Auto Updater");
|
||||
var response = await client.GetStringAsync(apiUrl);
|
||||
var releases = JArray.Parse(response);
|
||||
|
||||
foreach (var release in releases)
|
||||
{
|
||||
string version = release["tag_name"]?.ToString();
|
||||
if (version == targetVersion || version == $"v{targetVersion}" || version == $"V{targetVersion}")
|
||||
{
|
||||
string releaseNotes = release["body"]?.ToString();
|
||||
string downloadUrl = release["assets"]?.First?["browser_download_url"]?.ToString();
|
||||
|
||||
// 解析发布时间
|
||||
DateTime? releaseTime = null;
|
||||
if (release["published_at"] != null && DateTime.TryParse(release["published_at"].ToString(), out DateTime parsedTime))
|
||||
{
|
||||
releaseTime = parsedTime;
|
||||
}
|
||||
|
||||
return (version, downloadUrl, releaseNotes, releaseTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | GitHub Releases API 获取版本 {targetVersion} 失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
return (null, null, null, null);
|
||||
}
|
||||
|
||||
// 通过GitHub API获取最新Release信息
|
||||
private static async Task<(string version, string downloadUrl, string releaseNotes)> GetLatestGithubRelease(UpdateChannel channel)
|
||||
private static async Task<(string version, string downloadUrl, string releaseNotes, DateTime? releaseTime)> GetLatestGithubRelease(UpdateChannel channel)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -393,28 +452,42 @@ namespace Ink_Canvas.Helpers
|
||||
string version = json["tag_name"]?.ToString();
|
||||
string releaseNotes = json["body"]?.ToString();
|
||||
string downloadUrl = json["assets"]?.First?["browser_download_url"]?.ToString();
|
||||
|
||||
// 解析发布时间
|
||||
DateTime? releaseTime = null;
|
||||
if (json["published_at"] != null && DateTime.TryParse(json["published_at"].ToString(), out DateTime parsedTime))
|
||||
{
|
||||
releaseTime = parsedTime;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(version) && !string.IsNullOrEmpty(downloadUrl))
|
||||
return (version, downloadUrl, releaseNotes);
|
||||
return (version, downloadUrl, releaseNotes, releaseTime);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | GitHub Releases API 获取失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
return (null, null, null);
|
||||
return (null, null, null, null);
|
||||
}
|
||||
|
||||
// 主要的更新检测方法(优先检测延迟,失败时自动切换线路组)
|
||||
// 仅检测新版本时用GitHub API,实际下载时只用线路组
|
||||
public static async Task<(string remoteVersion, UpdateLineGroup lineGroup, string releaseNotes)> CheckForUpdates(UpdateChannel channel = UpdateChannel.Release, bool alwaysGetRemote = false)
|
||||
public static async Task<(string remoteVersion, UpdateLineGroup lineGroup, string releaseNotes)> CheckForUpdates(UpdateChannel channel = UpdateChannel.Release, bool alwaysGetRemote = false, bool isVersionFix = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 记录更新检查时间
|
||||
DeviceIdentifier.RecordUpdateCheck();
|
||||
|
||||
string localVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 本地版本: {localVersion}");
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 设备ID: {DeviceIdentifier.GetDeviceId()}");
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 更新优先级: {DeviceIdentifier.GetUpdatePriority()}");
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 优先通过GitHub Releases API检测...");
|
||||
|
||||
// 1. 优先通过GitHub Releases API获取
|
||||
var (apiVersion, _, apiReleaseNotes) = await GetLatestGithubRelease(channel);
|
||||
var (apiVersion, _, apiReleaseNotes, apiReleaseTime) = await GetLatestGithubRelease(channel);
|
||||
if (!string.IsNullOrEmpty(apiVersion))
|
||||
{
|
||||
Version local = new Version(localVersion);
|
||||
@@ -422,15 +495,42 @@ namespace Ink_Canvas.Helpers
|
||||
if (remote > local || alwaysGetRemote)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 通过GitHub Releases API发现新版本: {apiVersion}");
|
||||
|
||||
// 检查是否应该根据用户优先级推送更新(版本修复功能不受限制)
|
||||
if (!isVersionFix)
|
||||
{
|
||||
DateTime releaseTime = apiReleaseTime ?? DateTime.Now;
|
||||
|
||||
// 尝试获取当前版本的发布时间
|
||||
DateTime? currentVersionReleaseTime = await GetVersionReleaseTime(localVersion, channel);
|
||||
|
||||
bool shouldPush = DeviceIdentifier.ShouldPushUpdate(apiVersion, releaseTime, true, currentVersionReleaseTime); // 明确标记为自动更新
|
||||
if (!shouldPush)
|
||||
{
|
||||
var priority = DeviceIdentifier.GetUpdatePriority();
|
||||
var daysBetweenVersions = currentVersionReleaseTime.HasValue
|
||||
? (releaseTime - currentVersionReleaseTime.Value).TotalDays
|
||||
: (DateTime.Now - releaseTime).TotalDays;
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 根据用户优先级({priority}),暂不推送更新 {apiVersion},版本间隔: {daysBetweenVersions:F1} 天");
|
||||
var group = (await GetAvailableLineGroupsOrdered(channel)).FirstOrDefault();
|
||||
return (null, group, apiReleaseNotes); // 返回null表示不推送
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 版本修复模式,跳过分级策略检查");
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 根据用户优先级,推送更新 {apiVersion}");
|
||||
// 只返回版本号和日志,不返回直链
|
||||
var group = (await GetAvailableLineGroupsOrdered(channel)).FirstOrDefault();
|
||||
return (apiVersion, group, apiReleaseNotes);
|
||||
var availableGroup = (await GetAvailableLineGroupsOrdered(channel)).FirstOrDefault();
|
||||
return (apiVersion, availableGroup, apiReleaseNotes);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 当前版本已是最新 (GitHub Releases API)");
|
||||
var group = (await GetAvailableLineGroupsOrdered(channel)).FirstOrDefault();
|
||||
return (null, group, apiReleaseNotes);
|
||||
var availableGroup = (await GetAvailableLineGroupsOrdered(channel)).FirstOrDefault();
|
||||
return (null, availableGroup, apiReleaseNotes);
|
||||
}
|
||||
}
|
||||
// 2. 回退到原有txt方案
|
||||
@@ -453,6 +553,27 @@ namespace Ink_Canvas.Helpers
|
||||
if (remote > local || alwaysGetRemote)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 发现新版本或强制获取: {remoteVersion}");
|
||||
|
||||
// 检查是否应该根据用户优先级推送更新(版本修复功能不受限制)
|
||||
if (!isVersionFix)
|
||||
{
|
||||
// 尝试获取当前版本的发布时间
|
||||
DateTime? currentVersionReleaseTime = await GetVersionReleaseTime(localVersion, channel);
|
||||
|
||||
bool shouldPush = DeviceIdentifier.ShouldPushUpdate(remoteVersion, DateTime.Now, true, currentVersionReleaseTime); // 明确标记为自动更新
|
||||
if (!shouldPush)
|
||||
{
|
||||
var priority = DeviceIdentifier.GetUpdatePriority();
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 根据用户优先级({priority}),暂不推送更新 {remoteVersion}");
|
||||
return (null, group, null); // 返回null表示不推送
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 版本修复模式,跳过分级策略检查");
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 根据用户优先级,推送更新 {remoteVersion}");
|
||||
return (remoteVersion, group, null);
|
||||
}
|
||||
else
|
||||
@@ -547,10 +668,21 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
// 优先尝试“智教联盟”线路组
|
||||
var zhiJiaoGroup = groups.FirstOrDefault(g => g.GroupName == "智教联盟");
|
||||
if (zhiJiaoGroup != null)
|
||||
var inkeysGroup = groups.FirstOrDefault(g => g.GroupName == "inkeys");
|
||||
if (zhiJiaoGroup != null || inkeysGroup != null)
|
||||
{
|
||||
groups = new List<UpdateLineGroup> { zhiJiaoGroup }.Concat(groups.Where(g => g.GroupName != "智教联盟")).ToList();
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 下载时优先尝试智教联盟线路组");
|
||||
var priorityGroups = new List<UpdateLineGroup>();
|
||||
if (zhiJiaoGroup != null)
|
||||
{
|
||||
priorityGroups.Add(zhiJiaoGroup);
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 下载时优先尝试智教联盟线路组");
|
||||
}
|
||||
if (inkeysGroup != null)
|
||||
{
|
||||
priorityGroups.Add(inkeysGroup);
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 下载时优先尝试inkeys线路组");
|
||||
}
|
||||
groups = priorityGroups.Concat(groups.Where(g => g.GroupName != "智教联盟" && g.GroupName != "inkeys")).ToList();
|
||||
}
|
||||
|
||||
// 依次尝试每个线路组
|
||||
@@ -571,6 +703,11 @@ namespace Ink_Canvas.Helpers
|
||||
url = realUrl;
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 智教联盟真实下载地址: {url}");
|
||||
}
|
||||
// inkeys线路组直接使用下载地址,无需特殊处理
|
||||
else if (group.GroupName == "inkeys")
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 使用inkeys线路组下载地址: {url}");
|
||||
}
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 尝试从线路组 {group.GroupName} 下载: {url}");
|
||||
|
||||
bool downloadSuccess = await DownloadFile(url, zipFilePath, progressCallback);
|
||||
@@ -611,7 +748,8 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 正在尝试多线程下载: {fileUrl}");
|
||||
int maxRetry = 3;
|
||||
int[] threadOptions = new int[] { 32, 4 };
|
||||
// 降低并发数,减少网络压力
|
||||
int[] threadOptions = new int[] { 32, 16, 8, 4, 1 };
|
||||
|
||||
// 检查服务器是否支持Range分块下载
|
||||
bool supportRange = false;
|
||||
@@ -655,46 +793,14 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 服务器不支持分块下载,自动降级为单线程下载");
|
||||
progressCallback?.Invoke(0, "服务器不支持分块下载,自动降级为单线程下载");
|
||||
try
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
||||
using (var resp = await client.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead))
|
||||
{
|
||||
resp.EnsureSuccessStatusCode();
|
||||
using (var fs = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
var stream = await resp.Content.ReadAsStreamAsync();
|
||||
byte[] buffer = new byte[8192];
|
||||
int read;
|
||||
long downloaded = 0;
|
||||
while ((read = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
await fs.WriteAsync(buffer, 0, read);
|
||||
downloaded += read;
|
||||
if (totalSize > 0)
|
||||
{
|
||||
double percent = (double)downloaded / totalSize * 100;
|
||||
progressCallback?.Invoke(percent, $"单线程下载中: {percent:F1}%");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
progressCallback?.Invoke(100, "单线程下载完成");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 单线程下载失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
progressCallback?.Invoke(0, $"单线程下载失败: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
return await DownloadSingleThread(fileUrl, destinationPath, totalSize, progressCallback);
|
||||
}
|
||||
|
||||
foreach (int threadCount in threadOptions)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 尝试使用 {threadCount} 线程下载");
|
||||
progressCallback?.Invoke(0, $"尝试使用 {threadCount} 线程下载");
|
||||
|
||||
if (totalSize <= 0)
|
||||
{
|
||||
totalSize = await GetContentLength(fileUrl);
|
||||
@@ -704,19 +810,28 @@ namespace Ink_Canvas.Helpers
|
||||
progressCallback?.Invoke(0, "无法获取文件大小,取消下载");
|
||||
return false;
|
||||
}
|
||||
int blockSize = (int)Math.Ceiling((double)totalSize / threadCount);
|
||||
|
||||
// 根据文件大小动态调整分块大小,避免分块过小
|
||||
int minBlockSize = 32 * 1024; // 最小32KB
|
||||
int blockSize = Math.Max(minBlockSize, (int)Math.Ceiling((double)totalSize / threadCount));
|
||||
int blockCount = (int)Math.Ceiling((double)totalSize / blockSize);
|
||||
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 文件大小: {totalSize}, 分块数: {blockCount}, 分块大小: {blockSize}");
|
||||
|
||||
var blockQueue = new System.Collections.Concurrent.ConcurrentQueue<BlockTask>();
|
||||
var finishedBlocks = new System.Collections.Concurrent.ConcurrentDictionary<int, bool>();
|
||||
long[] blockDownloaded = new long[blockCount];
|
||||
|
||||
for (int i = 0; i < blockCount; i++)
|
||||
{
|
||||
long start = i * blockSize;
|
||||
long end = Math.Min(start + blockSize - 1, totalSize - 1);
|
||||
blockQueue.Enqueue(new BlockTask { Index = i, Start = start, End = end, RetryCount = 0 });
|
||||
}
|
||||
|
||||
CancellationTokenSource cts = new CancellationTokenSource();
|
||||
var tasks = new List<Task>();
|
||||
|
||||
for (int t = 0; t < threadCount; t++)
|
||||
{
|
||||
tasks.Add(Task.Run(async () =>
|
||||
@@ -724,6 +839,8 @@ namespace Ink_Canvas.Helpers
|
||||
while (blockQueue.TryDequeue(out var block))
|
||||
{
|
||||
bool success = false;
|
||||
string tempPath = destinationPath + $".part{block.Index}";
|
||||
|
||||
for (int retry = block.RetryCount; retry < maxRetry && !success; retry++)
|
||||
{
|
||||
try
|
||||
@@ -734,7 +851,9 @@ namespace Ink_Canvas.Helpers
|
||||
var req = new HttpRequestMessage(HttpMethod.Get, fileUrl);
|
||||
req.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(block.Start, block.End);
|
||||
|
||||
// 新增:分块下载超时机制
|
||||
// 增加连接超时设置
|
||||
client.Timeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
var downloadCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token);
|
||||
var lastReadTime = DateTime.UtcNow;
|
||||
bool dataReceived = false;
|
||||
@@ -743,31 +862,33 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index} 响应状态: {resp.StatusCode}");
|
||||
resp.EnsureSuccessStatusCode();
|
||||
string tempPath = destinationPath + $".part{block.Index}";
|
||||
using (var fs = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
var stream = await resp.Content.ReadAsStreamAsync();
|
||||
byte[] buffer = new byte[8192];
|
||||
int read;
|
||||
long blockDownloadedBytes = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var readTask = stream.ReadAsync(buffer, 0, buffer.Length, downloadCts.Token);
|
||||
var timeoutTask = Task.Delay(15000, downloadCts.Token); // 15秒超时
|
||||
var timeoutTask = Task.Delay(20000, downloadCts.Token); // 增加到20秒超时
|
||||
var completed = await Task.WhenAny(readTask, timeoutTask);
|
||||
if (completed == timeoutTask)
|
||||
{
|
||||
// 超时未收到数据,取消本线程,重新入队
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index} 15秒无数据,线程超时重试", LogHelper.LogType.Warning);
|
||||
progressCallback?.Invoke(0, $"分块{block.Index} 15秒无数据,线程超时重试");
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index} 20秒无数据,线程超时重试", LogHelper.LogType.Warning);
|
||||
progressCallback?.Invoke(0, $"分块{block.Index} 20秒无数据,线程超时重试");
|
||||
downloadCts.Cancel();
|
||||
break;
|
||||
}
|
||||
read = await readTask;
|
||||
if (read <= 0) break;
|
||||
await fs.WriteAsync(buffer, 0, read, downloadCts.Token);
|
||||
blockDownloaded[block.Index] += read;
|
||||
blockDownloadedBytes += read;
|
||||
blockDownloaded[block.Index] = blockDownloadedBytes;
|
||||
lastReadTime = DateTime.UtcNow;
|
||||
dataReceived = true;
|
||||
|
||||
// 合并所有块进度
|
||||
long totalDownloaded = blockDownloaded.Sum();
|
||||
double percent = (double)totalDownloaded / totalSize * 100;
|
||||
@@ -775,19 +896,37 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果因超时break且未完成,success为false,重新入队
|
||||
|
||||
if (!dataReceived)
|
||||
{
|
||||
throw new IOException("分块下载超时无数据");
|
||||
}
|
||||
|
||||
// 验证分块大小是否正确
|
||||
var fileInfo = new FileInfo(tempPath);
|
||||
long expectedSize = block.End - block.Start + 1;
|
||||
if (fileInfo.Length != expectedSize)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}大小不匹配,期望:{expectedSize},实际:{fileInfo.Length}", LogHelper.LogType.Warning);
|
||||
throw new IOException($"分块{block.Index}大小不匹配");
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}下载成功");
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException || ex is IOException || ex is TaskCanceledException)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}下载失败,第{retry + 1}次: {ex.Message}", LogHelper.LogType.Warning);
|
||||
progressCallback?.Invoke(0, $"分块{block.Index}下载失败,第{retry + 1}次: {ex.Message}");
|
||||
await Task.Delay(15000);
|
||||
|
||||
// 清理可能损坏的分块文件
|
||||
if (File.Exists(tempPath))
|
||||
{
|
||||
try { File.Delete(tempPath); } catch { }
|
||||
}
|
||||
|
||||
// 增加重试间隔,避免频繁重试
|
||||
await Task.Delay(2000 * (retry + 1));
|
||||
}
|
||||
}
|
||||
if (success)
|
||||
@@ -803,76 +942,161 @@ namespace Ink_Canvas.Helpers
|
||||
else
|
||||
{
|
||||
// 超过最大重试,取消所有任务
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}超过最大重试次数,取消下载", LogHelper.LogType.Error);
|
||||
cts.Cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
if (cts.IsCancellationRequested || finishedBlocks.Count != blockCount)
|
||||
{
|
||||
progressCallback?.Invoke(0, $"多线程下载失败({threadCount}线程)");
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | {threadCount}线程下载失败,完成分块数: {finishedBlocks.Count}/{blockCount}", LogHelper.LogType.Warning);
|
||||
progressCallback?.Invoke(0, $"{threadCount}线程下载失败,完成分块数: {finishedBlocks.Count}/{blockCount}");
|
||||
|
||||
// 清理分块文件
|
||||
for (int i = 0; i < blockCount; i++)
|
||||
{
|
||||
string tempPath = destinationPath + $".part{i}";
|
||||
if (File.Exists(tempPath)) File.Delete(tempPath);
|
||||
}
|
||||
|
||||
if (threadCount == threadOptions.Last())
|
||||
{
|
||||
// 已经是最后一次尝试
|
||||
return false;
|
||||
// 已经是最后一次尝试,降级为单线程
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 所有多线程尝试失败,降级为单线程下载");
|
||||
progressCallback?.Invoke(0, "所有多线程尝试失败,降级为单线程下载");
|
||||
return await DownloadSingleThread(fileUrl, destinationPath, totalSize, progressCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | {threadCount}线程下载失败,尝试降级为{threadOptions.Last()}线程");
|
||||
progressCallback?.Invoke(0, $"{threadCount}线程下载失败,尝试降级为{threadOptions.Last()}线程");
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | {threadCount}线程下载失败,尝试降级为{threadOptions[Array.IndexOf(threadOptions, threadCount) + 1]}线程");
|
||||
progressCallback?.Invoke(0, $"{threadCount}线程下载失败,尝试降级为{threadOptions[Array.IndexOf(threadOptions, threadCount) + 1]}线程");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 合并所有块
|
||||
using (var output = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < blockCount; i++)
|
||||
using (var output = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
string tempPath = destinationPath + $".part{i}";
|
||||
using (var input = new FileStream(tempPath, FileMode.Open, FileAccess.Read))
|
||||
for (int i = 0; i < blockCount; i++)
|
||||
{
|
||||
await input.CopyToAsync(output);
|
||||
string tempPath = destinationPath + $".part{i}";
|
||||
if (!File.Exists(tempPath))
|
||||
{
|
||||
throw new FileNotFoundException($"分块文件不存在: {tempPath}");
|
||||
}
|
||||
|
||||
using (var input = new FileStream(tempPath, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
await input.CopyToAsync(output);
|
||||
}
|
||||
File.Delete(tempPath);
|
||||
}
|
||||
File.Delete(tempPath);
|
||||
}
|
||||
}
|
||||
progressCallback?.Invoke(100, $"多线程下载完成({threadCount}线程)");
|
||||
|
||||
progressCallback?.Invoke(100, $"多线程下载完成({threadCount}线程)");
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 多线程下载完成({threadCount}线程)");
|
||||
|
||||
FileInfo fileInfo = new FileInfo(destinationPath);
|
||||
if (fileInfo.Length != totalSize)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 文件大小校验失败,本地:{fileInfo.Length},服务器:{totalSize}", LogHelper.LogType.Error);
|
||||
File.Delete(destinationPath);
|
||||
progressCallback?.Invoke(0, "文件大小校验失败,已删除损坏文件");
|
||||
return false;
|
||||
}
|
||||
if (destinationPath.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
// 文件大小校验
|
||||
FileInfo fileInfo = new FileInfo(destinationPath);
|
||||
if (fileInfo.Length != totalSize)
|
||||
{
|
||||
System.IO.Compression.ZipFile.OpenRead(destinationPath).Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
LogHelper.WriteLogToFile("AutoUpdate | ZIP文件解压测试失败,文件可能已损坏", LogHelper.LogType.Error);
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 文件大小校验失败,本地:{fileInfo.Length},服务器:{totalSize}", LogHelper.LogType.Error);
|
||||
File.Delete(destinationPath);
|
||||
progressCallback?.Invoke(0, "ZIP文件解压测试失败,已删除损坏文件");
|
||||
progressCallback?.Invoke(0, "文件大小校验失败,已删除损坏文件");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ZIP文件完整性校验
|
||||
if (destinationPath.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
System.IO.Compression.ZipFile.OpenRead(destinationPath).Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
LogHelper.WriteLogToFile("AutoUpdate | ZIP文件解压测试失败,文件可能已损坏", LogHelper.LogType.Error);
|
||||
File.Delete(destinationPath);
|
||||
progressCallback?.Invoke(0, "ZIP文件解压测试失败,已删除损坏文件");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 合并分块文件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
File.Delete(destinationPath);
|
||||
progressCallback?.Invoke(0, $"合并分块文件时出错: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 单线程下载方法
|
||||
private static async Task<bool> DownloadSingleThread(string fileUrl, string destinationPath, long totalSize, Action<double, string> progressCallback = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 开始单线程下载: {fileUrl}");
|
||||
progressCallback?.Invoke(0, "开始单线程下载");
|
||||
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
||||
client.Timeout = TimeSpan.FromMinutes(10); // 单线程下载设置更长的超时时间
|
||||
|
||||
using (var resp = await client.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead))
|
||||
{
|
||||
resp.EnsureSuccessStatusCode();
|
||||
using (var fs = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
var stream = await resp.Content.ReadAsStreamAsync();
|
||||
byte[] buffer = new byte[8192];
|
||||
int read;
|
||||
long downloaded = 0;
|
||||
var lastProgressUpdate = DateTime.UtcNow;
|
||||
|
||||
while ((read = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
await fs.WriteAsync(buffer, 0, read);
|
||||
downloaded += read;
|
||||
|
||||
// 限制进度更新频率,避免UI卡顿
|
||||
if (DateTime.UtcNow - lastProgressUpdate > TimeSpan.FromMilliseconds(500))
|
||||
{
|
||||
if (totalSize > 0)
|
||||
{
|
||||
double percent = (double)downloaded / totalSize * 100;
|
||||
progressCallback?.Invoke(percent, $"单线程下载中: {percent:F1}%");
|
||||
}
|
||||
lastProgressUpdate = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
progressCallback?.Invoke(100, "单线程下载完成");
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 单线程下载完成");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 单线程下载失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
progressCallback?.Invoke(0, $"单线程下载失败: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取文件总大小
|
||||
private static async Task<long> GetContentLength(string fileUrl)
|
||||
{
|
||||
@@ -1214,8 +1438,8 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 开始修复版本,通道: {channel}");
|
||||
|
||||
// 获取远程版本号(自动选择最快线路组,始终下载远程版本)
|
||||
var (remoteVersion, group, _) = await CheckForUpdates(channel, true);
|
||||
// 获取远程版本号(自动选择最快线路组,始终下载远程版本,版本修复模式)
|
||||
var (remoteVersion, group, _) = await CheckForUpdates(channel, true, true);
|
||||
if (string.IsNullOrEmpty(remoteVersion) || group == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 修复版本时获取远程版本失败", LogHelper.LogType.Error);
|
||||
@@ -1327,6 +1551,26 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定版本的发布时间
|
||||
/// </summary>
|
||||
/// <param name="version">版本号</param>
|
||||
/// <param name="channel">更新通道</param>
|
||||
/// <returns>版本发布时间,如果获取失败则返回null</returns>
|
||||
public static async Task<DateTime?> GetVersionReleaseTime(string version, UpdateChannel channel = UpdateChannel.Release)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (_, _, _, releaseTime) = await GetGithubReleaseByVersion(version, channel);
|
||||
return releaseTime;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 获取版本 {version} 发布时间失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动手动指定版本的多线路多线程下载并自动安装(用于历史版本回滚等场景)
|
||||
/// </summary>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,259 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Effects;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 硬件加速的墨迹处理器,利用WPF的GPU渲染能力
|
||||
/// </summary>
|
||||
public class HardwareAcceleratedInkProcessor
|
||||
{
|
||||
private readonly RenderTargetBitmap _renderTarget;
|
||||
private readonly DrawingVisual _drawingVisual;
|
||||
private readonly DrawingContext _drawingContext;
|
||||
private bool _isInitialized = false;
|
||||
|
||||
public HardwareAcceleratedInkProcessor(int width = 1920, int height = 1080)
|
||||
{
|
||||
// 创建硬件加速的渲染目标
|
||||
_renderTarget = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
|
||||
_drawingVisual = new DrawingVisual();
|
||||
|
||||
// 启用硬件加速
|
||||
RenderOptions.SetBitmapScalingMode(_drawingVisual, BitmapScalingMode.HighQuality);
|
||||
RenderOptions.SetEdgeMode(_drawingVisual, EdgeMode.Aliased);
|
||||
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用GPU加速的贝塞尔曲线平滑
|
||||
/// </summary>
|
||||
public async Task<Stroke> SmoothStrokeWithGPU(Stroke originalStroke)
|
||||
{
|
||||
if (!_isInitialized || originalStroke == null || originalStroke.StylusPoints.Count < 2)
|
||||
return originalStroke;
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 使用PathGeometry进行硬件加速的曲线拟合
|
||||
var pathGeometry = CreateSmoothPathGeometry(originalStroke.StylusPoints);
|
||||
|
||||
// 将PathGeometry转换回StylusPoint集合
|
||||
var smoothedPoints = ConvertPathGeometryToStylusPoints(pathGeometry, originalStroke.StylusPoints);
|
||||
|
||||
return new Stroke(new StylusPointCollection(smoothedPoints))
|
||||
{
|
||||
DrawingAttributes = originalStroke.DrawingAttributes.Clone()
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
return originalStroke;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建平滑的路径几何体
|
||||
/// </summary>
|
||||
private PathGeometry CreateSmoothPathGeometry(StylusPointCollection points)
|
||||
{
|
||||
var pathGeometry = new PathGeometry();
|
||||
var pathFigure = new PathFigure();
|
||||
|
||||
if (points.Count < 2) return pathGeometry;
|
||||
|
||||
pathFigure.StartPoint = new Point(points[0].X, points[0].Y);
|
||||
|
||||
// 使用贝塞尔曲线段创建平滑路径
|
||||
for (int i = 0; i < points.Count - 1; i += 3)
|
||||
{
|
||||
var p1 = i + 1 < points.Count ? new Point(points[i + 1].X, points[i + 1].Y) : pathFigure.StartPoint;
|
||||
var p2 = i + 2 < points.Count ? new Point(points[i + 2].X, points[i + 2].Y) : p1;
|
||||
var p3 = i + 3 < points.Count ? new Point(points[i + 3].X, points[i + 3].Y) : p2;
|
||||
|
||||
var bezierSegment = new BezierSegment(p1, p2, p3, true);
|
||||
pathFigure.Segments.Add(bezierSegment);
|
||||
}
|
||||
|
||||
pathGeometry.Figures.Add(pathFigure);
|
||||
return pathGeometry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将PathGeometry转换为StylusPoint集合
|
||||
/// </summary>
|
||||
private List<StylusPoint> ConvertPathGeometryToStylusPoints(PathGeometry pathGeometry, StylusPointCollection originalPoints)
|
||||
{
|
||||
var result = new List<StylusPoint>();
|
||||
var flattened = pathGeometry.GetFlattenedPathGeometry();
|
||||
|
||||
foreach (var figure in flattened.Figures)
|
||||
{
|
||||
result.Add(new StylusPoint(figure.StartPoint.X, figure.StartPoint.Y, 0.5f));
|
||||
|
||||
foreach (var segment in figure.Segments)
|
||||
{
|
||||
if (segment is LineSegment lineSegment)
|
||||
{
|
||||
result.Add(new StylusPoint(lineSegment.Point.X, lineSegment.Point.Y, 0.5f));
|
||||
}
|
||||
else if (segment is PolyLineSegment polyLineSegment)
|
||||
{
|
||||
foreach (var point in polyLineSegment.Points)
|
||||
{
|
||||
result.Add(new StylusPoint(point.X, point.Y, 0.5f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保持原始压感信息
|
||||
InterpolatePressure(result, originalPoints);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插值压感信息
|
||||
/// </summary>
|
||||
private void InterpolatePressure(List<StylusPoint> smoothedPoints, StylusPointCollection originalPoints)
|
||||
{
|
||||
if (originalPoints.Count == 0 || smoothedPoints.Count == 0) return;
|
||||
|
||||
for (int i = 0; i < smoothedPoints.Count; i++)
|
||||
{
|
||||
double ratio = (double)i / (smoothedPoints.Count - 1);
|
||||
int originalIndex = (int)(ratio * (originalPoints.Count - 1));
|
||||
originalIndex = Math.Max(0, Math.Min(originalIndex, originalPoints.Count - 1));
|
||||
|
||||
var point = smoothedPoints[i];
|
||||
float pressure = originalPoints[originalIndex].PressureFactor;
|
||||
smoothedPoints[i] = new StylusPoint(point.X, point.Y, Math.Max(pressure, 0.1f));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用GPU加速的并行贝塞尔计算
|
||||
/// </summary>
|
||||
public static StylusPoint[] ParallelBezierInterpolation(StylusPoint[] controlPoints, int segments = 16)
|
||||
{
|
||||
if (controlPoints.Length < 4) return controlPoints;
|
||||
|
||||
var result = new StylusPoint[segments * (controlPoints.Length / 4)];
|
||||
|
||||
Parallel.For(0, controlPoints.Length / 4, segmentIndex =>
|
||||
{
|
||||
var p0 = controlPoints[segmentIndex * 4];
|
||||
var p1 = controlPoints[segmentIndex * 4 + 1];
|
||||
var p2 = controlPoints[segmentIndex * 4 + 2];
|
||||
var p3 = controlPoints[segmentIndex * 4 + 3];
|
||||
|
||||
for (int i = 0; i < segments; i++)
|
||||
{
|
||||
double t = (double)i / (segments - 1);
|
||||
result[segmentIndex * segments + i] = CubicBezierFast(p0, p1, p2, p3, t);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 优化的三次贝塞尔曲线计算
|
||||
/// </summary>
|
||||
private static StylusPoint CubicBezierFast(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3, double t)
|
||||
{
|
||||
double u = 1 - t;
|
||||
double tt = t * t;
|
||||
double uu = u * u;
|
||||
double uuu = uu * u;
|
||||
double ttt = tt * t;
|
||||
|
||||
double x = uuu * p0.X + 3 * uu * t * p1.X + 3 * u * tt * p2.X + ttt * p3.X;
|
||||
double y = uuu * p0.Y + 3 * uu * t * p1.Y + 3 * u * tt * p2.Y + ttt * p3.Y;
|
||||
float pressure = (float)(p1.PressureFactor * u + p2.PressureFactor * t);
|
||||
|
||||
return new StylusPoint(x, y, Math.Max(pressure, 0.1f));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放GPU资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_drawingContext?.Close();
|
||||
_renderTarget?.Clear();
|
||||
_isInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 质量配置枚举
|
||||
/// </summary>
|
||||
public enum InkSmoothingQuality
|
||||
{
|
||||
HighPerformance = 0, // 高性能低质量
|
||||
Balanced = 1, // 平衡
|
||||
HighQuality = 2 // 高质量低性能
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹平滑配置
|
||||
/// </summary>
|
||||
public class InkSmoothingConfig
|
||||
{
|
||||
public InkSmoothingQuality Quality { get; set; } = InkSmoothingQuality.Balanced;
|
||||
public bool UseHardwareAcceleration { get; set; } = true;
|
||||
public bool UseAsyncProcessing { get; set; } = true;
|
||||
public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount;
|
||||
public double SmoothingStrength { get; set; } = 0.6;
|
||||
public double ResampleInterval { get; set; } = 1.2;
|
||||
public int InterpolationSteps { get; set; } = 16;
|
||||
|
||||
public static InkSmoothingConfig FromSettings()
|
||||
{
|
||||
return new InkSmoothingConfig
|
||||
{
|
||||
Quality = (InkSmoothingQuality)MainWindow.Settings.Canvas.InkSmoothingQuality,
|
||||
UseHardwareAcceleration = MainWindow.Settings.Canvas.UseHardwareAcceleration,
|
||||
UseAsyncProcessing = MainWindow.Settings.Canvas.UseAsyncInkSmoothing,
|
||||
MaxConcurrentTasks = MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks > 0 ?
|
||||
MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks : Environment.ProcessorCount
|
||||
};
|
||||
}
|
||||
|
||||
public void ApplyQualitySettings()
|
||||
{
|
||||
switch (Quality)
|
||||
{
|
||||
case InkSmoothingQuality.HighPerformance:
|
||||
SmoothingStrength = 0.4;
|
||||
ResampleInterval = 2.0;
|
||||
InterpolationSteps = 8;
|
||||
break;
|
||||
case InkSmoothingQuality.Balanced:
|
||||
SmoothingStrength = 0.6;
|
||||
ResampleInterval = 1.2;
|
||||
InterpolationSteps = 16;
|
||||
break;
|
||||
case InkSmoothingQuality.HighQuality:
|
||||
SmoothingStrength = 0.8;
|
||||
ResampleInterval = 0.8;
|
||||
InterpolationSteps = 32;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 统一的墨迹平滑管理器,整合异步处理和硬件加速
|
||||
/// </summary>
|
||||
public class InkSmoothingManager : IDisposable
|
||||
{
|
||||
private readonly AsyncAdvancedBezierSmoothing _asyncSmoothing;
|
||||
private readonly HardwareAcceleratedInkProcessor _hardwareProcessor;
|
||||
private readonly InkSmoothingPerformanceMonitor _performanceMonitor;
|
||||
private readonly InkSmoothingConfig _config;
|
||||
private readonly Dispatcher _uiDispatcher;
|
||||
private bool _disposed = false;
|
||||
|
||||
public InkSmoothingManager(Dispatcher uiDispatcher)
|
||||
{
|
||||
_uiDispatcher = uiDispatcher;
|
||||
_config = InkSmoothingConfig.FromSettings();
|
||||
_config.ApplyQualitySettings();
|
||||
|
||||
_asyncSmoothing = new AsyncAdvancedBezierSmoothing(uiDispatcher)
|
||||
{
|
||||
SmoothingStrength = _config.SmoothingStrength,
|
||||
ResampleInterval = _config.ResampleInterval,
|
||||
InterpolationSteps = _config.InterpolationSteps,
|
||||
UseHardwareAcceleration = _config.UseHardwareAcceleration,
|
||||
MaxConcurrentTasks = _config.MaxConcurrentTasks
|
||||
};
|
||||
|
||||
_hardwareProcessor = new HardwareAcceleratedInkProcessor();
|
||||
_performanceMonitor = new InkSmoothingPerformanceMonitor();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 平滑笔画(自动选择最佳方法)
|
||||
/// </summary>
|
||||
public async Task<Stroke> SmoothStrokeAsync(Stroke originalStroke,
|
||||
Action<Stroke, Stroke> onCompleted = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (originalStroke == null || originalStroke.StylusPoints.Count < 2)
|
||||
return originalStroke;
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
Stroke result = originalStroke;
|
||||
|
||||
try
|
||||
{
|
||||
if (_config.UseAsyncProcessing)
|
||||
{
|
||||
// 使用异步处理
|
||||
result = await _asyncSmoothing.SmoothStrokeAsync(originalStroke, onCompleted, cancellationToken);
|
||||
}
|
||||
else if (_config.UseHardwareAcceleration)
|
||||
{
|
||||
// 使用硬件加速但同步处理
|
||||
result = await _hardwareProcessor.SmoothStrokeWithGPU(originalStroke);
|
||||
onCompleted?.Invoke(originalStroke, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 回退到传统同步处理
|
||||
result = await Task.Run(() =>
|
||||
{
|
||||
var traditionalSmoothing = new AdvancedBezierSmoothing();
|
||||
return traditionalSmoothing.SmoothStroke(originalStroke);
|
||||
}, cancellationToken);
|
||||
onCompleted?.Invoke(originalStroke, result);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
result = originalStroke;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"墨迹平滑失败: {ex.Message}");
|
||||
result = originalStroke;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stopwatch.Stop();
|
||||
_performanceMonitor.RecordProcessingTime(stopwatch.Elapsed);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步平滑笔画(用于向后兼容)
|
||||
/// </summary>
|
||||
public Stroke SmoothStroke(Stroke originalStroke)
|
||||
{
|
||||
if (originalStroke == null || originalStroke.StylusPoints.Count < 2)
|
||||
return originalStroke;
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
Stroke result;
|
||||
|
||||
try
|
||||
{
|
||||
if (_config.UseHardwareAcceleration)
|
||||
{
|
||||
// 使用硬件加速的同步版本
|
||||
var task = _hardwareProcessor.SmoothStrokeWithGPU(originalStroke);
|
||||
task.Wait(5000); // 5秒超时
|
||||
result = task.Status == TaskStatus.RanToCompletion ? task.Result : originalStroke;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 传统同步处理
|
||||
var traditionalSmoothing = new AdvancedBezierSmoothing();
|
||||
result = traditionalSmoothing.SmoothStroke(originalStroke);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"同步墨迹平滑失败: {ex.Message}");
|
||||
result = originalStroke;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stopwatch.Stop();
|
||||
_performanceMonitor.RecordProcessingTime(stopwatch.Elapsed);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新配置
|
||||
/// </summary>
|
||||
public void UpdateConfig()
|
||||
{
|
||||
var newConfig = InkSmoothingConfig.FromSettings();
|
||||
newConfig.ApplyQualitySettings();
|
||||
|
||||
_asyncSmoothing.SmoothingStrength = newConfig.SmoothingStrength;
|
||||
_asyncSmoothing.ResampleInterval = newConfig.ResampleInterval;
|
||||
_asyncSmoothing.InterpolationSteps = newConfig.InterpolationSteps;
|
||||
_asyncSmoothing.UseHardwareAcceleration = newConfig.UseHardwareAcceleration;
|
||||
_asyncSmoothing.MaxConcurrentTasks = newConfig.MaxConcurrentTasks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取性能统计信息
|
||||
/// </summary>
|
||||
public string GetPerformanceStats()
|
||||
{
|
||||
return $"平均处理时间: {_performanceMonitor.GetAverageProcessingTimeMs():F2}ms, " +
|
||||
$"最大处理时间: {_performanceMonitor.GetMaxProcessingTimeMs():F2}ms, " +
|
||||
$"样本数: {_performanceMonitor.GetSampleCount()}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消所有正在进行的任务
|
||||
/// </summary>
|
||||
public void CancelAllTasks()
|
||||
{
|
||||
_asyncSmoothing?.CancelAllTasks();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查系统是否支持硬件加速
|
||||
/// </summary>
|
||||
public static bool IsHardwareAccelerationSupported()
|
||||
{
|
||||
try
|
||||
{
|
||||
return System.Windows.Media.RenderCapability.Tier >= 0x00020000;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取推荐的配置
|
||||
/// </summary>
|
||||
public static InkSmoothingConfig GetRecommendedConfig()
|
||||
{
|
||||
var config = new InkSmoothingConfig();
|
||||
|
||||
// 根据系统性能调整配置
|
||||
var processorCount = Environment.ProcessorCount;
|
||||
var isHardwareAccelerated = IsHardwareAccelerationSupported();
|
||||
|
||||
if (processorCount >= 8 && isHardwareAccelerated)
|
||||
{
|
||||
config.Quality = InkSmoothingQuality.HighQuality;
|
||||
config.UseHardwareAcceleration = true;
|
||||
config.UseAsyncProcessing = true;
|
||||
config.MaxConcurrentTasks = Math.Min(processorCount, 8);
|
||||
}
|
||||
else if (processorCount >= 4)
|
||||
{
|
||||
config.Quality = InkSmoothingQuality.Balanced;
|
||||
config.UseHardwareAcceleration = isHardwareAccelerated;
|
||||
config.UseAsyncProcessing = true;
|
||||
config.MaxConcurrentTasks = Math.Min(processorCount, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
config.Quality = InkSmoothingQuality.HighPerformance;
|
||||
config.UseHardwareAcceleration = false;
|
||||
config.UseAsyncProcessing = false;
|
||||
config.MaxConcurrentTasks = 1;
|
||||
}
|
||||
|
||||
config.ApplyQualitySettings();
|
||||
return config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用推荐配置到设置
|
||||
/// </summary>
|
||||
public static void ApplyRecommendedSettings()
|
||||
{
|
||||
var config = GetRecommendedConfig();
|
||||
|
||||
MainWindow.Settings.Canvas.InkSmoothingQuality = (int)config.Quality;
|
||||
MainWindow.Settings.Canvas.UseHardwareAcceleration = config.UseHardwareAcceleration;
|
||||
MainWindow.Settings.Canvas.UseAsyncInkSmoothing = config.UseAsyncProcessing;
|
||||
MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks = config.MaxConcurrentTasks;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
CancelAllTasks();
|
||||
_asyncSmoothing?.Dispose();
|
||||
_hardwareProcessor?.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹平滑事件参数
|
||||
/// </summary>
|
||||
public class InkSmoothingEventArgs : EventArgs
|
||||
{
|
||||
public Stroke OriginalStroke { get; set; }
|
||||
public Stroke SmoothedStroke { get; set; }
|
||||
public TimeSpan ProcessingTime { get; set; }
|
||||
public bool WasAsync { get; set; }
|
||||
public bool UsedHardwareAcceleration { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,60 @@
|
||||
<UserControl x:Class="Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher.LauncherSettingsControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher"
|
||||
mc:Ignorable="d"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="500" d:DesignWidth="600">
|
||||
|
||||
|
||||
<UserControl.Resources>
|
||||
<!-- 自定义按钮样式 -->
|
||||
<Style x:Key="DefaultButtonStyle" TargetType="Button">
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextElement.Foreground="{TemplateBinding Foreground}"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.8"/>
|
||||
<Setter Property="Effect">
|
||||
<Setter.Value>
|
||||
<DropShadowEffect Color="Black" Direction="270" ShadowDepth="2" Opacity="0.3" BlurRadius="4"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.6"/>
|
||||
<Setter Property="RenderTransform">
|
||||
<Setter.Value>
|
||||
<ScaleTransform ScaleX="0.95" ScaleY="0.95"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.4"/>
|
||||
<Setter Property="Cursor" Value="Arrow"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -16,12 +64,12 @@
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 标题 -->
|
||||
<TextBlock Grid.Row="0" Text="超级启动台设置" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="超级启动台设置" FontSize="16" FontWeight="Bold" Margin="0,0,0,15" Foreground="Black"/>
|
||||
|
||||
<!-- 基本设置 -->
|
||||
<StackPanel Grid.Row="1" Margin="0,0,0,15">
|
||||
<TextBlock Text="基本设置" FontSize="14" FontWeight="SemiBold" Margin="0,0,0,10"/>
|
||||
|
||||
<TextBlock Text="基本设置" FontSize="14" FontWeight="SemiBold" Margin="0,0,0,10" Foreground="Black"/>
|
||||
|
||||
<Grid Margin="10,0,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
@@ -30,12 +78,12 @@
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
|
||||
<!-- 按钮位置 -->
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="按钮位置:" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="按钮位置:" VerticalAlignment="Center" Foreground="Black"/>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" Margin="0,5">
|
||||
<RadioButton x:Name="RbtnLeft" Content="浮动栏左侧" Margin="0,0,20,0" Checked="RbtnPosition_Checked"/>
|
||||
<RadioButton x:Name="RbtnRight" Content="浮动栏右侧" IsChecked="True" Checked="RbtnPosition_Checked"/>
|
||||
<RadioButton x:Name="RbtnLeft" Content="浮动栏左侧" Margin="0,0,20,0" Checked="RbtnPosition_Checked" Foreground="Black"/>
|
||||
<RadioButton x:Name="RbtnRight" Content="浮动栏右侧" IsChecked="True" Checked="RbtnPosition_Checked" Foreground="Black"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
@@ -47,7 +95,7 @@
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="应用管理" FontSize="14" FontWeight="SemiBold" Margin="0,0,0,10"/>
|
||||
<TextBlock Grid.Row="0" Text="应用管理" FontSize="14" FontWeight="SemiBold" Margin="0,0,0,10" Foreground="Black"/>
|
||||
|
||||
<Border Grid.Row="1" BorderThickness="1" BorderBrush="#CCCCCC" CornerRadius="5">
|
||||
<Grid>
|
||||
@@ -71,9 +119,15 @@
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="5">
|
||||
<Button x:Name="BtnAdd" Content="添加" Padding="10,5" Margin="0,5,5,5" Click="BtnAdd_Click"/>
|
||||
<Button x:Name="BtnEdit" Content="编辑" Padding="10,5" Margin="5" Click="BtnEdit_Click"/>
|
||||
<Button x:Name="BtnDelete" Content="删除" Padding="10,5" Margin="5" Click="BtnDelete_Click"/>
|
||||
<Button x:Name="BtnAdd" Content="添加" Padding="10,5" Margin="0,5,5,5" Click="BtnAdd_Click"
|
||||
Background="#FF007ACC" Foreground="White" BorderBrush="#FF005A9B" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
<Button x:Name="BtnEdit" Content="编辑" Padding="10,5" Margin="5" Click="BtnEdit_Click"
|
||||
Background="#FF6C757D" Foreground="White" BorderBrush="#FF5A6268" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
<Button x:Name="BtnDelete" Content="删除" Padding="10,5" Margin="5" Click="BtnDelete_Click"
|
||||
Background="#FFDC3545" Foreground="White" BorderBrush="#FFBD2130" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
@@ -81,7 +135,9 @@
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,15,0,0">
|
||||
<Button x:Name="BtnSave" Content="保存设置" Padding="15,5" Click="BtnSave_Click"/>
|
||||
<Button x:Name="BtnSave" Content="保存设置" Padding="15,5" Click="BtnSave_Click"
|
||||
Background="#FF28A745" Foreground="White" BorderBrush="#FF1E7E34" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -2594,6 +2594,38 @@
|
||||
<TextBlock x:Name="AppVersionTextBlock" FontSize="18" FontWeight="Bold"
|
||||
Text="1.X.X.X" />
|
||||
</ui:SimpleStackPanel>
|
||||
|
||||
<!-- 设备信息 -->
|
||||
<Border BorderBrush="#a1a1aa" BorderThickness="1" CornerRadius="8"
|
||||
Background="#18181b" Padding="12">
|
||||
<ui:SimpleStackPanel Spacing="8">
|
||||
<TextBlock Text="设备信息" FontWeight="Bold" Foreground="#fafafa" FontSize="14" />
|
||||
<ui:SimpleStackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="设备ID:" Foreground="#a1a1aa" FontSize="12" />
|
||||
<TextBlock x:Name="DeviceIdTextBlock" Text="正在获取..." Foreground="#fafafa" FontSize="12" />
|
||||
</ui:SimpleStackPanel>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="使用频率:" Foreground="#a1a1aa" FontSize="12" />
|
||||
<TextBlock x:Name="UsageFrequencyTextBlock" Text="正在获取..." Foreground="#fafafa" FontSize="12" />
|
||||
</ui:SimpleStackPanel>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="更新优先级:" Foreground="#a1a1aa" FontSize="12" />
|
||||
<TextBlock x:Name="UpdatePriorityTextBlock" Text="正在获取..." Foreground="#fafafa" FontSize="12" />
|
||||
</ui:SimpleStackPanel>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="启动次数:" Foreground="#a1a1aa" FontSize="12" />
|
||||
<TextBlock x:Name="LaunchCountTextBlock" Text="正在获取..." Foreground="#fafafa" FontSize="12" />
|
||||
</ui:SimpleStackPanel>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="总使用时长:" Foreground="#a1a1aa" FontSize="12" />
|
||||
<TextBlock x:Name="TotalUsageTextBlock" Text="正在获取..." Foreground="#fafafa" FontSize="12" />
|
||||
</ui:SimpleStackPanel>
|
||||
<Button x:Name="RefreshDeviceInfoButton" Content="刷新设备信息"
|
||||
Width="120" Height="30" Margin="0,8,0,0"
|
||||
Click="RefreshDeviceInfo_Click" />
|
||||
</ui:SimpleStackPanel>
|
||||
</Border>
|
||||
|
||||
<TextBlock
|
||||
Text="# 使用和分发本软件前,请您应当且务必知晓相关开源协议,且您应当知晓本软件基于 https://github.com/WXRIW/Ink-Canvas 修改而成。"
|
||||
TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||
|
||||
@@ -110,6 +110,9 @@ namespace Ink_Canvas {
|
||||
CheckColorTheme(true);
|
||||
CheckPenTypeUIState();
|
||||
|
||||
// 初始化墨迹平滑管理器
|
||||
_inkSmoothingManager = new Helpers.InkSmoothingManager(Dispatcher);
|
||||
|
||||
// 注册输入事件
|
||||
inkCanvas.PreviewMouseDown += inkCanvas_PreviewMouseDown;
|
||||
inkCanvas.StylusDown += inkCanvas_StylusDown;
|
||||
@@ -182,6 +185,7 @@ namespace Ink_Canvas {
|
||||
private System.Windows.Media.Color Ink_DefaultColor = Colors.Red;
|
||||
|
||||
private DrawingAttributes drawingAttributes;
|
||||
private Helpers.InkSmoothingManager _inkSmoothingManager;
|
||||
|
||||
private void loadPenCanvas() {
|
||||
try {
|
||||
@@ -854,6 +858,8 @@ namespace Ink_Canvas {
|
||||
{
|
||||
// 切换到关于页面
|
||||
ShowSettingsSection("about");
|
||||
// 刷新设备信息
|
||||
RefreshDeviceInfo();
|
||||
}
|
||||
|
||||
// 新增:个性化设置
|
||||
@@ -881,6 +887,99 @@ namespace Ink_Canvas {
|
||||
BorderSettingsMask.Background = null; // 确保清除蒙层背景
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新设备信息按钮点击事件
|
||||
/// </summary>
|
||||
private void RefreshDeviceInfo_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
RefreshDeviceInfo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新设备信息显示
|
||||
/// </summary>
|
||||
private void RefreshDeviceInfo()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取设备ID
|
||||
string deviceId = DeviceIdentifier.GetDeviceId();
|
||||
DeviceIdTextBlock.Text = deviceId;
|
||||
|
||||
// 获取使用频率
|
||||
var usageFrequency = DeviceIdentifier.GetUsageFrequency();
|
||||
string frequencyText;
|
||||
switch (usageFrequency)
|
||||
{
|
||||
case DeviceIdentifier.UsageFrequency.High:
|
||||
frequencyText = "高频用户";
|
||||
break;
|
||||
case DeviceIdentifier.UsageFrequency.Medium:
|
||||
frequencyText = "中频用户";
|
||||
break;
|
||||
case DeviceIdentifier.UsageFrequency.Low:
|
||||
frequencyText = "低频用户";
|
||||
break;
|
||||
default:
|
||||
frequencyText = "未知";
|
||||
break;
|
||||
}
|
||||
UsageFrequencyTextBlock.Text = frequencyText;
|
||||
|
||||
// 获取更新优先级
|
||||
var updatePriority = DeviceIdentifier.GetUpdatePriority();
|
||||
string priorityText;
|
||||
switch (updatePriority)
|
||||
{
|
||||
case DeviceIdentifier.UpdatePriority.High:
|
||||
priorityText = "高优先级(优先推送更新)";
|
||||
break;
|
||||
case DeviceIdentifier.UpdatePriority.Medium:
|
||||
priorityText = "中优先级(正常推送更新)";
|
||||
break;
|
||||
case DeviceIdentifier.UpdatePriority.Low:
|
||||
priorityText = "低优先级(延迟推送更新)";
|
||||
break;
|
||||
default:
|
||||
priorityText = "未知";
|
||||
break;
|
||||
}
|
||||
UpdatePriorityTextBlock.Text = priorityText;
|
||||
|
||||
// 获取使用统计
|
||||
var (launchCount, totalMinutes, avgSession, _) = DeviceIdentifier.GetUsageStats();
|
||||
LaunchCountTextBlock.Text = launchCount.ToString();
|
||||
|
||||
string totalUsageText;
|
||||
if (totalMinutes < 60)
|
||||
{
|
||||
totalUsageText = $"{totalMinutes}分钟";
|
||||
}
|
||||
else if (totalMinutes < 1440)
|
||||
{
|
||||
totalUsageText = $"{totalMinutes / 60}小时{totalMinutes % 60}分钟";
|
||||
}
|
||||
else
|
||||
{
|
||||
totalUsageText = $"{totalMinutes / 1440}天{(totalMinutes % 1440) / 60}小时";
|
||||
}
|
||||
TotalUsageTextBlock.Text = totalUsageText;
|
||||
|
||||
LogHelper.WriteLogToFile($"MainWindow | 设备信息已刷新 - ID: {deviceId}, 频率: {frequencyText}, 优先级: {priorityText}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"MainWindow | 刷新设备信息失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
|
||||
// 显示错误信息
|
||||
DeviceIdTextBlock.Text = "获取失败";
|
||||
UsageFrequencyTextBlock.Text = "获取失败";
|
||||
UpdatePriorityTextBlock.Text = "获取失败";
|
||||
LaunchCountTextBlock.Text = "获取失败";
|
||||
TotalUsageTextBlock.Text = "获取失败";
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:折叠侧边栏
|
||||
private void CollapseNavSidebar_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
||||
@@ -1270,10 +1270,27 @@ namespace Ink_Canvas {
|
||||
LogHelper.WriteLogToFile($"结束PPT放映操作异常: {ex.ToString()}", LogHelper.LogType.Error);
|
||||
}
|
||||
await Application.Current.Dispatcher.InvokeAsync(() => {
|
||||
// 隐藏侧边栏退出按钮
|
||||
if (BtnExitPptFromSidebarLeft != null)
|
||||
BtnExitPptFromSidebarLeft.Visibility = Visibility.Collapsed;
|
||||
if (BtnExitPptFromSidebarRight != null)
|
||||
BtnExitPptFromSidebarRight.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 确保所有放映模式按钮都被隐藏,防止PptApplication_SlideShowEnd事件未触发的情况
|
||||
try {
|
||||
BtnPPTSlideShow.Visibility = Visibility.Visible;
|
||||
BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed;
|
||||
StackPanelPPTControls.Visibility = Visibility.Collapsed;
|
||||
LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
|
||||
LogHelper.WriteLogToFile("手动隐藏所有放映模式按钮", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
LogHelper.WriteLogToFile($"手动隐藏放映模式按钮失败: {ex.ToString()}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1298,7 +1298,7 @@ namespace Ink_Canvas {
|
||||
private void ToggleSwitchAdvancedBezierSmoothing_Toggled(object sender, RoutedEventArgs e) {
|
||||
if (!isLoaded) return;
|
||||
Settings.Canvas.UseAdvancedBezierSmoothing = ToggleSwitchAdvancedBezierSmoothing.IsOn;
|
||||
|
||||
|
||||
// 启用高级贝塞尔平滑时自动禁用原来的FitToCurve
|
||||
if (ToggleSwitchAdvancedBezierSmoothing.IsOn)
|
||||
{
|
||||
@@ -1306,9 +1306,61 @@ namespace Ink_Canvas {
|
||||
Settings.Canvas.FitToCurve = false;
|
||||
drawingAttributes.FitToCurve = false;
|
||||
}
|
||||
|
||||
|
||||
// 更新墨迹平滑管理器配置
|
||||
_inkSmoothingManager?.UpdateConfig();
|
||||
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
// 注释掉这些方法,因为对应的UI控件还没有在XAML中定义
|
||||
/*
|
||||
private void ToggleSwitchAsyncInkSmoothing_Toggled(object sender, RoutedEventArgs e) {
|
||||
if (!isLoaded) return;
|
||||
Settings.Canvas.UseAsyncInkSmoothing = ToggleSwitchAsyncInkSmoothing.IsOn;
|
||||
_inkSmoothingManager?.UpdateConfig();
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchHardwareAcceleration_Toggled(object sender, RoutedEventArgs e) {
|
||||
if (!isLoaded) return;
|
||||
Settings.Canvas.UseHardwareAcceleration = ToggleSwitchHardwareAcceleration.IsOn;
|
||||
_inkSmoothingManager?.UpdateConfig();
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ComboBoxInkSmoothingQuality_SelectionChanged(object sender, SelectionChangedEventArgs e) {
|
||||
if (!isLoaded) return;
|
||||
Settings.Canvas.InkSmoothingQuality = ComboBoxInkSmoothingQuality.SelectedIndex;
|
||||
_inkSmoothingManager?.UpdateConfig();
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void SliderMaxConcurrentTasks_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
|
||||
if (!isLoaded) return;
|
||||
Settings.Canvas.MaxConcurrentSmoothingTasks = (int)SliderMaxConcurrentTasks.Value;
|
||||
_inkSmoothingManager?.UpdateConfig();
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ButtonApplyRecommendedSettings_Click(object sender, RoutedEventArgs e) {
|
||||
// 应用推荐的性能设置
|
||||
Helpers.InkSmoothingManager.ApplyRecommendedSettings();
|
||||
LoadSettings(false);
|
||||
_inkSmoothingManager?.UpdateConfig();
|
||||
SaveSettingsToFile();
|
||||
|
||||
ShowNotification("已应用推荐的性能设置");
|
||||
}
|
||||
|
||||
private void ButtonShowPerformanceStats_Click(object sender, RoutedEventArgs e) {
|
||||
if (_inkSmoothingManager != null)
|
||||
{
|
||||
var stats = _inkSmoothingManager.GetPerformanceStats();
|
||||
ShowNotification($"性能统计: {stats}");
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
private void ToggleSwitchAutoSaveStrokesInPowerPoint_Toggled(object sender, RoutedEventArgs e) {
|
||||
if (!isLoaded) return;
|
||||
|
||||
@@ -538,6 +538,23 @@ namespace Ink_Canvas {
|
||||
ToggleSwitchAdvancedBezierSmoothing.IsOn = false;
|
||||
drawingAttributes.FitToCurve = false;
|
||||
}
|
||||
|
||||
// 注释掉新的墨迹平滑性能设置,因为UI控件还没有定义
|
||||
/*
|
||||
// 初始化新的墨迹平滑性能设置
|
||||
ToggleSwitchAsyncInkSmoothing.IsOn = Settings.Canvas.UseAsyncInkSmoothing;
|
||||
ToggleSwitchHardwareAcceleration.IsOn = Settings.Canvas.UseHardwareAcceleration;
|
||||
ComboBoxInkSmoothingQuality.SelectedIndex = Settings.Canvas.InkSmoothingQuality;
|
||||
SliderMaxConcurrentTasks.Value = Settings.Canvas.MaxConcurrentSmoothingTasks > 0 ?
|
||||
Settings.Canvas.MaxConcurrentSmoothingTasks : Environment.ProcessorCount;
|
||||
|
||||
// 检查硬件加速支持
|
||||
if (!Helpers.InkSmoothingManager.IsHardwareAccelerationSupported())
|
||||
{
|
||||
ToggleSwitchHardwareAcceleration.IsEnabled = false;
|
||||
// 可以添加提示文本说明硬件加速不可用
|
||||
}
|
||||
*/
|
||||
|
||||
// 初始化直线自动拉直相关设置
|
||||
ToggleSwitchAutoStraightenLine.IsOn = Settings.Canvas.AutoStraightenLine;
|
||||
|
||||
@@ -1579,17 +1579,16 @@ namespace Ink_Canvas {
|
||||
{
|
||||
try
|
||||
{
|
||||
var advancedSmoothing = new Helpers.AdvancedBezierSmoothing
|
||||
{
|
||||
};
|
||||
|
||||
// 对临时笔画应用平滑
|
||||
if (lastTempStroke != null)
|
||||
if (lastTempStroke != null && _inkSmoothingManager != null)
|
||||
{
|
||||
var smoothedStroke = advancedSmoothing.SmoothStroke(lastTempStroke);
|
||||
inkCanvas.Strokes.Remove(lastTempStroke);
|
||||
lastTempStroke = smoothedStroke;
|
||||
inkCanvas.Strokes.Add(smoothedStroke);
|
||||
var smoothedStroke = _inkSmoothingManager.SmoothStroke(lastTempStroke);
|
||||
if (smoothedStroke != lastTempStroke)
|
||||
{
|
||||
inkCanvas.Strokes.Remove(lastTempStroke);
|
||||
lastTempStroke = smoothedStroke;
|
||||
inkCanvas.Strokes.Add(smoothedStroke);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Ink;
|
||||
@@ -587,18 +588,27 @@ namespace Ink_Canvas {
|
||||
// 检查原始笔画是否仍然存在于画布中
|
||||
if (inkCanvas.Strokes.Contains(e.Stroke))
|
||||
{
|
||||
var advancedSmoothing = new Helpers.AdvancedBezierSmoothing
|
||||
// 使用新的异步墨迹平滑管理器
|
||||
if (Settings.Canvas.UseAsyncInkSmoothing && _inkSmoothingManager != null)
|
||||
{
|
||||
};
|
||||
// 异步处理
|
||||
_ = ProcessStrokeAsync(e.Stroke);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 同步处理(向后兼容)
|
||||
var smoothedStroke = _inkSmoothingManager?.SmoothStroke(e.Stroke) ?? e.Stroke;
|
||||
|
||||
var smoothedStroke = advancedSmoothing.SmoothStroke(e.Stroke);
|
||||
|
||||
// 替换原始笔画
|
||||
SetNewBackupOfStroke();
|
||||
_currentCommitType = CommitReason.ShapeRecognition;
|
||||
inkCanvas.Strokes.Remove(e.Stroke);
|
||||
inkCanvas.Strokes.Add(smoothedStroke);
|
||||
_currentCommitType = CommitReason.UserInput;
|
||||
if (smoothedStroke != e.Stroke)
|
||||
{
|
||||
// 替换原始笔画
|
||||
SetNewBackupOfStroke();
|
||||
_currentCommitType = CommitReason.ShapeRecognition;
|
||||
inkCanvas.Strokes.Remove(e.Stroke);
|
||||
inkCanvas.Strokes.Add(smoothedStroke);
|
||||
_currentCommitType = CommitReason.UserInput;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -613,6 +623,32 @@ namespace Ink_Canvas {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步处理笔画平滑
|
||||
/// </summary>
|
||||
private async Task ProcessStrokeAsync(Stroke originalStroke)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _inkSmoothingManager.SmoothStrokeAsync(originalStroke, (original, smoothed) =>
|
||||
{
|
||||
// 在UI线程上执行笔画替换
|
||||
if (inkCanvas.Strokes.Contains(original) && smoothed != original)
|
||||
{
|
||||
SetNewBackupOfStroke();
|
||||
_currentCommitType = CommitReason.ShapeRecognition;
|
||||
inkCanvas.Strokes.Remove(original);
|
||||
inkCanvas.Strokes.Add(smoothed);
|
||||
_currentCommitType = CommitReason.UserInput;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"异步墨迹平滑失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// New method: Checks if a stroke is potentially a straight line
|
||||
private bool IsPotentialStraightLine(Stroke stroke) {
|
||||
// 确保有足够的点来进行线条分析
|
||||
|
||||
@@ -49,5 +49,5 @@ using System.Windows;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.7.2.0")]
|
||||
[assembly: AssemblyFileVersion("1.7.2.0")]
|
||||
[assembly: AssemblyVersion("1.7.3.0")]
|
||||
[assembly: AssemblyFileVersion("1.7.3.0")]
|
||||
|
||||
@@ -50,6 +50,14 @@ namespace Ink_Canvas
|
||||
public bool FitToCurve { get; set; } = false; // 默认关闭原来的贝塞尔平滑
|
||||
[JsonProperty("useAdvancedBezierSmoothing")]
|
||||
public bool UseAdvancedBezierSmoothing { get; set; } = true; // 默认启用高级贝塞尔曲线平滑
|
||||
[JsonProperty("useAsyncInkSmoothing")]
|
||||
public bool UseAsyncInkSmoothing { get; set; } = true; // 默认启用异步墨迹平滑
|
||||
[JsonProperty("useHardwareAcceleration")]
|
||||
public bool UseHardwareAcceleration { get; set; } = true; // 默认启用硬件加速
|
||||
[JsonProperty("inkSmoothingQuality")]
|
||||
public int InkSmoothingQuality { get; set; } = 1; // 0-低质量高性能, 1-平衡, 2-高质量低性能
|
||||
[JsonProperty("maxConcurrentSmoothingTasks")]
|
||||
public int MaxConcurrentSmoothingTasks { get; set; } = 0; // 0表示自动检测CPU核心数
|
||||
[JsonProperty("clearCanvasAndClearTimeMachine")]
|
||||
public bool ClearCanvasAndClearTimeMachine { get; set; } = false;
|
||||
[JsonProperty("enablePressureTouchMode")]
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="插件列表" Margin="10,10,0,5" FontSize="16" FontWeight="SemiBold"/>
|
||||
<TextBlock Grid.Row="0" Text="插件列表" Margin="10,10,0,5" FontSize="16" FontWeight="SemiBold" Foreground="Black"/>
|
||||
|
||||
<ListView Grid.Row="1" x:Name="PluginListView" BorderThickness="0" Margin="0,5,0,0"
|
||||
SelectionChanged="PluginListView_SelectionChanged">
|
||||
@@ -101,17 +101,17 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="名称:" FontWeight="SemiBold" Margin="0,0,0,5"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Name}" Margin="0,0,0,5"/>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="版本:" FontWeight="SemiBold" Margin="0,0,0,5"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Version}" Margin="0,0,0,5"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="作者:" FontWeight="SemiBold" Margin="0,0,0,5"/>
|
||||
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Author}" Margin="0,0,0,5"/>
|
||||
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Text="描述:" FontWeight="SemiBold" Margin="0,0,0,5"/>
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding Description}" TextWrapping="Wrap" Margin="0,0,0,5"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="名称:" FontWeight="SemiBold" Margin="0,0,0,5" Foreground="Black"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Name}" Margin="0,0,0,5" Foreground="Black"/>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="版本:" FontWeight="SemiBold" Margin="0,0,0,5" Foreground="Black"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Version}" Margin="0,0,0,5" Foreground="Black"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="作者:" FontWeight="SemiBold" Margin="0,0,0,5" Foreground="Black"/>
|
||||
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Author}" Margin="0,0,0,5" Foreground="Black"/>
|
||||
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Text="描述:" FontWeight="SemiBold" Margin="0,0,0,5" Foreground="Black"/>
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding Description}" TextWrapping="Wrap" Margin="0,0,0,5" Foreground="Black"/>
|
||||
|
||||
<Button Grid.Row="3" Grid.Column="2" x:Name="BtnDeletePlugin" Content="删除插件"
|
||||
Visibility="Collapsed" Click="BtnDeletePlugin_Click"
|
||||
@@ -123,7 +123,7 @@
|
||||
<!-- 插件设置区域 -->
|
||||
<Border Grid.Row="1" BorderThickness="1" BorderBrush="{DynamicResource BorderBrush}" Margin="0,10,0,0" Padding="15" CornerRadius="5">
|
||||
<StackPanel>
|
||||
<TextBlock Text="插件设置" FontSize="16" FontWeight="SemiBold" Margin="0,0,0,10"/>
|
||||
<TextBlock Text="插件设置" FontSize="16" FontWeight="SemiBold" Margin="0,0,0,10" Foreground="Black"/>
|
||||
<ContentControl x:Name="PluginSettingsContainer" MinHeight="50"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -12,7 +12,7 @@ TRACE;DEBUG;NETFRAMEWORK;NET472;;NET30_OR_GREATER;NET35_OR_GREATER;NET40_OR_GREA
|
||||
E:\ICC CE\ICC CE main\community\Ink Canvas\App.xaml
|
||||
22-2143008179
|
||||
|
||||
76-141727233
|
||||
79-461684434
|
||||
471037513499
|
||||
Helpers\Plugins\BuiltIn\SuperLauncher\LauncherSettingsControl.xaml;Helpers\Plugins\BuiltIn\SuperLauncher\LauncherWindow.xaml;MainWindow.xaml;MainWindow_cs\MW_Eraser.xaml;Resources\DrawShapeImageDictionary.xaml;Resources\IconImageDictionary.xaml;Resources\SeewoImageDictionary.xaml;Resources\Styles\Dark.xaml;Resources\Styles\Light.xaml;Windows\AddCustomIconWindow.xaml;Windows\AddPickNameBackgroundWindow.xaml;Windows\CountdownTimerWindow.xaml;Windows\CustomIconWindow.xaml;Windows\CycleProcessBar.xaml;Windows\HasNewUpdateWindow.xaml;Windows\HistoryRollbackWindow.xaml;Windows\ManagePickNameBackgroundsWindow.xaml;Windows\NamesInputWindow.xaml;Windows\OperatingGuideWindow.xaml;Windows\PluginSettingsWindow.xaml;Windows\RandWindow.xaml;Windows\YesOrNoNotificationWindow.xaml;
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
使用该版本 InkCanvasForClass,意味着您同意自行承担任何可能存在的问题与风险。建议不要在公众场合(例如公开课,录播课,线上直播课,大型会议)使用未经广泛测试和优化的 Beta 版本,对使用 Beta 版本而带来的任何问题和风险(例如:被班主任批斗,被校长处罚,崩溃而导致的场面混乱,全球海平面上升等),**将由使用者自行承担**,[CJKmkp](https://github,com/CJK-mkp) 和 [DotteringDoge471](https://github.com/DotteringDoge471) 及其项目的所有维护者不提供任何担保。
|
||||
|
||||
♥️ **本项目版权归 [CJKmkp](https://github,com/CJK-mkp) 和 [DotteringDoge471](https://github.com/DotteringDoge471) 共同所有。[CJKmkp](https://github,com/CJK-mkp) 拥有最终解释权。**
|
||||
♥️ **本项目版权归 [CJKmkp](https://github,com/CJK-mkp) 所有。[CJKmkp](https://github,com/CJK-mkp) 拥有最终解释权。**
|
||||
|
||||
**智教联盟 InkCanvasForClass Community Edition 板块:** [forum.smart-teach.cn/t/icc-ce](https://forum.smart-teach.cn/t/icc-ce) ,我们会在此处发布版本更新日志,同时,您也可以在遵守论坛对应管理规则与InkCanvasForClass Community Edition 板块管理条约的情况下,在该板块内提问或发表自己的使用体验。
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
2. 在“信息”标签内,点击右侧的“启用编辑”按钮。
|
||||
2. 曾经安装过 WPS Office 办公软件,导致 COM 组件被破坏,解决方法为完全卸载 WPS Office 后重新安装 Microsoft Office Mondo 2016 即可解决。
|
||||
3. 请确保 PowerPoint 和本应用运行在同一权限下,如果 PowerPoint 以管理员身份运行而本应用以普通用户身份运行,也会出现无法切换到 PPT 模式的现象,您可以通过检查 PowerPoint 的兼容性设置或提权本应用运行来解决该问题。
|
||||
4. 如果上述方法不能解决你的问题,请参考这个链接[【点击此处以跳转】](https://www.inkeys.top/tutorial/ppt-com.html)
|
||||
|
||||
### 程序无法正常启动
|
||||
请检查你的电脑上是否安装了 `.Net Framework 4.7.2` 或更高版本。若没有,请[前往官网](https://dotnet.microsoft.com/zh-cn/download/dotnet-framework/thank-you/net472-offline-installer "下载 .Net Framework 4.7.2")下载安装。
|
||||
@@ -86,6 +87,10 @@
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CJKmkp"><img src="https://avatars.githubusercontent.com/u/113243675?v=4?s=100" width="100px;" alt="CJK_mkp"/><br /><sub><b>CJK_mkp</b></sub></a><br /><a href="#maintenance-CJKmkp" title="Maintenance">🚧</a> <a href="#doc-CJKmkp" title="Documentation">📖</a> <a href="#code-CJKmkp" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://hydro11451.qzz.io"><img src="https://avatars.githubusercontent.com/u/214308559?v=4?s=100" width="100px;" alt="Hydrogen"/><br /><sub><b>Hydrogen</b></sub></a><br /><a href="#code-Hydro11451" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CreeperAWA"><img src="https://avatars.githubusercontent.com/u/134939494?v=4?s=100" width="100px;" alt="CreeperAWA"/><br /><sub><b>CreeperAWA</b></sub></a><br /><a href="#code-CreeperAWA" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/2-2-3-trimethylpentane"><img src="https://avatars.githubusercontent.com/u/141403762?v=4?s=100" width="100px;" alt="2,2,3-三甲基戊烷"/><br /><sub><b>2,2,3-三甲基戊烷</b></sub></a><br /><a href="#blog-2-2-3-trimethylpentane" title="Blogposts">📝</a> <a href="#doc-2-2-3-trimethylpentane" title="Documentation">📖</a> <a href="#design-2-2-3-trimethylpentane" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Alan-CRL"><img src="https://avatars.githubusercontent.com/u/92425617?v=4?s=100" width="100px;" alt="Alan-CRL"/><br /><sub><b>Alan-CRL</b></sub></a><br /><a href="#code-Alan-CRL" title="Code">💻</a> <a href="#infra-Alan-CRL" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#doc-Alan-CRL" title="Documentation">📖</a> <a href="#financial-Alan-CRL" title="Financial">💵</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MKStoler1024"><img src="https://avatars.githubusercontent.com/u/158786854?v=4?s=100" width="100px;" alt="MKStoler1024"/><br /><sub><b>MKStoler1024</b></sub></a><br /><a href="#doc-MKStoler1024" title="Documentation">📖</a> <a href="#code-MKStoler1024" title="Code">💻</a> <a href="#design-MKStoler1024" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/awesome-iwb"><img src="https://avatars.githubusercontent.com/u/184760810?v=4?s=100" width="100px;" alt="Awesome Iwb"/><br /><sub><b>Awesome Iwb</b></sub></a><br /><a href="#doc-awesome-iwb" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -29,3 +29,5 @@
|
||||
29. 修复墨迹错页
|
||||
30. 改进直线拉直
|
||||
31. 改进白板时间显示
|
||||
32. 修复快速面板中的退出放映按钮
|
||||
33. 优化多墨迹卡顿问题
|
||||
|
||||
Reference in New Issue
Block a user