#![allow(dead_code)]
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum PostProcessEffect {
Bloom,
Sharpen,
Ssao,
ToneMap,
ChromAberration,
Vignette,
}
impl PostProcessEffect {
#[allow(dead_code)]
pub fn display_name(&self) -> &'static str {
match self {
PostProcessEffect::Bloom => "Bloom",
PostProcessEffect::Sharpen => "Sharpen",
PostProcessEffect::Ssao => "SSAO",
PostProcessEffect::ToneMap => "ToneMap",
PostProcessEffect::ChromAberration => "ChromAberration",
PostProcessEffect::Vignette => "Vignette",
}
}
}
#[derive(Debug, Clone)]
pub struct EffectState {
pub enabled: bool,
pub params: HashMap<String, f32>,
}
impl Default for EffectState {
fn default() -> Self {
EffectState {
enabled: true,
params: HashMap::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct PostProcessConfig {
pub json_indent: usize,
pub default_enabled: bool,
}
impl Default for PostProcessConfig {
fn default() -> Self {
PostProcessConfig {
json_indent: 2,
default_enabled: true,
}
}
}
#[derive(Debug, Clone)]
pub struct PostProcessChain {
pub effects: Vec<PostProcessEffect>,
pub states: Vec<EffectState>,
pub config: PostProcessConfig,
}
pub type EffectParam = (String, f32);
pub type ChainApplyResult = String;
#[allow(dead_code)]
pub fn default_post_process_config() -> PostProcessConfig {
PostProcessConfig::default()
}
#[allow(dead_code)]
pub fn new_post_process_chain(config: PostProcessConfig) -> PostProcessChain {
PostProcessChain {
effects: Vec::new(),
states: Vec::new(),
config,
}
}
#[allow(dead_code)]
pub fn add_effect(chain: &mut PostProcessChain, effect: PostProcessEffect) -> usize {
let enabled = chain.config.default_enabled;
chain.effects.push(effect);
chain.states.push(EffectState {
enabled,
params: HashMap::new(),
});
chain.effects.len() - 1
}
#[allow(dead_code)]
pub fn remove_effect(chain: &mut PostProcessChain, idx: usize) -> bool {
if idx >= chain.effects.len() {
return false;
}
chain.effects.remove(idx);
chain.states.remove(idx);
true
}
#[allow(dead_code)]
pub fn effect_count(chain: &PostProcessChain) -> usize {
chain.effects.len()
}
#[allow(dead_code)]
pub fn reset_chain(chain: &mut PostProcessChain) {
chain.effects.clear();
chain.states.clear();
}
#[allow(dead_code)]
pub fn set_effect_param(chain: &mut PostProcessChain, idx: usize, key: &str, value: f32) -> bool {
if idx >= chain.states.len() {
return false;
}
chain.states[idx].params.insert(key.to_string(), value);
true
}
#[allow(dead_code)]
pub fn get_effect_param(chain: &PostProcessChain, idx: usize, key: &str) -> Option<f32> {
chain.states.get(idx)?.params.get(key).copied()
}
#[allow(dead_code)]
pub fn effect_enabled(chain: &PostProcessChain, idx: usize) -> bool {
chain.states.get(idx).is_some_and(|s| s.enabled)
}
#[allow(dead_code)]
pub fn toggle_effect(chain: &mut PostProcessChain, idx: usize) -> bool {
if idx >= chain.states.len() {
return false;
}
chain.states[idx].enabled = !chain.states[idx].enabled;
chain.states[idx].enabled
}
#[allow(dead_code)]
pub fn effect_name(chain: &PostProcessChain, idx: usize) -> Option<&'static str> {
chain.effects.get(idx).map(PostProcessEffect::display_name)
}
fn state_to_json_inner(state: &EffectState) -> String {
let mut parts: Vec<String> = Vec::new();
parts.push(format!("\"enabled\":{}", state.enabled));
if !state.params.is_empty() {
let mut sorted: Vec<(&String, &f32)> = state.params.iter().collect();
sorted.sort_by_key(|(k, _)| k.as_str());
let param_strs: Vec<String> = sorted
.iter()
.map(|(k, v)| format!("\"{}\":{:.6}", k, v))
.collect();
parts.push(format!("\"params\":{{{}}}", param_strs.join(",")));
} else {
parts.push("\"params\":{}".to_string());
}
parts.join(",")
}
#[allow(dead_code)]
pub fn chain_to_json(chain: &PostProcessChain) -> String {
let mut effect_entries: Vec<String> = Vec::new();
for (i, effect) in chain.effects.iter().enumerate() {
let state_json = state_to_json_inner(&chain.states[i]);
effect_entries.push(format!(
"{{\"effect\":\"{}\",{}}}",
effect.display_name(),
state_json
));
}
format!("{{\"effects\":[{}]}}", effect_entries.join(","))
}
#[allow(dead_code)]
pub fn apply_chain_stub(chain: &PostProcessChain) -> ChainApplyResult {
chain_to_json(chain)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_chain() -> PostProcessChain {
new_post_process_chain(default_post_process_config())
}
#[test]
fn test_default_post_process_config() {
let cfg = default_post_process_config();
assert_eq!(cfg.json_indent, 2);
assert!(cfg.default_enabled);
}
#[test]
fn test_new_post_process_chain_empty() {
let chain = make_chain();
assert_eq!(effect_count(&chain), 0);
}
#[test]
fn test_add_effect_returns_index() {
let mut chain = make_chain();
let idx = add_effect(&mut chain, PostProcessEffect::Bloom);
assert_eq!(idx, 0);
let idx2 = add_effect(&mut chain, PostProcessEffect::ToneMap);
assert_eq!(idx2, 1);
}
#[test]
fn test_effect_count() {
let mut chain = make_chain();
add_effect(&mut chain, PostProcessEffect::Bloom);
add_effect(&mut chain, PostProcessEffect::Sharpen);
assert_eq!(effect_count(&chain), 2);
}
#[test]
fn test_remove_effect_success() {
let mut chain = make_chain();
add_effect(&mut chain, PostProcessEffect::Bloom);
add_effect(&mut chain, PostProcessEffect::Vignette);
assert!(remove_effect(&mut chain, 0));
assert_eq!(effect_count(&chain), 1);
}
#[test]
fn test_remove_effect_out_of_range() {
let mut chain = make_chain();
assert!(!remove_effect(&mut chain, 5));
}
#[test]
fn test_set_get_effect_param() {
let mut chain = make_chain();
add_effect(&mut chain, PostProcessEffect::Bloom);
assert!(set_effect_param(&mut chain, 0, "intensity", 0.5));
let v = get_effect_param(&chain, 0, "intensity");
assert!(v.is_some());
assert!((v.expect("should succeed") - 0.5).abs() < 1e-6);
}
#[test]
fn test_get_effect_param_missing() {
let mut chain = make_chain();
add_effect(&mut chain, PostProcessEffect::Bloom);
assert!(get_effect_param(&chain, 0, "nonexistent").is_none());
}
#[test]
fn test_effect_enabled_default_true() {
let mut chain = make_chain();
add_effect(&mut chain, PostProcessEffect::Ssao);
assert!(effect_enabled(&chain, 0));
}
#[test]
fn test_toggle_effect() {
let mut chain = make_chain();
add_effect(&mut chain, PostProcessEffect::Bloom);
assert!(effect_enabled(&chain, 0));
let new_state = toggle_effect(&mut chain, 0);
assert!(!new_state);
assert!(!effect_enabled(&chain, 0));
toggle_effect(&mut chain, 0);
assert!(effect_enabled(&chain, 0));
}
#[test]
fn test_effect_name() {
let mut chain = make_chain();
add_effect(&mut chain, PostProcessEffect::ToneMap);
assert_eq!(effect_name(&chain, 0), Some("ToneMap"));
}
#[test]
fn test_effect_name_out_of_range() {
let chain = make_chain();
assert!(effect_name(&chain, 99).is_none());
}
#[test]
fn test_chain_to_json_non_empty() {
let mut chain = make_chain();
add_effect(&mut chain, PostProcessEffect::Bloom);
let json = chain_to_json(&chain);
assert!(!json.is_empty());
assert!(json.contains("Bloom"));
}
#[test]
fn test_chain_to_json_empty() {
let chain = make_chain();
let json = chain_to_json(&chain);
assert!(json.contains("\"effects\":[]"), "empty chain JSON wrong: {json}");
}
#[test]
fn test_chain_to_json_with_params() {
let mut chain = make_chain();
let idx = add_effect(&mut chain, PostProcessEffect::Bloom);
set_effect_param(&mut chain, idx, "threshold", 0.9);
let json = chain_to_json(&chain);
assert!(json.contains("threshold"), "param 'threshold' not in JSON");
}
#[test]
fn test_apply_chain_stub_returns_json() {
let mut chain = make_chain();
add_effect(&mut chain, PostProcessEffect::ToneMap);
let result = apply_chain_stub(&chain);
assert!(result.contains("ToneMap"));
}
#[test]
fn test_reset_chain() {
let mut chain = make_chain();
add_effect(&mut chain, PostProcessEffect::Bloom);
add_effect(&mut chain, PostProcessEffect::Sharpen);
reset_chain(&mut chain);
assert_eq!(effect_count(&chain), 0);
}
#[test]
fn test_display_name_all_variants() {
assert_eq!(PostProcessEffect::Bloom.display_name(), "Bloom");
assert_eq!(PostProcessEffect::Sharpen.display_name(), "Sharpen");
assert_eq!(PostProcessEffect::Ssao.display_name(), "SSAO");
assert_eq!(PostProcessEffect::ToneMap.display_name(), "ToneMap");
assert_eq!(PostProcessEffect::ChromAberration.display_name(), "ChromAberration");
assert_eq!(PostProcessEffect::Vignette.display_name(), "Vignette");
}
#[test]
fn test_set_effect_param_out_of_range() {
let mut chain = make_chain();
assert!(!set_effect_param(&mut chain, 5, "k", 1.0));
}
#[test]
fn test_toggle_effect_out_of_range() {
let mut chain = make_chain();
assert!(!toggle_effect(&mut chain, 0));
}
#[test]
fn test_effects_preserve_order() {
let mut chain = make_chain();
add_effect(&mut chain, PostProcessEffect::Bloom);
add_effect(&mut chain, PostProcessEffect::Ssao);
add_effect(&mut chain, PostProcessEffect::ToneMap);
assert_eq!(chain.effects[0], PostProcessEffect::Bloom);
assert_eq!(chain.effects[1], PostProcessEffect::Ssao);
assert_eq!(chain.effects[2], PostProcessEffect::ToneMap);
}
#[test]
fn test_disabled_effect_in_json() {
let mut chain = make_chain();
add_effect(&mut chain, PostProcessEffect::Bloom);
toggle_effect(&mut chain, 0);
let json = chain_to_json(&chain);
assert!(json.contains("\"enabled\":false"), "disabled state not in JSON: {json}");
}
#[test]
fn test_default_enabled_false() {
let cfg = PostProcessConfig {
default_enabled: false,
..PostProcessConfig::default()
};
let mut chain = new_post_process_chain(cfg);
add_effect(&mut chain, PostProcessEffect::Vignette);
assert!(!effect_enabled(&chain, 0));
}
}