mirror of
https://github.com/servo/servo.git
synced 2025-08-05 05:30:08 +01:00
canvas: Properly bound all image pattern axis by inserting clip (#37668)
Before we only handled no-repeat for rect, this means we rendered https://sagudev.github.io/briefcase/no-repeat.html incorrectly (like firefox). Now if one of the axis is bounded (does not repeat) we clip it and let other axis be unbounded (technically we clip it to end of canvas). This is also needed for vello backend. Testing: Tests in WPT exists and another test is added. --------- Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
parent
464d71ecfc
commit
75c13f1422
8 changed files with 314 additions and 70 deletions
|
@ -98,6 +98,7 @@ pub(crate) trait GenericDrawTarget<B: Backend> {
|
||||||
fn get_transform(&self) -> Transform2D<f32>;
|
fn get_transform(&self) -> Transform2D<f32>;
|
||||||
fn pop_clip(&mut self);
|
fn pop_clip(&mut self);
|
||||||
fn push_clip(&mut self, path: &B::Path);
|
fn push_clip(&mut self, path: &B::Path);
|
||||||
|
fn push_clip_rect(&mut self, rect: &Rect<i32>);
|
||||||
fn set_transform(&mut self, matrix: &Transform2D<f32>);
|
fn set_transform(&mut self, matrix: &Transform2D<f32>);
|
||||||
fn stroke(
|
fn stroke(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -309,11 +310,13 @@ pub(crate) trait GenericPath<B: Backend<Path = Self>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn bounding_box(&self) -> Rect<f64>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait PatternHelpers {
|
pub(crate) trait PatternHelpers {
|
||||||
fn is_zero_size_gradient(&self) -> bool;
|
fn is_zero_size_gradient(&self) -> bool;
|
||||||
fn draw_rect(&self, rect: &Rect<f32>) -> Rect<f32>;
|
fn x_bound(&self) -> Option<u32>;
|
||||||
|
fn y_bound(&self) -> Option<u32>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait StrokeOptionsHelpers {
|
pub(crate) trait StrokeOptionsHelpers {
|
||||||
|
@ -327,4 +330,5 @@ pub(crate) trait StrokeOptionsHelpers {
|
||||||
|
|
||||||
pub(crate) trait DrawOptionsHelpers {
|
pub(crate) trait DrawOptionsHelpers {
|
||||||
fn set_alpha(&mut self, val: f32);
|
fn set_alpha(&mut self, val: f32);
|
||||||
|
fn is_clear(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
|
@ -492,11 +492,17 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
||||||
// > from left to right (if any), adding to the array, for each glyph, the shape of the glyph
|
// > from left to right (if any), adding to the array, for each glyph, the shape of the glyph
|
||||||
// > as it is in the inline box, positioned on a coordinate space using CSS pixels with its
|
// > as it is in the inline box, positioned on a coordinate space using CSS pixels with its
|
||||||
// > origin is at the anchor point.
|
// > origin is at the anchor point.
|
||||||
self.drawtarget.fill_text(
|
self.maybe_bound_shape_with_pattern(
|
||||||
shaped_runs,
|
self.state.fill_style.clone(),
|
||||||
start,
|
&Rect::from_size(Size2D::new(total_advance, size)),
|
||||||
&self.state.fill_style,
|
|self_| {
|
||||||
&self.state.draw_options,
|
self_.drawtarget.fill_text(
|
||||||
|
shaped_runs,
|
||||||
|
start,
|
||||||
|
&self_.state.fill_style,
|
||||||
|
&self_.state.draw_options,
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -676,19 +682,22 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
||||||
return; // Paint nothing if gradient size is zero.
|
return; // Paint nothing if gradient size is zero.
|
||||||
}
|
}
|
||||||
|
|
||||||
let draw_rect = self.state.fill_style.draw_rect(rect);
|
|
||||||
|
|
||||||
if self.need_to_draw_shadow() {
|
if self.need_to_draw_shadow() {
|
||||||
self.draw_with_shadow(&draw_rect, |new_draw_target: &mut B::DrawTarget| {
|
self.draw_with_shadow(rect, |new_draw_target: &mut B::DrawTarget| {
|
||||||
new_draw_target.fill_rect(
|
new_draw_target.fill_rect(rect, &self.state.fill_style, &self.state.draw_options);
|
||||||
&draw_rect,
|
|
||||||
&self.state.fill_style,
|
|
||||||
&self.state.draw_options,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
self.drawtarget
|
self.maybe_bound_shape_with_pattern(
|
||||||
.fill_rect(&draw_rect, &self.state.fill_style, &self.state.draw_options);
|
self.state.fill_style.clone(),
|
||||||
|
&rect.cast(),
|
||||||
|
|self_| {
|
||||||
|
self_.drawtarget.fill_rect(
|
||||||
|
rect,
|
||||||
|
&self_.state.fill_style,
|
||||||
|
&self_.state.draw_options,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -711,12 +720,18 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
self.drawtarget.stroke_rect(
|
self.maybe_bound_shape_with_pattern(
|
||||||
rect,
|
self.state.stroke_style.clone(),
|
||||||
&self.state.stroke_style,
|
&rect.cast(),
|
||||||
&self.state.stroke_opts,
|
|self_| {
|
||||||
&self.state.draw_options,
|
self_.drawtarget.stroke_rect(
|
||||||
);
|
rect,
|
||||||
|
&self_.state.stroke_style,
|
||||||
|
&self_.state.stroke_opts,
|
||||||
|
&self_.state.draw_options,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -781,11 +796,17 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
||||||
return; // Path is uninvertible.
|
return; // Path is uninvertible.
|
||||||
};
|
};
|
||||||
|
|
||||||
self.drawtarget.fill(
|
self.maybe_bound_shape_with_pattern(
|
||||||
&path,
|
self.state.fill_style.clone(),
|
||||||
&self.state.fill_style,
|
&path.bounding_box(),
|
||||||
&self.state.draw_options.clone(),
|
|self_| {
|
||||||
);
|
self_.drawtarget.fill(
|
||||||
|
&path,
|
||||||
|
&self_.state.fill_style,
|
||||||
|
&self_.state.draw_options.clone(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn fill_path(&mut self, path_segments: &[PathSegment]) {
|
pub(crate) fn fill_path(&mut self, path_segments: &[PathSegment]) {
|
||||||
|
@ -809,12 +830,18 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
||||||
return; // Path is uninvertible.
|
return; // Path is uninvertible.
|
||||||
};
|
};
|
||||||
|
|
||||||
self.drawtarget.stroke(
|
self.maybe_bound_shape_with_pattern(
|
||||||
&path,
|
self.state.stroke_style.clone(),
|
||||||
&self.state.stroke_style,
|
&path.bounding_box(),
|
||||||
&self.state.stroke_opts,
|
|self_| {
|
||||||
&self.state.draw_options,
|
self_.drawtarget.stroke(
|
||||||
);
|
&path,
|
||||||
|
&self_.state.stroke_style,
|
||||||
|
&self_.state.stroke_opts,
|
||||||
|
&self_.state.draw_options,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn stroke_path(&mut self, path_segments: &[PathSegment]) {
|
pub(crate) fn stroke_path(&mut self, path_segments: &[PathSegment]) {
|
||||||
|
@ -825,12 +852,18 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
||||||
let mut path = B::Path::new();
|
let mut path = B::Path::new();
|
||||||
path.add_segments(path_segments);
|
path.add_segments(path_segments);
|
||||||
|
|
||||||
self.drawtarget.stroke(
|
self.maybe_bound_shape_with_pattern(
|
||||||
&path,
|
self.state.stroke_style.clone(),
|
||||||
&self.state.stroke_style,
|
&path.bounding_box(),
|
||||||
&self.state.stroke_opts,
|
|self_| {
|
||||||
&self.state.draw_options,
|
self_.drawtarget.stroke(
|
||||||
);
|
&path,
|
||||||
|
&self_.state.stroke_style,
|
||||||
|
&self_.state.stroke_opts,
|
||||||
|
&self_.state.draw_options,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn clip(&mut self) {
|
pub(crate) fn clip(&mut self) {
|
||||||
|
@ -1183,6 +1216,34 @@ impl<'a, B: Backend> CanvasData<'a, B> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Push a clip to the draw target to respect the non-repeating bound (either x, y, or both)
|
||||||
|
/// of the given pattern.
|
||||||
|
fn maybe_bound_shape_with_pattern<F>(
|
||||||
|
&mut self,
|
||||||
|
pattern: B::Pattern<'_>,
|
||||||
|
path_bound_box: &Rect<f64>,
|
||||||
|
draw_shape: F,
|
||||||
|
) where
|
||||||
|
F: FnOnce(&mut Self),
|
||||||
|
{
|
||||||
|
let x_bound = pattern.x_bound();
|
||||||
|
let y_bound = pattern.y_bound();
|
||||||
|
// Clear operations are also unbounded.
|
||||||
|
if self.state.draw_options.is_clear() || (x_bound.is_none() && y_bound.is_none()) {
|
||||||
|
draw_shape(self);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let rect = Rect::from_size(Size2D::new(
|
||||||
|
x_bound.unwrap_or(path_bound_box.size.width.ceil() as u32),
|
||||||
|
y_bound.unwrap_or(path_bound_box.size.height.ceil() as u32),
|
||||||
|
))
|
||||||
|
.cast();
|
||||||
|
let rect = self.get_transform().outer_transformed_rect(&rect);
|
||||||
|
self.drawtarget.push_clip_rect(&rect.cast());
|
||||||
|
draw_shape(self);
|
||||||
|
self.drawtarget.pop_clip();
|
||||||
|
}
|
||||||
|
|
||||||
/// It reads image data from the canvas
|
/// It reads image data from the canvas
|
||||||
/// canvas_size: The size of the canvas we're reading from
|
/// canvas_size: The size of the canvas we're reading from
|
||||||
/// read_rect: The area of the canvas we want to read from
|
/// read_rect: The area of the canvas we want to read from
|
||||||
|
|
|
@ -8,7 +8,7 @@ use std::collections::HashMap;
|
||||||
use canvas_traits::canvas::*;
|
use canvas_traits::canvas::*;
|
||||||
use compositing_traits::SerializableImageData;
|
use compositing_traits::SerializableImageData;
|
||||||
use cssparser::color::clamp_unit_f32;
|
use cssparser::color::clamp_unit_f32;
|
||||||
use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D};
|
use euclid::default::{Box2D, Point2D, Rect, Size2D, Transform2D, Vector2D};
|
||||||
use font_kit::font::Font;
|
use font_kit::font::Font;
|
||||||
use fonts::{ByteIndex, FontIdentifier, FontTemplateRefMethods};
|
use fonts::{ByteIndex, FontIdentifier, FontTemplateRefMethods};
|
||||||
use ipc_channel::ipc::IpcSharedMemory;
|
use ipc_channel::ipc::IpcSharedMemory;
|
||||||
|
@ -197,15 +197,13 @@ impl SurfacePattern {
|
||||||
transform,
|
transform,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn size(&self) -> Size2D<f32> {
|
|
||||||
self.image.size().cast()
|
|
||||||
}
|
|
||||||
pub fn repetition(&self) -> &Repetition {
|
pub fn repetition(&self) -> &Repetition {
|
||||||
&self.repeat
|
&self.repeat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Repetition {
|
pub enum Repetition {
|
||||||
Repeat,
|
Repeat,
|
||||||
RepeatX,
|
RepeatX,
|
||||||
|
@ -284,33 +282,23 @@ impl PatternHelpers for Pattern {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_rect(&self, rect: &Rect<f32>) -> Rect<f32> {
|
fn x_bound(&self) -> Option<u32> {
|
||||||
match self {
|
match self {
|
||||||
Pattern::Surface(pattern) => {
|
Pattern::Surface(pattern) => match pattern.repetition() {
|
||||||
let pattern_rect = Rect::new(Point2D::origin(), pattern.size());
|
Repetition::RepeatX | Repetition::Repeat => None, // x is not bounded
|
||||||
let mut draw_rect = rect.intersection(&pattern_rect).unwrap_or(Rect::zero());
|
Repetition::RepeatY | Repetition::NoRepeat => Some(pattern.image.size().width),
|
||||||
|
|
||||||
match pattern.repetition() {
|
|
||||||
Repetition::NoRepeat => {
|
|
||||||
draw_rect.size.width = draw_rect.size.width.min(pattern_rect.size.width);
|
|
||||||
draw_rect.size.height = draw_rect.size.height.min(pattern_rect.size.height);
|
|
||||||
},
|
|
||||||
Repetition::RepeatX => {
|
|
||||||
draw_rect.size.width = rect.size.width;
|
|
||||||
draw_rect.size.height = draw_rect.size.height.min(pattern_rect.size.height);
|
|
||||||
},
|
|
||||||
Repetition::RepeatY => {
|
|
||||||
draw_rect.size.height = rect.size.height;
|
|
||||||
draw_rect.size.width = draw_rect.size.width.min(pattern_rect.size.width);
|
|
||||||
},
|
|
||||||
Repetition::Repeat => {
|
|
||||||
draw_rect = *rect;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
draw_rect
|
|
||||||
},
|
},
|
||||||
Pattern::Color(..) | Pattern::LinearGradient(..) | Pattern::RadialGradient(..) => *rect,
|
Pattern::Color(..) | Pattern::LinearGradient(..) | Pattern::RadialGradient(..) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn y_bound(&self) -> Option<u32> {
|
||||||
|
match self {
|
||||||
|
Pattern::Surface(pattern) => match pattern.repetition() {
|
||||||
|
Repetition::RepeatY | Repetition::Repeat => None, // y is not bounded
|
||||||
|
Repetition::RepeatX | Repetition::NoRepeat => Some(pattern.image.size().height),
|
||||||
|
},
|
||||||
|
Pattern::Color(..) | Pattern::LinearGradient(..) | Pattern::RadialGradient(..) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -340,6 +328,10 @@ impl DrawOptionsHelpers for raqote::DrawOptions {
|
||||||
fn set_alpha(&mut self, val: f32) {
|
fn set_alpha(&mut self, val: f32) {
|
||||||
self.alpha = val;
|
self.alpha = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_clear(&self) -> bool {
|
||||||
|
matches!(self.blend_mode, raqote::BlendMode::Clear)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_gradient_stops(gradient_stops: Vec<CanvasGradientStop>) -> Vec<raqote::GradientStop> {
|
fn create_gradient_stops(gradient_stops: Vec<CanvasGradientStop>) -> Vec<raqote::GradientStop> {
|
||||||
|
@ -576,6 +568,9 @@ impl GenericDrawTarget<RaqoteBackend> for raqote::DrawTarget {
|
||||||
fn push_clip(&mut self, path: &<RaqoteBackend as Backend>::Path) {
|
fn push_clip(&mut self, path: &<RaqoteBackend as Backend>::Path) {
|
||||||
self.push_clip(&path.into());
|
self.push_clip(&path.into());
|
||||||
}
|
}
|
||||||
|
fn push_clip_rect(&mut self, rect: &Rect<i32>) {
|
||||||
|
self.push_clip_rect(rect.to_box2d());
|
||||||
|
}
|
||||||
fn set_transform(&mut self, matrix: &Transform2D<f32>) {
|
fn set_transform(&mut self, matrix: &Transform2D<f32>) {
|
||||||
self.set_transform(matrix);
|
self.set_transform(matrix);
|
||||||
}
|
}
|
||||||
|
@ -730,6 +725,28 @@ impl GenericPath<RaqoteBackend> for Path {
|
||||||
let path: raqote::Path = (&*self).into();
|
let path: raqote::Path = (&*self).into();
|
||||||
self.0.set(path.transform(transform).into());
|
self.0.set(path.transform(transform).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bounding_box(&self) -> Rect<f64> {
|
||||||
|
let path: raqote::Path = self.into();
|
||||||
|
let mut points = vec![];
|
||||||
|
for op in path.ops {
|
||||||
|
match op {
|
||||||
|
PathOp::MoveTo(p) => points.push(p),
|
||||||
|
PathOp::LineTo(p) => points.push(p),
|
||||||
|
PathOp::QuadTo(p1, p2) => {
|
||||||
|
points.push(p1);
|
||||||
|
points.push(p2);
|
||||||
|
},
|
||||||
|
PathOp::CubicTo(p1, p2, p3) => {
|
||||||
|
points.push(p1);
|
||||||
|
points.push(p2);
|
||||||
|
points.push(p3);
|
||||||
|
},
|
||||||
|
PathOp::Close => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Box2D::from_points(points).to_rect().cast()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_first_point(op: &PathOp) -> Option<euclid::Point2D<f32, euclid::UnknownUnit>> {
|
fn get_first_point(op: &PathOp) -> Option<euclid::Point2D<f32, euclid::UnknownUnit>> {
|
||||||
|
|
23
tests/wpt/meta/MANIFEST.json
vendored
23
tests/wpt/meta/MANIFEST.json
vendored
|
@ -478477,7 +478477,7 @@
|
||||||
[]
|
[]
|
||||||
],
|
],
|
||||||
"fill-and-stroke-styles.yaml": [
|
"fill-and-stroke-styles.yaml": [
|
||||||
"f2888e648f42bd052fdc3f38c317866c7f153824",
|
"32402680bf14ee87815aafee0b33af3703c71c3b",
|
||||||
[]
|
[]
|
||||||
],
|
],
|
||||||
"filters.yaml": [
|
"filters.yaml": [
|
||||||
|
@ -703792,6 +703792,13 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"2d.pattern.paint.norepeat.outside_arc.html": [
|
||||||
|
"73159d1bbc6e40a1c361e393bd1b0ea33e30478c",
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"2d.pattern.paint.orientation.canvas.html": [
|
"2d.pattern.paint.orientation.canvas.html": [
|
||||||
"b68e04272cd62933a1f09da984adda4746a48e1a",
|
"b68e04272cd62933a1f09da984adda4746a48e1a",
|
||||||
[
|
[
|
||||||
|
@ -714584,6 +714591,20 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"2d.pattern.paint.norepeat.outside_arc.html": [
|
||||||
|
"9052f14e5b501a98c82538504911013e08913bef",
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"2d.pattern.paint.norepeat.outside_arc.worker.js": [
|
||||||
|
"96e27da06b0714ae27d1414908a5b320396fc84a",
|
||||||
|
[
|
||||||
|
"html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.outside_arc.worker.html",
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"2d.pattern.paint.orientation.canvas.html": [
|
"2d.pattern.paint.orientation.canvas.html": [
|
||||||
"3fa8a96a4ad8a17559c613c761eb30a34b71445e",
|
"3fa8a96a4ad8a17559c613c761eb30a34b71445e",
|
||||||
[
|
[
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Canvas test: 2d.pattern.paint.norepeat.outside_arc</title>
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
<script src="/html/canvas/resources/canvas-tests.js"></script>
|
||||||
|
<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
|
||||||
|
<body class="show_output">
|
||||||
|
|
||||||
|
<h1>2d.pattern.paint.norepeat.outside_arc</h1>
|
||||||
|
<p class="desc"></p>
|
||||||
|
|
||||||
|
|
||||||
|
<p class="output">Actual output:</p>
|
||||||
|
<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
|
||||||
|
<p class="output expectedtext">Expected output:<p><img src="/images/green-100x50.png" class="output expected" id="expected" alt="">
|
||||||
|
<ul id="d"></ul>
|
||||||
|
<script>
|
||||||
|
var t = async_test("");
|
||||||
|
_addTest(function(canvas, ctx) {
|
||||||
|
|
||||||
|
ctx.fillStyle = '#f00';
|
||||||
|
ctx.fillRect(0, 0, 100, 50);
|
||||||
|
var img = document.getElementById('red-16x16.png');
|
||||||
|
var pattern = ctx.createPattern(img, 'no-repeat');
|
||||||
|
ctx.fillStyle = '#0f0';
|
||||||
|
ctx.fillRect(0, 0, 100, 50);
|
||||||
|
ctx.fillStyle = pattern;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, 50, 0, Math.PI * 2);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
ctx.fillStyle = '#0f0';
|
||||||
|
ctx.fillRect(0, 0, 16, 16);
|
||||||
|
_assertPixel(canvas, 1,1, 0,255,0,255);
|
||||||
|
_assertPixel(canvas, 20,1, 0,255,0,255);
|
||||||
|
_assertPixel(canvas, 1,20, 0,255,0,255);
|
||||||
|
_assertPixel(canvas, 48,48, 0,255,0,255);
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<img src="/images/red-16x16.png" id="red-16x16.png" class="resource">
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>OffscreenCanvas test: 2d.pattern.paint.norepeat.outside_arc</title>
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
<script src="/html/canvas/resources/canvas-tests.js"></script>
|
||||||
|
|
||||||
|
<h1>2d.pattern.paint.norepeat.outside_arc</h1>
|
||||||
|
<p class="desc"></p>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
promise_test(async t => {
|
||||||
|
|
||||||
|
var canvas = new OffscreenCanvas(100, 50);
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
ctx.fillStyle = '#f00';
|
||||||
|
ctx.fillRect(0, 0, 100, 50);
|
||||||
|
var response = await fetch('/images/red-16x16.png')
|
||||||
|
var blob = await response.blob();
|
||||||
|
var img = await createImageBitmap(blob);
|
||||||
|
var pattern = ctx.createPattern(img, 'no-repeat');
|
||||||
|
ctx.fillStyle = '#0f0';
|
||||||
|
ctx.fillRect(0, 0, 100, 50);
|
||||||
|
ctx.fillStyle = pattern;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, 50, 0, Math.PI * 2);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
ctx.fillStyle = '#0f0';
|
||||||
|
ctx.fillRect(0, 0, 16, 16);
|
||||||
|
_assertPixel(canvas, 1,1, 0,255,0,255);
|
||||||
|
_assertPixel(canvas, 20,1, 0,255,0,255);
|
||||||
|
_assertPixel(canvas, 1,20, 0,255,0,255);
|
||||||
|
_assertPixel(canvas, 48,48, 0,255,0,255);
|
||||||
|
|
||||||
|
}, "");
|
||||||
|
</script>
|
|
@ -0,0 +1,33 @@
|
||||||
|
// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
|
||||||
|
// OffscreenCanvas test in a worker:2d.pattern.paint.norepeat.outside_arc
|
||||||
|
// Description:
|
||||||
|
// Note:
|
||||||
|
|
||||||
|
importScripts("/resources/testharness.js");
|
||||||
|
importScripts("/html/canvas/resources/canvas-tests.js");
|
||||||
|
|
||||||
|
promise_test(async t => {
|
||||||
|
var canvas = new OffscreenCanvas(100, 50);
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
ctx.fillStyle = '#f00';
|
||||||
|
ctx.fillRect(0, 0, 100, 50);
|
||||||
|
var response = await fetch('/images/red-16x16.png')
|
||||||
|
var blob = await response.blob();
|
||||||
|
var img = await createImageBitmap(blob);
|
||||||
|
var pattern = ctx.createPattern(img, 'no-repeat');
|
||||||
|
ctx.fillStyle = '#0f0';
|
||||||
|
ctx.fillRect(0, 0, 100, 50);
|
||||||
|
ctx.fillStyle = pattern;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, 50, 0, Math.PI * 2);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
ctx.fillStyle = '#0f0';
|
||||||
|
ctx.fillRect(0, 0, 16, 16);
|
||||||
|
_assertPixel(canvas, 1,1, 0,255,0,255);
|
||||||
|
_assertPixel(canvas, 20,1, 0,255,0,255);
|
||||||
|
_assertPixel(canvas, 1,20, 0,255,0,255);
|
||||||
|
_assertPixel(canvas, 48,48, 0,255,0,255);
|
||||||
|
}, "");
|
||||||
|
done();
|
|
@ -1867,6 +1867,30 @@
|
||||||
expected: green
|
expected: green
|
||||||
variants: *load-image-variant-definition
|
variants: *load-image-variant-definition
|
||||||
|
|
||||||
|
- name: 2d.pattern.paint.norepeat.outside_arc
|
||||||
|
images:
|
||||||
|
- red-16x16.png
|
||||||
|
code: |
|
||||||
|
ctx.fillStyle = '#f00';
|
||||||
|
ctx.fillRect(0, 0, 100, 50);
|
||||||
|
{{ load_image }}
|
||||||
|
var pattern = ctx.createPattern(img, 'no-repeat');
|
||||||
|
ctx.fillStyle = '#0f0';
|
||||||
|
ctx.fillRect(0, 0, 100, 50);
|
||||||
|
ctx.fillStyle = pattern;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, 50, 0, Math.PI * 2);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
ctx.fillStyle = '#0f0';
|
||||||
|
ctx.fillRect(0, 0, 16, 16);
|
||||||
|
@assert pixel 1,1 == 0,255,0,255;
|
||||||
|
@assert pixel 20,1 == 0,255,0,255;
|
||||||
|
@assert pixel 1,20 == 0,255,0,255;
|
||||||
|
@assert pixel 48,48 == 0,255,0,255;
|
||||||
|
expected: green
|
||||||
|
variants: *load-image-variant-definition
|
||||||
|
|
||||||
- name: 2d.pattern.paint.norepeat.coord1
|
- name: 2d.pattern.paint.norepeat.coord1
|
||||||
images:
|
images:
|
||||||
- green.png
|
- green.png
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue