From 975ff6da28fd1df500ab4125cef9301138699e2c Mon Sep 17 00:00:00 2001 From: jim kolpack Date: Thu, 1 Oct 2026 21:57:18 -0400 Subject: [PATCH] Center the album art zoom instead of favoring the top-left animation.Scale's pivot is fixed at the top-left corner with no way to change it (an explicit origin param has no effect at all). Left alone, that meant zooming in cropped progressively more off the bottom and right than the top and left -- and since most of the animation's duration is spent between keyframes rather than sitting at the symmetric rest state, this read as a persistent "bottom and right are cropped" on the real device. Fix: pair Scale with a Translate that's a deterministic function of the same scale value, re-centering the zoom so it crops evenly from all sides. Verified against a synthetic calibration grid image (concentric color rings + corner markers) rendered through the actual _art_widget code -- confirms even margins at rest and symmetric cropping as it zooms in, with no black border anywhere in the cycle. Safe because scale never drops below 1.0 here; the earlier black-border bug came from combining Translate with scale below 1.0, not from Translate itself. --- subwave_nowplaying.star | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/subwave_nowplaying.star b/subwave_nowplaying.star index 4896ab4..e2ccab3 100644 --- a/subwave_nowplaying.star +++ b/subwave_nowplaying.star @@ -223,23 +223,37 @@ def _art_widget(art_bytes, zoom_scale): # 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. + # + # animation.Scale's pivot is fixed at the top-left corner (confirmed + # empirically; an explicit `origin` param has no effect at all). Left + # alone, that means zooming in crops progressively *more* off the + # bottom and right than the top and left -- most of the animation's + # duration is spent between keyframes, not sitting at the symmetric + # rest state, so this read as a persistent bottom/right crop. Pairing + # Scale with a Translate that's a deterministic function of the same + # scale value (`shift`) re-centers the zoom so it crops evenly from + # all sides instead -- verified against a calibration grid image. + # This is safe because scale never drops below 1.0 here: the + # black-border bug from an earlier version came from combining + # Translate with scale *below* 1.0, not from Translate itself. 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, 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", ), ],