From fcb3cc1a21a5debaa35c11c732fdea56bbf7e81d Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 9 Feb 2026 11:31:20 -0800 Subject: [PATCH] add delelteImage, map kitty ID to storage IDs --- addons/addon-image/src/ImageStorage.ts | 18 ++++++++++++-- .../src/kitty/KittyGraphicsHandler.ts | 24 +++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/addons/addon-image/src/ImageStorage.ts b/addons/addon-image/src/ImageStorage.ts index aea9b5e5..03925f0b 100644 --- a/addons/addon-image/src/ImageStorage.ts +++ b/addons/addon-image/src/ImageStorage.ts @@ -235,9 +235,22 @@ export class ImageStorage implements IDisposable { } /** - * Method to add an image to the storage. + * Delete an image by its internal storage ID. + * Used by protocols that support explicit deletion (e.g. Kitty a=d). */ - public addImage(img: HTMLCanvasElement | ImageBitmap): void { + public deleteImage(id: number): void { + const spec = this._images.get(id); + if (spec) { + spec.marker?.dispose(); + this._delImg(id); + } + } + + /** + * Method to add an image to the storage. + * Returns the internal image ID assigned to the stored image. + */ + public addImage(img: HTMLCanvasElement | ImageBitmap): number { // never allow storage to exceed memory limit this._evictOldest(img.width * img.height); @@ -331,6 +344,7 @@ export class ImageStorage implements IDisposable { // finally add the image this._images.set(imageId, imgSpec); + return imageId; } diff --git a/addons/addon-image/src/kitty/KittyGraphicsHandler.ts b/addons/addon-image/src/kitty/KittyGraphicsHandler.ts index 9c2ed292..1062a3cc 100644 --- a/addons/addon-image/src/kitty/KittyGraphicsHandler.ts +++ b/addons/addon-image/src/kitty/KittyGraphicsHandler.ts @@ -64,6 +64,14 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler { private _pendingTransmissions: Map = new Map(); private _nextImageId = 1; + /** Maps Kitty protocol image ID → ImageStorage internal ID for deletion/lookup. */ + private _kittyIdToStorageId: Map = new Map(); + // TODO: Eliminate double storage — raw image data lives here (as Blob) AND rendered + // ImageBitmaps live in ImageStorage. Currently we only use ImageStorage.addImage(bitmap) + // for tiling + cursor movement + marker-based eviction. + // + + // See: https://github.com/xtermjs/xterm.js/pull/5619#issuecomment-3853678815 private _images: Map = new Map(); constructor( @@ -88,6 +96,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler { this._activeDecoder = null; } this._images.clear(); + this._kittyIdToStorageId.clear(); } public start(): void { @@ -411,8 +420,17 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler { if (id !== undefined) { this._images.delete(id); + const storageId = this._kittyIdToStorageId.get(id); + if (storageId !== undefined) { + this._storage.deleteImage(storageId); + this._kittyIdToStorageId.delete(id); + } } else { this._images.clear(); + for (const storageId of this._kittyIdToStorageId.values()) { + this._storage.deleteImage(storageId); + } + this._kittyIdToStorageId.clear(); } return true; } @@ -453,12 +471,14 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler { if (w * h > this._opts.pixelLimit) return; + let storageId: number; if (w !== bitmap.width || h !== bitmap.height) { const resized = await createImageBitmap(bitmap, { resizeWidth: w, resizeHeight: h }); - this._storage.addImage(resized); + storageId = this._storage.addImage(resized); } else { - this._storage.addImage(bitmap); + storageId = this._storage.addImage(bitmap); } + this._kittyIdToStorageId.set(image.id, storageId); // TODO: Implement cursor movement per Kitty graphics protocol spec // Per spec: "After placing an image on the screen the cursor must be moved to the