2026-07-09 02:59:53 +03:00
|
|
|
#![allow(dead_code, unused_imports, unused_variables, unused_macros)]
|
|
|
|
|
/// Analyze bevel emboss MAE breakdown by style, shape, direction, soften, depth, size.
|
|
|
|
|
///
|
|
|
|
|
/// Usage:
|
|
|
|
|
/// cargo run --example analyze_bevel_emboss -p hcie-io
|
|
|
|
|
|
|
|
|
|
const GT_DIR: &str = "_tmp/bevel_emboss_png_set";
|
|
|
|
|
const CANVAS: u32 = 256;
|
|
|
|
|
const MARGIN: u32 = 64;
|
|
|
|
|
const RESULTS_FILE: &str = "bevel_emboss_analysis.txt";
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
struct Sample {
|
|
|
|
|
name: String,
|
|
|
|
|
shape: String,
|
|
|
|
|
style: String,
|
|
|
|
|
depth: f32,
|
|
|
|
|
size: f32,
|
|
|
|
|
soften: f32,
|
|
|
|
|
direction: String,
|
|
|
|
|
altitude: f32,
|
|
|
|
|
ref_pixels: Vec<u8>,
|
|
|
|
|
content_mask: Vec<bool>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
|
let best_lb = 40i32;
|
|
|
|
|
let best_hl = 0.50f32;
|
|
|
|
|
let best_sh = 2.00f32;
|
|
|
|
|
|
|
|
|
|
let entries: Vec<_> = std::fs::read_dir(GT_DIR)
|
|
|
|
|
.unwrap_or_else(|e| panic!("cannot read {}: {}", GT_DIR, e))
|
|
|
|
|
.filter_map(|e| e.ok())
|
|
|
|
|
.filter(|e| {
|
|
|
|
|
let s = e.file_name();
|
|
|
|
|
let s = s.to_string_lossy();
|
|
|
|
|
s.starts_with("be_") && s.ends_with(".png")
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
let mut samples: Vec<Sample> = Vec::new();
|
|
|
|
|
for entry in &entries {
|
|
|
|
|
let path = entry.path();
|
|
|
|
|
let stem = path.file_stem().unwrap().to_string_lossy().to_string();
|
|
|
|
|
let params = match parse_filename(&stem) {
|
|
|
|
|
Some(p) => p,
|
|
|
|
|
None => { eprintln!(" SKIP: {}", stem); continue; }
|
|
|
|
|
};
|
|
|
|
|
let (shape, style, depth, size, soften, direction, altitude, _technique) = params;
|
|
|
|
|
let img = image::open(&path).expect("open png");
|
|
|
|
|
let rgba = img.to_rgba8();
|
|
|
|
|
let raw = rgba.into_raw();
|
|
|
|
|
let npix = (CANVAS * CANVAS) as usize;
|
|
|
|
|
let mut content_mask = vec![false; npix];
|
|
|
|
|
for i in 0..npix {
|
|
|
|
|
content_mask[i] = raw[i * 4 + 3] > 0;
|
|
|
|
|
}
|
|
|
|
|
samples.push(Sample { name: stem, shape, style, depth, size, soften, direction, altitude, ref_pixels: raw, content_mask });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
println!("Loaded {} samples", samples.len());
|
|
|
|
|
|
|
|
|
|
let mut per_sample: Vec<(Sample, f64)> = Vec::new();
|
|
|
|
|
let mut buckets: std::collections::HashMap<String, (f64, u64)> = std::collections::HashMap::new();
|
|
|
|
|
|
|
|
|
|
for s in &samples {
|
|
|
|
|
let alpha = make_shape_alpha(&s.shape);
|
|
|
|
|
let style_enum = match s.style.as_str() {
|
|
|
|
|
"emboss" => hcie_fx::types::BevelStyle::Emboss,
|
|
|
|
|
"outer" => hcie_fx::types::BevelStyle::OuterBevel,
|
|
|
|
|
"pillowemboss" => hcie_fx::types::BevelStyle::PillowEmboss,
|
|
|
|
|
_ => hcie_fx::types::BevelStyle::InnerBevel,
|
|
|
|
|
};
|
|
|
|
|
let dir_enum = match s.direction.as_str() {
|
|
|
|
|
"down" => hcie_fx::types::Direction::Down,
|
|
|
|
|
_ => hcie_fx::types::Direction::Up,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let (highlight, shadow) = hcie_fx::tuned::generate_bevel_tuned(
|
|
|
|
|
&alpha, CANVAS, CANVAS,
|
|
|
|
|
s.depth, s.size, s.soften, 120.0, s.altitude,
|
|
|
|
|
&dir_enum, &hcie_fx::types::Technique::Smooth, &style_enum,
|
2026-07-19 00:55:40 +03:00
|
|
|
best_lb, best_hl, best_sh, 1.0, 1.0,
|
2026-07-09 02:59:53 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let px = npix();
|
|
|
|
|
let mut out = vec![0u8; px * 4];
|
|
|
|
|
// Base shape: black fill for both Inner Bevel and Emboss.
|
|
|
|
|
// Photoshop Emboss applies highlights/shadows on top of the original fill.
|
|
|
|
|
for i in 0..px {
|
|
|
|
|
if alpha[i] > 0 {
|
|
|
|
|
out[i * 4] = 0;
|
|
|
|
|
out[i * 4 + 1] = 0;
|
|
|
|
|
out[i * 4 + 2] = 0;
|
|
|
|
|
out[i * 4 + 3] = alpha[i];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for i in 0..px {
|
|
|
|
|
let sa = highlight[i * 4 + 3];
|
|
|
|
|
if sa > 0 {
|
|
|
|
|
let dst = [out[i * 4], out[i * 4 + 1], out[i * 4 + 2], out[i * 4 + 3]];
|
|
|
|
|
let src = [highlight[i * 4], highlight[i * 4 + 1], highlight[i * 4 + 2], sa];
|
|
|
|
|
let blended = hcie_blend::blend_pixels(dst, src, hcie_blend::BlendMode::Screen, 1.0);
|
|
|
|
|
out[i * 4..i * 4 + 4].copy_from_slice(&blended);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for i in 0..px {
|
|
|
|
|
let sa = shadow[i * 4 + 3];
|
|
|
|
|
if sa > 0 {
|
|
|
|
|
let dst = [out[i * 4], out[i * 4 + 1], out[i * 4 + 2], out[i * 4 + 3]];
|
|
|
|
|
let src = [shadow[i * 4], shadow[i * 4 + 1], shadow[i * 4 + 2], sa];
|
|
|
|
|
let blended = hcie_blend::blend_pixels(dst, src, hcie_blend::BlendMode::Multiply, 1.0);
|
|
|
|
|
out[i * 4..i * 4 + 4].copy_from_slice(&blended);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut diff_sum = 0.0f64;
|
|
|
|
|
let mut content_pixels = 0u64;
|
|
|
|
|
for i in 0..px {
|
|
|
|
|
if !s.content_mask[i] { continue; }
|
|
|
|
|
content_pixels += 1;
|
|
|
|
|
for c in 0..4 {
|
|
|
|
|
diff_sum += (out[i * 4 + c] as i32 - s.ref_pixels[i * 4 + c] as i32).abs() as f64;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let mae = if content_pixels > 0 { diff_sum / content_pixels as f64 / 4.0 } else { 0.0 };
|
|
|
|
|
per_sample.push((s.clone(), mae));
|
|
|
|
|
|
|
|
|
|
add_bucket(&mut buckets, format!("style={}", s.style), mae);
|
|
|
|
|
add_bucket(&mut buckets, format!("shape={}", s.shape), mae);
|
|
|
|
|
add_bucket(&mut buckets, format!("dir={}", s.direction), mae);
|
|
|
|
|
add_bucket(&mut buckets, format!("soften={}", s.soften), mae);
|
|
|
|
|
add_bucket(&mut buckets, format!("depth={}", s.depth), mae);
|
|
|
|
|
add_bucket(&mut buckets, format!("size={}", s.size), mae);
|
|
|
|
|
add_bucket(&mut buckets, format!("altitude={}", s.altitude), mae);
|
|
|
|
|
add_bucket(&mut buckets, "all".to_string(), mae);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
per_sample.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
|
|
|
|
|
|
|
|
|
let mut out_str = String::new();
|
|
|
|
|
out_str.push_str(&format!("Bevel & Emboss analysis (lb={}, hl={:.2}, sh={:.2})\n", best_lb, best_hl, best_sh));
|
|
|
|
|
out_str.push_str("=== WORST SAMPLES ===\n");
|
|
|
|
|
for (s, mae) in per_sample.iter().take(20) {
|
|
|
|
|
out_str.push_str(&format!(" MAE={:.2} {} (style={} shape={} d={} sz={} sf={} dir={} alt={})\n",
|
|
|
|
|
mae, s.name, s.style, s.shape, s.depth, s.size, s.soften, s.direction, s.altitude));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out_str.push_str("\n=== BUCKET AVERAGES ===\n");
|
|
|
|
|
let mut bucket_list: Vec<(&String, &(f64, u64))> = buckets.iter().collect();
|
|
|
|
|
bucket_list.sort_by(|a, b| a.0.cmp(b.0));
|
|
|
|
|
for (key, (sum, count)) in bucket_list {
|
|
|
|
|
let avg = sum / *count as f64;
|
|
|
|
|
out_str.push_str(&format!(" {:20} count={:4} avg MAE={:.4}\n", key, count, avg));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
println!("{}", out_str);
|
|
|
|
|
std::fs::write(RESULTS_FILE, out_str).expect("write analysis");
|
|
|
|
|
println!("Wrote {}", RESULTS_FILE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn add_bucket(buckets: &mut std::collections::HashMap<String, (f64, u64)>, key: String, mae: f64) {
|
|
|
|
|
let entry = buckets.entry(key).or_insert((0.0, 0));
|
|
|
|
|
entry.0 += mae;
|
|
|
|
|
entry.1 += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn make_shape_alpha(shape: &str) -> Vec<u8> {
|
|
|
|
|
let n = (CANVAS * CANVAS) as usize;
|
|
|
|
|
let mut a = vec![0u8; n];
|
|
|
|
|
let cx = CANVAS / 2;
|
|
|
|
|
let cy = CANVAS / 2;
|
|
|
|
|
|
|
|
|
|
for y in 0..CANVAS {
|
|
|
|
|
for x in 0..CANVAS {
|
|
|
|
|
let inside = match shape {
|
|
|
|
|
"rect" => x >= 64 && x < CANVAS - 64 && y >= 64 && y < CANVAS - 64,
|
|
|
|
|
"tall" => x >= 96 && x < CANVAS - 96 && y >= 32 && y < CANVAS - 32,
|
|
|
|
|
"wide" => x >= 32 && x < CANVAS - 32 && y >= 96 && y < CANVAS - 96,
|
|
|
|
|
"circle" => {
|
|
|
|
|
let dx = x as f32 - cx as f32;
|
|
|
|
|
let dy = y as f32 - cy as f32;
|
|
|
|
|
let r = (CANVAS / 2 - 64) as f32;
|
|
|
|
|
(dx * dx + dy * dy).sqrt() <= r
|
|
|
|
|
}
|
|
|
|
|
"triangle" => {
|
|
|
|
|
let dx = (x as i32 - cx as i32).abs();
|
|
|
|
|
let top = 64i32;
|
|
|
|
|
let bottom = (CANVAS - 64) as i32;
|
|
|
|
|
if (y as i32) < top || (y as i32) > bottom { false } else {
|
|
|
|
|
let y_span = ((y as i32) - top) as f32 / (bottom - top) as f32;
|
|
|
|
|
let half_width_at_y = (CANVAS - 128) as f32 / 2.0 * (1.0 - y_span);
|
|
|
|
|
(dx as f32) <= half_width_at_y
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"star" => {
|
|
|
|
|
let dx = x as f32 - cx as f32;
|
|
|
|
|
let dy = y as f32 - cy as f32;
|
|
|
|
|
let r = (dx * dx + dy * dy).sqrt();
|
|
|
|
|
if r > 96.0 || r < 20.0 { false } else {
|
|
|
|
|
let angle = dy.atan2(dx);
|
|
|
|
|
let sector = (angle * 5.0).cos();
|
|
|
|
|
let limit = 64.0 + sector * 32.0;
|
|
|
|
|
r <= limit
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"L" => {
|
|
|
|
|
(x >= 64 && x < 96 && y >= 64 && y < CANVAS - 64)
|
|
|
|
|
|| (x >= 64 && x < CANVAS - 64 && y >= CANVAS - 96 && y < CANVAS - 64)
|
|
|
|
|
}
|
|
|
|
|
"T" => {
|
|
|
|
|
(y >= 64 && y < 96 && x >= 64 && x < CANVAS - 64)
|
|
|
|
|
|| (x >= 112 && x < 144 && y >= 64 && y < CANVAS - 64)
|
|
|
|
|
}
|
|
|
|
|
"cross" => {
|
|
|
|
|
(x >= 112 && x < 144 && y >= 48 && y < CANVAS - 48)
|
|
|
|
|
|| (y >= 112 && y < 144 && x >= 48 && x < CANVAS - 48)
|
|
|
|
|
}
|
|
|
|
|
_ => false,
|
|
|
|
|
};
|
|
|
|
|
if inside {
|
|
|
|
|
a[(y * CANVAS + x) as usize] = 255;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
a
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn npix() -> usize { (CANVAS * CANVAS) as usize }
|
|
|
|
|
|
|
|
|
|
fn parse_filename(name: &str) -> Option<(String, String, f32, f32, f32, String, f32, String)> {
|
|
|
|
|
// be_{shape}_{style}_d{depth}_sz{size}_sf{soften}_{dir}_alt{altitude}_{technique}
|
|
|
|
|
let parts: Vec<&str> = name.split('_').collect();
|
|
|
|
|
if parts.len() < 9 || parts[0] != "be" { return None; }
|
|
|
|
|
let shape = parts[1].to_string();
|
|
|
|
|
let style = parts[2].to_string();
|
|
|
|
|
let depth = parts[3].strip_prefix('d')?.parse::<f32>().ok()?;
|
|
|
|
|
let size = parts[4].strip_prefix("sz")?.parse::<f32>().ok()?;
|
|
|
|
|
let soften = parts[5].strip_prefix("sf")?.parse::<f32>().ok()?;
|
|
|
|
|
let direction = parts[6].to_string();
|
|
|
|
|
let altitude = parts[7].strip_prefix("alt")?.parse::<f32>().ok()?;
|
|
|
|
|
let technique = parts[8].to_string();
|
|
|
|
|
Some((shape, style, depth, size, soften, direction, altitude, technique))
|
|
|
|
|
}
|