Real-image posters: match the box to the poster's shape and crop the remainder

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 23:13:47 +02:00
co-authored by Claude Sonnet 5.5
parent b3275f6a81
commit dd6f862d61
5 changed files with 153 additions and 27 deletions
+28 -9
View File
@@ -30,12 +30,19 @@ const kittyChunk = 4096 // bytes of base64 per escape sequence, the protocol max
type kittyGraphics struct {
write func([]byte) error // writes to the terminal, in step with the screen
nextID uint32
ids map[*image.RGBA]uint32
placed map[uint32][2]int // columns and rows each image is currently placed with
ids map[cropKey]uint32
latest map[*image.RGBA]cropKey // the crop of each image that is currently uploaded
placed map[uint32][2]int // columns and rows each image is currently placed with
}
// cropKey identifies an uploaded image: a poster and the part of it that was sent.
type cropKey struct {
img *image.RGBA
rect image.Rectangle
}
func newKittyGraphics(write func([]byte) error) *kittyGraphics {
return &kittyGraphics{write: write, nextID: 1, ids: map[*image.RGBA]uint32{}, placed: map[uint32][2]int{}}
return &kittyGraphics{write: write, nextID: 1, ids: map[cropKey]uint32{}, latest: map[*image.RGBA]cropKey{}, placed: map[uint32][2]int{}}
}
// kittyAPC builds one graphics command: ESC _ G <keys> ; <payload> ESC \.
@@ -51,14 +58,17 @@ func kittyAPC(keys string, payload []byte) []byte {
return b.Bytes()
}
// imageID returns the terminal's id for the image, uploading it on first use.
func (k *kittyGraphics) imageID(img *image.RGBA) (uint32, error) {
if id, ok := k.ids[img]; ok {
// imageID returns the terminal's id for the part rect of the image,
// uploading it on first use. When the same image is needed with another crop
// (after a resize) the earlier upload is removed.
func (k *kittyGraphics) imageID(img *image.RGBA, rect image.Rectangle) (uint32, error) {
key := cropKey{img, rect}
if id, ok := k.ids[key]; ok {
return id, nil
}
var data bytes.Buffer
enc := png.Encoder{CompressionLevel: png.BestSpeed}
if err := enc.Encode(&data, img); err != nil {
if err := enc.Encode(&data, img.SubImage(rect)); err != nil {
return 0, err
}
encoded := base64.StdEncoding.EncodeToString(data.Bytes())
@@ -81,7 +91,15 @@ func (k *kittyGraphics) imageID(img *image.RGBA) (uint32, error) {
rest = rest[n:]
}
k.nextID++
k.ids[img] = id
if old, ok := k.latest[img]; ok {
if oldID, ok := k.ids[old]; ok {
_ = k.write(kittyAPC(fmt.Sprintf("a=d,d=I,i=%d,q=2", oldID), nil))
delete(k.placed, oldID)
delete(k.ids, old)
}
}
k.ids[key] = id
k.latest[img] = key
return id, nil
}
@@ -110,7 +128,8 @@ func (k *kittyGraphics) deleteAll() {
for _, id := range k.ids {
_ = k.write(kittyAPC(fmt.Sprintf("a=d,d=I,i=%d,q=2", id), nil))
}
k.ids = map[*image.RGBA]uint32{}
k.ids = map[cropKey]uint32{}
k.latest = map[*image.RGBA]cropKey{}
k.placed = map[uint32][2]int{}
}