Skip to content

Commit

Permalink
Fix antialiased hairline clipping.
Browse files Browse the repository at this point in the history
  • Loading branch information
RazrFalcon committed Oct 30, 2020
1 parent aff9966 commit 00a4aee
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 17 deletions.
25 changes: 25 additions & 0 deletions src/alpha_runs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,4 +198,29 @@ impl AlphaRuns {
alpha_offset += n;
}
}

/// Cut (at offset x in the buffer) a run into two shorter runs with
/// matching alpha values.
///
/// Used by the RectClipBlitter to trim a RLE encoding to match the
/// clipping rectangle.
pub fn break_at(alpha: &mut [AlphaU8], runs: &mut [AlphaRun], mut x: i32) {
let mut alpha_i = 0;
let mut run_i = 0;
while x > 0 {
let n = runs[run_i].unwrap().get();
let n_usize = usize::from(n);
let n_i32 = i32::from(n);
if x < n_i32 {
alpha[alpha_i + x as usize] = alpha[alpha_i];
runs[0] = NonZeroU16::new(x as u16);
runs[x as usize] = NonZeroU16::new((n_i32 - x) as u16);
break;
}

run_i += n_usize;
alpha_i += n_usize;
x -= n_i32;
}
}
}
2 changes: 1 addition & 1 deletion src/blitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub trait Blitter {
/// entry will by at runs[7]. The runs array and antialias[] are coupled by index. So, if the
/// np entry is at runs[45] = 12 then the alpha value can be found at antialias[45] = 0x88.
/// This would mean to use an alpha value of 0x88 for the next 12 pixels starting at pixel 45.
fn blit_anti_h(&mut self, _x: u32, _y: u32, _antialias: &[AlphaU8], _runs: &[AlphaRun]) {
fn blit_anti_h(&mut self, _x: u32, _y: u32, _antialias: &mut [AlphaU8], _runs: &mut [AlphaRun]) {
unreachable!()
}

Expand Down
2 changes: 1 addition & 1 deletion src/pipeline/blitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ impl Blitter for RasterPipelineBlitter<'_> {
self.blit_rect(&r);
}

fn blit_anti_h(&mut self, mut x: u32, y: u32, aa: &[AlphaU8], runs: &[AlphaRun]) {
fn blit_anti_h(&mut self, mut x: u32, y: u32, aa: &mut [AlphaU8], runs: &mut [AlphaRun]) {
if self.blit_anti_h_rp.is_none() {
let ctx_ptr = &self.pixels_ctx as *const _ as *const c_void;
let curr_cov_ptr = &self.current_coverage as *const _ as *const c_void;
Expand Down
140 changes: 127 additions & 13 deletions src/scan/hairline_aa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::num::NonZeroU16;

use crate::{IntRect, LengthU32, Path, LineCap, Point, Rect};

use crate::alpha_runs::{AlphaRun, AlphaRuns};
use crate::blitter::Blitter;
use crate::color::AlphaU8;
use crate::fixed_point::{fdot6, fdot8, fdot16, FDot6, FDot8, FDot16};
Expand Down Expand Up @@ -175,7 +176,7 @@ fn do_scanline(l: FDot8, top: i32, r: FDot8, alpha: AlphaU8, blitter: &mut dyn B
let width = right - left;
if let Some(width) = u32::try_from(width).ok().and_then(LengthU32::new) {
if let Ok(left) = u32::try_from(left) {
call_hline_blitter(left, top, width, alpha, blitter);
call_hline_blitter(left, Some(top), width, alpha, blitter);
}
}

Expand All @@ -186,7 +187,7 @@ fn do_scanline(l: FDot8, top: i32, r: FDot8, alpha: AlphaU8, blitter: &mut dyn B
}
}

fn call_hline_blitter(mut x: u32, y: u32, count: LengthU32, alpha: AlphaU8, blitter: &mut dyn Blitter) {
fn call_hline_blitter(mut x: u32, y: Option<u32>, count: LengthU32, alpha: AlphaU8, blitter: &mut dyn Blitter) {
const HLINE_STACK_BUFFER: usize = 100;

let mut runs = [None; HLINE_STACK_BUFFER + 1];
Expand All @@ -209,10 +210,12 @@ fn call_hline_blitter(mut x: u32, y: u32, count: LengthU32, alpha: AlphaU8, blit
debug_assert!(n <= std::u16::MAX as u32);
runs[0] = NonZeroU16::new(n as u16);
runs[n as usize] = None;
blitter.blit_anti_h(x, y, &aa, &runs);
if let Some(y) = y {
blitter.blit_anti_h(x, y, &mut aa, &mut runs);
}
x += n;

if n > count || count == 0 {
if n >= count || count == 0 {
break;
}

Expand Down Expand Up @@ -286,7 +289,13 @@ fn anti_hair_line_rgn(points: &[Point], clip: Option<&ScreenIntRect>, blitter: &
}

if !clip.to_int_rect().contains(&ir) {
do_anti_hairline(x0, y0, x1, y1, Some(clip), blitter);
let subclip = ir.intersect(&clip.to_int_rect())
.and_then(|r| r.to_screen_int_rect());

if let Some(subclip) = subclip {
do_anti_hairline(x0, y0, x1, y1, Some(subclip), blitter);
}

continue;
}

Expand All @@ -312,7 +321,7 @@ fn do_anti_hairline(
mut y0: FDot6,
mut x1: FDot6,
mut y1: FDot6,
clip_opt: Option<&ScreenIntRect>,
mut clip_opt: Option<ScreenIntRect>,
blitter: &mut dyn Blitter,
) -> Option<()> {
// check for integer NaN (0x80000000) which we can't handle (can't negate it)
Expand Down Expand Up @@ -387,7 +396,7 @@ fn do_anti_hairline(
let clip = clip.to_int_rect();

if istart >= clip.right() || istop <= clip.left() {
return None;
return Some(());
}

if istart < clip.left() {
Expand All @@ -408,7 +417,7 @@ fn do_anti_hairline(

debug_assert!(istart <= istop);
if istart == istop {
return None;
return Some(());
}

// now test if our Y values are completely inside the clip
Expand All @@ -428,7 +437,11 @@ fn do_anti_hairline(
bottom += 1;

if top >= clip.bottom() || bottom <= clip.top() {
return None;
return Some(());
}

if clip.top() <= top && clip.bottom() >= bottom {
clip_opt = None;
}
}
} else {
Expand Down Expand Up @@ -515,9 +528,21 @@ fn do_anti_hairline(
if left >= clip.right() || right <= clip.left() {
return Some(());
}

if clip.left() <= left && clip.right() >= right {
clip_opt = None;
}
}
}

let mut clip_blitter;
let blitter = if let Some(clip) = clip_opt {
clip_blitter = RectClipBlitter { blitter, clip };
&mut clip_blitter
} else {
blitter
};

// A bit ugly, but looks like this is the only way to have stack allocated object trait.
let mut hline_blitter;
let mut horish_blitter;
Expand Down Expand Up @@ -601,13 +626,13 @@ impl AntiHairBlitter for HLineAntiHairBlitter<'_> {
// lower line
let mut ma = fdot6::small_scale(a, mod64);
if ma != 0 {
call_hline_blitter(x, y, LENGTH_U32_ONE, ma, self.0);
call_hline_blitter(x, Some(y), LENGTH_U32_ONE, ma, self.0);
}

// upper line
ma = fdot6::small_scale(255 - a, mod64);
if ma != 0 {
call_hline_blitter(x, y.max(1) - 1, LENGTH_U32_ONE, ma, self.0);
call_hline_blitter(x, y.checked_sub(1), LENGTH_U32_ONE, ma, self.0);
}

fy - fdot16::ONE / 2
Expand All @@ -627,13 +652,13 @@ impl AntiHairBlitter for HLineAntiHairBlitter<'_> {

// lower line
if a != 0 {
call_hline_blitter(x, y, count, a, self.0);
call_hline_blitter(x, Some(y), count, a, self.0);
}

// upper line
a = 255 - a;
if a != 0 {
call_hline_blitter(x, y.max(1) - 1, count, a, self.0);
call_hline_blitter(x, y.checked_sub(1), count, a, self.0);
}

fy - fdot16::ONE / 2
Expand Down Expand Up @@ -768,3 +793,92 @@ impl AntiHairBlitter for VertishAntiHairBlitter<'_> {
fn i32_to_alpha(a: i32) -> u8 {
(a & 0xFF) as u8
}


struct RectClipBlitter<'a> {
blitter: &'a mut dyn Blitter,
clip: ScreenIntRect,
}

impl Blitter for RectClipBlitter<'_> {
fn blit_anti_h(&mut self, x: u32, y: u32, mut antialias: &mut [AlphaU8], mut runs: &mut [AlphaRun]) {
fn y_in_rect(y: u32, rect: ScreenIntRect) -> bool {
(y - rect.top()) < rect.height()
}

if !y_in_rect(y, self.clip) || x >= self.clip.right() {
return;
}

let mut x0 = x;
let mut x1 = x + compute_anti_width(runs);

if x1 <= self.clip.left() {
return;
}

debug_assert!(x0 < x1);
if x0 < self.clip.left() {
let dx = self.clip.left() - x0;
AlphaRuns::break_at(antialias, runs, dx as i32);
antialias = &mut antialias[dx as usize..];
runs = &mut runs[dx as usize..];
x0 = self.clip.left();
}

debug_assert!(x0 < x1 && runs[(x1 - x0) as usize].is_none());
if x1 > self.clip.right() {
x1 = self.clip.right();
AlphaRuns::break_at(antialias, runs, (x1 - x0) as i32);
runs[(x1 - x0) as usize] = None;
}

debug_assert!(x0 < x1 && runs[(x1 - x0) as usize].is_none());
debug_assert!(compute_anti_width(runs) == x1 - x0);

self.blitter.blit_anti_h(x0, y, antialias, runs);
}

fn blit_v(&mut self, x: u32, y: u32, height: LengthU32, alpha: AlphaU8) {
fn x_in_rect(x: u32, rect: ScreenIntRect) -> bool {
(x - rect.left()) < rect.width()
}

if !x_in_rect(x, self.clip) {
return;
}

let mut y0 = y;
let mut y1 = y + height.get();

if y0 < self.clip.top() {
y0 = self.clip.top();
}

if y1 > self.clip.bottom() {
y1 = self.clip.bottom();
}

if y0 < y1 {
if let Some(h) = LengthU32::new(y1 - y0) {
self.blitter.blit_v(x, y0, h, alpha);
}
}
}
}

fn compute_anti_width(runs: &[AlphaRun]) -> u32 {
let mut i = 0;
let mut width = 0;
// TODO: turn [AlphaRun] into an iterator?
loop {
if let Some(count) = runs[i] {
width += u32::from(count.get());
i += usize::from(count.get());
} else {
break;
}
}

width
}
4 changes: 2 additions & 2 deletions src/scan/path_aa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@ impl<'a> SuperBlitter<'a> {
self.base.real_blitter.blit_anti_h(
self.base.left,
u32::try_from(self.base.curr_iy).unwrap(),
&self.runs.alpha,
&self.runs.runs,
&mut self.runs.alpha,
&mut self.runs.runs,
);
self.runs.reset(self.base.width);
self.offset_x = 0;
Expand Down
24 changes: 24 additions & 0 deletions tests/hairline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,30 @@ fn clip_line_00() {
assert_eq!(draw_line(-10.0, 10.0, 110.0, 70.0, false, 0.0, LineCap::Butt), expected);
}

#[test]
fn clip_hline_top_aa() {
let expected = Pixmap::load_png("tests/images/hairline/clip-hline-top-aa.png").unwrap();
assert_eq!(draw_line(-1.0, 0.0, 101.0, 0.0, true, 1.0, LineCap::Butt), expected);
}

#[test]
fn clip_hline_bottom_aa() {
let expected = Pixmap::load_png("tests/images/hairline/clip-hline-bottom-aa.png").unwrap();
assert_eq!(draw_line(-1.0, 100.0, 101.0, 100.0, true, 1.0, LineCap::Butt), expected);
}

#[test]
fn clip_vline_left_aa() {
let expected = Pixmap::load_png("tests/images/hairline/clip-vline-left-aa.png").unwrap();
assert_eq!(draw_line(0.0, -1.0, 0.0, 101.0, true, 1.0, LineCap::Butt), expected);
}

#[test]
fn clip_vline_right_aa() {
let expected = Pixmap::load_png("tests/images/hairline/clip-vline-right-aa.png").unwrap();
assert_eq!(draw_line(100.0, -1.0, 100.0, 101.0, true, 1.0, LineCap::Butt), expected);
}

fn draw_quad(anti_alias: bool, width: f32, line_cap: LineCap) -> Pixmap {
let pixmap = Pixmap::new(200, 100).unwrap();
let mut canvas = Canvas::from(pixmap);
Expand Down
Binary file added tests/images/hairline/clip-hline-bottom-aa.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/images/hairline/clip-hline-top-aa.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/images/hairline/clip-vline-left-aa.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/images/hairline/clip-vline-right-aa.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 00a4aee

Please sign in to comment.