Keep a natural-size Kitty image at its pixel size when the cell size changes #41

Merged
natsukium merged 1 commit from fix/natural-extent-rescale into main 2026-10-07 10:00:34 +09:00
Owner

Closes #19.

A Kitty graphics image placed without c=/r= got its cell extent once, at the cell size of the moment, and felis kept only the result. After a font zoom, a config font change or a scale-factor change, the stored box no longer matched the image, so the renderer scaled the image into it: squashed and aliased when zooming out, stretched when zooming in. kitty keeps the request and recomputes the extent on every cell-size change (grman_rescale).

  • Rescale: each placement now keeps its c=/r= request. A cell-size change re-resolves every auto axis on both screens (the alternate screen's stashed primary placements included) and re-states the placements that changed. The extent stays daemon state, because erase, delete-by-cell and the cursor move read it.
  • Idle shells: the resize ships the re-statements itself. Before, queued image events went out only with PTY output or a grid pull, so a zoom over an idle shell would not have reached the client.
  • No cell size yet: an image placed before the window reported its size still counts a pixel as a cell, so the cursor clears it. The first real size now shrinks that extent; before, a 420×130 px image kept 420×130 cells. A resize without pixel dimensions leaves extents alone, since growing one would cover text printed past it.
  • Source rectangle: the daemon sized a natural placement from the whole image while the renderer compared against the clipped x/y/w/h rectangle, so such an image never drew at native size. Both now share one clip.

Unicode-placeholder (U=1) extents needed no change: they resolve at draw time with the client's cell size, as kitty's grman_put_cell_image does.

Before / after: a 420×130 px image placed without c=/r=, shown at size_px 14, then 12, then 17 (left to right). Top row is before, bottom row is after. Before, the image follows its old 60×8-cell box. After, it stays at 420×130 px. At 12 px the following text line lands under the image, as it does in kitty: the rows shrink while the image keeps its pixel size.

before and after

Out of scope, two further differences from kitty's update_dest_rect:

  • With only one of c=/r= given, kitty derives the other from the aspect ratio. felis resolves it to that axis's natural size. (#42)
  • kitty defaults each of x/y/w/h on its own. felis ignores a partial set. (#43)
Closes #19. A Kitty graphics image placed without `c=`/`r=` got its cell extent once, at the cell size of the moment, and felis kept only the result. After a font zoom, a config font change or a scale-factor change, the stored box no longer matched the image, so the renderer scaled the image into it: squashed and aliased when zooming out, stretched when zooming in. kitty keeps the request and recomputes the extent on every cell-size change (`grman_rescale`). - **Rescale:** each placement now keeps its `c=`/`r=` request. A cell-size change re-resolves every auto axis on both screens (the alternate screen's stashed primary placements included) and re-states the placements that changed. The extent stays daemon state, because erase, delete-by-cell and the cursor move read it. - **Idle shells:** the resize ships the re-statements itself. Before, queued image events went out only with PTY output or a grid pull, so a zoom over an idle shell would not have reached the client. - **No cell size yet:** an image placed before the window reported its size still counts a pixel as a cell, so the cursor clears it. The first real size now shrinks that extent; before, a 420×130 px image kept 420×130 cells. A resize without pixel dimensions leaves extents alone, since growing one would cover text printed past it. - **Source rectangle:** the daemon sized a natural placement from the whole image while the renderer compared against the clipped `x/y/w/h` rectangle, so such an image never drew at native size. Both now share one clip. Unicode-placeholder (`U=1`) extents needed no change: they resolve at draw time with the client's cell size, as kitty's `grman_put_cell_image` does. Before / after: a 420×130 px image placed without `c=`/`r=`, shown at `size_px` 14, then 12, then 17 (left to right). Top row is before, bottom row is after. Before, the image follows its old 60×8-cell box. After, it stays at 420×130 px. At 12 px the following text line lands under the image, as it does in kitty: the rows shrink while the image keeps its pixel size. ![before and after](https://git.natsukium.com/attachments/8b5a2782-552e-4275-b31f-d4acebcd8aec) Out of scope, two further differences from kitty's `update_dest_rect`: - With only one of `c=`/`r=` given, kitty derives the other from the aspect ratio. felis resolves it to that axis's natural size. (#42) - kitty defaults each of `x/y/w/h` on its own. felis ignores a partial set. (#43)
daemon, grid: re-resolve natural image extents on a cell-size change
Some checks failed
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / detect relevant changes (pull_request) Successful in 8s
darwin / darwin docs-only no-op (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 17s
windows / windows docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 1m4s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m16s
pr / nix flake check (pull_request) Successful in 41s
windows / cargo nextest (Windows) (pull_request) Successful in 4m12s
windows / frontend smoke (Windows) (pull_request) Successful in 56s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
pr / cargo build / clippy / test / deny (pull_request) Successful in 2m35s
pr / wire schema is compatible with the base (pull_request) Successful in 2s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 27s
windows / cargo clippy (Windows cross) (pull_request) Successful in 14s
bench / Criterion regression gate (pull_request) Failing after 1m54s
33dcb9c066
A placement without c=/r= resolved its cell extent once, at the cell
size of the moment, and kept only the result. After a font zoom, a
config font change or a scale-factor change, the stored box no longer
matched ceil(px / cell), so the renderer stopped drawing the image at
its native pixels and scaled it into the stale box, aliased with no
mipmaps. kitty keeps the request and recomputes the effective extent on
every cell-size change (grman_rescale), which is what producers that
draw at native size expect.

The placement now keeps its c=/r= request, and a cell-size change
re-resolves every auto axis on both screens and re-states the changed
placements. The extent stays daemon state because erase, delete-by-cell
and the cursor move all read it; a mirror window with a different cell
size scales the image into the same cells, which keeps it aligned with
the text it covers. The resize flushes the re-statements itself: queued
image events otherwise ship only from PTY output or a grid pull, and an
idle shell produces neither.

An unknown cell size still counts as 1 px, so an image placed before the
first client size keeps clearing the cursor past it; the first real size
now shrinks that extent instead of leaving a 420x130 px image at 420x130
cells. A resize that reports no pixel size leaves extents alone, since
growing one would cover text already printed past it.

The daemon also resolved from the whole image while the renderer
compared the box against the clipped source rectangle, so a placement
with x/y/w/h never drew at native size. Both now use one clip.

Unicode-placeholder extents need nothing: they resolve at draw time with
the client's cell size, as kitty's grman_put_cell_image does.

Closes #19.

Assisted-by: Claude Code
natsukium deleted branch fix/natural-extent-rescale 2026-10-07 10:00:35 +09:00
Sign in to join this conversation.
No reviewers
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
felis-terminal/felis!41
No description provided.