diff --git a/subwave_nowplaying.star b/subwave_nowplaying.star index 4641c49..ab4c32e 100644 --- a/subwave_nowplaying.star +++ b/subwave_nowplaying.star @@ -429,16 +429,47 @@ _GLITCH_TICKS_PER_SECOND = 20 # 1000ms / 50ms-per-tick GLITCH_REPEATS = int(GLITCH_ACTIVE_SECONDS * _GLITCH_TICKS_PER_SECOND / _GLITCH_CYCLE_TICKS) + 1 def _glitch_rest_art(art_bytes, zoom_position, subsonic_id): - # A separate, non-animated rendering of the art (scale locked at 1.0 - # throughout, via _zoom_transformation's own zoom_scale argument) for - # the glitch segment to jitter, instead of reusing the live `zoom` - # that's actually mid-cycle on the base layer underneath. Simpler than - # keeping the jitter synced to wherever that 10s cycle currently is -- - # there's nothing here that depends on frame position, so nothing to - # desync regardless of how the jitter's own timing is built. Same + # A separate, non-animated rendering of the art for the glitch segment + # to jitter, instead of reusing the live `zoom` that's actually + # mid-cycle on the base layer underneath. Simpler than keeping the + # jitter synced to wherever that 10s cycle currently is -- there's + # nothing here that depends on frame position, so nothing to desync + # regardless of how the jitter's own timing is built. Same # origin/zoom_position as the real zoom, so it doesn't visually jump # to a different part of the image when the glitch starts or ends. - return _zoom_transformation(art_bytes, 1.0, zoom_position, subsonic_id) + # + # Deliberately does NOT call _zoom_transformation(art_bytes, 1.15, + # ...): that function always *breathes* between 1.0 and whatever + # scale is passed in (it's the same "Ken Burns" animation the live + # zoom uses, over its own ART_ZOOM_DURATION cycle), so passing 1.15 + # still spends part of every cycle back down near the unsafe 1.0 + # floor -- confirmed empirically, it made the black border *worse* + # (680 black-corner hits across 200 frames, vs. 372 at a literal + # 1.0). What's needed here is a scale that's constantly 1.15, never + # breathing back down to 1.0, which this builds directly rather than + # reusing that function. + # + # At exactly scale=1.0, the outer Box auto-fits the oversized art to + # exactly ART_SLOT with no overflow left over (see _zoom_transformation's + # own comment for that behavior), leaving zero spare margin for the + # jitter's shift to reveal and producing a real, on-device black + # border (confirmed: every corner checked across 133 frames of a real + # render, not an isolated edge case). A constant 1.15 gives enough + # overflow to safely cover the jitter's +-1px shift while staying + # close enough to 1.0 to still read as "the full picture," not a + # noticeable zoom. + art = render.Image(src = art_bytes, width = ART_SIZE, height = ART_SIZE) + origin_x, origin_y = _zoom_spot(zoom_position, subsonic_id) + static_scale = animation.Transformation( + child = art, + duration = 1, + origin = animation.Origin(origin_x, origin_y), + keyframes = [ + animation.Keyframe(percentage = 0.0, transforms = [animation.Scale(1.15, 1.15)]), + animation.Keyframe(percentage = 1.0, transforms = [animation.Scale(1.15, 1.15)]), + ], + ) + return static_scale def _glitch_states_for_cycle(cycle_index): # Rotate through GLITCH_STATE_POOL instead of slicing (which doesn't @@ -638,7 +669,7 @@ def _glitch_effect(zoom): width = ART_SLOT, height = ART_SLOT, child = render.Stack(children = [ - _glitch_jitter(zoom), + # _glitch_jitter(zoom), # disabled -- isolating scanlines for now _glitch_scanlines(), _glitch_corner_ticks("#00000080"), ]),