From 8b103977a656340c8bcf0ad26e3185593f241c55 Mon Sep 17 00:00:00 2001 From: Valentyn Pavliuchenko Date: Wed, 5 Aug 2026 11:02:15 +0300 Subject: [PATCH 1/2] fix: resample key images with a real filter instead of nearest-neighbour convert_image_with_format scaled every key image to the device's key size with FilterType::Nearest, which discards the antialiasing of whatever the caller drew. On a Stream Deck + the ratio is 144 -> 120, so it dropped every sixth row and column and point-sampled soft edges rather than averaging them: stair-stepped diagonals, strokes whose width alternates along their length, and unreadable small text. Use Lanczos3, and skip the resample altogether when the image already matches the key size. Key images are at most 200x200, so the filter costs microseconds; the common case of a correctly-sized image now costs nothing at all, where before it paid for a full resample. --- src/images.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/images.rs b/src/images.rs index 0215030..b3b5e25 100644 --- a/src/images.rs +++ b/src/images.rs @@ -26,7 +26,16 @@ pub fn convert_image_with_format(image_format: ImageFormat, image: DynamicImage) ImageRotation::Rot270 => image.rotate270(), }; - let image = image.resize_exact(ws as u32, hs as u32, FilterType::Nearest); + // Nearest-neighbour discards the antialiasing of whatever the caller drew. At the + // Stream Deck +'s 6:5 ratio (144 -> 120) it drops every sixth row and column and + // point-samples soft edges into stair-steps. Key images are at most 200x200, so a + // proper filter is not worth optimising away; skipping the resample entirely when + // the image is already the right size is, since that is the common case. + let image = if image.dimensions() == (ws as u32, hs as u32) { + image + } else { + image.resize_exact(ws as u32, hs as u32, FilterType::Lanczos3) + }; // Applying mirroring let image = match image_format.mirror { From 5dcdd9350bbe443180184a023b1ba17d2fb8a0e3 Mon Sep 17 00:00:00 2001 From: Valentyn Pavliuchenko Date: Wed, 5 Aug 2026 11:50:05 +0300 Subject: [PATCH 2/2] docs: drop an unjustified size claim from the resample comment --- src/images.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/images.rs b/src/images.rs index b3b5e25..adfd31a 100644 --- a/src/images.rs +++ b/src/images.rs @@ -28,9 +28,12 @@ pub fn convert_image_with_format(image_format: ImageFormat, image: DynamicImage) // Nearest-neighbour discards the antialiasing of whatever the caller drew. At the // Stream Deck +'s 6:5 ratio (144 -> 120) it drops every sixth row and column and - // point-samples soft edges into stair-steps. Key images are at most 200x200, so a - // proper filter is not worth optimising away; skipping the resample entirely when - // the image is already the right size is, since that is the common case. + // point-samples soft edges into stair-steps. + // + // Cost scales with the source area, which is up to the caller, so skip the resample + // altogether when the image already matches the format. That is both the common case + // and the escape hatch: a caller that scales to the target size itself pays nothing + // here, rather than paying for a resample to get its own pixels back. let image = if image.dimensions() == (ws as u32, hs as u32) { image } else {