tiny_skia/scan/
path_aa.rs

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
7use core::convert::TryFrom;
8
9use crate::{FillRule, IntRect, LengthU32, Path, Rect};
10
11use crate::alpha_runs::AlphaRuns;
12use crate::blitter::Blitter;
13use crate::color::AlphaU8;
14use crate::geom::{IntRectExt, ScreenIntRect};
15use crate::math::left_shift;
16
17#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
18use tiny_skia_path::NoStdFloat;
19
20/// controls how much we super-sample (when we use that scan conversion)
21const SUPERSAMPLE_SHIFT: u32 = 2;
22
23const SHIFT: u32 = SUPERSAMPLE_SHIFT;
24const SCALE: u32 = 1 << SHIFT;
25const MASK: u32 = SCALE - 1;
26
27pub fn fill_path(
28    path: &Path,
29    fill_rule: FillRule,
30    clip: &ScreenIntRect,
31    blitter: &mut dyn Blitter,
32) {
33    // Unlike `path.bounds.to_rect()?.round_out()`,
34    // this method rounds out first and then converts into a Rect.
35    let ir = Rect::from_ltrb(
36        path.bounds().left().floor(),
37        path.bounds().top().floor(),
38        path.bounds().right().ceil(),
39        path.bounds().bottom().ceil(),
40    )
41    .and_then(|r| r.round_out());
42    let ir = match ir {
43        Some(v) => v,
44        None => return,
45    };
46
47    // TODO: remove
48    // If the intersection of the path bounds and the clip bounds
49    // will overflow 32767 when << by SHIFT, we can't supersample,
50    // so draw without antialiasing.
51    let clipped_ir = match ir.intersect(&clip.to_int_rect()) {
52        Some(v) => v,
53        None => return,
54    };
55    if rect_overflows_short_shift(&clipped_ir, SHIFT as i32) != 0 {
56        super::path::fill_path(path, fill_rule, clip, blitter);
57        return;
58    }
59
60    // TODO: remove
61    // Our antialiasing can't handle a clip larger than 32767.
62    // TODO: skia actually limits the clip to 32767
63    {
64        const MAX_CLIP_COORD: u32 = 32767;
65        if clip.right() > MAX_CLIP_COORD || clip.bottom() > MAX_CLIP_COORD {
66            return;
67        }
68    }
69
70    // TODO: SkScanClipper
71    // TODO: AAA
72
73    fill_path_impl(path, fill_rule, &ir, clip, blitter)
74}
75
76// Would any of the coordinates of this rectangle not fit in a short,
77// when left-shifted by shift?
78fn rect_overflows_short_shift(rect: &IntRect, shift: i32) -> i32 {
79    debug_assert!(overflows_short_shift(8191, shift) == 0);
80    debug_assert!(overflows_short_shift(8192, shift) != 0);
81    debug_assert!(overflows_short_shift(32767, 0) == 0);
82    debug_assert!(overflows_short_shift(32768, 0) != 0);
83
84    // Since we expect these to succeed, we bit-or together
85    // for a tiny extra bit of speed.
86    overflows_short_shift(rect.left(), shift)
87        | overflows_short_shift(rect.top(), shift)
88        | overflows_short_shift(rect.right(), shift)
89        | overflows_short_shift(rect.bottom(), shift)
90}
91
92fn overflows_short_shift(value: i32, shift: i32) -> i32 {
93    let s = 16 + shift;
94    (left_shift(value, s) >> s) - value
95}
96
97fn fill_path_impl(
98    path: &Path,
99    fill_rule: FillRule,
100    bounds: &IntRect,
101    clip: &ScreenIntRect,
102    blitter: &mut dyn Blitter,
103) {
104    // TODO: MaskSuperBlitter
105
106    // TODO: 15% slower than skia, find out why
107    let mut blitter = match SuperBlitter::new(bounds, clip, blitter) {
108        Some(v) => v,
109        None => return, // clipped out, nothing else to do
110    };
111
112    let path_contained_in_clip = if let Some(bounds) = bounds.to_screen_int_rect() {
113        clip.contains(&bounds)
114    } else {
115        // If bounds cannot be converted into ScreenIntRect,
116        // the path is out of clip.
117        false
118    };
119
120    super::path::fill_path_impl(
121        path,
122        fill_rule,
123        clip,
124        bounds.top(),
125        bounds.bottom(),
126        SHIFT as i32,
127        path_contained_in_clip,
128        &mut blitter,
129    );
130}
131
132struct BaseSuperBlitter<'a> {
133    real_blitter: &'a mut dyn Blitter,
134
135    /// Current y coordinate, in destination coordinates.
136    curr_iy: i32,
137    /// Widest row of region to be blitted, in destination coordinates.
138    width: LengthU32,
139    /// Leftmost x coordinate in any row, in destination coordinates.
140    left: u32,
141    /// Leftmost x coordinate in any row, in supersampled coordinates.
142    super_left: u32,
143
144    /// Current y coordinate in supersampled coordinates.
145    curr_y: i32,
146    /// Initial y coordinate (top of bounds).
147    top: i32,
148}
149
150impl<'a> BaseSuperBlitter<'a> {
151    fn new(
152        bounds: &IntRect,
153        clip_rect: &ScreenIntRect,
154        blitter: &'a mut dyn Blitter,
155    ) -> Option<Self> {
156        let sect = bounds
157            .intersect(&clip_rect.to_int_rect())?
158            .to_screen_int_rect()?;
159        Some(BaseSuperBlitter {
160            real_blitter: blitter,
161            curr_iy: sect.top() as i32 - 1,
162            width: sect.width_safe(),
163            left: sect.left(),
164            super_left: sect.left() << SHIFT,
165            curr_y: (sect.top() << SHIFT) as i32 - 1,
166            top: sect.top() as i32,
167        })
168    }
169}
170
171struct SuperBlitter<'a> {
172    base: BaseSuperBlitter<'a>,
173    runs: AlphaRuns,
174    offset_x: usize,
175}
176
177impl<'a> SuperBlitter<'a> {
178    fn new(
179        bounds: &IntRect,
180        clip_rect: &ScreenIntRect,
181        blitter: &'a mut dyn Blitter,
182    ) -> Option<Self> {
183        let base = BaseSuperBlitter::new(bounds, clip_rect, blitter)?;
184        let runs_width = base.width;
185        Some(SuperBlitter {
186            base,
187            runs: AlphaRuns::new(runs_width),
188            offset_x: 0,
189        })
190    }
191
192    /// Once `runs` contains a complete supersampled row, flush() blits
193    /// it out through the wrapped blitter.
194    fn flush(&mut self) {
195        if self.base.curr_iy >= self.base.top {
196            if !self.runs.is_empty() {
197                self.base.real_blitter.blit_anti_h(
198                    self.base.left,
199                    u32::try_from(self.base.curr_iy).unwrap(),
200                    &mut self.runs.alpha,
201                    &mut self.runs.runs,
202                );
203                self.runs.reset(self.base.width);
204                self.offset_x = 0;
205            }
206
207            self.base.curr_iy = self.base.top - 1;
208        }
209    }
210}
211
212impl Drop for SuperBlitter<'_> {
213    fn drop(&mut self) {
214        self.flush();
215    }
216}
217
218impl Blitter for SuperBlitter<'_> {
219    /// Blits a row of pixels, with location and width specified
220    /// in supersampled coordinates.
221    fn blit_h(&mut self, mut x: u32, y: u32, mut width: LengthU32) {
222        let iy = (y >> SHIFT) as i32;
223        debug_assert!(iy >= self.base.curr_iy);
224
225        // hack, until I figure out why my cubics (I think) go beyond the bounds
226        match x.checked_sub(self.base.super_left) {
227            Some(n) => x = n,
228            None => {
229                width = LengthU32::new(x + width.get()).unwrap();
230                x = 0;
231            }
232        }
233
234        debug_assert!(y as i32 >= self.base.curr_y);
235        if self.base.curr_y != y as i32 {
236            self.offset_x = 0;
237            self.base.curr_y = y as i32;
238        }
239
240        if iy != self.base.curr_iy {
241            // new scanline
242            self.flush();
243            self.base.curr_iy = iy;
244        }
245
246        let start = x;
247        let stop = x + width.get();
248
249        debug_assert!(stop > start);
250        // integer-pixel-aligned ends of blit, rounded out
251        let mut fb = start & MASK;
252        let mut fe = stop & MASK;
253        let mut n: i32 = (stop as i32 >> SHIFT) - (start as i32 >> SHIFT) - 1;
254
255        if n < 0 {
256            fb = fe - fb;
257            n = 0;
258            fe = 0;
259        } else {
260            if fb == 0 {
261                n += 1;
262            } else {
263                fb = SCALE - fb;
264            }
265        }
266
267        let max_value = u8::try_from((1 << (8 - SHIFT)) - (((y & MASK) + 1) >> SHIFT)).unwrap();
268        self.offset_x = self.runs.add(
269            x >> SHIFT,
270            coverage_to_partial_alpha(fb),
271            n as usize,
272            coverage_to_partial_alpha(fe),
273            max_value,
274            self.offset_x,
275        );
276    }
277}
278
279// coverage_to_partial_alpha() is being used by AlphaRuns, which
280// *accumulates* SCALE pixels worth of "alpha" in [0,(256/SCALE)]
281// to produce a final value in [0, 255] and handles clamping 256->255
282// itself, with the same (alpha - (alpha >> 8)) correction as
283// coverage_to_exact_alpha().
284fn coverage_to_partial_alpha(mut aa: u32) -> AlphaU8 {
285    aa <<= 8 - 2 * SHIFT;
286    aa as AlphaU8
287}