Compare commits

..

7 Commits

Author SHA1 Message Date
Sweetbread 7d2ea3bf4b New filters 2025-04-15 20:04:34 +03:00
Sweetbread 202f76b38b fix 2025-04-07 21:32:42 +03:00
Sweetbread c7441349a1 wip 2025-04-07 19:12:51 +03:00
Sweetbread 33d6bb64e3 wip 2025-03-24 19:18:58 +03:00
Sweetbread c1ccad6229 tmp 2025-03-24 17:22:18 +03:00
Sweetbread 84099d4928 Open and image 2025-03-24 16:00:24 +03:00
Sweetbread ac17f705ba wip 2025-03-23 23:06:27 +03:00
5 changed files with 1077 additions and 1701 deletions
Generated
+488 -1657
View File
File diff suppressed because it is too large Load Diff
+7 -2
View File
@@ -4,5 +4,10 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
show-image = "0.14.0" gtk = { package = "gtk4", version = "0.9.6", features=["v4_10"] }
image2 = "1.9.2" image = "0.24"
rayon = "1.8"
rand = "0.9.0"
# gtk4 = "0.9.6"
# show-image = "0.14.0"
# image2 = "1.9.2"
+3
View File
@@ -11,10 +11,13 @@ with pkgs; mkShell rec {
libxkbcommon libxkbcommon
egl-wayland egl-wayland
vulkan-loader vulkan-loader
gtk4
gsettings-desktop-schemas
]; ];
shellHook = '' shellHook = ''
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${lib.makeLibraryPath (buildInputs) } export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${lib.makeLibraryPath (buildInputs) }
export XDG_DATA_DIRS=${pkgs.gtk4}/share/gsettings-schemas/${pkgs.gtk4.name}:$XDG_DATA_DIRS
''; '';
WINIT_UNIX_BACKEND="wayland"; WINIT_UNIX_BACKEND="wayland";
+413
View File
@@ -0,0 +1,413 @@
use image::{
Rgb, RgbImage
};
use rand;
use std::f64::consts::PI;
pub fn heatmap(image: &mut RgbImage) {
for pixel in image.pixels_mut() {
let intensity = (pixel[0] as u32 + pixel[1] as u32 + pixel[2] as u32) / 3;
if intensity < 64 {
pixel[0] = 0;
pixel[1] = 0;
pixel[2] = (intensity * 4) as u8;
} else if intensity < 128 {
pixel[0] = 0;
pixel[1] = ((intensity - 64) * 4) as u8;
pixel[2] = 255 - ((intensity - 64) * 4) as u8;
} else if intensity < 192 {
pixel[0] = ((intensity - 128) * 4) as u8;
pixel[1] = 255;
pixel[2] = 0;
} else {
pixel[0] = 255;
pixel[1] = 255 - ((intensity - 192) * 4) as u8;
pixel[2] = 0;
}
}
}
pub fn pixelate(image: &mut RgbImage, block_size: u32) {
let (width, height) = image.dimensions();
let mut buffer = image.clone();
for y in (0..height).step_by(block_size as usize) {
for x in (0..width).step_by(block_size as usize) {
let mut r = 0;
let mut g = 0;
let mut b = 0;
let mut count = 0;
for dy in 0..block_size.min(height-y) {
for dx in 0..block_size.min(width-x) {
let px = image.get_pixel(x + dx, y + dy);
r += px[0] as u32;
g += px[1] as u32;
b += px[2] as u32;
count += 1;
}
}
let avg_r = (r / count) as u8;
let avg_g = (g / count) as u8;
let avg_b = (b / count) as u8;
for dy in 0..block_size.min(height-y) {
for dx in 0..block_size.min(width-x) {
buffer.put_pixel(x + dx, y + dy, Rgb([avg_r, avg_g, avg_b]));
}
}
}
}
*image = buffer;
}
pub fn retro_effect(image: &mut RgbImage) {
let (width, height) = image.dimensions();
let center_x = width as f32 / 2.0;
let center_y = height as f32 / 2.0;
let max_dist = (center_x.powi(2) + center_y.powi(2)).sqrt();
for y in 0..height {
for x in 0..width {
let mut pixel = *image.get_pixel(x, y);
let dx = x as f32 - center_x;
let dy = y as f32 - center_y;
let dist = (dx.powi(2) + dy.powi(2)).sqrt();
let vignette = 1.0 - (dist / max_dist).powi(2) * 0.7;
pixel[0] = (pixel[0] as f32 * vignette).clamp(0.0, 255.0) as u8;
pixel[1] = (pixel[1] as f32 * vignette).clamp(0.0, 255.0) as u8;
pixel[2] = (pixel[2] as f32 * vignette).clamp(0.0, 255.0) as u8;
let noise = (rand::random::<f32>() * 20.0) - 10.0;
pixel[0] = (pixel[0] as f32 + noise).clamp(0.0, 255.0) as u8;
pixel[1] = (pixel[1] as f32 + noise).clamp(0.0, 255.0) as u8;
pixel[2] = (pixel[2] as f32 + noise).clamp(0.0, 255.0) as u8;
image.put_pixel(x, y, pixel);
}
}
}
pub fn oil_painting(image: &mut RgbImage) {
let (width, height) = image.dimensions();
let mut buffer = image.clone();
let radius = 3i32;
let intensity_levels = 8;
let width_i32 = width as i32;
let height_i32 = height as i32;
for y in radius..height_i32 - radius {
for x in radius..width_i32 - radius {
let mut intensity_count = vec![0u32; intensity_levels];
let mut avg_r = vec![0u32; intensity_levels];
let mut avg_g = vec![0u32; intensity_levels];
let mut avg_b = vec![0u32; intensity_levels];
for dy in -radius..=radius {
for dx in -radius..=radius {
let px_x = (x + dx).clamp(0, width_i32 - 1) as u32;
let px_y = (y + dy).clamp(0, height_i32 - 1) as u32;
let px = image.get_pixel(px_x, px_y);
let intensity = ((px[0] as u32 + px[1] as u32 + px[2] as u32) / 3) as usize;
let level = (intensity * intensity_levels) / 256;
let level = level.min(intensity_levels - 1);
intensity_count[level] += 1;
avg_r[level] += px[0] as u32;
avg_g[level] += px[1] as u32;
avg_b[level] += px[2] as u32;
}
}
let max_level = intensity_count
.iter()
.enumerate()
.max_by_key(|&(_, count)| count)
.map(|(level, _)| level)
.unwrap_or(0);
let count = intensity_count[max_level].max(1);
let result_pixel = Rgb([
(avg_r[max_level] / count) as u8,
(avg_g[max_level] / count) as u8,
(avg_b[max_level] / count) as u8
]);
buffer.put_pixel(x as u32, y as u32, result_pixel);
}
}
*image = buffer;
}
pub fn watercolor(image: &mut RgbImage) {
let (width, height) = image.dimensions();
let mut buffer = image.clone();
let radius = 2i32;
for y in (radius as u32)..height-(radius as u32) {
for x in (radius as u32)..width-(radius as u32) {
let mut r = 0u32;
let mut g = 0u32;
let mut b = 0u32;
let mut count = 0u32;
for dy in -radius..=radius {
for dx in -radius..=radius {
let px_x = x.wrapping_add_signed(dx);
let px_y = y.wrapping_add_signed(dy);
let px = image.get_pixel(px_x, px_y);
r += px[0] as u32;
g += px[1] as u32;
b += px[2] as u32;
count += 1;
}
}
buffer.put_pixel(x, y, Rgb([
(r / count) as u8,
(g / count) as u8,
(b / count) as u8
]));
}
}
*image = buffer;
}
pub fn sharpen(image: &mut RgbImage) {
let (width, height) = image.dimensions();
let mut buffer = image.clone();
let kernel = [
[ 0.0, -1.0, 0.0],
[-1.0, 5.0, -1.0],
[ 0.0, -1.0, 0.0]
];
for y in 1..height-1 {
for x in 1..width-1 {
let mut r: f32 = 0.0;
let mut g: f32 = 0.0;
let mut b: f32 = 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;
}
}
buffer.put_pixel(x, y, Rgb([
r.clamp(0.0, 255.0) as u8,
g.clamp(0.0, 255.0) as u8,
b.clamp(0.0, 255.0) as u8
]));
}
}
*image = buffer;
}
pub fn sepia(image: &mut RgbImage) {
for pixel in image.pixels_mut() {
let r = pixel[0] as f32 * 0.393 + pixel[1] as f32 * 0.769 + pixel[2] as f32 * 0.189;
let g = pixel[0] as f32 * 0.349 + pixel[1] as f32 * 0.686 + pixel[2] as f32 * 0.168;
let b = pixel[0] as f32 * 0.272 + pixel[1] as f32 * 0.534 + pixel[2] as f32 * 0.131;
pixel[0] = r.clamp(0.0, 255.0) as u8;
pixel[1] = g.clamp(0.0, 255.0) as u8;
pixel[2] = b.clamp(0.0, 255.0) as u8;
}
}
pub fn invert(image: &mut RgbImage) {
for pixel in image.pixels_mut() {
pixel[0] = 255 - pixel[0];
pixel[1] = 255 - pixel[1];
pixel[2] = 255 - pixel[2];
}
}
pub 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;
}
}
pub 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);
}
}
}
pub 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: f32 = 0.0;
let mut g: f32 = 0.0;
let mut b: f32 = 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;
}
pub 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;
}
pub fn shift(image: &mut RgbImage, dx: i32, dy: i32) {
let (width, height) = image.dimensions();
let mut buffer = RgbImage::new(width, height);
for y in 0..height {
for x in 0..width {
let new_x = x as i32 - dx;
let new_y = y as i32 - dy;
if new_x >= 0 && new_x < width as i32 && new_y >= 0 && new_y < height as i32 {
let pixel = *image.get_pixel(new_x as u32, new_y as u32);
buffer.put_pixel(x, y, pixel);
}
}
}
*image = buffer;
}
+166 -42
View File
@@ -1,57 +1,181 @@
use image2::*; use gtk::prelude::*;
use show_image::{ImageView, ImageInfo, event, create_window}; 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;
#[show_image::main] mod filters;
fn main() -> Result<(), Error> {
println!("Hello, world!");
// Open the image fn main() {
// let image = Image::<u8, Rgb>::open("/home/sweetbread/nix/screenshot.png")?; let app = Application::builder()
.application_id("ru.risdeveau.imagefilters")
.build();
// // Show the image app.connect_activate(|app| {
// let img = ImageView::new(ImageInfo::rgb8(image.width().try_into().unwrap(), image.height().try_into().unwrap()), image.buffer()); build_ui(app);
// let win = create_window("image", Default::default()).unwrap(); });
// win.set_image("image-001", img).unwrap();
// // Wait until Escape will be pressed app.run();
// for event in win.event_channel().unwrap() { }
// if let event::WindowEvent::KeyboardInput(event) = event {
// if event.input.key_code == Some(event::VirtualKeyCode::Escape) && event.input.state.is_pressed() {
// break;
// }
// }
// }
// Create new imahe fn build_ui(app: &Application) {
// let new_image = image.new_like(); let window = ApplicationWindow::builder()
// let mut new_image = new_image.with_color::<Hsv>(); .application(app)
.title("Image Filters")
.default_width(800)
.default_height(600)
.build();
window.set_resizable(false);
// for x in 0..image.width() { let container = GtkBox::new(gtk::Orientation::Vertical, 5);
// for y in 0..image.height() {
// let p = image.get_pixel(Point::new(x, y));
// let mut hsv_p = Pixel::<Hsv>::new();
// for i in 0..3 { let open_button = Button::with_label("Open Image");
// hsv_p[i] = p[i]; let picture = Picture::new();
// } let brightness_slider = Scale::with_range(gtk::Orientation::Horizontal, -100.0, 100.0, 1.0);
brightness_slider.set_value(0.0);
// new_image.set_pixel(Point::new(x, y), &hsv_p); 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.append_text("Negative");
filter_combo.append_text("Heatmap");
filter_combo.append_text("Pixelate");
filter_combo.append_text("Retro");
filter_combo.append_text("Oil");
filter_combo.append_text("Water");
filter_combo.append_text("Sharpen");
filter_combo.append_text("Sepia");
filter_combo.append_text("Shift");
filter_combo.set_active(Some(0));
// new_image.save("result.png").expect("Failed to save resulting image"); let image_data = Rc::new(RefCell::new(None::<(PathBuf, RgbImage)>));
let mut image = Image::<u8, Hsv>::new(Size::new(100, 100)); fn parallel_brightness(image: &mut RgbImage, value: i16) {
for x in 0..100 { let value = value.clamp(-255, 255); // Ограничиваем диапазон
for y in 0..100 {
let mut p = Pixel::<Hsv>::new(); for pixel in image.pixels_mut() {
p[1] = 1.; for channel in 0..3 {
p[2] = 1.; let new_val = pixel[channel] as i32 + value as i32;
image.set_pixel(Point::new(x, y), &p); pixel[channel] = new_val.clamp(0, 255) as u8;
}
} }
} }
image.save("result.jpg")?; fn apply_filter(image: &mut RgbImage, filter_name: &str) {
match filter_name {
"Gray World" => filters::gray_world(image),
"Histogram Stretch" => filters::histogram_stretch(image),
"Emboss" => filters::emboss(image),
"Blur" => filters::blur(image),
"Negative" => filters::invert(image),
"Heatmap" => filters::heatmap(image),
"Pixelate" => filters::pixelate(image, 8),
"Retro" => filters::retro_effect(image),
"Oil" => filters::oil_painting(image),
"Water" => filters::watercolor(image),
"Sharpen" => filters::sharpen(image),
"Sepia" => filters::sepia(image),
"Shift" => filters::shift(image, 10, 25),
_ => {}
}
}
Ok(()) {
let window = window.clone();
let image_data = image_data.clone();
let picture = picture.clone();
open_button.connect_clicked(move |_| {
let dialog = FileDialog::builder()
.title("Select Image")
.build();
let filter = gtk::FileFilter::new();
filter.add_mime_type("image/*");
dialog.set_default_filter(Some(&filter));
let window = window.clone();
let image_data = image_data.clone();
let picture = picture.clone();
dialog.open(
Some(&window),
None::<&gtk::gio::Cancellable>,
move |result| {
if let Ok(file) = result {
if let Some(path) = file.path() {
match image::open(&path) {
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/current_image.png");
if rgb_img.save(&temp_path).is_ok() {
picture.set_filename(Some(temp_path));
}
}
Err(e) => eprintln!("Image load error: {}", e),
}
}
}
}
);
});
}
{
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 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));
}
}
});
}
container.append(&open_button);
container.append(&picture);
container.append(&brightness_slider);
container.append(&filter_combo);
window.set_child(Some(&container));
window.show();
} }