first commit
This commit is contained in:
commit
63c0901393
10 changed files with 966 additions and 0 deletions
174
lib/pack.js
Normal file
174
lib/pack.js
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
function hexToRgb(hex) {
|
||||
hex = hex.replace(/^#/, "");
|
||||
if (hex.length === 3) {
|
||||
hex = hex
|
||||
.split("")
|
||||
.map(function (x) {
|
||||
return x + x;
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
var num = parseInt(hex, 16);
|
||||
return [(num >> 16) & 255, (num >> 8) & 255, num & 255];
|
||||
}
|
||||
|
||||
function colorDistance(rgb1, rgb2) {
|
||||
return Math.sqrt(
|
||||
Math.pow(rgb1[0] - rgb2[0], 2) +
|
||||
Math.pow(rgb1[1] - rgb2[1], 2) +
|
||||
Math.pow(rgb1[2] - rgb2[2], 2),
|
||||
);
|
||||
}
|
||||
|
||||
function closestColor(targetHex, colorList) {
|
||||
var targetRgb = hexToRgb(targetHex);
|
||||
var minDist = Infinity;
|
||||
var closest = null;
|
||||
for (var i = 0; i < colorList.length; i++) {
|
||||
var rgb = hexToRgb(colorList[i]);
|
||||
var dist = colorDistance(targetRgb, rgb);
|
||||
if (dist < minDist) {
|
||||
minDist = dist;
|
||||
closest = colorList[i];
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
function middleColorsAndApproximations(colorList, count) {
|
||||
if (!Array.isArray(colorList) || colorList.length === 0 || count < 1) {
|
||||
return {
|
||||
middleColors: [],
|
||||
approximations: {},
|
||||
};
|
||||
}
|
||||
|
||||
var rgbs = colorList.map(hexToRgb);
|
||||
|
||||
var avg = [0, 0, 0];
|
||||
for (var i = 0; i < rgbs.length; i++) {
|
||||
avg[0] += rgbs[i][0];
|
||||
avg[1] += rgbs[i][1];
|
||||
avg[2] += rgbs[i][2];
|
||||
}
|
||||
avg[0] = Math.round(avg[0] / rgbs.length);
|
||||
avg[1] = Math.round(avg[1] / rgbs.length);
|
||||
avg[2] = Math.round(avg[2] / rgbs.length);
|
||||
|
||||
if (count >= colorList.length) {
|
||||
return {
|
||||
middleColors: colorList.slice(),
|
||||
approximations: Object.fromEntries(colorList.map((c) => [c, c])),
|
||||
};
|
||||
}
|
||||
|
||||
var colorDistances = colorList.map(function (hex, idx) {
|
||||
return {
|
||||
hex: hex,
|
||||
dist: colorDistance(hexToRgb(hex), avg),
|
||||
};
|
||||
});
|
||||
|
||||
colorDistances.sort(function (a, b) {
|
||||
return a.dist - b.dist;
|
||||
});
|
||||
|
||||
var middleColors = colorDistances.slice(0, count).map(function (obj) {
|
||||
return obj.hex;
|
||||
});
|
||||
|
||||
var approximations = {};
|
||||
for (var i = 0; i < colorList.length; i++) {
|
||||
var hex = colorList[i];
|
||||
if (middleColors.indexOf(hex) !== -1) {
|
||||
approximations[hex] = hex;
|
||||
} else {
|
||||
approximations[hex] = closestColor(hex, middleColors);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
middleColors: middleColors,
|
||||
approximations: approximations,
|
||||
};
|
||||
}
|
||||
|
||||
import * as fs from "fs/promises";
|
||||
import { compress } from "./tiny_lz4.js";
|
||||
|
||||
export function pack_image(blocks) {
|
||||
const colors = middleColorsAndApproximations(
|
||||
blocks.map((z) => z.color),
|
||||
300,
|
||||
);
|
||||
const header = new Uint8Array(300 * 3 + 1 + 2);
|
||||
const dv = new DataView(header.buffer);
|
||||
dv.setUint16(0, blocks.length);
|
||||
let idx = 2;
|
||||
|
||||
colors.middleColors.forEach((z, i) => {
|
||||
const [r, g, b] = hexToRgb(z);
|
||||
dv.setUint8(idx + 1, r);
|
||||
dv.setUint8(idx + 2, g);
|
||||
dv.setUint8(idx + 3, b);
|
||||
|
||||
idx += 3;
|
||||
});
|
||||
|
||||
function pack_block(text, paletteIdx) {
|
||||
let raw_bytes = [];
|
||||
|
||||
text
|
||||
.replaceAll("\n", "?")
|
||||
.split("")
|
||||
.forEach((z, i) => {
|
||||
let id = i % 8;
|
||||
if (id == 0) {
|
||||
raw_bytes.push([]);
|
||||
}
|
||||
|
||||
raw_bytes.at(-1).push(z == "▇" ? 0 : 1);
|
||||
});
|
||||
|
||||
let bytes = raw_bytes.map((z) =>
|
||||
z.reduce((acc, bit, i) => acc | (bit << (7 - i)), 0),
|
||||
);
|
||||
|
||||
const final = new Uint8Array(3 + bytes.length);
|
||||
const dv = new DataView(final.buffer);
|
||||
|
||||
dv.setUint16(0, paletteIdx);
|
||||
dv.setUint8(2, (bytes.length << 4) | ((8 - raw_bytes.at(-1).length) & 0xf));
|
||||
bytes.forEach((z, i) => {
|
||||
dv.setUint8(i + 3, z);
|
||||
});
|
||||
return final;
|
||||
}
|
||||
|
||||
const byte_blocks = [];
|
||||
|
||||
blocks.forEach((z) => {
|
||||
byte_blocks.push(
|
||||
pack_block(
|
||||
z.text,
|
||||
colors.middleColors.indexOf(colors.approximations[z.color]),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
const totalBlockBytes = byte_blocks
|
||||
.map((z) => z.length)
|
||||
.reduce((a, b) => a + b, 0);
|
||||
const final = new Uint8Array(header.length + totalBlockBytes);
|
||||
|
||||
final.set(header, 0);
|
||||
|
||||
let offset = header.length;
|
||||
byte_blocks.forEach((block) => {
|
||||
final.set(block, offset);
|
||||
offset += block.length;
|
||||
});
|
||||
|
||||
const compressed = compress(final);
|
||||
return compressed;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue