Files
BadNote/lib/editor/input/pressure_curve.dart
Akiba So eedb52d2f5
Some checks failed
CI / Windows build (push) Has been cancelled
feat(f5): configurable pen pressure curve (floor + gamma) pure core
The user's repeated "可配置笔" ask, as a pure value type: raw normalized
pressure is pre-shaped into [floor, 1] via a min-width floor (the plan's
marker fixed-pressure floor) and a gamma response (γ<1 = more sensitive at light
touch, γ>1 = firmer). Clamps out-of-range + NaN inputs; endpoints anchored at
floor and 1. Widget-free/storage-free; PenConfig + the canvas wire it later
(live-path, on-device validated).

flutter analyze lib/editor clean; 145/145 tests (+6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 03:20:59 +08:00

41 lines
1.6 KiB
Dart
Raw 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/input/pressure_curve.dart
//
// Configurable pen-pressure response (F5 — the user's repeated "可配置笔" ask).
// Raw normalized stylus pressure [0,1] is pre-shaped here before it reaches
// perfect_freehand, giving two user-facing knobs:
// - [floor]: a minimum output (the plan's marker "min-width floor" — a fixed-
// pressure marker uses floor≈1.0; a pen uses 0.0).
// - [gamma]: the response exponent — γ<1 makes light touches register more
// width (more sensitive), γ>1 requires firmer pressure (less sensitive).
//
// Pure value type (widget-free, storage-free) so the full mapping is unit
// tested; PenConfig / the canvas wire it later (the wiring touches the live
// draw path and is validated on-device).
import 'dart:math' as math;
/// Maps raw normalized pressure to a shaped response in `[floor, 1]`.
class PressureCurve {
const PressureCurve({this.floor = 0.0, this.gamma = 1.0})
: assert(floor >= 0.0 && floor < 1.0),
assert(gamma > 0.0);
/// Minimum output (>=0, <1). 0 = full dynamic range; raise toward 1 for a
/// fixed-pressure feel (marker).
final double floor;
/// Response exponent (>0). 1 = linear; <1 = more sensitive at light pressure;
/// >1 = firmer.
final double gamma;
/// Linear, full-range pen response (identity).
static const PressureCurve linear = PressureCurve();
/// Shape [pressure] (clamped to [0,1]) into `[floor, 1]`.
double apply(double pressure) {
final p = pressure.isNaN ? 0.0 : pressure.clamp(0.0, 1.0);
final shaped = gamma == 1.0 ? p : math.pow(p, gamma).toDouble();
return floor + (1.0 - floor) * shaped;
}
}