2019-04-15 13:37:21 +02:00
|
|
|
/**
|
2022-01-16 02:45:48 +01:00
|
|
|
* Identicon generator
|
|
|
|
* https://en.wikipedia.org/wiki/Identicon
|
2019-04-15 13:37:21 +02:00
|
|
|
*/
|
|
|
|
|
2023-01-13 05:40:33 +01:00
|
|
|
import type { WriteStream } from "node:fs";
|
|
|
|
import * as p from "pureimage";
|
|
|
|
import gen from "random-seed";
|
2019-04-15 13:37:21 +02:00
|
|
|
|
2022-07-09 06:22:35 +02:00
|
|
|
const size = 128; // px
|
2019-04-15 13:37:21 +02:00
|
|
|
const n = 5; // resolution
|
2023-01-13 05:40:33 +01:00
|
|
|
const margin = size / 4;
|
2019-04-15 13:37:21 +02:00
|
|
|
const colors = [
|
2023-01-13 05:40:33 +01:00
|
|
|
["#FF512F", "#DD2476"],
|
|
|
|
["#FF61D2", "#FE9090"],
|
|
|
|
["#72FFB6", "#10D164"],
|
|
|
|
["#FD8451", "#FFBD6F"],
|
|
|
|
["#305170", "#6DFC6B"],
|
|
|
|
["#00C0FF", "#4218B8"],
|
|
|
|
["#009245", "#FCEE21"],
|
|
|
|
["#0100EC", "#FB36F4"],
|
|
|
|
["#FDABDD", "#374A5A"],
|
|
|
|
["#38A2D7", "#561139"],
|
|
|
|
["#121C84", "#8278DA"],
|
|
|
|
["#5761B2", "#1FC5A8"],
|
|
|
|
["#FFDB01", "#0E197D"],
|
|
|
|
["#FF3E9D", "#0E1F40"],
|
|
|
|
["#766eff", "#00d4ff"],
|
|
|
|
["#9bff6e", "#00d4ff"],
|
|
|
|
["#ff6e94", "#00d4ff"],
|
|
|
|
["#ffa96e", "#00d4ff"],
|
|
|
|
["#ffa96e", "#ff009d"],
|
|
|
|
["#ffdd6e", "#ff009d"],
|
2019-04-15 13:37:21 +02:00
|
|
|
];
|
|
|
|
|
2023-01-13 05:40:33 +01:00
|
|
|
const actualSize = size - margin * 2;
|
2019-04-15 13:37:21 +02:00
|
|
|
const cellSize = actualSize / n;
|
|
|
|
const sideN = Math.floor(n / 2);
|
|
|
|
|
|
|
|
/**
|
2022-01-16 02:45:48 +01:00
|
|
|
* Generate buffer of an identicon by seed
|
2019-04-15 13:37:21 +02:00
|
|
|
*/
|
2022-01-16 02:45:48 +01:00
|
|
|
export function genIdenticon(seed: string, stream: WriteStream): Promise<void> {
|
2019-04-15 13:37:21 +02:00
|
|
|
const rand = gen.create(seed);
|
2022-02-03 13:20:25 +01:00
|
|
|
const canvas = p.make(size, size, undefined);
|
2023-01-13 05:40:33 +01:00
|
|
|
const ctx = canvas.getContext("2d");
|
2019-04-15 13:37:21 +02:00
|
|
|
|
2022-07-09 06:22:35 +02:00
|
|
|
const bgColors = colors[rand(colors.length)];
|
|
|
|
|
|
|
|
const bg = ctx.createLinearGradient(0, 0, size, size);
|
|
|
|
bg.addColorStop(0, bgColors[0]);
|
|
|
|
bg.addColorStop(1, bgColors[1]);
|
|
|
|
|
2019-04-15 13:37:21 +02:00
|
|
|
ctx.fillStyle = bg;
|
|
|
|
ctx.beginPath();
|
|
|
|
ctx.fillRect(0, 0, size, size);
|
|
|
|
|
2023-01-13 05:40:33 +01:00
|
|
|
ctx.fillStyle = "#ffffff";
|
2019-04-15 13:37:21 +02:00
|
|
|
|
|
|
|
// side bitmap (filled by false)
|
|
|
|
const side: boolean[][] = new Array(sideN);
|
|
|
|
for (let i = 0; i < side.length; i++) {
|
|
|
|
side[i] = new Array(n).fill(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 1*n (filled by false)
|
|
|
|
const center: boolean[] = new Array(n).fill(false);
|
|
|
|
|
|
|
|
for (let x = 0; x < side.length; x++) {
|
|
|
|
for (let y = 0; y < side[x].length; y++) {
|
|
|
|
side[x][y] = rand(3) === 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0; i < center.length; i++) {
|
|
|
|
center[i] = rand(3) === 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Draw
|
|
|
|
for (let x = 0; x < n; x++) {
|
|
|
|
for (let y = 0; y < n; y++) {
|
2023-01-13 05:40:33 +01:00
|
|
|
const isXCenter = x === (n - 1) / 2;
|
2019-04-15 13:37:21 +02:00
|
|
|
if (isXCenter && !center[y]) continue;
|
|
|
|
|
2023-01-13 05:40:33 +01:00
|
|
|
const isLeftSide = x < (n - 1) / 2;
|
2019-04-15 13:37:21 +02:00
|
|
|
if (isLeftSide && !side[x][y]) continue;
|
|
|
|
|
2023-01-13 05:40:33 +01:00
|
|
|
const isRightSide = x > (n - 1) / 2;
|
2019-04-15 13:37:21 +02:00
|
|
|
if (isRightSide && !side[sideN - (x - sideN)][y]) continue;
|
|
|
|
|
2023-01-13 05:40:33 +01:00
|
|
|
const actualX = margin + cellSize * x;
|
|
|
|
const actualY = margin + cellSize * y;
|
2019-04-15 13:37:21 +02:00
|
|
|
ctx.beginPath();
|
|
|
|
ctx.fillRect(actualX, actualY, cellSize, cellSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-15 15:58:04 +02:00
|
|
|
return p.encodePNGToStream(canvas, stream);
|
2019-04-15 13:37:21 +02:00
|
|
|
}
|