Skip to content

perf(assets): re-export the background at 2560x1440 - #181

Merged
Zaldaryon merged 1 commit into
devfrom
perf/background-1440p
Aug 22, 2026
Merged

perf(assets): re-export the background at 2560x1440#181
Zaldaryon merged 1 commit into
devfrom
perf/background-1440p

Conversation

@Pixnop

@Pixnop Pixnop commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

PR #169 compressed src/renderer/src/assets/background.jpg from 4.35 MiB to 1.51 MiB and left the dimensions at 3840x2160 on purpose. The review of that PR pointed out what the compression did not fix: file size is not what the background costs at runtime. Chromium decodes an image at its intrinsic size, so the app held a 4K bitmap in the image decode cache regardless of how big the window actually was. The window measured during that review was 1911x1052 at devicePixelRatio 1, roughly a quarter of the pixels being decoded. This PR re-exports the asset at 2560x1440 and changes nothing else.

The numbers:

before after
dimensions 3840x2160 2560x1440
decoded bitmap (w * h * 4 bytes) 33,177,600 B, 31.64 MiB 14,745,600 B, 14.06 MiB
file on disk 1,581,834 B, 1.51 MiB 1,381,147 B, 1.32 MiB

That is 17.58 MiB off the decode ceiling and 196 KiB off the packaged size. The decode figure is the arithmetic bound implied by the intrinsic dimensions, not a runtime measurement, so read it as a ceiling rather than as a number you would see in a profiler. Three elements reference the asset through the bg-image-vs utility (the root div in App.tsx, the loader overlay in the same file, and PopupDialogPanel), all of them with bg-cover, so they share one entry in the decode cache and the ceiling is per-image, not per-element.

Why 2560x1440

background-size: cover on a 16:9 image in a 16:9 window means the scale factor is just window device pixels divided by image width, so the question is what the largest realistic window is in device pixels. createWindow in src/main/index.ts opens at 1280x720, clamps to a 1024x600 minimum, sets fullscreenable: false, and restores whatever size and maximized state the user last had. So the window can be anything from 1024x600 up to a maximized window on the user's largest display, multiplied by their device pixel ratio.

2560x1440 covers a maximized window on a 1440p panel exactly 1:1, and it covers a 1280-wide window at devicePixelRatio 2, which is the common Windows-at-150%-or-200% case on a laptop panel. What it does not cover is a maximized window on a 4K panel, at either DPR 1 or DPR 2, where it upscales 1.5x. I went with that trade knowingly. The root div paints this image under before:...backdrop-blur-[2px] plus a bg-zinc-950/15 tint, so the background is already being blurred and dimmed before anyone sees it, and a 1.5x upscale of a soft game scene under a 2px blur is not something I can pick out side by side. The 31.6 MiB decode, on the other hand, was paid by every user on every screen size, including the 1024x600 minimum.

Shipping 4K for the minority who would benefit means the majority pays for pixels their monitor cannot show. If someone wants both, the right fix is image-set() with two files, which costs a second asset in the bundle and is worth doing only if 4K users actually complain.

How the file was produced

Source is not the currently shipped file. git log on the asset shows the pre-#169 version at 68c7636 is a quality-99 JPEG, and downscaling from that instead of from the shipped quality-76 file avoids stacking a second generation of JPEG loss on top of the first. The difference is measurable: a 1440p downscale of the shipped file scores 41.94 dB PSNR against a 1440p downscale of the quality-99 original, so the artifacts from the first compression pass do survive the resize.

magick orig.jpg -colorspace RGB -filter Lanczos -resize 2560x1440 -colorspace sRGB \
  -quality 92 -sampling-factor 4:4:4 -interlace JPEG -strip background.jpg

The -colorspace RGB round trip does the resize in linear light. 4:4:4 chroma rather than 4:2:0 because on this particular image it wins on both axes: at a matched file size around 675 KB, 4:4:4 at quality 70 scores 35.93 dB where 4:2:0 at quality 82 scores 35.09 dB. Quality 92 was chosen to land on the same quality bar #169 set for itself rather than to quietly cash in a quality drop as a memory win.

Quality, measured

PSNR across different resolutions is meaningless, so here are two same-resolution comparisons instead.

At a maximized 1440p window, against an uncompressed Lanczos downscale of the quality-99 original as the reference: what ships today (the 4K file, downscaled by the browser) scores 41.94 dB, the new file scores 40.49 dB.

At a maximized 4K window, against the quality-99 original: what ships today scores 40.53 dB, which reproduces the 40.53 dB figure in #169's description and confirms the baseline is the same file. The new file, upscaled back to 4K, scores 38.65 dB.

So 4K users lose about 1.9 dB and everyone at 1440p or below loses about 1.4 dB, both landing in territory usually called visually lossless, and both further softened by the blur that sits over this image anyway. DSSIM tracks the same ordering if you prefer a perceptual metric: 1109 vs 777 at 1440p, 1681 vs 1164 at 4K.

Type

  • Performance

Checklist

  • Targets dev, not main.
  • npm run typecheck passes.
  • npm run lint:ci passes (0 errors, 17 pre-existing react-hooks/exhaustive-deps warnings, untouched by this PR).
  • npm run format:check passes.
  • npm run test:coverage passes, coverage at or above the floor in vitest.config.ts (lines 92.48% vs 89%, statements 90.79% vs 87%, functions 89% vs 85%, branches 87.83% vs 85%).
  • npm run build:unpack passes.

Testing

107 test files, 1220 passing, 2 skipped, the same pre-existing skips as on dev. No test changed, and none could have caught this: nothing in the suite asserts on the asset's dimensions or file size, and jsdom has no image decoder.

The check that matters here is visual, so I ran the dev build and inspected the live renderer over CDP. The window came up at 1911x1052 at devicePixelRatio 1, the same geometry the #169 review measured. Computed style on the root div resolves to url("http://localhost:5173/src/assets/background.jpg") with background-size: cover and background-position: 50% 50%, the fetch returns 200 with 1,381,147 bytes, and createImageBitmap on those bytes reports 2560x1440, so the new asset is the one being painted and there is no 404 or fallback. A full-window screenshot shows the background rendering normally: sky gradient, foliage, the blur and tint over it, no banding or visible artifacts in the sky, which is where JPEG would show first on a scene like this.

Not covered: I have no 4K panel here, so the 1.5x upscale case is argued from the metrics above rather than from having looked at it. If a reviewer has one, that is the thing worth a second pair of eyes.

Related issues

None. This is the follow-up the #169 review asked for.

PR #169 recompressed this file but left it at 3840x2160, and the review
pointed out that the dimensions are where the memory actually is: Chromium
decodes to the intrinsic size, so every window paid for a 4K bitmap no
matter how small it was. The decoded ceiling drops from 31.6 MiB
(3840*2160*4) to 14.1 MiB (2560*1440*4).

Re-exported from the pre-#169 quality-99 original recovered from git
history rather than from the shipped quality-76 file, which avoids a
second generation of JPEG loss. Lanczos in linear light, then quality 92
with 4:4:4 chroma. Measured at 40.49 dB PSNR against an uncompressed
Lanczos downscale of that original, matching the 40.53 dB bar #169 held
itself to. File size 1,581,834 -> 1,381,147 bytes.

2560x1440 lands 1:1 on a maximized window on a 1440p panel and still has
headroom for a 1280-wide window at devicePixelRatio 2. A maximized window
on a 4K panel upscales 1.5x, which sits under the 2px backdrop-blur the
root div already applies over this image.

@Zaldaryon Zaldaryon left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked out 0078a4a and verified the asset directly rather than trusting the description's numbers: the shipped file is 1,381,147 bytes and decodes to 2560x1440, both exact matches to the PR body. Confirmed it's the only background asset in the tree and the only thing src/renderer/src/styles.css's --background-image-image-vs points to, referenced by exactly the three elements the description names (App.tsx's root div and loader overlay, PopupDialogPanel), all through the same bg-image-vs utility and bg-cover, so the decode-cache math in the description applies to a single shared entry as claimed.

No code changed, so typecheck/lint/format/test:coverage are identical to dev. The reasoning for 2560x1440 over the original 3840x2160 is laid out with real arithmetic (device pixel budgets, PSNR/DSSIM at matched resolution) rather than a guess, and honestly flags the one case it doesn't cover (a maximized 4K panel) instead of hiding the trade-off.

No blocking findings. Approving.

@Zaldaryon
Zaldaryon merged commit 55a129f into dev Aug 22, 2026
7 checks passed
@Zaldaryon
Zaldaryon deleted the perf/background-1440p branch August 22, 2026 00:39
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.

2 participants