Fix the periodic zoom pop at its root: disable Translate's pixel rounding
Root-caused the "pop" from the Scale+Translate centered zoom (reverted a few commits back) to pixlet's own source: render/animation/translate.go rounds a Translate's x/y to the nearest whole pixel every frame by default (rounding = "round"), while animation/scale.go never rounds Scale. A continuously-interpolating Scale combined with a rounded Translate holds still for several frames, then jumps a whole pixel at once -- a real, confirmed pixlet behavior, not a flaw in this app's formula. Transformation accepts a `rounding` parameter; passing "none" disables the snapping. Confirmed via frame-by-frame pixel-delta analysis: the per-frame change is now even throughout the animation (consistently ~900 AE out of 1024 possible, every frame) instead of long near-zero stretches punctuated by sudden full-frame jumps. Re-enables the centered zoom (Scale + Translate) that was reverted for jerkiness, now smooth. ZOOM_LEVELS["strong"] stays capped at 1.45 -- that black-border ceiling is unrelated to rounding and still applies. Verified against the live station: no pop, no black border at any zoom level, payload ~370KB (up from ~220-320KB, since rounding="none" means WebP can dedupe fewer near-identical frames) but still well under the 460KB device limit.
This commit is contained in:
1 parent
9f63adbfbf
commit
4a2b3caac8
3 files changed
+73
-47
No files matched your search
+28
-18
@@ -91,12 +91,16 @@ COLOR_SCHEMES = {
|
||||
DEFAULT_COLOR_SCHEME = "cyberpunk" # matches the station's own site theme
|
||||
|
||||
# How far in the album art zoom goes, relative to its fully-zoomed-out rest
|
||||
# state (see _art_widget below for why scale never goes below 1.0).
|
||||
# state (see _art_widget below for why scale never goes below 1.0). "strong"
|
||||
# is capped at 1.45, not pushed further: with the centering Translate in
|
||||
# _art_widget, empirically anything above ~1.46 starts revealing a real
|
||||
# black border in the bottom-right corner (verified with the calibration
|
||||
# tool) -- this is the safe ceiling, not an arbitrary choice.
|
||||
ZOOM_LEVELS = {
|
||||
"off": 1.0,
|
||||
"subtle": 1.15,
|
||||
"moderate": 1.4,
|
||||
"strong": 1.7,
|
||||
"strong": 1.45,
|
||||
}
|
||||
DEFAULT_ZOOM_LEVEL = "moderate"
|
||||
|
||||
@@ -224,36 +228,42 @@ def _art_widget(art_bytes, zoom_scale):
|
||||
# 1.0". Using the same wrapper unconditionally guarantees the "off"
|
||||
# state and the rest state of every zoom level line up exactly.
|
||||
#
|
||||
# animation.Scale's pivot is fixed at the top-left corner (confirmed
|
||||
# empirically; an explicit `origin` param has no effect at all). That
|
||||
# means zooming in crops progressively more off the bottom and right
|
||||
# than the top and left -- a real cosmetic asymmetry. Pairing Scale
|
||||
# with a Translate to re-center it was tried (and visually worked),
|
||||
# but introduced a worse problem: combining Scale + Translate causes
|
||||
# pixlet to periodically re-rasterize the whole image with a visible
|
||||
# "pop" (confirmed with both WebP and GIF output, and independent of
|
||||
# zoom amplitude -- so it's pixlet's animation engine, not this app's
|
||||
# math). Scale *alone*, with no Translate at all, has none of that
|
||||
# popping. Given the choice between a subtle, constant crop asymmetry
|
||||
# and a periodic visible pop, the asymmetry is the lesser issue, so
|
||||
# this uses Scale only.
|
||||
# animation.Scale's pivot is the center of the widget by default (per
|
||||
# pixlet's own docs) -- so zooming in via Scale alone is already close
|
||||
# to centered, just slightly skewed toward the top-left as zoom
|
||||
# increases. Pairing Scale with a Translate removes that residual skew.
|
||||
#
|
||||
# Root-caused the earlier "periodic pop" bug (found when this was first
|
||||
# tried) to pixlet's own animation.Transformation source: a Translate's
|
||||
# x/y are rounded to the nearest whole pixel every frame by default
|
||||
# (`rounding = "round"`), while Scale is never rounded. Combined with a
|
||||
# continuously-interpolating Scale, that rounding makes the Translate
|
||||
# hold still for several frames and then jump a whole pixel at once --
|
||||
# a real, confirmed pixlet behavior (see render/animation/translate.go),
|
||||
# not a flaw in this app's math. `rounding = "none"` disables that
|
||||
# snapping, letting Translate interpolate continuously alongside Scale
|
||||
# -- confirmed via frame-by-frame pixel-delta analysis: the per-frame
|
||||
# change is now even throughout the animation, instead of long still
|
||||
# stretches punctuated by sudden full-frame jumps.
|
||||
top_scale = max(zoom_scale, 1.0)
|
||||
shift = (1.0 - top_scale) * (ART_SIZE / 2.0)
|
||||
zoom = animation.Transformation(
|
||||
child = art,
|
||||
duration = ART_ZOOM_DURATION,
|
||||
rounding = "none",
|
||||
keyframes = [
|
||||
animation.Keyframe(
|
||||
percentage = 0.0,
|
||||
transforms = [animation.Scale(1.0, 1.0)],
|
||||
transforms = [animation.Scale(1.0, 1.0), animation.Translate(0, 0)],
|
||||
),
|
||||
animation.Keyframe(
|
||||
percentage = 0.5,
|
||||
transforms = [animation.Scale(top_scale, top_scale)],
|
||||
transforms = [animation.Scale(top_scale, top_scale), animation.Translate(shift, shift)],
|
||||
curve = "ease_in_out",
|
||||
),
|
||||
animation.Keyframe(
|
||||
percentage = 1.0,
|
||||
transforms = [animation.Scale(1.0, 1.0)],
|
||||
transforms = [animation.Scale(1.0, 1.0), animation.Translate(0, 0)],
|
||||
curve = "ease_in_out",
|
||||
),
|
||||
],
|
||||
|
||||