import '../models/ink_point.dart'; /// Stabilization level for hand-drawn strokes. enum StabilizationLevel { none, light, medium, heavy } /// Smooths pen input using an Exponential Moving Average (EMA) filter. /// /// - **none**: no smoothing (passthrough) /// - **light**: alpha = 0.6 (subtle smoothing) /// - **medium**: alpha = 0.4 (moderate smoothing) /// - **heavy**: alpha = 0.25 (strong smoothing, removes most tremor) /// /// The formula applied to each coordinate independently: /// x_smoothed = alpha * x_raw + (1 - alpha) * x_prev class StrokeStabilizer { final StabilizationLevel level; double? _prevX; double? _prevY; StrokeStabilizer({this.level = StabilizationLevel.none}); double get _alpha { switch (level) { case StabilizationLevel.none: return 1.0; case StabilizationLevel.light: return 0.6; case StabilizationLevel.medium: return 0.4; case StabilizationLevel.heavy: return 0.25; } } /// Filters a raw point through the EMA, returning a smoothed point. /// Returns the raw point unchanged if level is [StabilizationLevel.none]. InkPoint filter(InkPoint rawPoint) { if (level == StabilizationLevel.none) return rawPoint; final alpha = _alpha; if (_prevX == null || _prevY == null) { _prevX = rawPoint.x; _prevY = rawPoint.y; return rawPoint; } final smoothedX = alpha * rawPoint.x + (1 - alpha) * _prevX!; final smoothedY = alpha * rawPoint.y + (1 - alpha) * _prevY!; _prevX = smoothedX; _prevY = smoothedY; return InkPoint( x: smoothedX, y: smoothedY, pressure: rawPoint.pressure, tilt: rawPoint.tilt, timestamp: rawPoint.timestamp, pointerDeviceKind: rawPoint.pointerDeviceKind, ); } /// Resets the filter state. Call when starting a new stroke. void reset() { _prevX = null; _prevY = null; } }