| 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 core::convert::TryFrom; |
| 8 | |
| 9 | use crate::{FillRule, IntRect, LengthU32, Path, Rect}; |
| 10 | |
| 11 | use crate::alpha_runs::AlphaRuns; |
| 12 | use crate::blitter::Blitter; |
| 13 | use crate::color::AlphaU8; |
| 14 | use crate::geom::{IntRectExt, ScreenIntRect}; |
| 15 | use crate::math::left_shift; |
| 16 | |
| 17 | #[cfg (all(not(feature = "std" ), feature = "no-std-float" ))] |
| 18 | use tiny_skia_path::NoStdFloat; |
| 19 | |
| 20 | /// controls how much we super-sample (when we use that scan conversion) |
| 21 | const SUPERSAMPLE_SHIFT: u32 = 2; |
| 22 | |
| 23 | const SHIFT: u32 = SUPERSAMPLE_SHIFT; |
| 24 | const SCALE: u32 = 1 << SHIFT; |
| 25 | const MASK: u32 = SCALE - 1; |
| 26 | |
| 27 | pub 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? |
| 78 | fn 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(value:rect.left(), shift) |
| 87 | | overflows_short_shift(value:rect.top(), shift) |
| 88 | | overflows_short_shift(value:rect.right(), shift) |
| 89 | | overflows_short_shift(value:rect.bottom(), shift) |
| 90 | } |
| 91 | |
| 92 | fn overflows_short_shift(value: i32, shift: i32) -> i32 { |
| 93 | let s: i32 = 16 + shift; |
| 94 | (left_shift(value, shift:s) >> s) - value |
| 95 | } |
| 96 | |
| 97 | fn 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 | |
| 132 | struct 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 | |
| 150 | impl<'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: ScreenIntRect = boundsIntRect |
| 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 | |
| 171 | struct SuperBlitter<'a> { |
| 172 | base: BaseSuperBlitter<'a>, |
| 173 | runs: AlphaRuns, |
| 174 | offset_x: usize, |
| 175 | } |
| 176 | |
| 177 | impl<'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 | |
| 212 | impl Drop for SuperBlitter<'_> { |
| 213 | fn drop(&mut self) { |
| 214 | self.flush(); |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | impl 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(). |
| 284 | fn coverage_to_partial_alpha(mut aa: u32) -> AlphaU8 { |
| 285 | aa <<= 8 - 2 * SHIFT; |
| 286 | aa as AlphaU8 |
| 287 | } |
| 288 | |