Skip to content

Commit

Permalink
Implement drawing shadows in canvas.
Browse files Browse the repository at this point in the history
  • Loading branch information
hyowon committed Jun 15, 2015
1 parent 24af4c4 commit 465cea8
Show file tree
Hide file tree
Showing 30 changed files with 134 additions and 204 deletions.
193 changes: 134 additions & 59 deletions components/canvas/canvas_paint_task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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>) {
Expand Down Expand Up @@ -376,17 +332,41 @@ 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>,
dest_rect: Rect<f64>, source_rect: Rect<f64>,
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>) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand Down Expand Up @@ -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>;
}
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading

0 comments on commit 465cea8

Please sign in to comment.