Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add blurred rectangles #16

Merged
merged 1 commit into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,39 @@ impl Vger {
self.render(prim);
}

/// Fills a blurred rectangle.
pub fn fill_blurred_rect<Rect: Into<LocalRect>>(
&mut self,
rect: Rect,
radius: f32,
paint_index: PaintIndex,
blur_radius: f32,
) {
let mut prim = Prim::default();
prim.prim_type = PrimType::BlurredRect as u32;
let r: LocalRect = rect.into();
let min = r.min();
let max = r.max();
prim.cvs[0] = min.x;
prim.cvs[1] = min.y;
prim.cvs[2] = max.x;
prim.cvs[3] = max.y;
prim.cvs[4] = blur_radius;
prim.radius = radius;
prim.paint = paint_index.index as u32;
prim.quad_bounds = [
min.x - blur_radius * 3.0,
min.y - blur_radius * 3.0,
max.x + blur_radius * 3.0,
max.y + blur_radius * 3.0,
];
prim.tex_bounds = prim.quad_bounds;
prim.xform = self.add_xform() as u32;
prim.scissor = self.add_scissor() as u32;

self.render(prim);
}

/// Strokes a rectangle.
pub fn stroke_rect(
&mut self,
Expand Down
3 changes: 3 additions & 0 deletions src/prim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ pub enum PrimType {

/// Path fills.
PathFill,

/// Rounded blurred rectangle.
BlurredRect,
}

#[derive(Copy, Clone, Default)]
Expand Down
48 changes: 48 additions & 0 deletions src/shader.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ const vgerGlyph = 8;
/// Path fills.
const vgerPathFill = 9;

/// Rounded blurred rectangle.
const vgerBlurredRect = 10;

struct Prim {

/// Min and max coordinates of the quad we're rendering.
Expand Down Expand Up @@ -343,6 +346,10 @@ fn sdPrimBounds(prim: Prim) -> BBox {
b = expand(b, cvs.cvs[i32(prim.start)+i]);
}
}
case 10u: { // vgerBlurredRect
b.min = prim.cv0;
b.max = prim.cv1;
}
default: {}
}
return b;
Expand Down Expand Up @@ -463,6 +470,26 @@ fn sdPrim(prim: Prim, p: vec2<f32>, filterWidth: f32) -> f32 {
d = d * s;
break;
}
case 10u: { // vgerBlurredRect
let blur_radius = prim.cv2.x;
let center = 0.5*(prim.cv1 + prim.cv0);
let half_size = 0.5*(prim.cv1 - prim.cv0);
let point = p - center;

let low = point.y - half_size.y;
let high = point.y + half_size.y;
let start = clamp(-3.0 * blur_radius, low, high);
let end = clamp(3.0 * blur_radius, low, high);

let step = (end - start) / 4.0;
var y = start + step * 0.5;
var value = 0.0;
for (var i: i32 = 0; i < 4; i++) {
value += roundedBoxShadowX(point.x, point.y - y, blur_radius, prim.radius, half_size) * gaussian(y, blur_radius) * step;
y += step;
}
d = 1.0 - value * 4.0;
}
default: { }
}
return d;
Expand Down Expand Up @@ -619,6 +646,27 @@ fn toLinear(s: f32) -> f32
return pow((s + 0.055)/1.055, 2.4);
}

// This approximates the error function, needed for the gaussian integral
fn erf(x: vec2<f32>) -> vec2<f32> {
let s = sign(x);
let a = abs(x);
var y = 1.0 + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
y *= y;
return s - s / (y * y);
}

fn gaussian(x: f32, sigma: f32) -> f32 {
let pi: f32 = 3.141592653589793;
return exp(-(x * x) / (2.0 * sigma * sigma)) / (sqrt(2.0 * pi) * sigma);
}

fn roundedBoxShadowX(x: f32, y: f32, sigma: f32, corner: f32, halfSize: vec2<f32>) -> f32 {
let delta = min(halfSize.y - corner - abs(y), 0.0);
let curved = halfSize.x - corner + sqrt(max(0.0, corner * corner - delta * delta));
let integral = 0.5 + 0.5 * erf((x + vec2(-curved, curved)) * (sqrt(0.5) / sigma));
return integral.y - integral.x;
}

@fragment
fn fs_main(
in: VertexOutput,
Expand Down
17 changes: 17 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,23 @@ fn fill_circle_translate() {
render_test(&mut vger, &device, &queue, "circle_translate.png", false);
}

#[test]
fn fill_blurred_rect() {
let (device, queue) = setup();

let mut vger = Vger::new(
device.clone(),
queue.clone(),
wgpu::TextureFormat::Rgba8UnormSrgb,
);

vger.begin(512.0, 512.0, 1.0);
let cyan = vger.color_paint(Color::CYAN);
vger.fill_blurred_rect(euclid::rect(100.0, 100.0, 100.0, 100.0), 10.0, cyan, 10.0);

render_test(&mut vger, &device, &queue, "blurred_rect.png", false);
}

#[test]
fn fill_rect() {
let (device, queue) = setup();
Expand Down