This commit is contained in:
2026-07-09 02:59:53 +03:00
commit 7ace048d94
817 changed files with 234289 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
[package]
name = "panel-tuner"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
+92
View File
@@ -0,0 +1,92 @@
use std::path::{Path, PathBuf};
use std::process::Command;
#[derive(Debug, serde::Deserialize)]
struct Config {
target: PathBuf,
panel: String,
output_dir: PathBuf,
gui_binary: PathBuf,
/// Each parameter is a list of values to try.
parameters: Vec<(String, Vec<f32>)>,
}
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() != 2 {
eprintln!("Usage: panel-tuner <config.json>");
std::process::exit(1);
}
let config: Config = serde_json::from_reader(
std::fs::File::open(&args[1]).expect("Failed to open config"),
).expect("Failed to parse config");
std::fs::create_dir_all(&config.output_dir).ok();
let mut best: Option<(Vec<f32>, f64, PathBuf)> = None;
let combinations = cartesian_product(&config.parameters.iter().map(|p| p.1.clone()).collect::<Vec<_>>());
for (idx, values) in combinations.iter().enumerate() {
let out = config.output_dir.join(format!("capture_{}.png", idx));
let mut cmd = Command::new(&config.gui_binary);
for (name, value) in config.parameters.iter().map(|p| p.0.clone()).zip(values.iter()) {
cmd.env(format!("HCIE_TUNER_{}", name.to_uppercase()), value.to_string());
}
cmd.arg("--screenshot-panel")
.arg(&config.panel)
.arg(&out);
let status = cmd.status().expect("Failed to run GUI binary");
if !status.success() {
eprintln!("Run {} failed: {:?}", idx, status);
continue;
}
let diff_out = config.output_dir.join(format!("diff_{}.png", idx));
let mae = run_diff(&config.target, &out, &diff_out);
eprintln!(
"Run {}: params={:?} MAE={:.6}",
idx, values, mae
);
if best.as_ref().map_or(true, |b| mae < b.1) {
best = Some((values.clone(), mae, out.clone()));
}
}
if let Some((values, mae, out)) = best {
println!("Best params: {:?}", values);
println!("Best MAE: {:.6}", mae);
println!("Best capture: {}", out.display());
} else {
eprintln!("No successful run");
std::process::exit(1);
}
}
fn cartesian_product(lists: &[Vec<f32>]) -> Vec<Vec<f32>> {
let mut result = vec![vec![]];
for list in lists {
let mut new_result = Vec::new();
for item in list {
for prefix in &result {
let mut v = prefix.clone();
v.push(*item);
new_result.push(v);
}
}
result = new_result;
}
result
}
fn run_diff(target: &Path, actual: &Path, diff: &Path) -> f64 {
let out = Command::new("target/debug/screenshot-diff")
.arg(target)
.arg(actual)
.arg("--save-diff")
.arg(diff)
.output()
.expect("Failed to run screenshot-diff");
let stdout = String::from_utf8_lossy(&out.stdout);
stdout.trim().parse().expect("Invalid MAE output")
}
+7
View File
@@ -0,0 +1,7 @@
[package]
name = "screenshot-diff"
version = "0.1.0"
edition = "2021"
[dependencies]
image = { workspace = true }
+54
View File
@@ -0,0 +1,54 @@
use std::path::PathBuf;
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 3 {
eprintln!("Usage: screenshot-diff <target.png> <actual.png> [--save-diff <diff.png>]");
std::process::exit(1);
}
let target_path = PathBuf::from(&args[1]);
let actual_path = PathBuf::from(&args[2]);
let save_diff = args.iter().position(|a| a == "--save-diff").and_then(|i| args.get(i + 1)).map(PathBuf::from);
let target = image::open(&target_path).expect("Failed to open target").to_rgba8();
let actual = image::open(&actual_path).expect("Failed to open actual").to_rgba8();
let (w, h) = (target.width().min(actual.width()), target.height().min(actual.height()));
if w == 0 || h == 0 {
eprintln!("One of the images has zero size");
std::process::exit(1);
}
let mut total_err: f64 = 0.0;
let mut max_err: u32 = 0;
let mut diff = image::RgbaImage::new(w, h);
for y in 0..h {
for x in 0..w {
let t = target.get_pixel(x, y);
let a = actual.get_pixel(x, y);
let err = (0..4)
.map(|c| (t[c] as i32 - a[c] as i32).unsigned_abs() as u32)
.sum::<u32>();
total_err += err as f64;
max_err = max_err.max(err);
let e = (err / 4).min(255) as u8;
diff.put_pixel(x, y, image::Rgba([e, e, e, 255]));
}
}
let pixel_count = (w * h) as f64;
let mae_per_pixel = total_err / (pixel_count * 4.0); // per channel per pixel
let mae_total = total_err / pixel_count;
println!("{:.6}", mae_per_pixel);
eprintln!("Resolution: {}x{}", w, h);
eprintln!("MAE per channel per pixel: {:.6}", mae_per_pixel);
eprintln!("MAE per pixel (all channels): {:.2}", mae_total);
eprintln!("Max channel error: {}", max_err);
if let Some(diff_path) = save_diff {
diff.save(&diff_path).expect("Failed to save diff image");
eprintln!("Diff saved: {}", diff_path.display());
}
}