From 60ba5f8a3bb224fb8bf7e3a3811bc19f42d8881e Mon Sep 17 00:00:00 2001 From: jim kolpack Date: Tue, 29 Sep 2026 21:13:55 -0400 Subject: [PATCH] Fix album art zoom's rest frame differing from zoom-off's static frame zoom_level=off skipped animation.Transformation entirely, rendering the art as a bare Image in a Box. Every other zoom level wrapped the same Image in a Transformation. Empirically, that wrapper shifts the crop by a couple pixels versus the bare Image, even at an explicit identity Scale(1.0, 1.0) keyframe -- so "off" and the rest state of every other zoom level didn't actually agree on framing, despite both nominally being scale 1.0. That's what looked like the zoom always starting "a little zoomed in" relative to off. Fix: always go through the same Transformation, using a degenerate animation (identical Scale(1.0, 1.0) keyframes, no visible movement) for zoom_level=off. Verified pixel-identical (AE=0) first frames across off/ subtle/moderate/strong against the same live track. --- subwave_nowplaying.star | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/subwave_nowplaying.star b/subwave_nowplaying.star index a3ed250..4896ab4 100644 --- a/subwave_nowplaying.star +++ b/subwave_nowplaying.star @@ -213,9 +213,17 @@ ART_ZOOM_DURATION = 200 # 10s round-trip (5s in, 5s out) at the default 50ms/ti def _art_widget(art_bytes, zoom_scale): art = render.Image(src = art_bytes, width = ART_SIZE, height = ART_SIZE) - if zoom_scale <= 1.0: - return render.Box(width = ART_SLOT, height = ART_SLOT, child = art) + # Always route through animation.Transformation, even when zoom is off + # (a degenerate animation whose two keyframes are both Scale(1.0, 1.0), + # i.e. no visible movement). Wrapping the art in a Transformation shifts + # its crop by a few pixels versus a bare, unwrapped Image in the same + # Box -- confirmed empirically, even at an explicit Scale(1.0, 1.0) -- + # so a plain-Image "off" path and a Transformation-wrapped zoom path + # don't actually agree on framing despite both nominally being "scale + # 1.0". Using the same wrapper unconditionally guarantees the "off" + # state and the rest state of every zoom level line up exactly. + top_scale = max(zoom_scale, 1.0) zoom = animation.Transformation( child = art, duration = ART_ZOOM_DURATION, @@ -226,7 +234,7 @@ def _art_widget(art_bytes, zoom_scale): ), animation.Keyframe( percentage = 0.5, - transforms = [animation.Scale(zoom_scale, zoom_scale)], + transforms = [animation.Scale(top_scale, top_scale)], curve = "ease_in_out", ), animation.Keyframe(