Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions Ink Canvas/Ink/Native/NativeInkInputRouter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,18 +300,21 @@ private static bool TryGetPalmEraserWidth(
if (policy.IsSpecialScreen && policy.TouchMultiplier == 0)
return false;

var boundWidth = policy.IsQuadIr
? Math.Sqrt(Math.Max(0, pointer.ContactWidthDip * pointer.ContactHeightDip))
: pointer.ContactWidthDip;
var boundWidth = PalmEraserGeometry.GetEffectiveContactWidthDip(
pointer.ContactWidthDip,
pointer.ContactHeightDip,
policy.IsQuadIr);
var threshold = policy.BoundsWidthDip
* policy.ThresholdFactor
* policy.SensitivityMultiplier;
if (boundWidth <= policy.BoundsWidthDip || boundWidth <= threshold)
return false;

eraserWidthDip = boundWidth
* policy.EraserSizeFactor
* (policy.IsSpecialScreen ? policy.TouchMultiplier : 1);
eraserWidthDip = PalmEraserGeometry.ApplyPalmEraserSize(
boundWidth,
policy.EraserSizeFactor,
policy.IsSpecialScreen,
policy.TouchMultiplier);
return true;
}

Expand Down
59 changes: 59 additions & 0 deletions Ink Canvas/Ink/PalmEraserGeometry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;

namespace Ink_Canvas.Ink
{
/// <summary>
/// 手掌擦接触尺寸计算。
/// 上下红外等触摸框可能把接触矩形某一轴异常放大(例如接近全屏宽度的长条),
/// 这里把异常细长接触折算为几何平均宽度,并限制最终手掌橡皮大小。
/// </summary>
internal static class PalmEraserGeometry
{
/// <summary>
/// 手掌橡皮最大宽度(DIP)。用于防止上下红外等设备上报的异常接触矩形
/// 把橡皮放大到远超真实手掌的程度。
/// </summary>
internal const double MaxPalmEraserWidthDip = 200;

/// <summary>
/// 接触矩形长宽比超过该值时,认为某一轴可能是红外框的异常放大结果,
/// 改用几何平均 sqrt(width*height) 来估算有效接触宽度。
/// </summary>
private const double ElongatedAspectRatioThreshold = 3.0;

internal static double GetEffectiveContactWidthDip(
double contactWidthDip,
double contactHeightDip,
bool isQuadIr)
{
if (contactWidthDip <= 0)
return 0;

// 部分设备不提供高度,仍回退到宽度,避免破坏原有行为。
if (contactHeightDip <= 0)
return contactWidthDip;

var min = Math.Min(contactWidthDip, contactHeightDip);
var max = Math.Max(contactWidthDip, contactHeightDip);

@augmentcode augmentcode Bot Aug 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Ink Canvas/Ink/PalmEraserGeometry.cs:37, this early return also applies when isQuadIr is true, changing the previous quad-IR result for a zero-height contact from 0 to the raw width. A quad-IR frame with a screen-wide width and a missing/degenerate height can therefore activate an oversized palm eraser again rather than being rejected by the existing threshold checks.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

var elongated = min <= 0 || max / min > ElongatedAspectRatioThreshold;

if (isQuadIr || elongated)
return Math.Sqrt(Math.Max(0, contactWidthDip * contactHeightDip));

return contactWidthDip;
}

internal static double ApplyPalmEraserSize(
double effectiveContactWidthDip,
double eraserSizeFactor,
bool isSpecialScreen,
double touchMultiplier)
{
var widthDip = effectiveContactWidthDip * eraserSizeFactor;
if (isSpecialScreen)
widthDip *= touchMultiplier;

return Math.Min(widthDip, MaxPalmEraserWidthDip);
}
}
}
17 changes: 11 additions & 6 deletions Ink Canvas/MainWindow_cs/MW_TouchEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1864,8 +1864,10 @@ private void Main_Grid_TouchDown(object sender, TouchEventArgs e)
public double GetTouchBoundWidth(TouchEventArgs e)
{
var args = e.GetTouchPoint(null).Bounds;
if (!Settings.Advanced.IsQuadIR) return args.Width;
return Math.Sqrt(args.Width * args.Height);
return Ink_Canvas.Ink.PalmEraserGeometry.GetEffectiveContactWidthDip(
args.Width,
args.Height,
Settings.Advanced.IsQuadIR);
}

/// <summary>
Expand Down Expand Up @@ -2086,18 +2088,21 @@ private void InkCanvas_PreviewTouchDown(object sender, TouchEventArgs e)

if (boundWidth > BoundsWidth * EraserThresholdValue * thresholdMultiplier)
{
boundWidth *= Settings.Startup.IsEnableNibMode
var eraserSizeFactor = Settings.Startup.IsEnableNibMode
? Settings.Advanced.NibModeBoundsWidthEraserSize
: Settings.Advanced.FingerModeBoundsWidthEraserSize;
var palmEraserWidth = Ink_Canvas.Ink.PalmEraserGeometry.ApplyPalmEraserSize(
boundWidth,
eraserSizeFactor,
Settings.Advanced.IsSpecialScreen,
Settings.Advanced.TouchMultiplier);

if (Settings.Advanced.IsSpecialScreen)
boundWidth *= Settings.Advanced.TouchMultiplier;
palmEraserPreviousEditingMode = inkCanvas.EditingMode;
inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint;
isPalmEraserActive = true;

EnableEraserOverlay();
eraserWidth = boundWidth;
eraserWidth = palmEraserWidth;
UpdateEraserStyle();
touchPoint = e.GetTouchPoint(inkCanvas);
EraserOverlay_PointerDown(sender);
Expand Down
1 change: 1 addition & 0 deletions InkCanvas.NativeInk.Tests/InkCanvas.NativeInk.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<Compile Include="..\Ink Canvas\Ink\Native\NativeInkPerfProbe.cs" Link="Ink\Native\NativeInkPerfProbe.cs" />
<Compile Include="..\Ink Canvas\Ink\Native\NativePointerUpdatePump.cs" Link="Ink\Native\NativePointerUpdatePump.cs" />
<Compile Include="..\Ink Canvas\Ink\Native\NativeInkInputRouter.cs" Link="Ink\Native\NativeInkInputRouter.cs" />
<Compile Include="..\Ink Canvas\Ink\PalmEraserGeometry.cs" Link="Ink\PalmEraserGeometry.cs" />
<Compile Include="..\Ink Canvas\Ink\Native\NativePointerInputBatch.cs" Link="Ink\Native\NativePointerInputBatch.cs" />
<Compile Include="..\Ink Canvas\Ink\Native\NativePointerTimestampConverter.cs" Link="Ink\Native\NativePointerTimestampConverter.cs" />
<Compile Include="..\Ink Canvas\Ink\Native\WetInkCommandMailbox.cs" Link="Ink\Native\WetInkCommandMailbox.cs" />
Expand Down
64 changes: 64 additions & 0 deletions InkCanvas.NativeInk.Tests/NativeInkCoreTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ private static void Main()
Run(nameof(RouterMapsLogicalTools), RouterMapsLogicalTools);
Run(nameof(RouterPrefersMultiTouchWritingOverPalmErase), RouterPrefersMultiTouchWritingOverPalmErase);
Run(nameof(RouterAppliesQuadIrPalmThresholdAndSpecialMultiplier), RouterAppliesQuadIrPalmThresholdAndSpecialMultiplier);
Run(nameof(RouterUsesGeometricMeanForElongatedTopBottomIrPalm), RouterUsesGeometricMeanForElongatedTopBottomIrPalm);
Run(nameof(RouterKeepsWidthForNormalPalmContact), RouterKeepsWidthForNormalPalmContact);
Run(nameof(RouterCapsPalmEraserWidth), RouterCapsPalmEraserWidth);
Run(nameof(RouterAllowsDelayedTwoFingerTakeover), RouterAllowsDelayedTwoFingerTakeover);
Run(nameof(RouterKeepsCapturedInkAndSuppressesBarrelPoints), RouterKeepsCapturedInkAndSuppressesBarrelPoints);
Run(nameof(PointerBatchCopiesSamples), PointerBatchCopiesSamples);
Expand Down Expand Up @@ -540,6 +543,67 @@ private static void RouterAppliesQuadIrPalmThresholdAndSpecialMultiplier()
Equal(NativeInputRoute.Ink, disabledOnSpecialScreen.Route);
}

private static void RouterUsesGeometricMeanForElongatedTopBottomIrPalm()
{
// 上下红外框常见异常:接触矩形接近全宽长条(width 异常大、height 接近真实手掌)。
// 非四边红外模式下也应使用几何平均,避免橡皮被放大到远超手掌。
var decision = NativeInkInputRouter.DecideDown(
Pointer(NativeInkInputKind.Touch, contactWidthDip: 1920, contactHeightDip: 120),
Context(
LogicalInkTool.Pen,
palm: Palm(
enabled: true,
isQuadIr: false,
isSpecialScreen: true,
boundsWidthDip: 10,
thresholdFactor: 2,
sensitivityMultiplier: 2,
eraserSizeFactor: 0.8,
touchMultiplier: 0.3)));
Equal(NativeInputRoute.PointErase, decision.Route);
True(Math.Abs(decision.PalmEraserWidthDip - 115.2d) < 0.001);
}

private static void RouterKeepsWidthForNormalPalmContact()
{
// 普通触摸屏接触矩形不细长时,继续使用宽度,避免改变原有行为。
var decision = NativeInkInputRouter.DecideDown(
Pointer(NativeInkInputKind.Touch, contactWidthDip: 80, contactHeightDip: 100),
Context(
LogicalInkTool.Pen,
palm: Palm(
enabled: true,
isQuadIr: false,
isSpecialScreen: false,
boundsWidthDip: 10,
thresholdFactor: 2,
sensitivityMultiplier: 2,
eraserSizeFactor: 0.8,
touchMultiplier: 1)));
Equal(NativeInputRoute.PointErase, decision.Route);
Equal(64d, decision.PalmEraserWidthDip);
}

private static void RouterCapsPalmEraserWidth()
{
// 即使两个轴都异常大,最终手掌橡皮也不能超过保护上限。
var decision = NativeInkInputRouter.DecideDown(
Pointer(NativeInkInputKind.Touch, contactWidthDip: 500, contactHeightDip: 500),
Context(
LogicalInkTool.Pen,
palm: Palm(
enabled: true,
isQuadIr: false,
isSpecialScreen: false,
boundsWidthDip: 10,
thresholdFactor: 2,
sensitivityMultiplier: 2,
eraserSizeFactor: 1,
touchMultiplier: 1)));
Equal(NativeInputRoute.PointErase, decision.Route);
Equal(Ink_Canvas.Ink.PalmEraserGeometry.MaxPalmEraserWidthDip, decision.PalmEraserWidthDip);
}

private static void RouterAllowsDelayedTwoFingerTakeover()
{
var decision = NativeInkInputRouter.DecideDown(
Expand Down