1use crate::channels::color_sim;
4use crate::iter::ImageIterator;
5use crate::{helpers, GenericImage, PhotonImage, Rgb};
6use image::DynamicImage::ImageRgba8;
7use image::Pixel as ImagePixel;
8use image::{DynamicImage, GenericImageView, RgbaImage};
9use palette::{Blend, Gradient, Lab, Lch, LinSrgba, Srgb, Srgba};
10use palette::{FromColor, IntoColor};
11use std::cmp::{max, min};
12
13#[cfg(feature = "enable_wasm")]
14use wasm_bindgen::prelude::*;
15
16#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
35pub fn watermark(img: &mut PhotonImage, watermark: &PhotonImage, x: i64, y: i64) {
36 let dyn_watermark: DynamicImage = crate::helpers::dyn_image_from_raw(watermark);
37 let mut dyn_img: DynamicImage = crate::helpers::dyn_image_from_raw(img);
38 image::imageops::overlay(&mut dyn_img, &dyn_watermark, x, y);
39 img.raw_pixels = dyn_img.into_bytes();
40}
41
42#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
66pub fn blend(
67 photon_image: &mut PhotonImage,
68 photon_image2: &PhotonImage,
69 blend_mode: &str,
70) {
71 let img = crate::helpers::dyn_image_from_raw(photon_image);
72 let img2 = crate::helpers::dyn_image_from_raw(photon_image2);
73
74 let (width, height) = img.dimensions();
75 let (width2, height2) = img2.dimensions();
76
77 if width > width2 || height > height2 {
78 panic!("First image parameter must be smaller than second image parameter. To fix, swap img and img2 params.");
79 }
80 let mut img = img.to_rgba8();
81 let img2 = img2.to_rgba8();
82
83 for (x, y) in ImageIterator::new(width, height) {
84 let pixel = img.get_pixel(x, y);
85 let pixel_img2 = img2.get_pixel(x, y);
86
87 let px_data = pixel.channels();
88 let px_data2 = pixel_img2.channels();
89
90 let color = LinSrgba::new(
94 px_data[0] as f32 / 255.0,
95 px_data[1] as f32 / 255.0,
96 px_data[2] as f32 / 255.0,
97 px_data[3] as f32 / 255.0,
98 )
99 .into_linear();
100
101 let color2 = LinSrgba::new(
102 px_data2[0] as f32 / 255.0,
103 px_data2[1] as f32 / 255.0,
104 px_data2[2] as f32 / 255.0,
105 px_data2[3] as f32 / 255.0,
106 )
107 .into_linear();
108
109 let blended = match blend_mode.to_lowercase().as_str() {
110 "overlay" => color.overlay(color2),
112 "over" => color2.over(color),
113 "atop" => color2.atop(color),
114 "xor" => color2.xor(color),
115 "plus" => color2.plus(color),
116 "multiply" => color2.multiply(color),
117 "burn" => color2.burn(color),
118 "difference" => color2.difference(color),
119 "soft_light" | "soft light" | "softlight" => color2.soft_light(color),
120 "screen" => color2.screen(color),
121 "hard_light" | "hard light" | "hardlight" => color2.hard_light(color),
122 "dodge" => color2.dodge(color),
123 "exclusion" => color2.exclusion(color),
124 "lighten" => color2.lighten(color),
125 "darken" => color2.darken(color),
126 _ => color2.overlay(color),
127 };
128 let components = blended.into_components();
129
130 img.put_pixel(
131 x,
132 y,
133 image::Rgba([
134 (components.0 * 255.0) as u8,
135 (components.1 * 255.0) as u8,
136 (components.2 * 255.0) as u8,
137 (components.3 * 255.0) as u8,
138 ]),
139 );
140 }
141 let dynimage = ImageRgba8(img);
142 photon_image.raw_pixels = dynimage.into_bytes();
143}
144
145pub fn replace_background(
182 photon_image: &mut PhotonImage,
183 img2: &PhotonImage,
184 background_color: &Rgb,
185) {
186 let mut img = helpers::dyn_image_from_raw(photon_image);
187 let img2 = helpers::dyn_image_from_raw(img2);
188
189 for (x, y) in ImageIterator::with_dimension(&img.dimensions()) {
190 let px = img.get_pixel(x, y);
191
192 let lab: Lab = Srgb::new(
194 background_color.r as f32 / 255.0,
195 background_color.g as f32 / 255.0,
196 background_color.b as f32 / 255.0,
197 )
198 .into_color();
199
200 let channels = px.channels();
201
202 let r_val: f32 = channels[0] as f32 / 255.0;
203 let g_val: f32 = channels[1] as f32 / 255.0;
204 let b_val: f32 = channels[2] as f32 / 255.0;
205
206 let px_lab: Lab = Srgb::new(r_val, g_val, b_val).into_color();
207
208 let sim = color_sim(lab, px_lab);
209
210 if sim < 20 {
212 img.put_pixel(x, y, img2.get_pixel(x, y));
213 } else {
214 img.put_pixel(x, y, px);
215 }
216 }
217 let raw_pixels = img.into_bytes();
218 photon_image.raw_pixels = raw_pixels;
219}
220
221#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
222pub fn create_gradient(width: u32, height: u32) -> PhotonImage {
223 let mut image = RgbaImage::new(width, height);
224
225 let grad1 = Gradient::new(vec![
227 LinSrgba::new(1.0, 0.1, 0.1, 1.0),
228 LinSrgba::new(0.1, 0.1, 1.0, 1.0),
229 LinSrgba::new(0.1, 1.0, 0.1, 1.0),
230 ]);
231
232 let _grad3 = Gradient::new(vec![
233 Lch::from_color(LinSrgba::new(1.0, 0.1, 0.1, 1.0)),
234 Lch::from_color(LinSrgba::new(0.1, 0.1, 1.0, 1.0)),
235 Lch::from_color(LinSrgba::new(0.1, 1.0, 0.1, 1.0)),
236 ]);
237
238 for (i, c1) in grad1.take(width as usize).enumerate() {
239 let c1: Srgba<f32> = Srgba::from_linear(c1).into_format();
240 {
241 let mut sub_image = image.sub_image(i as u32, 0, 1, height);
242 for (x, y) in ImageIterator::with_dimension(&sub_image.dimensions()) {
243 let components = c1.into_components();
244 sub_image.put_pixel(
245 x,
246 y,
247 image::Rgba([
248 (components.0 * 255.0) as u8,
249 (components.1 * 255.0) as u8,
250 (components.2 * 255.0) as u8,
251 255,
252 ]),
253 );
254 }
255 }
256 }
257 let rgba_img = ImageRgba8(image);
258 let raw_pixels = rgba_img.into_bytes();
259 PhotonImage {
260 raw_pixels,
261 width,
262 height,
263 }
264}
265
266#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
268pub fn apply_gradient(image: &mut PhotonImage) {
269 let gradient = create_gradient(image.width, image.height);
270
271 blend(image, &gradient, "overlay");
272}
273
274fn build_horizontal_gradient(
276 width: usize,
277 height: usize,
278 start_x: i32,
279 end_x: i32,
280) -> Vec<f32> {
281 let min_x = min(start_x, end_x);
282 let max_x = max(start_x, end_x);
283 let total_grad_len = max_x - min_x;
284 let total_size = width * height;
285 let mut gradient = std::iter::repeat(0.0).take(total_size).collect::<Vec<_>>();
286 if total_grad_len <= 0 {
287 return gradient;
289 }
290
291 for row in 0..height {
293 for col in 0..min_x {
294 let pos = row * width + col as usize;
295 gradient[pos] = 0.0;
296 }
297 }
298
299 let first_col = max(max_x, 0) as usize;
302 for row in 0..height {
303 for col in first_col..width {
304 let pos = row * width + col;
305 gradient[pos] = 1.0;
306 }
307 }
308
309 let first_col = max(min_x, 0);
312 let last_col = min(max_x, width as i32);
313 for row in 0..height {
314 for col in first_col..last_col {
315 let pos = row * width + col as usize;
316 let total_len_f32 = total_grad_len as f32;
317 let column_f32 = (col - min_x) as f32;
318 gradient[pos] = (column_f32 / total_len_f32).clamp(0.0, 1.0);
319 }
320 }
321
322 if start_x > end_x {
324 gradient.iter_mut().for_each(|grad| *grad = 1.0 - *grad);
325 }
326
327 gradient
328}
329
330fn build_vertical_gradient(
332 width: usize,
333 height: usize,
334 start_y: i32,
335 end_y: i32,
336) -> Vec<f32> {
337 let min_y = min(start_y, end_y);
338 let max_y = max(start_y, end_y);
339 let total_grad_len = max_y - min_y;
340 let total_size = width * height;
341 let mut gradient = std::iter::repeat(0.0).take(total_size).collect::<Vec<_>>();
342 if total_grad_len <= 0 {
343 return gradient;
345 }
346
347 for row in 0..min_y {
349 for col in 0..width {
350 let pos = (row as usize) * width + col;
351 gradient[pos] = 0.0;
352 }
353 }
354
355 let first_row = max(max_y, 0) as usize;
358 for row in first_row..height {
359 for col in 0..width {
360 let pos = row * width + col;
361 gradient[pos] = 1.0;
362 }
363 }
364
365 let first_row = max(min_y, 0);
368 let last_row = min(max_y, height as i32);
369 for row in first_row..last_row {
370 for col in 0..width {
371 let pos = (row as usize) * width + col;
372 let total_len_f32 = total_grad_len as f32;
373 let row_f32 = (row - min_y) as f32;
374 gradient[pos] = (row_f32 / total_len_f32).clamp(0.0, 1.0);
375 }
376 }
377
378 if start_y > end_y {
380 gradient.iter_mut().for_each(|grad| *grad = 1.0 - *grad);
381 }
382
383 gradient
384}
385
386fn build_axial_gradient(
388 width: usize,
389 height: usize,
390 start_x: i32,
391 end_x: i32,
392 start_y: i32,
393 end_y: i32,
394) -> Vec<f32> {
395 let len_x = (end_x - start_x) as f32;
396 let len_y = (end_y - start_y) as f32;
397 let total_grad_len = (len_x * len_x + len_y * len_y).sqrt();
398
399 let total_size = width * height;
400 let mut gradient = std::iter::repeat(0.0).take(total_size).collect::<Vec<_>>();
401 if total_grad_len <= 0.0 {
402 return gradient;
404 }
405
406 let min_x = min(start_x, end_x) as f32;
407 let max_x = max(start_x, end_x) as f32;
408 let min_y = min(start_y, end_y) as f32;
409 let max_y = max(start_y, end_y) as f32;
410 let len_x_sq = len_x * len_x;
411 let len_y_sq = len_y * len_y;
412 let start_x_f32 = start_x as f32;
413 let start_y_f32 = start_y as f32;
414
415 for row in 0..height {
422 for col in 0..width {
423 let pos = row * width + col;
424 let col_f32 = col as f32;
425 let row_f32 = row as f32;
426 let foot_x = (start_x_f32 * len_y_sq
427 + col_f32 * len_x_sq
428 + len_x * len_y * (row_f32 - start_y_f32))
429 / (len_y_sq + len_x_sq);
430 let foot_y = (len_x * (col_f32 - foot_x)) / len_y + row_f32;
431
432 if min_x <= foot_x && foot_x <= max_x && min_y <= foot_y && foot_y <= max_y {
434 let norm_x = foot_x - start_x_f32;
435 let norm_y = foot_y - start_y_f32;
436 let grad_dist = (norm_x * norm_x + norm_y * norm_y).sqrt();
437 let total_len_f32 = total_grad_len;
438 gradient[pos] = (grad_dist / total_len_f32).clamp(0.0, 1.0);
439 } else {
440 let fill_bottom_right =
441 start_x < end_x && start_y < end_y && foot_x > max_x;
442 let fill_bottom_left =
443 start_x > end_x && start_y < end_y && foot_x < min_x;
444 let fill_top_right =
445 start_x < end_x && start_y > end_y && foot_y < min_y;
446 let fill_top_left = start_x > end_x && start_y > end_y && foot_y < min_y;
447 if fill_bottom_right
448 || fill_bottom_left
449 || fill_top_right
450 || fill_top_left
451 {
452 gradient[pos] = 1.0;
453 }
454 }
455 }
456 }
457
458 gradient
459}
460
461pub fn fade(
485 img1: &PhotonImage,
486 img2: &PhotonImage,
487 start_x: i32,
488 end_x: i32,
489 start_y: i32,
490 end_y: i32,
491) -> PhotonImage {
492 if img1.width != img2.width || img1.height != img2.height {
493 panic!("Images must have the same size.");
494 }
495
496 let width = img1.width as usize;
497 let height = img1.height as usize;
498
499 let buf_img1 = &img1.raw_pixels;
500 let buf_img2 = &img2.raw_pixels;
501 let mut buf_res = Vec::with_capacity(width * height * 4);
502
503 let gradient = if end_y == start_y {
505 build_horizontal_gradient(width, height, start_x, end_x)
506 } else if start_x == end_x {
507 build_vertical_gradient(width, height, start_y, end_y)
508 } else {
509 build_axial_gradient(width, height, start_x, end_x, start_y, end_y)
510 };
511
512 for row in 0..height {
513 for col in 0..width {
514 let grad_idx = row * width + col;
515 let opacity_img1 = gradient[grad_idx];
516 let opacity_img2 = 1.0 - opacity_img1;
517
518 let buf_idx = row * width * 4 + col * 4;
519
520 let img1_r = buf_img1[buf_idx] as f32;
521 let img1_g = buf_img1[buf_idx + 1] as f32;
522 let img1_b = buf_img1[buf_idx + 2] as f32;
523
524 let img2_r = buf_img2[buf_idx] as f32;
525 let img2_g = buf_img2[buf_idx + 1] as f32;
526 let img2_b = buf_img2[buf_idx + 2] as f32;
527
528 let res_r = ((img1_r * opacity_img1) + (img2_r * opacity_img2))
529 .clamp(0.0, 255.0) as u8;
530 let res_g = ((img1_g * opacity_img1) + (img2_g * opacity_img2))
531 .clamp(0.0, 255.0) as u8;
532 let res_b = ((img1_b * opacity_img1) + (img2_b * opacity_img2))
533 .clamp(0.0, 255.0) as u8;
534
535 let res_a = 255;
537
538 buf_res.push(res_r);
539 buf_res.push(res_g);
540 buf_res.push(res_b);
541 buf_res.push(res_a);
542 }
543 }
544
545 PhotonImage::new(buf_res, img1.width, img1.height)
546}