From d32089823117d73fd126b5907bc2d22b6d064cce Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Sat, 7 Feb 2026 23:30:06 -0800 Subject: [PATCH] offload raw image data to Blob for off-heap storage --- addons/addon-image/src/kitty/KittyGraphicsHandler.ts | 9 ++++----- addons/addon-image/src/kitty/KittyGraphicsTypes.ts | 4 ++-- addons/addon-image/test/KittyGraphics.test.ts | 6 +++++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/addons/addon-image/src/kitty/KittyGraphicsHandler.ts b/addons/addon-image/src/kitty/KittyGraphicsHandler.ts index ec305b73..923e1925 100644 --- a/addons/addon-image/src/kitty/KittyGraphicsHandler.ts +++ b/addons/addon-image/src/kitty/KittyGraphicsHandler.ts @@ -303,9 +303,9 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler { switch (action) { case KittyAction.TRANSMIT: - return this._handleTransmit(cmd, new Uint8Array(bytes), decodeError); + return this._handleTransmit(cmd, bytes, decodeError); case KittyAction.TRANSMIT_DISPLAY: - return this._handleTransmitDisplay(cmd, new Uint8Array(bytes), decodeError); + return this._handleTransmitDisplay(cmd, bytes, decodeError); case KittyAction.QUERY: return this._handleQuery(cmd, bytes, decodeError); default: @@ -329,7 +329,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler { const id = cmd.id ?? this._nextImageId++; const image: IKittyImageData = { id, - data: bytes, + data: new Blob([bytes as BlobPart]), width: cmd.width ?? 0, height: cmd.height ?? 0, format: (cmd.format ?? KittyFormat.PNG) as 24 | 32 | 100, @@ -475,8 +475,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler { * Create ImageBitmap from already-decoded image data. */ private async _createBitmap(image: IKittyImageData): Promise { - let bytes = image.data; - + let bytes: Uint8Array = new Uint8Array(await image.data.arrayBuffer()); if (image.compression === KittyCompression.ZLIB) { bytes = await this._decompressZlib(bytes); diff --git a/addons/addon-image/src/kitty/KittyGraphicsTypes.ts b/addons/addon-image/src/kitty/KittyGraphicsTypes.ts index d5f9c3f3..d4e1fe79 100644 --- a/addons/addon-image/src/kitty/KittyGraphicsTypes.ts +++ b/addons/addon-image/src/kitty/KittyGraphicsTypes.ts @@ -116,8 +116,8 @@ export interface IPendingTransmission { */ export interface IKittyImageData { id: number; - /** Decoded image bytes (already decoded from base64) */ - data: Uint8Array; + /** Decoded image data stored as Blob (off JS heap) to avoid 2GB heap limit */ + data: Blob; width: number; height: number; format: 24 | 32 | 100; diff --git a/addons/addon-image/test/KittyGraphics.test.ts b/addons/addon-image/test/KittyGraphics.test.ts index c5ef6364..7d06b99d 100644 --- a/addons/addon-image/test/KittyGraphics.test.ts +++ b/addons/addon-image/test/KittyGraphics.test.ts @@ -156,7 +156,11 @@ test.describe('Kitty Graphics Protocol', () => { await ctx.proxy.write(`\x1b_Ga=t,f=100,i=99;${part2}\x1b\\`); await timeout(100); - const storedData = await ctx.page.evaluate(`Array.from(window.imageAddon._handlers.get('kitty').images.get(99).data)`); + const storedData = await ctx.page.evaluate(async () => { + const blob = (window as any).imageAddon._handlers.get('kitty').images.get(99).data; + const buffer = await blob.arrayBuffer(); + return Array.from(new Uint8Array(buffer)); + }); deepStrictEqual(storedData, KITTY_BLACK_1X1_BYTES); });