mirror of
https://github.com/italicsjenga/vello.git
synced 2025-01-08 20:01:30 +11:00
Extend modes for gradients
This patch implements the pad, repeat and reflect extend modes for gradient brushes. Adds a new example demonstrating the functionality. Also fixes a few bugs: * Clamps alpha in blend.wgsl for the `blend_compose` function. The `Plus` mode was generating `alpha > 1.0` leading to incorrect rendering. * Small change to radial gradients in fine.wgsl to reject pixels outside the cone when the circles don't nest. This requires further work to properly extend the cone when one of the radii is not 0.
This commit is contained in:
parent
299b47ea06
commit
15cd306af6
|
@ -113,11 +113,13 @@ impl Encoding {
|
|||
Patch::Ramp {
|
||||
draw_data_offset: offset,
|
||||
stops,
|
||||
extend,
|
||||
} => {
|
||||
let stops = stops.start + stops_base..stops.end + stops_base;
|
||||
Patch::Ramp {
|
||||
draw_data_offset: offset + offsets.draw_data,
|
||||
stops,
|
||||
extend: *extend,
|
||||
}
|
||||
}
|
||||
Patch::GlyphRun { index } => Patch::GlyphRun {
|
||||
|
@ -264,9 +266,9 @@ impl Encoding {
|
|||
gradient: DrawLinearGradient,
|
||||
color_stops: impl Iterator<Item = ColorStop>,
|
||||
alpha: f32,
|
||||
_extend: Extend,
|
||||
extend: Extend,
|
||||
) {
|
||||
self.add_ramp(color_stops, alpha);
|
||||
self.add_ramp(color_stops, alpha, extend);
|
||||
self.draw_tags.push(DrawTag::LINEAR_GRADIENT);
|
||||
self.draw_data
|
||||
.extend_from_slice(bytemuck::bytes_of(&gradient));
|
||||
|
@ -278,9 +280,9 @@ impl Encoding {
|
|||
gradient: DrawRadialGradient,
|
||||
color_stops: impl Iterator<Item = ColorStop>,
|
||||
alpha: f32,
|
||||
_extend: Extend,
|
||||
extend: Extend,
|
||||
) {
|
||||
self.add_ramp(color_stops, alpha);
|
||||
self.add_ramp(color_stops, alpha, extend);
|
||||
self.draw_tags.push(DrawTag::RADIAL_GRADIENT);
|
||||
self.draw_data
|
||||
.extend_from_slice(bytemuck::bytes_of(&gradient));
|
||||
|
@ -331,7 +333,12 @@ impl Encoding {
|
|||
self.path_tags.swap(len - 1, len - 2);
|
||||
}
|
||||
|
||||
fn add_ramp(&mut self, color_stops: impl Iterator<Item = ColorStop>, alpha: f32) {
|
||||
fn add_ramp(
|
||||
&mut self,
|
||||
color_stops: impl Iterator<Item = ColorStop>,
|
||||
alpha: f32,
|
||||
extend: Extend,
|
||||
) {
|
||||
let offset = self.draw_data.len();
|
||||
let stops_start = self.color_stops.len();
|
||||
if alpha != 1.0 {
|
||||
|
@ -343,6 +350,7 @@ impl Encoding {
|
|||
self.patches.push(Patch::Ramp {
|
||||
draw_data_offset: offset,
|
||||
stops: stops_start..self.color_stops.len(),
|
||||
extend,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use peniko::Image;
|
||||
use peniko::{Extend, Image};
|
||||
|
||||
use super::{
|
||||
glyph_cache::{CachedRange, GlyphCache, GlyphKey},
|
||||
|
@ -221,11 +221,13 @@ impl Resolver {
|
|||
ResolvedPatch::Ramp {
|
||||
draw_data_offset,
|
||||
ramp_id,
|
||||
extend,
|
||||
} => {
|
||||
if pos < *draw_data_offset {
|
||||
data.extend_from_slice(&encoding.draw_data[pos..*draw_data_offset]);
|
||||
}
|
||||
data.extend_from_slice(bytemuck::bytes_of(ramp_id));
|
||||
let index_mode = (ramp_id << 2) | *extend as u32;
|
||||
data.extend_from_slice(bytemuck::bytes_of(&index_mode));
|
||||
pos = *draw_data_offset + 4;
|
||||
}
|
||||
ResolvedPatch::GlyphRun { .. } => {}
|
||||
|
@ -340,11 +342,13 @@ impl Resolver {
|
|||
Patch::Ramp {
|
||||
draw_data_offset,
|
||||
stops,
|
||||
extend,
|
||||
} => {
|
||||
let ramp_id = self.ramp_cache.add(&encoding.color_stops[stops.clone()]);
|
||||
self.patches.push(ResolvedPatch::Ramp {
|
||||
draw_data_offset: *draw_data_offset + sizes.draw_data,
|
||||
ramp_id,
|
||||
extend: *extend,
|
||||
});
|
||||
}
|
||||
Patch::GlyphRun { index } => {
|
||||
|
@ -472,6 +476,8 @@ pub enum Patch {
|
|||
draw_data_offset: usize,
|
||||
/// Range of the gradient stops in the resource set.
|
||||
stops: Range<usize>,
|
||||
/// Extend mode for the gradient.
|
||||
extend: Extend,
|
||||
},
|
||||
/// Glyph run resource.
|
||||
GlyphRun {
|
||||
|
@ -501,6 +507,8 @@ enum ResolvedPatch {
|
|||
draw_data_offset: usize,
|
||||
/// Resolved ramp index.
|
||||
ramp_id: u32,
|
||||
/// Extend mode for the gradient.
|
||||
extend: Extend,
|
||||
},
|
||||
GlyphRun {
|
||||
/// Index of the original glyph run in the encoding.
|
||||
|
|
|
@ -30,6 +30,7 @@ pub fn test_scenes() -> SceneSet {
|
|||
scene!(funky_paths),
|
||||
scene!(cardioid_and_friends),
|
||||
scene!(animated_text: animated),
|
||||
scene!(gradient_extend),
|
||||
scene!(brush_transform: animated),
|
||||
scene!(blend_grid),
|
||||
scene!(conflation_artifacts),
|
||||
|
@ -249,6 +250,72 @@ fn brush_transform(sb: &mut SceneBuilder, params: &mut SceneParams) {
|
|||
);
|
||||
}
|
||||
|
||||
fn gradient_extend(sb: &mut SceneBuilder, params: &mut SceneParams) {
|
||||
fn square(
|
||||
sb: &mut SceneBuilder,
|
||||
is_radial: bool,
|
||||
transform: Affine,
|
||||
brush_transform: Option<Affine>,
|
||||
extend: Extend,
|
||||
) {
|
||||
let colors = [Color::RED, Color::GREEN, Color::BLUE];
|
||||
let width = 400f64;
|
||||
let height = 400f64;
|
||||
let gradient: Brush = if is_radial {
|
||||
Gradient::new_radial((width * 0.5, height * 0.5), (width * 0.25) as f32)
|
||||
.with_stops(colors)
|
||||
.with_extend(extend)
|
||||
.into()
|
||||
} else {
|
||||
Gradient::new_linear((width * 0.35, height * 0.5), (width * 0.65, height * 0.5))
|
||||
.with_stops(colors)
|
||||
.with_extend(extend)
|
||||
.into()
|
||||
};
|
||||
sb.fill(
|
||||
Fill::NonZero,
|
||||
transform,
|
||||
&gradient,
|
||||
brush_transform,
|
||||
&Rect::new(0.0, 0.0, width, height),
|
||||
);
|
||||
}
|
||||
let extend_modes = [Extend::Pad, Extend::Repeat, Extend::Reflect];
|
||||
for x in 0..3 {
|
||||
let extend = extend_modes[x];
|
||||
for y in 0..2 {
|
||||
let is_radial = y & 1 != 0;
|
||||
let transform = Affine::translate((x as f64 * 450.0 + 50.0, y as f64 * 450.0 + 100.0));
|
||||
square(sb, is_radial, transform, None, extend);
|
||||
}
|
||||
}
|
||||
let white = Color::WHITE.into();
|
||||
params.text.add(
|
||||
sb,
|
||||
None,
|
||||
32.0,
|
||||
Some(&white),
|
||||
Affine::translate((50.0, 70.0)),
|
||||
"Pad",
|
||||
);
|
||||
params.text.add(
|
||||
sb,
|
||||
None,
|
||||
32.0,
|
||||
Some(&white),
|
||||
Affine::translate((500.0, 70.0)),
|
||||
"Repeat",
|
||||
);
|
||||
params.text.add(
|
||||
sb,
|
||||
None,
|
||||
32.0,
|
||||
Some(&white),
|
||||
Affine::translate((950.0, 70.0)),
|
||||
"Reflect",
|
||||
);
|
||||
}
|
||||
|
||||
fn blend_grid(sb: &mut SceneBuilder, _: &mut SceneParams) {
|
||||
const BLEND_MODES: &[Mix] = &[
|
||||
Mix::Normal,
|
||||
|
|
|
@ -61,16 +61,20 @@ fn read_color(cmd_ix: u32) -> CmdColor {
|
|||
}
|
||||
|
||||
fn read_lin_grad(cmd_ix: u32) -> CmdLinGrad {
|
||||
let index = ptcl[cmd_ix + 1u];
|
||||
let index_mode = ptcl[cmd_ix + 1u];
|
||||
let index = index_mode >> 2u;
|
||||
let extend_mode = index_mode & 0x3u;
|
||||
let info_offset = ptcl[cmd_ix + 2u];
|
||||
let line_x = bitcast<f32>(info[info_offset]);
|
||||
let line_y = bitcast<f32>(info[info_offset + 1u]);
|
||||
let line_c = bitcast<f32>(info[info_offset + 2u]);
|
||||
return CmdLinGrad(index, line_x, line_y, line_c);
|
||||
return CmdLinGrad(index, extend_mode, line_x, line_y, line_c);
|
||||
}
|
||||
|
||||
fn read_rad_grad(cmd_ix: u32) -> CmdRadGrad {
|
||||
let index = ptcl[cmd_ix + 1u];
|
||||
let index_mode = ptcl[cmd_ix + 1u];
|
||||
let index = index_mode >> 2u;
|
||||
let extend_mode = index_mode & 0x3u;
|
||||
let info_offset = ptcl[cmd_ix + 2u];
|
||||
let m0 = bitcast<f32>(info[info_offset]);
|
||||
let m1 = bitcast<f32>(info[info_offset + 1u]);
|
||||
|
@ -81,7 +85,7 @@ fn read_rad_grad(cmd_ix: u32) -> CmdRadGrad {
|
|||
let c1 = vec2(bitcast<f32>(info[info_offset + 6u]), bitcast<f32>(info[info_offset + 7u]));
|
||||
let ra = bitcast<f32>(info[info_offset + 8u]);
|
||||
let roff = bitcast<f32>(info[info_offset + 9u]);
|
||||
return CmdRadGrad(index, matrx, xlat, c1, ra, roff);
|
||||
return CmdRadGrad(index, extend_mode, matrx, xlat, c1, ra, roff);
|
||||
}
|
||||
|
||||
fn read_image(cmd_ix: u32) -> CmdImage {
|
||||
|
@ -108,6 +112,25 @@ fn read_end_clip(cmd_ix: u32) -> CmdEndClip {
|
|||
return CmdEndClip(blend, alpha);
|
||||
}
|
||||
|
||||
fn extend_mode(t: f32, mode: u32) -> f32 {
|
||||
// This can be replaced with two selects, exchanging the cost
|
||||
// of a branch for additional ALU
|
||||
switch mode {
|
||||
// PAD
|
||||
case 0u: {
|
||||
return clamp(t, 0.0, 1.0);
|
||||
}
|
||||
// REPEAT
|
||||
case 1u: {
|
||||
return fract(t);
|
||||
}
|
||||
// REFLECT (2)
|
||||
default: {
|
||||
return abs(t - 2.0 * round(0.5 * t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
@group(0) @binding(3)
|
||||
|
@ -262,7 +285,7 @@ fn main(
|
|||
let d = lin.line_x * xy.x + lin.line_y * xy.y + lin.line_c;
|
||||
for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) {
|
||||
let my_d = d + lin.line_x * f32(i);
|
||||
let x = i32(round(clamp(my_d, 0.0, 1.0) * f32(GRADIENT_WIDTH - 1)));
|
||||
let x = i32(round(extend_mode(my_d, lin.extend_mode) * f32(GRADIENT_WIDTH - 1)));
|
||||
let fg_rgba = textureLoad(gradients, vec2(x, i32(lin.index)), 0);
|
||||
let fg_i = fg_rgba * area[i];
|
||||
rgba[i] = rgba[i] * (1.0 - fg_i.a) + fg_i;
|
||||
|
@ -278,11 +301,16 @@ fn main(
|
|||
let xy_xformed = rad.matrx.xy * my_xy.x + rad.matrx.zw * my_xy.y + rad.xlat;
|
||||
let ba = dot(xy_xformed, rad.c1);
|
||||
let ca = rad.ra * dot(xy_xformed, xy_xformed);
|
||||
let t = sqrt(ba * ba + ca) - ba - rad.roff;
|
||||
let x = i32(round(clamp(t, 0.0, 1.0) * f32(GRADIENT_WIDTH - 1)));
|
||||
let fg_rgba = textureLoad(gradients, vec2(x, i32(rad.index)), 0);
|
||||
let fg_i = fg_rgba * area[i];
|
||||
rgba[i] = rgba[i] * (1.0 - fg_i.a) + fg_i;
|
||||
let t0 = sqrt(ba * ba + ca) - ba;
|
||||
// For radial gradients that generate a cone, reject pixels outside
|
||||
// the region.
|
||||
if t0 >= 0.0 {
|
||||
let t = t0 - rad.roff;
|
||||
let x = i32(round(extend_mode(t, rad.extend_mode) * f32(GRADIENT_WIDTH - 1)));
|
||||
let fg_rgba = textureLoad(gradients, vec2(x, i32(rad.index)), 0);
|
||||
let fg_i = fg_rgba * area[i];
|
||||
rgba[i] = rgba[i] * (1.0 - fg_i.a) + fg_i;
|
||||
}
|
||||
}
|
||||
cmd_ix += 3u;
|
||||
}
|
||||
|
@ -352,7 +380,7 @@ fn main(
|
|||
let fg = rgba[i];
|
||||
// Max with a small epsilon to avoid NaNs
|
||||
let a_inv = 1.0 / max(fg.a, 1e-6);
|
||||
let rgba_sep = vec4(fg.rgb * a_inv, fg.a);
|
||||
let rgba_sep = vec4(fg.rgb * a_inv, fg.a);
|
||||
textureStore(output, vec2<i32>(coords), rgba_sep);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -306,7 +306,8 @@ fn blend_compose(
|
|||
let as_fa = as_ * fa;
|
||||
let ab_fb = ab * fb;
|
||||
let co = as_fa * cs + ab_fb * cb;
|
||||
return vec4(co, as_fa + ab_fb);
|
||||
// Modes like COMPOSE_PLUS can generate alpha > 1.0, so clamp.
|
||||
return vec4(co, min(as_fa + ab_fb, 1.0));
|
||||
}
|
||||
|
||||
// Apply color mixing and composition. Both input and output colors are
|
||||
|
|
|
@ -44,6 +44,7 @@ struct CmdColor {
|
|||
|
||||
struct CmdLinGrad {
|
||||
index: u32,
|
||||
extend_mode: u32,
|
||||
line_x: f32,
|
||||
line_y: f32,
|
||||
line_c: f32,
|
||||
|
@ -51,6 +52,7 @@ struct CmdLinGrad {
|
|||
|
||||
struct CmdRadGrad {
|
||||
index: u32,
|
||||
extend_mode: u32,
|
||||
matrx: vec4<f32>,
|
||||
xlat: vec2<f32>,
|
||||
c1: vec2<f32>,
|
||||
|
|
Loading…
Reference in a new issue