resolves #47 Co-authored-by: johba <johba@harb.eth> Reviewed-on: https://codeberg.org/johba/harb/pulls/54
133 lines
3.5 KiB
TypeScript
133 lines
3.5 KiB
TypeScript
const randseed = new Array(4); // Xorshift: [x, y, z, w] 32 bit values
|
|
|
|
interface BlockiesOpt {
|
|
seed: string;
|
|
size: number;
|
|
scale: number;
|
|
color?: string;
|
|
bgcolor?: string;
|
|
spotcolor?: string;
|
|
}
|
|
|
|
export function getBlocky(address: string) {
|
|
if (!address || typeof address !== 'string') {
|
|
return;
|
|
}
|
|
|
|
const blockiesData = createIcon({
|
|
seed: address?.toLowerCase(),
|
|
size: 8,
|
|
scale: 4,
|
|
}).toDataURL();
|
|
|
|
return blockiesData;
|
|
}
|
|
|
|
function seedrand(seed: string) {
|
|
for (let i = 0; i < randseed.length; i++) {
|
|
randseed[i] = 0;
|
|
}
|
|
for (let i = 0; i < seed.length; i++) {
|
|
randseed[i % 4] = (randseed[i % 4] << 5) - randseed[i % 4] + seed.charCodeAt(i);
|
|
}
|
|
}
|
|
|
|
function rand() {
|
|
// based on Java's String.hashCode(), expanded to 4 32bit values
|
|
const t = randseed[0] ^ (randseed[0] << 11);
|
|
|
|
randseed[0] = randseed[1];
|
|
randseed[1] = randseed[2];
|
|
randseed[2] = randseed[3];
|
|
randseed[3] = randseed[3] ^ (randseed[3] >> 19) ^ t ^ (t >> 8);
|
|
|
|
return (randseed[3] >>> 0) / ((1 << 31) >>> 0);
|
|
}
|
|
|
|
function createColor() {
|
|
//saturation is the whole color spectrum
|
|
const h = Math.floor(rand() * 360);
|
|
//saturation goes from 40 to 100, it avoids greyish colors
|
|
const s = rand() * 60 + 40 + '%';
|
|
//lightness can be anything from 0 to 100, but probabilities are a bell curve around 50%
|
|
const l = (rand() + rand() + rand() + rand()) * 25 + '%';
|
|
|
|
const color = 'hsl(' + h + ',' + s + ',' + l + ')';
|
|
return color;
|
|
}
|
|
|
|
function createImageData(size: number) {
|
|
const width = size; // Only support square icons for now
|
|
const height = size;
|
|
|
|
const dataWidth = Math.ceil(width / 2);
|
|
const mirrorWidth = width - dataWidth;
|
|
|
|
const data = [];
|
|
for (let y = 0; y < height; y++) {
|
|
let row = [];
|
|
for (let x = 0; x < dataWidth; x++) {
|
|
// this makes foreground and background color to have a 43% (1/2.3) probability
|
|
// spot color has 13% chance
|
|
row[x] = Math.floor(rand() * 2.3);
|
|
}
|
|
const r = row.slice(0, mirrorWidth);
|
|
r.reverse();
|
|
row = row.concat(r);
|
|
|
|
for (let i = 0; i < row.length; i++) {
|
|
data.push(row[i]);
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
function buildOpts(opts: Partial<BlockiesOpt>): BlockiesOpt {
|
|
const seed = opts.seed || Math.floor(Math.random() * Math.pow(10, 16)).toString(16);
|
|
seedrand(seed);
|
|
|
|
return {
|
|
seed,
|
|
size: opts.size || 8,
|
|
scale: opts.scale || 4,
|
|
color: opts.color || createColor(),
|
|
bgcolor: opts.bgcolor || createColor(),
|
|
spotcolor: opts.spotcolor || createColor(),
|
|
};
|
|
}
|
|
|
|
function renderIcon(opts: Partial<BlockiesOpt>, canvas: HTMLCanvasElement) {
|
|
const fullOpts = buildOpts(opts);
|
|
const imageData = createImageData(fullOpts.size);
|
|
const width = Math.sqrt(imageData.length);
|
|
|
|
canvas.width = canvas.height = fullOpts.size * fullOpts.scale;
|
|
|
|
const cc = canvas.getContext('2d')!;
|
|
cc.fillStyle = fullOpts.bgcolor!;
|
|
cc.fillRect(0, 0, canvas.width, canvas.height);
|
|
cc.fillStyle = fullOpts.color!;
|
|
|
|
for (let i = 0; i < imageData.length; i++) {
|
|
// if data is 0, leave the background
|
|
if (imageData[i]) {
|
|
const row = Math.floor(i / width);
|
|
const col = i % width;
|
|
|
|
// if data is 2, choose spot color, if 1 choose foreground
|
|
cc.fillStyle = imageData[i] == 1 ? fullOpts.color! : fullOpts.spotcolor!;
|
|
|
|
cc.fillRect(col * fullOpts.scale, row * fullOpts.scale, fullOpts.scale, fullOpts.scale);
|
|
}
|
|
}
|
|
return canvas;
|
|
}
|
|
|
|
export function createIcon(opts: BlockiesOpt) {
|
|
const canvas = document.createElement('canvas');
|
|
|
|
renderIcon(opts, canvas);
|
|
|
|
return canvas;
|
|
}
|