use crate::{
Block, BlockCutted, Imbedded,
config::{WatermarkConfig, WatermarkMode},
quantization::{average_value, embed_quantization, extract_quantization},
strategy::Permutation,
transform::dct::{dct2_2d, dct3_2d},
};
use bitvec::prelude::*;
use itertools::Itertools;
use rayon::prelude::*;
impl BlockCutted {
pub fn embed_watermark_bits(
self,
watermark_bits: &BitSlice<u8>,
config: &WatermarkConfig,
) -> Imbedded {
let wm_len = watermark_bits.len();
let nblocks = self.blocks_dimensions.0 * self.blocks_dimensions.1;
assert!(nblocks >= wm_len, "not enough blocks for watermark");
let perm = match config.mode {
WatermarkMode::Normal => Permutation {
f: (0..nblocks).collect(),
n: nblocks,
},
WatermarkMode::Strategy(seed) => Permutation::new(nblocks, seed),
};
let (y_ll_blocks, cb_ll_blocks, cr_ll_blocks) = (0..nblocks)
.into_par_iter()
.map(|i| {
let bit = watermark_bits[perm.corresponding_wmbits_position(i, wm_len)];
(
self.y_ll_blocks[i].imbed_bit(bit, config),
self.cb_ll_blocks[i].imbed_bit(bit, config),
self.cr_ll_blocks[i].imbed_bit(bit, config),
)
})
.collect::<Vec<_>>()
.into_iter()
.multiunzip();
Imbedded {
y_ll_blocks,
cb_ll_blocks,
cr_ll_blocks,
y: self.y,
cb: self.cb,
cr: self.cr,
a: self.a,
original_dimensions: self.original_dimensions,
blocks_dimensions: self.blocks_dimensions,
}
}
pub fn extract_watermark_bits(self, wm_len: usize, config: &WatermarkConfig) -> BitVec<u8> {
let nblocks = self.blocks_dimensions.0 * self.blocks_dimensions.1;
assert!(wm_len > 0, "wm_len cannot be zero");
assert!(nblocks > 0);
let perm = match config.mode {
WatermarkMode::Normal => Permutation {
f: (0..nblocks).collect(),
n: nblocks,
},
WatermarkMode::Strategy(seed) => Permutation::new(nblocks, seed),
};
let block_bits: Vec<_> = (0..nblocks)
.into_par_iter()
.map(|i| {
(
self.y_ll_blocks[i].extract_bit(config),
self.cb_ll_blocks[i].extract_bit(config),
self.cr_ll_blocks[i].extract_bit(config),
)
})
.collect();
(0..wm_len)
.map(|i| {
let corresponding_block_positions = perm.corresponding_block_positions(i, wm_len);
let total = corresponding_block_positions
.iter()
.map(|&j| {
block_bits[j].0 as usize
+ block_bits[j].1 as usize
+ block_bits[j].2 as usize
})
.sum::<usize>();
let count = corresponding_block_positions.len() * 3;
total * 2 >= count
})
.collect::<Vec<bool>>()
.into_iter()
.collect() }
}
impl Block {
fn imbed_bit(&self, bit: bool, config: &WatermarkConfig) -> Block {
let Ok(svd_output) = dct2_2d(self.mat_data.as_ref()).svd() else {
return self.clone();
};
let u = svd_output.U();
let v = svd_output.V();
let mut s = svd_output.S() * 1.0;
let strength_1 = config.strength_1;
s[0] = embed_quantization(s[0], bit, strength_1);
if let Some(strength_2) = config.strength_2 {
s[1] = embed_quantization(s[1], bit, strength_2);
}
let mat_data = dct3_2d((u * s * v.transpose()).as_ref());
Block { mat_data }
}
fn extract_bit(&self, config: &WatermarkConfig) -> bool {
let Ok(singular) = dct2_2d(self.mat_data.as_ref()).singular_values() else {
return false;
};
let strength_1 = config.strength_1;
match config.strength_2 {
None => extract_quantization(singular[0], strength_1),
Some(strength_2) => {
let first = extract_quantization(singular[0], strength_1);
let second = extract_quantization(singular[1], strength_2);
average_value(first, second)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{WatermarkConfig, WatermarkConfigBuilder, WatermarkMode};
use faer::prelude::*;
fn create_test_block() -> Block {
let data = Mat::<f32>::full(4, 4, 1.0);
Block { mat_data: data }
}
fn create_test_config() -> WatermarkConfig {
WatermarkConfigBuilder::default()
.mode(WatermarkMode::Strategy(0))
.build()
.unwrap()
}
#[test]
fn test_embed_extract_bit_true() {
let block = create_test_block();
let config = create_test_config();
let watermarked = block.imbed_bit(true, &config);
let extracted = watermarked.extract_bit(&config);
assert!(extracted, "Embedded true bit should be extracted as true");
}
#[test]
fn test_embed_extract_bit_false() {
let block = create_test_block();
let config = create_test_config();
let watermarked = block.imbed_bit(false, &config);
let extracted = watermarked.extract_bit(&config);
assert!(
!extracted,
"Embedded false bit should be extracted as false"
);
}
}