From 88db75b0dc9830081c6aa2a036ff0c848ffa6aca Mon Sep 17 00:00:00 2001 From: Darrell van Swinderen Date: Wed, 23 Sep 2026 11:23:36 +0200 Subject: [PATCH] fix(streamdeck): crop the discard key's picture square instead of squashing it The Stream Deck app draws SVG with a renderer that ignores `preserveAspectRatio="xMidYMid slice"`, so the 21:9 thumbnail was squashed into the square key rather than covering it. GoodBit now crops the middle square of the frame and sends it at 144 x 144, so the key's SVG places it one to one and there is nothing left to scale wrongly. Plugin 0.4.2. Checked: the preview route now hands back a 144 x 144 JPEG of the middle of the newest clip, undistorted. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/main/services/streamdeck/actions.ts | 18 ++++++++++++++++-- .../manifest.json | 2 +- .../src/actions/discard-latest.ts | 6 ++++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/main/services/streamdeck/actions.ts b/src/main/services/streamdeck/actions.ts index d55b174..2350475 100644 --- a/src/main/services/streamdeck/actions.ts +++ b/src/main/services/streamdeck/actions.ts @@ -229,7 +229,7 @@ async function discardVerdict(clip: Clip): Promise { @@ -242,7 +242,21 @@ export async function latestPreview(): Promise { const file = await videoService.ensureThumbnail(clip); const image = nativeImage.createFromPath(file); if (!image.isEmpty()) { - thumbnail = `data:image/jpeg;base64,${image.resize({ height: 144 }).toJPEG(80).toString('base64')}`; + /* + * Cropped square here, not in the key's SVG. The Stream Deck app draws + * SVG with a renderer that ignores `preserveAspectRatio="... slice"`, so + * a 21:9 frame handed to it was squashed into the square key rather than + * covering it. The middle of the frame is where the action is. + */ + const { width, height } = image.getSize(); + const side = Math.min(width, height); + const square = image.crop({ + x: Math.floor((width - side) / 2), + y: Math.floor((height - side) / 2), + width: side, + height: side, + }); + thumbnail = `data:image/jpeg;base64,${square.resize({ width: 144, height: 144 }).toJPEG(82).toString('base64')}`; } } catch { // No picture is a key with a plain face, not a failed key. diff --git a/streamdeck-plugin/io.github.darrellvs.goodbit.sdPlugin/manifest.json b/streamdeck-plugin/io.github.darrellvs.goodbit.sdPlugin/manifest.json index 2c7626a..0fc6e6e 100644 --- a/streamdeck-plugin/io.github.darrellvs.goodbit.sdPlugin/manifest.json +++ b/streamdeck-plugin/io.github.darrellvs.goodbit.sdPlugin/manifest.json @@ -2,7 +2,7 @@ "$schema": "https://schemas.elgato.com/streamdeck/plugins/manifest.json", "UUID": "io.github.darrellvs.goodbit", "Name": "GoodBit", - "Version": "0.4.1.0", + "Version": "0.4.2.0", "Author": "GoodBit", "Description": "Save the replay, then tag, publish or discard the clip you just saved, and see today's count, without leaving the game. Needs GoodBit running with the Stream Deck connection switched on in Settings, Connections.", "URL": "https://darrellvs.github.io/goodbit/", diff --git a/streamdeck-plugin/src/actions/discard-latest.ts b/streamdeck-plugin/src/actions/discard-latest.ts index 811e4c5..fe75ae0 100644 --- a/streamdeck-plugin/src/actions/discard-latest.ts +++ b/streamdeck-plugin/src/actions/discard-latest.ts @@ -117,7 +117,9 @@ export class DiscardLatest extends SingletonAction { /** * The key's face as an SVG data URL, or '' for the plain icon from the manifest. * - * 144 square. The thumbnail fills it at nearly full brightness, because the + * 144 square. The thumbnail arrives already cropped square by GoodBit (the + * Stream Deck's SVG renderer ignores `slice` and would squash a wide frame), + * and fills the key at nearly full brightness, because the * picture is the point of the key: darkening the whole frame so the mark read * made the clip itself unreadable on the LCD. The mark sits on a small dark * disc of its own instead, trash in the danger colour, or a lock in the light @@ -131,7 +133,7 @@ function face(preview: Preview): string { const colour = kept ? '#f2efea' : '#d45757'; const svg = ` - + ${glyph}