Skip to content

Latest commit

 

History

History

wasm

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

Sixel band decoder

Features:

  • written in C
  • allocation free (single instance static memory)
  • crafted for web assembly / emscripten
  • also embeddable in native code
  • quite fast (decoding throughput >150 MB/s)
  • small wasm binary (~11 kB)
  • small memory footprint (<<1 MB, depending on MAX_WIDTH)

Note on WASM features

Currently wasm engines differ alot in supported wasm features. To still support a wide variety of engines, this module is coded in vanilla C and only uses the bulk-memory feature (optionally).

Note on native usage

The decoder can be used from other native C/C++ projects by simply including decoder.cpp. Still for native embedding there are a few things to consider first:

  • single instance / static memory
    The parser state is a single statically allocated struct. While this is a perfect fit for isolated wasm module instances, it is prolly not what you want in a C or C++ project, unless for a simple cmdline converter tool. Without code modifications decoding is limited to one parser state / one image at a time. (This is easy fixable by introducing state indirections at the function interfaces, but beyond the scope here.)

  • code optimizations
    The decoder loop is a highly optimized byte-by-byte loop in vanilla C. While this also runs fast natively, things like SIMD extensions would give another quite remarkable boost, but do not work reliable in wasm yet.

Future optimization ideas

  • wasm-SIMD, currently engines have no to lousy support with bad performance (see dysfunctional proof of concept in decoder-simd.cpp)
  • shared memory, currently shifting too much in browsers
  • wasm-threading, still not fully landed in wasm, depends on stable shared memory interfaces

Interface

To use the decoder from other non javascript wasm environments or natively, you gonna need to hook into the following API. For reference usage and how to build a full image decoder from the sixel band handling, see the implementation for javascript under src/WasmDecoder.ts.

Important compile time settings (see build.sh to adjust):

  • CHUNK_SIZE - max amount of chunk bytes
  • PALETTE_SIZE - max colors in the palette
  • MAX_WIDTH - max band width (excess pixels to the right are truncated)

Exported symbols:

  • void* get_state_address()
    Void pointer to the static ParserState struct in WASM memory.
    Properties of interest (indexed in 32bit):
    • 1: fill color as ABGR32 (as given by init)
    • 2: width+4 in M2, else 0
    • 3: height in M2, else 0
    • 4: raster numerator (unmodified) in M2, else 0
    • 5: raster denominator (unmodified) in M2, else 0
    • 6: raster width (unmodified) in M2, else 0
    • 7: raster height (unmodified)in M2, else 0
    • 8: truncate (as given by init)
    • 9: image level (L0 - undecided, L1 - level 1, L2 - level 2)
    • 10: operation mode (M0 - undecided, M1 - level 1/2 !truncate, M2 - level 2 truncating)
    • 11: palette length
  • void* get_chunk_address()
    Void pointer to ParserState.chunk byte array (max size of CHUNK_SIZE). Used to load image data to be processed by decode.
  • void* get_p0_address()
    Void pointer to first pixel line p0 the band. The other pixel lines p1 - p5 start at get_p0_address() + (MAX_WIDTH + 4) * line_idx. Used to grab pixel data when a band was finished.
  • void* get_palette_address()
    Void pointer to ParserState.palette ABGR32 array (max size of PALETTE_SIZE). Used to read/write palette colors.
  • void init(int fill_color, unsigned int palette_limit, int truncate)
    Initialize decoder for new image. Must be called before any decoding happens.
  • void decode(int start, int end)
    Decode data loaded into ParserState.chunk[start .. end] (right exclusive).
  • int current_width()
    Return the cursor advance of the current band in M1 mode, or width in M2 mode. This is needed to properly construct the full image at the end of decoding, in case the data did not finish with LF.
  • int current_width()
    M1 mode only - return the current lowermost pixel position touched by a sixel of the current band. This is needed to properly construct the full image with proper height for the current band, in case the data did not finish with LF.

Needed callbacks:

  • int mode_parsed(int mode)
    Called once early during decoding when the parser settled the operation mode. The operation mode will be settled by the decoder on reading a valid raster attributes command or any other sixel command or sixel data bytes. Used to announce the operation mode and potential raster attributes for further preparation steps. Return 0 to continue, 1 to abort further processing.
  • int handle_band(int width)
    Called upon finishing decoding of a sixel band (on LF). width is either the raster width (M2 mode), or the max cursor advance (both clamped to maxWidth). Used to copy pixel in p0 .. p5, before continuing with the next band. Return 0 to continue, 1 to abort further processing.

Note on SIXEL handling

  • The data to be digested by this decoder should only be the "Picture Definition" part of a SIXEL escape sequence, as denoted by the spec. Any other data beyond that is likely to screw up the image creation. In particular this means, that the decoder should run behind a terminal escape sequence, that is capable of proper DEC style DCS parsing (also handling spurious ESC, SUB and C1 codes).
  • Raster or pixel ratio definitions are not dealt with beside width and height (if truncate is set). Raster attributes are exposed unmodified in 0 .. 2^31-1 (as read from data), and can be used to postprocess pixel data as needed.
  • With truncate set in init, the decoder will truncate the image to given raster dimensions. This does not apply to level 1 images without any raster attributes. While truncation to raster dimensions on decoder side is not spec-conform, it is the expected data format created by a spec-conform encoder.
  • If truncate is not set, the width will be derived from cursor advance to the right, clamped to MAX_WIDTH-4. In this mode, current_height is reported as lowermost pixel position touched by sixels. Furthermore in this mode the height is not limited by any means, thus decoding may run forever. Use some sort of accounting during handle_band to spot malformed data or excessive memory usage, especially when dealing with data streams.
  • Palette colors are applied immediately to sixels (printer mode), there is no terminal-like indexed mode. While this is in line with the spec, it does not allow to mimick the palette behavior of older terminals (e.g. palette animations are not possible).
  • The decoder unconditionally strips the 8th bit, mapping all data bytes in 7-bit space. While the spec defines this only as error recovery strategy for GR codes, the decoder also does this for C1, which might lead to sixel command interpretation from spurious C1 codes. Note that C1 never should appear in sixel data, if used behind a proper escape sequence parser.
  • Other than stated in the spec, the decoder does not error on low C0 codes, instead silently ignores them. Again a proper escape sequence parser will filter / act upon those.
  • The repeat count gets not limited/clamped to 32767. The digits are parsed in signed int32 for performance reasons, and converted to unsigned before used as repeat count. While the counter will show weird behavior above 2^31-1 (counting backwards), it should not be possible to overflow the pixel arrays with malicious data (separately tested against MAX_WIDTH before any painting).

There are probably more deviations from the SIXEL spec not listed here.

Status

Beta. The code is tested with unit/regression tests from the JS integration.