Skip to content

Commit 4388a4a

Browse files
authored
Merge pull request #5695 from xtermjs/anthonykim1/realSeparateImageStoragePerMech
Separate image storage per mechanism (sixel, iip)
2 parents 08c97d1 + c01e4b7 commit 4388a4a

6 files changed

Lines changed: 96 additions & 28 deletions

File tree

addons/addon-image/src/IIPHandler.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
*/
55
import { IImageAddonOptions, IOscHandler, IResetHandler, ITerminalExt } from './Types';
66
import { ImageRenderer } from './ImageRenderer';
7-
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
7+
import { IIPImageStorage } from './IIPImageStorage';
8+
import { CELL_SIZE_DEFAULT } from './ImageStorage';
89
import Base64Decoder from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm';
910
import { HeaderParser, IHeaderFields, HeaderState } from './IIPHeaderParser';
1011
import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics';
@@ -40,7 +41,7 @@ export class IIPHandler implements IOscHandler, IResetHandler {
4041
constructor(
4142
private readonly _opts: IImageAddonOptions,
4243
private readonly _renderer: ImageRenderer,
43-
private readonly _storage: ImageStorage,
44+
private readonly _storage: IIPImageStorage,
4445
private readonly _coreTerminal: ITerminalExt
4546
) {
4647
const maxEncodedBytes = Math.ceil(this._opts.iipSizeLimit * 4 / 3);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Copyright (c) 2023 The xterm.js authors. All rights reserved.
3+
* @license MIT
4+
*/
5+
6+
import { ImageStorage } from './ImageStorage';
7+
8+
/**
9+
* IIP (iTerm Image Protocol) specific image storage controller.
10+
*
11+
* Wraps the shared ImageStorage with IIP protocol semantics:
12+
* - Always uses scrolling mode (cursor advances with image)
13+
*/
14+
export class IIPImageStorage {
15+
constructor(
16+
private readonly _storage: ImageStorage
17+
) {}
18+
19+
/**
20+
* Add an IIP image to storage.
21+
* Always uses scrolling mode — cursor advances past the image.
22+
*/
23+
public addImage(img: HTMLCanvasElement | ImageBitmap): void {
24+
this._storage.addImage(img, true);
25+
}
26+
}

addons/addon-image/src/ImageAddon.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { IIPHandler } from './IIPHandler';
99
import { ImageRenderer } from './ImageRenderer';
1010
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
1111
import { SixelHandler } from './SixelHandler';
12+
import { SixelImageStorage } from './SixelImageStorage';
13+
import { IIPImageStorage } from './IIPImageStorage';
1214
import { ITerminalExt, IImageAddonOptions, IResetHandler } from './Types';
1315

1416
// default values of addon ctor options
@@ -129,7 +131,8 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
129131

130132
// SIXEL handler
131133
if (this._opts.sixelSupport) {
132-
const sixelHandler = new SixelHandler(this._opts, this._storage!, terminal);
134+
const sixelStorage = new SixelImageStorage(this._storage!, this._opts, this._renderer!, terminal);
135+
const sixelHandler = new SixelHandler(this._opts, sixelStorage, terminal);
133136
this._handlers.set('sixel', sixelHandler);
134137
this._disposeLater(
135138
terminal._core._inputHandler._parser.registerDcsHandler({ final: 'q' }, sixelHandler)
@@ -138,7 +141,8 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
138141

139142
// iTerm IIP handler
140143
if (this._opts.iipSupport) {
141-
const iipHandler = new IIPHandler(this._opts, this._renderer!, this._storage!, terminal);
144+
const iipStorage = new IIPImageStorage(this._storage!);
145+
const iipHandler = new IIPHandler(this._opts, this._renderer!, iipStorage, terminal);
142146
this._handlers.set('iip', iipHandler);
143147
this._disposeLater(
144148
terminal._core._inputHandler._parser.registerOscHandler(1337, iipHandler)

addons/addon-image/src/ImageStorage.ts

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -216,28 +216,14 @@ export class ImageStorage implements IDisposable {
216216
this._fullyCleared = false;
217217
}
218218

219-
/**
220-
* Only advance text cursor.
221-
* This is an edge case from empty sixels carrying only a height but no pixels.
222-
* Partially fixes https://github.com/jerch/xterm-addon-image/issues/37.
223-
*/
224-
public advanceCursor(height: number): void {
225-
if (this._opts.sixelScrolling) {
226-
let cellSize = this._renderer.cellSize;
227-
if (cellSize.width === -1 || cellSize.height === -1) {
228-
cellSize = CELL_SIZE_DEFAULT;
229-
}
230-
const rows = Math.ceil(height / cellSize.height);
231-
for (let i = 1; i < rows; ++i) {
232-
this._terminal._core._inputHandler.lineFeed();
233-
}
234-
}
235-
}
236-
237219
/**
238220
* Method to add an image to the storage.
221+
* @param img - The image to add (canvas or bitmap).
222+
* @param scrolling - When true, cursor advances with the image (lineFeed per row).
223+
* When false, image is placed at (0,0) and cursor is restored (DECSET 80 / sixel origin mode).
224+
* @returns The internal image ID assigned to the stored image.
239225
*/
240-
public addImage(img: HTMLCanvasElement | ImageBitmap): void {
226+
public addImage(img: HTMLCanvasElement | ImageBitmap, scrolling: boolean): number {
241227
// never allow storage to exceed memory limit
242228
this._evictOldest(img.width * img.height);
243229

@@ -259,7 +245,7 @@ export class ImageStorage implements IDisposable {
259245
let offset = originX;
260246
let tileCount = 0;
261247

262-
if (!this._opts.sixelScrolling) {
248+
if (!scrolling) {
263249
buffer.x = 0;
264250
buffer.y = 0;
265251
offset = 0;
@@ -273,7 +259,7 @@ export class ImageStorage implements IDisposable {
273259
this._writeToCell(line as IBufferLineExt, offset + col, imageId, row * cols + col);
274260
tileCount++;
275261
}
276-
if (this._opts.sixelScrolling) {
262+
if (scrolling) {
277263
if (row < rows - 1) this._terminal._core._inputHandler.lineFeed();
278264
} else {
279265
if (++buffer.y >= termRows) break;
@@ -283,7 +269,7 @@ export class ImageStorage implements IDisposable {
283269
this._terminal._core._inputHandler._dirtyRowTracker.markDirty(buffer.y);
284270

285271
// cursor positioning modes
286-
if (this._opts.sixelScrolling) {
272+
if (scrolling) {
287273
buffer.x = offset;
288274
} else {
289275
buffer.x = originX;
@@ -331,6 +317,7 @@ export class ImageStorage implements IDisposable {
331317

332318
// finally add the image
333319
this._images.set(imageId, imgSpec);
320+
return imageId;
334321
}
335322

336323

addons/addon-image/src/SixelHandler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @license MIT
44
*/
55

6-
import { ImageStorage } from './ImageStorage';
6+
import { SixelImageStorage } from './SixelImageStorage';
77
import { IDcsHandler, IParams, IImageAddonOptions, ITerminalExt, AttributeData, IResetHandler, ReadonlyColorSet } from './Types';
88
import { toRGBA8888, BIG_ENDIAN, PALETTE_ANSI_256, PALETTE_VT340_COLOR } from 'sixel/lib/Colors';
99
import { RGBA8888 } from 'sixel/lib/Types';
@@ -26,7 +26,7 @@ export class SixelHandler implements IDcsHandler, IResetHandler {
2626

2727
constructor(
2828
private readonly _opts: IImageAddonOptions,
29-
private readonly _storage: ImageStorage,
29+
private readonly _storage: SixelImageStorage,
3030
private readonly _coreTerminal: ITerminalExt
3131
) {
3232
DecoderAsync({
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Copyright (c) 2020 The xterm.js authors. All rights reserved.
3+
* @license MIT
4+
*/
5+
6+
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
7+
import { IImageAddonOptions, ITerminalExt } from './Types';
8+
import { ImageRenderer } from './ImageRenderer';
9+
10+
/**
11+
* Sixel-specific image storage controller.
12+
*
13+
* Wraps the shared ImageStorage with sixel protocol semantics:
14+
* - Cursor behavior governed by DECSET 80 (sixelScrolling option)
15+
* - advanceCursor for empty sixels carrying only height
16+
*/
17+
export class SixelImageStorage {
18+
constructor(
19+
private readonly _storage: ImageStorage,
20+
private readonly _opts: IImageAddonOptions,
21+
private readonly _renderer: ImageRenderer,
22+
private readonly _terminal: ITerminalExt
23+
) {}
24+
25+
/**
26+
* Add a sixel image to storage.
27+
* Cursor behavior depends on the sixelScrolling option (DECSET 80).
28+
*/
29+
public addImage(img: HTMLCanvasElement | ImageBitmap): void {
30+
this._storage.addImage(img, this._opts.sixelScrolling);
31+
}
32+
33+
/**
34+
* Only advance text cursor.
35+
* This is an edge case from empty sixels carrying only a height but no pixels.
36+
* Partially fixes https://github.com/jerch/xterm-addon-image/issues/37.
37+
*/
38+
public advanceCursor(height: number): void {
39+
if (this._opts.sixelScrolling) {
40+
let cellSize = this._renderer.cellSize;
41+
if (cellSize.width === -1 || cellSize.height === -1) {
42+
cellSize = CELL_SIZE_DEFAULT;
43+
}
44+
const rows = Math.ceil(height / cellSize.height);
45+
for (let i = 1; i < rows; ++i) {
46+
this._terminal._core._inputHandler.lineFeed();
47+
}
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)