""" 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. 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 pixlet render dev/calibration_test.star ox=0.3 oy=0.3 -o calib_offcenter.gif Keep ART_SIZE/ART_SLOT/the zoom math below in sync with the real _art_widget if that ever changes. History, if this ever needs revisiting: a Scale+Translate centered zoom was tried first and produced a periodic rendering "pop", root-caused to pixlet's own source (render/animation/translate.go): Translate's x/y are rounded to the nearest whole pixel every frame by default, while Scale never rounds, so a continuously-interpolating Scale combined with a rounded Translate holds still for several frames then jumps a whole pixel at once. `rounding = "none"` fixed that. But the Translate's *magnitude* was also wrong: it assumed Scale pivots at the top-left corner by default, when pixlet's docs say the default `origin` is already the center (0.5, 0.5) -- so the "centering" Translate was overcorrecting, visibly skewing the zoom toward the bottom-right instead. Replaced with `origin` directly (which does, correctly, what that Translate was clumsily approximating), confirmed via this calibration tool with genuinely different (non-default) origin values -- they do change the output, despite an earlier, flawed test (origin set to its own default and compared against itself) suggesting `origin` had no effect at all. Later: `origin` turned out to only have any effect while Scale is actually changing frame to frame (confirmed with this tool: varying origin_x/origin_y produced identical output for a Scale pinned to a constant value at every keyframe). At the two instants this animation's own Scale is momentarily constant (percentage 0.0 and 1.0, "rest"), the crop falls back to render.Box's own default handling of an oversized Transformation child, which isn't centered -- the crosshair below sat several pixels toward the bottom-right of center. Fixed with BOX_CROP_BIAS, a *constant* Translate (same value at every keyframe, found via this tool's crosshair) -- not the same mistake as the first attempt above, since a constant value never changes frame to frame, so there's nothing for it to round-mismatch against Scale's interpolation. """ load("render.star", "render") load("animation.star", "animation") load("calibration.png", CALIB = "file") ART_SIZE = 36 ART_SLOT = 32 ART_ZOOM_DURATION = 200 BOX_CROP_BIAS = -2 # see the docstring above; keep in sync with box_crop_bias in the real _zoom_transformation def _art_widget(art_bytes, zoom_scale, origin_x, origin_y): art = render.Image(src = art_bytes, width = ART_SIZE, height = ART_SIZE) top_scale = max(zoom_scale, 1.0) zoom = animation.Transformation( child = art, duration = ART_ZOOM_DURATION, origin = animation.Origin(origin_x, origin_y), keyframes = [ animation.Keyframe(percentage = 0.0, transforms = [animation.Scale(1.0, 1.0), animation.Translate(BOX_CROP_BIAS, BOX_CROP_BIAS)]), animation.Keyframe(percentage = 0.5, transforms = [animation.Scale(top_scale, top_scale), animation.Translate(BOX_CROP_BIAS, BOX_CROP_BIAS)], curve = "ease_in_out"), animation.Keyframe(percentage = 1.0, transforms = [animation.Scale(1.0, 1.0), animation.Translate(BOX_CROP_BIAS, BOX_CROP_BIAS)], 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")) origin_x = float(config.get("ox", "0.5")) origin_y = float(config.get("oy", "0.5")) return render.Root(child = render.Row(children = [ _art_widget(CALIB.readall(), zoom_scale, origin_x, origin_y), render.Box(width = 32, height = 32, color = "#111111"), ]))