Skip to content

fix(icons): keep the hi-DPI master instead of downscaling on the JVM (#436) - #437

Merged
kdroidFilter merged 1 commit into
masterfrom
fix/436-hidpi-tray-icon-scaling
Sep 1, 2026
Merged

fix(icons): keep the hi-DPI master instead of downscaling on the JVM (#436)#437
kdroidFilter merged 1 commit into
masterfrom
fix/436-hidpi-tray-icon-scaling

Conversation

@kdroidFilter

@kdroidFilter kdroidFilter commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Fixes #436.

Summary

IconRenderProperties.forCurrentOperatingSystem() and forMenuItem() downsampled the Compose scene (192px / 64px) to a single physical size — Windows 32, macOS 44, Linux 24, menu items 16 — before the native layer ever saw the bitmap. High-DPI information was destroyed on the JVM, and native backends could only upscale from there.

The JVM now ships the scene-resolution master and each backend owns display-scale downsampling:

  • IconRenderProperties: new jvmOwnsDownscaling flag (default true, preserving explicit-size behaviour). The two platform factories set it to false; targetWidth/targetHeight become the logical size rather than a resample target.
  • Windows: renderComposableToIcoBytes emits a real multi-frame ICO (16/20/24/32/40/48/64, clipped to the master — never upscaled) instead of a single-frame PNG wrapper. tray_windows.c loads icons and menu bitmaps at SM_CXSMICON via LoadImageW, so the shell picks the exact frame for the current DPI (ExtractIconExW kept as fallback).
  • Linux: the SNI IconPixmap pyramid skips levels larger than the source instead of interpolating every 32–128 level from a 24px master; falls back to a single native-size pixmap when the source is smaller than every catalog size.
  • macOS: menu items stay at 16pt over a ≥3× master (comment translated to English).
  • ComposableIconUtils: extracted the shared render/cleanup path into withRenderedIcon so PNG and ICO encoding share scene lifecycle handling.

Test plan

  • Issue436IconScalingTest — asserts the platform factories no longer request JVM downscaling, the PNG keeps the scene master, and the ICO is a multi-frame pyramid with no frame larger than the master
  • Issue436WindowsTrayE2ETest — Windows-only; renders a tray ICO + menu ICO and drives WindowsTrayInitializer with them (no-ops on other platforms)
  • test_sni_pixmap.c + run_pixmap_test.sh — asserts sni_pixmap_level_count_for_source never yields an upscaled level; wired into the Linux job of build-natives.yaml
  • ./gradlew jvmTest ktlintCheck passes locally (Windows)
  • CI native builds green on Linux/macOS/Windows
  • Manual check of tray + menu icon sharpness at 100%, 150% and 200% DPI

…436)

forCurrentOperatingSystem() and forMenuItem() downsampled the Compose scene
to a single physical size before the native layer ever saw the bitmap, so
high-DPI detail was lost on the JVM. The JVM now emits the scene-resolution
master and each backend owns display-scale downsampling.

- Windows: multi-frame ICO (16/20/24/32/40/48/64, clipped to the master) and
  icon loading at SM_CXSMICON so the shell picks an exact DPI match.
- Linux: the SNI pixmap pyramid skips levels larger than the source instead
  of upscaling every level past a 24px master.
- macOS: menu icons stay at 16pt over a >=3x master.
@kdroidFilter
kdroidFilter merged commit acdde87 into master Sep 1, 2026
5 checks passed
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.

Icon rendering ignores display scaling: forCurrentOperatingSystem() bakes in fixed pixel sizes

1 participant