blob: 14a83d7284abe233f80895c1dff40125940909ec [file] [edit]
// Copyright 2026 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Tests for rendering a scene at an offset into the target.
use crate::load_image;
use crate::renderer::Renderer;
use crate::util::{crossed_line_star, stops_green_blue};
use vello_common::color::palette::css::{
BLUE, FUCHSIA, LIME, REBECCA_PURPLE, RED, ROYAL_BLUE, TOMATO, YELLOW,
};
use vello_common::color::{AlphaColor, Srgb};
use vello_common::filter_effects::{EdgeMode, Filter, FilterPrimitive};
use vello_common::kurbo::{Affine, Circle, Point, Rect, Shape};
use vello_common::paint::Image;
use vello_common::peniko::{
BlendMode, Compose, Extend, Gradient, ImageQuality, ImageSampler, LinearGradientPosition, Mix,
};
use vello_dev_macros::vello_test;
use vello_gpu::{ClearSettings, RectU16, TargetInit};
const OFFSET: (i32, i32) = (-11, -5);
const CLEAR_RECTS: &[RectU16] = &[
RectU16::new(6, 6, 32, 32),
RectU16::new(56, 10, 90, 44),
RectU16::new(22, 58, 66, 92),
];
fn fill_circle(ctx: &mut impl Renderer) {
ctx.set_paint(LIME);
ctx.fill_path(&Circle::new((50.0, 50.0), 45.0).to_path(0.1));
}
fn fill_target(ctx: &mut impl Renderer, color: AlphaColor<Srgb>) {
ctx.set_paint(color);
ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0));
ctx.flush();
ctx.render();
ctx.reset();
}
#[vello_test]
fn offset_overflowing_shape_stays_clipped(ctx: &mut impl Renderer) {
ctx.set_offset((50, 50));
ctx.set_paint(LIME);
ctx.fill_path(&Circle::new((0.0, 0.0), 45.0).to_path(0.1));
}
#[vello_test]
fn offset_gradient(ctx: &mut impl Renderer) {
ctx.set_offset((-49, -51));
ctx.set_paint(Gradient {
kind: LinearGradientPosition {
start: Point::new(20.0, 20.0),
end: Point::new(80.0, 20.0),
}
.into(),
stops: stops_green_blue(),
..Default::default()
});
ctx.fill_rect(&Rect::new(20.0, 20.0, 80.0, 80.0));
}
#[vello_test]
fn offset_image(ctx: &mut impl Renderer) {
ctx.set_offset((-49, -51));
let image = ctx.get_image_source(load_image!("color_grid_16x16"));
ctx.set_paint_transform(Affine::translate((20.0, 20.0)) * Affine::scale(3.75));
ctx.set_paint(Image {
image,
sampler: ImageSampler {
x_extend: Extend::Pad,
y_extend: Extend::Pad,
quality: ImageQuality::Low,
alpha: 1.0,
},
});
ctx.fill_rect(&Rect::new(20.0, 20.0, 80.0, 80.0));
}
fn fill_clipped_star(ctx: &mut impl Renderer, offset: (i32, i32)) {
ctx.set_offset(offset);
ctx.push_clip_layer(&crossed_line_star());
ctx.set_paint(REBECCA_PURPLE);
ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0));
ctx.pop_layer();
}
#[vello_test(webgl_texture)]
fn offset_up_left(ctx: &mut impl Renderer) {
fill_clipped_star(ctx, (-49, -51));
}
#[vello_test(webgl_texture)]
fn offset_up_right(ctx: &mut impl Renderer) {
fill_clipped_star(ctx, (49, -51));
}
#[vello_test(webgl_texture)]
fn offset_down_left(ctx: &mut impl Renderer) {
fill_clipped_star(ctx, (-49, 51));
}
#[vello_test(webgl_texture)]
fn offset_down_right(ctx: &mut impl Renderer) {
fill_clipped_star(ctx, (49, 51));
}
#[vello_test(skip_multithreaded, gpu_tolerance = 2)]
fn offset_filter_drop_shadow(ctx: &mut impl Renderer) {
ctx.set_offset(OFFSET);
ctx.push_filter_layer(Filter::from_primitive(FilterPrimitive::DropShadow {
dx: -20.0,
dy: -20.0,
std_deviation: 3.0,
color: TOMATO,
edge_mode: EdgeMode::None,
}));
ctx.set_paint(ROYAL_BLUE);
ctx.fill_rect(&Rect::new(20.0, 20.0, 60.0, 60.0));
ctx.pop_layer();
}
#[vello_test(transparent, gpu_tolerance = 2)]
fn offset_clear_viewport(ctx: &mut impl Renderer) {
ctx.set_target_init(TargetInit::Clear(ClearSettings::Viewport {
color: AlphaColor::from_rgba8(18, 52, 86, 120),
}));
ctx.set_offset(OFFSET);
ctx.set_paint(LIME);
let center = (50.0 - f64::from(OFFSET.0), 50.0 - f64::from(OFFSET.1));
ctx.fill_path(&Circle::new(center, 35.0).to_path(0.1));
}
#[vello_test(transparent)]
fn offset_outside_target(ctx: &mut impl Renderer) {
ctx.set_target_init(TargetInit::Clear(ClearSettings::Viewport {
color: AlphaColor::from_rgba8(18, 52, 86, 120),
}));
ctx.set_offset((100, 0));
fill_circle(ctx);
}
#[vello_test(skip_webgl)]
fn offset_clear_rects(ctx: &mut impl Renderer) {
fill_target(ctx, BLUE);
ctx.set_target_init(TargetInit::Clear(ClearSettings::Rects {
color: AlphaColor::TRANSPARENT,
rects: CLEAR_RECTS,
}));
ctx.set_offset(OFFSET);
}
#[vello_test(skip_webgl)]
fn offset_src_over_isolates_root_blend(ctx: &mut impl Renderer) {
for (color, quadrant) in [
(RED, Rect::new(0.0, 0.0, 50.0, 50.0)),
(YELLOW, Rect::new(50.0, 0.0, 100.0, 50.0)),
(LIME, Rect::new(0.0, 50.0, 50.0, 100.0)),
(FUCHSIA, Rect::new(50.0, 50.0, 100.0, 100.0)),
] {
ctx.set_paint(color);
ctx.fill_rect(&quadrant);
}
ctx.flush();
ctx.render();
ctx.reset();
ctx.set_target_init(TargetInit::SrcOver);
ctx.set_offset(OFFSET);
let center = (50.0 - f64::from(OFFSET.0), 50.0 - f64::from(OFFSET.1));
ctx.set_paint(BLUE);
ctx.fill_rect(&Rect::from_center_size(center, (60.0, 60.0)));
let hole = Circle::new(center, 25.0).to_path(0.1);
ctx.push_layer(
Some(&hole),
Some(BlendMode::new(Mix::Normal, Compose::Clear)),
None,
None,
None,
);
ctx.fill_path(&hole);
ctx.pop_layer();
}
#[vello_test(skip_multithreaded)]
fn offset_destructive_filter_layer_outside_target(ctx: &mut impl Renderer) {
ctx.set_offset((-50, 0));
ctx.set_paint(BLUE);
ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0));
ctx.push_layer(
None,
Some(BlendMode::new(Mix::Normal, Compose::Copy)),
None,
None,
Some(Filter::from_primitive(FilterPrimitive::gaussian_blur(
2.0,
EdgeMode::None,
))),
);
ctx.set_paint(RED);
ctx.fill_rect(&Rect::new(5.0, 5.0, 25.0, 25.0));
ctx.pop_layer();
}