Skip to content

Commit

Permalink
Merge pull request #20 from jerch/faster_decode
Browse files Browse the repository at this point in the history
faster decoding
  • Loading branch information
jerch authored Aug 31, 2021
2 parents 3a0d407 + 4dd5544 commit 2c74b11
Show file tree
Hide file tree
Showing 42 changed files with 14,995 additions and 664 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [12.x, 14.x, 16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm run build --if-present
- run: npm test
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
/dist/
/lib/
/lib-esm/
/node_modules/
/package-lock.json
/coverage
/.nyc_output
/benchmark
/.vscode
/wasm/decoder.wasm
/wasm/settings.json
/src/wasm.ts
/emsdk
12 changes: 11 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
testfiles
dist
*.png
*.jpg
*.sixel
node_example*
index.html
sixel_textcursor.sh
Expand All @@ -16,3 +16,13 @@ src/*.benchmark.*
src/*.test.*
img2sixel.js
.vscode
lib-esm
emsdk
bin
.github
examples
src
wasm
tsconfig.esm.json
tsconfig.json
tslint.json
318 changes: 250 additions & 68 deletions README.md

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions bin/install_emscripten.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

if [ -d emsdk ]; then
exit 0
fi

# pull emscripten on fresh checkout
echo "Fetching emscripten..."

git clone https://github.com/emscripten-core/emsdk.git
cd emsdk

# wasm module is only tested with 2.0.25, install by default
./emsdk install 2.0.25
./emsdk activate 2.0.25

cd ..
12 changes: 12 additions & 0 deletions bin/wrap_wasm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const fs = require('fs');
const LIMITS = require('../wasm/settings.json');

const file = `
export const LIMITS = {
CHUNK_SIZE: ${LIMITS.CHUNK_SIZE},
PALETTE_SIZE: ${LIMITS.PALETTE_SIZE},
MAX_WIDTH: ${LIMITS.MAX_WIDTH},
BYTES: '${fs.readFileSync('wasm/decoder.wasm').toString('base64')}'
};
`;
fs.writeFileSync('src/wasm.ts', file);
45 changes: 45 additions & 0 deletions examples/decode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const { decodeAsync, PALETTE_ANSI_256 } = require('../lib/index');
const { createCanvas, createImageData } = require('canvas');
const fs = require('fs');
const open = require('open');

/**
* Note on *_clean.six files:
*
* Normally SIXEL data is embedded in a DCS sequence like this:
* DCS P1 ; P2 ; P3 q <SIXEL DATA> ST
*
* To handle this properly we would have to include an escape
* sequence parser in the example which is beyond the scope. Thus
* the _clean.six files are stripped down to the <SIXEL DATA> part.
*/
fs.readFile('testfiles/screen_clean.six', (err, data) => {
// example with decodeAsync
// about the used options:
// - palette:
// colors to start with (some older sixel images dont define
// their colors, instead rely on a predefined default palette)
// default is 16 colors of VT340, here we set it to 256 colors of xterm
// - memoryUsage:
// hard limit the pixel memory to avoid running out of memory (default 128 MB)
// note this cannot be approximated from sixel data size without
// parsing (1 kB of sixel data can easily create >10M pixels)
decodeAsync(data, {palette: PALETTE_ANSI_256, memoryLimit: 65536 * 20})
.then(result => {
// transfer bitmap data to ImageData object
const imageData = createImageData(result.width, result.height);
new Uint32Array(imageData.data.buffer).set(result.data32);

// draw ImageData to canvas
const canvas = createCanvas(result.width, result.height);
const ctx = canvas.getContext('2d');
ctx.putImageData(imageData, 0, 0);

// write to some file and show it
const targetFile = __dirname + '/node_decode_output.png';
const out = fs.createWriteStream(targetFile);
const stream = canvas.createPNGStream();
stream.pipe(out);
out.on('finish', () => open(targetFile));
});
})
38 changes: 38 additions & 0 deletions examples/decode_encode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const { introducer, FINALIZER, sixelEncode, decode } = require('../lib/index');
const fs = require('fs');

/**
* Note on *_clean.six files:
*
* Normally SIXEL data is embedded in a DCS sequence like this:
* DCS P1 ; P2 ; P3 q <SIXEL DATA> ST
*
* To handle this properly we would have to include an escape
* sequence parser in the example which is beyond the scope. Thus
* the _clean.six files are stripped down to the <SIXEL DATA> part.
*/
fs.readFile('testfiles/biplane_clean.six', (err, data) => {

// decoding with sync version (does not work in browser main, use decodeAsync there)
const img = decode(data);

// extract colors
const palette = new Set();
for (let i = 0; i < img.data32.length; ++i) {
palette.add(img.data32[i]);
}

// encode to sixel again
const sixelData = sixelEncode(
new Uint8Array(img.data32.buffer),
img.width,
img.height,
Array.from(palette)
);

// write to sixel capable terminal
// `sixelEncode` gives us only the sixel data part,
// to get a full sequence, we need to add the introducer and the finalizer
// (never forget the finalizer or the terminal will "hang")
console.log(introducer(1) + sixelData + FINALIZER);
})
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
const { SixelDecoder, toRGBA8888 } = require('./lib/index');
const { toRGBA8888, Decoder } = require('../lib/index');
const { createCanvas, createImageData } = require('canvas');
const fs = require('fs');
const open = require('open');
const AnsiParser = require('node-ansiparser');

// create a sixel decoder instance (sync version, use DecoderAsync in browser main)
const decoder = new Decoder();

/**
* Despite the other decode examples we use the normal testfiles here
* with full DCS sequences. For parsing of the escape sequence we can use `node-anisparser`.
*
* Note that `node-ansiparser` works only with strings, thus we have to set 'utf-8' as file encoding
* (and use `SixelImage.writeString` later on). This will not work with all testfiles,
* some of them have 8bit control characters that get stripped with 'utf-8'.
* (and use `decodeString` later on). This will not work with all testfiles,
* some of them have 8bit control characters that get stripped/scrambled with 'utf-8'.
*/
fs.readFile('testfiles/boticelli.six', 'utf-8', (err, data) => {
let image = null;

// terminal object needed for the sequence parser
// we are only interested in the DCS calls, thus skip the other methods
Expand All @@ -35,15 +36,17 @@ fs.readFile('testfiles/boticelli.six', 'utf-8', (err, data) => {
// if set to 1 we should set fillColor to 0 (leave transparent)
// else set to background color from the terminal
// hint: try changing the test file or the color to see the effect of this setting
image = new SixelDecoder(params[1] === 1 ? 0 : this.backgroundColor);

// init a new image (null for `palette` means to keep the current loaded one)
decoder.init(params[1] === 1 ? 0 : this.backgroundColor, null, 256);
this.inSixel = true;
}
},

// inst_P: called for DCS payload chunks
inst_P(chunk) {
if (this.inSixel && image) {
image.decodeString(chunk);
if (this.inSixel) {
decoder.decodeString(chunk);
}
},

Expand All @@ -57,11 +60,11 @@ fs.readFile('testfiles/boticelli.six', 'utf-8', (err, data) => {
// can continue image handling:

// transfer bitmap data to ImageData object
const imageData = createImageData(image.width, image.height);
image.toPixelData(imageData.data, image.width, image.height);
const imageData = createImageData(decoder.width, decoder.height);
new Uint32Array(imageData.data.buffer).set(decoder.data32);

// draw ImageData to canvas
const canvas = createCanvas(image.width, image.height);
const canvas = createCanvas(decoder.width, decoder.height);
const ctx = canvas.getContext('2d');
ctx.putImageData(imageData, 0, 0);

Expand All @@ -71,11 +74,14 @@ fs.readFile('testfiles/boticelli.six', 'utf-8', (err, data) => {
const stream = canvas.createPNGStream();
stream.pipe(out);
out.on('finish', () => open(targetFile));

// free ressources on sixel decoder
decoder.release();
}
}
};

// create sequence parser and parse the file data
var parser = new AnsiParser(terminal);
const parser = new AnsiParser(terminal);
parser.parse(data);
})
81 changes: 81 additions & 0 deletions examples/encode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
const { decodeAsync, image2sixel } = require('../lib/index');
const { createCanvas, createImageData } = require('canvas');
const fs = require('fs');
const open = require('open');

// create some canvas
const width = 204;
const height = 202;
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);

// gradient
const gradient = ctx.createLinearGradient(0, 0, 204, 0);
gradient.addColorStop(0, 'green');
gradient.addColorStop(.5, 'cyan');
gradient.addColorStop(1, 'green');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 204, 50);

// yellow circle
ctx.strokeStyle = 'yellow';
ctx.beginPath();
ctx.arc(100, 120, 50, 0, 2 * Math.PI);
ctx.stroke();

// line at end - 1
ctx.translate(0.5,0.5);
ctx.strokeStyle = 'red';
ctx.beginPath();
ctx.moveTo(0, 200);
ctx.lineTo(204, 200);
ctx.stroke();

// some text
ctx.font = '30px Impact';
ctx.rotate(0.1);
ctx.fillStyle = 'black';
ctx.fillText('Awesome!', 50, 100);

// green underline half opaque
const text = ctx.measureText('Awesome!');
ctx.strokeStyle = 'rgba(0, 255, 0, 0.5)';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.lineTo(50, 102);
ctx.lineTo(50 + text.width, 102);
ctx.stroke();

// convert to sixel sequence
const sixelData = image2sixel(ctx.getImageData(0, 0, width, height).data, width, height, 256, 1);

// output SIXEL data to terminal (Terminal must have SIXEL enabled!)
console.log(sixelData);


/**
* For comparison we also output the image to a PNG file.
*/

// note: we strip the first 7 bytes, since they belong to the escape sequence introducer
// (should be handled by a proper sequence parser, see node_example_decode_full_sequence.js)
decodeAsync(sixelData.slice(7), {fillColor:0, memoryLimit: 65536 *20})
.then(result => {
// transfer bitmap data to ImageData object
const imageData = createImageData(result.width, result.height);
new Uint32Array(imageData.data.buffer).set(result.data32);

// draw ImageData to canvas
const canvas2 = createCanvas(result.width, result.height);
const ctx2 = canvas2.getContext('2d');
ctx2.putImageData(imageData, 0, 0);

// write to some file and show it
const targetFile = __dirname + '/node_encode_output.png';
const out = fs.createWriteStream(targetFile);
const stream = canvas2.createPNGStream();
stream.pipe(out);
out.on('finish', () => open(targetFile));
});
Loading

0 comments on commit 2c74b11

Please sign in to comment.