Skip to content

fix: resample key images with a real filter instead of nearest-neighbour - #62

Open
constrictor wants to merge 2 commits into
OpenActionAPI:mainfrom
constrictor:fix-key-image-resampling
Open

fix: resample key images with a real filter instead of nearest-neighbour#62
constrictor wants to merge 2 commits into
OpenActionAPI:mainfrom
constrictor:fix-key-image-resampling

Conversation

@constrictor

@constrictor constrictor commented Aug 5, 2026

Copy link
Copy Markdown

Fixes #61.

convert_image_with_format resampled every key image with FilterType::Nearest, which discards the antialiasing of whatever the caller drew. On a Stream Deck + that is 144 → 120, a 6:5 ratio, so it dropped every sixth row and column and point-sampled soft edges instead of averaging them — stair-stepped diagonals, strokes whose width visibly alternates along their length, and small text reduced to mush.

This switches to Lanczos3 and adds an early-out when the image already matches the key size.

The early-out is worth having on its own: resize_exact runs the sampler unconditionally, so a caller that already hands over a correctly-sized image was paying for a full resample to get its own pixels back. That case is now free, which also gives callers a way to opt out of the filter entirely by pre-scaling.

Triangle would also be a large improvement if Lanczos3 seems too heavy — happy to switch.

Correction to an earlier version of this description, prompted by @Terrorwolf01 in #61: I originally justified the filter choice with "key images are at most 200×200". That number was not derived from anything. The real ceiling in Kind::key_image_format() is 120×120, and it is the wrong bound regardless — this function is public and also takes lcd_image_format(), which reaches 800×100 and 100×1200. Cost is really driven by the source size, which is unbounded through the public API. Measured on image 0.25.10, release build:

case source → dest Nearest Triangle Lanczos3
Plus key 144×144 → 120×120 133 µs 188 µs 320 µs
Mk2 key 144×144 → 72×72 54 µs 88 µs 182 µs
XL key, large source 512×512 → 96×96 148 µs 597 µs 1507 µs
key, very large source 1024×1024 → 120×120 286 µs 2078 µs 6852 µs
Plus LCD strip 900×112 → 800×100 746 µs 1006 µs 1674 µs
PlusXl LCD 112×1400 → 100×1200 1088 µs 1512 µs 2573 µs

For a host rendering at roughly key size, Lanczos3 costs ~190 µs more per update than Nearest. The outlier is a large source image: 1024×1024 → 120×120 is 24× Nearest. On that evidence Triangle is arguably the better default — ~3.3× cheaper there, and visually near-identical to Lanczos3 at the ratios that motivated this. Say the word and I'll switch it.

Testing

I found this writing an OpenDeck plugin that draws its key faces as SVG, against a Stream Deck +. Rendering the same source through the full pipeline twice — including both JPEG passes, changing only the filter — the Nearest output matches what the panel shows and the Lanczos3 output is clean, with no change to the artwork.

I could not run cargo check on the crate itself here: hidapi needs libudev headers that aren't installed on this machine. I type-checked the changed expression against image 0.25.10 with the same imports images.rs has, and rustfmt --check on src/images.rs is clean. Worth a second pair of eyes on the build.

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.
@nekename

nekename commented Aug 5, 2026

Copy link
Copy Markdown
Member

I've commented on the issue. In the future, please only open a PR and not an issue, since the discussion can be tracked in a single place without increasing maintenance overhead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Key images are resampled with FilterType::Nearest, discarding their antialiasing

2 participants