Spaces:
Running
Running
| /** | |
| * Key derivation and PRNG for watermark embedding | |
| * | |
| * Uses a simple but effective approach: derive deterministic | |
| * pseudo-random sequences from a secret key using a seeded PRNG. | |
| * For browser compatibility, we use a pure-JS implementation. | |
| */ | |
| /** | |
| * Simple hash function (djb2 variant) for string to 32-bit seed | |
| */ | |
| function hashString(str: string): number { | |
| let hash = 5381; | |
| for (let i = 0; i < str.length; i++) { | |
| hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0; | |
| } | |
| return hash >>> 0; | |
| } | |
| /** | |
| * Mix two 32-bit values (MurmurHash3 finalizer) | |
| */ | |
| function mix(h: number): number { | |
| h = ((h >>> 16) ^ h) * 0x45d9f3b | 0; | |
| h = ((h >>> 16) ^ h) * 0x45d9f3b | 0; | |
| h = (h >>> 16) ^ h; | |
| return h >>> 0; | |
| } | |
| /** | |
| * Seeded PRNG (xorshift128) | |
| * Produces deterministic sequences from a seed | |
| */ | |
| export class SeededPRNG { | |
| private s0: number; | |
| private s1: number; | |
| private s2: number; | |
| private s3: number; | |
| constructor(seed: number) { | |
| // Initialize state from seed using splitmix32 | |
| this.s0 = seed | 0; | |
| this.s1 = mix(seed + 1); | |
| this.s2 = mix(seed + 2); | |
| this.s3 = mix(seed + 3); | |
| // Warm up | |
| for (let i = 0; i < 20; i++) this.next(); | |
| } | |
| /** Get next random 32-bit unsigned integer */ | |
| next(): number { | |
| let t = this.s3; | |
| const s = this.s0; | |
| this.s3 = this.s2; | |
| this.s2 = this.s1; | |
| this.s1 = s; | |
| t ^= t << 11; | |
| t ^= t >>> 8; | |
| this.s0 = t ^ s ^ (s >>> 19); | |
| return this.s0 >>> 0; | |
| } | |
| /** Get random float in [0, 1) */ | |
| nextFloat(): number { | |
| return this.next() / 0x100000000; | |
| } | |
| /** Get random float in [min, max) */ | |
| nextRange(min: number, max: number): number { | |
| return min + this.nextFloat() * (max - min); | |
| } | |
| /** Get random integer in [0, max) */ | |
| nextInt(max: number): number { | |
| return (this.next() % max) >>> 0; | |
| } | |
| } | |
| /** | |
| * Derive a seed from key + purpose string | |
| */ | |
| export function deriveSeed(key: string, purpose: string): number { | |
| const combined = key + ':' + purpose; | |
| return mix(hashString(combined)); | |
| } | |
| /** | |
| * Generate dither values for DM-QIM embedding | |
| * Returns array of dither values in [-delta/2, +delta/2] | |
| */ | |
| export function generateDithers(key: string, count: number, delta: number): Float64Array { | |
| const prng = new SeededPRNG(deriveSeed(key, 'dither')); | |
| const dithers = new Float64Array(count); | |
| for (let i = 0; i < count; i++) { | |
| dithers[i] = prng.nextRange(-delta / 2, delta / 2); | |
| } | |
| return dithers; | |
| } | |
| /** | |
| * Generate a permutation (Fisher-Yates) for bit interleaving | |
| */ | |
| export function generatePermutation(key: string, length: number): Uint32Array { | |
| const prng = new SeededPRNG(deriveSeed(key, 'permutation')); | |
| const perm = new Uint32Array(length); | |
| for (let i = 0; i < length; i++) perm[i] = i; | |
| for (let i = length - 1; i > 0; i--) { | |
| const j = prng.nextInt(i + 1); | |
| const tmp = perm[i]; | |
| perm[i] = perm[j]; | |
| perm[j] = tmp; | |
| } | |
| return perm; | |
| } | |
| /** | |
| * Generate inverse permutation | |
| */ | |
| export function generateInversePermutation(key: string, length: number): Uint32Array { | |
| const perm = generatePermutation(key, length); | |
| const inv = new Uint32Array(length); | |
| for (let i = 0; i < length; i++) { | |
| inv[perm[i]] = i; | |
| } | |
| return inv; | |
| } | |