init
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
#![allow(deprecated)]
|
||||
//! Visual test GUI for the decoupled `hcie-filter` library using `egui`.
|
||||
//! Compiles only when running `cargo run --example filter_tester`.
|
||||
|
||||
use eframe::egui;
|
||||
use hcie_filter::{apply_filter, Layer};
|
||||
use serde_json::Value;
|
||||
|
||||
fn main() -> eframe::Result {
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([700.0, 500.0])
|
||||
.with_title("HCIE Filter Engine Visual Tester"),
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(
|
||||
"HCIE Filter Visual Tester",
|
||||
options,
|
||||
Box::new(|cc| Ok(Box::new(FilterTesterApp::new(cc)))),
|
||||
)
|
||||
}
|
||||
|
||||
struct FilterTesterApp {
|
||||
original: Vec<u8>,
|
||||
filtered: Vec<u8>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
selected_filter: String,
|
||||
|
||||
// Parameter values
|
||||
blur_radius: f32,
|
||||
brightness: f32,
|
||||
contrast: f32,
|
||||
mosaic_size: u32,
|
||||
pinch_radius: f32,
|
||||
pinch_amount: f32,
|
||||
twirl_radius: f32,
|
||||
twirl_angle: f32,
|
||||
|
||||
texture: Option<egui::TextureHandle>,
|
||||
}
|
||||
|
||||
impl FilterTesterApp {
|
||||
fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||
let width = 256;
|
||||
let height = 256;
|
||||
let len = (width * height * 4) as usize;
|
||||
|
||||
// Generate a visual test pattern: A colorful gradient circle on checkerboard
|
||||
let mut original = vec![0u8; len];
|
||||
let cx = width as f32 / 2.0;
|
||||
let cy = height as f32 / 2.0;
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
let i = ((y * width + x) * 4) as usize;
|
||||
|
||||
// Checkerboard background
|
||||
let is_gray = ((x / 16) + (y / 16)) % 2 == 0;
|
||||
let bg = if is_gray { 180 } else { 220 };
|
||||
|
||||
// Circle gradient
|
||||
let dx = x as f32 - cx;
|
||||
let dy = y as f32 - cy;
|
||||
let dist = (dx * dx + dy * dy).sqrt();
|
||||
if dist < 90.0 {
|
||||
// Soft circle with color gradient
|
||||
let t = dist / 90.0;
|
||||
let alpha = if dist > 80.0 { ((90.0 - dist) / 10.0 * 255.0) as u8 } else { 255 };
|
||||
|
||||
original[i] = (t * 255.0) as u8; // Red gradient
|
||||
original[i + 1] = ((1.0 - t) * 255.0) as u8; // Green gradient
|
||||
original[i + 2] = 200; // Blue constant
|
||||
original[i + 3] = alpha;
|
||||
} else {
|
||||
original[i] = bg;
|
||||
original[i + 1] = bg;
|
||||
original[i + 2] = bg;
|
||||
original[i + 3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let filtered = original.clone();
|
||||
|
||||
let mut app = Self {
|
||||
original,
|
||||
filtered,
|
||||
width,
|
||||
height,
|
||||
selected_filter: "gaussian_blur".to_string(),
|
||||
blur_radius: 5.0,
|
||||
brightness: 0.0,
|
||||
contrast: 0.0,
|
||||
mosaic_size: 8,
|
||||
pinch_radius: 128.0,
|
||||
pinch_amount: 0.5,
|
||||
twirl_radius: 128.0,
|
||||
twirl_angle: 90.0,
|
||||
texture: None,
|
||||
};
|
||||
app.update_filter();
|
||||
app
|
||||
}
|
||||
|
||||
fn update_filter(&mut self) {
|
||||
// Reset filtered buffer to original
|
||||
self.filtered.copy_from_slice(&self.original);
|
||||
|
||||
// Build the JSON parameters based on selected filter
|
||||
let params_json = match self.selected_filter.as_str() {
|
||||
"gaussian_blur" => {
|
||||
format!("{{\"radius\": {}}}", self.blur_radius)
|
||||
}
|
||||
"brightness_contrast" => {
|
||||
format!("{{\"brightness\": {}, \"contrast\": {}}}", self.brightness, self.contrast)
|
||||
}
|
||||
"mosaic" => {
|
||||
format!("{{\"size\": {}}}", self.mosaic_size)
|
||||
}
|
||||
"pinch" => {
|
||||
format!("{{\"radius\": {}, \"amount\": {}, \"cx\": {}, \"cy\": {}}}", self.pinch_radius, self.pinch_amount, self.width / 2, self.height / 2)
|
||||
}
|
||||
"twirl" => {
|
||||
format!("{{\"radius\": {}, \"angle\": {}, \"cx\": {}, \"cy\": {}}}", self.twirl_radius, self.twirl_angle, self.width / 2, self.height / 2)
|
||||
}
|
||||
_ => "{}".to_string(),
|
||||
};
|
||||
|
||||
let params: Value = serde_json::from_str(¶ms_json).unwrap_or(Value::Object(serde_json::Map::new()));
|
||||
|
||||
// Create a temporary Layer, apply filter, copy pixels back
|
||||
let mut layer = Layer::from_rgba("preview", self.width, self.height, self.filtered.clone());
|
||||
apply_filter(&mut layer, &self.selected_filter, ¶ms);
|
||||
self.filtered = layer.pixels;
|
||||
|
||||
self.texture = None; // Invalidate texture cache
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for FilterTesterApp {
|
||||
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||
let ctx = ui.ctx().clone();
|
||||
let filters = [
|
||||
("gaussian_blur", "Gaussian Blur"),
|
||||
("brightness_contrast", "Brightness / Contrast"),
|
||||
("invert", "Invert (Instant)"),
|
||||
("grayscale", "Grayscale (Instant)"),
|
||||
("mosaic", "Mosaic / Pixelate"),
|
||||
("pinch", "Pinch Distort"),
|
||||
("twirl", "Twirl Distort"),
|
||||
];
|
||||
|
||||
// Ensure texture is loaded
|
||||
if self.texture.is_none() {
|
||||
let color_image = egui::ColorImage::from_rgba_unmultiplied(
|
||||
[self.width as usize, self.height as usize],
|
||||
&self.filtered,
|
||||
);
|
||||
self.texture = Some(ctx.load_texture("filtered_preview", color_image, Default::default()));
|
||||
}
|
||||
let texture = self.texture.as_ref().unwrap().clone();
|
||||
|
||||
egui::CentralPanel::default().show(&ctx, |ui| {
|
||||
ui.heading("HCIE Filter Engine Visual Tester");
|
||||
ui.add_space(10.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
// Left Column: Preview Image
|
||||
ui.vertical(|ui| {
|
||||
ui.image((texture.id(), texture.size_vec2()));
|
||||
});
|
||||
|
||||
ui.add_space(30.0);
|
||||
|
||||
// Right Column: Parameters and selection
|
||||
ui.vertical(|ui| {
|
||||
let mut changed = false;
|
||||
|
||||
// Filter Selector
|
||||
ui.label("Select Filter:");
|
||||
let current_label = filters.iter().find(|f| f.0 == self.selected_filter).map(|f| f.1).unwrap_or("Gaussian Blur");
|
||||
egui::ComboBox::from_id_source("filter_select")
|
||||
.selected_text(current_label)
|
||||
.show_ui(ui, |ui| {
|
||||
for &(id, label) in &filters {
|
||||
if ui.selectable_value(&mut self.selected_filter, id.to_string(), label).clicked() {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(20.0);
|
||||
ui.separator();
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Dynamic Sliders based on selected filter
|
||||
match self.selected_filter.as_str() {
|
||||
"gaussian_blur" => {
|
||||
ui.label("Blur Radius:");
|
||||
if ui.add(egui::Slider::new(&mut self.blur_radius, 0.1..=20.0)).changed() {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
"brightness_contrast" => {
|
||||
ui.label("Brightness:");
|
||||
if ui.add(egui::Slider::new(&mut self.brightness, -1.0..=1.0)).changed() {
|
||||
changed = true;
|
||||
}
|
||||
ui.add_space(10.0);
|
||||
ui.label("Contrast:");
|
||||
if ui.add(egui::Slider::new(&mut self.contrast, -1.0..=1.0)).changed() {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
"mosaic" => {
|
||||
ui.label("Block Size:");
|
||||
if ui.add(egui::Slider::new(&mut self.mosaic_size, 2..=32)).changed() {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
"pinch" => {
|
||||
ui.label("Pinch Radius:");
|
||||
if ui.add(egui::Slider::new(&mut self.pinch_radius, 10.0..=200.0)).changed() {
|
||||
changed = true;
|
||||
}
|
||||
ui.add_space(10.0);
|
||||
ui.label("Pinch Amount:");
|
||||
if ui.add(egui::Slider::new(&mut self.pinch_amount, -1.0..=1.0)).changed() {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
"twirl" => {
|
||||
ui.label("Twirl Radius:");
|
||||
if ui.add(egui::Slider::new(&mut self.twirl_radius, 10.0..=200.0)).changed() {
|
||||
changed = true;
|
||||
}
|
||||
ui.add_space(10.0);
|
||||
ui.label("Twirl Angle (Degrees):");
|
||||
if ui.add(egui::Slider::new(&mut self.twirl_angle, -360.0..=360.0)).changed() {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
ui.label("No parameters required for this filter.");
|
||||
}
|
||||
}
|
||||
|
||||
if changed {
|
||||
self.update_filter();
|
||||
}
|
||||
|
||||
ui.add_space(40.0);
|
||||
ui.colored_label(egui::Color32::LIGHT_GREEN, "✓ 19 Filters Rayon-parallelized");
|
||||
ui.colored_label(egui::Color32::LIGHT_GREEN, "✓ Decoupled from hcie-protocol");
|
||||
ui.colored_label(egui::Color32::LIGHT_GREEN, "✓ Under 1.5ms copy overhead on 4K");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user