Commit f69220b
authored
fix(images): use dual DPI baselines for correct pixelRatio on all platforms (tldraw#8163)
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)
<img width="1171" height="1065" alt="Screenshot 2026-03-05 at 15 15 46"
src="https://github.com/user-attachments/assets/83ccf063-27b4-48bc-9c72-0ca021087dcb"
/>
<img width="1158" height="1070" alt="Screenshot 2026-03-05 at 16 19 34"
src="https://github.com/user-attachments/assets/715dd7d6-288f-4342-8929-173c5741808b"
/>
### Change type
- [x] `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
- [x] 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.
<!-- CURSOR_SUMMARY -->
---
> [!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.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
864fe31. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->1 parent 975096a commit f69220b
8 files changed
Lines changed: 60 additions & 16 deletions
File tree
- apps/dotcom/client/src/utils
- packages
- sync/src
- tldraw/src/lib
- tlschema
- src
- assets
- utils
- src/lib/media
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
125 | 125 | | |
126 | 126 | | |
127 | 127 | | |
| 128 | + | |
| 129 | + | |
128 | 130 | | |
129 | 131 | | |
130 | | - | |
| 132 | + | |
131 | 133 | | |
132 | 134 | | |
133 | 135 | | |
134 | | - | |
| 136 | + | |
135 | 137 | | |
136 | 138 | | |
137 | 139 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
236 | 236 | | |
237 | 237 | | |
238 | 238 | | |
| 239 | + | |
| 240 | + | |
239 | 241 | | |
240 | 242 | | |
241 | | - | |
| 243 | + | |
242 | 244 | | |
243 | 245 | | |
244 | 246 | | |
245 | | - | |
| 247 | + | |
246 | 248 | | |
247 | 249 | | |
248 | 250 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
666 | 666 | | |
667 | 667 | | |
668 | 668 | | |
| 669 | + | |
| 670 | + | |
669 | 671 | | |
670 | 672 | | |
671 | 673 | | |
| |||
678 | 680 | | |
679 | 681 | | |
680 | 682 | | |
| 683 | + | |
681 | 684 | | |
682 | 685 | | |
683 | 686 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1134 | 1134 | | |
1135 | 1135 | | |
1136 | 1136 | | |
| 1137 | + | |
1137 | 1138 | | |
1138 | 1139 | | |
1139 | 1140 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| 20 | + | |
20 | 21 | | |
21 | 22 | | |
22 | 23 | | |
| |||
60 | 61 | | |
61 | 62 | | |
62 | 63 | | |
| 64 | + | |
63 | 65 | | |
64 | 66 | | |
65 | 67 | | |
| |||
69 | 71 | | |
70 | 72 | | |
71 | 73 | | |
| 74 | + | |
72 | 75 | | |
73 | 76 | | |
74 | 77 | | |
| |||
165 | 168 | | |
166 | 169 | | |
167 | 170 | | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
168 | 180 | | |
169 | 181 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2102 | 2102 | | |
2103 | 2103 | | |
2104 | 2104 | | |
| 2105 | + | |
| 2106 | + | |
| 2107 | + | |
| 2108 | + | |
| 2109 | + | |
| 2110 | + | |
| 2111 | + | |
| 2112 | + | |
| 2113 | + | |
| 2114 | + | |
| 2115 | + | |
| 2116 | + | |
| 2117 | + | |
| 2118 | + | |
| 2119 | + | |
2105 | 2120 | | |
2106 | 2121 | | |
2107 | 2122 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
300 | 300 | | |
301 | 301 | | |
302 | 302 | | |
| 303 | + | |
303 | 304 | | |
304 | 305 | | |
305 | 306 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
309 | 309 | | |
310 | 310 | | |
311 | 311 | | |
312 | | - | |
| 312 | + | |
313 | 313 | | |
314 | 314 | | |
315 | 315 | | |
| |||
320 | 320 | | |
321 | 321 | | |
322 | 322 | | |
323 | | - | |
324 | | - | |
325 | | - | |
326 | | - | |
327 | | - | |
328 | | - | |
329 | | - | |
330 | | - | |
331 | | - | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
332 | 340 | | |
333 | 341 | | |
334 | 342 | | |
335 | 343 | | |
336 | 344 | | |
337 | 345 | | |
338 | 346 | | |
339 | | - | |
| 347 | + | |
340 | 348 | | |
341 | | - | |
| 349 | + | |
342 | 350 | | |
343 | 351 | | |
344 | 352 | | |
| |||
0 commit comments