Fix gamma correction, frame interpolation flicker, and target card redraws

- Fix inverted gamma formula: use (i/255)^gamma instead of (i/255)^(1/gamma)
  so gamma>1 correctly darkens midtones (standard LED gamma correction)
- Fix frame interpolation flicker: move interp buffer update after temporal
  smoothing so idle-tick output is consistent with new-frame output
- Fix target card hover/animation reset: use stable placeholder values in
  card HTML for volatile metrics (data-tm attributes), patch real values
  in-place after reconcile instead of replacing entire card DOM element

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 21:35:05 +03:00
parent 7f80faf8be
commit f507a6cf11
4 changed files with 143 additions and 72 deletions

View File

@@ -64,13 +64,13 @@ def _build_gamma_lut(gamma: float) -> np.ndarray:
"""Build a 256-entry uint8 LUT for gamma correction.
gamma=1.0: identity (no correction)
gamma<1.0: brighter midtones
gamma>1.0: darker midtones
gamma<1.0: brighter midtones (gamma < 1 lifts shadows)
gamma>1.0: darker midtones (standard LED gamma, e.g. 2.22.8)
"""
if gamma == 1.0:
return np.arange(256, dtype=np.uint8)
lut = np.array(
[min(255, int(((i / 255.0) ** (1.0 / gamma)) * 255 + 0.5)) for i in range(256)],
[min(255, int(((i / 255.0) ** gamma) * 255 + 0.5)) for i in range(256)],
dtype=np.uint8,
)
return lut
@@ -407,13 +407,6 @@ class PictureColorStripStream(ColorStripStream):
frame_buf[:] = led_colors
led_colors = frame_buf
# Update interpolation buffers (raw colors, before corrections)
if self._frame_interpolation:
self._interp_from = self._interp_to
self._interp_to = led_colors.copy()
self._interp_start = loop_start
self._interp_duration = max(interval, 0.001)
# Temporal smoothing (pre-allocated uint16 scratch)
smoothing = self._smoothing
if (
@@ -426,6 +419,15 @@ class PictureColorStripStream(ColorStripStream):
int(smoothing * 256), led_colors)
t3 = time.perf_counter()
# Update interpolation buffers (smoothed colors, before corrections)
# Must be AFTER smoothing so idle-tick interpolation produces
# output consistent with new-frame ticks (both smoothed).
if self._frame_interpolation:
self._interp_from = self._interp_to
self._interp_to = led_colors.copy()
self._interp_start = loop_start
self._interp_duration = max(interval, 0.001)
# Saturation (pre-allocated int32 scratch)
saturation = self._saturation
if saturation != 1.0: