108 lines
2.9 KiB
JavaScript
108 lines
2.9 KiB
JavaScript
'use strict';
|
|
|
|
//const debug = require('debug')('compress');
|
|
|
|
function count_same(arr, offset) {
|
|
let same = 1;
|
|
let val = arr[offset];
|
|
|
|
for (let i = offset + 1; i < arr.length; i++) {
|
|
if (arr[i] !== val) break;
|
|
same++;
|
|
}
|
|
|
|
return same;
|
|
}
|
|
|
|
//
|
|
// Compress pixels with RLE-like algorythm (modified I3BN)
|
|
//
|
|
// 1. Require minimal repeat count (1) to enter I3BN mode
|
|
// 2. Increased 1-bit-replaced repeat limit (2 => 10)
|
|
// 3. Length of direct repetition counter reduced (8 => 6 bits).
|
|
//
|
|
// pixels - flat array of pixels (one per entry)
|
|
// options.bpp - bits per pixels
|
|
//
|
|
module.exports = function compress(bitStream, pixels, options) {
|
|
const opts = Object.assign({}, { repeat: 1 }, options);
|
|
|
|
// Minimal repetitions count to enable RLE mode.
|
|
const RLE_SKIP_COUNT = 1;
|
|
// Number of repeats, when `1` used to replace data
|
|
// If more - write as number
|
|
const RLE_BIT_COLLAPSED_COUNT = 10;
|
|
|
|
const RLE_COUNTER_BITS = 6; // (2^bits - 1) - max value
|
|
const RLE_COUNTER_MAX = (1 << RLE_COUNTER_BITS) - 1;
|
|
// Force flush if counter dencity exceeded.
|
|
const RLE_MAX_REPEATS = RLE_COUNTER_MAX + RLE_BIT_COLLAPSED_COUNT + 1;
|
|
|
|
//let bits_start_offset = bitStream.index;
|
|
|
|
let offset = 0;
|
|
|
|
while (offset < pixels.length) {
|
|
const p = pixels[offset];
|
|
|
|
let same = count_same(pixels, offset);
|
|
|
|
// Clamp value because RLE counter density is limited
|
|
if (same > RLE_MAX_REPEATS + RLE_SKIP_COUNT) {
|
|
same = RLE_MAX_REPEATS + RLE_SKIP_COUNT;
|
|
}
|
|
|
|
//debug(`offset: ${offset}, count: ${same}, pixel: ${p}`);
|
|
|
|
offset += same;
|
|
|
|
// If not enough for RLE - write as is.
|
|
if (same <= RLE_SKIP_COUNT) {
|
|
for (let i = 0; i < same; i++) {
|
|
bitStream.writeBits(p, opts.bpp);
|
|
//debug(`==> ${opts.bpp} bits`);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// First, write "skipped" head as is.
|
|
for (let i = 0; i < RLE_SKIP_COUNT; i++) {
|
|
bitStream.writeBits(p, opts.bpp);
|
|
//debug(`==> ${opts.bpp} bits`);
|
|
}
|
|
|
|
same -= RLE_SKIP_COUNT;
|
|
|
|
// Not reached state to use counter => dump bit-extended
|
|
if (same <= RLE_BIT_COLLAPSED_COUNT) {
|
|
bitStream.writeBits(p, opts.bpp);
|
|
//debug(`==> ${opts.bpp} bits (val)`);
|
|
for (let i = 0; i < same; i++) {
|
|
/*eslint-disable max-depth*/
|
|
if (i < same - 1) {
|
|
bitStream.writeBits(1, 1);
|
|
//debug('==> 1 bit (rle repeat)');
|
|
} else {
|
|
bitStream.writeBits(0, 1);
|
|
//debug('==> 1 bit (rle repeat last)');
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
same -= RLE_BIT_COLLAPSED_COUNT + 1;
|
|
|
|
bitStream.writeBits(p, opts.bpp);
|
|
//debug(`==> ${opts.bpp} bits (val)`);
|
|
|
|
for (let i = 0; i < RLE_BIT_COLLAPSED_COUNT + 1; i++) {
|
|
bitStream.writeBits(1, 1);
|
|
//debug('==> 1 bit (rle repeat)');
|
|
}
|
|
bitStream.writeBits(same, RLE_COUNTER_BITS);
|
|
//debug(`==> 4 bits (rle repeat count ${same})`);
|
|
}
|
|
|
|
//debug(`output bits: ${bitStream.index - bits_start_offset}`);
|
|
};
|