Files
BadNote/lib/editor/engine/stroke_simplify.dart
Akiba So 1e836d6e7d
Some checks failed
CI / Windows build (push) Has been cancelled
feat(perf): RDP stroke simplification for storage/render compaction (R1/R10)
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>
2026-06-23 03:33:39 +08:00

81 lines
2.5 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// lib/editor/engine/stroke_simplify.dart
//
// RamerDouglasPeucker stroke point reduction. A fast Surface-Pen stroke can
// land hundreds of nearly-collinear samples; thinning them before persistence
// shrinks the DB row + speeds re-rasterization (R1/R10 perf) with no visible
// change. Endpoints + perceptually-significant vertices are kept; pressure/tilt
// ride along on the retained points.
//
// Pure geometry over EditorStroke (normalized coords); fully unit-tested. The
// commit path can call this before saveHost; the live in-progress stroke is left
// untouched so drawing stays crisp.
import 'stroke_model.dart';
/// Returns [stroke] with its points reduced by RDP at [tolerance] (perpendicular
/// distance in normalized units; larger = more aggressive). Strokes with <= 2
/// points, or a non-positive tolerance, are returned unchanged.
EditorStroke simplifyStroke(EditorStroke stroke, {double tolerance = 0.0008}) {
final pts = stroke.points;
if (pts.length <= 2 || tolerance <= 0) return stroke;
final keep = List<bool>.filled(pts.length, false);
keep[0] = true;
keep[pts.length - 1] = true;
_rdp(pts, 0, pts.length - 1, tolerance * tolerance, keep);
final reduced = <EditorPoint>[
for (var i = 0; i < pts.length; i++)
if (keep[i]) pts[i],
];
if (reduced.length == pts.length) return stroke;
return stroke.copyWith(points: reduced);
}
// Iterative-friendly recursion over the index range [first, last].
void _rdp(
List<EditorPoint> pts,
int first,
int last,
double tolSq,
List<bool> keep,
) {
if (last <= first + 1) return;
var maxDistSq = 0.0;
var index = -1;
final ax = pts[first].x, ay = pts[first].y;
final bx = pts[last].x, by = pts[last].y;
for (var i = first + 1; i < last; i++) {
final d = _perpDistSq(pts[i].x, pts[i].y, ax, ay, bx, by);
if (d > maxDistSq) {
maxDistSq = d;
index = i;
}
}
if (maxDistSq > tolSq && index != -1) {
keep[index] = true;
_rdp(pts, first, index, tolSq, keep);
_rdp(pts, index, last, tolSq, keep);
}
}
/// Squared perpendicular distance of (px,py) from the segment (ax,ay)-(bx,by).
/// Degenerate segment (a==b) falls back to squared distance to the point.
double _perpDistSq(
double px,
double py,
double ax,
double ay,
double bx,
double by,
) {
final dx = bx - ax, dy = by - ay;
final lenSq = dx * dx + dy * dy;
if (lenSq == 0) {
final ex = px - ax, ey = py - ay;
return ex * ex + ey * ey;
}
final cross = (px - ax) * dy - (py - ay) * dx;
return (cross * cross) / lenSq;
}