add:实时笔锋及墨迹预测
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
<Window Name="window" x:Class="Ink_Canvas.MainWindow"
|
<Window Name="window" x:Class="Ink_Canvas.MainWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
@@ -836,6 +836,22 @@
|
|||||||
Toggled="ToggleSwitchDisablePressure_Toggled" />
|
Toggled="ToggleSwitchDisablePressure_Toggled" />
|
||||||
</ikw:SimpleStackPanel>
|
</ikw:SimpleStackPanel>
|
||||||
<TextBlock Text="{i18n:I18n Key=Canvas_DisablePressureHint}" TextWrapping="Wrap" Foreground="#a1a1aa" />
|
<TextBlock Text="{i18n:I18n Key=Canvas_DisablePressureHint}" TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||||
|
<ikw:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||||
|
<TextBlock Foreground="#fafafa" Text="{i18n:I18n Key=Canvas_InkStrokePrediction}" VerticalAlignment="Center"
|
||||||
|
FontSize="14" Margin="0,0,16,0" />
|
||||||
|
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchEnableInkStrokePrediction"
|
||||||
|
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
|
||||||
|
Toggled="ToggleSwitchEnableInkStrokePrediction_Toggled" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
<TextBlock Text="{i18n:I18n Key=Canvas_InkStrokePredictionHint}" TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||||
|
<ikw:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||||
|
<TextBlock Foreground="#fafafa" Text="{i18n:I18n Key=Canvas_VelocityBrushTip}" VerticalAlignment="Center"
|
||||||
|
FontSize="14" Margin="0,0,16,0" />
|
||||||
|
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchEnableVelocityBrushTip"
|
||||||
|
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
|
||||||
|
Toggled="ToggleSwitchEnableVelocityBrushTip_Toggled" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
<TextBlock Text="{i18n:I18n Key=Canvas_VelocityBrushTipHint}" TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||||
|
|
||||||
<ikw:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
<ikw:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||||
<TextBlock Foreground="#fafafa" Text="{i18n:I18n Key=Canvas_EraserSize}" VerticalAlignment="Center"
|
<TextBlock Foreground="#fafafa" Text="{i18n:I18n Key=Canvas_EraserSize}" VerticalAlignment="Center"
|
||||||
@@ -4605,6 +4621,9 @@
|
|||||||
PreviewTouchDown="InkCanvas_PreviewTouchDown"
|
PreviewTouchDown="InkCanvas_PreviewTouchDown"
|
||||||
PreviewTouchMove="InkCanvas_PreviewTouchMove"
|
PreviewTouchMove="InkCanvas_PreviewTouchMove"
|
||||||
PreviewTouchUp="InkCanvas_PreviewTouchUp"
|
PreviewTouchUp="InkCanvas_PreviewTouchUp"
|
||||||
|
PreviewStylusMove="inkCanvas_PreviewStylusMove"
|
||||||
|
PreviewMouseMove="inkCanvas_PreviewMouseMoveForPrediction"
|
||||||
|
LostStylusCapture="inkCanvas_LostStylusCapture"
|
||||||
MouseDown="inkCanvas_MouseDown"
|
MouseDown="inkCanvas_MouseDown"
|
||||||
MouseMove="inkCanvas_MouseMove"
|
MouseMove="inkCanvas_MouseMove"
|
||||||
MouseUp="inkCanvas_MouseUp"
|
MouseUp="inkCanvas_MouseUp"
|
||||||
@@ -4614,6 +4633,12 @@
|
|||||||
ClipToBounds="False"
|
ClipToBounds="False"
|
||||||
Background="Transparent" />
|
Background="Transparent" />
|
||||||
|
|
||||||
|
<Canvas x:Name="InkPredictionOverlay" Background="Transparent" IsHitTestVisible="False"
|
||||||
|
Panel.ZIndex="900">
|
||||||
|
<Polyline x:Name="InkPredictionPolyline" Visibility="Collapsed" StrokeLineJoin="Round"
|
||||||
|
StrokeStartLineCap="Round" StrokeEndLineCap="Round" />
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
<Canvas x:Name="EraserOverlayCanvas"
|
<Canvas x:Name="EraserOverlayCanvas"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
IsHitTestVisible="False"
|
IsHitTestVisible="False"
|
||||||
|
|||||||
@@ -1156,6 +1156,9 @@ namespace Ink_Canvas
|
|||||||
Trace.WriteLine("Eraser: Overlay disabled in non-eraser mode");
|
Trace.WriteLine("Eraser: Overlay disabled in non-eraser mode");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inkCanvas1.EditingMode != InkCanvasEditingMode.Ink)
|
||||||
|
EndInkPredictionStroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Ink Canvas
|
#endregion Ink Canvas
|
||||||
@@ -2264,6 +2267,14 @@ namespace Ink_Canvas
|
|||||||
SetFloatingBarHighlightPosition("select");
|
SetFloatingBarHighlightPosition("select");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (e.LeftButton == MouseButtonState.Pressed
|
||||||
|
&& inkCanvas != null
|
||||||
|
&& inkCanvas.EditingMode == InkCanvasEditingMode.Ink
|
||||||
|
&& penType != 1)
|
||||||
|
{
|
||||||
|
BeginInkPredictionStrokeIfNeeded();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 手写笔输入
|
// 手写笔输入
|
||||||
@@ -2271,11 +2282,13 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
// 使用辅助方法设置光标
|
// 使用辅助方法设置光标
|
||||||
SetCursorBasedOnEditingMode(sender as InkCanvas);
|
SetCursorBasedOnEditingMode(sender as InkCanvas);
|
||||||
|
BeginInkPredictionStrokeIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 手写笔抬起事件(用于橡皮擦自动切换)
|
// 手写笔抬起事件(用于橡皮擦自动切换)
|
||||||
private void inkCanvas_StylusUp(object sender, StylusEventArgs e)
|
private void inkCanvas_StylusUp(object sender, StylusEventArgs e)
|
||||||
{
|
{
|
||||||
|
EndInkPredictionStroke();
|
||||||
HandleEraserOperationEnded();
|
HandleEraserOperationEnded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,187 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Ink;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace Ink_Canvas
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 墨迹预测(书写中速度外推预览线)与笔锋相关输入状态,思路参考智绘教 Inkeys 的 RTS 速度与低延迟手感。
|
||||||
|
/// </summary>
|
||||||
|
public partial class MainWindow
|
||||||
|
{
|
||||||
|
private bool _inkPredictionStrokeActive;
|
||||||
|
private bool _inkPredictionHasSample;
|
||||||
|
private bool _inkPredictionHasVelocity;
|
||||||
|
private Point _inkPredictionLastPos;
|
||||||
|
private int _inkPredictionLastTime;
|
||||||
|
private double _inkPredictionVx;
|
||||||
|
private double _inkPredictionVy;
|
||||||
|
|
||||||
|
private void ResetInkPredictionState()
|
||||||
|
{
|
||||||
|
_inkPredictionStrokeActive = false;
|
||||||
|
_inkPredictionHasSample = false;
|
||||||
|
_inkPredictionHasVelocity = false;
|
||||||
|
ClearInkPredictionOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearInkPredictionOverlay()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (InkPredictionPolyline == null) return;
|
||||||
|
InkPredictionPolyline.Visibility = Visibility.Collapsed;
|
||||||
|
InkPredictionPolyline.Points.Clear();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BeginInkPredictionStrokeIfNeeded()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Settings?.Canvas == null || !Settings.Canvas.EnableInkStrokePrediction)
|
||||||
|
{
|
||||||
|
_inkPredictionStrokeActive = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_inkPredictionStrokeActive = inkCanvas != null
|
||||||
|
&& inkCanvas.EditingMode == InkCanvasEditingMode.Ink
|
||||||
|
&& penType != 1
|
||||||
|
&& !_isBoardBrushMode;
|
||||||
|
_inkPredictionHasSample = false;
|
||||||
|
_inkPredictionHasVelocity = false;
|
||||||
|
ClearInkPredictionOverlay();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_inkPredictionStrokeActive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EndInkPredictionStroke()
|
||||||
|
{
|
||||||
|
_inkPredictionStrokeActive = false;
|
||||||
|
_inkPredictionHasSample = false;
|
||||||
|
_inkPredictionHasVelocity = false;
|
||||||
|
ClearInkPredictionOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void inkCanvas_PreviewStylusMove(object sender, StylusEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Settings?.Canvas == null || !Settings.Canvas.EnableInkStrokePrediction) return;
|
||||||
|
if (inkCanvas == null || InkPredictionPolyline == null) return;
|
||||||
|
if (!_inkPredictionStrokeActive || penType == 1) return;
|
||||||
|
if (inkCanvas.EditingMode != InkCanvasEditingMode.Ink) return;
|
||||||
|
|
||||||
|
if (e.InAir)
|
||||||
|
{
|
||||||
|
ClearInkPredictionOverlay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pos = e.GetPosition(inkCanvas);
|
||||||
|
UpdateInkPredictionCore(pos, e.Timestamp);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void inkCanvas_LostStylusCapture(object sender, StylusEventArgs e)
|
||||||
|
{
|
||||||
|
EndInkPredictionStroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void inkCanvas_PreviewMouseMoveForPrediction(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Settings?.Canvas == null || !Settings.Canvas.EnableInkStrokePrediction) return;
|
||||||
|
if (inkCanvas == null || InkPredictionPolyline == null) return;
|
||||||
|
if (!_inkPredictionStrokeActive || penType == 1) return;
|
||||||
|
if (inkCanvas.EditingMode != InkCanvasEditingMode.Ink) return;
|
||||||
|
if (e.LeftButton != MouseButtonState.Pressed) return;
|
||||||
|
if (e.StylusDevice != null) return;
|
||||||
|
|
||||||
|
var pos = e.GetPosition(inkCanvas);
|
||||||
|
UpdateInkPredictionCore(pos, Environment.TickCount & int.MaxValue);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateInkPredictionCore(Point pos, int timestamp)
|
||||||
|
{
|
||||||
|
if (InkPredictionPolyline == null || Settings?.Canvas == null) return;
|
||||||
|
|
||||||
|
if (!_inkPredictionHasSample)
|
||||||
|
{
|
||||||
|
_inkPredictionLastPos = pos;
|
||||||
|
_inkPredictionLastTime = timestamp;
|
||||||
|
_inkPredictionHasSample = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double dtMs = timestamp - _inkPredictionLastTime;
|
||||||
|
if (dtMs <= 0 || dtMs > 120) dtMs = 16;
|
||||||
|
|
||||||
|
double vx = (pos.X - _inkPredictionLastPos.X) / dtMs * 1000.0;
|
||||||
|
double vy = (pos.Y - _inkPredictionLastPos.Y) / dtMs * 1000.0;
|
||||||
|
|
||||||
|
const double velocitySmooth = 0.62;
|
||||||
|
if (!_inkPredictionHasVelocity)
|
||||||
|
{
|
||||||
|
_inkPredictionVx = vx;
|
||||||
|
_inkPredictionVy = vy;
|
||||||
|
_inkPredictionHasVelocity = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_inkPredictionVx = velocitySmooth * _inkPredictionVx + (1.0 - velocitySmooth) * vx;
|
||||||
|
_inkPredictionVy = velocitySmooth * _inkPredictionVy + (1.0 - velocitySmooth) * vy;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double leadMs = 24.0;
|
||||||
|
double predX = pos.X + _inkPredictionVx * (leadMs / 1000.0);
|
||||||
|
double predY = pos.Y + _inkPredictionVy * (leadMs / 1000.0);
|
||||||
|
|
||||||
|
double maxDist = Math.Max(4.0, Settings.Canvas.InkStrokePredictionMaxDistance);
|
||||||
|
double dx = predX - pos.X;
|
||||||
|
double dy = predY - pos.Y;
|
||||||
|
double len = Math.Sqrt(dx * dx + dy * dy);
|
||||||
|
if (len > maxDist && len > 1e-6)
|
||||||
|
{
|
||||||
|
double s = maxDist / len;
|
||||||
|
predX = pos.X + dx * s;
|
||||||
|
predY = pos.Y + dy * s;
|
||||||
|
}
|
||||||
|
|
||||||
|
_inkPredictionLastPos = pos;
|
||||||
|
_inkPredictionLastTime = timestamp;
|
||||||
|
|
||||||
|
var da = inkCanvas.DefaultDrawingAttributes;
|
||||||
|
var c = da.Color;
|
||||||
|
InkPredictionPolyline.Stroke = new SolidColorBrush(Color.FromArgb(110, c.R, c.G, c.B));
|
||||||
|
InkPredictionPolyline.StrokeThickness = Math.Max(1.0, da.Width * 0.42);
|
||||||
|
|
||||||
|
InkPredictionPolyline.Points.Clear();
|
||||||
|
InkPredictionPolyline.Points.Add(pos);
|
||||||
|
InkPredictionPolyline.Points.Add(new Point(predX, predY));
|
||||||
|
InkPredictionPolyline.Visibility = Visibility.Visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2700,6 +2700,24 @@ namespace Ink_Canvas
|
|||||||
SaveSettingsToFile();
|
SaveSettingsToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ToggleSwitchEnableInkStrokePrediction_Toggled(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!isLoaded) return;
|
||||||
|
|
||||||
|
Settings.Canvas.EnableInkStrokePrediction = ToggleSwitchEnableInkStrokePrediction.IsOn;
|
||||||
|
if (!Settings.Canvas.EnableInkStrokePrediction)
|
||||||
|
EndInkPredictionStroke();
|
||||||
|
SaveSettingsToFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleSwitchEnableVelocityBrushTip_Toggled(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!isLoaded) return;
|
||||||
|
|
||||||
|
Settings.Canvas.EnableVelocityBrushTip = ToggleSwitchEnableVelocityBrushTip.IsOn;
|
||||||
|
SaveSettingsToFile();
|
||||||
|
}
|
||||||
|
|
||||||
private void ToggleSwitchAutoStraightenLine_Toggled(object sender, RoutedEventArgs e)
|
private void ToggleSwitchAutoStraightenLine_Toggled(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
|
|||||||
@@ -941,6 +941,11 @@ namespace Ink_Canvas
|
|||||||
// 初始化直线端点吸附相关设置
|
// 初始化直线端点吸附相关设置
|
||||||
ToggleSwitchLineEndpointSnapping.IsOn = Settings.Canvas.LineEndpointSnapping;
|
ToggleSwitchLineEndpointSnapping.IsOn = Settings.Canvas.LineEndpointSnapping;
|
||||||
ToggleSwitchCompressPicturesUploaded.IsOn = Settings.Canvas.IsCompressPicturesUploaded;
|
ToggleSwitchCompressPicturesUploaded.IsOn = Settings.Canvas.IsCompressPicturesUploaded;
|
||||||
|
|
||||||
|
if (ToggleSwitchEnableInkStrokePrediction != null)
|
||||||
|
ToggleSwitchEnableInkStrokePrediction.IsOn = Settings.Canvas.EnableInkStrokePrediction;
|
||||||
|
if (ToggleSwitchEnableVelocityBrushTip != null)
|
||||||
|
ToggleSwitchEnableVelocityBrushTip.IsOn = Settings.Canvas.EnableVelocityBrushTip;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2565,6 +2565,7 @@ namespace Ink_Canvas
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void inkCanvas_MouseUp(object sender, MouseButtonEventArgs e)
|
private void inkCanvas_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
|
EndInkPredictionStroke();
|
||||||
HandleEraserOperationEnded(); // 橡皮擦自动切换回批注模式:松手后启动/重置计时
|
HandleEraserOperationEnded(); // 橡皮擦自动切换回批注模式:松手后启动/重置计时
|
||||||
inkCanvas.ReleaseMouseCapture();
|
inkCanvas.ReleaseMouseCapture();
|
||||||
ViewboxFloatingBar.IsHitTestVisible = true;
|
ViewboxFloatingBar.IsHitTestVisible = true;
|
||||||
|
|||||||
@@ -204,6 +204,7 @@ namespace Ink_Canvas
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
inkCanvas.Opacity = 1;
|
inkCanvas.Opacity = 1;
|
||||||
|
var touchPressureSimulationApplied = false;
|
||||||
|
|
||||||
if (Settings.Canvas.DisablePressure)
|
if (Settings.Canvas.DisablePressure)
|
||||||
{
|
{
|
||||||
@@ -256,6 +257,7 @@ namespace Ink_Canvas
|
|||||||
stylusPoints.Add(point);
|
stylusPoints.Add(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
touchPressureSimulationApplied = true;
|
||||||
e.Stroke.StylusPoints = stylusPoints;
|
e.Stroke.StylusPoints = stylusPoints;
|
||||||
}
|
}
|
||||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||||
@@ -304,6 +306,7 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
touchPressureSimulationApplied = true;
|
||||||
e.Stroke.StylusPoints = stylusPoints;
|
e.Stroke.StylusPoints = stylusPoints;
|
||||||
}
|
}
|
||||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||||
@@ -312,6 +315,18 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Settings.Canvas.EnableVelocityBrushTip
|
||||||
|
&& !touchPressureSimulationApplied
|
||||||
|
&& !Settings.Canvas.DisablePressure
|
||||||
|
&& penType != 1
|
||||||
|
&& e.Stroke?.DrawingAttributes != null
|
||||||
|
&& !e.Stroke.DrawingAttributes.IsHighlighter
|
||||||
|
&& !e.Stroke.DrawingAttributes.IgnorePressure
|
||||||
|
&& e.Stroke.StylusPoints.Count >= 3)
|
||||||
|
{
|
||||||
|
ApplyVelocityBrushTipFromSpeed(e.Stroke);
|
||||||
|
}
|
||||||
|
|
||||||
// Apply line straightening and endpoint snapping if ink-to-shape is enabled
|
// Apply line straightening and endpoint snapping if ink-to-shape is enabled
|
||||||
|
|
||||||
if (Settings.InkToShape.IsInkToShapeEnabled)
|
if (Settings.InkToShape.IsInkToShapeEnabled)
|
||||||
@@ -2033,6 +2048,56 @@ namespace Ink_Canvas
|
|||||||
/ 20;
|
/ 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将沿线速度映射为压感并与硬件压感混合,快写略细、慢写略粗;与 Inkeys 中 RTSSpeed 驱动的笔锋类似,在落笔后统一施加。
|
||||||
|
/// </summary>
|
||||||
|
private void ApplyVelocityBrushTipFromSpeed(Stroke stroke)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mix = Settings.Canvas.VelocityBrushTipMix;
|
||||||
|
if (mix <= 0 || stroke == null) return;
|
||||||
|
if (mix > 1) mix = 1;
|
||||||
|
|
||||||
|
var pts = stroke.StylusPoints;
|
||||||
|
if (pts.Count < 3) return;
|
||||||
|
|
||||||
|
var n = pts.Count - 1;
|
||||||
|
var stylusPoints = new StylusPointCollection();
|
||||||
|
|
||||||
|
for (var i = 0; i <= n; i++)
|
||||||
|
{
|
||||||
|
var speed = GetPointSpeed(
|
||||||
|
pts[Math.Max(i - 1, 0)].ToPoint(),
|
||||||
|
pts[i].ToPoint(),
|
||||||
|
pts[Math.Min(i + 1, n)].ToPoint());
|
||||||
|
|
||||||
|
float speedPressure;
|
||||||
|
if (speed >= 0.25)
|
||||||
|
speedPressure = (float)(0.5 - 0.3 * (Math.Min(speed, 1.5) - 0.3) / 1.2);
|
||||||
|
else if (speed >= 0.05)
|
||||||
|
speedPressure = 0.5f;
|
||||||
|
else
|
||||||
|
speedPressure = (float)(0.5 + 0.4 * (0.05 - speed) / 0.05);
|
||||||
|
|
||||||
|
speedPressure = (float)Math.Max(0.08, Math.Min(1.0, speedPressure));
|
||||||
|
|
||||||
|
var basePf = pts[i].PressureFactor;
|
||||||
|
var blended = (float)((1.0 - mix) * basePf + mix * speedPressure);
|
||||||
|
blended = (float)Math.Max(0.08, Math.Min(1.0, blended));
|
||||||
|
|
||||||
|
var p = new StylusPoint(pts[i].X, pts[i].Y, blended);
|
||||||
|
stylusPoints.Add(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
stroke.StylusPoints = stylusPoints;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Point[] FixPointsDirection(Point p1, Point p2)
|
public Point[] FixPointsDirection(Point p1, Point p2)
|
||||||
{
|
{
|
||||||
double deltaY = Math.Abs(p1.Y - p2.Y);
|
double deltaY = Math.Abs(p1.Y - p2.Y);
|
||||||
|
|||||||
@@ -374,6 +374,10 @@
|
|||||||
<data name="Canvas_EnablePressureTouchHint" xml:space="preserve"><value># When on, touch screens that support pressure will show pressure; for devices not recognized by the system.</value></data>
|
<data name="Canvas_EnablePressureTouchHint" xml:space="preserve"><value># When on, touch screens that support pressure will show pressure; for devices not recognized by the system.</value></data>
|
||||||
<data name="Canvas_DisablePressure" xml:space="preserve"><value>Ignore pressure</value></data>
|
<data name="Canvas_DisablePressure" xml:space="preserve"><value>Ignore pressure</value></data>
|
||||||
<data name="Canvas_DisablePressureHint" xml:space="preserve"><value># When on, all strokes use uniform thickness; mutually exclusive with pressure-sensitive touch.</value></data>
|
<data name="Canvas_DisablePressureHint" xml:space="preserve"><value># When on, all strokes use uniform thickness; mutually exclusive with pressure-sensitive touch.</value></data>
|
||||||
|
<data name="Canvas_InkStrokePrediction" xml:space="preserve"><value>Ink stroke prediction (latency hint)</value></data>
|
||||||
|
<data name="Canvas_InkStrokePredictionHint" xml:space="preserve"><value># While inking, draws a short semi-transparent segment ahead along motion to reduce perceived latency (similar idea to Inkeys).</value></data>
|
||||||
|
<data name="Canvas_VelocityBrushTip" xml:space="preserve"><value>Velocity brush tip (pressure blend)</value></data>
|
||||||
|
<data name="Canvas_VelocityBrushTipHint" xml:space="preserve"><value># For pen pressure, blends speed with hardware pressure: faster strokes thinner, slower thicker. Touch simulated pressure is unchanged.</value></data>
|
||||||
<data name="Canvas_EraserSize" xml:space="preserve"><value>Eraser size</value></data>
|
<data name="Canvas_EraserSize" xml:space="preserve"><value>Eraser size</value></data>
|
||||||
<data name="Canvas_EraserSize_VerySmall" xml:space="preserve"><value>Very small</value></data>
|
<data name="Canvas_EraserSize_VerySmall" xml:space="preserve"><value>Very small</value></data>
|
||||||
<data name="Canvas_EraserSize_Small" xml:space="preserve"><value>Small</value></data>
|
<data name="Canvas_EraserSize_Small" xml:space="preserve"><value>Small</value></data>
|
||||||
|
|||||||
@@ -389,6 +389,10 @@
|
|||||||
<data name="Canvas_EnablePressureTouchHint" xml:space="preserve"><value># 开启后,触屏设备也将支持压感效果,适用于部分支持压感但无法被系统识别的触屏设备。</value></data>
|
<data name="Canvas_EnablePressureTouchHint" xml:space="preserve"><value># 开启后,触屏设备也将支持压感效果,适用于部分支持压感但无法被系统识别的触屏设备。</value></data>
|
||||||
<data name="Canvas_DisablePressure" xml:space="preserve"><value>屏蔽压感</value></data>
|
<data name="Canvas_DisablePressure" xml:space="preserve"><value>屏蔽压感</value></data>
|
||||||
<data name="Canvas_DisablePressureHint" xml:space="preserve"><value># 开启后,将忽略所有设备的压感信息,使所有笔画具有统一的粗细。与压感触屏模式互斥。</value></data>
|
<data name="Canvas_DisablePressureHint" xml:space="preserve"><value># 开启后,将忽略所有设备的压感信息,使所有笔画具有统一的粗细。与压感触屏模式互斥。</value></data>
|
||||||
|
<data name="Canvas_InkStrokePrediction" xml:space="preserve"><value>墨迹预测(低延迟预览线)</value></data>
|
||||||
|
<data name="Canvas_InkStrokePredictionHint" xml:space="preserve"><value># 书写时沿运动方向外推一小段半透明预览线,减轻显示与采样延迟;思路类似智绘教 Inkeys 的流畅笔迹。</value></data>
|
||||||
|
<data name="Canvas_VelocityBrushTip" xml:space="preserve"><value>速度笔锋(压感混合)</value></data>
|
||||||
|
<data name="Canvas_VelocityBrushTipHint" xml:space="preserve"><value># 对触控笔等真实压感,按运笔速度与硬件压感混合:快画略细、慢画略粗;触屏模拟压感路径仍单独处理,避免重复叠加。</value></data>
|
||||||
<data name="Canvas_EraserSize" xml:space="preserve"><value>橡皮大小</value></data>
|
<data name="Canvas_EraserSize" xml:space="preserve"><value>橡皮大小</value></data>
|
||||||
<data name="Canvas_EraserSize_VerySmall" xml:space="preserve"><value>很小</value></data>
|
<data name="Canvas_EraserSize_VerySmall" xml:space="preserve"><value>很小</value></data>
|
||||||
<data name="Canvas_EraserSize_Small" xml:space="preserve"><value>较小</value></data>
|
<data name="Canvas_EraserSize_Small" xml:space="preserve"><value>较小</value></data>
|
||||||
|
|||||||
@@ -146,6 +146,30 @@ namespace Ink_Canvas
|
|||||||
[JsonProperty("eraserAutoSwitchBackDelaySeconds")]
|
[JsonProperty("eraserAutoSwitchBackDelaySeconds")]
|
||||||
public int EraserAutoSwitchBackDelaySeconds { get; set; } = 10; // 默认10秒
|
public int EraserAutoSwitchBackDelaySeconds { get; set; } = 10; // 默认10秒
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 书写时根据速度外推一小段预览线,补偿显示/采样延迟(类似智绘教 Inkeys 的低延迟手感)。
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("enableInkStrokePrediction")]
|
||||||
|
public bool EnableInkStrokePrediction { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预测线段最大长度(与设备无关的逻辑像素/DIP),过大易飘,过小不明显。
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("inkStrokePredictionMaxDistance")]
|
||||||
|
public double InkStrokePredictionMaxDistance { get; set; } = 18.0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用笔等真实压感设备时,将速度与硬件压感按 <see cref="VelocityBrushTipMix"/> 混合,使快画偏细、慢画偏粗(参考 Inkeys RTSSpeed 思路)。
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("enableVelocityBrushTip")]
|
||||||
|
public bool EnableVelocityBrushTip { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 速度笔锋混合比例 0–1,越大速度对粗细影响越明显。
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("velocityBrushTipMix")]
|
||||||
|
public double VelocityBrushTipMix { get; set; } = 0.22;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum OptionalOperation
|
public enum OptionalOperation
|
||||||
|
|||||||
Reference in New Issue
Block a user