libeffectengine/effects/
pixel_sort.rs1use std::io::Cursor;
2#[cfg(not(target_arch = "wasm32"))]
3use std::process::exit;
4use wasm_bindgen::prelude::*;
5
6use image::{DynamicImage, GenericImageView, ImageBuffer, ImageFormat, Rgba};
7
8use crate::util::{get_paths, pixel_to_grayscale_value, read_image};
9
10#[cfg(not(target_arch = "wasm32"))]
11use crate::util::subcommand_help_requested;
12
13#[wasm_bindgen(js_name = pixelSort)]
22pub fn effect() -> Vec<u8> {
23 #[cfg(not(target_arch = "wasm32"))]
24 {
25 if subcommand_help_requested() {
26 print_help();
27 exit(0);
28 }
29 }
30
31 let paths = get_paths();
32 let image_data = read_image(paths.input_path);
33
34 let image = DynamicImage::ImageRgba8(
35 image::load_from_memory(&image_data.data)
36 .expect("Failed to decode image from memory")
37 .to_rgba8(),
38 );
39 let image_width = image.width();
40 let image_height = image.height();
41
42 let mut new_image = ImageBuffer::new(image_width, image_height);
43
44 let total_brightness = image.pixels().fold(0, |acc, pixel| {
45 let grayscale = pixel_to_grayscale_value(pixel) as usize;
46
47 acc + grayscale
48 });
49
50 let avg_brightness = total_brightness / (image_width as usize * image_height as usize);
51
52 let mode = std::env::args()
53 .nth(4)
54 .or_else(|| Some(String::from("horizontal")))
55 .unwrap();
56
57 match mode.as_str() {
58 "vertical" => {
59 let (pixel_positions, pixels_to_be_sorted) =
60 get_vertical_pixels_to_be_sorted(&image, &mut new_image, avg_brightness);
61 sort_pixels(&mut new_image, pixel_positions, pixels_to_be_sorted);
62 }
63 "both" => {
64 let (mut pixel_positions, mut pixels_to_be_sorted) =
65 get_horizontal_pixels_to_be_sorted(&image, &mut new_image, avg_brightness);
66 sort_pixels(&mut new_image, pixel_positions, pixels_to_be_sorted);
67
68 let new_image_base = DynamicImage::ImageRgba8(new_image.clone());
69 (pixel_positions, pixels_to_be_sorted) =
70 get_vertical_pixels_to_be_sorted(&new_image_base, &mut new_image, avg_brightness);
71 sort_pixels(&mut new_image, pixel_positions, pixels_to_be_sorted);
72 }
73 "horizontal" => {
74 let (pixel_positions, pixels_to_be_sorted) =
75 get_horizontal_pixels_to_be_sorted(&image, &mut new_image, avg_brightness);
76 sort_pixels(&mut new_image, pixel_positions, pixels_to_be_sorted);
77 }
78 _ => {
79 let (pixel_positions, pixels_to_be_sorted) =
80 get_horizontal_pixels_to_be_sorted(&image, &mut new_image, avg_brightness);
81 sort_pixels(&mut new_image, pixel_positions, pixels_to_be_sorted);
82 }
83 }
84
85 let mut cursor = Cursor::new(Vec::new());
86
87 if image_data.format == ImageFormat::Jpeg {
88 let rgb_image = DynamicImage::ImageRgba8(new_image).into_rgb8();
89 rgb_image
90 .write_to(&mut cursor, image_data.format)
91 .expect("Failed to encode JPEG");
92 } else {
93 new_image
94 .write_to(&mut cursor, image_data.format)
95 .expect("Failed to encode image");
96 }
97
98 return cursor.into_inner();
99}
100
101fn sort_pixels(
104 new_image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
105 pixel_positions: Vec<Vec<Vec<(u32, u32)>>>,
106 pixels_to_be_sorted: Vec<Vec<Vec<(Rgba<u8>, i32)>>>,
107) {
108 let mut i = 0;
109 for interval in pixels_to_be_sorted {
110 let mut j = 0;
111 for mut pixels in interval {
112 pixels.sort_by(|a, b| a.1.cmp(&b.1));
113
114 for (k, pixel) in pixels.iter().enumerate() {
115 new_image.put_pixel(
116 pixel_positions[i][j][k].0,
117 pixel_positions[i][j][k].1,
118 pixel.0,
119 );
120 }
121
122 j += 1;
123 }
124
125 i += 1;
126 }
127}
128
129fn get_horizontal_pixels_to_be_sorted(
131 image: &DynamicImage,
132 new_image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
133 avg_brightness: usize,
134) -> (Vec<Vec<Vec<(u32, u32)>>>, Vec<Vec<Vec<(Rgba<u8>, i32)>>>) {
135 let mut pixel_positions: Vec<Vec<Vec<(u32, u32)>>> = Vec::new();
136 let mut pixels_to_be_sorted: Vec<Vec<Vec<(Rgba<u8>, i32)>>> = Vec::new();
137
138 let mut current_row = 0;
139 let mut interval = 0;
140
141 for (i, pixel) in image.pixels().enumerate() {
142 let row_check = i / image.width() as usize;
143 if row_check > current_row {
144 current_row += 1;
145 interval = 0;
146 }
147
148 if pixel_positions.len() <= current_row {
149 pixel_positions.push(Vec::new());
150 pixels_to_be_sorted.push(Vec::new());
151 }
152
153 if pixel_positions[current_row].len() <= interval {
154 pixel_positions[current_row].push(Vec::new());
155 pixels_to_be_sorted[current_row].push(Vec::new());
156 }
157
158 let grayscale = pixel_to_grayscale_value(pixel);
159
160 if grayscale > avg_brightness as i32 {
161 pixel_positions[current_row][interval].push((pixel.0, pixel.1));
162
163 pixels_to_be_sorted[current_row][interval].push((pixel.2, grayscale));
164 } else {
165 new_image.put_pixel(pixel.0, pixel.1, pixel.2);
166 interval += 1;
167 }
168 }
169
170 (pixel_positions, pixels_to_be_sorted)
171}
172
173fn get_vertical_pixels_to_be_sorted(
175 image: &DynamicImage,
176 new_image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
177 avg_brightness: usize,
178) -> (Vec<Vec<Vec<(u32, u32)>>>, Vec<Vec<Vec<(Rgba<u8>, i32)>>>) {
179 let mut pixel_positions: Vec<Vec<Vec<(u32, u32)>>> = Vec::new();
180 let mut pixels_to_be_sorted: Vec<Vec<Vec<(Rgba<u8>, i32)>>> = Vec::new();
181
182 let width = image.width();
183 let height = image.height();
184
185 for x in 0..width {
186 let mut column_positions = Vec::new();
187 let mut column_pixels = Vec::new();
188
189 let mut interval = 0;
190 column_positions.push(Vec::new());
191 column_pixels.push(Vec::new());
192
193 for y in 0..height {
194 let pixel = image.get_pixel(x, y);
195 let grayscale = pixel_to_grayscale_value((x, y, pixel));
196
197 if grayscale > avg_brightness as i32 {
198 column_positions[interval].push((x, y));
199 column_pixels[interval].push((pixel, grayscale));
200 } else {
201 new_image.put_pixel(x, y, pixel);
202
203 if !column_positions[interval].is_empty() {
204 interval += 1;
205 column_positions.push(Vec::new());
206 column_pixels.push(Vec::new());
207 }
208 }
209 }
210 pixel_positions.push(column_positions);
211 pixels_to_be_sorted.push(column_pixels);
212 }
213
214 (pixel_positions, pixels_to_be_sorted)
215}
216
217#[cfg(not(target_arch = "wasm32"))]
219fn print_help() {
220 println!(
221 r#"
222Pixel Sorting Effect
223Sorts all pixels in an image above the image's average brightness by their
224brightness value.
225
226USAGE:
227 effectengine-cli pixel-sort <INPUT_PATH> <OUTPUT_PATH> [DIRECTION]
228
229ARGUMENTS:
230 <INPUT_PATH> The path to an input image that should be processed.
231 <OUTPUT_PATH> The path where the resulting image should be saved.
232 Needs to include the filename.
233 [DIRECTION] Optional. The direction the pixels should be sorted in.
234 Valid options are "horizontal", "vertical" or "both".
235 (Default: "horizontal")
236 "#
237 );
238}