pxsort/
heuristic.rs

1use image::Rgba;
2use strum::IntoEnumIterator;
3use strum_macros::{EnumIter, EnumString, IntoStaticStr};
4
5fn pixel_max(Rgba { data, .. }: &Rgba<u8>) -> u8 {
6    data[..3].iter().max().cloned().unwrap_or_default()
7}
8
9fn pixel_min(Rgba { data, .. }: &Rgba<u8>) -> u8 {
10    data[..3].iter().min().cloned().unwrap_or_default()
11}
12
13fn pixel_chroma(pixel: &Rgba<u8>) -> u8 {
14    pixel_max(pixel) - pixel_min(pixel)
15}
16
17fn pixel_hue(pixel: &Rgba<u8>) -> u8 {
18    let c = pixel_chroma(pixel);
19
20    if c == 0 {
21        return 0;
22    }
23
24    let Rgba { data, .. } = pixel;
25
26    match data[..3].iter().enumerate().max_by_key(|&(_, e)| e) {
27        Some((0, _)) => (data[1] as i16 - data[2] as i16).abs() as u8 / c * 43,
28        Some((1, _)) => (data[2] as i16 - data[0] as i16).abs() as u8 / c * 43 + 85,
29        Some((2, _)) => (data[0] as i16 - data[1] as i16).abs() as u8 / c * 43 + 171,
30        _ => 0,
31    }
32}
33
34fn pixel_saturation(pixel: &Rgba<u8>) -> u8 {
35    match pixel_max(pixel) {
36        0 => 0,
37        v => pixel_chroma(pixel) / v,
38    }
39}
40
41fn pixel_brightness(Rgba { data, .. }: &Rgba<u8>) -> u8 {
42    data[0] / 3 + data[1] / 3 + data[2] / 3 + (data[0] % 3 + data[1] % 3 + data[2] % 3) / 3
43}
44
45/// https://stackoverflow.com/a/596241
46fn pixel_luma(Rgba { data, .. }: &Rgba<u8>) -> u8 {
47    ((data[0] as u16 * 2 + data[1] as u16 + data[2] as u16 * 4) >> 3) as u8
48}
49
50/// Basis to use for sorting individual pixels.
51#[allow(non_camel_case_types)]
52#[derive(EnumIter, EnumString, IntoStaticStr)]
53#[strum(serialize_all = "snake_case")]
54pub enum Heuristic {
55    Luma,
56    Brightness,
57    Max,
58    Min,
59    Chroma,
60    Hue,
61    Saturation,
62    Value,
63    Red,
64    Blue,
65    Green,
66    #[doc(hidden)]
67    __Nonexhaustive,
68}
69
70impl Heuristic {
71    pub(crate) fn variants() -> Vec<&'static str> {
72        Self::iter()
73            .filter_map(|v| {
74                if let Heuristic::__Nonexhaustive = v {
75                    None
76                } else {
77                    Some(v)
78                }
79            })
80            .map(Into::into)
81            .collect()
82    }
83
84    /// Get the key extraction function for this heuristic.
85    pub fn func(&self) -> Box<Fn(&Rgba<u8>) -> u8> {
86        match self {
87            Heuristic::Red => Box::new(|Rgba { data, .. }| data[0]),
88            Heuristic::Green => Box::new(|Rgba { data, .. }| data[1]),
89            Heuristic::Blue => Box::new(|Rgba { data, .. }| data[2]),
90            Heuristic::Max => Box::new(pixel_max),
91            Heuristic::Min => Box::new(pixel_min),
92            Heuristic::Chroma => Box::new(pixel_chroma),
93            Heuristic::Hue => Box::new(pixel_hue),
94            Heuristic::Saturation => Box::new(pixel_saturation),
95            Heuristic::Value => Box::new(pixel_max),
96            Heuristic::Brightness => Box::new(pixel_brightness),
97            Heuristic::Luma => Box::new(pixel_luma),
98            Heuristic::__Nonexhaustive => unreachable!(),
99        }
100    }
101}