UNPKG

@ledgerhq/coin-aptos

Version:
167 lines (137 loc) 5.98 kB
// Utility for Xorshift-based PRNG // Random number generator using Xorshift algorithm, as established in the Ethereum Blockies // https://github.com/ethereum/blockies class XorshiftPRNG { private state: number[] = [0, 0, 0, 0]; seed(seedStr: string): void { this.state.fill(0); for (let i = 0; i < seedStr.length; i++) { this.state[i % 4] = (this.state[i % 4] << 5) - this.state[i % 4] + seedStr.charCodeAt(i); } } random(): number { const t = this.state[0] ^ (this.state[0] << 11); this.state[0] = this.state[1]; this.state[1] = this.state[2]; this.state[2] = this.state[3]; this.state[3] = this.state[3] ^ (this.state[3] >> 19) ^ t ^ (t >> 8); return (this.state[3] >>> 0) / ((1 << 31) >>> 0); } } type RGB = [number, number, number]; // Identicon generator class // Generates a unique identicon based on a seed string, and returns it as a BMP image data URL. export class IconGenerator { private rng: XorshiftPRNG; constructor(seed: string) { this.rng = new XorshiftPRNG(); this.rng.seed(seed); } private random = () => this.rng.random(); // Generates a random color in RGB format // The color is generated using HSL values, ensuring a good distribution of colors. // The hue is randomized, saturation is between 40% and 100%, and lightness is a combination of random values. // The resulting RGB values are rounded to integers between 0 and 255. private createColor(): RGB { let h = Math.floor(this.random() * 360); let s = this.random() * 60 + 40; let l = (this.random() + this.random() + this.random() + this.random()) * 25; h = ((h % 360) + 360) % 360; s /= 100; l /= 100; const hueToRgb = (p: number, q: number, t: number): number => { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1 / 6) return p + (q - p) * 6 * t; if (t < 1 / 2) return q; if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; }; if (s === 0) return [l, l, l].map(v => Math.round(v * 255)) as RGB; const q = l < 0.5 ? l * (1 + s) : l + s - l * s; const p = 2 * l - q; const r = hueToRgb(p, q, h / 360 + 1 / 3); const g = hueToRgb(p, q, h / 360); const b = hueToRgb(p, q, h / 360 - 1 / 3); return [r, g, b].map(v => Math.round(v * 255)) as RGB; } // Creates a symmetric image data array for the identicon // The image is symmetric, with the left half being a mirror of the right half. // The data is generated by filling the left half with random values (0, 1, or 2), // and mirroring it to the right half. private createImageData(size: number): number[] { const width = size; const dataWidth = Math.ceil(width / 2); const mirrorWidth = width - dataWidth; const data: number[] = []; for (let y = 0; y < size; y++) { const row: number[] = Array.from({ length: dataWidth }, () => Math.floor(this.random() * 2.3), ); const mirrored = row.slice(0, mirrorWidth).reverse(); row.push(...mirrored); data.push(...row); } return data; } // Converts the image data into pixel data for BMP format // The pixel data is generated by scaling the image data according to the specified scale factor. // Each cell in the image data is represented by a block of pixels in the output, // with the color determined by the value in the cell (0 for background, 1 for main color, 2 for spot color). private toPixels( size: number, cellData: number[], scale: number, color: RGB, spotColor: RGB, bgColor: RGB, ): number[] { const rowSize = size * scale; const rowDataSize = rowSize * 3; const data = new Array<number>(rowSize * rowDataSize); for (let x = 0; x < size; x++) { for (let y = 0; y < size; y++) { const value = cellData[(size - x - 1) * size + y]; const c = value === 0 ? bgColor : value === 1 ? color : spotColor; for (let dx = 0; dx < scale; dx++) { for (let dy = 0; dy < scale; dy++) { const pixelIndex = (x * scale + dx) * rowDataSize + (y * scale + dy) * 3; [2, 1, 0].forEach((offset, idx) => { data[pixelIndex + offset] = c[idx]; }); } } } } return data; } // Converts a number to little-endian hexadecimal format private static toLEHex(n: number): string { return (n + 2 ** 32).toString(16).match(/\B../g)?.reverse().join("") ?? ""; } // Generates a BMP image data URL from pixel data // The BMP format is constructed with a header and pixel data correctly padded. // The header includes metadata such as size, width, height, and color depth. private static generateBMP(width: number, pixels: number[]): string { const height = Math.floor(pixels.length / (width * 3)); const size = this.toLEHex(26 + pixels.length); const wh = this.toLEHex(width).slice(0, 4) + this.toLEHex(height).slice(0, 4); const headerHex = `424d${size}000000001b0000000C000000${wh}0100180000`; const headerBytes = headerHex.match(/../g)?.map(h => parseInt(h, 16)) ?? []; const pixelChars = pixels.map(p => String.fromCharCode(p)); const base64Header = btoa(String.fromCharCode(...headerBytes)); const base64Pixels = btoa(pixelChars.join("")); return `data:image/bmp;base64,${base64Header}${base64Pixels}`; } // Generates a BMP image data URL for the identicon // The size and scale can be adjusted, with a default size of 8 and scale of 4. // The generated image will have a symmetric pattern based on the seed string. generate(size = 8, scale = 4): string { const mainColor = this.createColor(); const backgroundColor = this.createColor(); const spotColor = this.createColor(); const cellData = this.createImageData(size); const pixelData = this.toPixels(size, cellData, scale, mainColor, spotColor, backgroundColor); return IconGenerator.generateBMP(size * scale, pixelData); } }