// Copyright 2006 The Android Open Source Project // Copyright 2020 Yevhenii Reizner // // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use crate::Point; use crate::fixed_point::{fdot16, fdot6, FDot16, FDot6}; use crate::math::left_shift; /// We store 1< &LineEdge { match self { Edge::Line(line) => line, Edge::Quadratic(quad) => &quad.line, Edge::Cubic(cubic) => &cubic.line, } } pub fn as_line_mut(&mut self) -> &mut LineEdge { match self { Edge::Line(line) => line, Edge::Quadratic(quad) => &mut quad.line, Edge::Cubic(cubic) => &mut cubic.line, } } } impl core::ops::Deref for Edge { type Target = LineEdge; fn deref(&self) -> &Self::Target { self.as_line() } } impl core::ops::DerefMut for Edge { fn deref_mut(&mut self) -> &mut Self::Target { self.as_line_mut() } } #[derive(Clone, Default, Debug)] pub struct LineEdge { // Imitate a linked list. pub prev: Option, pub next: Option, pub x: FDot16, pub dx: FDot16, pub first_y: i32, pub last_y: i32, pub winding: i8, // 1 or -1 } impl LineEdge { pub fn new(p0: Point, p1: Point, shift: i32) -> Option { let scale = (1 << (shift + 6)) as f32; let mut x0 = (p0.x * scale) as i32; let mut y0 = (p0.y * scale) as i32; let mut x1 = (p1.x * scale) as i32; let mut y1 = (p1.y * scale) as i32; let mut winding = 1; if y0 > y1 { core::mem::swap(&mut x0, &mut x1); core::mem::swap(&mut y0, &mut y1); winding = -1; } let top = fdot6::round(y0); let bottom = fdot6::round(y1); // are we a zero-height line? if top == bottom { return None; } let slope = fdot6::div(x1 - x0, y1 - y0); let dy = compute_dy(top, y0); Some(LineEdge { next: None, prev: None, x: fdot6::to_fdot16(x0 + fdot16::mul(slope, dy)), dx: slope, first_y: top, last_y: bottom - 1, winding, }) } pub fn is_vertical(&self) -> bool { self.dx == 0 } fn update(&mut self, mut x0: FDot16, mut y0: FDot16, mut x1: FDot16, mut y1: FDot16) -> bool { debug_assert!(self.winding == 1 || self.winding == -1); y0 >>= 10; y1 >>= 10; debug_assert!(y0 <= y1); let top = fdot6::round(y0); let bottom = fdot6::round(y1); // are we a zero-height line? if top == bottom { return false; } x0 >>= 10; x1 >>= 10; let slope = fdot6::div(x1 - x0, y1 - y0); let dy = compute_dy(top, y0); self.x = fdot6::to_fdot16(x0 + fdot16::mul(slope, dy)); self.dx = slope; self.first_y = top; self.last_y = bottom - 1; true } } #[derive(Clone, Debug)] pub struct QuadraticEdge { pub line: LineEdge, pub curve_count: i8, curve_shift: u8, // applied to all dx/ddx/dddx qx: FDot16, qy: FDot16, qdx: FDot16, qdy: FDot16, qddx: FDot16, qddy: FDot16, q_last_x: FDot16, q_last_y: FDot16, } impl QuadraticEdge { pub fn new(points: &[Point], shift: i32) -> Option { let mut quad = Self::new2(points, shift)?; if quad.update() { Some(quad) } else { None } } fn new2(points: &[Point], mut shift: i32) -> Option { let scale = (1 << (shift + 6)) as f32; let mut x0 = (points[0].x * scale) as i32; let mut y0 = (points[0].y * scale) as i32; let x1 = (points[1].x * scale) as i32; let y1 = (points[1].y * scale) as i32; let mut x2 = (points[2].x * scale) as i32; let mut y2 = (points[2].y * scale) as i32; let mut winding = 1; if y0 > y2 { core::mem::swap(&mut x0, &mut x2); core::mem::swap(&mut y0, &mut y2); winding = -1; } debug_assert!(y0 <= y1 && y1 <= y2); let top = fdot6::round(y0); let bottom = fdot6::round(y2); // are we a zero-height quad (line)? if top == bottom { return None; } // compute number of steps needed (1 << shift) { let dx = (left_shift(x1, 1) - x0 - x2) >> 2; let dy = (left_shift(y1, 1) - y0 - y2) >> 2; // This is a little confusing: // before this line, shift is the scale up factor for AA; // after this line, shift is the fCurveShift. shift = diff_to_shift(dx, dy, shift); debug_assert!(shift >= 0); } // need at least 1 subdivision for our bias trick if shift == 0 { shift = 1; } else if shift > MAX_COEFF_SHIFT { shift = MAX_COEFF_SHIFT; } let curve_count = (1 << shift) as i8; // We want to reformulate into polynomial form, to make it clear how we // should forward-difference. // // p0 (1 - t)^2 + p1 t(1 - t) + p2 t^2 ==> At^2 + Bt + C // // A = p0 - 2p1 + p2 // B = 2(p1 - p0) // C = p0 // // Our caller must have constrained our inputs (p0..p2) to all fit into // 16.16. However, as seen above, we sometimes compute values that can be // larger (e.g. B = 2*(p1 - p0)). To guard against overflow, we will store // A and B at 1/2 of their actual value, and just apply a 2x scale during // application in updateQuadratic(). Hence we store (shift - 1) in // curve_shift. let curve_shift = (shift - 1) as u8; let mut a = fdot6_to_fixed_div2(x0 - x1 - x1 + x2); // 1/2 the real value let mut b = fdot6::to_fdot16(x1 - x0); // 1/2 the real value let qx = fdot6::to_fdot16(x0); let qdx = b + (a >> shift); // biased by shift let qddx = a >> (shift - 1); // biased by shift a = fdot6_to_fixed_div2(y0 - y1 - y1 + y2); // 1/2 the real value b = fdot6::to_fdot16(y1 - y0); // 1/2 the real value let qy = fdot6::to_fdot16(y0); let qdy = b + (a >> shift); // biased by shift let qddy = a >> (shift - 1); // biased by shift let q_last_x = fdot6::to_fdot16(x2); let q_last_y = fdot6::to_fdot16(y2); Some(QuadraticEdge { line: LineEdge { next: None, prev: None, x: 0, dx: 0, first_y: 0, last_y: 0, winding, }, curve_count, curve_shift, qx, qy, qdx, qdy, qddx, qddy, q_last_x, q_last_y, }) } pub fn update(&mut self) -> bool { let mut success; let mut count = self.curve_count; let mut oldx = self.qx; let mut oldy = self.qy; let mut dx = self.qdx; let mut dy = self.qdy; let mut newx; let mut newy; let shift = self.curve_shift; debug_assert!(count > 0); loop { count -= 1; if count > 0 { newx = oldx + (dx >> shift); dx += self.qddx; newy = oldy + (dy >> shift); dy += self.qddy; } else { // last segment newx = self.q_last_x; newy = self.q_last_y; } success = self.line.update(oldx, oldy, newx, newy); oldx = newx; oldy = newy; if count == 0 || success { break; } } self.qx = newx; self.qy = newy; self.qdx = dx; self.qdy = dy; self.curve_count = count as i8; success } } #[derive(Clone, Debug)] pub struct CubicEdge { pub line: LineEdge, pub curve_count: i8, curve_shift: u8, // applied to all dx/ddx/dddx except for dshift exception dshift: u8, // applied to cdx and cdy cx: FDot16, cy: FDot16, cdx: FDot16, cdy: FDot16, cddx: FDot16, cddy: FDot16, cdddx: FDot16, cdddy: FDot16, c_last_x: FDot16, c_last_y: FDot16, } impl CubicEdge { pub fn new(points: &[Point], shift: i32) -> Option { let mut cubic = Self::new2(points, shift, true)?; if cubic.update() { Some(cubic) } else { None } } fn new2(points: &[Point], mut shift: i32, sort_y: bool) -> Option { let scale = (1 << (shift + 6)) as f32; let mut x0 = (points[0].x * scale) as i32; let mut y0 = (points[0].y * scale) as i32; let mut x1 = (points[1].x * scale) as i32; let mut y1 = (points[1].y * scale) as i32; let mut x2 = (points[2].x * scale) as i32; let mut y2 = (points[2].y * scale) as i32; let mut x3 = (points[3].x * scale) as i32; let mut y3 = (points[3].y * scale) as i32; let mut winding = 1; if sort_y && y0 > y3 { core::mem::swap(&mut x0, &mut x3); core::mem::swap(&mut x1, &mut x2); core::mem::swap(&mut y0, &mut y3); core::mem::swap(&mut y1, &mut y2); winding = -1; } let top = fdot6::round(y0); let bot = fdot6::round(y3); // are we a zero-height cubic (line)? if sort_y && top == bot { return None; } // compute number of steps needed (1 << shift) { // Can't use (center of curve - center of baseline), since center-of-curve // need not be the max delta from the baseline (it could even be coincident) // so we try just looking at the two off-curve points let dx = cubic_delta_from_line(x0, x1, x2, x3); let dy = cubic_delta_from_line(y0, y1, y2, y3); // add 1 (by observation) shift = diff_to_shift(dx, dy, 2) + 1; } // need at least 1 subdivision for our bias trick debug_assert!(shift > 0); if shift > MAX_COEFF_SHIFT { shift = MAX_COEFF_SHIFT; } // Since our in coming data is initially shifted down by 10 (or 8 in // antialias). That means the most we can shift up is 8. However, we // compute coefficients with a 3*, so the safest upshift is really 6 let mut up_shift = 6; // largest safe value let mut down_shift = shift + up_shift - 10; if down_shift < 0 { down_shift = 0; up_shift = 10 - shift; } let curve_count = left_shift(-1, shift) as i8; let curve_shift = shift as u8; let dshift = down_shift as u8; let mut b = fdot6_up_shift(3 * (x1 - x0), up_shift); let mut c = fdot6_up_shift(3 * (x0 - x1 - x1 + x2), up_shift); let mut d = fdot6_up_shift(x3 + 3 * (x1 - x2) - x0, up_shift); let cx = fdot6::to_fdot16(x0); let cdx = b + (c >> shift) + (d >> (2 * shift)); // biased by shift let cddx = 2 * c + ((3 * d) >> (shift - 1)); // biased by 2*shift let cdddx = (3 * d) >> (shift - 1); // biased by 2*shift b = fdot6_up_shift(3 * (y1 - y0), up_shift); c = fdot6_up_shift(3 * (y0 - y1 - y1 + y2), up_shift); d = fdot6_up_shift(y3 + 3 * (y1 - y2) - y0, up_shift); let cy = fdot6::to_fdot16(y0); let cdy = b + (c >> shift) + (d >> (2 * shift)); // biased by shift let cddy = 2 * c + ((3 * d) >> (shift - 1)); // biased by 2*shift let cdddy = (3 * d) >> (shift - 1); // biased by 2*shift let c_last_x = fdot6::to_fdot16(x3); let c_last_y = fdot6::to_fdot16(y3); Some(CubicEdge { line: LineEdge { next: None, prev: None, x: 0, dx: 0, first_y: 0, last_y: 0, winding, }, curve_count, curve_shift, dshift, cx, cy, cdx, cdy, cddx, cddy, cdddx, cdddy, c_last_x, c_last_y, }) } pub fn update(&mut self) -> bool { let mut success; let mut count = self.curve_count; let mut oldx = self.cx; let mut oldy = self.cy; let mut newx; let mut newy; let ddshift = self.curve_shift; let dshift = self.dshift; debug_assert!(count < 0); loop { count += 1; if count < 0 { newx = oldx + (self.cdx >> dshift); self.cdx += self.cddx >> ddshift; self.cddx += self.cdddx; newy = oldy + (self.cdy >> dshift); self.cdy += self.cddy >> ddshift; self.cddy += self.cdddy; } else { // last segment newx = self.c_last_x; newy = self.c_last_y; } // we want to say debug_assert(oldy <= newy), but our finite fixedpoint // doesn't always achieve that, so we have to explicitly pin it here. if newy < oldy { newy = oldy; } success = self.line.update(oldx, oldy, newx, newy); oldx = newx; oldy = newy; if count == 0 || success { break; } } self.cx = newx; self.cy = newy; self.curve_count = count; success } } // This correctly favors the lower-pixel when y0 is on a 1/2 pixel boundary fn compute_dy(top: FDot6, y0: FDot6) -> FDot6 { left_shift(top, 6) + 32 - y0 } fn diff_to_shift(dx: FDot6, dy: FDot6, shift_aa: i32) -> i32 { // cheap calc of distance from center of p0-p2 to the center of the curve let mut dist = cheap_distance(dx, dy); // shift down dist (it is currently in dot6) // down by 3 should give us 1/8 pixel accuracy (assuming our dist is accurate...) // this is chosen by heuristic: make it as big as possible (to minimize segments) // ... but small enough so that our curves still look smooth // When shift > 0, we're using AA and everything is scaled up so we can // lower the accuracy. dist = (dist + (1 << 4)) >> (3 + shift_aa); // each subdivision (shift value) cuts this dist (error) by 1/4 (32 - dist.leading_zeros() as i32) >> 1 } fn cheap_distance(mut dx: FDot6, mut dy: FDot6) -> FDot6 { dx = dx.abs(); dy = dy.abs(); // return max + min/2 if dx > dy { dx + (dy >> 1) } else { dy + (dx >> 1) } } // In LineEdge::new, QuadraticEdge::new, CubicEdge::new, the first thing we do is to convert // the points into FDot6. This is modulated by the shift parameter, which // will either be 0, or something like 2 for antialiasing. // // In the float case, we want to turn the float into .6 by saying pt * 64, // or pt * 256 for antialiasing. This is implemented as 1 << (shift + 6). // // In the fixed case, we want to turn the fixed into .6 by saying pt >> 10, // or pt >> 8 for antialiasing. This is implemented as pt >> (10 - shift). fn fdot6_to_fixed_div2(value: FDot6) -> FDot16 { // we want to return SkFDot6ToFixed(value >> 1), but we don't want to throw // away data in value, so just perform a modify up-shift left_shift(value, 16 - 6 - 1) } fn fdot6_up_shift(x: FDot6, up_shift: i32) -> i32 { debug_assert!((left_shift(x, up_shift) >> up_shift) == x); left_shift(x, up_shift) } // f(1/3) = (8a + 12b + 6c + d) / 27 // f(2/3) = (a + 6b + 12c + 8d) / 27 // // f(1/3)-b = (8a - 15b + 6c + d) / 27 // f(2/3)-c = (a + 6b - 15c + 8d) / 27 // // use 16/512 to approximate 1/27 fn cubic_delta_from_line(a: FDot6, b: FDot6, c: FDot6, d: FDot6) -> FDot6 { // since our parameters may be negative, we don't use << let one_third = ((a * 8 - b * 15 + 6 * c + d) * 19) >> 9; let two_third = ((a + 6 * b - c * 15 + d * 8) * 19) >> 9; one_third.abs().max(two_third.abs()) }