Replace Translate-based centering with animation.Origin; add zoom position config
The previous "fix" for Scale's crop bias paired Scale with a computed Translate, on the mistaken assumption that Scale's default pivot is the top-left corner. It isn't: pixlet's docs say the default origin is already the center (0.5, 0.5). That Translate was sized assuming Scale had no self-centering at all, so it overcorrected, visibly skewing the zoom toward the bottom-right -- the opposite of the bug it meant to fix. animation.Origin does directly and correctly what that Translate was clumsily approximating, so the Translate (and its rounding="none" workaround) is removed entirely in favor of Origin. This also means "strong" no longer needs the 1.45 safety cap -- restored to 1.7, since the black-border risk was specific to the Translate formula, not Scale + Origin (verified via the calibration tool at several off-center origins). Added a "Zoom position" schema option: Center (default) or Random. Random picks from a handful of preset off-center spots, chosen per-track from subsonic_id the same way the old corner-selection logic worked, so a still-playing track doesn't jump between spots on every refresh. Verified: no black border, no popping (confirmed via frame-delta analysis -- smooth throughout, no Translate means rounding is moot), and payload well under the 460KB limit at every zoom level/position combination, against the live station.
This commit is contained in:
1 parent
4a2b3caac8
commit
d8add24c5e
3 files changed
+121
-73
No files matched your search
@@ -23,6 +23,9 @@ app's config when adding it to a device.
|
||||
muted color for the time/temp line.
|
||||
- **Album art zoom** — how far the album art's breathing zoom effect goes
|
||||
in: Off, Subtle, Moderate (default), or Strong.
|
||||
- **Zoom position** — where the zoom centers on: Center (default), or
|
||||
Random (a handful of preset off-center spots, picked per-track so a
|
||||
still-playing track doesn't jump between spots on every refresh).
|
||||
|
||||
## How it works
|
||||
|
||||
@@ -46,30 +49,39 @@ app's config when adding it to a device.
|
||||
attempts produced a real, on-device black border; see the fuller
|
||||
history below.
|
||||
|
||||
`animation.Scale`'s pivot defaults to the *center* of the widget per
|
||||
pixlet's own docs, so Scale alone is already close to centered — just
|
||||
slightly skewed toward the top-left as the zoom amount increases.
|
||||
Paired with a computed `animation.Translate` to cancel out that residual
|
||||
skew. Combining Scale with Translate at all was tried once before and
|
||||
reverted after it produced a periodic rendering "pop" roughly every
|
||||
0.7-1s — confirmed with both WebP and GIF output, independent of zoom
|
||||
amplitude. Root-caused to pixlet's own source
|
||||
Where the zoom centers on is controlled via `animation.Origin`, which
|
||||
works correctly and does real work here — contrary to an earlier,
|
||||
mistaken belief in this app's own history that it "had no effect" (that
|
||||
came from testing it set to its own default value and comparing the
|
||||
result against itself, which is of course identical either way). The
|
||||
"Random" zoom position picks from a handful of preset off-center spots
|
||||
(`ZOOM_RANDOM_SPOTS`), chosen per-track from `subsonic_id` the same way
|
||||
the old corner-selection logic worked, so a still-playing track doesn't
|
||||
jump between spots on every `NOWPLAYING_TTL` refresh. Those spots are
|
||||
kept modest (0.3-0.7 of the image's own bounds, not the full 0.0-1.0
|
||||
range) and verified safe (no black border, even at the strongest 1.7x
|
||||
zoom) via the calibration tool; more extreme corners weren't tested and
|
||||
may not be safe.
|
||||
|
||||
An earlier version tried to fix a *different* problem — `animation.Scale`
|
||||
zooming in asymmetrically toward one corner — by pairing Scale with a
|
||||
*computed* `animation.Translate`, reasoning (wrongly) that Scale's
|
||||
default pivot is the top-left corner. It isn't: pixlet's own docs say
|
||||
the default origin is already the center. That Translate, sized
|
||||
assuming Scale had no self-centering at all, overcorrected and visibly
|
||||
skewed the zoom toward the *opposite* corner instead. It also introduced
|
||||
a periodic rendering "pop" (confirmed with both WebP and GIF output,
|
||||
independent of zoom amplitude) root-caused to pixlet's own source
|
||||
(`render/animation/translate.go`): a Translate's x/y are rounded to the
|
||||
nearest whole pixel every frame by default, while Scale is never
|
||||
rounded, so a continuously-changing Scale combined with a rounded
|
||||
Translate holds still for several frames and then jumps a whole pixel
|
||||
at once. Passing `rounding = "none"` on the `Transformation` disables
|
||||
that snapping and fixes it — confirmed via frame-by-frame pixel-delta
|
||||
analysis (the amount of change is now even from frame to frame, instead
|
||||
of long still stretches punctuated by sudden jumps). "strong" is capped
|
||||
at 1.45 (not pushed further): above ~1.46 the centered crop starts
|
||||
revealing a real black border in the bottom-right corner, verified with
|
||||
the calibration tool — this is the safe ceiling, not an arbitrary
|
||||
choice. Verified payload stays well under the 460KB device limit at
|
||||
every zoom level, measured against the live station (though with
|
||||
`rounding = "none"`, WebP can no longer dedupe as many near-identical
|
||||
frames, so payloads run larger than the "pop" version did — ~370KB
|
||||
vs. ~220-320KB — still comfortably within budget).
|
||||
nearest whole pixel every frame by default, while Scale never rounds,
|
||||
so a continuously-changing Scale combined with a rounded Translate
|
||||
holds still for several frames and then jumps a whole pixel at once.
|
||||
Since `animation.Origin` already does directly and correctly what that
|
||||
Translate was clumsily approximating, the Translate was removed
|
||||
entirely rather than patched further (e.g. with `rounding = "none"`,
|
||||
which does fix the popping but wasn't needed once Origin replaced it).
|
||||
Verified payload stays well under the 460KB device limit at every zoom
|
||||
level and position, measured against the live station.
|
||||
|
||||
**Why a loop, not a one-shot reveal, and why it's short:** a one-way
|
||||
version looked jumpy in practice — since the app re-renders on
|
||||
|
||||