The centering Translate formula, (1-s)*(ART_SIZE/2), was derived assuming it's applied in final output pixels. That held up 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, confirmed with the calibration tool (true black RGB(0,0,0) pixels, not just dark content). Dividing the shift by the scale factor keeps the crop inside the image's bounds at every zoom level the app offers, verified via the calibration tool at 1.0/1.15/1.4/1.7x and against a live station -- at the cost of the most extreme (1.7x) setting being very slightly off-center rather than perfectly centered, which is a trade worth making since going outside the art's bounds is worse. Moderate and subtle remain visibly centered.
51 lines
2.2 KiB
Python
51 lines
2.2 KiB
Python
"""
|
|
Dev tool, not part of the deployed app: renders dev/calibration.png through
|
|
the exact same zoom logic as _art_widget in subwave_nowplaying.star, so you
|
|
can visually check how much margin gets cropped at rest and at full zoom,
|
|
and whether the crop is even on all sides.
|
|
|
|
calibration.png is a 360x360 grid: concentric 10%-wide color rings (magenta
|
|
outermost, then yellow, then cyan) plus four corner markers (red TL, green
|
|
TR, blue BL, white BR) and a center crosshair. At rest, you should see a
|
|
thin, EVEN sliver of magenta on all four edges. As it zooms in, each corner
|
|
marker should get clipped by the same amount -- if one side/corner loses
|
|
noticeably more than the others, the zoom isn't centered.
|
|
|
|
Usage:
|
|
pixlet render dev/calibration_test.star -o calib.gif
|
|
pixlet render dev/calibration_test.star zoom_scale=1.0 -o calib_rest.gif # rest only
|
|
|
|
Keep ART_SIZE/ART_SLOT/the zoom math below in sync with the real
|
|
_art_widget if that ever changes.
|
|
"""
|
|
|
|
load("render.star", "render")
|
|
load("animation.star", "animation")
|
|
load("calibration.png", CALIB = "file")
|
|
|
|
ART_SIZE = 36
|
|
ART_SLOT = 32
|
|
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) / top_scale
|
|
zoom = animation.Transformation(
|
|
child = art,
|
|
duration = ART_ZOOM_DURATION,
|
|
keyframes = [
|
|
animation.Keyframe(percentage = 0.0, transforms = [animation.Scale(1.0, 1.0), animation.Translate(0, 0)]),
|
|
animation.Keyframe(percentage = 0.5, 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), animation.Translate(0, 0)], curve = "ease_in_out"),
|
|
],
|
|
)
|
|
return render.Box(width = ART_SLOT, height = ART_SLOT, child = zoom)
|
|
|
|
def main(config):
|
|
zoom_scale = float(config.get("zoom_scale", "1.4"))
|
|
return render.Root(child = render.Row(children = [
|
|
_art_widget(CALIB.readall(), zoom_scale),
|
|
render.Box(width = 32, height = 32, color = "#111111"),
|
|
]))
|