cranpose_render_common/
geometry.rs1use cranpose_ui_graphics::Rect;
2
3pub const BLUR_MAX_TAPS: u32 = 32;
6
7pub fn blur_scratch_block(radius_px: f32) -> u32 {
10 if radius_px < 6.0 {
11 1
12 } else if radius_px < 16.0 {
13 2
14 } else {
15 4
16 }
17}
18
19pub fn union_rect(lhs: Option<Rect>, rhs: Rect) -> Option<Rect> {
20 if rhs.width <= 0.0 || rhs.height <= 0.0 {
21 return lhs;
22 }
23
24 Some(match lhs {
25 Some(current) => current.union(rhs),
26 None => rhs,
27 })
28}
29
30pub fn blur_reach_px(radius_px: f32) -> f32 {
38 if radius_px.is_nan() || radius_px <= 0.0 {
39 return 1.0;
40 }
41 let block = blur_scratch_block(radius_px) as f32;
42 let reach = radius_px.min(BLUR_MAX_TAPS as f32 * block) + 3.0 * block + 1.0;
43 (reach / block).ceil() * block
44}
45
46pub fn blur_reach(blur_radius: f32, scale: f32) -> f32 {
49 let scale = if scale.is_finite() && scale > 0.0 {
50 scale
51 } else {
52 1.0
53 };
54 blur_reach_px(blur_radius.max(0.0) * scale) / scale
55}
56
57pub fn expand_blurred_rect(
58 mut rect: Rect,
59 blur_radius: f32,
60 scale: f32,
61 clip: Option<Rect>,
62) -> Option<Rect> {
63 let blur_margin = blur_reach(blur_radius, scale);
64 rect.x -= blur_margin;
65 rect.y -= blur_margin;
66 rect.width += blur_margin * 2.0;
67 rect.height += blur_margin * 2.0;
68 if let Some(clip) = clip {
69 rect = rect.intersect(clip)?;
70 }
71 Some(rect)
72}
73
74#[cfg(test)]
75#[path = "tests/geometry_tests.rs"]
76mod tests;
77
78pub const BLUR_TAP_PAIRS: usize = (BLUR_MAX_TAPS / 2) as usize;
81
82#[derive(Clone, Copy, Debug, Default, PartialEq)]
88pub struct BlurTapPair {
89 pub inner: f32,
90 pub outer: f32,
91 pub offset: f32,
92 pub weight: f32,
93}
94
95#[derive(Clone, Copy, Debug, PartialEq)]
99pub struct BlurKernel {
100 pub pairs: [BlurTapPair; BLUR_TAP_PAIRS],
101 pub pair_count: u32,
102 pub total_weight: f32,
103}
104
105impl BlurKernel {
106 pub fn of_radius(radius: f32) -> Self {
109 let radius = radius.max(0.0);
110 let sigma = (radius * 0.5).max(0.001);
111 let tap_count = (radius.ceil() as u32).min(BLUR_MAX_TAPS);
112 let inv_2sigma2 = 1.0 / (2.0 * sigma * sigma);
113 let mut pairs = [BlurTapPair::default(); BLUR_TAP_PAIRS];
114 let mut total_weight = 1.0f32;
115 let mut pair_count = 0;
116 for i in (1..=tap_count).step_by(2) {
117 let fi = i as f32;
118 let fj = fi + 1.0;
119 let inner = (-(fi * fi) * inv_2sigma2).exp();
120 let outer = if i < tap_count {
121 (-(fj * fj) * inv_2sigma2).exp()
122 } else {
123 0.0
124 };
125 total_weight += 2.0 * (inner + outer);
126 let weight = inner + outer;
127 let offset = if weight > 0.0 {
128 (fi * inner + fj * outer) / weight
129 } else {
130 0.0
131 };
132 pairs[pair_count] = BlurTapPair {
133 inner,
134 outer,
135 offset,
136 weight,
137 };
138 pair_count += 1;
139 }
140 Self {
141 pairs,
142 pair_count: pair_count as u32,
143 total_weight,
144 }
145 }
146}
147
148#[cfg(test)]
149#[path = "tests/geometry_blur_kernel_tests.rs"]
150mod blur_kernel_tests;