diff --git a/dev/calibration_test.star b/dev/calibration_test.star index e4c657d..ca5232c 100644 --- a/dev/calibration_test.star +++ b/dev/calibration_test.star @@ -30,7 +30,7 @@ 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) + shift = (1.0 - top_scale) * (ART_SIZE / 2.0) / top_scale zoom = animation.Transformation( child = art, duration = ART_ZOOM_DURATION, diff --git a/subwave_nowplaying.star b/subwave_nowplaying.star index e2ccab3..2ddf611 100644 --- a/subwave_nowplaying.star +++ b/subwave_nowplaying.star @@ -230,14 +230,24 @@ def _art_widget(art_bytes, zoom_scale): # 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 + # Scale with a Translate 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 + # + # The naive "final pixel shift" formula, (1-s)*(ART_SIZE/2), looked + # right at a moderate zoom (1.4x) but pushed past the image's actual + # edge at a strong one (1.7x) -- a real, on-device black border in the + # bottom-right corner. That means Translate isn't applied in final + # output pixels here; empirically, dividing by `top_scale` keeps the + # crop inside the image's bounds at every zoom level this app offers + # (verified via the calibration tool at both 1.4x and 1.7x), at the + # cost of the *most* extreme (1.7x) zoom being very slightly + # off-center rather than perfectly centered -- a trade worth making + # since going outside the art's bounds is worse than a slight + # asymmetry. 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) + shift = (1.0 - top_scale) * (ART_SIZE / 2.0) / top_scale zoom = animation.Transformation( child = art, duration = ART_ZOOM_DURATION,