use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CustomShader {
pub name: String,
pub code: String,
pub uniforms: HashMap<String, ShaderUniform>,
compiled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ShaderUniform {
Float(f32),
Vec2(f32, f32),
Vec3(f32, f32, f32),
Vec4(f32, f32, f32, f32),
Color([u8; 4]),
Texture(String),
}
impl CustomShader {
pub fn new(name: &str, code: &str) -> Self {
Self {
name: name.to_string(),
code: code.to_string(),
uniforms: HashMap::new(),
compiled: false,
}
}
pub fn set_uniform(&mut self, name: &str, value: ShaderUniform) {
self.uniforms.insert(name.to_string(), value);
}
pub fn get_uniform(&self, name: &str) -> Option<&ShaderUniform> {
self.uniforms.get(name)
}
pub fn compile(&mut self) -> Result<(), String> {
if self.code.is_empty() {
return Err("Shader code is empty".to_string());
}
if !self.code.contains("fn main") && !self.code.contains("fn process") {
return Err("Shader must have a 'main' or 'process' function".to_string());
}
self.compiled = true;
Ok(())
}
pub fn apply(&self, pixel: [u8; 4], x: u32, y: u32, width: u32, height: u32) -> [u8; 4] {
if !self.compiled {
return pixel;
}
self.interpret_shader(pixel, x, y, width, height)
}
fn interpret_shader(&self, pixel: [u8; 4], x: u32, y: u32, width: u32, height: u32) -> [u8; 4] {
let mut result = pixel;
let u = x as f32 / width as f32;
let v = y as f32 / height as f32;
if self.code.contains("invert") {
result[0] = 255 - result[0];
result[1] = 255 - result[1];
result[2] = 255 - result[2];
}
if self.code.contains("grayscale") {
let gray = (result[0] as f32 * 0.299 + result[1] as f32 * 0.587 + result[2] as f32 * 0.114) as u8;
result[0] = gray;
result[1] = gray;
result[2] = gray;
}
if self.code.contains("sepia") {
let r = result[0] as f32;
let g = result[1] as f32;
let b = result[2] as f32;
result[0] = ((r * 0.393 + g * 0.769 + b * 0.189).min(255.0)) as u8;
result[1] = ((r * 0.349 + g * 0.686 + b * 0.168).min(255.0)) as u8;
result[2] = ((r * 0.272 + g * 0.534 + b * 0.131).min(255.0)) as u8;
}
if self.code.contains("vignette") {
let dx = u - 0.5;
let dy = v - 0.5;
let dist = (dx * dx + dy * dy).sqrt();
let vignette = (1.0 - dist * 1.5).max(0.0);
result[0] = (result[0] as f32 * vignette) as u8;
result[1] = (result[1] as f32 * vignette) as u8;
result[2] = (result[2] as f32 * vignette) as u8;
}
if self.code.contains("brightness") {
if let Some(ShaderUniform::Float(brightness)) = self.get_uniform("brightness") {
result[0] = (result[0] as f32 * brightness).min(255.0) as u8;
result[1] = (result[1] as f32 * brightness).min(255.0) as u8;
result[2] = (result[2] as f32 * brightness).min(255.0) as u8;
}
}
if self.code.contains("contrast") {
if let Some(ShaderUniform::Float(contrast)) = self.get_uniform("contrast") {
let factor = (259.0 * (contrast + 255.0)) / (255.0 * (259.0 - contrast));
result[0] = ((factor * (result[0] as f32 - 128.0) + 128.0).clamp(0.0, 255.0)) as u8;
result[1] = ((factor * (result[1] as f32 - 128.0) + 128.0).clamp(0.0, 255.0)) as u8;
result[2] = ((factor * (result[2] as f32 - 128.0) + 128.0).clamp(0.0, 255.0)) as u8;
}
}
if self.code.contains("tint") {
if let Some(ShaderUniform::Color(tint)) = self.get_uniform("tint") {
result[0] = ((result[0] as f32 * tint[0] as f32 / 255.0).min(255.0)) as u8;
result[1] = ((result[1] as f32 * tint[1] as f32 / 255.0).min(255.0)) as u8;
result[2] = ((result[2] as f32 * tint[2] as f32 / 255.0).min(255.0)) as u8;
}
}
result
}
pub fn from_file(path: &str) -> Result<Self, String> {
let code = std::fs::read_to_string(path)
.map_err(|e| format!("Failed to read shader file: {}", e))?;
let name = std::path::Path::new(path)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("unnamed")
.to_string();
let mut shader = Self::new(&name, &code);
shader.compile()?;
Ok(shader)
}
pub fn save(&self, path: &str) -> Result<(), String> {
std::fs::write(path, &self.code)
.map_err(|e| format!("Failed to save shader: {}", e))
}
}
pub struct ShaderLibrary {
shaders: HashMap<String, CustomShader>,
}
impl ShaderLibrary {
pub fn new() -> Self {
let mut library = Self {
shaders: HashMap::new(),
};
library.load_builtin_shaders();
library
}
fn load_builtin_shaders(&mut self) {
let invert = CustomShader::new("invert", "fn main() { invert }");
self.add_shader(invert);
let grayscale = CustomShader::new("grayscale", "fn main() { grayscale }");
self.add_shader(grayscale);
let sepia = CustomShader::new("sepia", "fn main() { sepia }");
self.add_shader(sepia);
let vignette = CustomShader::new("vignette", "fn main() { vignette }");
self.add_shader(vignette);
let mut brightness = CustomShader::new("brightness", "fn main() { brightness }");
brightness.set_uniform("brightness", ShaderUniform::Float(1.5));
self.add_shader(brightness);
let mut contrast = CustomShader::new("contrast", "fn main() { contrast }");
contrast.set_uniform("contrast", ShaderUniform::Float(50.0));
self.add_shader(contrast);
let mut tint = CustomShader::new("tint", "fn main() { tint }");
tint.set_uniform("tint", ShaderUniform::Color([255, 200, 150, 255]));
self.add_shader(tint);
}
pub fn add_shader(&mut self, mut shader: CustomShader) {
let _ = shader.compile();
self.shaders.insert(shader.name.clone(), shader);
}
pub fn get_shader(&self, name: &str) -> Option<&CustomShader> {
self.shaders.get(name)
}
pub fn get_shader_mut(&mut self, name: &str) -> Option<&mut CustomShader> {
self.shaders.get_mut(name)
}
pub fn list_shaders(&self) -> Vec<String> {
self.shaders.keys().cloned().collect()
}
pub fn apply_shader(&self, name: &str, pixels: &mut [u8], width: u32, height: u32) {
if let Some(shader) = self.get_shader(name) {
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 4) as usize;
if idx + 3 < pixels.len() {
let pixel = [
pixels[idx],
pixels[idx + 1],
pixels[idx + 2],
pixels[idx + 3],
];
let result = shader.apply(pixel, x, y, width, height);
pixels[idx..idx + 4].copy_from_slice(&result);
}
}
}
}
}
}
pub struct ShaderBuilder {
name: String,
operations: Vec<String>,
uniforms: HashMap<String, ShaderUniform>,
}
impl ShaderBuilder {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
operations: Vec::new(),
uniforms: HashMap::new(),
}
}
pub fn invert(mut self) -> Self {
self.operations.push("invert".to_string());
self
}
pub fn grayscale(mut self) -> Self {
self.operations.push("grayscale".to_string());
self
}
pub fn sepia(mut self) -> Self {
self.operations.push("sepia".to_string());
self
}
pub fn vignette(mut self) -> Self {
self.operations.push("vignette".to_string());
self
}
pub fn brightness(mut self, value: f32) -> Self {
self.operations.push("brightness".to_string());
self.uniforms.insert("brightness".to_string(), ShaderUniform::Float(value));
self
}
pub fn contrast(mut self, value: f32) -> Self {
self.operations.push("contrast".to_string());
self.uniforms.insert("contrast".to_string(), ShaderUniform::Float(value));
self
}
pub fn tint(mut self, color: [u8; 4]) -> Self {
self.operations.push("tint".to_string());
self.uniforms.insert("tint".to_string(), ShaderUniform::Color(color));
self
}
pub fn build(self) -> CustomShader {
let code = format!("fn main() {{ {} }}", self.operations.join(" "));
let mut shader = CustomShader::new(&self.name, &code);
shader.uniforms = self.uniforms;
let _ = shader.compile();
shader
}
}