From eae449395402f33e49d848ae208a45a57a72ccc0 Mon Sep 17 00:00:00 2001 From: Akiba So Date: Tue, 23 Jun 2026 16:52:14 +0800 Subject: [PATCH] fix(zoom): stop re-baseline scale oscillation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Device log showed the applied scale oscillating ~1.4x every frame while the raw pinch was smooth (cur 1.116->0.797->1.074, raw ~0.46). Root cause: on a pointer-count re-baseline (Windows touch flickers 2<->1<->2 mid-pinch) the code set _scaleStart = matrix.getMaxScaleOnAxis() — a read-back captured at a glitchy instant — so the absolute map K = scaleStart/rawScaleAtBaseline jumped frame to frame. Fix: anchor the re-baseline to the CLEAN tracked _lastAppliedScale instead of the live matrix read-back, so the displayed scale is continuous across the re-baseline regardless of any matrix transient. The math is already covered by the pinch_scale_solver "same-instant re-baseline" test; this just feeds it the right value. flutter analyze: 0. pinch_scale_solver + pen_zoom: pass. --- lib/editor/canvas/pen_interactive_viewer.dart | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/editor/canvas/pen_interactive_viewer.dart b/lib/editor/canvas/pen_interactive_viewer.dart index 52d7c40..4c60988 100644 --- a/lib/editor/canvas/pen_interactive_viewer.dart +++ b/lib/editor/canvas/pen_interactive_viewer.dart @@ -194,13 +194,20 @@ class _PenInteractiveViewerState extends State // The transitional frame itself is skipped. if (details.pointerCount != _lastPointerCount) { _lastPointerCount = details.pointerCount; - _scaleStart = _transformer.value.getMaxScaleOnAxis(); + // Anchor the new baseline to the CLEAN tracked scale (_lastAppliedScale), + // NOT a fresh matrix read-back. Windows touch flickers the pointer count + // (2↔1↔2) mid-pinch, firing this re-baseline spuriously; reading + // getMaxScaleOnAxis() at that glitchy instant popped _scaleStart to a + // noisy value, so the absolute map K = scaleStart / rawScaleAtBaseline + // oscillated frame-to-frame (the reported "zoom jump"). Using + // _lastAppliedScale makes the displayed scale CONTINUOUS across the + // re-baseline: target == _lastAppliedScale at this instant, regardless of + // any transient in the live matrix. + _scaleStart = _lastAppliedScale; _referenceFocalPoint = _transformer.toScene(details.localFocalPoint); _lastRawScale = details.scale; - _lastAppliedScale = _scaleStart!; - // Re-anchor the absolute mapping: from here, cumulative scale is measured - // relative to THIS frame's details.scale (so the next good frame starts - // from _scaleStart, not _scaleStart * a stale cumulative value). + // Re-anchor the cumulative scale to THIS frame's details.scale so the next + // good frame resumes from _scaleStart (not _scaleStart × a stale ratio). _rawScaleAtBaseline = details.scale; InputDiagnostics.instance.recordRebaseline(); return;