From 3c24bdc1325a494a4d2250b2fecf14a99be3f241 Mon Sep 17 00:00:00 2001 From: dano <81797108+dwaycik@users.noreply.github.com> Date: Fri, 31 Jul 2026 08:01:32 +0200 Subject: [PATCH] Clamp GIF frame dimensions to one alignment block best_gif_dimensions() floors each axis to a multiple of 64, which drives the short axis of a wide or tall source to zero: an 800x200 GIF fits to 162x41, and floor(41 / 64) * 64 == 0. Zero then passes the per_frame_size % 4096 guard in __init__, because 0 % 4096 == 0, so the upload proceeds and sends an empty frame rather than raising. Clamping to a single 64px block keeps every result 4K-aligned: 64a * 64b * 2 == 8192ab. --- .../commands/EpomakerGifCommand.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/epomakercontroller/commands/EpomakerGifCommand.py b/src/epomakercontroller/commands/EpomakerGifCommand.py index 44bced5..b49e04a 100644 --- a/src/epomakercontroller/commands/EpomakerGifCommand.py +++ b/src/epomakercontroller/commands/EpomakerGifCommand.py @@ -45,6 +45,11 @@ from ..logger.logger import Logger +ALIGNMENT = 64 +"""Frame dimensions must be multiples of this; the firmware's animation +framebuffer is 4K page-aligned and unaligned sizes produce line artifacts.""" + + class EpomakerGifCommand(EpomakerStreamedCommand): """A command for sending animated GIFs natively to the keyboard.""" @@ -71,7 +76,16 @@ def best_gif_dimensions(source_width: int, source_height: int) -> tuple[int, int source_width = math.ceil(source_width * compress_ratio) source_height = math.ceil(source_height * compress_ratio) - return math.floor(source_width / 64) * 64, math.floor(source_height / 64) * 64 + # Clamp to one alignment block. Flooring alone drives the short axis of + # a wide or tall source to zero (a 800x200 GIF fits to 162x41, and + # floor(41 / 64) * 64 == 0). Zero then satisfies the + # `per_frame_size % 4096` check in __init__, because 0 % 4096 == 0, so + # the upload proceeds and sends an empty frame instead of raising. + # Any pair of multiples of 64 stays 4K-aligned: 64a * 64b * 2 == 8192ab. + return ( + max(ALIGNMENT, math.floor(source_width / ALIGNMENT) * ALIGNMENT), + max(ALIGNMENT, math.floor(source_height / ALIGNMENT) * ALIGNMENT), + ) def __init__(self, gif_path: str) -> None: prepared_data = self.prepare_gif(gif_path)