phasm_core/stego/cost/mod.rs
1// Copyright (c) 2026 Christoph Gaffga
2// SPDX-License-Identifier: GPL-3.0-only
3// https://github.com/cgaffga/phasmcore
4
5//! Embedding cost functions for steganographic coding.
6//!
7//! Cost functions assign a distortion value to each DCT coefficient position,
8//! guiding the STC encoder to modify only the least detectable positions.
9//! Lower cost = safer to modify. Infinite cost (`WET_COST`) = must never modify.
10//!
11//! Implements:
12//! - **J-UNIWARD** (JPEG Universal Wavelet Relative Distortion): state-of-the-art
13//! cost function using directional wavelet decomposition for superior
14//! steganalysis resistance.
15//! - **UERD** (test-only): simple block-energy cost, replaced by J-UNIWARD in production.
16
17#[cfg(test)]
18pub mod uerd;
19pub mod uniward;
20
21// HEVC-specific cost modules — archived behind `hevc-archive` feature.
22
23// H.264 (production) cost modules.
24#[cfg(feature = "video")]
25pub mod h264_cost;
26#[cfg(feature = "video")]
27pub mod h264_uniward;
28#[cfg(feature = "video")]
29pub mod h264_ddca;
30#[cfg(feature = "video")]
31pub mod h264_mvd_cost;
32
33/// Cost assigned to coefficients that must never be modified (f32 version).
34/// Using infinity ensures the Viterbi STC never selects these positions for flipping.
35pub const WET_COST: f32 = f32::INFINITY;
36
37/// WET_COST as f64 for use in the STC pipeline (which operates in f64).
38pub const WET_COST_F64: f64 = f64::INFINITY;
39
40/// Per-coefficient embedding costs for one image component.
41///
42/// Stored in the same block-raster order as `DctGrid`: block index * 64 + row * 8 + col.
43/// DC positions (index % 64 == 0) always have `WET_COST`.
44///
45/// Uses f32 storage to halve memory usage (~46 MB instead of ~93 MB for a
46/// 4032x3024 image). f32 has more than enough precision for cost ranking.
47pub struct CostMap {
48 /// Number of 8×8 blocks horizontally.
49 blocks_wide: usize,
50 /// Number of 8×8 blocks vertically.
51 blocks_tall: usize,
52 /// Flat storage: one f32 cost per coefficient position.
53 costs: Vec<f32>,
54}
55
56impl CostMap {
57 pub fn new(blocks_wide: usize, blocks_tall: usize) -> Self {
58 let n = blocks_wide * blocks_tall * 64;
59 Self {
60 blocks_wide,
61 blocks_tall,
62 costs: vec![WET_COST; n],
63 }
64 }
65
66 pub fn blocks_wide(&self) -> usize {
67 self.blocks_wide
68 }
69
70 pub fn blocks_tall(&self) -> usize {
71 self.blocks_tall
72 }
73
74 pub fn total_blocks(&self) -> usize {
75 self.blocks_wide * self.blocks_tall
76 }
77
78 /// Get the cost at block (br, bc), frequency position (i, j).
79 pub fn get(&self, br: usize, bc: usize, i: usize, j: usize) -> f32 {
80 self.costs[self.index(br, bc, i, j)]
81 }
82
83 /// Set the cost at block (br, bc), frequency position (i, j).
84 pub fn set(&mut self, br: usize, bc: usize, i: usize, j: usize, val: f32) {
85 let idx = self.index(br, bc, i, j);
86 self.costs[idx] = val;
87 }
88
89 /// Get a raw pointer into the cost storage for direct writes.
90 ///
91 /// # Safety
92 /// Caller must ensure no aliasing writes to the same index.
93 /// Safe when blocks write to non-overlapping regions (each block
94 /// occupies indices `[block_idx * 64 .. block_idx * 64 + 64)`).
95 pub(crate) fn costs_ptr(&mut self) -> *mut f32 {
96 self.costs.as_mut_ptr()
97 }
98
99 fn index(&self, br: usize, bc: usize, i: usize, j: usize) -> usize {
100 (br * self.blocks_wide + bc) * 64 + i * 8 + j
101 }
102}