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;
}