1use crate::helpers;
4use crate::iter::ImageIterator;
5use crate::{PhotonImage, Rgb};
6use image::Pixel;
7use image::Rgba;
8use image::{GenericImage, GenericImageView};
9use imageproc::drawing::draw_filled_rect_mut;
10use imageproc::rect::Rect;
11use perlin2d::PerlinNoise2D;
12use std::collections::HashMap;
13use std::f64;
14
15#[cfg(feature = "enable_wasm")]
16use wasm_bindgen::prelude::*;
17
18#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
37pub fn offset(photon_image: &mut PhotonImage, channel_index: usize, offset: u32) {
38 if channel_index > 2 {
39 panic!("Invalid channel index passed. Channel1 must be equal to 0, 1, or 2.");
40 }
41
42 let mut img = helpers::dyn_image_from_raw(photon_image);
43 let (width, height) = img.dimensions();
44
45 for x in 0..width - 10 {
46 for y in 0..height - 10 {
47 let px = img.get_pixel(x, y);
48
49 if x + offset < width - 1 && y + offset < height - 1 {
50 let offset_px = img.get_pixel(x + offset, y + offset);
51 let offset_px_channels = offset_px.channels();
52
53 let px_channels = px.channels();
54
55 let px = match channel_index {
56 0 => image::Rgba([
57 offset_px_channels[0],
58 px_channels[1],
59 px_channels[2],
60 255,
61 ]),
62 1 => image::Rgba([
63 px_channels[0],
64 offset_px_channels[1],
65 px_channels[2],
66 255,
67 ]),
68 2 => image::Rgba([
69 px_channels[0],
70 px_channels[1],
71 offset_px_channels[2],
72 255,
73 ]),
74 _ => image::Rgba([
75 px_channels[0],
76 px_channels[1],
77 offset_px_channels[2],
78 255,
79 ]),
80 };
81 img.put_pixel(x, y, px);
82 }
83 }
84 }
85 let raw_pixels = img.into_bytes();
86 photon_image.raw_pixels = raw_pixels;
87}
88
89#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
105pub fn offset_red(img: &mut PhotonImage, offset_amt: u32) {
106 offset(img, 0, offset_amt)
107}
108
109#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
125pub fn offset_green(img: &mut PhotonImage, offset_amt: u32) {
126 offset(img, 1, offset_amt)
127}
128
129#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
145pub fn offset_blue(img: &mut PhotonImage, offset_amt: u32) {
146 offset(img, 2, offset_amt)
147}
148
149#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
165pub fn multiple_offsets(
166 photon_image: &mut PhotonImage,
167 offset: u32,
168 channel_index: usize,
169 channel_index2: usize,
170) {
171 if channel_index > 2 {
172 panic!("Invalid channel index passed. Channel1 must be equal to 0, 1, or 2.");
173 }
174 if channel_index2 > 2 {
175 panic!("Invalid channel index passed. Channel2 must be equal to 0, 1, or 2.");
176 }
177 let mut img = helpers::dyn_image_from_raw(photon_image);
178 let (width, height) = img.dimensions();
179
180 for (x, y) in ImageIterator::new(width, height) {
181 let mut px = img.get_pixel(x, y);
182
183 if x + offset < width - 1 && y + offset < height - 1 {
184 let offset_px = img.get_pixel(x + offset, y);
185
186 px[channel_index] = offset_px[channel_index];
187 }
188
189 if x as i32 - offset as i32 > 0 && y as i32 - offset as i32 > 0 {
190 let offset_px2 = img.get_pixel(x - offset, y);
191
192 px[channel_index2] = offset_px2[channel_index2];
193 }
194
195 img.put_pixel(x, y, px);
196 }
197 let raw_pixels = img.into_bytes();
198 photon_image.raw_pixels = raw_pixels;
199}
200
201#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
216pub fn halftone(photon_image: &mut PhotonImage) {
217 let mut img = helpers::dyn_image_from_raw(photon_image);
218 let (width, height) = img.dimensions();
219
220 for x in (0..width - 4).step_by(2_usize) {
221 for y in (0..height - 4).step_by(2_usize) {
222 let mut px1 = img.get_pixel(x, y);
223 let mut px2 = img.get_pixel(x, y + 1);
224 let mut px3 = img.get_pixel(x + 1, y);
225 let mut px4 = img.get_pixel(x + 1, y + 1);
226
227 let gray1 = (px1[0] as f64 * 0.299)
228 + (px1[1] as f64 * 0.587)
229 + (px1[2] as f64 * 0.114);
230 let gray2 = (px2[0] as f64 * 0.299)
231 + (px2[1] as f64 * 0.587)
232 + (px2[2] as f64 * 0.114);
233 let gray3 = (px3[0] as f64 * 0.299)
234 + (px3[1] as f64 * 0.587)
235 + (px3[2] as f64 * 0.114);
236 let gray4 = (px4[0] as f64 * 0.299)
237 + (px4[1] as f64 * 0.587)
238 + (px4[2] as f64 * 0.114);
239
240 let sat = (gray1 + gray2 + gray3 + gray4) / 4.0;
241
242 if sat > 200.0 {
243 px1[0] = 255;
244 px1[1] = 255;
245 px1[2] = 255;
246
247 px2[0] = 255;
248 px2[1] = 255;
249 px2[2] = 255;
250
251 px3[0] = 255;
252 px3[1] = 255;
253 px3[2] = 255;
254
255 px4[0] = 255;
256 px4[1] = 255;
257 px4[2] = 255;
258 } else if sat > 159.0 {
259 px1[0] = 255;
260 px1[1] = 255;
261 px1[2] = 255;
262
263 px2[0] = 0;
264 px2[1] = 0;
265 px2[2] = 0;
266
267 px3[0] = 255;
268 px3[1] = 255;
269 px3[2] = 255;
270
271 px4[0] = 255;
272 px4[1] = 255;
273 px4[2] = 255;
274 } else if sat > 95.0 {
275 px1[0] = 255;
276 px1[1] = 255;
277 px1[2] = 255;
278
279 px2[0] = 0;
280 px2[1] = 0;
281 px2[2] = 0;
282
283 px3[0] = 0;
284 px3[1] = 0;
285 px3[2] = 0;
286
287 px4[0] = 255;
288 px4[1] = 255;
289 px4[2] = 255;
290 } else if sat > 32.0 {
291 px1[0] = 0;
292 px1[1] = 0;
293 px1[2] = 0;
294
295 px2[0] = 255;
296 px2[1] = 255;
297 px2[2] = 255;
298
299 px3[0] = 0;
300 px3[1] = 0;
301 px3[2] = 0;
302
303 px4[0] = 0;
304 px4[1] = 0;
305 px4[2] = 0;
306 } else {
307 px1[0] = 0;
308 px1[1] = 0;
309 px1[2] = 0;
310
311 px2[0] = 0;
312 px2[1] = 0;
313 px2[2] = 0;
314
315 px3[0] = 0;
316 px3[1] = 0;
317 px3[2] = 0;
318
319 px4[0] = 0;
320 px4[1] = 0;
321 px4[2] = 0;
322 }
323
324 img.put_pixel(x, y, px1);
325 }
327 }
328 let raw_pixels = img.into_bytes();
329 photon_image.raw_pixels = raw_pixels;
330}
331
332#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
347pub fn primary(img: &mut PhotonImage) {
348 let end = img.raw_pixels.len() - 4;
349
350 for i in (0..end).step_by(4) {
351 let mut r_val = img.raw_pixels[0];
352 let mut g_val = img.raw_pixels[1];
353 let mut b_val = img.raw_pixels[2];
354
355 if r_val > 128 {
356 r_val = 255;
357 } else {
358 r_val = 0;
359 }
360
361 if g_val > 128 {
362 g_val = 255;
363 } else {
364 g_val = 0;
365 }
366
367 if b_val > 128 {
368 g_val = 255;
369 } else {
370 b_val = 0;
371 }
372
373 img.raw_pixels[i] = r_val;
374 img.raw_pixels[i + 1] = g_val;
375 img.raw_pixels[i + 2] = b_val;
376 }
377}
378
379#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
394pub fn colorize(photon_image: &mut PhotonImage) {
395 let mut img = helpers::dyn_image_from_raw(photon_image);
396 let threshold = 220;
397
398 for (x, y) in ImageIterator::with_dimension(&img.dimensions()) {
399 let mut px = img.get_pixel(x, y);
400 let channels = px.channels();
401 let px_as_rgb = Rgb {
402 r: channels[0],
403 g: channels[1],
404 b: channels[2],
405 };
406
407 let baseline_color = Rgb {
408 r: 0,
409 g: 255,
410 b: 255,
411 };
412
413 let square_distance = crate::helpers::square_distance(baseline_color, px_as_rgb);
414
415 let mut r = channels[0] as f32;
416 let mut g = channels[1] as f32;
417 let mut b = channels[2] as f32;
418
419 if square_distance < i32::pow(threshold, 2) {
420 r *= 0.5;
421 g *= 1.25;
422 b *= 0.5;
423 }
424
425 px = image::Rgba([r as u8, g as u8, b as u8, 255]);
426 img.put_pixel(x, y, px);
427 }
428 let raw_pixels = img.into_bytes();
429 photon_image.raw_pixels = raw_pixels;
430}
431
432#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
496pub fn solarize(photon_image: &mut PhotonImage) {
497 let end = photon_image.get_raw_pixels().len();
498
499 for i in (0..end).step_by(4) {
500 let r_val = photon_image.raw_pixels[i];
501
502 if 200 - r_val as i32 > 0 {
503 photon_image.raw_pixels[i] = 200 - r_val;
504 }
505 }
506}
507
508#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
524pub fn solarize_retimg(photon_image: &PhotonImage) -> PhotonImage {
525 let mut img = helpers::dyn_image_from_raw(photon_image);
526
527 for (x, y) in ImageIterator::with_dimension(&img.dimensions()) {
528 let mut px = img.get_pixel(x, y);
529 let channels = px.channels();
530 if 200_i32 - channels[0] as i32 > 0 {
531 let new_r_val = 200 - channels[0];
532 px = image::Rgba([new_r_val, channels[1], channels[2], channels[3]]);
533 }
534 img.put_pixel(x, y, px);
535 }
536
537 let (width, height) = img.dimensions();
538
539 PhotonImage {
540 raw_pixels: img.into_bytes(),
541 width,
542 height,
543 }
544}
545
546#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
563pub fn adjust_brightness(photon_image: &mut PhotonImage, brightness: i16) {
564 if brightness > 0 {
565 inc_brightness(photon_image, brightness as u8)
566 } else {
567 dec_brightness(photon_image, brightness.unsigned_abs() as u8)
568 }
569}
570
571#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
586pub fn inc_brightness(photon_image: &mut PhotonImage, brightness: u8) {
587 let end = photon_image.get_raw_pixels().len() - 4;
588
589 for i in (0..end).step_by(4) {
590 let r_val = photon_image.raw_pixels[i];
591 let g_val = photon_image.raw_pixels[i + 1];
592 let b_val = photon_image.raw_pixels[i + 2];
593
594 if r_val <= 255 - brightness {
595 photon_image.raw_pixels[i] += brightness;
596 } else {
597 photon_image.raw_pixels[i] = 255;
598 }
599 if g_val <= 255 - brightness {
600 photon_image.raw_pixels[i + 1] += brightness;
601 } else {
602 photon_image.raw_pixels[i + 1] = 255
603 }
604
605 if b_val <= 255 - brightness {
606 photon_image.raw_pixels[i + 2] += brightness;
607 } else {
608 photon_image.raw_pixels[i + 2] = 255
609 }
610 }
611}
612
613#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
629pub fn dec_brightness(photon_image: &mut PhotonImage, brightness: u8) {
630 let end = photon_image.get_raw_pixels().len() - 4;
633
634 for i in (0..end).step_by(4) {
635 photon_image.raw_pixels[i] =
636 photon_image.raw_pixels[i].saturating_sub(brightness);
637 photon_image.raw_pixels[i + 1] =
638 photon_image.raw_pixels[i + 1].saturating_sub(brightness);
639 photon_image.raw_pixels[i + 2] =
640 photon_image.raw_pixels[i + 2].saturating_sub(brightness);
641 }
642}
643
644#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
660pub fn adjust_contrast(photon_image: &mut PhotonImage, contrast: f32) {
661 let mut img = helpers::dyn_image_from_raw(photon_image);
662
663 let clamped_contrast = contrast.clamp(-255.0, 255.0);
664
665 let factor =
669 (259.0 * (clamped_contrast + 255.0)) / (255.0 * (259.0 - clamped_contrast));
670 let mut lookup_table: Vec<u8> = vec![0; 256];
671 let offset = -128.0 * factor + 128.0;
672
673 for (i, table) in lookup_table.iter_mut().enumerate().take(256_usize) {
674 let new_val = i as f32 * factor + offset;
675 *table = new_val.clamp(0.0, 255.0) as u8;
676 }
677
678 for (x, y) in ImageIterator::with_dimension(&img.dimensions()) {
679 let mut px = img.get_pixel(x, y);
680 let channels = px.channels();
681
682 px = image::Rgba([
683 lookup_table[channels[0] as usize],
684 lookup_table[channels[1] as usize],
685 lookup_table[channels[2] as usize],
686 255,
687 ]);
688 img.put_pixel(x, y, px);
689 }
690
691 photon_image.raw_pixels = img.into_bytes();
692}
693
694#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
713pub fn tint(
714 photon_image: &mut PhotonImage,
715 r_offset: u32,
716 g_offset: u32,
717 b_offset: u32,
718) {
719 let mut img = helpers::dyn_image_from_raw(photon_image);
720
721 for (x, y) in ImageIterator::with_dimension(&img.dimensions()) {
722 let mut px = img.get_pixel(x, y);
723 let channels = px.channels();
724 let (r_val, g_val, b_val) =
725 (channels[0] as u32, channels[1] as u32, channels[2] as u32);
726
727 let new_r_val = if r_val + r_offset < 255 {
728 r_val as u8 + r_offset as u8
729 } else {
730 255
731 };
732 let new_g_val = if g_val + g_offset < 255 {
733 g_val as u8 + g_offset as u8
734 } else {
735 255
736 };
737 let new_b_val = if b_val + b_offset < 255 {
738 b_val as u8 + b_offset as u8
739 } else {
740 255
741 };
742
743 px = image::Rgba([new_r_val, new_g_val, new_b_val, 255]);
744
745 img.put_pixel(x, y, px);
746 }
747
748 let raw_pixels = img.into_bytes();
749 photon_image.raw_pixels = raw_pixels;
750}
751
752fn draw_horizontal_strips(photon_image: &mut PhotonImage, num_strips: u8, color: Rgb) {
753 let mut img = helpers::dyn_image_from_raw(photon_image);
754 let (width, height) = img.dimensions();
755
756 let total_strips = (num_strips * 2) - 1;
757 let height_strip = height / total_strips as u32;
758 let mut y_pos: u32 = 0;
759 for i in 1..num_strips {
760 draw_filled_rect_mut(
761 &mut img,
762 Rect::at(0, (y_pos + height_strip) as i32).of_size(width, height_strip),
763 Rgba([color.r, color.g, color.b, 255u8]),
764 );
765 y_pos = i as u32 * (height_strip * 2);
766 }
767
768 let raw_pixels = img.into_bytes();
769 photon_image.raw_pixels = raw_pixels;
770}
771
772#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
789pub fn horizontal_strips(photon_image: &mut PhotonImage, num_strips: u8) {
790 let color = Rgb {
791 r: 255,
792 g: 255,
793 b: 255,
794 };
795 draw_horizontal_strips(photon_image, num_strips, color)
796}
797
798#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
818pub fn color_horizontal_strips(
819 photon_image: &mut PhotonImage,
820 num_strips: u8,
821 color: Rgb,
822) {
823 draw_horizontal_strips(photon_image, num_strips, color)
824}
825
826fn draw_vertical_strips(photon_image: &mut PhotonImage, num_strips: u8, color: Rgb) {
827 let mut img = helpers::dyn_image_from_raw(photon_image);
828 let (width, height) = img.dimensions();
829
830 let total_strips = (num_strips * 2) - 1;
831 let width_strip = width / total_strips as u32;
832 let mut x_pos: u32 = 0;
833 for i in 1..num_strips {
834 draw_filled_rect_mut(
835 &mut img,
836 Rect::at((x_pos + width_strip) as i32, 0).of_size(width_strip, height),
837 Rgba([color.r, color.g, color.b, 255u8]),
838 );
839 x_pos = i as u32 * (width_strip * 2);
840 }
841
842 let raw_pixels = img.into_bytes();
843 photon_image.raw_pixels = raw_pixels;
844}
845
846#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
863pub fn vertical_strips(photon_image: &mut PhotonImage, num_strips: u8) {
864 let color = Rgb {
865 r: 255,
866 g: 255,
867 b: 255,
868 };
869 draw_vertical_strips(photon_image, num_strips, color)
870}
871
872#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
892pub fn color_vertical_strips(
893 photon_image: &mut PhotonImage,
894 num_strips: u8,
895 color: Rgb,
896) {
897 draw_vertical_strips(photon_image, num_strips, color)
898}
899
900struct Intensity {
901 val: i32,
902 r: i32,
903 g: i32,
904 b: i32,
905}
906#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
924pub fn oil(photon_image: &mut PhotonImage, radius: i32, intensity: f64) {
925 let img = helpers::dyn_image_from_raw(photon_image);
926 let (width, height) = img.dimensions();
927 let mut target = image::DynamicImage::new_rgba8(width, height);
928 let mut pixel_intensity_count: HashMap<usize, Intensity>;
929 let mut intensity_lut = vec![vec![0; width as usize]; height as usize];
930
931 for y in 0..height {
932 for x in 0..width {
933 let single_pix = img.get_pixel(x, y);
934 let current_val = single_pix.channels();
935 let avg = (current_val[0] as i32
936 + current_val[1] as i32
937 + current_val[2] as i32) as f64
938 / 3.0;
939 let val = (avg * intensity) / 255.0;
940 intensity_lut[y as usize][x as usize] = val.round() as usize;
941 }
942 }
943
944 for y in 0..height {
945 for x in 0..width {
946 pixel_intensity_count = HashMap::new();
947
948 for yy in -radius..=radius {
949 let yyy = (y as i32) + yy;
950 for xx in -radius..=radius {
951 let xxx = (x as i32) + xx;
952 if yyy > 0
953 && yyy < (height as i32)
954 && xxx > 0
955 && xxx < (width as i32)
956 {
957 let idx_x = xxx as usize;
958 let idx_y = yyy as usize;
959 let intensity_val = intensity_lut[idx_y][idx_x];
960 let single_pix = img.get_pixel(idx_x as u32, idx_y as u32);
961 let pix = single_pix.channels();
962 match pixel_intensity_count.get_mut(&(intensity_val)) {
963 Some(val) => {
964 val.val += 1;
965 val.r += pix[0] as i32;
966 val.g += pix[1] as i32;
967 val.b += pix[2] as i32;
968 }
969 None => {
970 pixel_intensity_count.insert(
971 intensity_val,
972 Intensity {
973 val: 1,
974 r: pix[0] as i32,
975 g: pix[1] as i32,
976 b: pix[2] as i32,
977 },
978 );
979 }
980 }
981 }
982 }
983 }
984
985 let mut map_vec: Vec<_> = pixel_intensity_count.iter().collect();
986 map_vec.sort_by(|a, b| (b.1.val - a.1.val).cmp(&0));
987
988 let cur_max = map_vec[0].1;
989 target.put_pixel(
990 x,
991 y,
992 Rgba::<u8>([
993 (cur_max.r / cur_max.val) as u8,
994 (cur_max.g / cur_max.val) as u8,
995 (cur_max.b / cur_max.val) as u8,
996 255,
997 ]),
998 )
999 }
1000 }
1001 let raw_pixels = target.into_bytes();
1002 photon_image.raw_pixels = raw_pixels;
1003}
1004#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
1020pub fn frosted_glass(photon_image: &mut PhotonImage) {
1021 let img_orig_buf = photon_image.get_raw_pixels();
1022 let width = photon_image.get_width();
1023 let height = photon_image.get_height();
1024 let end = img_orig_buf.len();
1025
1026 let mut img_buf = Vec::<u8>::new();
1027 Vec::resize(&mut img_buf, end, 0_u8);
1028
1029 let perlin = PerlinNoise2D::new(2, 10.0, 10.0, 10.0, 1.0, (100.0, 100.0), 0.5, 101);
1030
1031 for pixel in (0..end).step_by(4) {
1032 let x = (pixel / 4) / width as usize;
1033 let y = (pixel / 4) % width as usize;
1034
1035 let res = [
1036 perlin.get_noise(x as f64, y as f64) - 0.5,
1037 (perlin.get_noise(100.0 + x as f64, y as f64) - 0.5) * 4.0,
1038 ];
1039
1040 let x_new = f64::clamp(f64::floor(x as f64 + res[0]), 0.0, height as f64 - 1.0);
1041 let x_new = x_new as usize;
1042 let y_new = f64::clamp(f64::floor(y as f64 + res[1]), 0.0, width as f64 - 1.0);
1043 let y_new = y_new as usize;
1044
1045 let pixel_new = (x_new * width as usize + y_new) * 4;
1046 if pixel_new > end {
1047 continue;
1048 }
1049 img_buf[pixel] = img_orig_buf[pixel_new];
1050 img_buf[pixel + 1] = img_orig_buf[pixel_new + 1];
1051 img_buf[pixel + 2] = img_orig_buf[pixel_new + 2];
1052 img_buf[pixel + 3] = img_orig_buf[pixel_new + 3];
1053 }
1054
1055 photon_image.raw_pixels = img_buf;
1056}
1057
1058#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
1075pub fn pixelize(photon_image: &mut PhotonImage, pixel_size: i32) {
1076 let step_size = pixel_size.max(0) as usize;
1077 if step_size <= 1 {
1078 return;
1079 }
1080
1081 let buf = photon_image.raw_pixels.as_mut_slice();
1082 let width = photon_image.width as usize;
1083 let height = photon_image.height as usize;
1084
1085 for sy in (0..height).step_by(step_size) {
1086 let src_row_index = sy * width;
1087
1088 for sx in (0..width).step_by(step_size) {
1089 let src_index = 4 * (src_row_index + sx);
1090 let block_end_y = (sy + step_size).min(height);
1091 let block_end_x = (sx + step_size).min(width);
1092
1093 for dy in sy..block_end_y {
1094 let dst_row_index = dy * width;
1095
1096 for dx in sx..block_end_x {
1097 let dst_index = 4 * (dst_row_index + dx);
1098 buf[dst_index] = buf[src_index];
1099 buf[dst_index + 1] = buf[src_index + 1];
1100 buf[dst_index + 2] = buf[src_index + 2];
1101 buf[dst_index + 3] = buf[src_index + 3];
1102 }
1103 }
1104 }
1105 }
1106}
1107
1108#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
1125pub fn normalize(photon_image: &mut PhotonImage) {
1126 let buf = photon_image.raw_pixels.as_mut_slice();
1127 let buf_size = buf.len();
1128
1129 let mut min_rgb = Rgb::new(255, 255, 255);
1130 let mut max_rgb = Rgb::new(0, 0, 0);
1131
1132 for i in (0..buf_size).step_by(4) {
1133 let r = buf[i];
1134 let g = buf[i + 1];
1135 let b = buf[i + 2];
1136
1137 min_rgb.r = min_rgb.r.min(r);
1138 min_rgb.g = min_rgb.g.min(g);
1139 min_rgb.b = min_rgb.b.min(b);
1140
1141 max_rgb.r = max_rgb.r.max(r);
1142 max_rgb.g = max_rgb.g.max(g);
1143 max_rgb.b = max_rgb.b.max(b);
1144 }
1145
1146 let min_r = min_rgb.r as i32;
1147 let min_g = min_rgb.g as i32;
1148 let min_b = min_rgb.b as i32;
1149 let delta_r = (max_rgb.r as i32) - min_r;
1150 let delta_g = (max_rgb.g as i32) - min_g;
1151 let delta_b = (max_rgb.b as i32) - min_b;
1152
1153 for i in (0..buf_size).step_by(4) {
1154 let r = buf[i] as i32;
1155 let g = buf[i + 1] as i32;
1156 let b = buf[i + 2] as i32;
1157
1158 if delta_r > 0 {
1159 buf[i] = (((r - min_r) * 255) / delta_r) as u8;
1160 }
1161
1162 if delta_b > 0 {
1163 buf[i + 1] = (((g - min_g) * 255) / delta_g) as u8;
1164 }
1165
1166 if delta_b > 0 {
1167 buf[i + 2] = (((b - min_b) * 255) / delta_b) as u8;
1168 }
1169 }
1170}
1171
1172#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
1190pub fn dither(photon_image: &mut PhotonImage, depth: u32) {
1191 let width = photon_image.get_width() as usize;
1192 let height = photon_image.get_height() as usize;
1193 let buf = photon_image.raw_pixels.as_mut_slice();
1194 let channels = 4;
1195 let chan_stride = 1;
1196 let col_stride = chan_stride * channels;
1197 let row_stride = col_stride * width;
1198
1199 let depth = depth.clamp(1, 8);
1205 let num_colours = u16::pow(2, depth);
1206 let quant_rate = (256_u16 / num_colours) as u8;
1207 let mut lookup_table: Vec<u8> = vec![0; 256];
1208 for (tbl_idx, table) in lookup_table.iter_mut().enumerate().take(256_usize) {
1209 let downscaled_val = (tbl_idx as u8) / quant_rate;
1210 let upscaled_val = downscaled_val * quant_rate;
1211 *table = upscaled_val.clamp(0, 255);
1212 }
1213
1214 for row in 0..height - 1 {
1215 for col in 0..width - 1 {
1216 for chan in 0..channels - 1 {
1217 let buf_idx = row * row_stride + col * col_stride + chan * chan_stride;
1218 let old_pixel = buf[buf_idx];
1219 let new_pixel = lookup_table[old_pixel as usize];
1220
1221 buf[buf_idx] = new_pixel;
1222
1223 let quant_error = (old_pixel as i16) - (new_pixel as i16);
1224
1225 let buf_idx =
1226 row * row_stride + (col + 1) * col_stride + chan * chan_stride;
1227 let new_pixel = (buf[buf_idx] as i16) + (quant_error * 7) / 16;
1228 buf[buf_idx] = new_pixel.clamp(0, 255) as u8;
1229
1230 let buf_idx = (row + 1) * row_stride + col * col_stride - col_stride
1231 + chan * chan_stride;
1232 let new_pixel = (buf[buf_idx] as i16) + (quant_error * 3) / 16;
1233 buf[buf_idx] = new_pixel.clamp(0, 255) as u8;
1234
1235 let buf_idx =
1236 (row + 1) * row_stride + col * col_stride + chan * chan_stride;
1237 let new_pixel = (buf[buf_idx] as i16) + (quant_error * 5) / 16;
1238 buf[buf_idx] = new_pixel.clamp(0, 255) as u8;
1239
1240 let buf_idx =
1241 (row + 1) * row_stride + (col + 1) * col_stride + chan * chan_stride;
1242 let new_pixel = (buf[buf_idx] as i16) + quant_error / 16;
1243 buf[buf_idx] = new_pixel.clamp(0, 255) as u8;
1244 }
1245 }
1246 }
1247}
1248
1249fn create_gradient_map(color_a: Rgb, color_b: Rgb) -> Vec<Rgb> {
1250 let mut gradient_map = vec![Rgb::new(0, 0, 0); 256];
1251
1252 for (px, pos) in gradient_map.iter_mut().zip(0_u32..) {
1253 let inv_pos = 256 - pos;
1254
1255 px.r = (((color_a.r as u32) * inv_pos + (color_b.r as u32) * pos) / 256)
1256 .clamp(0, 255) as u8;
1257 px.g = (((color_a.g as u32) * inv_pos + (color_b.g as u32) * pos) / 256)
1258 .clamp(0, 255) as u8;
1259 px.b = (((color_a.b as u32) * inv_pos + (color_b.b as u32) * pos) / 256)
1260 .clamp(0, 255) as u8;
1261 }
1262
1263 gradient_map
1264}
1265
1266#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
1267pub fn duotone(photon_image: &mut PhotonImage, color_a: Rgb, color_b: Rgb) {
1268 let gradient_map = create_gradient_map(color_a, color_b);
1269 let buf = photon_image.raw_pixels.as_mut_slice();
1270
1271 for px in buf.chunks_mut(4) {
1272 let luma =
1274 (((px[0] as u32) * 2126 + (px[1] as u32) * 7152 + (px[2] as u32) * 722)
1275 / 10000)
1276 .clamp(0, 255);
1277
1278 let mapped_luma = &gradient_map[luma as usize];
1279 px[0] = mapped_luma.r;
1280 px[1] = mapped_luma.g;
1281 px[2] = mapped_luma.b;
1282 }
1283}