Spaces:
Running
Running
| /** | |
| * Perceptual masking β adapt watermark strength based on local image content | |
| * | |
| * High-energy (textured) areas can tolerate stronger watermarks, | |
| * while smooth areas need weaker embedding to remain imperceptible. | |
| */ | |
| /** | |
| * Compute AC energy of an 8x8 DCT block (sum of squared AC coefficients) | |
| * Assumes the block is already in DCT domain. | |
| */ | |
| export function blockAcEnergy(dctBlock: Float64Array): number { | |
| let energy = 0; | |
| for (let i = 1; i < 64; i++) { // Skip DC (index 0) | |
| energy += dctBlock[i] * dctBlock[i]; | |
| } | |
| return energy; | |
| } | |
| /** | |
| * Compute perceptual masking factors for a set of DCT blocks | |
| * | |
| * Returns per-block multiplier for delta: | |
| * Ξ_effective = Ξ_base Γ masking_factor | |
| * | |
| * Factors are in [0.1, 2.0]: | |
| * - Smooth/flat blocks β factor near 0.1 (barely embed β changes would be visible) | |
| * - Textured/noisy blocks β factor up to 2.0 (can embed aggressively) | |
| * | |
| * Uses a square-root curve so that low-energy blocks are suppressed | |
| * more aggressively than a linear mapping would. | |
| * | |
| * @param blockEnergies - AC energy for each block | |
| * @returns Array of masking factors, same length as input | |
| */ | |
| export function computeMaskingFactors(blockEnergies: Float64Array): Float64Array { | |
| const n = blockEnergies.length; | |
| if (n === 0) return new Float64Array(0); | |
| // Compute median energy | |
| const sorted = new Float64Array(blockEnergies).sort(); | |
| const median = n % 2 === 0 | |
| ? (sorted[n / 2 - 1] + sorted[n / 2]) / 2 | |
| : sorted[Math.floor(n / 2)]; | |
| // Avoid division by zero | |
| const safeMedian = Math.max(median, 1e-6); | |
| const factors = new Float64Array(n); | |
| for (let i = 0; i < n; i++) { | |
| const ratio = blockEnergies[i] / safeMedian; | |
| // sqrt curve: suppresses low-energy blocks more aggressively | |
| // ratio=0 β 0, ratio=0.25 β 0.5, ratio=1 β 1, ratio=4 β 2 | |
| const curved = Math.sqrt(ratio); | |
| // Clamp to [0.1, 2.0] β flat areas barely embed | |
| factors[i] = Math.max(0.1, Math.min(2.0, curved)); | |
| } | |
| return factors; | |
| } | |