use serde::{Deserialize, Deserializer, Serializer}; pub fn parse_hex_color(s: &str) -> Result<(f64, f64, f64, f64), String> { let s = s.trim_start_matches('#'); let len = s.len(); if len != 6 && len != 8 { return Err("Color must be 6 (RRGGBB) or 8 (RRGGBBAA)".to_string()); } let r = u8::from_str_radix(&s[0..2], 16).map_err(|_| "Invalid red")? as f64 / 255.0; let g = u8::from_str_radix(&s[2..4], 16).map_err(|_| "Invalid green")? as f64 / 255.0; let b = u8::from_str_radix(&s[4..6], 16).map_err(|_| "Invalid blue")? as f64 / 255.0; let a = if len == 8 { u8::from_str_radix(&s[6..8], 16).map_err(|_| "Invalid alpha")? as f64 / 255.0 } else { 1.0 }; Ok((r, g, b, a)) } pub fn deserialize_hex_color<'de, D>(deserializer: D) -> Result<(f64, f64, f64, f64), D::Error> where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; parse_hex_color(&s).map_err(serde::de::Error::custom) } pub fn serialize_hex_color( color: &(f64, f64, f64, f64), serializer: S, ) -> Result where S: Serializer, { let (r, g, b, a) = color; let r = (r * 255.0) as u8; let g = (g * 255.0) as u8; let b = (b * 255.0) as u8; let a = (a * 255.0) as u8; if a == 255 { serializer.serialize_str(&format!("{:02x}{:02x}{:02x}", r, g, b)) } else { serializer.serialize_str(&format!("{:02x}{:02x}{:02x}{:02x}", r, g, b, a)) } } pub fn parse_blur_effect(s: &str) -> Result<(u32, u32), String> { let parts: Vec<&str> = s.split('x').collect(); if parts.len() != 2 { return Err("Blur must be radiusxtimes".to_string()); } let radius = parts[0].parse().map_err(|_| "Invalid radius")?; let times = parts[1].parse().map_err(|_| "Invalid times")?; Ok((radius, times)) } pub fn deserialize_blur_effect<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { let s = Option::::deserialize(deserializer)?; match s { Some(s) => parse_blur_effect(&s) .map(Some) .map_err(serde::de::Error::custom), None => Ok(None), } } pub fn serialize_blur_effect(val: &Option<(u32, u32)>, serializer: S) -> Result where S: Serializer, { match val { Some((radius, times)) => serializer.serialize_str(&format!("{}x{}", radius, times)), None => serializer.serialize_none(), } } pub fn parse_vignette_effect(s: &str) -> Result<(f32, f32), String> { let parts: Vec<&str> = s.split(':').collect(); if parts.len() != 2 { return Err("Vignette must be base:factor".to_string()); } let base = parts[0].parse().map_err(|_| "Invalid base")?; let factor = parts[1].parse().map_err(|_| "Invalid factor")?; Ok((base, factor)) } pub fn deserialize_vignette_effect<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { let s = Option::::deserialize(deserializer)?; match s { Some(s) => parse_vignette_effect(&s) .map(Some) .map_err(serde::de::Error::custom), None => Ok(None), } } pub fn serialize_vignette_effect( val: &Option<(f32, f32)>, serializer: S, ) -> Result where S: Serializer, { match val { Some((base, factor)) => serializer.serialize_str(&format!("{}:{}", base, factor)), None => serializer.serialize_none(), } }