photon_rs/
colour_spaces.rs

1//! Image manipulation effects in HSL, HSLuv, LCh and HSV.
2
3use crate::iter::ImageIterator;
4use crate::{helpers, PhotonImage, Rgb};
5use image::GenericImageView;
6use image::Pixel as ImagePixel;
7use palette::{FromColor, IntoColor};
8use palette::{Hsla, Hsluva, Hsva, Hue, Lcha, Saturate, Shade, Srgba};
9
10#[cfg(feature = "enable_wasm")]
11use wasm_bindgen::prelude::*;
12
13/// Applies gamma correction to an image.
14/// # Arguments
15/// * `photon_image` - A PhotonImage that contains a view into the image.
16/// * `red` - Gamma value for red channel.
17/// * `green` - Gamma value for green channel.
18/// * `blue` - Gamma value for blue channel.
19/// # Example
20///
21/// ```no_run
22/// // For example, to turn an image of type `PhotonImage` into a gamma corrected image:
23/// use photon_rs::colour_spaces::gamma_correction;
24/// use photon_rs::native::open_image;
25///
26/// let mut img = open_image("img.jpg").expect("File should open");
27/// gamma_correction(&mut img, 2.2, 2.2, 2.2);
28/// ```
29///
30#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
31pub fn gamma_correction(
32    photon_image: &mut PhotonImage,
33    red: f32,
34    green: f32,
35    blue: f32,
36) {
37    let buf = photon_image.raw_pixels.as_mut_slice();
38    let buf_size = buf.len();
39
40    // Initialize gamma arrays
41    let mut gamma_r: Vec<u8> = vec![0; 256];
42    let mut gamma_g: Vec<u8> = vec![0; 256];
43    let mut gamma_b: Vec<u8> = vec![0; 256];
44
45    let inv_red = 1.0 / red;
46    let inv_green = 1.0 / green;
47    let inv_blue = 1.0 / blue;
48
49    // Set values within gamma arrays
50    for i in 0..256 {
51        let input = (i as f32) / 255.0;
52        gamma_r[i] = (255.0 * input.powf(inv_red) + 0.5).clamp(0.0, 255.0) as u8;
53        gamma_g[i] = (255.0 * input.powf(inv_green) + 0.5).clamp(0.0, 255.0) as u8;
54        gamma_b[i] = (255.0 * input.powf(inv_blue) + 0.5).clamp(0.0, 255.0) as u8;
55    }
56
57    // Apply gamma correction
58    for i in (0..buf_size).step_by(4) {
59        let r = buf[i];
60        let g = buf[i + 1];
61        let b = buf[i + 2];
62
63        buf[i] = gamma_r[r as usize];
64        buf[i + 1] = gamma_g[g as usize];
65        buf[i + 2] = gamma_b[b as usize];
66    }
67}
68
69/// Image manipulation effects in the HSLuv colour space
70///
71/// Effects include:
72/// * **saturate** - Saturation increase.
73/// * **desaturate** - Desaturate the image.
74/// * **shift_hue** - Hue rotation by a specified number of degrees.
75/// * **darken** - Decrease the brightness.
76/// * **lighten** - Increase the brightness.
77///
78/// # Arguments
79/// * `photon_image` - A PhotonImage.
80/// * `mode` - The effect desired to be applied. Choose from: `saturate`, `desaturate`, `shift_hue`, `darken`, `lighten`
81/// * `amt` - A float value from 0 to 1 which represents the amount the effect should be increased by.
82/// # Example
83/// ```no_run
84/// // For example to increase the saturation by 10%:
85/// use photon_rs::colour_spaces::hsluv;
86/// use photon_rs::native::open_image;
87///
88/// // Open the image. A PhotonImage is returned.
89/// let mut img = open_image("img.jpg").expect("File should open");
90/// hsluv(&mut img, "saturate", 0.1_f32);
91/// ```
92#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
93pub fn hsluv(photon_image: &mut PhotonImage, mode: &str, amt: f32) {
94    let img = helpers::dyn_image_from_raw(photon_image);
95    let (width, height) = img.dimensions();
96    let mut img = img.to_rgba8();
97
98    for (x, y) in ImageIterator::new(width, height) {
99        let px_data = img.get_pixel(x, y).channels();
100        let hsluv_color: Hsluva = Srgba::new(
101            px_data[0] as f32 / 255.0,
102            px_data[1] as f32 / 255.0,
103            px_data[2] as f32 / 255.0,
104            px_data[3] as f32 / 255.0,
105        )
106        .into_linear()
107        .into_color();
108
109        let new_color = match mode {
110            // Match a single value
111            "desaturate" => hsluv_color.desaturate(amt),
112            "saturate" => hsluv_color.saturate(amt),
113            "lighten" => hsluv_color.lighten(amt),
114            "darken" => hsluv_color.darken(amt),
115            "shift_hue" => hsluv_color.shift_hue(amt * 360.0),
116            _ => hsluv_color.saturate(amt),
117        };
118        let final_color: Srgba =
119            Srgba::from_linear(new_color.into_color()).into_format();
120
121        let components = final_color.into_components();
122
123        img.put_pixel(
124            x,
125            y,
126            image::Rgba([
127                (components.0 * 255.0) as u8,
128                (components.1 * 255.0) as u8,
129                (components.2 * 255.0) as u8,
130                (components.3 * 255.0) as u8,
131            ]),
132        );
133    }
134    photon_image.raw_pixels = img.to_vec();
135}
136
137/// Image manipulation effects in the LCh colour space
138///
139/// Effects include:
140/// * **saturate** - Saturation increase.
141/// * **desaturate** - Desaturate the image.
142/// * **shift_hue** - Hue rotation by a specified number of degrees.
143/// * **darken** - Decrease the brightness.
144/// * **lighten** - Increase the brightness.
145///
146/// # Arguments
147/// * `photon_image` - A PhotonImage.
148/// * `mode` - The effect desired to be applied. Choose from: `saturate`, `desaturate`, `shift_hue`, `darken`, `lighten`
149/// * `amt` - A float value from 0 to 1 which represents the amount the effect should be increased by.
150/// # Example
151/// ```no_run
152/// // For example to increase the saturation by 10%:
153/// use photon_rs::colour_spaces::lch;
154/// use photon_rs::native::open_image;
155///
156/// // Open the image. A PhotonImage is returned.
157/// let mut img = open_image("img.jpg").expect("File should open");
158/// lch(&mut img, "saturate", 0.1_f32);
159/// ```
160#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
161pub fn lch(photon_image: &mut PhotonImage, mode: &str, amt: f32) {
162    let img = helpers::dyn_image_from_raw(photon_image);
163    let (width, height) = img.dimensions();
164    let mut img = img.to_rgba8();
165
166    for (x, y) in ImageIterator::new(width, height) {
167        let px_data = img.get_pixel(x, y).channels();
168        let lch_colour: Lcha = Srgba::new(
169            px_data[0] as f32 / 255.0,
170            px_data[1] as f32 / 255.0,
171            px_data[2] as f32 / 255.0,
172            px_data[3] as f32 / 255.0,
173        )
174        .into_linear()
175        .into_color();
176
177        let new_color = match mode {
178            // Match a single value
179            "desaturate" => lch_colour.desaturate(amt),
180            "saturate" => lch_colour.saturate(amt),
181            "lighten" => lch_colour.lighten(amt),
182            "darken" => lch_colour.darken(amt),
183            "shift_hue" => lch_colour.shift_hue(amt * 360.0),
184            _ => lch_colour.saturate(amt),
185        };
186        let final_color: Srgba =
187            Srgba::from_linear(new_color.into_color()).into_format();
188
189        let components = final_color.into_components();
190
191        img.put_pixel(
192            x,
193            y,
194            image::Rgba([
195                (components.0 * 255.0) as u8,
196                (components.1 * 255.0) as u8,
197                (components.2 * 255.0) as u8,
198                (components.3 * 255.0) as u8,
199            ]),
200        );
201    }
202    photon_image.raw_pixels = img.to_vec();
203}
204
205/// Image manipulation effects in the HSL colour space.
206///
207/// Effects include:
208/// * **saturate** - Saturation increase.
209/// * **desaturate** - Desaturate the image.
210/// * **shift_hue** - Hue rotation by a specified number of degrees.
211/// * **darken** - Decrease the brightness.
212/// * **lighten** - Increase the brightness.
213///
214/// # Arguments
215/// * `photon_image` - A PhotonImage.
216/// * `mode` - The effect desired to be applied. Choose from: `saturate`, `desaturate`, `shift_hue`, `darken`, `lighten`
217/// * `amt` - A float value from 0 to 1 which represents the amount the effect should be increased by.
218/// # Example
219/// ```no_run
220/// // For example to increase the saturation by 10%:
221/// use photon_rs::colour_spaces::hsl;
222/// use photon_rs::native::open_image;
223///
224/// // Open the image. A PhotonImage is returned.
225/// let mut img = open_image("img.jpg").expect("File should open");
226/// hsl(&mut img, "saturate", 0.1_f32);
227/// ```
228#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
229pub fn hsl(photon_image: &mut PhotonImage, mode: &str, amt: f32) {
230    // The function logic is kept separate from other colour spaces for now,
231    // since other HSL-specific logic may be implemented here, which isn't available in other colour spaces
232    let mut img = helpers::dyn_image_from_raw(photon_image).to_rgba8();
233    for (x, y) in ImageIterator::with_dimension(&img.dimensions()) {
234        let px_data = img.get_pixel(x, y).channels();
235
236        let colour = Srgba::new(
237            px_data[0] as f32 / 255.0,
238            px_data[1] as f32 / 255.0,
239            px_data[2] as f32 / 255.0,
240            px_data[3] as f32 / 255.0,
241        );
242
243        let hsl_colour = Hsla::from_color(colour);
244
245        let new_color = match mode {
246            // Match a single value
247            "desaturate" => hsl_colour.desaturate(amt),
248            "saturate" => hsl_colour.saturate(amt),
249            "lighten" => hsl_colour.lighten(amt),
250            "darken" => hsl_colour.darken(amt),
251            "shift_hue" => hsl_colour.shift_hue(amt * 360.0),
252            _ => hsl_colour.saturate(amt),
253        };
254        let final_color = Srgba::from_color(new_color);
255
256        let components = final_color.into_components();
257
258        img.put_pixel(
259            x,
260            y,
261            image::Rgba([
262                (components.0 * 255.0) as u8,
263                (components.1 * 255.0) as u8,
264                (components.2 * 255.0) as u8,
265                (components.3 * 255.0) as u8,
266            ]),
267        );
268    }
269
270    photon_image.raw_pixels = img.to_vec();
271}
272
273/// Image manipulation in the HSV colour space.
274///
275/// Effects include:
276/// * **saturate** - Saturation increase.
277/// * **desaturate** - Desaturate the image.
278/// * **shift_hue** - Hue rotation by a specified number of degrees.
279/// * **darken** - Decrease the brightness.
280/// * **lighten** - Increase the brightness.
281///
282/// # Arguments
283/// * `photon_image` - A PhotonImage.
284/// * `mode` - The effect desired to be applied. Choose from: `saturate`, `desaturate`, `shift_hue`, `darken`, `lighten`
285/// * `amt` - A float value from 0 to 1 which represents the amount the effect should be increased by.
286///
287/// # Example
288/// ```no_run
289/// // For example to increase the saturation by 10%:
290/// use photon_rs::colour_spaces::hsv;
291/// use photon_rs::native::open_image;
292///
293/// // Open the image. A PhotonImage is returned.
294/// let mut img = open_image("img.jpg").expect("File should open");
295/// hsv(&mut img, "saturate", 0.1_f32);
296/// ```
297#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
298pub fn hsv(photon_image: &mut PhotonImage, mode: &str, amt: f32) {
299    let img = helpers::dyn_image_from_raw(photon_image);
300    let (width, height) = img.dimensions();
301    let mut img = img.to_rgba8();
302
303    for (x, y) in ImageIterator::new(width, height) {
304        let px_data = img.get_pixel(x, y).channels();
305
306        let color = Srgba::new(
307            px_data[0] as f32 / 255.0,
308            px_data[1] as f32 / 255.0,
309            px_data[2] as f32 / 255.0,
310            px_data[3] as f32 / 255.0,
311        );
312
313        let hsv_colour = Hsva::from_color(color);
314
315        let new_color = match mode {
316            // Match a single value
317            "desaturate" => hsv_colour.desaturate(amt),
318            "saturate" => hsv_colour.saturate(amt),
319            "lighten" => hsv_colour.lighten(amt),
320            "darken" => hsv_colour.darken(amt),
321            "shift_hue" => hsv_colour.shift_hue(amt * 360.0),
322            _ => hsv_colour.saturate(amt),
323        };
324
325        let srgba_new_color = Srgba::from_color(new_color);
326        // let final_color: Srgba = Srgba::from_linear(srgba_new_color).into_format();
327
328        let components = srgba_new_color.into_components();
329
330        img.put_pixel(
331            x,
332            y,
333            image::Rgba([
334                (components.0 * 255.0) as u8,
335                (components.1 * 255.0) as u8,
336                (components.2 * 255.0) as u8,
337                (components.3 * 255.0) as u8,
338            ]),
339        );
340    }
341    photon_image.raw_pixels = img.to_vec();
342}
343
344/// Shift hue by a specified number of degrees in the HSL colour space.
345/// # Arguments
346/// * `img` - A PhotonImage.
347/// * `mode` - A float value from 0 to 1 which is the amount to shift the hue by, or hue rotate by.
348///
349/// # Example
350/// ```no_run
351/// // For example to hue rotate/shift the hue by 120 degrees in the HSL colour space:
352/// use photon_rs::colour_spaces::hue_rotate_hsl;
353/// use photon_rs::native::open_image;
354///
355/// // Open the image. A PhotonImage is returned.
356/// let mut img = open_image("img.jpg").expect("File should open");
357/// hue_rotate_hsl(&mut img, 120_f32);
358/// ```
359#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
360pub fn hue_rotate_hsl(img: &mut PhotonImage, degrees: f32) {
361    hsl(img, "shift_hue", degrees);
362}
363
364/// Shift hue by a specified number of degrees in the HSV colour space.
365/// # Arguments
366/// * `img` - A PhotonImage.
367/// * `mode` - A float value from 0 to 1 which is the amount to shift the hue by, or hue rotate by.
368///
369/// # Example
370/// ```no_run
371/// // For example to hue rotate/shift the hue by 120 degrees in the HSV colour space:
372/// use photon_rs::colour_spaces::hue_rotate_hsv;
373/// use photon_rs::native::open_image;
374///
375/// // Open the image. A PhotonImage is returned.
376/// let mut img = open_image("img.jpg").expect("File should open");
377/// hue_rotate_hsv(&mut img, 120_f32);
378/// ```
379#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
380pub fn hue_rotate_hsv(img: &mut PhotonImage, degrees: f32) {
381    hsv(img, "shift_hue", degrees);
382}
383
384/// Shift hue by a specified number of degrees in the LCh colour space.
385/// # Arguments
386/// * `img` - A PhotonImage.
387/// * `mode` - A float value from 0 to 1 which is the amount to shift the hue by, or hue rotate by.
388///
389/// # Example
390/// ```no_run
391/// // For example to hue rotate/shift the hue by 120 degrees in the HSL colour space:
392/// use photon_rs::colour_spaces::hue_rotate_lch;
393/// use photon_rs::native::open_image;
394///
395/// // Open the image. A PhotonImage is returned.
396/// let mut img = open_image("img.jpg").expect("File should open");
397/// hue_rotate_lch(&mut img, 120_f32);
398/// ```
399#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
400pub fn hue_rotate_lch(img: &mut PhotonImage, degrees: f32) {
401    lch(img, "shift_hue", degrees)
402}
403
404/// Shift hue by a specified number of degrees in the HSLuv colour space.
405/// # Arguments
406/// * `img` - A PhotonImage.
407/// * `mode` - A float value from 0 to 1 which is the amount to shift the hue by, or hue rotate by.
408///
409/// # Example
410/// ```no_run
411/// // For example to hue rotate/shift the hue by 120 degrees in the HSL colour space:
412/// use photon_rs::colour_spaces::hue_rotate_hsluv;
413/// use photon_rs::native::open_image;
414///
415/// // Open the image. A PhotonImage is returned.
416/// let mut img = open_image("img.jpg").expect("File should open");
417/// hue_rotate_hsluv(&mut img, 120_f32);
418/// ```
419#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
420pub fn hue_rotate_hsluv(img: &mut PhotonImage, degrees: f32) {
421    hsluv(img, "shift_hue", degrees)
422}
423
424/// Increase the image's saturation by converting each pixel's colour to the HSL colour space
425/// and increasing the colour's saturation.
426/// # Arguments
427/// * `img` - A PhotonImage.
428/// * `level` - Float value from 0 to 1 representing the level to which to increase the saturation by.
429/// The `level` must be from 0 to 1 in floating-point, `f32` format.
430/// Increasing saturation by 80% would be represented by a `level` of 0.8
431///
432/// # Example
433/// ```no_run
434/// // For example to increase saturation by 10% in the HSL colour space:
435/// use photon_rs::colour_spaces::saturate_hsl;
436/// use photon_rs::native::open_image;
437///
438/// // Open the image. A PhotonImage is returned.
439/// let mut img = open_image("img.jpg").expect("File should open");
440/// saturate_hsl(&mut img, 0.1_f32);
441/// ```
442#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
443pub fn saturate_hsl(img: &mut PhotonImage, level: f32) {
444    hsl(img, "saturate", level)
445}
446
447/// Increase the image's saturation in the LCh colour space.
448/// # Arguments
449/// * `img` - A PhotonImage.
450/// * `level` - Float value from 0 to 1 representing the level to which to increase the saturation by.
451/// The `level` must be from 0 to 1 in floating-point, `f32` format.
452/// Increasing saturation by 80% would be represented by a `level` of 0.8
453///
454/// # Example
455/// ```no_run
456/// // For example to increase saturation by 40% in the Lch colour space:
457/// use photon_rs::colour_spaces::saturate_lch;
458/// use photon_rs::native::open_image;
459///
460/// // Open the image. A PhotonImage is returned.
461/// let mut img = open_image("img.jpg").expect("File should open");
462/// saturate_lch(&mut img, 0.4_f32);
463/// ```
464#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
465pub fn saturate_lch(img: &mut PhotonImage, level: f32) {
466    lch(img, "saturate", level)
467}
468
469/// Increase the image's saturation in the HSLuv colour space.
470/// # Arguments
471/// * `img` - A PhotonImage.
472/// * `level` - Float value from 0 to 1 representing the level to which to increase the saturation by.
473/// The `level` must be from 0 to 1 in floating-point, `f32` format.
474/// Increasing saturation by 80% would be represented by a `level` of 0.8
475///
476/// # Example
477/// ```no_run
478/// // For example to increase saturation by 40% in the HSLuv colour space:
479/// use photon_rs::colour_spaces::saturate_hsluv;
480/// use photon_rs::native::open_image;
481///
482/// // Open the image. A PhotonImage is returned.
483/// let mut img = open_image("img.jpg").expect("File should open");
484/// saturate_hsluv(&mut img, 0.4_f32);
485/// ```
486#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
487pub fn saturate_hsluv(img: &mut PhotonImage, level: f32) {
488    hsluv(img, "saturate", level)
489}
490
491/// Increase the image's saturation in the HSV colour space.
492/// # Arguments
493/// * `img` - A PhotonImage.
494/// * `level` - Float value from 0 to 1 representing the level by which to increase the saturation by.
495/// The `level` must be from 0 to 1 in floating-point, `f32` format.
496/// Increasing saturation by 80% would be represented by a `level` of 0.8
497///
498/// # Example
499/// ```no_run
500/// // For example to increase saturation by 30% in the HSV colour space:
501/// use photon_rs::colour_spaces::saturate_hsv;
502/// use photon_rs::native::open_image;
503///
504/// // Open the image. A PhotonImage is returned.
505/// let mut img = open_image("img.jpg").expect("File should open");
506/// saturate_hsv(&mut img, 0.3_f32);
507/// ```
508#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
509pub fn saturate_hsv(img: &mut PhotonImage, level: f32) {
510    hsv(img, "saturate", level)
511}
512
513/// Lighten an image by a specified amount in the LCh colour space.
514///
515/// # Arguments
516/// * `img` - A PhotonImage.
517/// * `level` - Float value from 0 to 1 representing the level to which to lighten the image by.
518/// The `level` must be from 0 to 1 in floating-point, `f32` format.
519/// Lightening by 80% would be represented by a `level` of 0.8
520///
521/// # Example
522/// ```no_run
523/// // For example to lighten an image by 10% in the LCh colour space:
524/// use photon_rs::colour_spaces::lighten_lch;
525/// use photon_rs::native::open_image;
526///
527/// // Open the image. A PhotonImage is returned.
528/// let mut img = open_image("img.jpg").expect("File should open");
529/// lighten_lch(&mut img, 0.1_f32);
530/// ```
531#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
532pub fn lighten_lch(img: &mut PhotonImage, level: f32) {
533    lch(img, "lighten", level)
534}
535
536/// Lighten an image by a specified amount in the HSLuv colour space.
537///
538/// # Arguments
539/// * `img` - A PhotonImage.
540/// * `level` - Float value from 0 to 1 representing the level to which to lighten the image by.
541/// The `level` must be from 0 to 1 in floating-point, `f32` format.
542/// Lightening by 80% would be represented by a `level` of 0.8
543///
544/// # Example
545/// ```no_run
546/// // For example to lighten an image by 10% in the HSLuv colour space:
547/// use photon_rs::colour_spaces::lighten_hsluv;
548/// use photon_rs::native::open_image;
549///
550/// // Open the image. A PhotonImage is returned.
551/// let mut img = open_image("img.jpg").expect("File should open");
552/// lighten_hsluv(&mut img, 0.1_f32);
553/// ```
554#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
555pub fn lighten_hsluv(img: &mut PhotonImage, level: f32) {
556    hsluv(img, "lighten", level)
557}
558
559/// Lighten an image by a specified amount in the HSL colour space.
560/// # Arguments
561/// * `img` - A PhotonImage.
562/// * `level` - Float value from 0 to 1 representing the level to which to lighten the image by.
563/// The `level` must be from 0 to 1 in floating-point, `f32` format.
564/// Lightening by 80% would be represented by a `level` of 0.8
565///
566/// # Example
567/// ```no_run
568/// // For example to lighten an image by 10% in the HSL colour space:
569/// use photon_rs::colour_spaces::lighten_hsl;
570/// use photon_rs::native::open_image;
571///
572/// // Open the image. A PhotonImage is returned.
573/// let mut img = open_image("img.jpg").expect("File should open");
574/// lighten_hsl(&mut img, 0.1_f32);
575/// ```
576#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
577pub fn lighten_hsl(img: &mut PhotonImage, level: f32) {
578    hsl(img, "lighten", level)
579}
580
581/// Lighten an image by a specified amount in the HSV colour space.
582///
583/// # Arguments
584/// * `img` - A PhotonImage.
585/// * `level` - Float value from 0 to 1 representing the level to which to lighten the image by.
586/// The `level` must be from 0 to 1 in floating-point, `f32` format.
587/// Lightening by 80% would be represented by a `level` of 0.8
588///
589/// # Example
590/// ```no_run
591/// // For example to lighten an image by 10% in the HSV colour space:
592/// use photon_rs::colour_spaces::lighten_hsv;
593/// use photon_rs::native::open_image;
594///
595/// // Open the image. A PhotonImage is returned.
596/// let mut img = open_image("img.jpg").expect("File should open");
597/// lighten_hsv(&mut img, 0.1_f32);
598/// ```
599#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
600pub fn lighten_hsv(img: &mut PhotonImage, level: f32) {
601    hsv(img, "lighten", level)
602}
603
604/// Darken the image by a specified amount in the LCh colour space.
605///
606/// # Arguments
607/// * `img` - A PhotonImage.
608/// * `level` - Float value from 0 to 1 representing the level to which to darken the image by.
609/// The `level` must be from 0 to 1 in floating-point, `f32` format.
610/// Darkening by 80% would be represented by a `level` of 0.8
611///
612/// # Example
613/// ```no_run
614/// // For example to darken an image by 10% in the LCh colour space:
615/// use photon_rs::colour_spaces::darken_lch;
616/// use photon_rs::native::open_image;
617///
618/// // Open the image. A PhotonImage is returned.
619/// let mut img = open_image("img.jpg").expect("File should open");
620/// darken_lch(&mut img, 0.1_f32);
621/// ```
622#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
623pub fn darken_lch(img: &mut PhotonImage, level: f32) {
624    lch(img, "darken", level)
625}
626
627/// Darken the image by a specified amount in the HSLuv colour space.
628///
629/// # Arguments
630/// * `img` - A PhotonImage.
631/// * `level` - Float value from 0 to 1 representing the level to which to darken the image by.
632/// The `level` must be from 0 to 1 in floating-point, `f32` format.
633/// Darkening by 80% would be represented by a `level` of 0.8
634///
635/// # Example
636/// ```no_run
637/// // For example to darken an image by 10% in the HSLuv colour space:
638/// use photon_rs::colour_spaces::darken_hsluv;
639/// use photon_rs::native::open_image;
640///
641/// // Open the image. A PhotonImage is returned.
642/// let mut img = open_image("img.jpg").expect("File should open");
643/// darken_hsluv(&mut img, 0.1_f32);
644/// ```
645#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
646pub fn darken_hsluv(img: &mut PhotonImage, level: f32) {
647    hsluv(img, "darken", level)
648}
649
650/// Darken the image by a specified amount in the HSL colour space.
651///
652/// # Arguments
653/// * `img` - A PhotonImage.
654/// * `level` - Float value from 0 to 1 representing the level to which to darken the image by.
655/// The `level` must be from 0 to 1 in floating-point, `f32` format.
656/// Darkening by 80% would be represented by a `level` of 0.8
657///
658/// # Example
659/// ```no_run
660/// // For example to darken an image by 10% in the HSL colour space:
661/// use photon_rs::colour_spaces::darken_hsl;
662/// use photon_rs::native::open_image;
663///
664/// // Open the image. A PhotonImage is returned.
665/// let mut img = open_image("img.jpg").expect("File should open");
666/// darken_hsl(&mut img, 0.1_f32);
667/// ```
668#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
669pub fn darken_hsl(img: &mut PhotonImage, level: f32) {
670    hsl(img, "darken", level)
671}
672
673/// Darken the image's colours by a specified amount in the HSV colour space.
674///
675/// # Arguments
676/// * `img` - A PhotonImage.
677/// * `level` - Float value from 0 to 1 representing the level to which to darken the image by.
678/// The `level` must be from 0 to 1 in floating-point, `f32` format.
679/// Darkening by 80% would be represented by a `level` of 0.8
680///
681/// # Example
682/// ```no_run
683/// // For example to darken an image by 10% in the HSV colour space:
684/// use photon_rs::colour_spaces::darken_hsv;
685/// use photon_rs::native::open_image;
686///
687/// // Open the image. A PhotonImage is returned.
688/// let mut img = open_image("img.jpg").expect("File should open");
689/// darken_hsv(&mut img, 0.1_f32);
690/// ```
691#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
692pub fn darken_hsv(img: &mut PhotonImage, level: f32) {
693    hsv(img, "darken", level)
694}
695
696/// Desaturate the image by a specified amount in the HSV colour space.
697///
698/// # Arguments
699/// * `img` - A PhotonImage.
700/// * `level` - Float value from 0 to 1 representing the level to which to desaturate the image by.
701/// The `level` must be from 0 to 1 in floating-point, `f32` format.
702/// Desaturating by 80% would be represented by a `level` of 0.8
703///
704/// # Example
705/// ```no_run
706/// // For example to desaturate an image by 10% in the HSV colour space:
707/// use photon_rs::colour_spaces::desaturate_hsv;
708/// use photon_rs::native::open_image;
709///
710/// // Open the image. A PhotonImage is returned.
711/// let mut img = open_image("img.jpg").expect("File should open");
712/// desaturate_hsv(&mut img, 0.1_f32);
713/// ```
714#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
715pub fn desaturate_hsv(img: &mut PhotonImage, level: f32) {
716    hsv(img, "desaturate", level)
717}
718
719/// Desaturate the image by a specified amount in the HSL colour space.
720///
721/// # Arguments
722/// * `img` - A PhotonImage.
723/// * `level` - Float value from 0 to 1 representing the level to which to desaturate the image by.
724/// The `level` must be from 0 to 1 in floating-point, `f32` format.
725/// Desaturating by 80% would be represented by a `level` of 0.8
726///
727/// # Example
728/// ```no_run
729/// // For example to desaturate an image by 10% in the LCh colour space:
730/// use photon_rs::colour_spaces::desaturate_hsl;
731/// use photon_rs::native::open_image;
732///
733/// // Open the image. A PhotonImage is returned.
734/// let mut img = open_image("img.jpg").expect("File should open");
735/// desaturate_hsl(&mut img, 0.1_f32);
736/// ```
737#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
738pub fn desaturate_hsl(img: &mut PhotonImage, level: f32) {
739    hsl(img, "desaturate", level)
740}
741
742/// Desaturate the image by a specified amount in the LCh colour space.
743///
744/// # Arguments
745/// * `img` - A PhotonImage.
746/// * `level` - Float value from 0 to 1 representing the level to which to desaturate the image by.
747/// The `level` must be from 0 to 1 in floating-point, `f32` format.
748/// Desaturating by 80% would be represented by a `level` of 0.8
749///
750/// # Example
751/// ```no_run
752/// // For example to desaturate an image by 10% in the LCh colour space:
753/// use photon_rs::colour_spaces::desaturate_lch;
754/// use photon_rs::native::open_image;
755///
756/// // Open the image. A PhotonImage is returned.
757/// let mut img = open_image("img.jpg").expect("File should open");
758/// desaturate_lch(&mut img, 0.1_f32);
759/// ```
760#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
761pub fn desaturate_lch(img: &mut PhotonImage, level: f32) {
762    lch(img, "desaturate", level)
763}
764
765/// Desaturate the image by a specified amount in the HSLuv colour space.
766///
767/// # Arguments
768/// * `img` - A PhotonImage.
769/// * `level` - Float value from 0 to 1 representing the level to which to desaturate the image by.
770/// The `level` must be from 0 to 1 in floating-point, `f32` format.
771/// Desaturating by 80% would be represented by a `level` of 0.8
772///
773/// # Example
774/// ```no_run
775/// // For example to desaturate an image by 10% in the HSLuv colour space:
776/// use photon_rs::colour_spaces::desaturate_hsluv;
777/// use photon_rs::native::open_image;
778///
779/// // Open the image. A PhotonImage is returned.
780/// let mut img = open_image("img.jpg").expect("File should open");
781/// desaturate_hsluv(&mut img, 0.1_f32);
782/// ```
783#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
784pub fn desaturate_hsluv(img: &mut PhotonImage, level: f32) {
785    hsluv(img, "desaturate", level)
786}
787
788/// Mix image with a single color, supporting passing `opacity`.
789/// The algorithm comes from Jimp. See `function mix` and `function colorFn` at following link:
790/// https://github.com/oliver-moran/jimp/blob/29679faa597228ff2f20d34c5758e4d2257065a3/packages/plugin-color/src/index.js
791/// Specifically, result_value = (mix_color_value - origin_value) * opacity + origin_value =
792/// mix_color_value * opacity + (1 - opacity) * origin_value for each
793/// of RGB channel.
794///
795/// # Arguments
796/// * `photon_image` - A PhotonImage that contains a view into the image.
797/// * `mix_color` - the color to be mixed in, as an RGB value.
798/// * `opacity` - the opacity of color when mixed to image. Float value from 0 to 1.
799/// # Example
800///
801/// ```no_run
802/// // For example, to mix an image with rgb (50, 255, 254) and opacity 0.4:
803/// use photon_rs::Rgb;
804/// use photon_rs::colour_spaces::mix_with_colour;
805/// use photon_rs::native::open_image;
806///
807/// let mix_colour = Rgb::new(50_u8, 255_u8, 254_u8);
808/// let mut img = open_image("img.jpg").expect("File should open");
809/// mix_with_colour(&mut img, mix_colour, 0.4_f32);
810/// ```
811#[cfg_attr(feature = "enable_wasm", wasm_bindgen)]
812pub fn mix_with_colour(photon_image: &mut PhotonImage, mix_colour: Rgb, opacity: f32) {
813    let img = helpers::dyn_image_from_raw(photon_image);
814    let (width, height) = img.dimensions();
815    let mut img = img.to_rgba8();
816
817    // cache (mix_color_value * opacity) and (1 - opacity) so we dont need to calculate them each time during loop.
818    let mix_red_offset = mix_colour.r as f32 * opacity;
819    let mix_green_offset = mix_colour.g as f32 * opacity;
820    let mix_blue_offset = mix_colour.b as f32 * opacity;
821    let factor = 1.0 - opacity;
822
823    for (x, y) in ImageIterator::new(width, height) {
824        let px = img.get_pixel(x, y);
825        let channels = px.channels();
826
827        let r_value = mix_red_offset + (channels[0] as f32) * factor;
828        let g_value = mix_green_offset + (channels[1] as f32) * factor;
829        let b_value = mix_blue_offset + (channels[2] as f32) * factor;
830        let alpha = channels[3];
831        img.put_pixel(
832            x,
833            y,
834            image::Rgba([r_value as u8, g_value as u8, b_value as u8, alpha]),
835        );
836    }
837    photon_image.raw_pixels = img.to_vec();
838}