Repository navigation
Hold Sixel as the pixels each line shows (3.0.0) - #176
Conversation
A Sixel is content: it paints the cells it lands on, a later Sixel paints over it, and printing over a cell replaces it. Keeping each Sixel as a picture with one placement per row, split wherever something covered part of it, left a program that repaints small regions with thousands of fragments per screen, each pinning its whole picture and each its own draw. Each line now keeps a SixelStrip: the pixels Sixel painted on it, one cell tall. A Sixel paints into the strips of the lines it covers (unset pixels under background select 1 leave what was there), printing and erasing clear those cells, and ICH/DCH, insert mode and margin scrolls move the pixels with their cells. Clones share a strip until either side writes. The image budget counts strips and drops scrollback strips first, oldest line first, never what is on screen. iTerm2 inline images are painted the same way. Kitty is unchanged. Sixel no longer appears in Placements or Images, so this is a major version: 3.0.0. README has an upgrade section. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Perf comparison — this change, against its base3 run(s) of each side, alternating on one machine. Allocation is a count and is gated exactly. Time is a measurement, so its gate is derived from the spread this job just observed in itself rather than fixed in advance.
Each corpus is gated at assemblies measured
Perf comparison — cumulative, everything since 2.0.73 run(s) of each side, alternating on one machine. Allocation is a count and is gated exactly. Time is a measurement, so its gate is derived from the spread this job just observed in itself rather than fixed in advance.
Each corpus is gated at assemblies measured
|
The images section now gives the whole draw order for a row (backgrounds, Kitty behind, text, the Sixel strip, Kitty in front) with code, how to key uploads (TerminalImage + FrameSerial, SixelStrip + Version, and the torn read), mapping cells onto the grid after a font or DPI change, and the per-column lookups including Sixel. It also says to call NotifyTextAreaPixelsChanged after changing the cell size: without it an application sizing pictures from the cell size keeps drawing at the old one after a move to another monitor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Why
A Sixel is content: it paints the cells it lands on, a later Sixel paints over it, and printing over a cell replaces it. 2.x kept every Sixel as a picture — a
TerminalImageplus oneLinePlacementper row — and split it wherever something covered part of it. A program that repaints small regions (a cursor, a hover, a diffing framebuffer such as SixelBuffer) left a patchwork behind.Measured in Avalonia.Terminal at 307×77 running SixelBuffer's ControlCatalog in Sixel mode:
What changes
SixelStrip(new, public, read-only from outside): the Sixel pixels one line shows, one cell tall, BGRA straight alpha, withCellWidth/CellHeight,PaintedColumns,HasPixelsAt(col)and aVersionthat moves after every change.BufferLine.Sixelexposes it.PlaceImagekeeps its cursor, DECSDM, scroll and mode-8452 behaviour, but for Sixel it paints each row into the line's strip. Opaque pixels replace, unset pixels (background select 1) leave what was there, partial alpha (inline PNG) blends.SetCell,SetSingleWidthRunandFillclear the cells' pixels; a strip with nothing left is dropped.CopyCellsFrommoves pixels with their cells, soICH/DCH, insert mode and margin scrolls no longer leave Sixel behind.Clone/CopyFromshare the strip until either side writes (copy-on-write), so frame capture costs a reference.MaxImageBytescounts strips; over budget, scrollback strips go first, oldest line first; what is on screen is never taken.Terminal.SixelPlaced(internal) lets tests see the decoded picture, which the buffer no longer keeps.Breaking (3.0.0)
Sixel no longer appears in
line.Placements/line.Images;TryGetPlacementAt/TryGetImageAtfind Kitty pictures only;PlacementKind.Sixelis never created (the value stays so comparisons compile). A 2.x renderer keeps compiling and silently draws no Sixel, hence the major version. README has an "Upgrading to 3.0" section with the renderer change.Tests
2,303 pass. The Sixel placement and lifetime tests now assert pixels (a striped picture checks which band landed on which row); new tests cover ICH/DCH moving pixels, transparent pixels showing through, repainting 50 times without growth, copy-on-write, cell-size rescaling, and the budget rules.
The renderer side is in Avalonia.Terminal (separate PR), verified pixel-identical between its Skia and classic paths.
🤖 Generated with Claude Code