Skip to content

Commit

Permalink
feat(range-coder): re-import @thi.ng/range-coder package from MB2010
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Jul 21, 2018
1 parent 8389a2c commit 76dc450
Show file tree
Hide file tree
Showing 7 changed files with 292 additions and 0 deletions.
14 changes: 14 additions & 0 deletions packages/range-coder/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
bench/*
build/*
dev/*
node_modules
src*
test*
bundle.*
tsconfig.json
webpack.config.js
*.html
*.tgz
!doc/*
!*.d.ts
!*.js
53 changes: 53 additions & 0 deletions packages/range-coder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# @thi.ng/range-coder

[![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/range-coder.svg)](https://www.npmjs.com/package/@thi.ng/range-coder)

This project is part of the
[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo.

## About

Range encoder / decoder for binary data, based on [Java implementation
by Joe Halliwell](https://www.winterwell.com/software/compressor.php).

## Installation

```bash
yarn add @thi.ng/range-coder
```

## Dependencies

- [@thi.ng/bitstream](https://github.com/thi-ng/umbrella/tree/master/packages/bitstream)

## API

```ts
import * as rc "@thi.ng/range-coder";
```
```ts
// prepare dummy data
src = new Uint8Array(1024);
src.set([1,1,1,1,1,2,2,2,2,3,3,3,4,4,5,4,4,3,3,3,2,2,2,2,1,1,1,1,1], 512);

// pack data
packed = rc.encodeBytes(src);

packed.length
// 146

packed.length/src.length
// 0.142578125

// unpack
dest = rc.decodeBytes(packed);
```
## Authors
- Karsten Schmidt
## License
© 2017 Karsten Schmidt // Apache Software License 2.0
45 changes: 45 additions & 0 deletions packages/range-coder/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "@thi.ng/range-coder",
"version": "0.0.1",
"description": "Binary data range encoder / decoder",
"main": "./index.js",
"typings": "./index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/thi-ng/umbrella.git"
},
"homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/rstream",
"author": "Karsten Schmidt <[email protected]>",
"license": "Apache-2.0",
"scripts": {
"build": "yarn clean && tsc --declaration",
"clean": "rm -rf *.js *.d.ts .nyc_output build coverage doc",
"cover": "yarn test && nyc report --reporter=lcov",
"doc": "node_modules/.bin/typedoc --mode modules --out doc src",
"pub": "yarn build && yarn publish --access public",
"test": "rm -rf build && tsc -p test && nyc mocha build/test/*.js"
},
"devDependencies": {
"@thi.ng/transducers": "1.14.1",
"@types/mocha": "^5.2.0",
"@types/node": "^10.0.6",
"mocha": "^5.1.1",
"nyc": "^11.7.1",
"typedoc": "^0.11.1",
"typescript": "^2.8.3"
},
"dependencies": {
"@thi.ng/bitstream": "0.4.14"
},
"keywords": [
"ES6",
"binary",
"entropy",
"packer",
"range encoding",
"typescript"
],
"publishConfig": {
"access": "public"
}
}
135 changes: 135 additions & 0 deletions packages/range-coder/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { BitInputStream, BitOutputStream } from "@thi.ng/bitstream";

const HIGH = 0x7fffff;
const HALF = 0x400000;
const QUARTER = 0x200000;
const THREE_QUARTER = 0x600000;
const INITIAL_READ = 23;
const FREQ = 257;

export const encodeBytes = (src: Uint8Array) => {
const freq = new Uint32Array(FREQ).fill(1);
const out = new BitOutputStream(Math.max(src.length >> 1, 1));
const len = src.length;
let total = FREQ;
let lo = 0;
let hi = HIGH;
let _lo = lo;
let _hi = hi;
let step;
let scale = 0;
let curr = 0;
let i, j;

for (i = 0; i <= len; i++) {
if (i === len) {
lo = total - 1;
hi = total;
} else {
curr = src[i];
lo = 0;
for (j = 0; j < curr; j++) {
lo += freq[j];
}
hi = lo + freq[curr];
}

step = ((_hi - _lo + 1) / total) >>> 0;
_hi = _lo + step * hi - 1;
_lo += step * lo;

while (true) {
if (_hi < HALF) {
out.writeBit(0);
_lo <<= 1;
_hi = (_hi << 1) + 1;
scale && out.write(HIGH, scale);
} else if (_lo >= HALF) {
out.writeBit(1);
_lo = (_lo - HALF) << 1;
_hi = ((_hi - HALF) << 1) + 1;
scale && out.write(0, scale);
} else {
break;
}
scale = 0;
}

while (_lo > QUARTER && _hi < THREE_QUARTER) {
scale++;
_lo = (_lo - QUARTER) << 1;
_hi = ((_hi - QUARTER) << 1) + 1;
}

freq[curr]++;
total++;
}
if (_lo < QUARTER) {
out.writeBit(0);
out.write(HIGH, scale + 1);
} else {
out.writeBit(1);
}
return out.bytes();
};

export const decodeBytes = (src: Uint8Array) => {
const freq = new Uint32Array(FREQ).fill(1);
const input = new BitInputStream(src)
const nbits = input.length
const out = []
let total = FREQ
let current = 0
let lo = 0
let hi = HIGH
let _lo = lo
let _hi = hi
let step = 0
let buf = input.read(INITIAL_READ)
let val;

const read = () => input.position < nbits ? input.readBit() : 0;

while (true) {
step = ((_hi - _lo + 1) / total) >>> 0;
val = ((buf - _lo) / step) >>> 0;
lo = 0;
for (current = 0; current < 256 && lo + freq[current] <= val; current++) {
lo += freq[current];
}
if (current === 256) break;

out.push(current);
hi = lo + freq[current];

_hi = _lo + step * hi - 1;
_lo += step * lo;

while (true) {
if (_hi < HALF) {
buf <<= 1;
_lo <<= 1;
_hi = (_hi << 1) + 1;
} else if (_lo >= HALF) {
buf = (buf - HALF) << 1;
_lo = (_lo - HALF) << 1;
_hi = ((_hi - HALF) << 1) + 1;
} else {
break;
}
buf += read();
}

while (_lo > QUARTER && _hi < THREE_QUARTER) {
_lo = (_lo - QUARTER) << 1;
_hi = ((_hi - QUARTER) << 1) + 1;
buf = (buf - QUARTER) << 1;
buf += read();
}

freq[current]++;
total++;
}

return new Uint8Array(out);
};
26 changes: 26 additions & 0 deletions packages/range-coder/test/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { repeat, repeatedly } from "@thi.ng/transducers";
import * as assert from "assert";
import { decodeBytes, encodeBytes } from "../src";

describe("range-coder", () => {

it("fixed", () => {
const src = new Uint8Array([10, 20, 30, 10, 10, 10]);
const dest = encodeBytes(src);
assert.deepEqual(dest, [10, 10, 224, 160, 49, 91, 88]);
assert.deepEqual(src, decodeBytes(dest));
});

it("fuzz", () => {
for (let i = 0; i < 10; i++) {
const src = randomArray(640, 1024);
const dest = encodeBytes(src);
console.log(`${(dest.length / src.length * 100).toFixed(2)}%`);
assert.deepEqual(src, decodeBytes(dest));
}
});
});

function randomArray(n: number, len: number) {
return new Uint8Array([...repeatedly(() => ~~(Math.random() * 256), n), ...repeat(0, len - n)]);
}
10 changes: 10 additions & 0 deletions packages/range-coder/test/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../build"
},
"include": [
"./**/*.ts",
"../src/**/*.ts"
]
}
9 changes: 9 additions & 0 deletions packages/range-coder/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "."
},
"include": [
"./src/**/*.ts",
]
}

0 comments on commit 76dc450

Please sign in to comment.