diff --git a/src/main/java/dev/jlibghostty/KittyGraphics.java b/src/main/java/dev/jlibghostty/KittyGraphics.java index ec99535..5861d0d 100644 --- a/src/main/java/dev/jlibghostty/KittyGraphics.java +++ b/src/main/java/dev/jlibghostty/KittyGraphics.java @@ -53,7 +53,7 @@ public final class KittyGraphics { iterator.getU32(GhosttyLibrary.KITTY_GRAPHICS_PLACEMENT_DATA_COLUMNS), iterator.getU32(GhosttyLibrary.KITTY_GRAPHICS_PLACEMENT_DATA_ROWS), iterator.getI32(GhosttyLibrary.KITTY_GRAPHICS_PLACEMENT_DATA_Z), - image.map(KittyImage::snapshot), + image, Optional.ofNullable(renderInfo) )); } diff --git a/src/main/java/dev/jlibghostty/KittyImage.java b/src/main/java/dev/jlibghostty/KittyImage.java index da2192a..c3677ff 100644 --- a/src/main/java/dev/jlibghostty/KittyImage.java +++ b/src/main/java/dev/jlibghostty/KittyImage.java @@ -13,16 +13,46 @@ public final class KittyImage { this.handle = handle; } + // Cheap metadata accessors: a single native field read each, no pixel-buffer copy. Use these + // to build cache keys; only call data() once you've decided you actually need the bytes. + + public long id() { + return library.kittyImageGetU32(handle, GhosttyLibrary.KITTY_IMAGE_DATA_ID); + } + + public long number() { + return library.kittyImageGetU32(handle, GhosttyLibrary.KITTY_IMAGE_DATA_NUMBER); + } + + public long width() { + return library.kittyImageGetU32(handle, GhosttyLibrary.KITTY_IMAGE_DATA_WIDTH); + } + + public long height() { + return library.kittyImageGetU32(handle, GhosttyLibrary.KITTY_IMAGE_DATA_HEIGHT); + } + + public KittyImageFormat format() { + return KittyImageFormat.fromNative(library.kittyImageGetI32(handle, GhosttyLibrary.KITTY_IMAGE_DATA_FORMAT)); + } + + public KittyImageCompression compression() { + return KittyImageCompression.fromNative(library.kittyImageGetI32(handle, GhosttyLibrary.KITTY_IMAGE_DATA_COMPRESSION)); + } + + // Byte length of the pixel buffer, read without copying it. + public long dataLength() { + return library.kittyImageDataLength(handle); + } + + // Copies the pixel buffer out of native memory. Expensive for large images; call only when + // the decoded image isn't already cached. + public byte[] data() { + return library.kittyImageData(handle); + } + public KittyImageSnapshot snapshot() { - return new KittyImageSnapshot( - library.kittyImageGetU32(handle, GhosttyLibrary.KITTY_IMAGE_DATA_ID), - library.kittyImageGetU32(handle, GhosttyLibrary.KITTY_IMAGE_DATA_NUMBER), - library.kittyImageGetU32(handle, GhosttyLibrary.KITTY_IMAGE_DATA_WIDTH), - library.kittyImageGetU32(handle, GhosttyLibrary.KITTY_IMAGE_DATA_HEIGHT), - KittyImageFormat.fromNative(library.kittyImageGetI32(handle, GhosttyLibrary.KITTY_IMAGE_DATA_FORMAT)), - KittyImageCompression.fromNative(library.kittyImageGetI32(handle, GhosttyLibrary.KITTY_IMAGE_DATA_COMPRESSION)), - library.kittyImageData(handle) - ); + return new KittyImageSnapshot(id(), number(), width(), height(), format(), compression(), data()); } MemorySegment handle() { diff --git a/src/main/java/dev/jlibghostty/KittyPlacement.java b/src/main/java/dev/jlibghostty/KittyPlacement.java index 708e344..51eb94d 100644 --- a/src/main/java/dev/jlibghostty/KittyPlacement.java +++ b/src/main/java/dev/jlibghostty/KittyPlacement.java @@ -15,7 +15,7 @@ public record KittyPlacement( long columns, long rows, int z, - Optional image, + Optional image, Optional renderInfo ) { } diff --git a/src/main/java/dev/jlibghostty/internal/GhosttyLibrary.java b/src/main/java/dev/jlibghostty/internal/GhosttyLibrary.java index a2f10a9..9dbc52c 100644 --- a/src/main/java/dev/jlibghostty/internal/GhosttyLibrary.java +++ b/src/main/java/dev/jlibghostty/internal/GhosttyLibrary.java @@ -1434,6 +1434,19 @@ public final class GhosttyLibrary { } } + // Length of the image's pixel buffer without copying it out of native memory. Lets callers + // build a cache key cheaply and only pull the bytes (kittyImageData) on a cache miss. + public long kittyImageDataLength(MemorySegment image) { + try (Arena arena = Arena.ofConfined()) { + MemorySegment outLen = arena.allocate(C_SIZE_T); + int result = (int) kittyGraphicsImageGet.invoke(image, KITTY_IMAGE_DATA_DATA_LEN, outLen); + checkResult("ghostty_kitty_graphics_image_get", result); + return outLen.get(C_SIZE_T, 0); + } catch (Throwable t) { + return rethrow(t); + } + } + public MemorySegment kittyPlacementIteratorNew() { try (Arena arena = Arena.ofConfined()) { MemorySegment out = arena.allocate(C_POINTER);