312 lines
11 KiB
Rust
312 lines
11 KiB
Rust
|
|
use hcie_protocol::Layer;
|
||
|
|
use hcie_protocol::effects::{LayerEffect, BevelStyle, Technique, Direction, StrokePosition, StrokeFillType, GradientDef};
|
||
|
|
use std::path::Path;
|
||
|
|
|
||
|
|
fn create_layer(name: &str, w: u32, h: u32, fill_r: u8, fill_g: u8, fill_b: u8) -> Layer {
|
||
|
|
create_layer_at(name, w, h, fill_r, fill_g, fill_b, w / 2, h / 2)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn create_layer_at(name: &str, w: u32, h: u32, fill_r: u8, fill_g: u8, fill_b: u8, offset_x: u32, offset_y: u32) -> Layer {
|
||
|
|
let px_count = (w * h) as usize;
|
||
|
|
let mut pixels = vec![0u8; px_count * 4];
|
||
|
|
for y in 0..h {
|
||
|
|
for x in 0..w {
|
||
|
|
let idx = (y * w + x) as usize * 4;
|
||
|
|
let cx = offset_x as f32;
|
||
|
|
let cy = offset_y as f32;
|
||
|
|
let rx = (w as f32 * 0.2) + (y as f32 * 0.1).sin() * 20.0;
|
||
|
|
let ry = (h as f32 * 0.2) + (x as f32 * 0.1).cos() * 20.0;
|
||
|
|
let dx = (x as f32 - cx).abs();
|
||
|
|
let dy = (y as f32 - cy).abs();
|
||
|
|
if dx < rx && dy < ry {
|
||
|
|
pixels[idx] = fill_r;
|
||
|
|
pixels[idx + 1] = fill_g;
|
||
|
|
pixels[idx + 2] = fill_b;
|
||
|
|
pixels[idx + 3] = 255;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Layer::from_rgba(name, w, h, pixels)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn create_layer_with_effect(name: &str, w: u32, h: u32, effect: LayerEffect) -> Layer {
|
||
|
|
let mut layer = create_layer(name, w, h, 120, 140, 200);
|
||
|
|
layer.effects = vec![effect];
|
||
|
|
layer
|
||
|
|
}
|
||
|
|
|
||
|
|
fn save_png(path: &str, pixels: &[u8], w: u32, h: u32) {
|
||
|
|
if let Some(img) = image::ImageBuffer::<image::Rgba<u8>, _>::from_raw(w, h, pixels.to_vec()) {
|
||
|
|
let _ = img.save(path);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn main() {
|
||
|
|
let out_dir = "/home/hc/Pictures/_psd_stil_test";
|
||
|
|
let w: u32 = 512;
|
||
|
|
let h: u32 = 512;
|
||
|
|
|
||
|
|
let tests: Vec<(&str, Layer)> = vec![
|
||
|
|
(
|
||
|
|
"fx_drop_shadow",
|
||
|
|
create_layer_with_effect("Drop Shadow", w, h, LayerEffect::DropShadow {
|
||
|
|
enabled: true,
|
||
|
|
blend_mode: "Multiply".to_string(),
|
||
|
|
color: [0, 0, 0, 255],
|
||
|
|
opacity: 0.75,
|
||
|
|
angle: 120.0,
|
||
|
|
distance: 15.0,
|
||
|
|
spread: 0.0,
|
||
|
|
size: 25.0,
|
||
|
|
noise: 0.0,
|
||
|
|
contour: None,
|
||
|
|
})
|
||
|
|
),
|
||
|
|
(
|
||
|
|
"fx_inner_shadow",
|
||
|
|
create_layer_with_effect("Inner Shadow", w, h, LayerEffect::InnerShadow {
|
||
|
|
enabled: true,
|
||
|
|
blend_mode: "Multiply".to_string(),
|
||
|
|
color: [0, 0, 0, 255],
|
||
|
|
opacity: 0.75,
|
||
|
|
angle: 120.0,
|
||
|
|
distance: 15.0,
|
||
|
|
choke: 0.0,
|
||
|
|
size: 25.0,
|
||
|
|
noise: 0.0,
|
||
|
|
contour: None,
|
||
|
|
})
|
||
|
|
),
|
||
|
|
(
|
||
|
|
"fx_outer_glow",
|
||
|
|
create_layer_with_effect("Outer Glow", w, h, LayerEffect::OuterGlow {
|
||
|
|
enabled: true,
|
||
|
|
blend_mode: "Normal".to_string(),
|
||
|
|
color: [255, 100, 0, 255],
|
||
|
|
opacity: 0.75,
|
||
|
|
spread: 0.0,
|
||
|
|
size: 30.0,
|
||
|
|
noise: 0.0,
|
||
|
|
contour: None,
|
||
|
|
})
|
||
|
|
),
|
||
|
|
(
|
||
|
|
"fx_inner_glow",
|
||
|
|
create_layer_with_effect("Inner Glow", w, h, LayerEffect::InnerGlow {
|
||
|
|
enabled: true,
|
||
|
|
blend_mode: "Normal".to_string(),
|
||
|
|
color: [0, 200, 255, 255],
|
||
|
|
opacity: 0.75,
|
||
|
|
choke: 0.0,
|
||
|
|
size: 15.0,
|
||
|
|
noise: 0.0,
|
||
|
|
contour: None,
|
||
|
|
source: 0,
|
||
|
|
})
|
||
|
|
),
|
||
|
|
(
|
||
|
|
"fx_bevel_inner",
|
||
|
|
create_layer_with_effect("Bevel Inner", w, h, LayerEffect::BevelEmboss {
|
||
|
|
enabled: true,
|
||
|
|
style: BevelStyle::InnerBevel,
|
||
|
|
technique: Technique::Smooth,
|
||
|
|
depth: 100.0,
|
||
|
|
direction: Direction::Up,
|
||
|
|
size: 15.0,
|
||
|
|
soften: 0.0,
|
||
|
|
angle: 120.0,
|
||
|
|
altitude: 30.0,
|
||
|
|
highlight_blend: "Screen".to_string(),
|
||
|
|
highlight_color: [255, 255, 255, 255],
|
||
|
|
highlight_opacity: 0.75,
|
||
|
|
shadow_blend: "Multiply".to_string(),
|
||
|
|
shadow_color: [0, 0, 0, 255],
|
||
|
|
shadow_opacity: 0.75,
|
||
|
|
contour: None,
|
||
|
|
})
|
||
|
|
),
|
||
|
|
(
|
||
|
|
"fx_bevel_outer",
|
||
|
|
create_layer_with_effect("Bevel Outer", w, h, LayerEffect::BevelEmboss {
|
||
|
|
enabled: true,
|
||
|
|
style: BevelStyle::OuterBevel,
|
||
|
|
technique: Technique::Smooth,
|
||
|
|
depth: 100.0,
|
||
|
|
direction: Direction::Up,
|
||
|
|
size: 15.0,
|
||
|
|
soften: 0.0,
|
||
|
|
angle: 120.0,
|
||
|
|
altitude: 30.0,
|
||
|
|
highlight_blend: "Screen".to_string(),
|
||
|
|
highlight_color: [255, 255, 255, 255],
|
||
|
|
highlight_opacity: 0.75,
|
||
|
|
shadow_blend: "Multiply".to_string(),
|
||
|
|
shadow_color: [0, 0, 0, 255],
|
||
|
|
shadow_opacity: 0.75,
|
||
|
|
contour: None,
|
||
|
|
})
|
||
|
|
),
|
||
|
|
(
|
||
|
|
"fx_emboss",
|
||
|
|
create_layer_with_effect("Emboss", w, h, LayerEffect::BevelEmboss {
|
||
|
|
enabled: true,
|
||
|
|
style: BevelStyle::Emboss,
|
||
|
|
technique: Technique::Smooth,
|
||
|
|
depth: 100.0,
|
||
|
|
direction: Direction::Up,
|
||
|
|
size: 15.0,
|
||
|
|
soften: 0.0,
|
||
|
|
angle: 120.0,
|
||
|
|
altitude: 30.0,
|
||
|
|
highlight_blend: "Screen".to_string(),
|
||
|
|
highlight_color: [255, 255, 255, 255],
|
||
|
|
highlight_opacity: 0.75,
|
||
|
|
shadow_blend: "Multiply".to_string(),
|
||
|
|
shadow_color: [0, 0, 0, 255],
|
||
|
|
shadow_opacity: 0.75,
|
||
|
|
contour: None,
|
||
|
|
})
|
||
|
|
),
|
||
|
|
(
|
||
|
|
"fx_pillow_emboss",
|
||
|
|
create_layer_with_effect("Pillow Emboss", w, h, LayerEffect::BevelEmboss {
|
||
|
|
enabled: true,
|
||
|
|
style: BevelStyle::PillowEmboss,
|
||
|
|
technique: Technique::Smooth,
|
||
|
|
depth: 100.0,
|
||
|
|
direction: Direction::Up,
|
||
|
|
size: 15.0,
|
||
|
|
soften: 0.0,
|
||
|
|
angle: 120.0,
|
||
|
|
altitude: 30.0,
|
||
|
|
highlight_blend: "Screen".to_string(),
|
||
|
|
highlight_color: [255, 255, 255, 255],
|
||
|
|
highlight_opacity: 0.75,
|
||
|
|
shadow_blend: "Multiply".to_string(),
|
||
|
|
shadow_color: [0, 0, 0, 255],
|
||
|
|
shadow_opacity: 0.75,
|
||
|
|
contour: None,
|
||
|
|
})
|
||
|
|
),
|
||
|
|
(
|
||
|
|
"fx_satin",
|
||
|
|
create_layer_with_effect("Satin", w, h, LayerEffect::Satin {
|
||
|
|
enabled: true,
|
||
|
|
blend_mode: "Multiply".to_string(),
|
||
|
|
color: [0, 0, 0, 255],
|
||
|
|
opacity: 0.5,
|
||
|
|
angle: 120.0,
|
||
|
|
distance: 15.0,
|
||
|
|
size: 25.0,
|
||
|
|
invert: false,
|
||
|
|
contour: None,
|
||
|
|
})
|
||
|
|
),
|
||
|
|
(
|
||
|
|
"fx_color_overlay",
|
||
|
|
create_layer_with_effect("Color Overlay", w, h, LayerEffect::ColorOverlay {
|
||
|
|
enabled: true,
|
||
|
|
blend_mode: "Normal".to_string(),
|
||
|
|
color: [255, 0, 180, 255],
|
||
|
|
opacity: 0.6,
|
||
|
|
})
|
||
|
|
),
|
||
|
|
(
|
||
|
|
"fx_gradient_overlay",
|
||
|
|
create_layer_with_effect("Gradient Overlay", w, h, LayerEffect::GradientOverlay {
|
||
|
|
enabled: true,
|
||
|
|
blend_mode: "Normal".to_string(),
|
||
|
|
opacity: 0.8,
|
||
|
|
angle: 90.0,
|
||
|
|
scale: 100.0,
|
||
|
|
gradient: GradientDef {
|
||
|
|
color_stops: vec![
|
||
|
|
(0, [255, 80, 0, 255]),
|
||
|
|
(4096, [255, 220, 0, 255]),
|
||
|
|
],
|
||
|
|
transparency_stops: vec![
|
||
|
|
(0, 100),
|
||
|
|
(4096, 100),
|
||
|
|
],
|
||
|
|
midpoint: 50.0,
|
||
|
|
angle: 90.0,
|
||
|
|
scale: 100.0,
|
||
|
|
gradient_type: 0,
|
||
|
|
},
|
||
|
|
})
|
||
|
|
),
|
||
|
|
(
|
||
|
|
"fx_pattern_overlay",
|
||
|
|
create_layer_with_effect("Pattern Overlay", w, h, LayerEffect::PatternOverlay {
|
||
|
|
enabled: true,
|
||
|
|
blend_mode: "Normal".to_string(),
|
||
|
|
opacity: 0.6,
|
||
|
|
scale: 100.0,
|
||
|
|
pattern_id: "b4d43394-d71c-11e5-b1ae-a548a96ef5f9".to_string(),
|
||
|
|
})
|
||
|
|
),
|
||
|
|
(
|
||
|
|
"fx_stroke_inside",
|
||
|
|
create_layer_with_effect("Stroke Inside", w, h, LayerEffect::Stroke {
|
||
|
|
enabled: true,
|
||
|
|
position: StrokePosition::Inside,
|
||
|
|
fill_type: StrokeFillType::Color,
|
||
|
|
blend_mode: "Normal".to_string(),
|
||
|
|
opacity: 1.0,
|
||
|
|
size: 12.0,
|
||
|
|
color: [255, 220, 0, 255],
|
||
|
|
})
|
||
|
|
),
|
||
|
|
(
|
||
|
|
"fx_stroke_center",
|
||
|
|
create_layer_with_effect("Stroke Center", w, h, LayerEffect::Stroke {
|
||
|
|
enabled: true,
|
||
|
|
position: StrokePosition::Center,
|
||
|
|
fill_type: StrokeFillType::Color,
|
||
|
|
blend_mode: "Normal".to_string(),
|
||
|
|
opacity: 1.0,
|
||
|
|
size: 12.0,
|
||
|
|
color: [255, 220, 0, 255],
|
||
|
|
})
|
||
|
|
),
|
||
|
|
(
|
||
|
|
"fx_stroke_outside",
|
||
|
|
create_layer_with_effect("Stroke Outside", w, h, LayerEffect::Stroke {
|
||
|
|
enabled: true,
|
||
|
|
position: StrokePosition::Outside,
|
||
|
|
fill_type: StrokeFillType::Color,
|
||
|
|
blend_mode: "Normal".to_string(),
|
||
|
|
opacity: 1.0,
|
||
|
|
size: 12.0,
|
||
|
|
color: [255, 220, 0, 255],
|
||
|
|
})
|
||
|
|
),
|
||
|
|
];
|
||
|
|
|
||
|
|
// Generate PLAIN PSDs (no effects) — user will apply effects manually in Photopea
|
||
|
|
let effect_labels: Vec<&str> = tests.iter().map(|(n, _)| *n).collect();
|
||
|
|
for name in &effect_labels {
|
||
|
|
let layer = create_layer(name, w, h, 120, 140, 200);
|
||
|
|
let composited = layer.pixels.clone();
|
||
|
|
let plain_path = format!("{}/plain_{}.psd", out_dir, name);
|
||
|
|
if let Err(e) = hcie_psd::save_psd(&[layer.clone()], w, h, &composited, Path::new(&plain_path)) {
|
||
|
|
eprintln!("Failed to save {}: {}", name, e);
|
||
|
|
} else {
|
||
|
|
println!("Generated: {}", plain_path);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Also generate baseline with no effects
|
||
|
|
let baseline = create_layer("Baseline", w, h, 100, 180, 140);
|
||
|
|
let composited = baseline.pixels.clone();
|
||
|
|
let path = format!("{}/fx_baseline.psd", out_dir);
|
||
|
|
if let Err(e) = hcie_psd::save_psd(&[baseline.clone()], w, h, &composited, Path::new(&path)) {
|
||
|
|
eprintln!("Failed to save fx_baseline: {}", e);
|
||
|
|
} else {
|
||
|
|
println!("Generated: {}", path);
|
||
|
|
}
|
||
|
|
let png_path = format!("{}/fx_baseline.png", out_dir);
|
||
|
|
save_png(&png_path, &composited, w, h);
|
||
|
|
println!(" PNG: {}", png_path);
|
||
|
|
}
|