Compare commits
No commits in common. "202f76b38b9bcb04505f4e4694a13cfe3ae8455d" and "33d6bb64e3dbb1c4d083e247142abbb9240758e0" have entirely different histories.
202f76b38b
...
33d6bb64e3
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -429,7 +429,6 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"gtk4",
|
"gtk4",
|
||||||
"image",
|
"image",
|
||||||
"rayon",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -6,7 +6,6 @@ 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"
|
||||||
|
251
src/main.rs
251
src/main.rs
@ -1,14 +1,13 @@
|
|||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Application, ApplicationWindow, Button, FileDialog, Picture, Scale, Box as GtkBox, ComboBoxText};
|
use gtk::{Application, ApplicationWindow, Button, FileDialog, Picture, Scale, Box as GtkBox};
|
||||||
use image::{DynamicImage, ImageBuffer, RgbImage, Rgb, imageops};
|
use image::{DynamicImage, ImageBuffer, RgbImage};
|
||||||
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.imagefilters")
|
.application_id("ru.risdeveau.imageeditor")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
app.connect_activate(|app| {
|
app.connect_activate(|app| {
|
||||||
@ -21,204 +20,33 @@ 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 Filters")
|
.title("Image Editor")
|
||||||
.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.0);
|
brightness_slider.set_value(0.);
|
||||||
|
|
||||||
let filter_combo = ComboBoxText::new();
|
// Храним путь и оригинальное изображение
|
||||||
filter_combo.append_text("Original");
|
let image_data = Rc::new(RefCell::new(None::<(PathBuf, ImageBuffer<image::Rgb<u8>, Vec<u8>>)>));
|
||||||
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) {
|
|
||||||
let value = value.clamp(-255, 255); // Ограничиваем диапазон
|
|
||||||
|
|
||||||
|
// Функция для редактирования изображения
|
||||||
|
fn edit_image(image: &mut RgbImage, brightness: i8) {
|
||||||
for pixel in image.pixels_mut() {
|
for pixel in image.pixels_mut() {
|
||||||
for channel in 0..3 {
|
pixel.0 = [
|
||||||
let new_val = pixel[channel] as i32 + value as i32;
|
(pixel.0[0] as i16 + brightness as i16).clamp(0, 255) as u8,
|
||||||
pixel[channel] = new_val.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,
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
for pixel in image.pixels_mut() {
|
|
||||||
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, mut max_r) = (255u8, 0u8);
|
|
||||||
let (mut min_g, mut max_g) = (255u8, 0u8);
|
|
||||||
let (mut min_b, mut max_b) = (255u8, 0u8);
|
|
||||||
|
|
||||||
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 == 0 && max_r == 255 &&
|
|
||||||
min_g == 0 && max_g == 255 &&
|
|
||||||
min_b == 0 && max_b == 255 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for y in 0..height {
|
|
||||||
for x in 0..width {
|
|
||||||
let mut pixel = *image.get_pixel(x, y);
|
|
||||||
|
|
||||||
if max_r != min_r {
|
|
||||||
pixel[0] = ((pixel[0] - min_r) as f32 * 255.0 / (max_r - min_r) as f32).clamp(0.0, 255.0) as u8;
|
|
||||||
}
|
|
||||||
if max_g != min_g {
|
|
||||||
pixel[1] = ((pixel[1] - min_g) as f32 * 255.0 / (max_g - min_g) as f32).clamp(0.0, 255.0) as u8;
|
|
||||||
}
|
|
||||||
if max_b != min_b {
|
|
||||||
pixel[2] = ((pixel[2] - min_b) as f32 * 255.0 / (max_b - min_b) as f32).clamp(0.0, 255.0) as u8;
|
|
||||||
}
|
|
||||||
|
|
||||||
image.put_pixel(x, y, pixel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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" => blur(image),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn blur(image: &mut RgbImage) {
|
|
||||||
let (width, height) = image.dimensions();
|
|
||||||
let mut buffer = image.clone();
|
|
||||||
|
|
||||||
let kernel = [
|
|
||||||
[1.0, 4.0, 6.0, 4.0, 1.0],
|
|
||||||
[4.0, 16.0, 24.0, 16.0, 4.0],
|
|
||||||
[6.0, 24.0, 36.0, 24.0, 6.0],
|
|
||||||
[4.0, 16.0, 24.0, 16.0, 4.0],
|
|
||||||
[1.0, 4.0, 6.0, 4.0, 1.0]
|
|
||||||
];
|
|
||||||
let kernel_sum: f32 = 256.0;
|
|
||||||
|
|
||||||
for y in 2..height-2 {
|
|
||||||
for x in 2..width-2 {
|
|
||||||
let mut r = 0.0;
|
|
||||||
let mut g = 0.0;
|
|
||||||
let mut b = 0.0;
|
|
||||||
|
|
||||||
for ky in 0..5 {
|
|
||||||
for kx in 0..5 {
|
|
||||||
let px = image.get_pixel(x + kx - 2, y + ky - 2);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let r_val = (r / kernel_sum).clamp(0.0, 255.0) as u8;
|
|
||||||
let g_val = (g / kernel_sum).clamp(0.0, 255.0) as u8;
|
|
||||||
let b_val = (b / kernel_sum).clamp(0.0, 255.0) as u8;
|
|
||||||
|
|
||||||
buffer.put_pixel(x, y, Rgb([r_val, g_val, b_val]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*image = buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let window = window.clone();
|
let window = window.clone();
|
||||||
let image_data = image_data.clone();
|
let image_data = image_data.clone();
|
||||||
@ -246,12 +74,13 @@ 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");
|
// Сохраняем временную копию для отображения
|
||||||
if rgb_img.save(&temp_path).is_ok() {
|
let temp_path = Path::new("/tmp/gtk_temp_img.png");
|
||||||
|
if rgb_img.save(temp_path).is_ok() {
|
||||||
picture.set_filename(Some(temp_path));
|
picture.set_filename(Some(temp_path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => eprintln!("Image load error: {}", e),
|
Err(e) => eprintln!("Failed to load image: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -260,48 +89,25 @@ 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 original_img)) = image_data.borrow_mut().as_mut() {
|
if let Some((path, ref mut image)) = image_data.borrow_mut().as_mut() {
|
||||||
let mut edited_img = original_img.clone();
|
let value = slider.value() as i8;
|
||||||
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() {
|
// Сохраняем и отображаем
|
||||||
apply_filter(&mut edited_img, &active_filter);
|
let temp_path = Path::new("/tmp/gtk_temp_img.png");
|
||||||
}
|
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -309,7 +115,6 @@ 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();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user