photon_rs/
conv.rs

1//! Convolution effects such as sharpening, blurs, sobel filters, etc.,
2
3use crate::helpers;
4use crate::PhotonImage;
5use image::DynamicImage::ImageRgba8;
6use image::{GenericImage, GenericImageView, Pixel};
7use std::cmp::min;
8
9#[cfg(feature = "enable_wasm")]
10use wasm_bindgen::prelude::*;
11
12type Kernel = [f32; 9];
13
14fn conv(photon_image: &mut PhotonImage, kernel: Kernel) {
15    let mut img = helpers::dyn_image_from_raw(photon_image);
16    img = ImageRgba8(img.to_rgba8());
17
18    let mut filtered_img = img.filter3x3(&kernel);
19    filtered_img = ImageRgba8(filtered_img.to_rgba8());
20
21    if filtered_img.pixels().all(|p| p.2[3] == 0) {
22        for x in 0..GenericImageView::width(&img) - 1 {
23            for y in 0..GenericImageView::height(&img) - 1 {
24                let mut pixel = GenericImageView::get_pixel(&filtered_img, x, y);
25                let original_alpha =
26                    GenericImageView::get_pixel(&img, x, y).channels()[3];
27                pixel.channels_mut()[3] = original_alpha;
28                filtered_img.put_pixel(x, y, pixel);
29            }
30        }
31    }
32
33    photon_image.raw_pixels = filtered_img.into_bytes();
34}
35
36/// Noise reduction.
37///
38/// # Arguments
39/// * `img` - A PhotonImage.
40///
41/// # Example
42///
43/// ```no_run
44/// // For example, to noise reduct an image:
45/// use photon_rs::conv::noise_reduction;
46/// use photon_rs::native::open_image;
47///
48/// let mut img = open_image("img.jpg").expect("File should open");
49/// noise_reduction(&mut img);
50/// ```
51/// Adds a constant to a select R, G, or B channel's value.
52#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
53pub fn noise_reduction(photon_image: &mut PhotonImage) {
54    conv(
55        photon_image,
56        [0.0_f32, -1.0, 7.0, -1.0, 5.0, 9.0, 0.0, 7.0, 9.0],
57    );
58}
59
60/// Sharpen an image.
61///
62/// # Arguments
63/// * `img` - A PhotonImage.
64///
65/// # Example
66///
67/// ```no_run
68/// // For example, to sharpen an image:
69/// use photon_rs::conv::sharpen;
70/// use photon_rs::native::open_image;
71///
72/// let mut img = open_image("img.jpg").expect("File should open");
73/// sharpen(&mut img);
74/// ```
75/// Adds a constant to a select R, G, or B channel's value.
76#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
77pub fn sharpen(photon_image: &mut PhotonImage) {
78    conv(
79        photon_image,
80        [0.0_f32, -1.0, 0.0, -1.0, 5.0, -1.0, 0.0, -1.0, 0.0],
81    );
82}
83
84/// Apply edge detection to an image, to create a dark version with its edges highlighted.
85///
86/// # Arguments
87/// * `img` - A PhotonImage.
88///
89/// # Example
90///
91/// ```no_run
92/// // For example, to increase the Red channel for all pixels by 10:
93/// use photon_rs::conv::edge_detection;
94/// use photon_rs::native::open_image;
95///
96/// let mut img = open_image("img.jpg").expect("File should open");
97/// edge_detection(&mut img);
98/// ```
99#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
100pub fn edge_detection(photon_image: &mut PhotonImage) {
101    conv(
102        photon_image,
103        [-1.0_f32, -1.0, -1.0, -1.0, 8.0, -1.0, -1.0, -1.0, -1.0],
104    );
105}
106
107/// Apply an identity kernel convolution to an image.
108///
109/// # Arguments
110/// * `img` -A PhotonImage.
111///
112/// # Example
113///
114/// ```no_run
115/// // For example, to apply an identity kernel convolution:
116/// use photon_rs::conv::identity;
117/// use photon_rs::native::open_image;
118///
119/// let mut img = open_image("img.jpg").expect("File should open");
120/// identity(&mut img);
121/// ```
122#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
123pub fn identity(photon_image: &mut PhotonImage) {
124    conv(
125        photon_image,
126        [0.0_f32, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0],
127    );
128}
129
130/// Apply a box blur effect.
131///
132/// # Arguments
133/// * `img` - A PhotonImage.
134///
135/// # Example
136///
137/// ```no_run
138/// // For example, to apply a box blur effect:
139/// use photon_rs::conv::box_blur;
140/// use photon_rs::native::open_image;
141///
142/// let mut img = open_image("img.jpg").expect("File should open");
143/// box_blur(&mut img);
144/// ```
145#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
146pub fn box_blur(photon_image: &mut PhotonImage) {
147    conv(
148        photon_image,
149        [1.0_f32, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
150    );
151}
152
153/// Gaussian blur in linear time.
154///
155/// Reference: http://blog.ivank.net/fastest-gaussian-blur.html
156///
157/// # Arguments
158/// * `photon_image` - A PhotonImage
159/// * `radius` - blur radius
160/// # Example
161///
162/// ```no_run
163/// use photon_rs::conv::gaussian_blur;
164/// use photon_rs::native::open_image;
165///
166/// let mut img = open_image("img.jpg").expect("File should open");
167/// gaussian_blur(&mut img, 3_i32);
168/// ```
169#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
170pub fn gaussian_blur(photon_image: &mut PhotonImage, radius: i32) {
171    // construct pixel data
172    let img = helpers::dyn_image_from_raw(photon_image);
173    let mut src = img.into_bytes();
174
175    let width = photon_image.get_width();
176    let height = photon_image.get_height();
177    let mut target: Vec<u8> = src.clone();
178
179    // Clamp radius value when it exceeds width or height.
180    // Divide by 2 since maximal radius must satisfy these conditions:
181    // rad + ((w - 1) * h) + rad < w * h
182    // rad + ((h - 1) * w) + rad < w * h
183    // After all transformations they become:
184    // rad < h / 2
185    // rad < w / 2
186    // Subtract 1 because the inequalies are strict.
187    let radius = min(width as i32 / 2 - 1, radius);
188    let radius = min(height as i32 / 2 - 1, radius);
189
190    let bxs = boxes_for_gauss(radius as f32, 3);
191    box_blur_inner(&mut src, &mut target, width, height, (bxs[0] - 1) / 2);
192    box_blur_inner(&mut target, &mut src, width, height, (bxs[1] - 1) / 2);
193    box_blur_inner(&mut src, &mut target, width, height, (bxs[2] - 1) / 2);
194
195    // manipulate back
196    photon_image.raw_pixels = target;
197}
198
199fn boxes_for_gauss(sigma: f32, n: usize) -> Vec<i32> {
200    let n_float = n as f32;
201
202    let w_ideal = (12.0 * sigma * sigma / n_float).sqrt() + 1.0;
203    let mut wl: i32 = w_ideal.floor() as i32;
204
205    if wl % 2 == 0 {
206        wl -= 1;
207    };
208
209    let wu = wl + 2;
210
211    let wl_float = wl as f32;
212
213    let m_ideal = (12.0 * sigma * sigma
214        - n_float * wl_float * wl_float
215        - 4.0 * n_float * wl_float
216        - 3.0 * n_float)
217        / (-4.0 * wl_float - 4.0);
218
219    let m: usize = m_ideal.round() as usize;
220
221    let mut sizes = Vec::<i32>::new();
222    for i in 0..n {
223        if i < m {
224            sizes.push(wl);
225        } else {
226            sizes.push(wu);
227        }
228    }
229
230    sizes
231}
232
233fn box_blur_inner(
234    src: &mut [u8],
235    target: &mut [u8],
236    width: u32,
237    height: u32,
238    radius: i32,
239) {
240    let length = (width * height * 4) as usize;
241    target[..length].clone_from_slice(&src[..length]);
242    box_blur_horizontal(target, src, width, height, radius);
243    box_blur_vertical(src, target, width, height, radius);
244}
245
246fn box_blur_horizontal(
247    src: &[u8],
248    target: &mut [u8],
249    width: u32,
250    height: u32,
251    radius: i32,
252) {
253    let iarr = 1.0 / (radius + radius + 1) as f32;
254    for i in 0..height {
255        let mut ti: usize = (i * width) as usize * 4;
256        let mut li: usize = ti;
257        let mut ri: usize = ti + radius as usize * 4;
258
259        let fv_r = src[ti] as i32;
260        let fv_g = src[ti + 1] as i32;
261        let fv_b = src[ti + 2] as i32;
262
263        let lv_r = src[ti + (width - 1) as usize * 4];
264        let lv_g = src[ti + (width - 1) as usize * 4 + 1];
265        let lv_b = src[ti + (width - 1) as usize * 4 + 2];
266
267        let mut val_r = (radius + 1) * fv_r;
268        let mut val_g = (radius + 1) * fv_g;
269        let mut val_b = (radius + 1) * fv_b;
270
271        for j in 0..radius {
272            val_r += src[ti + j as usize * 4] as i32;
273            val_g += src[ti + j as usize * 4 + 1] as i32;
274            val_b += src[ti + j as usize * 4 + 2] as i32;
275        }
276
277        for _ in 0..radius + 1 {
278            val_r += src[ri] as i32 - fv_r;
279            val_g += src[ri + 1] as i32 - fv_g;
280            val_b += src[ri + 2] as i32 - fv_b;
281            ri += 4;
282
283            target[ti] = (val_r as f32 * iarr).clamp(0.0, 255.0) as u8;
284            target[ti + 1] = (val_g as f32 * iarr).clamp(0.0, 255.0) as u8;
285            target[ti + 2] = (val_b as f32 * iarr).clamp(0.0, 255.0) as u8;
286            ti += 4;
287        }
288
289        for _ in (radius + 1)..(width as i32 - radius) {
290            val_r += src[ri] as i32 - src[li] as i32;
291            val_g += src[ri + 1] as i32 - src[li + 1] as i32;
292            val_b += src[ri + 2] as i32 - src[li + 2] as i32;
293            ri += 4;
294            li += 4;
295
296            target[ti] = (val_r as f32 * iarr).clamp(0.0, 255.0) as u8;
297            target[ti + 1] = (val_g as f32 * iarr).clamp(0.0, 255.0) as u8;
298            target[ti + 2] = (val_b as f32 * iarr).clamp(0.0, 255.0) as u8;
299            ti += 4;
300        }
301
302        for _ in (width as i32 - radius)..width as i32 {
303            val_r += lv_r as i32 - src[li] as i32;
304            val_g += lv_g as i32 - src[li + 1] as i32;
305            val_b += lv_b as i32 - src[li + 2] as i32;
306            li += 4;
307
308            target[ti] = (val_r as f32 * iarr).clamp(0.0, 255.0) as u8;
309            target[ti + 1] = (val_g as f32 * iarr).clamp(0.0, 255.0) as u8;
310            target[ti + 2] = (val_b as f32 * iarr).clamp(0.0, 255.0) as u8;
311            ti += 4;
312        }
313    }
314}
315
316fn box_blur_vertical(
317    src: &[u8],
318    target: &mut [u8],
319    width: u32,
320    height: u32,
321    radius: i32,
322) {
323    let iarr = 1.0 / (radius + radius + 1) as f32;
324
325    for i in 0..width {
326        let mut ti: usize = i as usize * 4;
327        let mut li: usize = ti;
328        let mut ri: usize = ti + (radius * width as i32) as usize * 4;
329
330        let fv_r = src[ti] as i32;
331        let fv_g = src[ti + 1] as i32;
332        let fv_b = src[ti + 2] as i32;
333
334        let lv_r = src[ti + ((height - 1) * width) as usize * 4];
335        let lv_g = src[ti + ((height - 1) * width) as usize * 4 + 1];
336        let lv_b = src[ti + ((height - 1) * width) as usize * 4 + 2];
337
338        let mut val_r = (radius + 1) * fv_r;
339        let mut val_g = (radius + 1) * fv_g;
340        let mut val_b = (radius + 1) * fv_b;
341
342        for j in 0..radius {
343            val_r += src[ti + (j * width as i32) as usize * 4] as i32;
344            val_g += src[ti + (j * width as i32) as usize * 4 + 1] as i32;
345            val_b += src[ti + (j * width as i32) as usize * 4 + 2] as i32;
346        }
347
348        for _ in 0..radius + 1 {
349            val_r += src[ri] as i32 - fv_r;
350            val_g += src[ri + 1] as i32 - fv_g;
351            val_b += src[ri + 2] as i32 - fv_b;
352            ri += width as usize * 4;
353
354            target[ti] = (val_r as f32 * iarr).clamp(0.0, 255.0) as u8;
355            target[ti + 1] = (val_g as f32 * iarr).clamp(0.0, 255.0) as u8;
356            target[ti + 2] = (val_b as f32 * iarr).clamp(0.0, 255.0) as u8;
357            ti += width as usize * 4;
358        }
359
360        for _ in (radius + 1)..(height as i32 - radius) {
361            val_r += src[ri] as i32 - src[li] as i32;
362            val_g += src[ri + 1] as i32 - src[li + 1] as i32;
363            val_b += src[ri + 2] as i32 - src[li + 2] as i32;
364            ri += width as usize * 4;
365            li += width as usize * 4;
366
367            target[ti] = (val_r as f32 * iarr).clamp(0.0, 255.0) as u8;
368            target[ti + 1] = (val_g as f32 * iarr).clamp(0.0, 255.0) as u8;
369            target[ti + 2] = (val_b as f32 * iarr).clamp(0.0, 255.0) as u8;
370            ti += width as usize * 4;
371        }
372
373        for _ in (height as i32 - radius)..height as i32 {
374            val_r += lv_r as i32 - src[li] as i32;
375            val_g += lv_g as i32 - src[li + 1] as i32;
376            val_b += lv_b as i32 - src[li + 2] as i32;
377            li += width as usize * 4;
378
379            target[ti] = (val_r as f32 * iarr).clamp(0.0, 255.0) as u8;
380            target[ti + 1] = (val_g as f32 * iarr).clamp(0.0, 255.0) as u8;
381            target[ti + 2] = (val_b as f32 * iarr).clamp(0.0, 255.0) as u8;
382            ti += width as usize * 4;
383        }
384    }
385}
386
387/// Detect horizontal lines in an image, and highlight these only.
388///
389/// # Arguments
390/// * `img` - A PhotonImage.
391///
392/// # Example
393///
394/// ```no_run
395/// // For example, to display the horizontal lines in an image:
396/// use photon_rs::conv::detect_horizontal_lines;
397/// use photon_rs::native::open_image;
398///
399/// let mut img = open_image("img.jpg").expect("File should open");
400/// detect_horizontal_lines(&mut img);
401/// ```
402#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
403pub fn detect_horizontal_lines(photon_image: &mut PhotonImage) {
404    conv(
405        photon_image,
406        [-1.0_f32, -1.0, -1.0, 2.0, 2.0, 2.0, -1.0, -1.0, -1.0],
407    );
408}
409
410/// Detect vertical lines in an image, and highlight these only.
411///
412/// # Arguments
413/// * `img` - A PhotonImage.
414///
415/// # Example
416///
417/// ```no_run
418/// // For example, to display the vertical lines in an image:
419/// use photon_rs::conv::detect_vertical_lines;
420/// use photon_rs::native::open_image;
421///
422/// let mut img = open_image("img.jpg").expect("File should open");
423/// detect_vertical_lines(&mut img);
424/// ```
425#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
426pub fn detect_vertical_lines(photon_image: &mut PhotonImage) {
427    conv(
428        photon_image,
429        [-1.0_f32, 2.0, -1.0, -1.0, 2.0, -1.0, -1.0, 2.0, -1.0],
430    );
431}
432
433/// Detect lines at a forty five degree angle in an image, and highlight these only.
434///
435/// # Arguments
436/// * `img` - A PhotonImage.
437///
438/// # Example
439///
440/// ```no_run
441/// // For example, to display the lines at a forty five degree angle in an image:
442/// use photon_rs::conv::detect_45_deg_lines;
443/// use photon_rs::native::open_image;
444///
445/// let mut img = open_image("img.jpg").expect("File should open");
446/// detect_45_deg_lines(&mut img);
447/// ```
448#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
449pub fn detect_45_deg_lines(photon_image: &mut PhotonImage) {
450    conv(
451        photon_image,
452        [-1.0_f32, -1.0, 2.0, -1.0, 2.0, -1.0, 2.0, -1.0, -1.0],
453    );
454}
455
456/// Detect lines at a 135 degree angle in an image, and highlight these only.
457///
458/// # Arguments
459/// * `img` - A PhotonImage.
460///
461/// # Example
462///
463/// ```no_run
464/// // For example, to display the lines at a 135 degree angle in an image:
465/// use photon_rs::conv::detect_135_deg_lines;
466/// use photon_rs::native::open_image;
467///
468/// let mut img = open_image("img.jpg").expect("File should open");
469/// detect_135_deg_lines(&mut img);
470/// ```
471#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
472pub fn detect_135_deg_lines(photon_image: &mut PhotonImage) {
473    conv(
474        photon_image,
475        [2.0_f32, -1.0, -1.0, -1.0, 2.0, -1.0, -1.0, -1.0, 2.0],
476    );
477}
478
479/// Apply a standard laplace convolution.
480///
481/// # Arguments
482/// * `img` - A PhotonImage.
483///
484/// # Example
485///
486/// ```no_run
487/// // For example, to apply a laplace effect:
488/// use photon_rs::conv::laplace;
489/// use photon_rs::native::open_image;
490///
491/// let mut img = open_image("img.jpg").expect("File should open");
492/// laplace(&mut img);
493/// ```
494#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
495pub fn laplace(photon_image: &mut PhotonImage) {
496    conv(
497        photon_image,
498        [0.0_f32, -1.0, 0.0, -1.0, 4.0, -1.0, 0.0, -1.0, 0.0],
499    );
500}
501
502/// Preset edge effect.
503///
504/// # Arguments
505/// * `img` - A PhotonImage.
506///
507/// # Example
508///
509/// ```no_run
510/// // For example, to apply this effect:
511/// use photon_rs::conv::edge_one;
512/// use photon_rs::native::open_image;
513///
514/// let mut img = open_image("img.jpg").expect("File should open");
515/// edge_one(&mut img);
516/// ```
517#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
518pub fn edge_one(photon_image: &mut PhotonImage) {
519    conv(
520        photon_image,
521        [0.0_f32, -2.2, -0.6, -0.4, 2.8, -0.3, -0.8, -1.0, 2.7],
522    );
523}
524
525/// Apply an emboss effect to an image.
526///
527/// # Arguments
528/// * `img` - A PhotonImage.
529///
530/// # Example
531///
532/// ```no_run
533/// // For example, to apply an emboss effect:
534/// use photon_rs::conv::emboss;
535/// use photon_rs::native::open_image;
536///
537/// let mut img = open_image("img.jpg").expect("File should open");
538/// emboss(&mut img);
539/// ```
540#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
541pub fn emboss(photon_image: &mut PhotonImage) {
542    conv(
543        photon_image,
544        [-2.0_f32, -1.0, 0.0, -1.0, 1.0, 1.0, 0.0, 1.0, 2.0],
545    );
546}
547
548/// Apply a horizontal Sobel filter to an image.
549///
550/// # Arguments
551/// * `img` - A PhotonImage.
552///
553/// # Example
554///
555/// ```no_run
556/// // For example, to apply a horizontal Sobel filter:
557/// use photon_rs::conv::sobel_horizontal;
558/// use photon_rs::native::open_image;
559///
560/// let mut img = open_image("img.jpg").expect("File should open");
561/// sobel_horizontal(&mut img);
562/// ```
563#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
564pub fn sobel_horizontal(photon_image: &mut PhotonImage) {
565    conv(
566        photon_image,
567        [-1.0_f32, -2.0, -1.0, 0.0, 0.0, 0.0, 1.0, 2.0, 1.0],
568    );
569}
570
571/// Apply a horizontal Prewitt convolution to an image.
572///
573/// # Arguments
574/// * `img` - A PhotonImage.
575///
576/// # Example
577///
578/// ```no_run
579/// // For example, to apply a horizontal Prewitt convolution effect:
580/// use photon_rs::conv::prewitt_horizontal;
581/// use photon_rs::native::open_image;
582///
583/// let mut img = open_image("img.jpg").expect("File should open");
584/// prewitt_horizontal(&mut img);
585/// ```
586#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
587pub fn prewitt_horizontal(photon_image: &mut PhotonImage) {
588    conv(
589        photon_image,
590        [5.0_f32, -3.0, -3.0, 5.0, 0.0, -3.0, 5.0, -3.0, -3.0],
591    );
592}
593
594/// Apply a vertical Sobel filter to an image.
595///
596/// # Arguments
597/// * `img` - A PhotonImage.
598///
599/// # Example
600///
601/// ```no_run
602/// // For example, to apply a vertical Sobel filter:
603/// use photon_rs::conv::sobel_vertical;
604/// use photon_rs::native::open_image;
605///
606/// let mut img = open_image("img.jpg").expect("File should open");
607/// sobel_vertical(&mut img);
608/// ```
609#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
610pub fn sobel_vertical(photon_image: &mut PhotonImage) {
611    conv(
612        photon_image,
613        [-1.0_f32, 0.0, 1.0, -2.0, 0.0, 2.0, -1.0, 0.0, 1.0],
614    );
615}
616
617/// Apply a global Sobel filter to an image
618///
619/// Each pixel is calculated as the magnitude of the horizontal and vertical components of the Sobel filter,
620/// ie if X is the horizontal sobel and Y is the vertical, for each pixel, we calculate sqrt(X^2 + Y^2)
621///
622/// # Arguments
623/// * `img` - A PhotonImage.
624///
625/// # Example
626///
627/// ```no_run
628/// // For example, to apply a global Sobel filter:
629/// use photon_rs::conv::sobel_global;
630/// use photon_rs::native::open_image;
631///
632/// let mut img = open_image("img.jpg").expect("File should open");
633/// sobel_global(&mut img);
634/// ```
635#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
636pub fn sobel_global(photon_image: &mut PhotonImage) {
637    let mut sobel_x = photon_image.clone();
638    let sobel_y = photon_image;
639
640    sobel_horizontal(&mut sobel_x);
641    sobel_vertical(sobel_y);
642
643    let sob_x_values = sobel_x.get_raw_pixels();
644    let sob_y_values = sobel_y.get_raw_pixels();
645
646    let mut sob_xy_values = vec![];
647
648    for i in 0..(sob_x_values.len()) {
649        let kx = (sob_x_values[i]) as u32;
650        let ky = (sob_y_values[i]) as u32; // this could panic if for some reason the sobel_y doesn't have the same size as the sobel_x
651        let kxy_2 = kx * kx + ky * ky; // u8 * u8 is u16 and we sum two so we need u32
652        sob_xy_values.push((kxy_2 as f64).sqrt() as u8); // f64::max is bigger than u32::max so no problem with conversion
653    }
654
655    sobel_y.raw_pixels = sob_xy_values;
656}