libblur/
edge_mode.rs

1// Copyright (c) Radzivon Bartoshyk. All rights reserved.
2//
3// Redistribution and use in source and binary forms, with or without modification,
4// are permitted provided that the following conditions are met:
5//
6// 1.  Redistributions of source code must retain the above copyright notice, this
7// list of conditions and the following disclaimer.
8//
9// 2.  Redistributions in binary form must reproduce the above copyright notice,
10// this list of conditions and the following disclaimer in the documentation
11// and/or other materials provided with the distribution.
12//
13// 3.  Neither the name of the copyright holder nor the names of its
14// contributors may be used to endorse or promote products derived from
15// this software without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28use std::ops::Index;
29
30#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Default)]
31/// Declares a 2D edge handling mode.
32///
33/// `EdgeMode2D` allows specifying how out-of-bounds pixel access
34/// is handled independently in the horizontal and vertical directions.
35pub struct EdgeMode2D {
36    /// Edge handling mode in the horizontal direction.
37    pub horizontal: EdgeMode,
38    /// Edge handling mode in the vertical direction.
39    pub vertical: EdgeMode,
40}
41
42impl EdgeMode2D {
43    pub const fn new(mode: EdgeMode) -> Self {
44        Self {
45            horizontal: mode,
46            vertical: mode,
47        }
48    }
49
50    pub const fn anisotropy(horizontal: EdgeMode, vertical: EdgeMode) -> Self {
51        Self {
52            horizontal,
53            vertical,
54        }
55    }
56}
57
58#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Default)]
59/// Declares an edge handling mode
60pub enum EdgeMode {
61    /// If kernel goes out of bounds it will be clipped to an edge and edge pixel replicated across filter
62    #[default]
63    Clamp = 0,
64    /// If kernel goes out of bounds it will be clipped, this is a slightly faster than clamp, however have different visual effects at the edge.
65    Wrap = 1,
66    /// If filter goes out of bounds image will be replicated with rule `fedcba|abcdefgh|hgfedcb`.
67    Reflect = 2,
68    /// If filter goes out of bounds image will be replicated with rule `gfedcb|abcdefgh|gfedcba`.
69    Reflect101 = 3,
70    /// If filter goes out of bounds image will be replicated with provided constant.
71    /// Works only for filter APIs.
72    Constant = 4,
73}
74
75impl From<usize> for EdgeMode {
76    fn from(value: usize) -> Self {
77        match value {
78            0 => EdgeMode::Clamp,
79            1 => EdgeMode::Wrap,
80            2 => EdgeMode::Reflect,
81            3 => EdgeMode::Reflect101,
82            4 => EdgeMode::Constant,
83            _ => {
84                unreachable!("Unknown edge mode for value: {value}");
85            }
86        }
87    }
88}
89
90impl EdgeMode {
91    pub const fn as_2d(self) -> EdgeMode2D {
92        EdgeMode2D::new(self)
93    }
94}
95
96#[inline(always)]
97pub(crate) fn reflect_index(i: isize, n: isize) -> usize {
98    (n - i.rem_euclid(n) - 1) as usize
99}
100
101#[inline(always)]
102#[allow(dead_code)]
103pub(crate) fn reflect_index_101(i: isize, n: isize) -> usize {
104    let n_r = n - 1;
105    if n_r == 0 {
106        return 0;
107    }
108    (n_r - i.rem_euclid(n_r)) as usize
109}
110
111#[allow(clippy::int_plus_one)]
112macro_rules! clamp_edge {
113    ($edge_mode:expr, $value:expr, $min:expr, $max:expr) => {{
114        use crate::edge_mode::EdgeMode;
115        match $edge_mode {
116            EdgeMode::Clamp | EdgeMode::Constant => $value.max($min).min($max - 1) as usize,
117            EdgeMode::Wrap => {
118                if $value < $min || $value >= $max {
119                    $value.rem_euclid($max) as usize
120                } else {
121                    $value as usize
122                }
123            }
124            EdgeMode::Reflect => {
125                if $value < $min || $value >= $max {
126                    use crate::edge_mode::reflect_index;
127                    let cx = reflect_index($value as isize, $max as isize);
128                    cx as usize
129                } else {
130                    $value as usize
131                }
132            }
133            EdgeMode::Reflect101 => {
134                if $value < $min || $value >= $max {
135                    use crate::edge_mode::reflect_index_101;
136                    reflect_index_101($value as isize, $max as isize)
137                } else {
138                    $value as usize
139                }
140            }
141        }
142    }};
143}
144
145#[derive(Clone, Copy)]
146pub struct BorderHandle {
147    pub edge_mode: EdgeMode,
148    pub scalar: Scalar,
149}
150
151macro_rules! border_interpolate {
152    ($slice: expr, $edge_mode:expr, $value:expr, $min:expr, $max:expr, $scale: expr, $cn: expr) => {{
153        use crate::edge_mode::EdgeMode;
154        use num_traits::AsPrimitive;
155        match $edge_mode.edge_mode {
156            EdgeMode::Constant => {
157                if $value < $min || $value >= $max {
158                    $edge_mode.scalar[$cn].as_()
159                } else {
160                    *$slice.get_unchecked($value as usize * $scale + $cn)
161                }
162            }
163            EdgeMode::Clamp => {
164                *$slice.get_unchecked($value.max($min).min($max - 1) as usize * $scale + $cn)
165            }
166            EdgeMode::Wrap => {
167                if $value < $min || $value >= $max {
168                    *$slice.get_unchecked($value.rem_euclid($max) as usize * $scale + $cn)
169                } else {
170                    *$slice.get_unchecked($value as usize * $scale + $cn)
171                }
172            }
173            EdgeMode::Reflect => {
174                if $value < $min || $value >= $max {
175                    use crate::edge_mode::reflect_index;
176                    let cx = reflect_index($value as isize, $max as isize);
177                    *$slice.get_unchecked(cx as usize * $scale + $cn)
178                } else {
179                    *$slice.get_unchecked($value as usize * $scale + $cn)
180                }
181            }
182            EdgeMode::Reflect101 => {
183                if $value < $min || $value >= $max {
184                    use crate::edge_mode::reflect_index_101;
185                    let cx = reflect_index_101($value as isize, $max as isize);
186                    *$slice.get_unchecked(cx as usize * $scale + $cn)
187                } else {
188                    *$slice.get_unchecked($value as usize * $scale + $cn)
189                }
190            }
191        }
192    }};
193}
194
195pub(crate) use border_interpolate;
196pub(crate) use clamp_edge;
197
198#[repr(C)]
199#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)]
200pub struct Scalar {
201    pub v0: f64,
202    pub v1: f64,
203    pub v2: f64,
204    pub v3: f64,
205}
206
207impl Scalar {
208    pub fn new(v0: f64, v1: f64, v2: f64, v3: f64) -> Self {
209        Self { v0, v1, v2, v3 }
210    }
211
212    pub fn dup(v: f64) -> Self {
213        Scalar::new(v, v, v, v)
214    }
215}
216
217impl Default for Scalar {
218    fn default() -> Self {
219        Self::new(0.0, 0.0, 0.0, 0.0)
220    }
221}
222
223impl Index<usize> for Scalar {
224    type Output = f64;
225    fn index(&self, index: usize) -> &Self::Output {
226        match index {
227            0 => &self.v0,
228            1 => &self.v1,
229            2 => &self.v2,
230            3 => &self.v3,
231            _ => {
232                unimplemented!("Index out of bounds: {}", index);
233            }
234        }
235    }
236}