Skip to main content

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}