Skip to main content

libeffectengine/effects/
pixelate.rs

1use std::io::Cursor;
2#[cfg(not(target_arch = "wasm32"))]
3use std::process::exit;
4use wasm_bindgen::prelude::*;
5
6use image::{DynamicImage, ImageBuffer, ImageFormat, Rgba};
7
8#[cfg(not(target_arch = "wasm32"))]
9use crate::util::subcommand_help_requested;
10use crate::util::{get_paths, read_image};
11
12/// Applies a pixelation filter to an image by combining multiple pixels into bigger ones.
13/// Calculates the average color of each "big pixel" to do so.
14#[wasm_bindgen(js_name = pixelate)]
15pub fn effect() -> Vec<u8> {
16    #[cfg(not(target_arch = "wasm32"))]
17    {
18        if subcommand_help_requested() {
19            print_help();
20            exit(0);
21        }
22    }
23
24    let paths = get_paths();
25    let image_data = read_image(paths.input_path);
26
27    let img =
28        image::load_from_memory(&image_data.data).expect("Failed to decode image from memory");
29    let image = img.to_rgba8();
30    let image_width = image.width();
31    let image_height = image.height();
32
33    // How big the pixels in the final image should be
34    let processed_pixel_size = std::env::args()
35        .nth(4)
36        .or_else(|| Some(String::from("16")))
37        .unwrap()
38        .parse()
39        .unwrap_or_else(|_| 16);
40
41    let mut new_image = ImageBuffer::new(image_width, image_height);
42
43    for i in (0..image_width).step_by(processed_pixel_size as usize) {
44        for j in (0..image_height).step_by(processed_pixel_size as usize) {
45            let mut r_sum: u64 = 0;
46            let mut g_sum: u64 = 0;
47            let mut b_sum: u64 = 0;
48            let mut a_sum: u64 = 0;
49            let mut count: u64 = 0;
50
51            let x_end: u32 = (i + processed_pixel_size).min(image_width);
52            let y_end: u32 = (j + processed_pixel_size).min(image_height);
53
54            for k in i..x_end {
55                for l in j..y_end {
56                    let pixel = image.get_pixel(k, l);
57
58                    r_sum += pixel.0[0] as u64;
59                    g_sum += pixel.0[1] as u64;
60                    b_sum += pixel.0[2] as u64;
61                    a_sum += pixel.0[3] as u64;
62                    count += 1;
63                }
64            }
65
66            if count > 0 {
67                let color = Rgba([
68                    (r_sum / count) as u8,
69                    (g_sum / count) as u8,
70                    (b_sum / count) as u8,
71                    (a_sum / count) as u8,
72                ]);
73
74                for k in i..x_end {
75                    for l in j..y_end {
76                        new_image.put_pixel(k, l, color);
77                    }
78                }
79            }
80        }
81    }
82
83    let mut cursor = Cursor::new(Vec::new());
84
85    if image_data.format == ImageFormat::Jpeg {
86        let rgb_image = DynamicImage::ImageRgba8(new_image).into_rgb8();
87        rgb_image
88            .write_to(&mut cursor, image_data.format)
89            .expect("Failed to encode JPEG");
90    } else {
91        new_image
92            .write_to(&mut cursor, image_data.format)
93            .expect("Failed to encode image");
94    }
95
96    return cursor.into_inner();
97}
98
99/// Prints the help text for this effect.
100#[cfg(not(target_arch = "wasm32"))]
101fn print_help() {
102    println!(
103        r#"
104Pixelation Effect
105Applies a pixelation filter to an image by combining multiple pixels into bigger
106ones.
107
108USAGE:
109  effectengine-cli pixelate <INPUT_PATH> <OUTPUT_PATH> [PIXELATION_STRENGTH]
110
111ARGUMENTS:
112  <INPUT_PATH>             The path to an input image that should be processed.
113  <OUTPUT_PATH>            The path where the resulting image should be saved.
114                           Needs to include the filename.
115  [PIXELATION_STRENGTH]    Optional. How strong the pixelation effect should be.
116                           Specifies the size of each big pixel. (Default: 16)
117  "#
118    );
119}