Compare commits
4 Commits
c1ccad6229
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d2ea3bf4b | |||
| 202f76b38b | |||
| c7441349a1 | |||
| 33d6bb64e3 |
Generated
+97
@@ -305,6 +305,18 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.13.1"
|
||||
@@ -429,6 +441,8 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"gtk4",
|
||||
"image",
|
||||
"rand",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -674,6 +688,15 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.3.0"
|
||||
@@ -710,6 +733,42 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
@@ -882,6 +941,15 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.8"
|
||||
@@ -970,6 +1038,35 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-inflate"
|
||||
version = "0.2.54"
|
||||
|
||||
@@ -6,6 +6,8 @@ edition = "2021"
|
||||
[dependencies]
|
||||
gtk = { package = "gtk4", version = "0.9.6", features=["v4_10"] }
|
||||
image = "0.24"
|
||||
rayon = "1.8"
|
||||
rand = "0.9.0"
|
||||
# gtk4 = "0.9.6"
|
||||
# show-image = "0.14.0"
|
||||
# image2 = "1.9.2"
|
||||
|
||||
+413
@@ -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;
|
||||
}
|
||||
+133
-128
@@ -1,24 +1,18 @@
|
||||
use gtk::builders::ImageBuilder;
|
||||
use gtk::gio::Cancellable;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Application, ApplicationWindow, Picture, Scale, Label, FileDialog};
|
||||
use image::{DynamicImage, ImageBuffer, RgbImage};
|
||||
use gtk::{Button, Box as GtkBox, Orientation};
|
||||
use gtk::glib::Propagation;
|
||||
use gtk::gdk_pixbuf::Pixbuf;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use gtk::gio::File;
|
||||
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;
|
||||
|
||||
mod filters;
|
||||
|
||||
fn main() {
|
||||
// Инициализация приложения
|
||||
let app = Application::builder()
|
||||
.application_id("ru.risdeveau.imageeditor")
|
||||
.application_id("ru.risdeveau.imagefilters")
|
||||
.build();
|
||||
|
||||
// Запуск приложения
|
||||
app.connect_activate(|app| {
|
||||
build_ui(app);
|
||||
});
|
||||
@@ -27,150 +21,161 @@ 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 selected_file = Rc::new(RefCell::new(None::<File>));
|
||||
let container = GtkBox::new(gtk::Orientation::Vertical, 5);
|
||||
|
||||
// Создание кнопки для открытия диалога выбора файла
|
||||
let button = Button::with_label("Select Image");
|
||||
// let picture = Picture::new();
|
||||
// picture.set_size_request(400, 400);
|
||||
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.0);
|
||||
|
||||
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));
|
||||
|
||||
let image_data = Rc::new(RefCell::new(None::<(PathBuf, RgbImage)>));
|
||||
|
||||
fn parallel_brightness(image: &mut RgbImage, value: i16) {
|
||||
let value = value.clamp(-255, 255); // Ограничиваем диапазон
|
||||
|
||||
for pixel in image.pixels_mut() {
|
||||
for channel in 0..3 {
|
||||
let new_val = pixel[channel] as i32 + value as i32;
|
||||
pixel[channel] = new_val.clamp(0, 255) as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let window = window.clone();
|
||||
let selected_file = selected_file.clone();
|
||||
// let picture = picture.clone();
|
||||
button.connect_clicked(move |_| {
|
||||
// Создание FileDialog
|
||||
let image_data = image_data.clone();
|
||||
let picture = picture.clone();
|
||||
open_button.connect_clicked(move |_| {
|
||||
let dialog = FileDialog::builder()
|
||||
.title("Select an Image")
|
||||
.accept_label("Open")
|
||||
.title("Select Image")
|
||||
.build();
|
||||
|
||||
// Фильтр для изображений
|
||||
let filter = gtk::FileFilter::new();
|
||||
filter.add_mime_type("image/png"); // Разрешить только изображения
|
||||
filter.set_name(Some("Image Files"));
|
||||
filter.add_mime_type("image/*");
|
||||
dialog.set_default_filter(Some(&filter));
|
||||
|
||||
// Показ диалога и обработка результата
|
||||
let window = window.clone();
|
||||
let selected_file = selected_file.clone();
|
||||
// let picture = picture.clone();
|
||||
dialog.open(Some(&window), None::<>k::gio::Cancellable>, move |result| {
|
||||
if let Ok(file) = result {
|
||||
// Получение выбранного файла
|
||||
let path = file.path().unwrap();
|
||||
println!("Selected image: {:?}", path);
|
||||
let image_data = image_data.clone();
|
||||
let picture = picture.clone();
|
||||
dialog.open(
|
||||
Some(&window),
|
||||
None::<>k::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()));
|
||||
|
||||
// Загрузка изображения (опционально)
|
||||
if let Ok(pixbuf) = Pixbuf::from_file(&path) {
|
||||
println!("Image loaded successfully: {}x{}", pixbuf.width(), pixbuf.height());
|
||||
let _ = fs::copy(&path, "/tmp/modified.png");
|
||||
*selected_file.borrow_mut() = Some(file.clone());
|
||||
// picture.set_pixbuf(Some(&pixbuf));
|
||||
|
||||
} else {
|
||||
eprintln!("Failed to load image.");
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!("File selection canceled.");
|
||||
}
|
||||
});
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if selected_file.borrow().is_some() {
|
||||
button.set_visible(false);
|
||||
}
|
||||
|
||||
let picture = Picture::for_filename("/tmp/modified.png");
|
||||
let image = image::open("/tmp/modified.png")
|
||||
.map(|img| img.to_rgb8())
|
||||
.map_err(|e| eprintln!("Failed to load image: {}", e))
|
||||
.ok().unwrap();
|
||||
|
||||
let br_slider = Scale::with_range(Orientation::Horizontal, -128., 128., 10.);
|
||||
br_slider.set_value(0.);
|
||||
br_slider.set_hexpand(true);
|
||||
|
||||
let br_text = Label::new(Some("Brightness: +000"));
|
||||
|
||||
let br_container = GtkBox::new(Orientation::Horizontal, 5);
|
||||
br_container.append(&br_text);
|
||||
br_container.append(&br_slider);
|
||||
|
||||
{
|
||||
br_slider.clone().connect_change_value(move |_, _, _| {
|
||||
let mut value = br_slider.value();
|
||||
value = value.round();
|
||||
br_slider.set_value(value);
|
||||
br_text.set_text(&format!("Brightness: {:+04.0}", value));
|
||||
edit_image(&mut image, value as i8);
|
||||
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;
|
||||
|
||||
// Продолжаем обработку события
|
||||
Propagation::Proceed
|
||||
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 container = GtkBox::new(Orientation::Vertical, 8);
|
||||
container.append(&button);
|
||||
|
||||
{
|
||||
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(&br_container);
|
||||
|
||||
container.append(&brightness_slider);
|
||||
container.append(&filter_combo);
|
||||
window.set_child(Some(&container));
|
||||
|
||||
// // Загрузка изображения
|
||||
// let mut img: RgbImage = ImageBuffer::new(256, 256); // Пример: пустое изображение
|
||||
// let mut dynamic_img = DynamicImage::ImageRgb8(img.clone());
|
||||
|
||||
// // Сохранение изображения во временный файл
|
||||
// let temp_path = "temp_image.png";
|
||||
// dynamic_img.save(temp_path).unwrap();
|
||||
|
||||
// // Создание виджета Picture для отображения изображения
|
||||
// let picture = Picture::for_filename(temp_path);
|
||||
|
||||
// // Создание кнопки
|
||||
// let button = Button::with_label("Edit Image");
|
||||
|
||||
// // Контейнер для размещения виджетов
|
||||
// let container = GtkBox::new(Orientation::Vertical, 5);
|
||||
// container.append(&picture);
|
||||
// container.append(&button);
|
||||
|
||||
// // Обработка нажатия кнопки
|
||||
// button.connect_clicked(move |_| {
|
||||
// // Редактирование изображения
|
||||
// edit_image(&mut img);
|
||||
// dynamic_img = DynamicImage::ImageRgb8(img.clone());
|
||||
|
||||
// // Обновление изображения в интерфейсе
|
||||
// update_image(&picture, &dynamic_img);
|
||||
// });
|
||||
|
||||
// // Добавление контейнера в окно
|
||||
// window.set_child(Some(&container));
|
||||
|
||||
// Отображение окна
|
||||
window.show();
|
||||
}
|
||||
|
||||
fn edit_image(img: &mut RgbImage, brightness: i8) {
|
||||
for (x, y, pixel) in img.enumerate_pixels_mut() {
|
||||
pixel[0] = (pixel[0] as i16 + brightness as i16) as u8;
|
||||
}
|
||||
}
|
||||
|
||||
// fn update_image(picture: &Picture, img: &DynamicImage) {
|
||||
// img.save("tmp.png").unwrap();
|
||||
// picture.set_filename(Some(temp_path));
|
||||
// }
|
||||
|
||||
Reference in New Issue
Block a user