Skip to content

Commit 0f1acc9

Browse files
committed
better docs and full sequence example
1 parent 46dbddc commit 0f1acc9

File tree

4 files changed

+122
-21
lines changed

4 files changed

+122
-21
lines changed

README.md

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,51 @@
11
## Sixel images in Javascript.
22

3-
SIXEL decoding / encoding library for node and the browser.
3+
SIXEL image decoding / encoding library for node and the browser.
4+
45
The library provides a class `SixelImage` with the following properties:
56

67
- `constructor(fillColor: RGBA8888 = DEFAULT_BACKGROUND)`
78
Creates a new SIXEL image. The optional `fillColor` (default black) is used to fill
89
"holey pixels" with a background color during pixel transfer in `toImageData`.
910

1011
- `width: number`
11-
Pixel width of the image. Updates during `write`.
12+
Pixel width of the image. Updates during `write`. Other than stated in the SIXEL specification (DEC STD 070)
13+
a width from raster attributes takes precedence, thus if a SIXEL data stream contains raster attributes
14+
with a valid horizontal extend, width will always be set to this value, even for half transmitted images.
15+
Also overlong sixel bands (a row of 6 vertical pixels) will be stripped to the raster attribute width.
16+
(band data beyond width is not accessible anymore later on). If no raster attributes were transmitted
17+
(used in earlier SIXEL variant) width will be set to the longest sixel band width found.
1218

1319
- `height: number`
14-
Pixel height of the image. Updates during `write`.
20+
Pixel height of the image. Updates during `write`. Height is either set from a valid vertical extend in
21+
raster attributes, or grows with the number of sixel bands found in the data stream (number of bands * 6).
22+
Raster attributes again have precedence over number of bands (band data beyond height is not
23+
accessible anymore later on, also see `width`).
1524

1625
- `fillColor: RGBA8888`
17-
Number respresenting the background fill color. The number depends on endianess of the architecture,
18-
create it with `toRGBA8888(r, g, b, a)`.
26+
Number respresenting the background fill color. A value of 0 will leave background pixels untouched.
27+
The number depends on endianess of the architecture, create it with `toRGBA8888(r, g, b, a)`.
1928

2029
- `write(data: UintTypedArray, start: number = 0, end: number = data.length): void`
21-
Writes SIXEL bytes to the image and updates the image data. This is done as a stream,
22-
therefore it is possible to grab partly transmitted images (see browser example).
23-
`data` can be any array like type with single bytes per index position.
30+
Decodes SIXEL bytes and updates the image data. This is done as a stream,
31+
therefore it is possible to grab partly transmitted images (see "Simulate slow chunks" in browser example).
32+
`data` can be any array like type with single byte values per index position.
33+
Note: Normally SIXEL data is embedded in a DCS escape sequence. To properly handle the full sequence with introducer
34+
and finalizer you should to use an escape sequence parser (like `node-ansiparser` or the parser found in `xterm.js`).
35+
The `write` method of `SixelImage` is only meant for the data part
36+
(also see example `node_example_decode_full_sequence.js`).
2437

2538
- `writeString(data: string, start: number = 0, end: number = data.length): void`
2639
Same as `write` but with string data instead. For better performance use `write`.
2740

2841
- `toImageData(target: Uint8ClampedArray, width: number, height: number, dx: number = 0, dy: number = 0, sx: number = 0, sy: number = 0, swidth: number = this.width, sheight: number = this.height, fillColor: RGBA8888 = this.fillColor): Uint8ClampedArray`
29-
Write pixel data to pixel array `target`. A pixel array can be obtained from `ImageData.data`, e.g. from a canvas.
42+
Writes pixel data to pixel array `target`. A pixel array can be obtained from `ImageData.data`, e.g. from a canvas.
3043
`width` and `height` must contain the full dimension of the target. Use `dx`, `dy` (offset in target) and
3144
`sx`, `sy` (offset in source) and `swidth`, `sheight` (area in source) for cropping/clipping. `fillColor` has the same
3245
meaning as in the constructor, explicit setting it to 0 will leave non encoded pixels unaltered (pixels, that were not colored in the SIXEL data). This can be used for a transparency like effect (background/previous pixel value will remain). Returns the altered `target`.
3346

3447
- `toSixelBytes(cb: (chunk: Uint8Array) => void): void`
35-
Encodes image data as chunks of SIXEL bytes. `cb` will be called multiple times the SIXEL data in `chunk` until all image data was transmitted. `chunk` is borrowed, thus the data should be copied/written right away.
48+
Encodes image data as chunks of SIXEL bytes. `cb` will be called multiple times with SIXEL data in `chunk` until all image data was transmitted. `chunk` is borrowed, thus the data should be copied/written right away.
3649
Note: The output contains only the SIXEL image data (no escape sequence introducer / finalizer).
3750

3851
- `toSixelString(): string`
@@ -45,7 +58,8 @@ The library provides a class `SixelImage` with the following properties:
4558
of `data`. Since SIXEL is a palette based image format, `palette` should contain the used colors in `data`.
4659
If no palette was given the colors will fallback to a 16 colors palette derived from VT340. This is most
4760
likely unwanted, also to avoid poor results in general use proper quantization/dithering and palette creation
48-
before creating the SIXEL image. See `node_example_encode.js` for an example usage in conjunction with `rgbquant`.
61+
before creating the SIXEL image. See `node_example_encode.js` for an example usage in conjunction with `rgbquant`.
62+
For transparency only an alpha value of 0 will be respected as fully transparent, other alpha values are set to fully opaque (255). Transparent pixels will be colored by the terminal later on depending on the `backgroundSelect` setting of the introducer.
4963
Note: Some terminals have strict palette limitations (e.g. xterm is bound to 16 colors only in VT340 mode).
5064

5165
- `static introducer(backgroundSelect: number = 0): string`
@@ -54,30 +68,30 @@ The library provides a class `SixelImage` with the following properties:
5468
`backgroundSelect` is a hint for the terminal how to deal with uncolored pixels:
5569

5670
- 0 - device default action (most terminals will apply background color)
57-
- 1 - no action (no change to zero bit value grid positions)
58-
- 2 - set to background color - zero bit value grid positions are set to background color (device dependent).
71+
- 1 - no action (previous pixel value at output position should remain)
72+
- 2 - set to background color (device dependent)
5973

6074
- `static finalizer(): string`
61-
Finalizes the SIXEL escape sequence. Write this, when the SIXEL data stream has ended to restore
62-
the terminal to normal operation. Note that a SIXEL escape sequences changes the operation mode
63-
of a terminal, forgetting the finalizer might leave the terminal in an unrecoverable state.
75+
Finalizes the SIXEL escape sequence. Write this, when the SIXEL data stream has ended.
76+
Note that a SIXEL escape sequences changes the operation mode of a terminal,
77+
forgetting the finalizer might leave the terminal in an unrecoverable state.
6478

65-
Furthermore the library exposes two convenient functions to convert native RGBA colors:
79+
Furthermore the library exposes two convenient functions to convert to and from native RGBA colors:
6680

6781
- `function toRGBA8888(r: number, g: number, b: number, a: number = 255): RGBA8888`
68-
Converts the RGB channels values to the native color type `RGBA8888`.
82+
Converts the RGBA channel values to the native color type `RGBA8888`.
6983

7084
- `function fromRGBA8888(color: RGBA8888): number[]`
7185
Converts the native color to an array of [r, g, b, a].
7286

7387
### Installation
7488
Install the library with `npm install sixel`.
7589

76-
### Demos and examples
90+
### Examples and browser demo
7791
See the example files for decoding/encoding in nodejs. Note that the examples and the browser demo are not part
7892
of the npm package, clone the repo and run `npm install` if you want to see them in action.
7993

80-
Decoding can also be tested in the browser with `npm start` under `localhost:8080`.
94+
Decoding can also be tested in the browser after `npm start` under `localhost:8080`.
8195

8296
### Status
8397
Currently alpha, tests are yet to come.
@@ -86,3 +100,7 @@ Currently alpha, tests are yet to come.
86100
- tests
87101
- optimizations
88102
- more docs
103+
104+
### References
105+
106+
While being quite common in the DEC ecosystem in the 80s (even used for printer protocols), SIXEL references are very limited these days. The closest to a specification we have can be found in the Video Systems Reference Manual ([DEC STD 070](http://www.bitsavers.org/pdf/dec/standards/EL-SM070-00_DEC_STD_070_Video_Systems_Reference_Manual_Dec91.pdf#page=908), p. 908-933). Also see [Sixel Graphics](https://www.vt100.net/docs/vt3xx-gp/chapter14.html) on vt100.net, which gives a quick overview. For implementation this old usenet article "[All About SIXELs](https://www.digiater.nl/openvms/decus/vax90b1/krypton-nasa/all-about-sixels.text)" was very helpful.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const { SixelImage, toRGBA8888 } = require('./lib/index');
2+
const { createCanvas, createImageData } = require('canvas');
3+
const fs = require('fs');
4+
const open = require('open');
5+
const AnsiParser = require('node-ansiparser');
6+
7+
8+
/**
9+
* Despite the other decode examples we use the normal testfiles here
10+
* with full DCS sequences. For parsing of the escape sequence we can use `node-anisparser`.
11+
*
12+
* Note that `node-ansiparser` works only with strings, thus we have to set 'utf-8' as file encoding
13+
* (and use `SixelImage.writeString` later on). This will not work with all testfiles,
14+
* some of them have 8bit control characters that get stripped with 'utf-8'.
15+
*/
16+
fs.readFile('testfiles/boticelli.six', 'utf-8', (err, data) => {
17+
let image = null;
18+
19+
// terminal object needed for the sequence parser
20+
// we are only interested in the DCS calls, thus skip the other methods
21+
const terminal = {
22+
// some background color
23+
backgroundColor: toRGBA8888(255, 255, 0, 255),
24+
25+
// some state to determine whether DCS payload should go to sixel image
26+
// the state is needed since the input might contain other non DCS sequences
27+
// that should not be treated as SIXEL data
28+
inSixel: false,
29+
30+
// inst_H: called whan a DCS sequence starts
31+
inst_H(collected, params, flag) {
32+
// q means incoming SIXEL DCS, thus create new SIXEL image
33+
if (flag === 'q') {
34+
// also eval params of the sequence, P2 is backgroundSelect
35+
// if set to 1 we should set fillColor to 0 (leave transparent)
36+
// else set to background color from the terminal
37+
// hint: try changing the test file or the color to see the effect of this setting
38+
image = new SixelImage(params[1] === 1 ? 0 : this.backgroundColor);
39+
this.inSixel = true;
40+
}
41+
},
42+
43+
// inst_P: called for DCS payload chunks
44+
inst_P(chunk) {
45+
if (this.inSixel && image) {
46+
image.writeString(chunk);
47+
}
48+
},
49+
50+
// inst_U: called when DCS sequence finishs
51+
inst_U() {
52+
if (this.inSixel) {
53+
this.inSixel = false;
54+
55+
// we were actually in a SIXEL DCS sequence
56+
// and have now all image data received, thus
57+
// can continue image handling:
58+
59+
// transfer bitmap data to ImageData object
60+
const imageData = createImageData(image.width, image.height);
61+
image.toImageData(imageData.data, image.width, image.height);
62+
63+
// draw ImageData to canvas
64+
const canvas = createCanvas(image.width, image.height);
65+
const ctx = canvas.getContext('2d');
66+
ctx.putImageData(imageData, 0, 0);
67+
68+
// write to some file and show it
69+
const targetFile = __dirname + '/node_decode_full_sequence_output.png';
70+
const out = fs.createWriteStream(targetFile);
71+
const stream = canvas.createPNGStream();
72+
stream.pipe(out);
73+
out.on('finish', () => open(targetFile));
74+
}
75+
}
76+
};
77+
78+
// create sequence parser and parse the file data
79+
var parser = new AnsiParser(terminal);
80+
parser.parse(data);
81+
})

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sixel",
3-
"version": "0.0.6",
3+
"version": "0.0.7",
44
"description": "Sixel image format for node and browser.",
55
"main": "./lib/index.js",
66
"scripts": {
@@ -28,6 +28,7 @@
2828
"chai": "^4.2.0",
2929
"http-server": "^0.11.1",
3030
"mocha": "^5.2.0",
31+
"node-ansiparser": "^2.2.0",
3132
"open": "^6.4.0",
3233
"rgbquant": "^1.1.2",
3334
"ts-loader": "^5.3.3",

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ class SixelBand {
334334
const end = this.width * 6;
335335
let lastCode = -1;
336336
let accu = 1;
337+
// FIXME: avoid running beyond data.length (may happen if a single band is shorter than image width)
337338
for (let cursor = 0; cursor < end; cursor += 6) {
338339
let code = 0;
339340
for (let p = 0; p < 6; ++p) {

0 commit comments

Comments
 (0)