-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
Description
There is a calculation error in WPF’s IncrementalStrokeHitTester. Occasionally, when I use IncrementalStrokeHitTester and provide an eraser trajectory that should only split the stroke into two new strokes, the whole stroke is erased instead.
I have managed to reproduce this issue with a minimal test case. Using the following data:
{X=445.3333333333333,Y=456,P=0.7618542313575745},
{X=448,Y=456,P=0.7618542313575745},
{X=450,Y=455.3333333333333,P=0.7742698192596436},
{X=452,Y=455.3333333333333,P=0.7923250794410706},
{X=454,Y=455.3333333333333,P=0.7947957515716553},
{X=456,Y=455.3333333333333,P=0.7871016263961792},
{X=458,Y=455.3333333333333,P=0.7873656749725342},
{X=460,Y=455.3333333333333,P=0.7871317267417908},
{X=462,Y=455.3333333333333,P=0.7867388129234314},
{X=464.66666666666663,Y=456,P=0.7443132400512695},
{X=466.66666666666663,Y=456,P=0.7917653322219849},
{X=468.66666666666663,Y=456.66666666666663,P=0.7867511510848999},
{X=472,Y=456.66666666666663,P=0.7043896913528442},
{X=474,Y=457.3333333333333,P=0.7800122499465942},
{X=476,Y=457.3333333333333,P=0.7910786271095276},
{X=478,Y=458,P=0.7841806411743164},
{X=482,Y=458.66666666666663,P=0.6613860726356506},
{X=484.66666666666663,Y=459.3333333333333,P=0.7315411567687988},
{X=486.66666666666663,Y=460,P=0.7758287191390991},
{X=488.66666666666663,Y=460,P=0.785304844379425},
{X=492,Y=460.66666666666663,P=0.6987720131874084},
{X=496,Y=461.3333333333333,P=0.6533879637718201},
{X=500,Y=462,P=0.6456292271614075},
{X=503.3333333333333,Y=462,P=0.6683430075645447},
{X=505.3333333333333,Y=462.66666666666663,P=0.7513852119445801},
{X=510,Y=463.3333333333333,P=0.5926470756530762},
{X=514,Y=464,P=0.621231198310852},
{X=516,Y=464.66666666666663,P=0.7479339241981506},
{X=518,Y=465.3333333333333,P=0.7547337412834167},
{X=520.6666666666666,Y=465.3333333333333,P=0.7243622541427612},
{X=526.6666666666666,Y=466,P=0.5270907282829285},
{X=529.3333333333333,Y=467.3333333333333,P=0.6958931684494019},
{X=534,Y=468,P=0.6012994050979614},
{X=538,Y=468.66666666666663,P=0.6275204420089722},
{X=542.6666666666666,Y=469.3333333333333,P=0.5978454351425171},
{X=546,Y=470,P=0.6662331819534302},
{X=552.6666666666666,Y=470.66666666666663,P=0.5002615451812744},
{X=555.3333333333333,Y=471.3333333333333,P=0.6958120465278625},
{X=562,Y=472,P=0.4994753301143646},
{X=567.3333333333333,Y=472.66666666666663,P=0.5455721616744995},
{X=573.3333333333333,Y=474,P=0.5134470462799072},
{X=580.6666666666666,Y=474.66666666666663,P=0.4518575370311737},
{X=585.3333333333333,Y=475.3333333333333,P=0.5670377612113953},
{X=589.3333333333333,Y=476,P=0.609724223613739},
{X=594.6666666666666,Y=476.66666666666663,P=0.5481007099151611},
{X=601.3333333333333,Y=477.3333333333333,P=0.4885927140712738},
{X=606.6666666666666,Y=478,P=0.5432637929916382},
{X=610,Y=478.66666666666663,P=0.6355723142623901},
{X=616.6666666666666,Y=479.3333333333333,P=0.47008851170539856},
{X=622,Y=480,P=0.5248528122901917},
{X=628.6666666666666,Y=480,P=0.46539586782455444},
{X=636.6666666666666,Y=480.66666666666663,P=0.39966750144958496},
{X=642,Y=480.66666666666663,P=0.509570300579071},
{X=647.3333333333333,Y=482,P=0.5069840550422668},
{X=652,Y=482,P=0.56694495677948},
{X=654,Y=482,P=0.7375491857528687},
{X=662,Y=482.66666666666663,P=0.4425170123577118},
{X=666.6666666666666,Y=482.66666666666663,P=0.5797702074050903},
{X=669.3333333333333,Y=482.66666666666663,P=0.7043377757072449},
{X=676,Y=482.66666666666663,P=0.49506327509880066},
{X=680,Y=482.66666666666663,P=0.6218303442001343},
{X=684.6666666666666,Y=482.66666666666663,P=0.5944092869758606},
{X=689.3333333333333,Y=482.66666666666663,P=0.5880598425865173},
{X=694,Y=482.66666666666663,P=0.5764558911323547},
{X=696,Y=482.66666666666663,P=0.7410839796066284},
{X=700,Y=482.66666666666663,P=0.626220166683197},
{X=704,Y=482.66666666666663,P=0.6229243278503418},
{X=707.3333333333333,Y=482.66666666666663,P=0.6664982438087463},
{X=714.6666666666666,Y=482.66666666666663,P=0.45310068130493164},
{X=720.6666666666666,Y=482.66666666666663,P=0.4970242381095886},
{X=724,Y=482.66666666666663,P=0.6377686858177185},
{X=728.6666666666666,Y=482.66666666666663,P=0.5830304622650146},
{X=733.3333333333333,Y=482,P=0.5852826237678528},
{X=735.3333333333333,Y=482,P=0.7534965872764587},
{X=739.3333333333333,Y=481.3333333333333,P=0.6395262479782104},
{X=742,Y=481.3333333333333,P=0.7220669984817505},
{X=746,Y=480.66666666666663,P=0.6478912830352783},
{X=750,Y=480,P=0.6395424604415894},
{X=754,Y=478.66666666666663,P=0.629923403263092},
{X=759.3333333333333,Y=478,P=0.5632662177085876},
{X=762.6666666666666,Y=477.3333333333333,P=0.6644474864006042},
{X=765.3333333333333,Y=476.66666666666663,P=0.7117174863815308},
{X=768.6666666666666,Y=476,P=0.6776741743087769},
{X=773.3333333333333,Y=474.66666666666663,P=0.5986015200614929},
{X=777.3333333333333,Y=473.3333333333333,P=0.6267250180244446},
{X=781.3333333333333,Y=471.3333333333333,P=0.6099798679351807},
{X=784.6666666666666,Y=470.66666666666663,P=0.6539067625999451},
{X=790.6666666666666,Y=468.66666666666663,P=0.49772119522094727},
{X=792.6666666666666,Y=468,P=0.7275203466415405},
{X=796,Y=466.66666666666663,P=0.6488282680511475},
{X=800,Y=465.3333333333333,P=0.6072457432746887},
{X=804,Y=463.3333333333333,P=0.5964605212211609},
{X=807.3333333333333,Y=462,P=0.6561574339866638},
{X=810,Y=460.66666666666663,P=0.6968104839324951},
{X=813.3333333333333,Y=458.66666666666663,P=0.6486343741416931},
{X=818,Y=456.66666666666663,P=0.5831184983253479},
{X=820.6666666666666,Y=455.3333333333333,P=0.6963025331497192},
{X=822.6666666666666,Y=454,P=0.7399721741676331},
{X=825.3333333333333,Y=452.66666666666663,P=0.7092769145965576},
{X=828.6666666666666,Y=451.3333333333333,P=0.6766396164894104},
{X=833.3333333333333,Y=448.66666666666663,P=0.5740551948547363},
{X=835.3333333333333,Y=447.3333333333333,P=0.7345168590545654},
{X=837.3333333333333,Y=446.66666666666663,P=0.7650047540664673},
{X=840.6666666666666,Y=445.3333333333333,P=0.6749776005744934},
{X=844.6666666666666,Y=442.66666666666663,P=0.5888175368309021},
{X=848.6666666666666,Y=440.66666666666663,P=0.5972996354103088},
{X=852,Y=438.66666666666663,P=0.6244710683822632},
{X=854,Y=437.3333333333333,P=0.7188596725463867},
{X=856.6666666666666,Y=436,P=0.6917470097541809},
{X=858.6666666666666,Y=434.66666666666663,P=0.7351683378219604},
{X=862.6666666666666,Y=432.66666666666663,P=0.6261351704597473},
{X=864.6666666666666,Y=431.3333333333333,P=0.7430726289749146},
{X=866.6666666666666,Y=430.66666666666663,P=0.7724213600158691},
{X=868.6666666666666,Y=429.3333333333333,P=0.7554715871810913},
{X=871.3333333333333,Y=428.66666666666663,P=0.7384610176086426},
{X=875.3333333333333,Y=426.66666666666663,P=0.6147278547286987},
{X=877.3333333333333,Y=426,P=0.7556149363517761},
{X=880,Y=425.3333333333333,P=0.7179256677627563},
{X=882,Y=424.66666666666663,P=0.7604349851608276},
{X=884,Y=424,P=0.7805911302566528},
{X=886,Y=423.3333333333333,P=0.7828847765922546},
{X=888,Y=422.66666666666663,P=0.78639817237854},
{X=890,Y=422,P=0.7878957390785217},
{X=892,Y=421.3333333333333,P=0.7883377075195312},
{X=894,Y=421.3333333333333,P=0.7963496446609497},
{X=896,Y=421.3333333333333,P=0.7972534894943237},
{X=898,Y=420.66666666666663,P=0.7801092267036438},
{X=900,Y=420.66666666666663,P=0.7869842052459717},
{X=902,Y=420.66666666666663,P=0.7963632345199585},
{X=904,Y=420,P=0.7890701293945312},
{X=906,Y=420,P=0.7968790531158447},
{X=908,Y=420,P=0.8703685998916626},
When passing the point (684.9383585999957, 446.44199735085795) to IncrementalStrokeHitTester, it erases the entire stroke. However, using Stroke.GetEraseResult
with the same input correctly returns two segments as expected.
Here is my minimal reproduction demo code (full project uploaded to GitHub: https://github.com/lindexi/lindexi_gd/tree/7f7f914c05829be1feb090ab05b0514df4e46e97/WPFDemo/RarlereninemniBohinabairhalljere ):
var stylusPointCollection = GetTestData();
var stroke = new Stroke(stylusPointCollection);
var x = 684.9383585999957;
var y = 446.44199735085795;
var strokeCollection = new StrokeCollection([stroke]);
var rectangleStylusShape = new RectangleStylusShape(50, 70);
var incrementalStrokeHitTester = strokeCollection.GetIncrementalStrokeHitTester(rectangleStylusShape);
var point = new Point(x, y);
incrementalStrokeHitTester.StrokeHit += (o, args) =>
{
var pointEraseResults = args.GetPointEraseResults();
if (pointEraseResults.Count == 0)
{
Debugger.Break();
GC.KeepAlive(point);
}
};
incrementalStrokeHitTester.AddPoint(point);
private static StylusPointCollection GetTestData()
{
var test =
"""
{X=445.3333333333333,Y=456,P=0.7618542313575745},
{X=448,Y=456,P=0.7618542313575745},
{X=450,Y=455.3333333333333,P=0.7742698192596436},
{X=452,Y=455.3333333333333,P=0.7923250794410706},
{X=454,Y=455.3333333333333,P=0.7947957515716553},
{X=456,Y=455.3333333333333,P=0.7871016263961792},
{X=458,Y=455.3333333333333,P=0.7873656749725342},
{X=460,Y=455.3333333333333,P=0.7871317267417908},
{X=462,Y=455.3333333333333,P=0.7867388129234314},
{X=464.66666666666663,Y=456,P=0.7443132400512695},
{X=466.66666666666663,Y=456,P=0.7917653322219849},
{X=468.66666666666663,Y=456.66666666666663,P=0.7867511510848999},
{X=472,Y=456.66666666666663,P=0.7043896913528442},
{X=474,Y=457.3333333333333,P=0.7800122499465942},
{X=476,Y=457.3333333333333,P=0.7910786271095276},
{X=478,Y=458,P=0.7841806411743164},
{X=482,Y=458.66666666666663,P=0.6613860726356506},
{X=484.66666666666663,Y=459.3333333333333,P=0.7315411567687988},
{X=486.66666666666663,Y=460,P=0.7758287191390991},
{X=488.66666666666663,Y=460,P=0.785304844379425},
{X=492,Y=460.66666666666663,P=0.6987720131874084},
{X=496,Y=461.3333333333333,P=0.6533879637718201},
{X=500,Y=462,P=0.6456292271614075},
{X=503.3333333333333,Y=462,P=0.6683430075645447},
{X=505.3333333333333,Y=462.66666666666663,P=0.7513852119445801},
{X=510,Y=463.3333333333333,P=0.5926470756530762},
{X=514,Y=464,P=0.621231198310852},
{X=516,Y=464.66666666666663,P=0.7479339241981506},
{X=518,Y=465.3333333333333,P=0.7547337412834167},
{X=520.6666666666666,Y=465.3333333333333,P=0.7243622541427612},
{X=526.6666666666666,Y=466,P=0.5270907282829285},
{X=529.3333333333333,Y=467.3333333333333,P=0.6958931684494019},
{X=534,Y=468,P=0.6012994050979614},
{X=538,Y=468.66666666666663,P=0.6275204420089722},
{X=542.6666666666666,Y=469.3333333333333,P=0.5978454351425171},
{X=546,Y=470,P=0.6662331819534302},
{X=552.6666666666666,Y=470.66666666666663,P=0.5002615451812744},
{X=555.3333333333333,Y=471.3333333333333,P=0.6958120465278625},
{X=562,Y=472,P=0.4994753301143646},
{X=567.3333333333333,Y=472.66666666666663,P=0.5455721616744995},
{X=573.3333333333333,Y=474,P=0.5134470462799072},
{X=580.6666666666666,Y=474.66666666666663,P=0.4518575370311737},
{X=585.3333333333333,Y=475.3333333333333,P=0.5670377612113953},
{X=589.3333333333333,Y=476,P=0.609724223613739},
{X=594.6666666666666,Y=476.66666666666663,P=0.5481007099151611},
{X=601.3333333333333,Y=477.3333333333333,P=0.4885927140712738},
{X=606.6666666666666,Y=478,P=0.5432637929916382},
{X=610,Y=478.66666666666663,P=0.6355723142623901},
{X=616.6666666666666,Y=479.3333333333333,P=0.47008851170539856},
{X=622,Y=480,P=0.5248528122901917},
{X=628.6666666666666,Y=480,P=0.46539586782455444},
{X=636.6666666666666,Y=480.66666666666663,P=0.39966750144958496},
{X=642,Y=480.66666666666663,P=0.509570300579071},
{X=647.3333333333333,Y=482,P=0.5069840550422668},
{X=652,Y=482,P=0.56694495677948},
{X=654,Y=482,P=0.7375491857528687},
{X=662,Y=482.66666666666663,P=0.4425170123577118},
{X=666.6666666666666,Y=482.66666666666663,P=0.5797702074050903},
{X=669.3333333333333,Y=482.66666666666663,P=0.7043377757072449},
{X=676,Y=482.66666666666663,P=0.49506327509880066},
{X=680,Y=482.66666666666663,P=0.6218303442001343},
{X=684.6666666666666,Y=482.66666666666663,P=0.5944092869758606},
{X=689.3333333333333,Y=482.66666666666663,P=0.5880598425865173},
{X=694,Y=482.66666666666663,P=0.5764558911323547},
{X=696,Y=482.66666666666663,P=0.7410839796066284},
{X=700,Y=482.66666666666663,P=0.626220166683197},
{X=704,Y=482.66666666666663,P=0.6229243278503418},
{X=707.3333333333333,Y=482.66666666666663,P=0.6664982438087463},
{X=714.6666666666666,Y=482.66666666666663,P=0.45310068130493164},
{X=720.6666666666666,Y=482.66666666666663,P=0.4970242381095886},
{X=724,Y=482.66666666666663,P=0.6377686858177185},
{X=728.6666666666666,Y=482.66666666666663,P=0.5830304622650146},
{X=733.3333333333333,Y=482,P=0.5852826237678528},
{X=735.3333333333333,Y=482,P=0.7534965872764587},
{X=739.3333333333333,Y=481.3333333333333,P=0.6395262479782104},
{X=742,Y=481.3333333333333,P=0.7220669984817505},
{X=746,Y=480.66666666666663,P=0.6478912830352783},
{X=750,Y=480,P=0.6395424604415894},
{X=754,Y=478.66666666666663,P=0.629923403263092},
{X=759.3333333333333,Y=478,P=0.5632662177085876},
{X=762.6666666666666,Y=477.3333333333333,P=0.6644474864006042},
{X=765.3333333333333,Y=476.66666666666663,P=0.7117174863815308},
{X=768.6666666666666,Y=476,P=0.6776741743087769},
{X=773.3333333333333,Y=474.66666666666663,P=0.5986015200614929},
{X=777.3333333333333,Y=473.3333333333333,P=0.6267250180244446},
{X=781.3333333333333,Y=471.3333333333333,P=0.6099798679351807},
{X=784.6666666666666,Y=470.66666666666663,P=0.6539067625999451},
{X=790.6666666666666,Y=468.66666666666663,P=0.49772119522094727},
{X=792.6666666666666,Y=468,P=0.7275203466415405},
{X=796,Y=466.66666666666663,P=0.6488282680511475},
{X=800,Y=465.3333333333333,P=0.6072457432746887},
{X=804,Y=463.3333333333333,P=0.5964605212211609},
{X=807.3333333333333,Y=462,P=0.6561574339866638},
{X=810,Y=460.66666666666663,P=0.6968104839324951},
{X=813.3333333333333,Y=458.66666666666663,P=0.6486343741416931},
{X=818,Y=456.66666666666663,P=0.5831184983253479},
{X=820.6666666666666,Y=455.3333333333333,P=0.6963025331497192},
{X=822.6666666666666,Y=454,P=0.7399721741676331},
{X=825.3333333333333,Y=452.66666666666663,P=0.7092769145965576},
{X=828.6666666666666,Y=451.3333333333333,P=0.6766396164894104},
{X=833.3333333333333,Y=448.66666666666663,P=0.5740551948547363},
{X=835.3333333333333,Y=447.3333333333333,P=0.7345168590545654},
{X=837.3333333333333,Y=446.66666666666663,P=0.7650047540664673},
{X=840.6666666666666,Y=445.3333333333333,P=0.6749776005744934},
{X=844.6666666666666,Y=442.66666666666663,P=0.5888175368309021},
{X=848.6666666666666,Y=440.66666666666663,P=0.5972996354103088},
{X=852,Y=438.66666666666663,P=0.6244710683822632},
{X=854,Y=437.3333333333333,P=0.7188596725463867},
{X=856.6666666666666,Y=436,P=0.6917470097541809},
{X=858.6666666666666,Y=434.66666666666663,P=0.7351683378219604},
{X=862.6666666666666,Y=432.66666666666663,P=0.6261351704597473},
{X=864.6666666666666,Y=431.3333333333333,P=0.7430726289749146},
{X=866.6666666666666,Y=430.66666666666663,P=0.7724213600158691},
{X=868.6666666666666,Y=429.3333333333333,P=0.7554715871810913},
{X=871.3333333333333,Y=428.66666666666663,P=0.7384610176086426},
{X=875.3333333333333,Y=426.66666666666663,P=0.6147278547286987},
{X=877.3333333333333,Y=426,P=0.7556149363517761},
{X=880,Y=425.3333333333333,P=0.7179256677627563},
{X=882,Y=424.66666666666663,P=0.7604349851608276},
{X=884,Y=424,P=0.7805911302566528},
{X=886,Y=423.3333333333333,P=0.7828847765922546},
{X=888,Y=422.66666666666663,P=0.78639817237854},
{X=890,Y=422,P=0.7878957390785217},
{X=892,Y=421.3333333333333,P=0.7883377075195312},
{X=894,Y=421.3333333333333,P=0.7963496446609497},
{X=896,Y=421.3333333333333,P=0.7972534894943237},
{X=898,Y=420.66666666666663,P=0.7801092267036438},
{X=900,Y=420.66666666666663,P=0.7869842052459717},
{X=902,Y=420.66666666666663,P=0.7963632345199585},
{X=904,Y=420,P=0.7890701293945312},
{X=906,Y=420,P=0.7968790531158447},
{X=908,Y=420,P=0.8703685998916626},
""";
var stylusPointCollection = new StylusPointCollection();
var stringReader = new StringReader(test);
while (stringReader.ReadLine() is { } line)
{
var regex = GetPointInfoRegex();
var match = regex.Match(line);
if (match.Success)
{
var x = match.Groups[1].Value;
var y = match.Groups[2].Value;
var p = match.Groups[3].Value;
// Do something with the extracted values
//Console.WriteLine($"X: {x}, Y: {y}, P: {p}");
stylusPointCollection.Add(new StylusPoint(double.Parse(x), double.Parse(y), float.Parse(p)));
}
else
{
Console.WriteLine("No match found.");
}
}
return stylusPointCollection;
}
[GeneratedRegex(@"\{X=(?<X>-?\d+(?:\.\d+)?),\s*Y=(?<Y>-?\d+(?:\.\d+)?),\s*P=(?<P>-?\d+(?:\.\d+)?)}\,?")]
private static partial Regex GetPointInfoRegex();
Running this code shows that calling incrementalStrokeHitTester.AddPoint(point)
triggers the StrokeHit event, but StrokeHitEventArgs.GetPointEraseResults()
returns a StrokeCollection with 0 strokes—meaning the whole stroke was erased when only a split should occur at XY=(684,446), WH=(50,70).
To make this issue clearer, I’ve visualized both the stroke and eraser range in the UI with the following code:
var geometry = stroke.GetGeometry();
Canvas.Children.Add(new Path()
{
Data = geometry,
Fill = Brushes.Black
});
Canvas.Children.Add(new Rectangle()
{
Margin = new Thickness(x, y, 0, 0),
Width = rectangleStylusShape.Width,
Height = rectangleStylusShape.Height,
Stroke = Brushes.Red,
StrokeThickness = 2
});
var eraseResult = stroke.GetEraseResult(new Rect(x,y, rectangleStylusShape.Width, rectangleStylusShape.Height));
if (eraseResult.Count > 0)
{
// It can get the correct result from GetEraseResult
}
The result looks like this:

This demonstrates that IncrementalStrokeHitTester has a calculation bug in this scenario.
Reproduction Steps
Clone my demo project code: https://github.com/lindexi/lindexi_gd/tree/7f7f914c05829be1feb090ab05b0514df4e46e97/WPFDemo/RarlereninemniBohinabairhalljere
You can clone my project by the command line:
mkdir RarlereninemniBohinabairhalljere && cd RarlereninemniBohinabairhalljere
git init
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 7f7f914c05829be1feb090ab05b0514df4e46e97
cd WPFDemo/RarlereninemniBohinabairhalljere
Run the project in VisualStudio, and you can find the VisualStudio enter the break point.
Expected behavior
The IncrementalStrokeHitTester can split into two segments.
Actual behavior
The IncrementalStrokeHitTester erases the entire stroke. As the code shows, we can find it can get the correct result from Stroke.GetEraseResult
Regression?
No response
Known Workarounds
No response
Impact
No response
Configuration
No response
Other information
No response