first commit

This commit is contained in:
Soph :3 2025-09-27 18:20:54 +03:00
commit 63c0901393
10 changed files with 966 additions and 0 deletions

174
lib/pack.js Normal file
View 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;
}