Fix black border in album art zoom by only ever scaling up
The corner-reveal zoom scaled down from 1.0 toward ~0.71 (via Translate to shift toward a corner), which showed up on a real device as a black border cutting into the art. Empirical testing against the actual Row layout at native canvas size showed the outer Box auto-fits an oversized child at Scale(1.0, 1.0), so scaling down from there shrinks the already- fitted image further and exposes background outside its bounds. Scaling up only ever crops further into the image, which is always safe. This drops Translate and the per-track corner selection (animation.Scale pivots at the top-left corner by default with no way to change it -- an explicit origin parameter was tried and had no effect), so the effect now always zooms into the top-left corner instead of a track-dependent one.
This commit is contained in:
1 parent
83b817c3b2
commit
cda256d1c2
2 files changed
+75
-79
No files matched your search
@@ -28,38 +28,45 @@ app's config when adding it to a device.
|
||||
instead of stale track info.
|
||||
- If a track has no `subsonic_id`, or the cover fetch fails, falls back to a
|
||||
full-width layout with no art rather than a broken image.
|
||||
- Album art gets a slow "Ken Burns" breathing effect: eases from one corner
|
||||
of the image out to the full frame, then back into that same corner, on a
|
||||
- Album art gets a slow "Ken Burns" breathing effect: eases from the full
|
||||
frame in to a zoomed-in crop of the top-left corner, then back out, on a
|
||||
continuous ~60s round-trip loop (not a one-shot reveal — see below).
|
||||
Rendered oversized (45x45 vs. the 32x32 display slot) and scaled between
|
||||
1.0 and ~0.71, since `animation.Scale` above 1.0 has no visible effect on
|
||||
a widget whose declared size already matches its container. Which corner
|
||||
it uses is derived from the track's `subsonic_id` (not true randomness) so
|
||||
a still-playing track re-rendered on the `NOWPLAYING_TTL` cache doesn't
|
||||
jarringly jump corners — only a genuinely new track does. Verified this
|
||||
doesn't blow the payload budget: counterintuitively, a *slower* zoom
|
||||
compresses *smaller*, since more adjacent ticks become pixel-identical at
|
||||
this resolution and WebP dedupes them — measured 97-264KB depending on the
|
||||
art's own visual complexity, all well under the 460KB device limit.
|
||||
Rendered oversized (45x45 vs. the 32x32 display slot) since
|
||||
`animation.Scale` has no visible effect on a widget whose declared size
|
||||
already matches its container, and scaled between 1.0 and 1.4 — only ever
|
||||
scaling *up*, never down. That's a fix for a real on-device bug: an
|
||||
earlier version zoomed *out* from a corner (scaling down from 1.0 toward
|
||||
~0.71, reached via `animation.Translate`), which showed up on a real
|
||||
device as a black border cutting into the art. Empirical testing (against
|
||||
the actual Row layout, at native canvas size) showed the outer `Box`
|
||||
auto-fits an oversized child to its own size at `Scale(1.0, 1.0)` — so
|
||||
scaling *down* from there shrinks the already-fitted image further,
|
||||
exposing background outside its bounds. Scaling *up* only ever crops
|
||||
further into the image, which is always safe. `animation.Scale`'s pivot
|
||||
defaults to the top-left corner (confirmed empirically — an explicit
|
||||
`origin` parameter was tried and had no effect at all), so there's no
|
||||
reliable way to zoom into any corner but that one; the effect always
|
||||
zooms into the top-left. Verified this doesn't blow the payload budget:
|
||||
measured ~280KB for a full render against the live station, comfortably
|
||||
under the 460KB device limit.
|
||||
|
||||
**Why a loop, not a one-shot reveal:** a one-way "start in corner, end on
|
||||
full frame, then hold" version looked jumpy in practice — since the app
|
||||
re-renders on `NOWPLAYING_TTL`, each fresh render restarts its own
|
||||
animation from tick 0, so whatever the *previous* pushed image happened to
|
||||
be showing gets abruptly replaced by a fresh "zoomed into a corner" frame.
|
||||
Looping back-and-forth doesn't eliminate that (a server-side re-render
|
||||
always restarts at tick 0 regardless of app logic), but ending exactly
|
||||
where it started means the animation also loops seamlessly *on-device*
|
||||
between refreshes, which reads as far less jarring than a one-way reveal
|
||||
that snaps back. Considered using genuinely random per-frame content (like
|
||||
`ormakers-tronbyt`'s LoRa segment, which pre-crops 60 frames directly from
|
||||
a high-resolution *static* source photo via offline Pillow processing) for
|
||||
a sharper zoom, but that technique fundamentally doesn't transfer here:
|
||||
album art is fetched live per-track at render time, and pixlet's Starlark
|
||||
sandbox has no image-cropping/pixel-analysis capability at all (confirmed
|
||||
by checking its full module list) — so there's no way to pre-bake or
|
||||
crop-at-render-time from a source we don't have until the request hits
|
||||
the live station.
|
||||
**Why a loop, not a one-shot reveal:** a one-way version looked jumpy in
|
||||
practice — since the app re-renders on `NOWPLAYING_TTL`, each fresh
|
||||
render restarts its own animation from tick 0, so whatever the *previous*
|
||||
pushed image happened to be showing gets abruptly replaced by a fresh
|
||||
zoomed-in frame. Looping back-and-forth doesn't eliminate that (a
|
||||
server-side re-render always restarts at tick 0 regardless of app logic),
|
||||
but ending exactly where it started means the animation also loops
|
||||
seamlessly *on-device* between refreshes, which reads as far less jarring
|
||||
than a one-way reveal that snaps back. Considered using genuinely random
|
||||
per-frame content (like `ormakers-tronbyt`'s LoRa segment, which pre-crops
|
||||
60 frames directly from a high-resolution *static* source photo via
|
||||
offline Pillow processing) for a sharper zoom, but that technique
|
||||
fundamentally doesn't transfer here: album art is fetched live per-track
|
||||
at render time, and pixlet's Starlark sandbox has no image-cropping/
|
||||
pixel-analysis capability at all (confirmed by checking its full module
|
||||
list) — so there's no way to pre-bake or crop-at-render-time from a
|
||||
source we don't have until the request hits the live station.
|
||||
- Shows the current show name (`context.activeShow.name`) and host name
|
||||
(`dj.name`) below the artist, separated by a thin divider line, and the
|
||||
local time/temperature (`context.clock`/`context.weather`) below that —
|
||||
|
||||