@@ -8,6 +8,7 @@ import { readFileSync } from 'fs';
88import { FINALIZER , introducer , sixelEncode } from 'sixel' ;
99import { ITestContext , createTestContext , openTerminal , pollFor , timeout } from '../../../test/playwright/TestUtils' ;
1010import { deepStrictEqual , ok , strictEqual } from 'assert' ;
11+ import QoiEncoder from 'xterm-wasm-parts/lib/qoi/QoiEncoder.wasm' ;
1112
1213/**
1314 * Plugin ctor options.
@@ -74,6 +75,10 @@ const TESTDATA_IIP: [string, [number, number]][] = [
7475 [ readFileSync ( './addons/addon-image/fixture/iip/w3c_jpg.iip' , { encoding : 'utf-8' } ) , [ 72 , 48 ] ] ,
7576 [ readFileSync ( './addons/addon-image/fixture/iip/w3c_png.iip' , { encoding : 'utf-8' } ) , [ 72 , 48 ] ]
7677] ;
78+ const PALETTE_PNG_BASE64 = readFileSync ( './addons/addon-image/fixture/palette.png' ) . toString ( 'base64' ) ;
79+ const qoiEnc = new QoiEncoder ( 1024 * 1024 ) ;
80+ const qoiData = qoiEnc . encode ( TESTDATA . bytes , 640 , 80 ) ;
81+ const PALETTE_QOI_BASE64 = Buffer . from ( qoiData ) . toString ( 'base64' ) ;
7782
7883let ctx : ITestContext ;
7984test . beforeAll ( async ( { browser } ) => {
@@ -85,11 +90,6 @@ test.afterAll(async () => await ctx.page.close());
8590test . describe ( 'ImageAddon' , ( ) => {
8691
8792 test . beforeEach ( async ( { } , testInfo ) => {
88- // DEBT: This test never worked on webkit
89- if ( ctx . browser . browserType ( ) . name ( ) === 'webkit' ) {
90- testInfo . skip ( ) ;
91- return ;
92- }
9393 await ctx . page . evaluate ( `
9494 window.term.reset()
9595 window.imageAddon?.dispose();
@@ -312,6 +312,68 @@ test.describe('ImageAddon', () => {
312312 deepStrictEqual ( await getOrigSize ( 1 ) , TESTDATA_IIP [ 4 ] [ 1 ] ) ;
313313 } ) ;
314314 } ) ;
315+
316+ test . describe ( 'IIP - QOI support' , ( ) => {
317+ test ( 'palette should yield same bytes from PNG and QOI' , async ( ) => {
318+ await ctx . proxy . write ( `\x1b]1337;File=inline=1;size=525:${ PALETTE_PNG_BASE64 } \x07` ) ;
319+ deepStrictEqual ( await getOrigSize ( 1 ) , [ 640 , 80 ] ) ;
320+ await ctx . proxy . write ( `\x1b[10H\x1b]1337;File=inline=1;size=${ qoiData . length } :${ PALETTE_QOI_BASE64 } \x07` ) ;
321+ deepStrictEqual ( await getOrigSize ( 2 ) , [ 640 , 80 ] ) ;
322+ const pngScrape = await getImageAtBufferCell ( 0 , 0 ) ;
323+ const qoiScrape = await getImageAtBufferCell ( 0 , 11 ) ;
324+ deepStrictEqual ( qoiScrape , pngScrape ) ;
325+ } ) ;
326+ } ) ;
327+
328+ test . describe ( 'IIP - resizing' , ( ) => {
329+ /**
330+ * The correct resize behavior is wonky and needs to be tested against iTerm2,
331+ * especially for missing params to derive correct default behavior.
332+ * We document the current behavior here w'o claiming to be fully in line with iTerm2.
333+ * ref: https://iterm2.com/documentation-images.html
334+ * imgcat: https://iterm2.com/utilities/imgcat
335+ *
336+ * NOTE: QOI has a slightly different parse path, thus we test resizing explicitly
337+ */
338+ const images = [
339+ [ 'palette.png' , 525 , PALETTE_PNG_BASE64 ] ,
340+ [ 'palette.qoi' , qoiData . length , PALETTE_QOI_BASE64 ]
341+ ] ;
342+ for ( const [ name , size , payload ] of images ) {
343+ test ( name + ': N --> width=20 height=5 preserveAspectRatio=0' , async ( ) => {
344+ // cell based resize
345+ const header = 'width=20;height=5;preserveAspectRatio=0' ;
346+ await ctx . proxy . write ( `\x1b]1337;File=inline=1;size=${ size } ;${ header } :${ payload } \x07` ) ;
347+ const dim = await getDimensions ( ) ;
348+ deepStrictEqual ( await getOrigSize ( 1 ) , [ dim . cellWidth * 20 , dim . cellHeight * 5 ] ) ;
349+ } ) ;
350+ test ( name + ': Npx --> width=320px height=160px preserveAspectRatio=0' , async ( ) => {
351+ // pixel based resize
352+ const header = 'width=320px;height=160px;preserveAspectRatio=0' ;
353+ await ctx . proxy . write ( `\x1b]1337;File=inline=1;size=${ size } ;${ header } :${ payload } \x07` ) ;
354+ deepStrictEqual ( await getOrigSize ( 1 ) , [ 320 , 160 ] ) ;
355+ } ) ;
356+ test ( name + ': N% --> width=50% height=30% preserveAspectRatio=0' , async ( ) => {
357+ // % of viewport resize
358+ const header = 'width=50%;height=30%;preserveAspectRatio=0' ;
359+ await ctx . proxy . write ( `\x1b]1337;File=inline=1;size=${ size } ;${ header } :${ payload } \x07` ) ;
360+ const dim = await getDimensions ( ) ;
361+ deepStrictEqual ( await getOrigSize ( 1 ) , [ Math . floor ( dim . width * 0.5 ) , Math . floor ( dim . height * 0.3 ) ] ) ;
362+ } ) ;
363+ test ( name + ': ommitted dimension assumes preserveAspectRatio=1' , async ( ) => {
364+ // width provided in percent
365+ const header = 'width=50%' ;
366+ await ctx . proxy . write ( `\x1b]1337;File=inline=1;size=${ size } ;${ header } :${ payload } \x07` ) ;
367+ const dim = await getDimensions ( ) ;
368+ const width = Math . floor ( dim . width * 0.5 ) ;
369+ deepStrictEqual ( await getOrigSize ( 1 ) , [ width , Math . floor ( width * 80 / 640 ) ] ) ;
370+ // height provided in pixel
371+ const header2 = 'height=200px' ;
372+ await ctx . proxy . write ( `\x1b]1337;File=inline=1;size=${ size } ;${ header2 } :${ payload } \x07` ) ;
373+ deepStrictEqual ( await getOrigSize ( 2 ) , [ Math . floor ( 200 * 640 / 80 ) , 200 ] ) ;
374+ } ) ;
375+ }
376+ } ) ;
315377} ) ;
316378
317379/**
@@ -345,3 +407,7 @@ async function getOrigSize(id: number): Promise<[number, number]> {
345407 window.imageAddon._storage._images.get(${ id } ).orig.height
346408 ]` ) ;
347409}
410+
411+ async function getImageAtBufferCell ( x : number , y : number ) : Promise < string | undefined > {
412+ return ctx . page . evaluate < any > ( `window.imageAddon.getImageAtBufferCell(${ x } , ${ y } )?.toDataURL('image/png')` ) ;
413+ }
0 commit comments