From c7441349a1dd3e36dd46b784ec847fb494c9dbd4 Mon Sep 17 00:00:00 2001 From: Sweetbread Date: Sun, 6 Apr 2025 18:08:22 +0300 Subject: [PATCH] wip --- Cargo.lock | 1 + Cargo.toml | 1 + src/main.rs | 207 +++++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 175 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index edc5bf7..585ff47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -429,6 +429,7 @@ version = "0.1.0" dependencies = [ "gtk4", "image", + "rayon", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d70b85c..a635885 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] gtk = { package = "gtk4", version = "0.9.6", features=["v4_10"] } image = "0.24" +rayon = "1.8" # gtk4 = "0.9.6" # show-image = "0.14.0" # image2 = "1.9.2" diff --git a/src/main.rs b/src/main.rs index 0e767a1..4c52839 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,14 @@ use gtk::prelude::*; -use gtk::{Application, ApplicationWindow, Button, FileDialog, Picture, Scale, Box as GtkBox}; -use image::{DynamicImage, ImageBuffer, RgbImage}; +use gtk::{Application, ApplicationWindow, Button, FileDialog, Picture, Scale, Box as GtkBox, ComboBoxText}; +use image::{DynamicImage, ImageBuffer, RgbImage, Rgb, imageops}; +use rayon::prelude::*; use std::path::{Path, PathBuf}; use std::rc::Rc; use std::cell::RefCell; fn main() { let app = Application::builder() - .application_id("ru.risdeveau.imageeditor") + .application_id("ru.risdeveau.imagefilters") .build(); app.connect_activate(|app| { @@ -20,33 +21,148 @@ fn main() { fn build_ui(app: &Application) { let window = ApplicationWindow::builder() .application(app) - .title("Image Editor") + .title("Image Filters") .default_width(800) .default_height(600) .build(); window.set_resizable(false); let container = GtkBox::new(gtk::Orientation::Vertical, 5); + let open_button = Button::with_label("Open Image"); let picture = Picture::new(); let brightness_slider = Scale::with_range(gtk::Orientation::Horizontal, -100.0, 100.0, 1.0); - brightness_slider.set_value(0.); + brightness_slider.set_value(0.0); - // Храним путь и оригинальное изображение - let image_data = Rc::new(RefCell::new(None::<(PathBuf, ImageBuffer, Vec>)>)); + let filter_combo = ComboBoxText::new(); + filter_combo.append_text("Original"); + filter_combo.append_text("Gray World"); + filter_combo.append_text("Histogram Stretch"); + filter_combo.append_text("Emboss"); + filter_combo.append_text("Blur"); + filter_combo.set_active(Some(0)); + + let image_data = Rc::new(RefCell::new(None::<(PathBuf, RgbImage)>)); + + fn parallel_brightness(image: &mut RgbImage, value: i16) { + image.par_iter_mut().for_each(|p| { + *p = p.saturating_add_signed(value.try_into().unwrap()); + }); + } + + fn gray_world(image: &mut RgbImage) { + let (width, height) = image.dimensions(); + let total_pixels = (width * height) as f32; + + let mut sum_r = 0.0; + let mut sum_g = 0.0; + let mut sum_b = 0.0; + + for pixel in image.pixels() { + sum_r += pixel[0] as f32; + sum_g += pixel[1] as f32; + sum_b += pixel[2] as f32; + } + + let avg_r = sum_r / total_pixels; + let avg_g = sum_g / total_pixels; + let avg_b = sum_b / total_pixels; + + let avg = (avg_r + avg_g + avg_b) / 3.0; + + let kr = avg / avg_r; + let kg = avg / avg_g; + let kb = avg / avg_b; - // Функция для редактирования изображения - fn edit_image(image: &mut RgbImage, brightness: i8) { for pixel in image.pixels_mut() { - pixel.0 = [ - (pixel.0[0] as i16 + brightness as i16).clamp(0, 255) as u8, - (pixel.0[1] as i16 + brightness as i16).clamp(0, 255) as u8, - (pixel.0[2] as i16 + brightness as i16).clamp(0, 255) as u8, - ]; + pixel[0] = (pixel[0] as f32 * kr).min(255.0).max(0.0) as u8; + pixel[1] = (pixel[1] as f32 * kg).min(255.0).max(0.0) as u8; + pixel[2] = (pixel[2] as f32 * kb).min(255.0).max(0.0) as u8; + } + } + + fn histogram_stretch(image: &mut RgbImage) { + let (width, height) = image.dimensions(); + let total_pixels = width * height; + + if total_pixels == 0 { + return; + } + + let mut min_r = 255; + let mut max_r = 0; + let mut min_g = 255; + let mut max_g = 0; + let mut min_b = 255; + let mut max_b = 0; + + for pixel in image.pixels() { + min_r = min_r.min(pixel[0]); + max_r = max_r.max(pixel[0]); + min_g = min_g.min(pixel[1]); + max_g = max_g.max(pixel[1]); + min_b = min_b.min(pixel[2]); + max_b = max_b.max(pixel[2]); + } + + if min_r == max_r && min_g == max_g && min_b == max_b { + return; + } + + for pixel in image.pixels_mut() { + pixel[0] = ((pixel[0] - min_r) as f32 * 255.0 / (max_r - min_r) as f32).round() as u8; + pixel[1] = ((pixel[1] - min_g) as f32 * 255.0 / (max_g - min_g) as f32).round() as u8; + pixel[2] = ((pixel[2] - min_b) as f32 * 255.0 / (max_b - min_b) as f32).round() as u8; + } + } + + fn emboss(image: &mut RgbImage) { + let kernel = [ + [-2.0, -1.0, 0.0], + [-1.0, 1.0, 1.0], + [ 0.0, 1.0, 2.0] + ]; + + let (width, height) = image.dimensions(); + let mut new_image = image.clone(); + + for y in 1..height-1 { + for x in 1..width-1 { + let mut r = 0.0; + let mut g = 0.0; + let mut b = 0.0; + + for ky in 0..3 { + for kx in 0..3 { + let px = image.get_pixel(x + kx - 1, y + ky - 1); + let weight = kernel[ky as usize][kx as usize]; + r += px[0] as f32 * weight; + g += px[1] as f32 * weight; + b += px[2] as f32 * weight; + } + } + + r = (r + 128.0).clamp(0.0, 255.0); + g = (g + 128.0).clamp(0.0, 255.0); + b = (b + 128.0).clamp(0.0, 255.0); + + new_image.put_pixel(x, y, Rgb([r as u8, g as u8, b as u8])); + } + } + + *image = new_image; + } + + fn apply_filter(image: &mut RgbImage, filter_name: &str) { + match filter_name { + "Gray World" => gray_world(image), + "Histogram Stretch" => histogram_stretch(image), + "Emboss" => emboss(image), + "Blur" => *image = imageops::blur(image, 3.0), + _ => {} } } - // Открытие изображения { let window = window.clone(); let image_data = image_data.clone(); @@ -73,14 +189,13 @@ fn build_ui(app: &Application) { Ok(img) => { let mut rgb_img = img.to_rgb8(); *image_data.borrow_mut() = Some((path.to_path_buf(), rgb_img.clone())); - - // Сохраняем временную копию для отображения - let temp_path = Path::new("/tmp/gtk_temp_img.png"); - if rgb_img.save(temp_path).is_ok() { + + let temp_path = Path::new("/tmp/current_image.png"); + if rgb_img.save(&temp_path).is_ok() { picture.set_filename(Some(temp_path)); } } - Err(e) => eprintln!("Failed to load image: {}", e), + Err(e) => eprintln!("Image load error: {}", e), } } } @@ -89,24 +204,47 @@ fn build_ui(app: &Application) { }); } - // Регулировка яркости { let image_data = image_data.clone(); let picture = picture.clone(); + let filter_combo = filter_combo.clone(); brightness_slider.connect_value_changed(move |slider| { - if let Some((path, ref mut image)) = image_data.borrow_mut().as_mut() { - let value = slider.value() as i8; - - // Редактируем копию оригинального изображения - if let Ok(original) = image::open(path) { - let mut edited = original.to_rgb8(); - edit_image(&mut edited, value); - - // Сохраняем и отображаем - let temp_path = Path::new("/tmp/gtk_temp_img.png"); - if edited.save(temp_path).is_ok() { - picture.set_filename(Some(temp_path)); - } + if let Some((path, ref mut original_img)) = image_data.borrow_mut().as_mut() { + let mut edited_img = original_img.clone(); + let brightness_value = (slider.value() * 2.55) as i16; + + parallel_brightness(&mut edited_img, brightness_value); + + if let Some(active_filter) = filter_combo.active_text() { + apply_filter(&mut edited_img, &active_filter); + } + + let temp_path = Path::new("/tmp/current_image.png"); + if edited_img.save(&temp_path).is_ok() { + picture.set_filename(Some(temp_path)); + } + } + }); + } + + { + let image_data = image_data.clone(); + let picture = picture.clone(); + let brightness_slider = brightness_slider.clone(); + filter_combo.connect_changed(move |combo| { + if let Some((path, ref mut original_img)) = image_data.borrow_mut().as_mut() { + let mut edited_img = original_img.clone(); + let brightness_value = (brightness_slider.value() * 2.55) as i16; + + parallel_brightness(&mut edited_img, brightness_value); + + if let Some(active_filter) = combo.active_text() { + apply_filter(&mut edited_img, &active_filter); + } + + let temp_path = Path::new("/tmp/current_image.png"); + if edited_img.save(&temp_path).is_ok() { + picture.set_filename(Some(temp_path)); } } }); @@ -115,6 +253,7 @@ fn build_ui(app: &Application) { container.append(&open_button); container.append(&picture); container.append(&brightness_slider); + container.append(&filter_combo); window.set_child(Some(&container)); window.show(); }