1 | // Copyright 2006 The Android Open Source Project |
2 | // Copyright 2020 Yevhenii Reizner |
3 | // |
4 | // Use of this source code is governed by a BSD-style license that can be |
5 | // found in the LICENSE file. |
6 | |
7 | use tiny_skia_path::{NormalizedF32, NormalizedF32Exclusive, Point}; |
8 | |
9 | #[cfg (all(not(feature = "std" ), feature = "no-std-float" ))] |
10 | use tiny_skia_path::NoStdFloat; |
11 | |
12 | pub use tiny_skia_path::path_geometry::{ |
13 | chop_cubic_at2, chop_quad_at, find_cubic_max_curvature, find_unit_quad_roots, new_t_values, |
14 | CubicCoeff, QuadCoeff, |
15 | }; |
16 | |
17 | use tiny_skia_path::path_geometry::valid_unit_divide; |
18 | |
19 | // TODO: return custom type |
20 | /// Returns 0 for 1 quad, and 1 for two quads, either way the answer is stored in dst[]. |
21 | /// |
22 | /// Guarantees that the 1/2 quads will be monotonic. |
23 | pub fn chop_quad_at_x_extrema(src: &[Point; 3], dst: &mut [Point; 5]) -> usize { |
24 | let a = src[0].x; |
25 | let mut b = src[1].x; |
26 | let c = src[2].x; |
27 | |
28 | if is_not_monotonic(a, b, c) { |
29 | if let Some(t_value) = valid_unit_divide(a - b, a - b - b + c) { |
30 | chop_quad_at(src, t_value, dst); |
31 | |
32 | // flatten double quad extrema |
33 | dst[1].x = dst[2].x; |
34 | dst[3].x = dst[2].x; |
35 | |
36 | return 1; |
37 | } |
38 | |
39 | // if we get here, we need to force dst to be monotonic, even though |
40 | // we couldn't compute a unit_divide value (probably underflow). |
41 | b = if (a - b).abs() < (b - c).abs() { a } else { c }; |
42 | } |
43 | |
44 | dst[0] = Point::from_xy(a, src[0].y); |
45 | dst[1] = Point::from_xy(b, src[1].y); |
46 | dst[2] = Point::from_xy(c, src[2].y); |
47 | 0 |
48 | } |
49 | |
50 | /// Returns 0 for 1 quad, and 1 for two quads, either way the answer is stored in dst[]. |
51 | /// |
52 | /// Guarantees that the 1/2 quads will be monotonic. |
53 | pub fn chop_quad_at_y_extrema(src: &[Point; 3], dst: &mut [Point; 5]) -> usize { |
54 | let a = src[0].y; |
55 | let mut b = src[1].y; |
56 | let c = src[2].y; |
57 | |
58 | if is_not_monotonic(a, b, c) { |
59 | if let Some(t_value) = valid_unit_divide(a - b, a - b - b + c) { |
60 | chop_quad_at(src, t_value, dst); |
61 | |
62 | // flatten double quad extrema |
63 | dst[1].y = dst[2].y; |
64 | dst[3].y = dst[2].y; |
65 | |
66 | return 1; |
67 | } |
68 | |
69 | // if we get here, we need to force dst to be monotonic, even though |
70 | // we couldn't compute a unit_divide value (probably underflow). |
71 | b = if (a - b).abs() < (b - c).abs() { a } else { c }; |
72 | } |
73 | |
74 | dst[0] = Point::from_xy(src[0].x, a); |
75 | dst[1] = Point::from_xy(src[1].x, b); |
76 | dst[2] = Point::from_xy(src[2].x, c); |
77 | 0 |
78 | } |
79 | |
80 | fn is_not_monotonic(a: f32, b: f32, c: f32) -> bool { |
81 | let ab: f32 = a - b; |
82 | let mut bc: f32 = b - c; |
83 | if ab < 0.0 { |
84 | bc = -bc; |
85 | } |
86 | |
87 | ab == 0.0 || bc < 0.0 |
88 | } |
89 | |
90 | pub fn chop_cubic_at_x_extrema(src: &[Point; 4], dst: &mut [Point; 10]) -> usize { |
91 | let mut t_values: [NormalizedF32Exclusive; 3] = new_t_values(); |
92 | let t_values: &[NormalizedF32Exclusive] = find_cubic_extrema(a:src[0].x, b:src[1].x, c:src[2].x, d:src[3].x, &mut t_values); |
93 | |
94 | chop_cubic_at(src, t_values, dst); |
95 | if !t_values.is_empty() { |
96 | // we do some cleanup to ensure our X extrema are flat |
97 | dst[2].x = dst[3].x; |
98 | dst[4].x = dst[3].x; |
99 | if t_values.len() == 2 { |
100 | dst[5].x = dst[6].x; |
101 | dst[7].x = dst[6].x; |
102 | } |
103 | } |
104 | |
105 | t_values.len() |
106 | } |
107 | |
108 | /// Given 4 points on a cubic bezier, chop it into 1, 2, 3 beziers such that |
109 | /// the resulting beziers are monotonic in Y. |
110 | /// |
111 | /// This is called by the scan converter. |
112 | /// |
113 | /// Depending on what is returned, dst[] is treated as follows: |
114 | /// |
115 | /// - 0: dst[0..3] is the original cubic |
116 | /// - 1: dst[0..3] and dst[3..6] are the two new cubics |
117 | /// - 2: dst[0..3], dst[3..6], dst[6..9] are the three new cubics |
118 | pub fn chop_cubic_at_y_extrema(src: &[Point; 4], dst: &mut [Point; 10]) -> usize { |
119 | let mut t_values: [NormalizedF32Exclusive; 3] = new_t_values(); |
120 | let t_values: &[NormalizedF32Exclusive] = find_cubic_extrema(a:src[0].y, b:src[1].y, c:src[2].y, d:src[3].y, &mut t_values); |
121 | |
122 | chop_cubic_at(src, t_values, dst); |
123 | if !t_values.is_empty() { |
124 | // we do some cleanup to ensure our Y extrema are flat |
125 | dst[2].y = dst[3].y; |
126 | dst[4].y = dst[3].y; |
127 | if t_values.len() == 2 { |
128 | dst[5].y = dst[6].y; |
129 | dst[7].y = dst[6].y; |
130 | } |
131 | } |
132 | |
133 | t_values.len() |
134 | } |
135 | |
136 | // Cubic'(t) = At^2 + Bt + C, where |
137 | // A = 3(-a + 3(b - c) + d) |
138 | // B = 6(a - 2b + c) |
139 | // C = 3(b - a) |
140 | // Solve for t, keeping only those that fit between 0 < t < 1 |
141 | fn find_cubic_extrema( |
142 | a: f32, |
143 | b: f32, |
144 | c: f32, |
145 | d: f32, |
146 | t_values: &mut [NormalizedF32Exclusive; 3], |
147 | ) -> &[NormalizedF32Exclusive] { |
148 | // we divide A,B,C by 3 to simplify |
149 | let na: f32 = d - a + 3.0 * (b - c); |
150 | let nb: f32 = 2.0 * (a - b - b + c); |
151 | let nc: f32 = b - a; |
152 | |
153 | let roots: usize = find_unit_quad_roots(a:na, b:nb, c:nc, roots:t_values); |
154 | &t_values[0..roots] |
155 | } |
156 | |
157 | // http://code.google.com/p/skia/issues/detail?id=32 |
158 | // |
159 | // This test code would fail when we didn't check the return result of |
160 | // valid_unit_divide in SkChopCubicAt(... NormalizedF32Exclusives[], int roots). The reason is |
161 | // that after the first chop, the parameters to valid_unit_divide are equal |
162 | // (thanks to finite float precision and rounding in the subtracts). Thus |
163 | // even though the 2nd NormalizedF32Exclusive looks < 1.0, after we renormalize it, we end |
164 | // up with 1.0, hence the need to check and just return the last cubic as |
165 | // a degenerate clump of 4 points in the same place. |
166 | pub fn chop_cubic_at(src: &[Point; 4], t_values: &[NormalizedF32Exclusive], dst: &mut [Point]) { |
167 | if t_values.is_empty() { |
168 | // nothing to chop |
169 | dst[0] = src[0]; |
170 | dst[1] = src[1]; |
171 | dst[2] = src[2]; |
172 | dst[3] = src[3]; |
173 | } else { |
174 | let mut t = t_values[0]; |
175 | let mut tmp = [Point::zero(); 4]; |
176 | |
177 | // Reduce the `src` lifetime, so we can use `src = &tmp` later. |
178 | let mut src = src; |
179 | |
180 | let mut dst_offset = 0; |
181 | for i in 0..t_values.len() { |
182 | chop_cubic_at2(src, t, &mut dst[dst_offset..]); |
183 | if i == t_values.len() - 1 { |
184 | break; |
185 | } |
186 | |
187 | dst_offset += 3; |
188 | // have src point to the remaining cubic (after the chop) |
189 | tmp[0] = dst[dst_offset + 0]; |
190 | tmp[1] = dst[dst_offset + 1]; |
191 | tmp[2] = dst[dst_offset + 2]; |
192 | tmp[3] = dst[dst_offset + 3]; |
193 | src = &tmp; |
194 | |
195 | // watch out in case the renormalized t isn't in range |
196 | let n = valid_unit_divide( |
197 | t_values[i + 1].get() - t_values[i].get(), |
198 | 1.0 - t_values[i].get(), |
199 | ); |
200 | |
201 | match n { |
202 | Some(n) => t = n, |
203 | None => { |
204 | // if we can't, just create a degenerate cubic |
205 | dst[dst_offset + 4] = src[3]; |
206 | dst[dst_offset + 5] = src[3]; |
207 | dst[dst_offset + 6] = src[3]; |
208 | break; |
209 | } |
210 | } |
211 | } |
212 | } |
213 | } |
214 | |
215 | pub fn chop_cubic_at_max_curvature( |
216 | src: &[Point; 4], |
217 | t_values: &mut [NormalizedF32Exclusive; 3], |
218 | dst: &mut [Point], |
219 | ) -> usize { |
220 | let mut roots: [NormalizedF32; 3] = [NormalizedF32::ZERO; 3]; |
221 | let roots: &[NormalizedF32] = find_cubic_max_curvature(src, &mut roots); |
222 | |
223 | // Throw out values not inside 0..1. |
224 | let mut count: usize = 0; |
225 | for root: &NormalizedF32 in roots { |
226 | if 0.0 < root.get() && root.get() < 1.0 { |
227 | t_values[count] = NormalizedF32Exclusive::new_bounded(root.get()); |
228 | count += 1; |
229 | } |
230 | } |
231 | |
232 | if count == 0 { |
233 | dst[0..4].copy_from_slice(src); |
234 | } else { |
235 | chop_cubic_at(src, &t_values[0..count], dst); |
236 | } |
237 | |
238 | count + 1 |
239 | } |
240 | |
241 | pub fn chop_mono_cubic_at_x(src: &[Point; 4], x: f32, dst: &mut [Point; 7]) -> bool { |
242 | cubic_dchop_at_intercept(src, intercept:x, is_vertical:true, dst) |
243 | } |
244 | |
245 | pub fn chop_mono_cubic_at_y(src: &[Point; 4], y: f32, dst: &mut [Point; 7]) -> bool { |
246 | cubic_dchop_at_intercept(src, intercept:y, is_vertical:false, dst) |
247 | } |
248 | |
249 | fn cubic_dchop_at_intercept( |
250 | src: &[Point; 4], |
251 | intercept: f32, |
252 | is_vertical: bool, |
253 | dst: &mut [Point; 7], |
254 | ) -> bool { |
255 | use crate::path64::{cubic64::Cubic64, line_cubic_intersections, point64::Point64}; |
256 | |
257 | let src = [ |
258 | Point64::from_point(src[0]), |
259 | Point64::from_point(src[1]), |
260 | Point64::from_point(src[2]), |
261 | Point64::from_point(src[3]), |
262 | ]; |
263 | |
264 | let cubic = Cubic64::new(src); |
265 | let mut roots = [0.0; 3]; |
266 | let count = if is_vertical { |
267 | line_cubic_intersections::vertical_intersect(&cubic, f64::from(intercept), &mut roots) |
268 | } else { |
269 | line_cubic_intersections::horizontal_intersect(&cubic, f64::from(intercept), &mut roots) |
270 | }; |
271 | |
272 | if count > 0 { |
273 | let pair = cubic.chop_at(roots[0]); |
274 | for i in 0..7 { |
275 | dst[i] = pair.points[i].to_point(); |
276 | } |
277 | |
278 | true |
279 | } else { |
280 | false |
281 | } |
282 | } |
283 | |
284 | #[cfg (test)] |
285 | mod tests { |
286 | use super::*; |
287 | |
288 | #[test ] |
289 | fn chop_cubic_at_y_extrema_1() { |
290 | let src = [ |
291 | Point::from_xy(10.0, 20.0), |
292 | Point::from_xy(67.0, 437.0), |
293 | Point::from_xy(298.0, 213.0), |
294 | Point::from_xy(401.0, 214.0), |
295 | ]; |
296 | |
297 | let mut dst = [Point::zero(); 10]; |
298 | let n = chop_cubic_at_y_extrema(&src, &mut dst); |
299 | assert_eq!(n, 2); |
300 | assert_eq!(dst[0], Point::from_xy(10.0, 20.0)); |
301 | assert_eq!(dst[1], Point::from_xy(37.508274, 221.24475)); |
302 | assert_eq!(dst[2], Point::from_xy(105.541855, 273.19803)); |
303 | assert_eq!(dst[3], Point::from_xy(180.15599, 273.19803)); |
304 | assert_eq!(dst[4], Point::from_xy(259.80502, 273.19803)); |
305 | assert_eq!(dst[5], Point::from_xy(346.9527, 213.99666)); |
306 | assert_eq!(dst[6], Point::from_xy(400.30844, 213.99666)); |
307 | assert_eq!(dst[7], Point::from_xy(400.53958, 213.99666)); |
308 | assert_eq!(dst[8], Point::from_xy(400.7701, 213.99777)); |
309 | assert_eq!(dst[9], Point::from_xy(401.0, 214.0)); |
310 | } |
311 | } |
312 | |