fix: Initial smoothing improved

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
Mark Tolmacs
2026-04-16 15:24:10 +00:00
parent 21a7f35345
commit ddcc8f3aad
3 changed files with 45 additions and 5 deletions
+3 -2
View File
@@ -266,7 +266,7 @@ const generateElementCanvas = (
const rc = rough.canvas(canvas);
drawElementOnCanvas(element, rc, context, renderConfig);
drawElementOnCanvas(element, rc, context, renderConfig, scale);
context.restore();
@@ -402,6 +402,7 @@ const drawElementOnCanvas = (
rc: RoughCanvas,
context: CanvasRenderingContext2D,
renderConfig: StaticCanvasRenderConfig,
scale = 1,
) => {
switch (element.type) {
case "rectangle":
@@ -429,7 +430,7 @@ const drawElementOnCanvas = (
}
case "freedraw": {
context.save();
drawFreeDrawSegments(element, context, renderConfig, 0);
drawFreeDrawSegments(element, context, renderConfig, 0, undefined, scale);
context.restore();
break;
}
+7 -1
View File
@@ -161,11 +161,13 @@ const drawSubdividedSegment = (
t0y: number,
t1x: number,
t1y: number,
scale: number,
) => {
const segLen = Math.sqrt((p1x - p0x) ** 2 + (p1y - p0y) ** 2);
// Target spacing is in screen pixels; divide by scale to get scene units.
const nSubdiv = Math.max(
1,
Math.ceil(segLen / BEZIER_SUBDIVIDE_TARGET_SPACING),
Math.ceil((segLen * scale) / BEZIER_SUBDIVIDE_TARGET_SPACING),
);
// Cubic Bezier control points derived from Catmull-Rom tangents.
@@ -220,6 +222,7 @@ export const drawFreeDrawSegments = (
renderConfig: StaticCanvasRenderConfig,
fromIndex: number,
upToIndex?: number,
scale = 1,
) => {
const { points, pressures } = element;
const N = points.length;
@@ -296,6 +299,7 @@ export const drawFreeDrawSegments = (
t0[1],
t1[0],
t1[1],
scale,
);
}
};
@@ -546,6 +550,7 @@ export const generateOrUpdateFreeDrawIncrementalCanvas = (
renderConfig,
committedFromIndex,
newCommittedCount, // upToIndex - stop before the last provisional segment
canvasScale,
);
});
inc.committedPointCount = newCommittedCount;
@@ -567,6 +572,7 @@ export const generateOrUpdateFreeDrawIncrementalCanvas = (
renderConfig,
inc.committedPointCount - 1,
undefined, // draw to natural end (the tip segment)
canvasScale,
);
});
+35 -2
View File
@@ -9495,6 +9495,15 @@ class App extends React.Component<AppProps, AppState> {
let prevRawY: number | null = null;
let prevTs: number | null = null; // previous event timestamp (ms)
// Pen pressure warmup: the first few samples from a pen digitizer are
// unreliable (often spike to ~0.5). We apply an EMA whose alpha ramps
// from PEN_PRESSURE_INITIAL_ALPHA up to 1 over PEN_PRESSURE_WARMUP_SAMPLES
// so the stroke gradually eases from heavy smoothing into raw pressure.
let penPressureEma: number | null = null;
let penPressureSampleCount = 0;
const PEN_PRESSURE_WARMUP_SAMPLES = 20;
const PEN_PRESSURE_INITIAL_ALPHA = 0.1;
// Tuning constants:
// MIN_CUTOFF cutoff frequency (Hz) when speed ≈ 0; smaller = more smoothing
// BETA rate at which cutoff grows with speed; larger = quicker adaptation
@@ -10236,7 +10245,11 @@ class App extends React.Component<AppProps, AppState> {
emaDy = alphaD * rawDyDt + (1 - alphaD) * emaDy;
// 2. Adaptive cutoff: higher speed → higher cutoff → less lag.
const speed = Math.sqrt(emaDx * emaDx + emaDy * emaDy);
// Normalize to screen-space pixels/sec so BETA behaves the same
// regardless of zoom level (scene units are 1/zoom px at high zoom).
const speed =
Math.sqrt(emaDx * emaDx + emaDy * emaDy) *
this.state.zoom.value;
const cutoff = MIN_CUTOFF + BETA * speed;
const alphaP = emaAlpha(cutoff, dt);
@@ -10257,7 +10270,27 @@ class App extends React.Component<AppProps, AppState> {
if (!lastPoint || lastPoint[0] !== dx || lastPoint[1] !== dy) {
const pt = pointFrom<LocalPoint>(dx, dy);
newPoints.push(pt);
newPressures.push(ev.pressure);
let pressure = ev.pressure;
if (event.pointerType === "pen") {
// Gradually ease from aggressive smoothing into raw pressure to
// suppress the unreliable spike at the start of a pen stroke.
const progress = Math.min(
1,
penPressureSampleCount / PEN_PRESSURE_WARMUP_SAMPLES,
);
const alpha =
PEN_PRESSURE_INITIAL_ALPHA +
progress * (1 - PEN_PRESSURE_INITIAL_ALPHA);
penPressureEma =
penPressureEma === null
? pressure
: alpha * pressure + (1 - alpha) * penPressureEma;
pressure = penPressureEma;
penPressureSampleCount++;
}
newPressures.push(pressure);
lastPoint = pt;
}
}