1use crate::helpers;
4use crate::iter::ImageIterator;
5use crate::{PhotonImage, Rgba};
6use image::imageops::FilterType;
7use image::DynamicImage::ImageRgba8;
8use image::{GenericImageView, ImageBuffer, Pixel, RgbaImage};
9use std::cmp::min;
10
11#[cfg(feature = "enable_wasm")]
12use wasm_bindgen::prelude::*;
13#[cfg(all(feature = "enable_wasm", target_arch = "wasm32"))]
14use wasm_bindgen::{Clamped, JsCast};
15#[cfg(all(feature = "enable_wasm", target_arch = "wasm32"))]
16use web_sys::{HtmlCanvasElement, ImageData};
17
18#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
36pub fn crop(
37 photon_image: &PhotonImage,
38 x1: u32,
39 y1: u32,
40 x2: u32,
41 y2: u32,
42) -> PhotonImage {
43 let img = helpers::dyn_image_from_raw(photon_image);
44
45 let mut cropped_img: RgbaImage = ImageBuffer::new(x2 - x1, y2 - y1);
46
47 for (x, y) in ImageIterator::with_dimension(&cropped_img.dimensions()) {
48 let px = img.get_pixel(x1 + x, y1 + y);
49 cropped_img.put_pixel(x, y, px);
50 }
51 let dynimage = ImageRgba8(cropped_img);
52
53 let width = dynimage.width();
54 let height = dynimage.height();
55
56 let raw_pixels = dynimage.into_bytes();
57 PhotonImage {
58 raw_pixels,
59 width,
60 height,
61 }
62}
63
64#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
65#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
66pub fn crop_img_browser(
67 source_canvas: HtmlCanvasElement,
68 width: f64,
69 height: f64,
70 left: f64,
71 top: f64,
72) -> HtmlCanvasElement {
73 let document = web_sys::window().unwrap().document().unwrap();
74 let dest_canvas = document
75 .create_element("canvas")
76 .unwrap()
77 .dyn_into::<web_sys::HtmlCanvasElement>()
78 .unwrap();
79
80 dest_canvas.set_width(width as u32);
81 dest_canvas.set_height(height as u32);
82
83 let ctx = dest_canvas
84 .get_context("2d")
85 .unwrap()
86 .unwrap()
87 .dyn_into::<web_sys::CanvasRenderingContext2d>()
88 .unwrap();
89
90 ctx.draw_image_with_html_canvas_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
91 &source_canvas,
92 left,
93 top,
94 width,
95 height,
96 0.0,
97 0.0,
98 width,
99 height,
100 )
101 .unwrap();
102
103 dest_canvas
104}
105
106#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
122pub fn fliph(photon_image: &mut PhotonImage) {
123 let img = helpers::dyn_image_from_raw(photon_image);
124
125 let width = img.width();
126 let height = img.height();
127 let mut flipped_img: RgbaImage = ImageBuffer::new(width, height);
128
129 for (x, y) in ImageIterator::new(width, height) {
130 let px = img.get_pixel(x, y);
131 flipped_img.put_pixel(width - x - 1, y, px);
132 }
133
134 let dynimage = ImageRgba8(flipped_img);
135 let raw_pixels = dynimage.into_bytes();
136 photon_image.raw_pixels = raw_pixels;
137}
138
139#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
155pub fn flipv(photon_image: &mut PhotonImage) {
156 let img = helpers::dyn_image_from_raw(photon_image);
157
158 let width = img.width();
159 let height = img.height();
160
161 let mut flipped_img: RgbaImage = ImageBuffer::new(width, height);
162
163 for (x, y) in ImageIterator::new(width, height) {
164 let px = img.get_pixel(x, y);
165 flipped_img.put_pixel(x, height - y - 1, px);
166 }
167
168 let dynimage = ImageRgba8(flipped_img);
169 let raw_pixels = dynimage.into_bytes();
170 photon_image.raw_pixels = raw_pixels;
171}
172
173#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
174pub enum SamplingFilter {
175 Nearest = 1,
176 Triangle = 2,
177 CatmullRom = 3,
178 Gaussian = 4,
179 Lanczos3 = 5,
180}
181
182fn filter_type_from_sampling_filter(sampling_filter: SamplingFilter) -> FilterType {
183 match sampling_filter {
184 SamplingFilter::Nearest => FilterType::Nearest,
185 SamplingFilter::Triangle => FilterType::Triangle,
186 SamplingFilter::CatmullRom => FilterType::CatmullRom,
187 SamplingFilter::Gaussian => FilterType::Gaussian,
188 SamplingFilter::Lanczos3 => FilterType::Lanczos3,
189 }
190}
191
192#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
200#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
201pub fn resize_img_browser(
202 photon_img: &PhotonImage,
203 width: u32,
204 height: u32,
205 sampling_filter: SamplingFilter,
206) -> HtmlCanvasElement {
207 let sampling_filter = filter_type_from_sampling_filter(sampling_filter);
208 let dyn_img = helpers::dyn_image_from_raw(photon_img);
209 let resized_img = ImageRgba8(image::imageops::resize(
210 &dyn_img,
211 width,
212 height,
213 sampling_filter,
214 ));
215
216 let document = web_sys::window().unwrap().document().unwrap();
218 let canvas = document
219 .create_element("canvas")
220 .unwrap()
221 .dyn_into::<web_sys::HtmlCanvasElement>()
222 .unwrap();
223
224 let width = resized_img.width();
225 let height = resized_img.height();
226
227 canvas.set_width(width);
228 canvas.set_height(width);
229
230 let new_img_data = ImageData::new_with_u8_clamped_array_and_sh(
231 Clamped(&mut resized_img.into_bytes()),
232 width,
233 height,
234 );
235
236 let ctx = canvas
237 .get_context("2d")
238 .unwrap()
239 .unwrap()
240 .dyn_into::<web_sys::CanvasRenderingContext2d>()
241 .unwrap();
242
243 ctx.put_image_data(&new_img_data.unwrap(), 0.0, 0.0)
245 .unwrap();
246
247 canvas
248}
249
250#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
258pub fn resize(
259 photon_img: &PhotonImage,
260 width: u32,
261 height: u32,
262 sampling_filter: SamplingFilter,
263) -> PhotonImage {
264 let sampling_filter = filter_type_from_sampling_filter(sampling_filter);
265
266 let dyn_img = helpers::dyn_image_from_raw(photon_img);
267 let resized_img = ImageRgba8(image::imageops::resize(
268 &dyn_img,
269 width,
270 height,
271 sampling_filter,
272 ));
273
274 let width = resized_img.width();
275 let height = resized_img.height();
276
277 PhotonImage {
278 raw_pixels: resized_img.into_bytes(),
279 width,
280 height,
281 }
282}
283
284#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
305pub fn seam_carve(img: &PhotonImage, width: u32, height: u32) -> PhotonImage {
306 let mut img: RgbaImage = ImageBuffer::from_raw(
307 img.get_width(),
308 img.get_height(),
309 img.raw_pixels.to_vec(),
310 )
311 .unwrap();
312 let (w, h) = img.dimensions();
313 let (diff_w, diff_h) = (w - w.min(width), h - h.min(height));
314
315 for _ in 0..diff_w {
316 let vec_steam = imageproc::seam_carving::find_vertical_seam(&img);
317 img = imageproc::seam_carving::remove_vertical_seam(&img, &vec_steam);
318 }
319 if diff_h.ne(&0_u32) {
320 img = image::imageops::rotate90(&img);
321 for _ in 0..diff_h {
322 let vec_steam = imageproc::seam_carving::find_vertical_seam(&img);
323 img = imageproc::seam_carving::remove_vertical_seam(&img, &vec_steam);
324 }
325 img = image::imageops::rotate270(&img);
326 }
327
328 let img = ImageRgba8(img);
329
330 let width = img.width();
331 let height = img.height();
332
333 PhotonImage {
334 raw_pixels: img.into_bytes(),
335 width,
336 height,
337 }
338}
339
340#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
358pub fn shearx(photon_img: &PhotonImage, shear: f32) -> PhotonImage {
359 let img = helpers::dyn_image_from_raw(photon_img);
360 let src_width = img.width();
361 let src_height = img.height();
362
363 let maxskew = shear * (src_height as f32);
364 let dst_width = maxskew.floor().abs() as u32 + src_width;
365
366 let mut delta = 0;
367 if shear < 0. {
368 delta = dst_width - src_width;
369 }
370
371 let mut sheared_image: RgbaImage = ImageBuffer::new(dst_width, src_height);
372
373 for old_y in 0..src_height {
374 let skew = shear * (old_y as f32 + 0.5);
375 let skewi = skew.floor() as i32 + delta as i32;
376 let skewf = skew.fract().abs();
377 let mut oleft = image::Rgba([0_u8, 0_u8, 0_u8, 0_u8]);
378 for old_x in (0..src_width).rev() {
379 let mut pixel = img.get_pixel(old_x, old_y);
380 let mut left = pixel.map(|val| (val as f32 * skewf) as u8);
381 if shear >= 0. {
382 left = pixel.map2(&left, |val1, val2| val1 - val2);
383 }
384 pixel = pixel.map2(&left, |val1, val2| val1 - val2);
385 pixel = pixel.map2(&oleft, |val1, val2| {
386 min(val1 as u16 + val2 as u16, 255_u16) as u8
387 });
388 let new_x = (old_x as i32 + skewi) as u32;
389 sheared_image.put_pixel(new_x, old_y, pixel);
390 oleft = left;
391 }
392 sheared_image.put_pixel(skewi as u32, old_y, oleft);
393 }
394
395 let dynimage = ImageRgba8(sheared_image);
396 let width = dynimage.width();
397 let height = dynimage.height();
398 let raw_pixels = dynimage.into_bytes();
399
400 PhotonImage::new(raw_pixels, width, height)
401}
402
403#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
421pub fn sheary(photon_img: &PhotonImage, shear: f32) -> PhotonImage {
422 let img = helpers::dyn_image_from_raw(photon_img);
423 let src_width = img.width();
424 let src_height = img.height();
425
426 let maxskew = shear * (src_width as f32);
427 let dst_height = maxskew.floor().abs() as u32 + src_height;
428
429 let mut delta = 0;
430 if shear < 0. {
431 delta = dst_height - src_height;
432 }
433
434 let mut sheared_image: RgbaImage = ImageBuffer::new(src_width, dst_height);
435
436 for old_x in 0..src_width {
437 let skew = shear * (old_x as f32 + 0.5);
438 let skewi = skew.floor() as i32 + delta as i32;
439 let skewf = skew.fract().abs();
440 let mut oleft = image::Rgba([0_u8, 0_u8, 0_u8, 0_u8]);
441 for old_y in (0..src_height).rev() {
442 let mut pixel = img.get_pixel(old_x, old_y);
443 let mut left = pixel.map(|val| (val as f32 * skewf).floor() as u8);
444 if shear >= 0. {
445 left = pixel.map2(&left, |val1, val2| val1 - val2);
446 }
447 pixel = pixel.map2(&left, |val1, val2| val1 - val2);
448 pixel = pixel.map2(&oleft, |val1, val2| {
449 min(val1 as u16 + val2 as u16, 255_u16) as u8
450 });
451 let new_y = (old_y as i32 + skewi) as u32;
452 sheared_image.put_pixel(old_x, new_y, pixel);
453 oleft = left;
454 }
455 sheared_image.put_pixel(old_x, skewi as u32, oleft);
456 }
457
458 let dynimage = ImageRgba8(sheared_image);
459 let width = dynimage.width();
460 let height = dynimage.height();
461 let raw_pixels = dynimage.into_bytes();
462
463 PhotonImage::new(raw_pixels, width, height)
464}
465
466#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
486pub fn padding_uniform(
487 img: &PhotonImage,
488 padding: u32,
489 padding_rgba: Rgba,
490) -> PhotonImage {
491 let image_buffer = img.get_raw_pixels();
492 let img_width = img.get_width();
493 let img_height = img.get_height();
494
495 let mut img_padded_buffer = Vec::<u8>::new();
496 let width_padded: u32 = img_width + 2 * padding;
497 let height_padded: u32 = img_height + 2 * padding;
498
499 for _ in 0..((width_padded + 1) * padding) {
500 img_padded_buffer.push(padding_rgba.get_red());
501 img_padded_buffer.push(padding_rgba.get_green());
502 img_padded_buffer.push(padding_rgba.get_blue());
503 img_padded_buffer.push(padding_rgba.get_alpha());
504 }
505
506 for i in (0..image_buffer.len()).step_by(4) {
507 if (i / 4) % img_width as usize == 0 && i != 0 {
508 for _ in (0..2 * padding).step_by(1) {
509 img_padded_buffer.push(padding_rgba.get_red());
510 img_padded_buffer.push(padding_rgba.get_green());
511 img_padded_buffer.push(padding_rgba.get_blue());
512 img_padded_buffer.push(padding_rgba.get_alpha());
513 }
514 }
515 img_padded_buffer.push(image_buffer[i]);
516 img_padded_buffer.push(image_buffer[i + 1]);
517 img_padded_buffer.push(image_buffer[i + 2]);
518 img_padded_buffer.push(image_buffer[i + 3]);
519 }
520
521 for _ in 0..((width_padded + 1) * padding) {
522 img_padded_buffer.push(padding_rgba.get_red());
523 img_padded_buffer.push(padding_rgba.get_green());
524 img_padded_buffer.push(padding_rgba.get_blue());
525 img_padded_buffer.push(padding_rgba.get_alpha());
526 }
527
528 PhotonImage::new(img_padded_buffer, width_padded, height_padded)
529}
530
531#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
551pub fn padding_left(img: &PhotonImage, padding: u32, padding_rgba: Rgba) -> PhotonImage {
552 let image_buffer = img.get_raw_pixels();
553 let img_width = img.get_width();
554 let img_height = img.get_height();
555
556 let mut img_padded_buffer = Vec::<u8>::new();
557 let width_padded: u32 = img_width + padding;
558
559 for i in 0..img_height as usize {
560 let img_slice = image_buffer
561 [(i * 4 * img_width as usize)..(i + 1) * 4 * img_width as usize]
562 .to_owned();
563
564 for _ in 0..padding {
565 img_padded_buffer.push(padding_rgba.get_red());
566 img_padded_buffer.push(padding_rgba.get_green());
567 img_padded_buffer.push(padding_rgba.get_blue());
568 img_padded_buffer.push(padding_rgba.get_alpha());
569 }
570 for x in img_slice {
571 img_padded_buffer.push(x);
572 }
573 }
574 PhotonImage::new(img_padded_buffer, width_padded, img_height)
575}
576
577#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
597pub fn padding_right(
598 img: &PhotonImage,
599 padding: u32,
600 padding_rgba: Rgba,
601) -> PhotonImage {
602 let image_buffer = img.get_raw_pixels();
603 let img_width = img.get_width();
604 let img_height = img.get_height();
605
606 let mut img_padded_buffer = Vec::<u8>::new();
607 let width_padded: u32 = img_width + padding;
608
609 for i in 0..img_height as usize {
610 let img_slice = image_buffer
611 [(i * 4 * img_width as usize)..(i + 1) * 4 * img_width as usize]
612 .to_owned();
613 for x in img_slice {
614 img_padded_buffer.push(x);
615 }
616 for _ in 0..padding {
617 img_padded_buffer.push(padding_rgba.get_red());
618 img_padded_buffer.push(padding_rgba.get_green());
619 img_padded_buffer.push(padding_rgba.get_blue());
620 img_padded_buffer.push(padding_rgba.get_alpha());
621 }
622 }
623 PhotonImage::new(img_padded_buffer, width_padded, img_height)
624}
625
626#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
646pub fn padding_top(img: &PhotonImage, padding: u32, padding_rgba: Rgba) -> PhotonImage {
647 let image_buffer = img.get_raw_pixels();
648 let img_width = img.get_width();
649 let img_height = img.get_height();
650
651 let height_padded: u32 = img_height + padding;
652 let mut img_padded_buffer: Vec<u8> = Vec::<u8>::new();
653
654 for _ in 0..(padding * img_width) {
655 img_padded_buffer.push(padding_rgba.get_red());
656 img_padded_buffer.push(padding_rgba.get_green());
657 img_padded_buffer.push(padding_rgba.get_blue());
658 img_padded_buffer.push(padding_rgba.get_alpha());
659 }
660
661 for i in (0..image_buffer.len()).step_by(4) {
662 img_padded_buffer.push(image_buffer[i]);
663 img_padded_buffer.push(image_buffer[i + 1]);
664 img_padded_buffer.push(image_buffer[i + 2]);
665 img_padded_buffer.push(image_buffer[i + 3]);
666 }
667
668 PhotonImage::new(img_padded_buffer, img_width, height_padded)
669}
670
671#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
691pub fn padding_bottom(
692 img: &PhotonImage,
693 padding: u32,
694 padding_rgba: Rgba,
695) -> PhotonImage {
696 let image_buffer = img.get_raw_pixels();
697 let img_width = img.get_width();
698 let img_height = img.get_height();
699
700 let height_padded: u32 = img_height + padding;
701 let mut img_padded_buffer: Vec<u8> = Vec::<u8>::new();
702
703 for i in (0..image_buffer.len()).step_by(4) {
704 img_padded_buffer.push(image_buffer[i]);
705 img_padded_buffer.push(image_buffer[i + 1]);
706 img_padded_buffer.push(image_buffer[i + 2]);
707 img_padded_buffer.push(image_buffer[i + 3]);
708 }
709
710 for _ in 0..(padding * img_width) {
711 img_padded_buffer.push(padding_rgba.get_red());
712 img_padded_buffer.push(padding_rgba.get_green());
713 img_padded_buffer.push(padding_rgba.get_blue());
714 img_padded_buffer.push(padding_rgba.get_alpha());
715 }
716
717 PhotonImage::new(img_padded_buffer, img_width, height_padded)
718}
719
720#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
738#[must_use]
739pub fn rotate(photon_img: &PhotonImage, angle: f32) -> PhotonImage {
740 let full_circle_count = angle as i32 / 360;
742 let normalized_angle = angle as i32 - full_circle_count * 360;
743 if normalized_angle == 0 {
744 return photon_img.clone();
745 }
746
747 let positive_angle = if normalized_angle < 0 {
749 normalized_angle + 360
750 } else {
751 normalized_angle
752 };
753
754 let right_angle_count = positive_angle / 90;
755 let mut rgba_img: RgbaImage = ImageBuffer::from_raw(
756 photon_img.get_width(),
757 photon_img.get_height(),
758 photon_img.get_raw_pixels().to_vec(),
759 )
760 .unwrap();
761 for _ in 0..right_angle_count {
762 rgba_img = image::imageops::rotate90(&rgba_img);
763 }
764
765 let dynimage = ImageRgba8(rgba_img);
766 let src_width = dynimage.width();
767 let src_height = dynimage.height();
768 let raw_pixels = dynimage.into_bytes();
769
770 let mut img_out = PhotonImage::new(raw_pixels, src_width, src_height);
771
772 let theta = ((angle % 360.) - (right_angle_count * 90) as f32).to_radians();
773 let beta = theta.sin();
774 let alpha = -1. * ((theta / 2.).tan());
775
776 img_out = shearx(&img_out, alpha);
777 img_out = sheary(&img_out, beta);
778 img_out = shearx(&img_out, alpha);
779
780 img_out
781}
782
783fn greatest_common_divisor(left_val: usize, right_val: usize) -> usize {
784 let mut a = left_val;
785 let mut b = right_val;
786 while b > 0 {
787 let t = b;
788 b = a % b;
789 a = t;
790 }
791 a
792}
793
794fn copy_row(buf: &[u8], row_pos: usize, row_stride: usize) -> Vec<u8> {
795 let mut result = Vec::<u8>::new();
796 for byte_idx in 0..row_stride {
797 let src_idx = (row_pos * row_stride) + byte_idx;
798 result.push(buf[src_idx]);
799 }
800
801 result
802}
803
804#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
822pub fn resample(img: &PhotonImage, dst_width: usize, dst_height: usize) -> PhotonImage {
823 let mut pix_buf = Vec::<u8>::new();
824 if dst_width == 0 || dst_height == 0 {
825 return PhotonImage::new(pix_buf, dst_width as u32, dst_height as u32);
826 }
827
828 let src_width = img.get_width() as usize;
829 let src_height = img.get_height() as usize;
830
831 let width_gcd = greatest_common_divisor(src_width, dst_width);
837 let height_gcd = greatest_common_divisor(src_height, dst_height);
838 let upsample_x = dst_width / width_gcd;
839 let downsample_x = src_width / width_gcd;
840 let upsample_y = dst_height / height_gcd;
841 let downsample_y = src_height / height_gcd;
842
843 let img_pixels = &img.raw_pixels;
851 let src_chan = 4;
852
853 let mut resampled_width = Vec::<u8>::new();
855 for row in 0..src_height {
856 let mut upsampled_width = Vec::<u8>::new();
858 for col in 0..src_width {
859 for _i in 0..upsample_x {
860 for chan in 0..src_chan {
861 let src_idx = (row * src_width * src_chan) + col * src_chan + chan;
862 upsampled_width.push(img_pixels[src_idx]);
863 }
864 }
865
866 let upsampled_pix_count = upsampled_width.len() / src_chan;
868 if (upsampled_pix_count % downsample_x) == 0 {
869 for i in 0..upsampled_pix_count / downsample_x {
870 for chan in 0..src_chan {
871 let src_idx = (i * downsample_x) * src_chan + chan;
872 resampled_width.push(upsampled_width[src_idx]);
873 }
874 }
875 upsampled_width.clear();
876 }
877 }
878 }
879
880 let mut upsampled_height = Vec::<u8>::new();
882 for row in 0..src_height {
883 for _i in 0..upsample_y {
885 let mut row_copy = copy_row(&resampled_width, row, dst_width * src_chan);
886 upsampled_height.append(&mut row_copy);
887 }
888
889 let upsampled_rows_count = upsampled_height.len() / src_chan / dst_width;
891 if (upsampled_rows_count % downsample_y) == 0 {
892 for i in 0..upsampled_rows_count / downsample_y {
893 let mut row_copy =
894 copy_row(&upsampled_height, i * downsample_y, dst_width * src_chan);
895 pix_buf.append(&mut row_copy);
896 }
897 upsampled_height.clear();
898 }
899 }
900
901 PhotonImage::new(pix_buf, dst_width as u32, dst_height as u32)
902}
903
904pub fn compress(img: &PhotonImage, quality: u8) -> PhotonImage {
922 let bytes = img.get_bytes_jpeg(quality);
923
924 PhotonImage::new_from_byteslice(bytes)
925}