Spaces:
Running
Running
File size: 3,208 Bytes
f2f99a3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | /**
* 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;
}
|