Skip to content

fix(images): use dual DPI baselines for correct pixelRatio on all platforms#8163

Merged
mimecuvalo merged 4 commits intomainfrom
mime/asset-resolution-zoom
Mar 6, 2026
Merged

fix(images): use dual DPI baselines for correct pixelRatio on all platforms#8163
mimecuvalo merged 4 commits intomainfrom
mime/asset-resolution-zoom

Conversation

@mimecuvalo
Copy link
Copy Markdown
Member

@mimecuvalo mimecuvalo commented Mar 5, 2026

High-DPI PNGs (e.g. Retina screenshots) store their logical size in asset.props.w/h, but their actual pixel dimensions are larger. Without knowing the pixel ratio, the CDN image resize endpoints cap the requested width at the logical size, returning a downscaled image that looks blurry on retina displays.

This PR adds an optional pixelRatio property to TLImageAsset, populated from the PNG pHYs chunk via MediaHelpers.getImageSize(), and uses it in both multiplayerAssetStore and createDemoAssetStore to request the correct pixel-level width from image transform endpoints.

The pHYs DPI detection now tries both standard baselines — 96 DPI (Windows/web) and 72 DPI (macOS) — and picks whichever yields a clean integer ratio above 1. This correctly handles all common cases:

Image DPI Source pixelRatio
72 macOS 1x 1
96 Windows 1x / web standard 1
144 macOS 2x Retina 2
192 Windows 2x HiDPI 2
216 macOS 3x 3
288 Windows 3x 3

The previous implementation used 72 DPI as the sole baseline, which was only correct for macOS. Any standard 96 DPI PNG (extremely common from Windows and tools like Photoshop/GIMP) got a pixelRatio of ~1.333, causing those images to appear ~25% smaller than intended.

before+after (looking at her blue hair can show you the crispness of the lines)
Screenshot 2026-03-05 at 15 15 46
Screenshot 2026-03-05 at 16 19 34

Change type

  • improvement

API changes

  • Added optional pixelRatio property to TLImageAsset
  • Added pixelRatio: number to MediaHelpers.getImageSize() return type
  • Added migration AddPixelRatio (version 6) for image assets

Test plan

  1. Drop a macOS 2x Retina screenshot (144 DPI pHYs) onto the canvas — should get pixelRatio: 2 and display at half its pixel dimensions
  2. Drop a standard Windows/web PNG (96 DPI pHYs) — should get no pixelRatio set (treated as 1) and display at full pixel size
  3. Drop a Windows HiDPI screenshot (192 DPI) — should get pixelRatio: 2
  4. Zoom in/out on a high-DPI image and confirm the resolved image URL requests width scaled by the pixel ratio
  • Unit tests

Release notes

  • Fix high-DPI image sizing to work correctly across macOS and Windows by detecting the source DPI baseline from the PNG metadata.

Note

Medium Risk
Introduces a new optional TLImageAsset.props.pixelRatio and uses it to change how resized image URLs are computed, which can affect image rendering quality and bandwidth/costs across clients. Also updates schema validation/migrations and MediaHelpers.getImageSize’s return type, so downstream consumers may need to handle the new field.

Overview
Fixes blurry/incorrectly scaled high-DPI PNG rendering by recording the source pixelRatio on image assets and using it when generating resize URLs.

MediaHelpers.getImageSize now returns { w, h, pixelRatio } and detects pixel ratio from PNG pHYs using dual 96/72 DPI baselines. TLImageAsset gains optional pixelRatio with validator + migration (AddPixelRatio), and both multiplayerAssetStore and the sync demo asset store scale requested transform width by w * pixelRatio to fetch true-resolution images.

Written by Cursor Bugbot for commit 864fe31. This will update automatically on new commits. Configure here.

…tion

High-DPI PNGs (e.g. 2x screenshots) have their logical size stored in
asset.props.w/h but their true pixel dimensions are larger. Without
knowing the pixelRatio, the CDN image resize endpoints cap the requested
width at the logical size, returning a downscaled image that looks blurry
on retina displays.

This adds an optional pixelRatio property to TLImageAsset, populated from
the PNG pHYs chunk via MediaHelpers.getImageSize(), and uses it in both
multiplayerAssetStore and createDemoAssetStore to request the correct
pixel-level width from image transform endpoints.

Made-with: Cursor
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
examples Ready Ready Preview Mar 6, 2026 5:43pm
5 Skipped Deployments
Project Deployment Actions Updated (UTC)
analytics Ignored Ignored Preview Mar 6, 2026 5:43pm
chat-template Ignored Ignored Preview Mar 6, 2026 5:43pm
tldraw-docs Ignored Ignored Preview Mar 6, 2026 5:43pm
tldraw-shader Ignored Ignored Preview Mar 6, 2026 5:43pm
workflow-template Ignored Ignored Preview Mar 6, 2026 5:43pm

Request Review

@huppy-bot huppy-bot bot added the improvement Product improvement label Mar 5, 2026
@mimecuvalo mimecuvalo added the dotcom-preview-please ⚙️ Deploys a preview of tldraw.com for this PR label Mar 5, 2026
…latforms

The previous 72 DPI baseline was only correct for macOS. Standard 96 DPI
PNGs (common on Windows and from tools like Photoshop/GIMP) got a
pixelRatio of ~1.333 instead of 1, making them appear ~25% smaller.

Now tries both baselines (96 for Windows/web, 72 for macOS) and picks
whichever yields a clean integer ratio > 1. This correctly handles:
- 96 DPI (Windows 1x) → ratio 1
- 72 DPI (macOS 1x) → ratio 1
- 144 DPI (macOS 2x Retina) → ratio 2
- 192 DPI (Windows 2x HiDPI) → ratio 2

Made-with: Cursor
@mimecuvalo mimecuvalo changed the title feat(images): store pixelRatio on image assets for correct CDN resolution fix(images): use dual DPI baselines for correct pixelRatio on all platforms Mar 5, 2026
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

@mimecuvalo mimecuvalo requested a review from ds300 March 5, 2026 16:23
Copy link
Copy Markdown
Collaborator

@ds300 ds300 left a comment

Choose a reason for hiding this comment

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

nice

Copy link
Copy Markdown
Collaborator

@steveruizok steveruizok left a comment

Choose a reason for hiding this comment

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

looks good!

@mimecuvalo mimecuvalo enabled auto-merge March 6, 2026 17:41
@mimecuvalo mimecuvalo added this pull request to the merge queue Mar 6, 2026
Merged via the queue into main with commit f69220b Mar 6, 2026
19 checks passed
@mimecuvalo mimecuvalo deleted the mime/asset-resolution-zoom branch March 6, 2026 17:52
github-merge-queue bot pushed a commit that referenced this pull request Mar 18, 2026
In order to publish the v4.5.0 release notes and record the v4.4.1
patch, this PR archives release notes and resets `next.mdx` for the next
cycle.

**v4.5.0.mdx** (new file):
- Archived from `next.mdx` with full frontmatter, keywords, and GitHub
release link
- Featured sections: click-through on transparent image pixels (#7942),
breaking `EmbedShapeUtil.configure()` change (#8034)
- API changes: `Editor.getResizeScaleFactor()` (#8042),
`TLImageAsset.pixelRatio` (#8163), `sanitizeSvg` (#7896),
`experimental__onDropOnCanvas` (#7911), enum-to-const refactoring
(#8084)
- 6 improvements and 20 bug fixes from production

**v4.4.0.mdx:**
- Add v4.4.1 patch release section with tooltip positioning fix (#8171)
- Add v4.4.1 to keywords

**next.mdx:**
- Reset with `last_version: v4.5.0` and empty content

### Change type

- [x] `other`

### Code changes

| Section       | LOC change    |
| ------------- | ------------- |
| Documentation | +128 / -107   |
huppy-bot bot pushed a commit that referenced this pull request Mar 18, 2026
In order to publish the v4.5.0 release notes and record the v4.4.1
patch, this PR archives release notes and resets `next.mdx` for the next
cycle.

**v4.5.0.mdx** (new file):
- Archived from `next.mdx` with full frontmatter, keywords, and GitHub
release link
- Featured sections: click-through on transparent image pixels (#7942),
breaking `EmbedShapeUtil.configure()` change (#8034)
- API changes: `Editor.getResizeScaleFactor()` (#8042),
`TLImageAsset.pixelRatio` (#8163), `sanitizeSvg` (#7896),
`experimental__onDropOnCanvas` (#7911), enum-to-const refactoring
(#8084)
- 6 improvements and 20 bug fixes from production

**v4.4.0.mdx:**
- Add v4.4.1 patch release section with tooltip positioning fix (#8171)
- Add v4.4.1 to keywords

**next.mdx:**
- Reset with `last_version: v4.5.0` and empty content

### Change type

- [x] `other`

### Code changes

| Section       | LOC change    |
| ------------- | ------------- |
| Documentation | +128 / -107   |
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dotcom-preview-please ⚙️ Deploys a preview of tldraw.com for this PR improvement Product improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants