mirror of
https://github.com/servo/servo.git
synced 2025-08-04 13:10:20 +01:00
Implement drawing shadows in canvas.
This commit is contained in:
parent
24af4c4ec6
commit
465cea8db5
30 changed files with 134 additions and 204 deletions
|
@ -54,40 +54,6 @@ impl<'a> CanvasPaintTask<'a> {
|
|||
image_data
|
||||
}
|
||||
|
||||
/// It writes image data to the canvas
|
||||
/// source_rect: the area of the image data to be written
|
||||
/// dest_rect: The area of the canvas where the imagedata will be copied
|
||||
/// smoothing_enabled: if smoothing is applied to the copied pixels
|
||||
fn write_pixels(&self, imagedata: &[u8],
|
||||
image_size: Size2D<f64>,
|
||||
source_rect: Rect<f64>,
|
||||
dest_rect: Rect<f64>,
|
||||
smoothing_enabled: bool) {
|
||||
// From spec https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
|
||||
// When scaling up, if the imageSmoothingEnabled attribute is set to true, the user agent should attempt
|
||||
// to apply a smoothing algorithm to the image data when it is scaled.
|
||||
// Otherwise, the image must be rendered using nearest-neighbor interpolation.
|
||||
let filter = if smoothing_enabled {
|
||||
Filter::Linear
|
||||
} else {
|
||||
Filter::Point
|
||||
};
|
||||
// azure_hl operates with integers. We need to cast the image size
|
||||
let image_size = image_size.to_i32();
|
||||
|
||||
let source_surface = self.drawtarget.create_source_surface_from_data(
|
||||
&imagedata,
|
||||
image_size, image_size.width * 4, SurfaceFormat::B8G8R8A8);
|
||||
|
||||
let draw_surface_options = DrawSurfaceOptions::new(filter, true);
|
||||
let draw_options = DrawOptions::new(self.state.draw_options.alpha, CompositionOp::Over, AntialiasMode::None);
|
||||
|
||||
self.drawtarget.draw_surface(source_surface,
|
||||
dest_rect.to_azfloat(),
|
||||
source_rect.to_azfloat(),
|
||||
draw_surface_options, draw_options);
|
||||
}
|
||||
|
||||
/// dirty_rect: original dirty_rect provided by the putImageData call
|
||||
/// image_data_rect: the area of the image to be copied
|
||||
/// Result: It retuns the modified dirty_rect by the rules described in
|
||||
|
@ -136,23 +102,6 @@ impl<'a> CanvasPaintTask<'a> {
|
|||
|
||||
dirty_rect
|
||||
}
|
||||
|
||||
/// It writes an image to the destination canvas
|
||||
/// imagedata: Pixel information of the image to be written. It takes RGBA8
|
||||
/// image_size: The size of the image to be written
|
||||
/// dest_rect: Area of the destination canvas where the pixels will be copied
|
||||
/// smoothing_enabled: It determines if smoothing is applied to the image result
|
||||
fn write_image(&self, mut imagedata: Vec<u8>,
|
||||
image_size: Size2D<f64>, dest_rect: Rect<f64>, smoothing_enabled: bool) {
|
||||
if imagedata.len() == 0 {
|
||||
return
|
||||
}
|
||||
let image_rect = Rect::new(Point2D::new(0f64, 0f64), image_size);
|
||||
// rgba -> bgra
|
||||
byte_swap(&mut imagedata);
|
||||
self.write_pixels(&imagedata, image_size, image_rect, dest_rect, smoothing_enabled);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub struct CanvasPaintTask<'a> {
|
||||
|
@ -316,8 +265,15 @@ impl<'a> CanvasPaintTask<'a> {
|
|||
}
|
||||
);
|
||||
|
||||
self.drawtarget.fill_rect(&draw_rect, self.state.fill_style.to_pattern_ref(),
|
||||
Some(&self.state.draw_options));
|
||||
if self.need_to_draw_shadow() {
|
||||
self.draw_with_shadow(&draw_rect, |new_draw_target: &DrawTarget| {
|
||||
new_draw_target.fill_rect(&draw_rect, self.state.fill_style.to_pattern_ref(),
|
||||
Some(&self.state.draw_options));
|
||||
});
|
||||
} else {
|
||||
self.drawtarget.fill_rect(&draw_rect, self.state.fill_style.to_pattern_ref(),
|
||||
Some(&self.state.draw_options));
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_rect(&self, rect: &Rect<f32>) {
|
||||
|
@ -376,7 +332,19 @@ impl<'a> CanvasPaintTask<'a> {
|
|||
let source_rect = source_rect.ceil();
|
||||
// It discards the extra pixels (if any) that won't be painted
|
||||
let image_data = crop_image(image_data, image_size, source_rect);
|
||||
self.write_image(image_data, source_rect.size, dest_rect, smoothing_enabled);
|
||||
|
||||
if self.need_to_draw_shadow() {
|
||||
let rect = Rect::new(Point2D::new(dest_rect.origin.x as f32, dest_rect.origin.y as f32),
|
||||
Size2D::new(dest_rect.size.width as f32, dest_rect.size.height as f32));
|
||||
|
||||
self.draw_with_shadow(&rect, |new_draw_target: &DrawTarget| {
|
||||
write_image(&new_draw_target, image_data, source_rect.size, dest_rect,
|
||||
smoothing_enabled, self.state.draw_options.alpha);
|
||||
});
|
||||
} else {
|
||||
write_image(&self.drawtarget, image_data, source_rect.size, dest_rect,
|
||||
smoothing_enabled, self.state.draw_options.alpha);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_image_self(&self, image_size: Size2D<f64>,
|
||||
|
@ -384,9 +352,21 @@ impl<'a> CanvasPaintTask<'a> {
|
|||
smoothing_enabled: bool) {
|
||||
// Reads pixels from source image
|
||||
// In this case source and target are the same canvas
|
||||
let imagedata = self.read_pixels(source_rect, image_size);
|
||||
// Writes on target canvas
|
||||
self.write_image(imagedata, image_size, dest_rect, smoothing_enabled);
|
||||
let image_data = self.read_pixels(source_rect, image_size);
|
||||
|
||||
if self.need_to_draw_shadow() {
|
||||
let rect = Rect::new(Point2D::new(dest_rect.origin.x as f32, dest_rect.origin.y as f32),
|
||||
Size2D::new(dest_rect.size.width as f32, dest_rect.size.height as f32));
|
||||
|
||||
self.draw_with_shadow(&rect, |new_draw_target: &DrawTarget| {
|
||||
write_image(&new_draw_target, image_data, source_rect.size, dest_rect,
|
||||
smoothing_enabled, self.state.draw_options.alpha);
|
||||
});
|
||||
} else {
|
||||
// Writes on target canvas
|
||||
write_image(&self.drawtarget, image_data, image_size, dest_rect,
|
||||
smoothing_enabled, self.state.draw_options.alpha);
|
||||
}
|
||||
}
|
||||
|
||||
fn move_to(&self, point: &Point2D<AzFloat>) {
|
||||
|
@ -577,7 +557,7 @@ impl<'a> CanvasPaintTask<'a> {
|
|||
// rgba -> bgra
|
||||
byte_swap(&mut imagedata);
|
||||
|
||||
let image_rect = Rect::new(Point2D::new(0f64, 0f64),
|
||||
let image_rect = Rect::new(Point2D::zero(),
|
||||
Size2D::new(image_data_rect.size.width, image_data_rect.size.height));
|
||||
|
||||
// Dirty rectangle defines the area of the source image to be copied
|
||||
|
@ -607,7 +587,8 @@ impl<'a> CanvasPaintTask<'a> {
|
|||
image_data_rect.origin.y + source_rect.origin.y),
|
||||
Size2D::new(source_rect.size.width, source_rect.size.height));
|
||||
|
||||
self.write_pixels(&imagedata, image_data_rect.size, source_rect, dest_rect, true)
|
||||
write_pixels(&self.drawtarget, &imagedata, image_data_rect.size, source_rect,
|
||||
dest_rect, true, self.state.draw_options.alpha)
|
||||
}
|
||||
|
||||
fn set_shadow_offset_x(&mut self, value: f64) {
|
||||
|
@ -625,6 +606,41 @@ impl<'a> CanvasPaintTask<'a> {
|
|||
fn set_shadow_color(&mut self, value: AzColor) {
|
||||
self.state.shadow_color = value;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#when-shadows-are-drawn
|
||||
fn need_to_draw_shadow(&self) -> bool {
|
||||
self.state.shadow_color.a != 0.0f32 &&
|
||||
(self.state.shadow_offset_x != 0.0f64 ||
|
||||
self.state.shadow_offset_y != 0.0f64 ||
|
||||
self.state.shadow_blur != 0.0f64)
|
||||
}
|
||||
|
||||
fn create_draw_target_for_shadow(&self, source_rect: &Rect<f32>) -> DrawTarget {
|
||||
let draw_target = self.drawtarget.create_similar_draw_target(&Size2D::new(source_rect.size.width as i32,
|
||||
source_rect.size.height as i32),
|
||||
self.drawtarget.get_format());
|
||||
let matrix = Matrix2D::identity().translate(-source_rect.origin.x as AzFloat,
|
||||
-source_rect.origin.y as AzFloat)
|
||||
.mul(&self.state.transform);
|
||||
draw_target.set_transform(&matrix);
|
||||
draw_target
|
||||
}
|
||||
|
||||
fn draw_with_shadow<F>(&self, rect: &Rect<f32>, draw_shadow_source: F)
|
||||
where F: FnOnce(&DrawTarget)
|
||||
{
|
||||
let shadow_src_rect = self.state.transform.transform_rect(rect);
|
||||
let new_draw_target = self.create_draw_target_for_shadow(&shadow_src_rect);
|
||||
draw_shadow_source(&new_draw_target);
|
||||
self.drawtarget.draw_surface_with_shadow(new_draw_target.snapshot(),
|
||||
&Point2D::new(shadow_src_rect.origin.x as AzFloat,
|
||||
shadow_src_rect.origin.y as AzFloat),
|
||||
&self.state.shadow_color,
|
||||
&Point2D::new(self.state.shadow_offset_x as AzFloat,
|
||||
self.state.shadow_offset_y as AzFloat),
|
||||
(self.state.shadow_blur / 2.0f64) as AzFloat,
|
||||
self.state.draw_options.composition);
|
||||
}
|
||||
}
|
||||
|
||||
/// Used by drawImage to get rid of the extra pixels of the image data that
|
||||
|
@ -658,6 +674,65 @@ fn crop_image(image_data: Vec<u8>,
|
|||
new_image_data
|
||||
}
|
||||
|
||||
/// It writes an image to the destination target
|
||||
/// draw_target: the destination target where the image_data will be copied
|
||||
/// image_data: Pixel information of the image to be written. It takes RGBA8
|
||||
/// image_size: The size of the image to be written
|
||||
/// dest_rect: Area of the destination target where the pixels will be copied
|
||||
/// smoothing_enabled: It determines if smoothing is applied to the image result
|
||||
fn write_image(draw_target: &DrawTarget,
|
||||
mut image_data: Vec<u8>,
|
||||
image_size: Size2D<f64>,
|
||||
dest_rect: Rect<f64>,
|
||||
smoothing_enabled: bool,
|
||||
global_alpha: f32) {
|
||||
if image_data.len() == 0 {
|
||||
return
|
||||
}
|
||||
let image_rect = Rect::new(Point2D::zero(), image_size);
|
||||
// rgba -> bgra
|
||||
byte_swap(&mut image_data);
|
||||
write_pixels(&draw_target, &image_data, image_size, image_rect, dest_rect, smoothing_enabled, global_alpha);
|
||||
}
|
||||
|
||||
/// It writes image data to the target
|
||||
/// draw_target: the destination target where the imagedata will be copied
|
||||
/// source_rect: the area of the image data to be written
|
||||
/// dest_rect: The area of the target where the imagedata will be copied
|
||||
/// smoothing_enabled: if smoothing is applied to the copied pixels
|
||||
fn write_pixels(draw_target: &DrawTarget,
|
||||
image_data: &[u8],
|
||||
image_size: Size2D<f64>,
|
||||
source_rect: Rect<f64>,
|
||||
dest_rect: Rect<f64>,
|
||||
smoothing_enabled: bool,
|
||||
global_alpha: f32) {
|
||||
// From spec https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
|
||||
// When scaling up, if the imageSmoothingEnabled attribute is set to true, the user agent should attempt
|
||||
// to apply a smoothing algorithm to the image data when it is scaled.
|
||||
// Otherwise, the image must be rendered using nearest-neighbor interpolation.
|
||||
let filter = if smoothing_enabled {
|
||||
Filter::Linear
|
||||
} else {
|
||||
Filter::Point
|
||||
};
|
||||
// azure_hl operates with integers. We need to cast the image size
|
||||
let image_size = image_size.to_i32();
|
||||
|
||||
let source_surface = draw_target.create_source_surface_from_data(
|
||||
&image_data,
|
||||
image_size, image_size.width * 4, SurfaceFormat::B8G8R8A8);
|
||||
|
||||
let draw_surface_options = DrawSurfaceOptions::new(filter, true);
|
||||
let draw_options = DrawOptions::new(global_alpha, CompositionOp::Over, AntialiasMode::None);
|
||||
|
||||
draw_target.draw_surface(source_surface,
|
||||
dest_rect.to_azfloat(),
|
||||
source_rect.to_azfloat(),
|
||||
draw_surface_options,
|
||||
draw_options);
|
||||
}
|
||||
|
||||
pub trait SizeToi32 {
|
||||
fn to_i32(&self) -> Size2D<i32>;
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
[2d.fillRect.shadow.html]
|
||||
type: testharness
|
||||
[fillRect draws shadows]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.alpha.2.html]
|
||||
type: testharness
|
||||
[Shadow colour alpha components are used]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.alpha.3.html]
|
||||
type: testharness
|
||||
[Shadows are affected by globalAlpha]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.alpha.4.html]
|
||||
type: testharness
|
||||
[Shadows with alpha components are correctly affected by globalAlpha]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.alpha.5.html]
|
||||
type: testharness
|
||||
[Shadows of shapes with alpha components are drawn correctly]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.canvas.alpha.html]
|
||||
type: testharness
|
||||
[Shadows are drawn correctly for partially-transparent canvases]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.canvas.basic.html]
|
||||
type: testharness
|
||||
[Shadows are drawn for canvases]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.canvas.transparent.2.html]
|
||||
type: testharness
|
||||
[Shadows are not drawn for transparent parts of canvases]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.clip.1.html]
|
||||
type: testharness
|
||||
[Shadows of clipped shapes are still drawn within the clipping region]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.clip.3.html]
|
||||
type: testharness
|
||||
[Shadows of clipped shapes are still drawn within the clipping region]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.composite.1.html]
|
||||
type: testharness
|
||||
[Shadows are drawn using globalCompositeOperation]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.composite.2.html]
|
||||
type: testharness
|
||||
[Shadows are drawn using globalCompositeOperation]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.gradient.alpha.html]
|
||||
type: testharness
|
||||
[Shadows are drawn correctly for partially-transparent gradient fills]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.gradient.basic.html]
|
||||
type: testharness
|
||||
[Shadows are drawn for gradient fills]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.gradient.transparent.2.html]
|
||||
type: testharness
|
||||
[Shadows are not drawn for transparent parts of gradient fills]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.image.alpha.html]
|
||||
type: testharness
|
||||
[Shadows are drawn correctly for partially-transparent images]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.image.basic.html]
|
||||
type: testharness
|
||||
[Shadows are drawn for images]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.image.scale.html]
|
||||
type: testharness
|
||||
[Shadows are drawn correctly for scaled images]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.image.transparent.2.html]
|
||||
type: testharness
|
||||
[Shadows are not drawn for transparent parts of images]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.offset.negativeX.html]
|
||||
type: testharness
|
||||
[Shadows can be offset with negative x]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.offset.negativeY.html]
|
||||
type: testharness
|
||||
[Shadows can be offset with negative y]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.offset.positiveX.html]
|
||||
type: testharness
|
||||
[Shadows can be offset with positive x]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.offset.positiveY.html]
|
||||
type: testharness
|
||||
[Shadows can be offset with positive y]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.outside.html]
|
||||
type: testharness
|
||||
[Shadows of shapes outside the visible area can be offset onto the visible area]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.pattern.alpha.html]
|
||||
type: testharness
|
||||
[Shadows are drawn correctly for partially-transparent fill patterns]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.pattern.basic.html]
|
||||
type: testharness
|
||||
[Shadows are drawn for fill patterns]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.pattern.transparent.2.html]
|
||||
type: testharness
|
||||
[Shadows are not drawn for transparent parts of fill patterns]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.transform.1.html]
|
||||
type: testharness
|
||||
[Shadows take account of transformations]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[2d.shadow.transform.2.html]
|
||||
type: testharness
|
||||
[Shadow offsets are not affected by transformations]
|
||||
expected: FAIL
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue