Implement drawing shadows in canvas.

This commit is contained in:
Hyowon Kim 2015-06-09 15:55:16 +09:00
parent 24af4c4ec6
commit 465cea8db5
30 changed files with 134 additions and 204 deletions

View file

@ -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>;
}

View file

@ -1,5 +0,0 @@
[2d.fillRect.shadow.html]
type: testharness
[fillRect draws shadows]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.alpha.2.html]
type: testharness
[Shadow colour alpha components are used]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.alpha.3.html]
type: testharness
[Shadows are affected by globalAlpha]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.alpha.4.html]
type: testharness
[Shadows with alpha components are correctly affected by globalAlpha]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.alpha.5.html]
type: testharness
[Shadows of shapes with alpha components are drawn correctly]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.canvas.alpha.html]
type: testharness
[Shadows are drawn correctly for partially-transparent canvases]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.canvas.basic.html]
type: testharness
[Shadows are drawn for canvases]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.canvas.transparent.2.html]
type: testharness
[Shadows are not drawn for transparent parts of canvases]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.clip.1.html]
type: testharness
[Shadows of clipped shapes are still drawn within the clipping region]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.clip.3.html]
type: testharness
[Shadows of clipped shapes are still drawn within the clipping region]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.composite.1.html]
type: testharness
[Shadows are drawn using globalCompositeOperation]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.composite.2.html]
type: testharness
[Shadows are drawn using globalCompositeOperation]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.gradient.alpha.html]
type: testharness
[Shadows are drawn correctly for partially-transparent gradient fills]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.gradient.basic.html]
type: testharness
[Shadows are drawn for gradient fills]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.gradient.transparent.2.html]
type: testharness
[Shadows are not drawn for transparent parts of gradient fills]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.image.alpha.html]
type: testharness
[Shadows are drawn correctly for partially-transparent images]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.image.basic.html]
type: testharness
[Shadows are drawn for images]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.image.scale.html]
type: testharness
[Shadows are drawn correctly for scaled images]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.image.transparent.2.html]
type: testharness
[Shadows are not drawn for transparent parts of images]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.offset.negativeX.html]
type: testharness
[Shadows can be offset with negative x]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.offset.negativeY.html]
type: testharness
[Shadows can be offset with negative y]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.offset.positiveX.html]
type: testharness
[Shadows can be offset with positive x]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.offset.positiveY.html]
type: testharness
[Shadows can be offset with positive y]
expected: FAIL

View file

@ -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

View file

@ -1,5 +0,0 @@
[2d.shadow.pattern.alpha.html]
type: testharness
[Shadows are drawn correctly for partially-transparent fill patterns]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.pattern.basic.html]
type: testharness
[Shadows are drawn for fill patterns]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.pattern.transparent.2.html]
type: testharness
[Shadows are not drawn for transparent parts of fill patterns]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.transform.1.html]
type: testharness
[Shadows take account of transformations]
expected: FAIL

View file

@ -1,5 +0,0 @@
[2d.shadow.transform.2.html]
type: testharness
[Shadow offsets are not affected by transformations]
expected: FAIL