sparse_strips: Add ColorMatrix filter - #1765
AdrianEddy wants to merge 1 commit into
Conversation
c85156e to
9af510d
Compare
89cc579 to
b77f5f3
Compare
Add a 4x5 color matrix filter (`feColorMatrix` style): each output channel is a linear combination of the straight-alpha input channels plus a constant offset. vello_cpu applies matrices that only mix the color channels and preserve alpha directly to the premultiplied channels, skipping the unpremultiply/premultiply round trip. vello_gpu now packs filter parameters back to back with a per-filter texel stride, like encoded paints, instead of a fixed three-texel slot, so the six-texel color matrix doesn't widen every other filter: offset and flood take one texel, gaussian blur two, drop shadow three. Builds on the original color matrix work in linebender#1623. Co-authored-by: Bruce Mitchener <bruce.mitchener@gmail.com>
b77f5f3 to
94c3b36
Compare
|
@LaurenzV @laurenz-canva @grebmeg What would it take to land this? This + "linear chains of filters" would be sufficient for feature complete CSS |
|
AI-review finds one issue:
Reproduction: use vello_common::color::{AlphaColor, PremulRgba8};
use vello_common::filter_effects::{Filter, FilterPrimitive};
use vello_common::kurbo::Rect;
use vello_cpu::{Level, RenderMode};
use vello_tests::renderer::{CpuRenderer, GpuRenderer, Renderer};
fn render<R: Renderer>(alpha: u8, inline: bool) -> PremulRgba8 {
let mut ctx = R::new(64, 64, 0, Level::baseline(), RenderMode::OptimizeQuality);
let filter = Filter::from_primitive(FilterPrimitive::ColorMatrix {
matrix: [
0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 1.0,
],
});
if inline {
ctx.set_filter_effect(filter);
} else {
ctx.push_filter_layer(filter);
}
ctx.set_paint(AlphaColor::from_rgba8(0, 0, 255, alpha));
ctx.fill_rect(&Rect::new(16.0, 16.0, 48.0, 48.0));
if !inline {
ctx.pop_layer();
}
ctx.flush();
ctx.render();
ctx.snapshot().data()[32 * 64 + 32]
}
#[test]
fn review_color_matrix_transparent_input() {
let expected = PremulRgba8 {
r: 255,
g: 0,
b: 0,
a: 255,
};
let mut all_match = true;
for inline in [false, true] {
for alpha in [0, 1, 128, 255] {
let cpu = render::<CpuRenderer>(alpha, inline);
let gpu = render::<GpuRenderer>(alpha, inline);
println!("inline={inline} alpha={alpha}: CPU={cpu:?} GPU={gpu:?}");
all_match &= cpu == expected && gpu == expected;
}
}
assert!(
all_match,
"constant red matrix must produce red for every input alpha"
);
} |
Thanks, the issue is real, but it's pre-existing on main and not specific to ColorMatrix. Fixed separately in #1966, which keeps transparent draws while a filter is active. |
I'm definitely in favour of landing At the earliest, I could look into that next week. Once that's landed, we can continue with this and other filters. |
Adds a
ColorMatrixfilter: a 4x5 matrix in the style of SVGfeColorMatrix, where each output channel (R, G, B, A) is a linear combination of the straight-alpha input channels plus a constant offset, clamped to [0, 1].This reimplements #1623 by @waywardmonkeys on top of current
main(he is credited as co-author) and addresses the review feedback from there. On the color space question raised there: like the other filters, the matrix is applied to the straight-alpha, sRGB-encoded values, and the math stays local with no new dependency; it could move to a shared crate later.In
vello_cpuit is a per-pixel pass. Matrices that only mix the color channels and keep alpha unchanged (grayscale, sepia) are applied directly to the premultiplied pixels, skipping the unpremultiply/premultiply round trip. SIMD and running it outside the spatial filter path are left as a TODO (Floodhas the same limitation).In
vello_gpuit is one more filter pass. A color matrix needs six texels of parameters, and until now every filter got the same fixed slot, so this switches the filter data to a variable stride like encoded paints: filters are packed back to back and each takes only what it needs (offset and flood one texel, blur two, drop shadow three, color matrix six).Tests: snapshot tests, each run on
vello_cpu(u8 and f32, every SIMD level) and onvello_gpu, for sepia (the premultiplied path on the CPU),ALPHA_TO_BLACK(the straight-alpha path), and a matrix with a distinct non-zero value in every coefficient and a positive alpha offset, so a layout mix-up shows and fully transparent pixels in the layer are covered too. Unit tests cover the CPU paths, including that the premultiplied shortcut matches the straight-alpha path, and the GPU packing.This PR was generated by Claude.