mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-02 06:28:49 -06:00
vello_cpu 0.0.8's clip_color, shared by the nonseparable blend modes, divided 0 by 0 when a gray's equal components were rounded just below 0 or above 1 after set_lum. The NaN drew the pixel black at the source's alpha whatever the backdrop: nine gray levels under /BM /Color in an empty isolated group (0.96 g among them) rendered (0, 0, 0), and two more at half opacity came out wrong. Vendor vello_cpu 0.0.8 (Apache-2.0 OR MIT) with a backport of linebender/vello#2000, which clips only when the denominator is positive. No vello_cpu release carries the fix yet; drop the copy when one does. Tests in it::nonseparable_gray run every gray level. The two Color ramps fail with the unpatched blend.rs. The Luminosity ramp (black over each opaque gray, upstream's other case) passes either way here: pages use the u8 pipeline, and that case only failed in the f32 one; it stays as a guard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
101 lines
3.9 KiB
Rust
101 lines
3.9 KiB
Rust
// Copyright 2025 the Vello Authors
|
|
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
|
|
//! Using masks with Vello CPU.
|
|
|
|
use vello_cpu::color::palette::css::{BLUE, RED, WHITE};
|
|
use vello_cpu::kurbo::Rect;
|
|
use vello_cpu::{Mask, Pixmap, RenderContext, Resources};
|
|
|
|
const SIZE: u16 = 200;
|
|
|
|
fn main() {
|
|
// Vello CPU supports applying luminance and alpha masks to your drawings.
|
|
|
|
// First, we need to create our actual mask. There are multiple ways how
|
|
// you can get to it, in our case we are going to draw our own custom mask.
|
|
let mask = {
|
|
// In this case, we are drawing the mask ourselves. Note that the
|
|
// dimensions of the final mask need to match the dimensions of our
|
|
// original render context!
|
|
let mut mask_ctx = RenderContext::new(SIZE, SIZE);
|
|
let mut mask_resources = Resources::new();
|
|
let mut pixmap = Pixmap::new(SIZE, SIZE);
|
|
|
|
mask_ctx.set_paint(RED);
|
|
mask_ctx.fill_rect(&Rect::new(30.0, 30.0, 170.0, 170.0));
|
|
mask_ctx.flush();
|
|
mask_ctx.render_to_pixmap(&mut mask_resources, &mut pixmap);
|
|
|
|
Mask::new_luminance(&pixmap)
|
|
};
|
|
|
|
// Create the main render context.
|
|
let mut ctx = RenderContext::new(SIZE, SIZE);
|
|
|
|
// Similarly to clip paths (see the clipping example), there are two
|
|
// different ways of applying them:
|
|
// The first method is by creating a new isolated layer where the mask
|
|
// will be applied once the whole layer has been drawn and is composited
|
|
// into the backdrop. The second method is by setting the mask in the
|
|
// render context, in which case the mask will be applied to each shape
|
|
// directly before being drawn. Which method you should use once again
|
|
// depends on the imaging model you are reflecting.
|
|
|
|
// Method 1: Non-isolated masking via `set_mask`.
|
|
{
|
|
ctx.set_paint(WHITE);
|
|
ctx.fill_rect(&Rect::new(0.0, 0.0, SIZE as f64, SIZE as f64));
|
|
|
|
// Once the mask is set, the mask will be applied to every path we
|
|
// are drawing individually before compositing it into the background.
|
|
ctx.set_mask(mask.clone());
|
|
// We first apply the mask to the blue rectangle and then composite it.
|
|
ctx.set_paint(BLUE);
|
|
ctx.fill_rect(&Rect::new(20.0, 20.0, 130.0, 130.0));
|
|
// Now, we yet again first apply the mask to the red rectangle only and
|
|
// then composite the result.
|
|
ctx.set_paint(RED);
|
|
ctx.fill_rect(&Rect::new(70.0, 70.0, 180.0, 180.0));
|
|
// Use this method if you want to reset the mask currently in place.
|
|
ctx.reset_mask();
|
|
|
|
ctx.flush();
|
|
save_pixmap(&ctx, "example_masking1");
|
|
}
|
|
|
|
ctx.reset();
|
|
// Method 2: Isolated masking via `push_mask_layer`.
|
|
{
|
|
ctx.set_paint(WHITE);
|
|
ctx.fill_rect(&Rect::new(0.0, 0.0, SIZE as f64, SIZE as f64));
|
|
|
|
// Using this method, we first push a new isolated layer. Apart from that,
|
|
// nothing happens so far.
|
|
ctx.push_mask_layer(mask);
|
|
// Here, the blue rectangle will be drawn first. Then, the red one is drawn
|
|
// and subsequently composited on top of the blue one,
|
|
// without any special handling.
|
|
ctx.set_paint(BLUE);
|
|
ctx.fill_rect(&Rect::new(20.0, 20.0, 130.0, 130.0));
|
|
ctx.set_paint(RED);
|
|
ctx.fill_rect(&Rect::new(70.0, 70.0, 180.0, 180.0));
|
|
// Now, the whole layer is taken, the mask is applied to all of it
|
|
// and then composited into the background.
|
|
ctx.pop_layer();
|
|
|
|
ctx.flush();
|
|
save_pixmap(&ctx, "example_masking2");
|
|
}
|
|
|
|
// As can be seen, the visual result of the two methods can be different!
|
|
}
|
|
|
|
fn save_pixmap(ctx: &RenderContext, filename: &str) {
|
|
let mut resources = Resources::new();
|
|
let mut pixmap = Pixmap::new(ctx.width(), ctx.height());
|
|
ctx.render_to_pixmap(&mut resources, &mut pixmap);
|
|
let png = pixmap.into_png().unwrap();
|
|
std::fs::write(format!("{filename}.png"), png).unwrap();
|
|
}
|