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
+21
-15
@@ -1,7 +1,8 @@
|
||||
"""
|
||||
Dev tool, not part of the deployed app: renders dev/calibration.png through
|
||||
the exact same zoom logic as _art_widget in subwave_nowplaying.star, so you
|
||||
can visually check how much margin gets cropped at rest and at full zoom.
|
||||
can visually check how much margin gets cropped at rest and at full zoom,
|
||||
and whether the crop is even on all sides.
|
||||
|
||||
calibration.png is a 360x360 grid: concentric 10%-wide color rings (magenta
|
||||
outermost, then yellow, then cyan) plus four corner markers (red TL, green
|
||||
@@ -14,17 +15,20 @@ Usage:
|
||||
Keep ART_SIZE/ART_SLOT/the zoom math below in sync with the real
|
||||
_art_widget if that ever changes.
|
||||
|
||||
Note: a centered (Scale + Translate) version of this zoom was tried and
|
||||
visually fixed the top-left crop bias below, but combining Scale with
|
||||
Translate causes pixlet to periodically re-rasterize the whole image with
|
||||
a visible "pop" -- confirmed with both WebP and GIF output, independent of
|
||||
zoom amplitude, so it's pixlet's animation engine, not app-level math. This
|
||||
tool (and the real app) stick to Scale alone -- a real but minor crop-bias
|
||||
cosmetic issue, instead of the pop a centered zoom introduces. If you want
|
||||
to re-investigate centering, add animation.Translate back in here first
|
||||
and compare frame-to-frame pixel deltas (see git history around the
|
||||
"Center the album art zoom" / "Revert jerky zoom fix" commits) before
|
||||
touching the real app.
|
||||
History, if this ever needs revisiting: a Scale+Translate centered zoom
|
||||
was tried, then reverted after it produced a periodic rendering "pop"
|
||||
(confirmed with both WebP and GIF output, independent of zoom amplitude).
|
||||
Root cause, found in pixlet's own source
|
||||
(render/animation/translate.go): a Translate's x/y are rounded to the
|
||||
nearest whole pixel every frame by default (`rounding = "round"`), while
|
||||
Scale is never rounded -- so a continuously-interpolating Scale combined
|
||||
with a rounded Translate holds still for several frames, then jumps a
|
||||
whole pixel at once. Passing `rounding = "none"` on the Transformation
|
||||
(see below) disables that snapping and fixed it -- confirmed via
|
||||
frame-by-frame pixel-delta analysis (even per-frame change throughout,
|
||||
instead of long still stretches punctuated by sudden jumps). If a future
|
||||
change reintroduces jerkiness, check whether `rounding = "none"` is still
|
||||
present before suspecting the shift formula.
|
||||
"""
|
||||
|
||||
load("render.star", "render")
|
||||
@@ -38,13 +42,15 @@ ART_ZOOM_DURATION = 200
|
||||
def _art_widget(art_bytes, zoom_scale):
|
||||
art = render.Image(src = art_bytes, width = ART_SIZE, height = ART_SIZE)
|
||||
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)]),
|
||||
animation.Keyframe(percentage = 0.5, transforms = [animation.Scale(top_scale, top_scale)], curve = "ease_in_out"),
|
||||
animation.Keyframe(percentage = 1.0, transforms = [animation.Scale(1.0, 1.0)], curve = "ease_in_out"),
|
||||
animation.Keyframe(percentage = 0.0, transforms = [animation.Scale(1.0, 1.0), animation.Translate(0, 0)]),
|
||||
animation.Keyframe(percentage = 0.5, 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), animation.Translate(0, 0)], curve = "ease_in_out"),
|
||||
],
|
||||
)
|
||||
return render.Box(width = ART_SLOT, height = ART_SLOT, child = zoom)
|
||||
|
||||