feat(perf): RDP stroke simplification for storage/render compaction (R1/R10)
Some checks failed
CI / Windows build (push) Has been cancelled

simplifyStroke reduces a stroke's points via Ramer–Douglas–Peucker at a
normalized perpendicular-distance tolerance: a fast Surface-Pen stroke drops
hundreds of near-collinear samples with no visible change, shrinking the DB row
and speeding re-rasterization (R1/R10). Endpoints + significant vertices kept;
pressure/tilt + color/width/tool/id preserved; <=2 points or tol<=0 are no-ops
(returns the same instance). The commit path can call it before saveHost; the
live in-progress stroke stays untouched.

Pure geometry over EditorStroke; fully unit-tested (collinear collapse, peak
retention, within-tolerance drop, metadata preservation).

flutter analyze lib/editor clean; 198/198 tests (+7).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-23 03:33:39 +08:00
parent b359000991
commit 1e836d6e7d
2 changed files with 184 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
// Tests for RDP stroke simplification (R1/R10 perf).
import 'package:flutter_test/flutter_test.dart';
import 'package:badnote/editor/engine/stroke_model.dart';
import 'package:badnote/editor/engine/stroke_simplify.dart';
EditorStroke _stroke(List<List<double>> pts, {List<double?>? pressures}) =>
EditorStroke.create(
id: 's',
points: [
for (var i = 0; i < pts.length; i++)
EditorPoint(
x: pts[i][0],
y: pts[i][1],
pressure: pressures?[i],
),
],
);
void main() {
test('collinear points collapse to the two endpoints', () {
final s = _stroke([
[0.0, 0.0],
[0.1, 0.1],
[0.2, 0.2],
[0.3, 0.3],
]);
final out = simplifyStroke(s, tolerance: 0.0008);
expect(out.points.length, 2);
expect(out.points.first.x, 0.0);
expect(out.points.last.x, 0.3);
});
test('a perceptually significant peak is kept', () {
final s = _stroke([
[0.0, 0.0],
[0.5, 0.5], // far off the (0,0)-(1,0) line → kept
[1.0, 0.0],
]);
final out = simplifyStroke(s, tolerance: 0.0008);
expect(out.points.length, 3);
});
test('a deviation within tolerance is dropped', () {
final s = _stroke([
[0.0, 0.0],
[0.5, 0.0001], // perp dist 0.0001 < 0.0008 → dropped
[1.0, 0.0],
]);
final out = simplifyStroke(s, tolerance: 0.0008);
expect(out.points.length, 2);
});
test('<= 2 points are returned unchanged (same instance)', () {
final s = _stroke([
[0.1, 0.1],
[0.2, 0.2],
]);
expect(identical(simplifyStroke(s), s), isTrue);
});
test('non-positive tolerance is a no-op', () {
final s = _stroke([
[0.0, 0.0],
[0.1, 0.11],
[0.2, 0.2],
]);
expect(identical(simplifyStroke(s, tolerance: 0), s), isTrue);
});
test('pressure rides along on retained points', () {
final s = _stroke([
[0.0, 0.0],
[0.5, 0.5],
[1.0, 0.0],
], pressures: [
0.2,
0.9,
0.3,
]);
final out = simplifyStroke(s, tolerance: 0.0008);
expect(out.points.map((p) => p.pressure), [0.2, 0.9, 0.3]);
});
test('metadata (color/width/tool/id) is preserved', () {
final s = EditorStroke.create(
id: 'keep-me',
color: 0xFF112233,
width: 0.02,
tool: EditorTool.highlighter,
points: [
const EditorPoint(x: 0, y: 0),
const EditorPoint(x: 0.1, y: 0.1),
const EditorPoint(x: 0.2, y: 0.2),
],
);
final out = simplifyStroke(s, tolerance: 0.0008);
expect(out.id, 'keep-me');
expect(out.color, 0xFF112233);
expect(out.width, 0.02);
expect(out.tool, EditorTool.highlighter);
});
}