···1212 GhosttySelection s = GHOSTTY_INIT_SIZED(GhosttySelection);
1313 return s;
1414}
1515+1616+// Helper to create a properly initialized GhosttyKittyGraphicsImageInfo (sized struct).
1717+static inline GhosttyKittyGraphicsImageInfo init_kitty_image_info() {
1818+ GhosttyKittyGraphicsImageInfo info = GHOSTTY_INIT_SIZED(GhosttyKittyGraphicsImageInfo);
1919+ return info;
2020+}
2121+2222+// Helper to create a properly initialized GhosttyKittyGraphicsPlacementInfo (sized struct).
2323+static inline GhosttyKittyGraphicsPlacementInfo init_kitty_placement_info() {
2424+ GhosttyKittyGraphicsPlacementInfo info = GHOSTTY_INIT_SIZED(GhosttyKittyGraphicsPlacementInfo);
2525+ return info;
2626+}
2727+2828+// Helper to create a properly initialized GhosttyKittyGraphicsPlacementRenderInfo (sized struct).
2929+static inline GhosttyKittyGraphicsPlacementRenderInfo init_kitty_placement_render_info() {
3030+ GhosttyKittyGraphicsPlacementRenderInfo info = GHOSTTY_INIT_SIZED(GhosttyKittyGraphicsPlacementRenderInfo);
3131+ return info;
3232+}
1533*/
1634import "C"
1735···127145 }
128146}
129147148148+// KittyGraphicsImageInfo contains all image metadata in a single struct.
149149+// This is more efficient than querying each field individually since it
150150+// requires only one cgo call.
151151+//
152152+// C: GhosttyKittyGraphicsImageInfo
153153+type KittyGraphicsImageInfo struct {
154154+ // ID is the image ID.
155155+ ID uint32
156156+157157+ // Number is the image number.
158158+ Number uint32
159159+160160+ // Width is the image width in pixels.
161161+ Width uint32
162162+163163+ // Height is the image height in pixels.
164164+ Height uint32
165165+166166+ // Format is the pixel format of the image.
167167+ Format KittyImageFormat
168168+169169+ // Compression is the compression of the image.
170170+ Compression KittyImageCompression
171171+172172+ // Data is a borrowed slice of the raw pixel data. Only valid
173173+ // until the next mutating terminal call.
174174+ Data []byte
175175+}
176176+177177+// KittyGraphicsPlacementInfo contains all placement metadata in a single
178178+// struct. This is more efficient than querying each field individually
179179+// since it requires only one cgo call.
180180+//
181181+// C: GhosttyKittyGraphicsPlacementInfo
182182+type KittyGraphicsPlacementInfo struct {
183183+ // ImageID is the image ID this placement belongs to.
184184+ ImageID uint32
185185+186186+ // PlacementID is the placement ID.
187187+ PlacementID uint32
188188+189189+ // IsVirtual indicates whether this is a virtual placement (unicode placeholder).
190190+ IsVirtual bool
191191+192192+ // XOffset is the pixel offset from the left edge of the cell.
193193+ XOffset uint32
194194+195195+ // YOffset is the pixel offset from the top edge of the cell.
196196+ YOffset uint32
197197+198198+ // SourceX is the source rectangle x origin in pixels.
199199+ SourceX uint32
200200+201201+ // SourceY is the source rectangle y origin in pixels.
202202+ SourceY uint32
203203+204204+ // SourceWidth is the source rectangle width in pixels (0 = full image width).
205205+ SourceWidth uint32
206206+207207+ // SourceHeight is the source rectangle height in pixels (0 = full image height).
208208+ SourceHeight uint32
209209+210210+ // Columns is the number of columns this placement occupies.
211211+ Columns uint32
212212+213213+ // Rows is the number of rows this placement occupies.
214214+ Rows uint32
215215+216216+ // Z is the z-index for this placement.
217217+ Z int32
218218+}
219219+220220+// KittyGraphicsPlacementRenderInfo contains all rendering geometry for a
221221+// placement in a single struct. Combines pixel size, grid size, viewport
222222+// position, and source rectangle into one cgo call.
223223+//
224224+// C: GhosttyKittyGraphicsPlacementRenderInfo
225225+type KittyGraphicsPlacementRenderInfo struct {
226226+ // PixelWidth is the rendered width in pixels.
227227+ PixelWidth uint32
228228+229229+ // PixelHeight is the rendered height in pixels.
230230+ PixelHeight uint32
231231+232232+ // GridCols is the number of grid columns the placement occupies.
233233+ GridCols uint32
234234+235235+ // GridRows is the number of grid rows the placement occupies.
236236+ GridRows uint32
237237+238238+ // ViewportCol is the viewport-relative column (may be negative
239239+ // for partially visible placements).
240240+ ViewportCol int32
241241+242242+ // ViewportRow is the viewport-relative row (may be negative
243243+ // for partially visible placements).
244244+ ViewportRow int32
245245+246246+ // ViewportVisible is false when the placement is fully off-screen
247247+ // or is a virtual placement. When false, ViewportCol and ViewportRow
248248+ // may contain meaningless values.
249249+ ViewportVisible bool
250250+251251+ // SourceX is the resolved source rectangle x origin in pixels.
252252+ SourceX uint32
253253+254254+ // SourceY is the resolved source rectangle y origin in pixels.
255255+ SourceY uint32
256256+257257+ // SourceWidth is the resolved source rectangle width in pixels.
258258+ SourceWidth uint32
259259+260260+ // SourceHeight is the resolved source rectangle height in pixels.
261261+ SourceHeight uint32
262262+}
263263+130264// PlacementIterator populates the given iterator with placement data
131265// from this storage. The iterator must have been created with
132266// NewKittyGraphicsPlacementIterator.
···224358 return 0, err
225359 }
226360 return KittyImageCompression(v), nil
361361+}
362362+363363+// Info returns all image metadata in a single call. This is more
364364+// efficient than calling ID, Number, Width, Height, Format,
365365+// Compression, and Data individually.
366366+func (img *KittyGraphicsImage) Info() (*KittyGraphicsImageInfo, error) {
367367+ ci := C.init_kitty_image_info()
368368+ if err := resultError(C.ghostty_kitty_graphics_image_get(
369369+ img.ptr,
370370+ C.GHOSTTY_KITTY_IMAGE_DATA_INFO,
371371+ unsafe.Pointer(&ci),
372372+ )); err != nil {
373373+ return nil, err
374374+ }
375375+376376+ info := &KittyGraphicsImageInfo{
377377+ ID: uint32(ci.id),
378378+ Number: uint32(ci.number),
379379+ Width: uint32(ci.width),
380380+ Height: uint32(ci.height),
381381+ Format: KittyImageFormat(ci.format),
382382+ Compression: KittyImageCompression(ci.compression),
383383+ }
384384+385385+ if ci.data_ptr != nil && ci.data_len > 0 {
386386+ info.Data = unsafe.Slice((*byte)(unsafe.Pointer(ci.data_ptr)), int(ci.data_len))
387387+ }
388388+389389+ return info, nil
227390}
228391229392// Data returns a borrowed slice of the raw pixel data. The slice is
···447610 return 0, err
448611 }
449612 return int32(v), nil
613613+}
614614+615615+// Info returns all placement metadata in a single call. This is more
616616+// efficient than calling ImageID, PlacementID, IsVirtual, XOffset,
617617+// YOffset, SourceX, SourceY, SourceWidth, SourceHeight, Columns,
618618+// Rows, and Z individually.
619619+func (it *KittyGraphicsPlacementIterator) Info() (*KittyGraphicsPlacementInfo, error) {
620620+ ci := C.init_kitty_placement_info()
621621+ if err := resultError(C.ghostty_kitty_graphics_placement_get(
622622+ it.ptr,
623623+ C.GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_INFO,
624624+ unsafe.Pointer(&ci),
625625+ )); err != nil {
626626+ return nil, err
627627+ }
628628+629629+ return &KittyGraphicsPlacementInfo{
630630+ ImageID: uint32(ci.image_id),
631631+ PlacementID: uint32(ci.placement_id),
632632+ IsVirtual: bool(ci.is_virtual),
633633+ XOffset: uint32(ci.x_offset),
634634+ YOffset: uint32(ci.y_offset),
635635+ SourceX: uint32(ci.source_x),
636636+ SourceY: uint32(ci.source_y),
637637+ SourceWidth: uint32(ci.source_width),
638638+ SourceHeight: uint32(ci.source_height),
639639+ Columns: uint32(ci.columns),
640640+ Rows: uint32(ci.rows),
641641+ Z: int32(ci.z),
642642+ }, nil
643643+}
644644+645645+// RenderInfo returns all rendering geometry for the current placement
646646+// in a single call. This combines the results of PixelSize, GridSize,
647647+// ViewportPos, and SourceRect into one cgo call.
648648+//
649649+// When ViewportVisible is false, the placement is fully off-screen or
650650+// is a virtual placement; ViewportCol and ViewportRow may contain
651651+// meaningless values in that case.
652652+func (it *KittyGraphicsPlacementIterator) RenderInfo(img *KittyGraphicsImage, t *Terminal) (*KittyGraphicsPlacementRenderInfo, error) {
653653+ ci := C.init_kitty_placement_render_info()
654654+ if err := resultError(C.ghostty_kitty_graphics_placement_render_info(
655655+ it.ptr,
656656+ img.ptr,
657657+ t.ptr,
658658+ &ci,
659659+ )); err != nil {
660660+ return nil, err
661661+ }
662662+663663+ return &KittyGraphicsPlacementRenderInfo{
664664+ PixelWidth: uint32(ci.pixel_width),
665665+ PixelHeight: uint32(ci.pixel_height),
666666+ GridCols: uint32(ci.grid_cols),
667667+ GridRows: uint32(ci.grid_rows),
668668+ ViewportCol: int32(ci.viewport_col),
669669+ ViewportRow: int32(ci.viewport_row),
670670+ ViewportVisible: bool(ci.viewport_visible),
671671+ SourceX: uint32(ci.source_x),
672672+ SourceY: uint32(ci.source_y),
673673+ SourceWidth: uint32(ci.source_width),
674674+ SourceHeight: uint32(ci.source_height),
675675+ }, nil
450676}
451677452678// Rect computes the grid rectangle occupied by the current placement.
+186
kitty_graphics_test.go
···321321 }
322322}
323323324324+func TestKittyGraphicsImageInfo(t *testing.T) {
325325+ term := newKittyTerminal(t)
326326+ defer term.Close()
327327+328328+ sendKittyImage(t, term)
329329+330330+ kg, err := term.KittyGraphics()
331331+ if err != nil {
332332+ t.Fatal(err)
333333+ }
334334+335335+ iter, err := NewKittyGraphicsPlacementIterator()
336336+ if err != nil {
337337+ t.Fatal(err)
338338+ }
339339+ defer iter.Close()
340340+341341+ if err := kg.PlacementIterator(iter); err != nil {
342342+ t.Fatal(err)
343343+ }
344344+345345+ if !iter.Next() {
346346+ t.Fatal("expected at least one placement")
347347+ }
348348+349349+ imageID, err := iter.ImageID()
350350+ if err != nil {
351351+ t.Fatal(err)
352352+ }
353353+354354+ img := kg.Image(imageID)
355355+ if img == nil {
356356+ t.Fatal("expected non-nil image")
357357+ }
358358+359359+ info, err := img.Info()
360360+ if err != nil {
361361+ t.Fatal(err)
362362+ }
363363+364364+ if info.ID != imageID {
365365+ t.Fatalf("expected image ID %d, got %d", imageID, info.ID)
366366+ }
367367+ if info.Width != 1 || info.Height != 1 {
368368+ t.Fatalf("expected 1x1 image, got %dx%d", info.Width, info.Height)
369369+ }
370370+ if info.Format != KittyImageFormatRGBA {
371371+ t.Fatalf("expected RGBA format, got %d", info.Format)
372372+ }
373373+ if info.Compression != KittyImageCompressionNone {
374374+ t.Fatalf("expected no compression, got %d", info.Compression)
375375+ }
376376+ if len(info.Data) != 4 {
377377+ t.Fatalf("expected 4 bytes of pixel data, got %d", len(info.Data))
378378+ }
379379+}
380380+381381+func TestKittyGraphicsPlacementInfo(t *testing.T) {
382382+ term := newKittyTerminal(t)
383383+ defer term.Close()
384384+385385+ sendKittyImage(t, term)
386386+387387+ kg, err := term.KittyGraphics()
388388+ if err != nil {
389389+ t.Fatal(err)
390390+ }
391391+392392+ iter, err := NewKittyGraphicsPlacementIterator()
393393+ if err != nil {
394394+ t.Fatal(err)
395395+ }
396396+ defer iter.Close()
397397+398398+ if err := kg.PlacementIterator(iter); err != nil {
399399+ t.Fatal(err)
400400+ }
401401+402402+ if !iter.Next() {
403403+ t.Fatal("expected at least one placement")
404404+ }
405405+406406+ info, err := iter.Info()
407407+ if err != nil {
408408+ t.Fatal(err)
409409+ }
410410+411411+ // Verify the info matches individual getters.
412412+ imageID, err := iter.ImageID()
413413+ if err != nil {
414414+ t.Fatal(err)
415415+ }
416416+ if info.ImageID != imageID {
417417+ t.Fatalf("expected image ID %d, got %d", imageID, info.ImageID)
418418+ }
419419+420420+ isVirtual, err := iter.IsVirtual()
421421+ if err != nil {
422422+ t.Fatal(err)
423423+ }
424424+ if info.IsVirtual != isVirtual {
425425+ t.Fatalf("expected IsVirtual=%v, got %v", isVirtual, info.IsVirtual)
426426+ }
427427+428428+ z, err := iter.Z()
429429+ if err != nil {
430430+ t.Fatal(err)
431431+ }
432432+ if info.Z != z {
433433+ t.Fatalf("expected Z=%d, got %d", z, info.Z)
434434+ }
435435+}
436436+437437+func TestKittyGraphicsPlacementRenderInfo(t *testing.T) {
438438+ term := newKittyTerminal(t)
439439+ defer term.Close()
440440+441441+ sendKittyImage(t, term)
442442+443443+ kg, err := term.KittyGraphics()
444444+ if err != nil {
445445+ t.Fatal(err)
446446+ }
447447+448448+ iter, err := NewKittyGraphicsPlacementIterator()
449449+ if err != nil {
450450+ t.Fatal(err)
451451+ }
452452+ defer iter.Close()
453453+454454+ if err := kg.PlacementIterator(iter); err != nil {
455455+ t.Fatal(err)
456456+ }
457457+458458+ if !iter.Next() {
459459+ t.Fatal("expected at least one placement")
460460+ }
461461+462462+ imageID, err := iter.ImageID()
463463+ if err != nil {
464464+ t.Fatal(err)
465465+ }
466466+ img := kg.Image(imageID)
467467+ if img == nil {
468468+ t.Fatal("expected image lookup to succeed")
469469+ }
470470+471471+ ri, err := iter.RenderInfo(img, term)
472472+ if err != nil {
473473+ t.Fatal(err)
474474+ }
475475+476476+ // Verify render info matches individual calls.
477477+ pw, ph, err := iter.PixelSize(img, term)
478478+ if err != nil {
479479+ t.Fatal(err)
480480+ }
481481+ if ri.PixelWidth != pw || ri.PixelHeight != ph {
482482+ t.Fatalf("pixel size mismatch: RenderInfo=%dx%d, PixelSize=%dx%d",
483483+ ri.PixelWidth, ri.PixelHeight, pw, ph)
484484+ }
485485+486486+ gc, gr, err := iter.GridSize(img, term)
487487+ if err != nil {
488488+ t.Fatal(err)
489489+ }
490490+ if ri.GridCols != gc || ri.GridRows != gr {
491491+ t.Fatalf("grid size mismatch: RenderInfo=%dx%d, GridSize=%dx%d",
492492+ ri.GridCols, ri.GridRows, gc, gr)
493493+ }
494494+495495+ _, _, sw, sh, err := iter.SourceRect(img)
496496+ if err != nil {
497497+ t.Fatal(err)
498498+ }
499499+ if ri.SourceWidth != sw || ri.SourceHeight != sh {
500500+ t.Fatalf("source rect size mismatch: RenderInfo=%dx%d, SourceRect=%dx%d",
501501+ ri.SourceWidth, ri.SourceHeight, sw, sh)
502502+ }
503503+504504+ // A freshly placed image should be viewport-visible.
505505+ if !ri.ViewportVisible {
506506+ t.Fatal("expected placement to be viewport-visible")
507507+ }
508508+}
509509+324510func TestKittyGraphicsPlacementLayerFilter(t *testing.T) {
325511 term := newKittyTerminal(t)
326512 defer term.Close()