This commit is contained in:
Sweetbread 2025-04-06 18:08:22 +03:00
parent 33d6bb64e3
commit c7441349a1
3 changed files with 175 additions and 34 deletions

1
Cargo.lock generated
View File

@ -429,6 +429,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"gtk4", "gtk4",
"image", "image",
"rayon",
] ]
[[package]] [[package]]

View File

@ -6,6 +6,7 @@ edition = "2021"
[dependencies] [dependencies]
gtk = { package = "gtk4", version = "0.9.6", features=["v4_10"] } gtk = { package = "gtk4", version = "0.9.6", features=["v4_10"] }
image = "0.24" image = "0.24"
rayon = "1.8"
# gtk4 = "0.9.6" # gtk4 = "0.9.6"
# show-image = "0.14.0" # show-image = "0.14.0"
# image2 = "1.9.2" # image2 = "1.9.2"

View File

@ -1,13 +1,14 @@
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, Button, FileDialog, Picture, Scale, Box as GtkBox}; use gtk::{Application, ApplicationWindow, Button, FileDialog, Picture, Scale, Box as GtkBox, ComboBoxText};
use image::{DynamicImage, ImageBuffer, RgbImage}; use image::{DynamicImage, ImageBuffer, RgbImage, Rgb, imageops};
use rayon::prelude::*;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::rc::Rc; use std::rc::Rc;
use std::cell::RefCell; use std::cell::RefCell;
fn main() { fn main() {
let app = Application::builder() let app = Application::builder()
.application_id("ru.risdeveau.imageeditor") .application_id("ru.risdeveau.imagefilters")
.build(); .build();
app.connect_activate(|app| { app.connect_activate(|app| {
@ -20,33 +21,148 @@ fn main() {
fn build_ui(app: &Application) { fn build_ui(app: &Application) {
let window = ApplicationWindow::builder() let window = ApplicationWindow::builder()
.application(app) .application(app)
.title("Image Editor") .title("Image Filters")
.default_width(800) .default_width(800)
.default_height(600) .default_height(600)
.build(); .build();
window.set_resizable(false); window.set_resizable(false);
let container = GtkBox::new(gtk::Orientation::Vertical, 5); let container = GtkBox::new(gtk::Orientation::Vertical, 5);
let open_button = Button::with_label("Open Image"); let open_button = Button::with_label("Open Image");
let picture = Picture::new(); let picture = Picture::new();
let brightness_slider = Scale::with_range(gtk::Orientation::Horizontal, -100.0, 100.0, 1.0); 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 filter_combo = ComboBoxText::new();
let image_data = Rc::new(RefCell::new(None::<(PathBuf, ImageBuffer<image::Rgb<u8>, Vec<u8>>)>)); 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() { for pixel in image.pixels_mut() {
pixel.0 = [ pixel[0] = (pixel[0] as f32 * kr).min(255.0).max(0.0) as u8;
(pixel.0[0] as i16 + brightness as i16).clamp(0, 255) as u8, pixel[1] = (pixel[1] as f32 * kg).min(255.0).max(0.0) as u8;
(pixel.0[1] as i16 + brightness as i16).clamp(0, 255) as u8, pixel[2] = (pixel[2] as f32 * kb).min(255.0).max(0.0) as u8;
(pixel.0[2] as i16 + brightness as i16).clamp(0, 255) 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 window = window.clone();
let image_data = image_data.clone(); let image_data = image_data.clone();
@ -74,13 +190,12 @@ fn build_ui(app: &Application) {
let mut rgb_img = img.to_rgb8(); let mut rgb_img = img.to_rgb8();
*image_data.borrow_mut() = Some((path.to_path_buf(), rgb_img.clone())); *image_data.borrow_mut() = Some((path.to_path_buf(), rgb_img.clone()));
// Сохраняем временную копию для отображения let temp_path = Path::new("/tmp/current_image.png");
let temp_path = Path::new("/tmp/gtk_temp_img.png"); if rgb_img.save(&temp_path).is_ok() {
if rgb_img.save(temp_path).is_ok() {
picture.set_filename(Some(temp_path)); picture.set_filename(Some(temp_path));
} }
} }
Err(e) => eprintln!("Failed to load image: {}", e), Err(e) => eprintln!("Image load error: {}", e),
} }
} }
} }
@ -89,25 +204,48 @@ fn build_ui(app: &Application) {
}); });
} }
// Регулировка яркости
{ {
let image_data = image_data.clone(); let image_data = image_data.clone();
let picture = picture.clone(); let picture = picture.clone();
let filter_combo = filter_combo.clone();
brightness_slider.connect_value_changed(move |slider| { brightness_slider.connect_value_changed(move |slider| {
if let Some((path, ref mut image)) = image_data.borrow_mut().as_mut() { if let Some((path, ref mut original_img)) = image_data.borrow_mut().as_mut() {
let value = slider.value() as i8; 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 Ok(original) = image::open(path) {
let mut edited = original.to_rgb8();
edit_image(&mut edited, value);
// Сохраняем и отображаем if let Some(active_filter) = filter_combo.active_text() {
let temp_path = Path::new("/tmp/gtk_temp_img.png"); apply_filter(&mut edited_img, &active_filter);
if edited.save(temp_path).is_ok() { }
let temp_path = Path::new("/tmp/current_image.png");
if edited_img.save(&temp_path).is_ok() {
picture.set_filename(Some(temp_path)); 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(&open_button);
container.append(&picture); container.append(&picture);
container.append(&brightness_slider); container.append(&brightness_slider);
container.append(&filter_combo);
window.set_child(Some(&container)); window.set_child(Some(&container));
window.show(); window.show();
} }