diff options
-rw-r--r-- | components/canvas/canvas_data.rs | 1001 | ||||
-rw-r--r-- | components/canvas/canvas_paint_thread.rs | 1323 | ||||
-rw-r--r-- | components/canvas/lib.rs | 1 | ||||
-rw-r--r-- | components/canvas_traits/canvas.rs | 5 | ||||
-rw-r--r-- | components/constellation/constellation.rs | 28 | ||||
-rw-r--r-- | components/script/dom/canvasrenderingcontext2d.rs | 21 |
6 files changed, 1269 insertions, 1110 deletions
diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs new file mode 100644 index 00000000000..20606774562 --- /dev/null +++ b/components/canvas/canvas_data.rs @@ -0,0 +1,1001 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use azure::azure::AzFloat; +use azure::azure_hl::{AntialiasMode, CapStyle, CompositionOp, JoinStyle}; +use azure::azure_hl::{BackendType, DrawOptions, DrawTarget, Pattern, StrokeOptions, SurfaceFormat}; +use azure::azure_hl::{Color, ColorPattern, DrawSurfaceOptions, Filter, PathBuilder}; +use azure::azure_hl::{ExtendMode, GradientStop, LinearGradientPattern, RadialGradientPattern}; +use azure::azure_hl::SurfacePattern; +use canvas_traits::canvas::*; +use cssparser::RGBA; +use euclid::{Transform2D, Point2D, Vector2D, Rect, Size2D}; +use ipc_channel::ipc::IpcSender; +use num_traits::ToPrimitive; +use serde_bytes::ByteBuf; +use std::mem; +use std::sync::Arc; +use webrender_api; + +pub struct CanvasData<'a> { + drawtarget: DrawTarget, + /// TODO(pcwalton): Support multiple paths. + path_builder: PathBuilder, + state: CanvasPaintState<'a>, + saved_states: Vec<CanvasPaintState<'a>>, + webrender_api: webrender_api::RenderApi, + image_key: Option<webrender_api::ImageKey>, + /// An old webrender image key that can be deleted when the next epoch ends. + old_image_key: Option<webrender_api::ImageKey>, + /// An old webrender image key that can be deleted when the current epoch ends. + very_old_image_key: Option<webrender_api::ImageKey>, + pub canvas_id: CanvasId, +} + +impl<'a> CanvasData<'a> { + pub fn new( + size: Size2D<i32>, + webrender_api_sender: webrender_api::RenderApiSender, + antialias: AntialiasMode, + canvas_id: CanvasId + ) -> CanvasData<'a> { + let draw_target = CanvasData::create(size); + let path_builder = draw_target.create_path_builder(); + let webrender_api = webrender_api_sender.create_api(); + CanvasData { + drawtarget: draw_target, + path_builder: path_builder, + state: CanvasPaintState::new(antialias), + saved_states: vec![], + webrender_api: webrender_api, + image_key: None, + old_image_key: None, + very_old_image_key: None, + canvas_id: canvas_id, + } + } + + pub fn draw_image( + &self, + image_data: Vec<u8>, + image_size: Size2D<f64>, + dest_rect: Rect<f64>, + source_rect: Rect<f64>, + smoothing_enabled: bool + ) { + // We round up the floating pixel values to draw the pixels + 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); + + 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.composition, + self.state.draw_options.alpha); + }); + } else { + write_image(&self.drawtarget, image_data, source_rect.size, dest_rect, + smoothing_enabled, self.state.draw_options.composition, + self.state.draw_options.alpha); + } + } + + pub 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 image_data = self.read_pixels(source_rect.to_i32(), 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.composition, + 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.composition, + self.state.draw_options.alpha); + } + } + + pub fn save_context_state(&mut self) { + self.saved_states.push(self.state.clone()); + } + + pub fn restore_context_state(&mut self) { + if let Some(state) = self.saved_states.pop() { + mem::replace(&mut self.state, state); + self.drawtarget.set_transform(&self.state.transform); + self.drawtarget.pop_clip(); + } + } + + pub fn fill_text(&self, text: String, x: f64, y: f64, max_width: Option<f64>) { + error!("Unimplemented canvas2d.fillText. Values received: {}, {}, {}, {:?}.", text, x, y, max_width); + } + + pub fn fill_rect(&self, rect: &Rect<f32>) { + if is_zero_size_gradient(&self.state.fill_style) { + return; // Paint nothing if gradient size is zero. + } + + let draw_rect = Rect::new(rect.origin, + match self.state.fill_style { + Pattern::Surface(ref surface) => { + let surface_size = surface.size(); + match (surface.repeat_x, surface.repeat_y) { + (true, true) => rect.size, + (true, false) => Size2D::new(rect.size.width, surface_size.height as f32), + (false, true) => Size2D::new(surface_size.width as f32, rect.size.height), + (false, false) => Size2D::new(surface_size.width as f32, surface_size.height as f32), + } + }, + _ => rect.size, + } + ); + + 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)); + } + } + + pub fn clear_rect(&self, rect: &Rect<f32>) { + self.drawtarget.clear_rect(rect); + } + + pub fn stroke_rect(&self, rect: &Rect<f32>) { + if is_zero_size_gradient(&self.state.stroke_style) { + return; // Paint nothing if gradient size is zero. + } + + if self.need_to_draw_shadow() { + self.draw_with_shadow(&rect, |new_draw_target: &DrawTarget| { + new_draw_target.stroke_rect(rect, self.state.stroke_style.to_pattern_ref(), + &self.state.stroke_opts, &self.state.draw_options); + }); + } else if rect.size.width == 0. || rect.size.height == 0. { + let cap = match self.state.stroke_opts.line_join { + JoinStyle::Round => CapStyle::Round, + _ => CapStyle::Butt + }; + + let stroke_opts = + StrokeOptions::new(self.state.stroke_opts.line_width, + self.state.stroke_opts.line_join, + cap, + self.state.stroke_opts.miter_limit, + self.state.stroke_opts.mDashPattern); + self.drawtarget.stroke_line(rect.origin, rect.bottom_right(), + self.state.stroke_style.to_pattern_ref(), + &stroke_opts, &self.state.draw_options); + } else { + self.drawtarget.stroke_rect(rect, self.state.stroke_style.to_pattern_ref(), + &self.state.stroke_opts, &self.state.draw_options); + } + } + + pub fn begin_path(&mut self) { + self.path_builder = self.drawtarget.create_path_builder() + } + + pub fn close_path(&self) { + self.path_builder.close() + } + + pub fn fill(&self) { + if is_zero_size_gradient(&self.state.fill_style) { + return; // Paint nothing if gradient size is zero. + } + + self.drawtarget.fill(&self.path_builder.finish(), + self.state.fill_style.to_pattern_ref(), + &self.state.draw_options); + } + + pub fn stroke(&self) { + if is_zero_size_gradient(&self.state.stroke_style) { + return; // Paint nothing if gradient size is zero. + } + + self.drawtarget.stroke(&self.path_builder.finish(), + self.state.stroke_style.to_pattern_ref(), + &self.state.stroke_opts, + &self.state.draw_options); + } + + pub fn clip(&self) { + self.drawtarget.push_clip(&self.path_builder.finish()); + } + + pub fn is_point_in_path( + &mut self, + x: f64, + y: f64, + _fill_rule: FillRule, + chan: IpcSender<bool> + ) { + let path = self.path_builder.finish(); + let result = path.contains_point(x, y, &self.state.transform); + self.path_builder = path.copy_to_builder(); + chan.send(result).unwrap(); + } + + pub fn move_to(&self, point: &Point2D<AzFloat>) { + self.path_builder.move_to(*point) + } + + pub fn line_to(&self, point: &Point2D<AzFloat>) { + self.path_builder.line_to(*point) + } + + pub fn rect(&self, rect: &Rect<f32>) { + self.path_builder.move_to(Point2D::new(rect.origin.x, rect.origin.y)); + self.path_builder.line_to(Point2D::new(rect.origin.x + rect.size.width, rect.origin.y)); + self.path_builder.line_to(Point2D::new(rect.origin.x + rect.size.width, + rect.origin.y + rect.size.height)); + self.path_builder.line_to(Point2D::new(rect.origin.x, rect.origin.y + rect.size.height)); + self.path_builder.close(); + } + + pub fn quadratic_curve_to( + &self, + cp: &Point2D<AzFloat>, + endpoint: &Point2D<AzFloat> + ) { + self.path_builder.quadratic_curve_to(cp, endpoint) + } + + pub fn bezier_curve_to( + &self, + cp1: &Point2D<AzFloat>, + cp2: &Point2D<AzFloat>, + endpoint: &Point2D<AzFloat> + ) { + self.path_builder.bezier_curve_to(cp1, cp2, endpoint) + } + + pub fn arc( + &self, + center: &Point2D<AzFloat>, + radius: AzFloat, + start_angle: AzFloat, + end_angle: AzFloat, + ccw: bool + ) { + self.path_builder.arc(*center, radius, start_angle, end_angle, ccw) + } + + pub fn arc_to( + &self, + cp1: &Point2D<AzFloat>, + cp2: &Point2D<AzFloat>, + radius: AzFloat + ) { + let cp0 = self.path_builder.get_current_point(); + let cp1 = *cp1; + let cp2 = *cp2; + + if (cp0.x == cp1.x && cp0.y == cp1.y) || cp1 == cp2 || radius == 0.0 { + self.line_to(&cp1); + return; + } + + // if all three control points lie on a single straight line, + // connect the first two by a straight line + let direction = (cp2.x - cp1.x) * (cp0.y - cp1.y) + (cp2.y - cp1.y) * (cp1.x - cp0.x); + if direction == 0.0 { + self.line_to(&cp1); + return; + } + + // otherwise, draw the Arc + let a2 = (cp0.x - cp1.x).powi(2) + (cp0.y - cp1.y).powi(2); + let b2 = (cp1.x - cp2.x).powi(2) + (cp1.y - cp2.y).powi(2); + let d = { + let c2 = (cp0.x - cp2.x).powi(2) + (cp0.y - cp2.y).powi(2); + let cosx = (a2 + b2 - c2) / (2.0 * (a2 * b2).sqrt()); + let sinx = (1.0 - cosx.powi(2)).sqrt(); + radius / ((1.0 - cosx) / sinx) + }; + + // first tangent point + let anx = (cp1.x - cp0.x) / a2.sqrt(); + let any = (cp1.y - cp0.y) / a2.sqrt(); + let tp1 = Point2D::new(cp1.x - anx * d, cp1.y - any * d); + + // second tangent point + let bnx = (cp1.x - cp2.x) / b2.sqrt(); + let bny = (cp1.y - cp2.y) / b2.sqrt(); + let tp2 = Point2D::new(cp1.x - bnx * d, cp1.y - bny * d); + + // arc center and angles + let anticlockwise = direction < 0.0; + let cx = tp1.x + any * radius * if anticlockwise { 1.0 } else { -1.0 }; + let cy = tp1.y - anx * radius * if anticlockwise { 1.0 } else { -1.0 }; + let angle_start = (tp1.y - cy).atan2(tp1.x - cx); + let angle_end = (tp2.y - cy).atan2(tp2.x - cx); + + self.line_to(&tp1); + if [cx, cy, angle_start, angle_end].iter().all(|x| x.is_finite()) { + self.arc(&Point2D::new(cx, cy), radius, + angle_start, angle_end, anticlockwise); + } + } + + pub fn ellipse( + &mut self, + center: &Point2D<AzFloat>, + radius_x: AzFloat, + radius_y: AzFloat, + rotation_angle: AzFloat, + start_angle: AzFloat, + end_angle: AzFloat, + ccw: bool + ) { + self.path_builder.ellipse(*center, radius_x, radius_y, rotation_angle, start_angle, end_angle, ccw); + } + + pub fn set_fill_style(&mut self, style: FillOrStrokeStyle) { + if let Some(pattern) = style.to_azure_pattern(&self.drawtarget) { + self.state.fill_style = pattern + } + } + + pub fn set_stroke_style(&mut self, style: FillOrStrokeStyle) { + if let Some(pattern) = style.to_azure_pattern(&self.drawtarget) { + self.state.stroke_style = pattern + } + } + + pub fn set_line_width(&mut self, width: f32) { + self.state.stroke_opts.line_width = width; + } + + pub fn set_line_cap(&mut self, cap: LineCapStyle) { + self.state.stroke_opts.line_cap = cap.to_azure_style(); + } + + pub fn set_line_join(&mut self, join: LineJoinStyle) { + self.state.stroke_opts.line_join = join.to_azure_style(); + } + + pub fn set_miter_limit(&mut self, limit: f32) { + self.state.stroke_opts.miter_limit = limit; + } + + pub fn set_transform(&mut self, transform: &Transform2D<f32>) { + self.state.transform = transform.clone(); + self.drawtarget.set_transform(transform) + } + + pub fn set_global_alpha(&mut self, alpha: f32) { + self.state.draw_options.alpha = alpha; + } + + pub fn set_global_composition(&mut self, op: CompositionOrBlending) { + self.state.draw_options.set_composition_op(op.to_azure_style()); + } + + pub fn create(size: Size2D<i32>) -> DrawTarget { + DrawTarget::new(BackendType::Skia, size, SurfaceFormat::B8G8R8A8) + } + + pub fn recreate(&mut self, size: Size2D<i32>) { + self.drawtarget = CanvasData::create(size); + self.state = CanvasPaintState::new(self.state.draw_options.antialias); + self.saved_states.clear(); + // Webrender doesn't let images change size, so we clear the webrender image key. + // TODO: there is an annying race condition here: the display list builder + // might still be using the old image key. Really, we should be scheduling the image + // for later deletion, not deleting it immediately. + // https://github.com/servo/servo/issues/17534 + if let Some(image_key) = self.image_key.take() { + // If this executes, then we are in a new epoch since we last recreated the canvas, + // so `old_image_key` must be `None`. + debug_assert!(self.old_image_key.is_none()); + self.old_image_key = Some(image_key); + } + } + + pub fn send_pixels(&mut self, chan: IpcSender<Option<ByteBuf>>) { + self.drawtarget.snapshot().get_data_surface().with_data(|element| { + chan.send(Some(Vec::from(element).into())).unwrap(); + }) + } + + pub fn send_data(&mut self, chan: IpcSender<CanvasImageData>) { + self.drawtarget.snapshot().get_data_surface().with_data(|element| { + let size = self.drawtarget.get_size(); + + let descriptor = webrender_api::ImageDescriptor { + width: size.width as u32, + height: size.height as u32, + stride: None, + format: webrender_api::ImageFormat::BGRA8, + offset: 0, + is_opaque: false, + allow_mipmaps: false, + }; + let data = webrender_api::ImageData::Raw(Arc::new(element.into())); + + let mut updates = webrender_api::ResourceUpdates::new(); + + match self.image_key { + Some(image_key) => { + debug!("Updating image {:?}.", image_key); + updates.update_image(image_key, + descriptor, + data, + None); + } + None => { + self.image_key = Some(self.webrender_api.generate_image_key()); + debug!("New image {:?}.", self.image_key); + updates.add_image(self.image_key.unwrap(), + descriptor, + data, + None); + } + } + + if let Some(image_key) = mem::replace(&mut self.very_old_image_key, self.old_image_key.take()) { + updates.delete_image(image_key); + } + + self.webrender_api.update_resources(updates); + + let data = CanvasImageData { + image_key: self.image_key.unwrap(), + }; + chan.send(data).unwrap(); + }) + } + + pub fn image_data( + &self, + dest_rect: Rect<i32>, + canvas_size: Size2D<f64>, + chan: IpcSender<ByteBuf>, + ) { + let mut dest_data = self.read_pixels(dest_rect, canvas_size); + + // bgra -> rgba + byte_swap(&mut dest_data); + chan.send(dest_data.into()).unwrap(); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata + pub fn put_image_data( + &mut self, + imagedata: Vec<u8>, + offset: Vector2D<f64>, + image_data_size: Size2D<f64>, + mut dirty_rect: Rect<f64> + ) { + if image_data_size.width <= 0.0 || image_data_size.height <= 0.0 { + return + } + + assert_eq!(image_data_size.width * image_data_size.height * 4.0, imagedata.len() as f64); + + // Step 1. TODO (neutered data) + + // Step 2. + if dirty_rect.size.width < 0.0f64 { + dirty_rect.origin.x += dirty_rect.size.width; + dirty_rect.size.width = -dirty_rect.size.width; + } + + if dirty_rect.size.height < 0.0f64 { + dirty_rect.origin.y += dirty_rect.size.height; + dirty_rect.size.height = -dirty_rect.size.height; + } + + // Step 3. + if dirty_rect.origin.x < 0.0f64 { + dirty_rect.size.width += dirty_rect.origin.x; + dirty_rect.origin.x = 0.0f64; + } + + if dirty_rect.origin.y < 0.0f64 { + dirty_rect.size.height += dirty_rect.origin.y; + dirty_rect.origin.y = 0.0f64; + } + + // Step 4. + if dirty_rect.max_x() > image_data_size.width { + dirty_rect.size.width = image_data_size.width - dirty_rect.origin.x; + } + + if dirty_rect.max_y() > image_data_size.height { + dirty_rect.size.height = image_data_size.height - dirty_rect.origin.y; + } + + // 5) If either dirtyWidth or dirtyHeight is negative or zero, + // stop without affecting any bitmaps + if dirty_rect.size.width <= 0.0 || dirty_rect.size.height <= 0.0 { + return + } + + // Step 6. + let dest_rect = dirty_rect.translate(&offset).to_i32(); + + // azure_hl operates with integers. We need to cast the image size + let image_size = image_data_size.to_i32(); + + let first_pixel = dest_rect.origin - offset.to_i32(); + let mut src_line = (first_pixel.y * (image_size.width * 4) + first_pixel.x * 4) as usize; + + let mut dest = + Vec::with_capacity((dest_rect.size.width * dest_rect.size.height * 4) as usize); + + for _ in 0 .. dest_rect.size.height { + let mut src_offset = src_line; + for _ in 0 .. dest_rect.size.width { + let alpha = imagedata[src_offset + 3] as u16; + // add 127 before dividing for more accurate rounding + let premultiply_channel = |channel: u8| (((channel as u16 * alpha) + 127) / 255) as u8; + dest.push(premultiply_channel(imagedata[src_offset + 2])); + dest.push(premultiply_channel(imagedata[src_offset + 1])); + dest.push(premultiply_channel(imagedata[src_offset + 0])); + dest.push(imagedata[src_offset + 3]); + src_offset += 4; + } + src_line += (image_size.width * 4) as usize; + } + + if let Some(source_surface) = self.drawtarget.create_source_surface_from_data( + &dest, + dest_rect.size, + dest_rect.size.width * 4, + SurfaceFormat::B8G8R8A8) { + self.drawtarget.copy_surface(source_surface, + Rect::new(Point2D::new(0, 0), dest_rect.size), + dest_rect.origin); + } + } + + pub fn set_shadow_offset_x(&mut self, value: f64) { + self.state.shadow_offset_x = value; + } + + pub fn set_shadow_offset_y(&mut self, value: f64) { + self.state.shadow_offset_y = value; + } + + pub fn set_shadow_blur(&mut self, value: f64) { + self.state.shadow_blur = value; + } + + pub fn set_shadow_color(&mut self, value: Color) { + 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 = Transform2D::identity() + .pre_translate(-source_rect.origin.to_vector().cast().unwrap()) + .pre_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, + &Vector2D::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); + } + + /// It reads image data from the canvas + /// canvas_size: The size of the canvas we're reading from + /// read_rect: The area of the canvas we want to read from + pub fn read_pixels(&self, read_rect: Rect<i32>, canvas_size: Size2D<f64>) -> Vec<u8> { + let canvas_size = canvas_size.to_i32(); + let canvas_rect = Rect::new(Point2D::new(0i32, 0i32), canvas_size); + let src_read_rect = canvas_rect.intersection(&read_rect).unwrap_or(Rect::zero()); + + let mut image_data = vec![]; + if src_read_rect.is_empty() || canvas_size.width <= 0 && canvas_size.height <= 0 { + return image_data; + } + + let data_surface = self.drawtarget.snapshot().get_data_surface(); + let mut src_data = Vec::new(); + data_surface.with_data(|element| { src_data = element.to_vec(); }); + let stride = data_surface.stride(); + + //start offset of the copyable rectangle + let mut src = (src_read_rect.origin.y * stride + src_read_rect.origin.x * 4) as usize; + //copy the data to the destination vector + for _ in 0..src_read_rect.size.height { + let row = &src_data[src .. src + (4 * src_read_rect.size.width) as usize]; + image_data.extend_from_slice(row); + src += stride as usize; + } + + image_data + } +} + +impl<'a> Drop for CanvasData<'a> { + fn drop(&mut self) { + let mut updates = webrender_api::ResourceUpdates::new(); + + if let Some(image_key) = self.old_image_key.take() { + updates.delete_image(image_key); + } + if let Some(image_key) = self.very_old_image_key.take() { + updates.delete_image(image_key); + } + + self.webrender_api.update_resources(updates); + } +} + +#[derive(Clone)] +struct CanvasPaintState<'a> { + draw_options: DrawOptions, + fill_style: Pattern, + stroke_style: Pattern, + stroke_opts: StrokeOptions<'a>, + /// The current 2D transform matrix. + transform: Transform2D<f32>, + shadow_offset_x: f64, + shadow_offset_y: f64, + shadow_blur: f64, + shadow_color: Color, +} + +impl<'a> CanvasPaintState<'a> { + fn new(antialias: AntialiasMode) -> CanvasPaintState<'a> { + CanvasPaintState { + draw_options: DrawOptions::new(1.0, CompositionOp::Over, antialias), + fill_style: Pattern::Color(ColorPattern::new(Color::black())), + stroke_style: Pattern::Color(ColorPattern::new(Color::black())), + stroke_opts: StrokeOptions::new(1.0, JoinStyle::MiterOrBevel, CapStyle::Butt, 10.0, &[]), + transform: Transform2D::identity(), + shadow_offset_x: 0.0, + shadow_offset_y: 0.0, + shadow_blur: 0.0, + shadow_color: Color::transparent(), + } + } +} + +fn is_zero_size_gradient(pattern: &Pattern) -> bool { + if let &Pattern::LinearGradient(ref gradient) = pattern { + if gradient.is_zero_size() { + return true; + } + } + false +} + +/// Used by drawImage to get rid of the extra pixels of the image data that +/// won't be copied to the canvas +/// image_data: Color pixel data of the image +/// image_size: Image dimensions +/// crop_rect: It determines the area of the image we want to keep +fn crop_image( + image_data: Vec<u8>, + image_size: Size2D<f64>, + crop_rect: Rect<f64> +) -> Vec<u8> { + // We're going to iterate over a pixel values array so we need integers + let crop_rect = crop_rect.to_i32(); + let image_size = image_size.to_i32(); + // Assuming 4 bytes per pixel and row-major order for storage + // (consecutive elements in a pixel row of the image are contiguous in memory) + let stride = image_size.width * 4; + let image_bytes_length = image_size.height * image_size.width * 4; + let crop_area_bytes_length = crop_rect.size.height * crop_rect.size.width * 4; + // If the image size is less or equal than the crop area we do nothing + if image_bytes_length <= crop_area_bytes_length { + return image_data; + } + + let mut new_image_data = Vec::new(); + let mut src = (crop_rect.origin.y * stride + crop_rect.origin.x * 4) as usize; + for _ in 0..crop_rect.size.height { + let row = &image_data[src .. src + (4 * crop_rect.size.width) as usize]; + new_image_data.extend_from_slice(row); + src += stride as usize; + } + 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, + composition_op: CompositionOp, + global_alpha: f32 +) { + if image_data.is_empty() { + return + } + let image_rect = Rect::new(Point2D::zero(), image_size); + // rgba -> bgra + byte_swap(&mut image_data); + + // 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(); + + if let Some(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, composition_op, AntialiasMode::None); + + draw_target.draw_surface(source_surface, + dest_rect.to_azure_style(), + image_rect.to_azure_style(), + draw_surface_options, + draw_options); + } +} + +pub trait PointToi32 { + fn to_i32(&self) -> Point2D<i32>; +} + +impl PointToi32 for Point2D<f64> { + fn to_i32(&self) -> Point2D<i32> { + Point2D::new(self.x.to_i32().unwrap(), + self.y.to_i32().unwrap()) + } +} + +pub trait SizeToi32 { + fn to_i32(&self) -> Size2D<i32>; +} + +impl SizeToi32 for Size2D<f64> { + fn to_i32(&self) -> Size2D<i32> { + Size2D::new(self.width.to_i32().unwrap(), + self.height.to_i32().unwrap()) + } +} + +pub trait RectToi32 { + fn to_i32(&self) -> Rect<i32>; + fn ceil(&self) -> Rect<f64>; +} + +impl RectToi32 for Rect<f64> { + fn to_i32(&self) -> Rect<i32> { + Rect::new(Point2D::new(self.origin.x.to_i32().unwrap(), + self.origin.y.to_i32().unwrap()), + Size2D::new(self.size.width.to_i32().unwrap(), + self.size.height.to_i32().unwrap())) + } + + fn ceil(&self) -> Rect<f64> { + Rect::new(Point2D::new(self.origin.x.ceil(), + self.origin.y.ceil()), + Size2D::new(self.size.width.ceil(), + self.size.height.ceil())) + } + +} + +pub trait ToAzureStyle { + type Target; + fn to_azure_style(self) -> Self::Target; +} + +impl ToAzureStyle for Rect<f64> { + type Target = Rect<AzFloat>; + + fn to_azure_style(self) -> Rect<AzFloat> { + Rect::new(Point2D::new(self.origin.x as AzFloat, self.origin.y as AzFloat), + Size2D::new(self.size.width as AzFloat, self.size.height as AzFloat)) + } +} + + +impl ToAzureStyle for LineCapStyle { + type Target = CapStyle; + + fn to_azure_style(self) -> CapStyle { + match self { + LineCapStyle::Butt => CapStyle::Butt, + LineCapStyle::Round => CapStyle::Round, + LineCapStyle::Square => CapStyle::Square, + } + } +} + +impl ToAzureStyle for LineJoinStyle { + type Target = JoinStyle; + + fn to_azure_style(self) -> JoinStyle { + match self { + LineJoinStyle::Round => JoinStyle::Round, + LineJoinStyle::Bevel => JoinStyle::Bevel, + LineJoinStyle::Miter => JoinStyle::Miter, + } + } +} + +impl ToAzureStyle for CompositionStyle { + type Target = CompositionOp; + + fn to_azure_style(self) -> CompositionOp { + match self { + CompositionStyle::SrcIn => CompositionOp::In, + CompositionStyle::SrcOut => CompositionOp::Out, + CompositionStyle::SrcOver => CompositionOp::Over, + CompositionStyle::SrcAtop => CompositionOp::Atop, + CompositionStyle::DestIn => CompositionOp::DestIn, + CompositionStyle::DestOut => CompositionOp::DestOut, + CompositionStyle::DestOver => CompositionOp::DestOver, + CompositionStyle::DestAtop => CompositionOp::DestAtop, + CompositionStyle::Copy => CompositionOp::Source, + CompositionStyle::Lighter => CompositionOp::Add, + CompositionStyle::Xor => CompositionOp::Xor, + } + } +} + +impl ToAzureStyle for BlendingStyle { + type Target = CompositionOp; + + fn to_azure_style(self) -> CompositionOp { + match self { + BlendingStyle::Multiply => CompositionOp::Multiply, + BlendingStyle::Screen => CompositionOp::Screen, + BlendingStyle::Overlay => CompositionOp::Overlay, + BlendingStyle::Darken => CompositionOp::Darken, + BlendingStyle::Lighten => CompositionOp::Lighten, + BlendingStyle::ColorDodge => CompositionOp::ColorDodge, + BlendingStyle::ColorBurn => CompositionOp::ColorBurn, + BlendingStyle::HardLight => CompositionOp::HardLight, + BlendingStyle::SoftLight => CompositionOp::SoftLight, + BlendingStyle::Difference => CompositionOp::Difference, + BlendingStyle::Exclusion => CompositionOp::Exclusion, + BlendingStyle::Hue => CompositionOp::Hue, + BlendingStyle::Saturation => CompositionOp::Saturation, + BlendingStyle::Color => CompositionOp::Color, + BlendingStyle::Luminosity => CompositionOp::Luminosity, + } + } +} + +impl ToAzureStyle for CompositionOrBlending { + type Target = CompositionOp; + + fn to_azure_style(self) -> CompositionOp { + match self { + CompositionOrBlending::Composition(op) => op.to_azure_style(), + CompositionOrBlending::Blending(op) => op.to_azure_style(), + } + } +} + +pub trait ToAzurePattern { + fn to_azure_pattern(&self, drawtarget: &DrawTarget) -> Option<Pattern>; +} + +impl ToAzurePattern for FillOrStrokeStyle { + fn to_azure_pattern(&self, drawtarget: &DrawTarget) -> Option<Pattern> { + match *self { + FillOrStrokeStyle::Color(ref color) => { + Some(Pattern::Color(ColorPattern::new(color.to_azure_style()))) + }, + FillOrStrokeStyle::LinearGradient(ref linear_gradient_style) => { + let gradient_stops: Vec<GradientStop> = linear_gradient_style.stops.iter().map(|s| { + GradientStop { + offset: s.offset as AzFloat, + color: s.color.to_azure_style() + } + }).collect(); + + Some(Pattern::LinearGradient(LinearGradientPattern::new( + &Point2D::new(linear_gradient_style.x0 as AzFloat, linear_gradient_style.y0 as AzFloat), + &Point2D::new(linear_gradient_style.x1 as AzFloat, linear_gradient_style.y1 as AzFloat), + drawtarget.create_gradient_stops(&gradient_stops, ExtendMode::Clamp), + &Transform2D::identity()))) + }, + FillOrStrokeStyle::RadialGradient(ref radial_gradient_style) => { + let gradient_stops: Vec<GradientStop> = radial_gradient_style.stops.iter().map(|s| { + GradientStop { + offset: s.offset as AzFloat, + color: s.color.to_azure_style() + } + }).collect(); + + Some(Pattern::RadialGradient(RadialGradientPattern::new( + &Point2D::new(radial_gradient_style.x0 as AzFloat, radial_gradient_style.y0 as AzFloat), + &Point2D::new(radial_gradient_style.x1 as AzFloat, radial_gradient_style.y1 as AzFloat), + radial_gradient_style.r0 as AzFloat, radial_gradient_style.r1 as AzFloat, + drawtarget.create_gradient_stops(&gradient_stops, ExtendMode::Clamp), + &Transform2D::identity()))) + }, + FillOrStrokeStyle::Surface(ref surface_style) => { + drawtarget.create_source_surface_from_data(&surface_style.surface_data, + surface_style.surface_size, + surface_style.surface_size.width * 4, + SurfaceFormat::B8G8R8A8) + .map(|source_surface| { + Pattern::Surface(SurfacePattern::new( + source_surface.azure_source_surface, + surface_style.repeat_x, + surface_style.repeat_y, + &Transform2D::identity())) + }) + } + } + } +} + +impl ToAzureStyle for RGBA { + type Target = Color; + + fn to_azure_style(self) -> Color { + Color::rgba(self.red_f32() as AzFloat, + self.green_f32() as AzFloat, + self.blue_f32() as AzFloat, + self.alpha_f32() as AzFloat) + } +} diff --git a/components/canvas/canvas_paint_thread.rs b/components/canvas/canvas_paint_thread.rs index cf1e3a79b62..ffcabe13174 100644 --- a/components/canvas/canvas_paint_thread.rs +++ b/components/canvas/canvas_paint_thread.rs @@ -2,253 +2,75 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use azure::azure::AzFloat; -use azure::azure_hl::{AntialiasMode, CapStyle, CompositionOp, JoinStyle}; -use azure::azure_hl::{BackendType, DrawOptions, DrawTarget, Pattern, StrokeOptions, SurfaceFormat}; -use azure::azure_hl::{Color, ColorPattern, DrawSurfaceOptions, Filter, PathBuilder}; -use azure::azure_hl::{ExtendMode, GradientStop, LinearGradientPattern, RadialGradientPattern}; -use azure::azure_hl::SurfacePattern; +use azure::azure_hl::AntialiasMode; +use canvas_data::*; use canvas_traits::canvas::*; -use cssparser::RGBA; -use euclid::{Transform2D, Point2D, Vector2D, Rect, Size2D}; +use euclid::Size2D; use ipc_channel::ipc::{self, IpcSender}; -use num_traits::ToPrimitive; -use serde_bytes::ByteBuf; use std::borrow::ToOwned; -use std::mem; -use std::sync::Arc; +use std::collections::HashMap; use std::thread; use webrender_api; -impl<'a> CanvasPaintThread<'a> { - /// It reads image data from the canvas - /// canvas_size: The size of the canvas we're reading from - /// read_rect: The area of the canvas we want to read from - fn read_pixels(&self, read_rect: Rect<i32>, canvas_size: Size2D<f64>) -> Vec<u8>{ - let canvas_size = canvas_size.to_i32(); - let canvas_rect = Rect::new(Point2D::new(0i32, 0i32), canvas_size); - let src_read_rect = canvas_rect.intersection(&read_rect).unwrap_or(Rect::zero()); - - let mut image_data = vec![]; - if src_read_rect.is_empty() || canvas_size.width <= 0 && canvas_size.height <= 0 { - return image_data; - } - - let data_surface = self.drawtarget.snapshot().get_data_surface(); - let mut src_data = Vec::new(); - data_surface.with_data(|element| { src_data = element.to_vec(); }); - let stride = data_surface.stride(); - - //start offset of the copyable rectangle - let mut src = (src_read_rect.origin.y * stride + src_read_rect.origin.x * 4) as usize; - //copy the data to the destination vector - for _ in 0..src_read_rect.size.height { - let row = &src_data[src .. src + (4 * src_read_rect.size.width) as usize]; - image_data.extend_from_slice(row); - src += stride as usize; - } - - image_data - } -} - -pub struct CanvasPaintThread<'a> { - drawtarget: DrawTarget, - /// TODO(pcwalton): Support multiple paths. - path_builder: PathBuilder, - state: CanvasPaintState<'a>, - saved_states: Vec<CanvasPaintState<'a>>, - webrender_api: webrender_api::RenderApi, - image_key: Option<webrender_api::ImageKey>, - /// An old webrender image key that can be deleted when the next epoch ends. - old_image_key: Option<webrender_api::ImageKey>, - /// An old webrender image key that can be deleted when the current epoch ends. - very_old_image_key: Option<webrender_api::ImageKey>, - canvas_id: CanvasId, -} - -#[derive(Clone)] -struct CanvasPaintState<'a> { - draw_options: DrawOptions, - fill_style: Pattern, - stroke_style: Pattern, - stroke_opts: StrokeOptions<'a>, - /// The current 2D transform matrix. - transform: Transform2D<f32>, - shadow_offset_x: f64, - shadow_offset_y: f64, - shadow_blur: f64, - shadow_color: Color, -} - -impl<'a> CanvasPaintState<'a> { - fn new(antialias: AntialiasMode) -> CanvasPaintState<'a> { - CanvasPaintState { - draw_options: DrawOptions::new(1.0, CompositionOp::Over, antialias), - fill_style: Pattern::Color(ColorPattern::new(Color::black())), - stroke_style: Pattern::Color(ColorPattern::new(Color::black())), - stroke_opts: StrokeOptions::new(1.0, JoinStyle::MiterOrBevel, CapStyle::Butt, 10.0, &[]), - transform: Transform2D::identity(), - shadow_offset_x: 0.0, - shadow_offset_y: 0.0, - shadow_blur: 0.0, - shadow_color: Color::transparent(), - } - } +pub struct CanvasPaintThread <'a> { + canvases: HashMap<CanvasId, CanvasData<'a>>, + next_canvas_id: CanvasId, } -impl<'a> CanvasPaintThread<'a> { - fn new(size: Size2D<i32>, - webrender_api_sender: webrender_api::RenderApiSender, - antialias: AntialiasMode, - canvas_id: CanvasId) -> CanvasPaintThread<'a> { - let draw_target = CanvasPaintThread::create(size); - let path_builder = draw_target.create_path_builder(); - let webrender_api = webrender_api_sender.create_api(); +impl<'a> CanvasPaintThread <'a> { + fn new() -> CanvasPaintThread <'a> { CanvasPaintThread { - drawtarget: draw_target, - path_builder: path_builder, - state: CanvasPaintState::new(antialias), - saved_states: vec![], - webrender_api: webrender_api, - image_key: None, - old_image_key: None, - very_old_image_key: None, - canvas_id: canvas_id, + canvases: HashMap::new(), + next_canvas_id: CanvasId(0), } } /// Creates a new `CanvasPaintThread` and returns an `IpcSender` to /// communicate with it. - pub fn start(size: Size2D<i32>, - webrender_api_sender: webrender_api::RenderApiSender, - antialias: bool, - canvas_id: CanvasId) - -> IpcSender<CanvasMsg> { + pub fn start() -> IpcSender<CanvasMsg> { let (sender, receiver) = ipc::channel::<CanvasMsg>().unwrap(); - let antialias = if antialias { - AntialiasMode::Default - } else { - AntialiasMode::None - }; thread::Builder::new().name("CanvasThread".to_owned()).spawn(move || { - let mut painter = CanvasPaintThread::new(size, webrender_api_sender, antialias, canvas_id); + let mut canvas_paint_thread = CanvasPaintThread::new(); loop { - let msg = receiver.recv(); - match msg.unwrap() { - CanvasMsg::Canvas2d(message, canvas_id) => { - assert!(canvas_id == painter.canvas_id); - match message { - Canvas2dMsg::FillText(text, x, y, max_width) => painter.fill_text(text, x, y, max_width), - Canvas2dMsg::FillRect(ref rect) => painter.fill_rect(rect), - Canvas2dMsg::StrokeRect(ref rect) => painter.stroke_rect(rect), - Canvas2dMsg::ClearRect(ref rect) => painter.clear_rect(rect), - Canvas2dMsg::BeginPath => painter.begin_path(), - Canvas2dMsg::ClosePath => painter.close_path(), - Canvas2dMsg::Fill => painter.fill(), - Canvas2dMsg::Stroke => painter.stroke(), - Canvas2dMsg::Clip => painter.clip(), - Canvas2dMsg::IsPointInPath(x, y, fill_rule, chan) => { - painter.is_point_in_path(x, y, fill_rule, chan) + match receiver.recv() { + Ok(msg) => { + match msg { + CanvasMsg::Canvas2d(message, canvas_id) => { + canvas_paint_thread.process_canvas_2d_message(message, canvas_id); + }, + CanvasMsg::Close(canvas_id) =>{ + canvas_paint_thread.canvases.remove(&canvas_id); + }, + CanvasMsg::Create(creator, size, webrenderer_api_sender, antialias) => { + let canvas_id = canvas_paint_thread.create_canvas( + size, + webrenderer_api_sender, + antialias + ); + creator.send(canvas_id).unwrap(); + }, + CanvasMsg::Recreate(size, canvas_id) =>{ + canvas_paint_thread.canvas(canvas_id).recreate(size); + }, + CanvasMsg::FromScript(message, canvas_id) => { + match message { + FromScriptMsg::SendPixels(chan) => { + canvas_paint_thread.canvas(canvas_id).send_pixels(chan); + } + } + }, + CanvasMsg::FromLayout(message, canvas_id) => { + match message { + FromLayoutMsg::SendData(chan) => { + canvas_paint_thread.canvas(canvas_id).send_data(chan); + } + } }, - Canvas2dMsg::DrawImage( - imagedata, - image_size, - dest_rect, - source_rect, - smoothing_enabled, - ) => { - painter.draw_image( - imagedata.into(), - image_size, - dest_rect, - source_rect, - smoothing_enabled, - ) - } - Canvas2dMsg::DrawImageSelf(image_size, dest_rect, source_rect, smoothing_enabled) => { - painter.draw_image_self(image_size, dest_rect, source_rect, smoothing_enabled) - } - Canvas2dMsg::DrawImageInOther( - renderer, other_canvas_id, image_size, dest_rect, source_rect, smoothing, sender - ) => { - painter.draw_image_in_other( - renderer, other_canvas_id, image_size, dest_rect, source_rect, smoothing, sender) - } - Canvas2dMsg::MoveTo(ref point) => painter.move_to(point), - Canvas2dMsg::LineTo(ref point) => painter.line_to(point), - Canvas2dMsg::Rect(ref rect) => painter.rect(rect), - Canvas2dMsg::QuadraticCurveTo(ref cp, ref pt) => { - painter.quadratic_curve_to(cp, pt) - } - Canvas2dMsg::BezierCurveTo(ref cp1, ref cp2, ref pt) => { - painter.bezier_curve_to(cp1, cp2, pt) - } - Canvas2dMsg::Arc(ref center, radius, start, end, ccw) => { - painter.arc(center, radius, start, end, ccw) - } - Canvas2dMsg::ArcTo(ref cp1, ref cp2, radius) => { - painter.arc_to(cp1, cp2, radius) - } - Canvas2dMsg::Ellipse(ref center, radius_x, radius_y, rotation, start, end, ccw) => { - painter.ellipse(center, radius_x, radius_y, rotation, start, end, ccw) - } - Canvas2dMsg::RestoreContext => painter.restore_context_state(), - Canvas2dMsg::SaveContext => painter.save_context_state(), - Canvas2dMsg::SetFillStyle(style) => painter.set_fill_style(style), - Canvas2dMsg::SetStrokeStyle(style) => painter.set_stroke_style(style), - Canvas2dMsg::SetLineWidth(width) => painter.set_line_width(width), - Canvas2dMsg::SetLineCap(cap) => painter.set_line_cap(cap), - Canvas2dMsg::SetLineJoin(join) => painter.set_line_join(join), - Canvas2dMsg::SetMiterLimit(limit) => painter.set_miter_limit(limit), - Canvas2dMsg::SetTransform(ref matrix) => painter.set_transform(matrix), - Canvas2dMsg::SetGlobalAlpha(alpha) => painter.set_global_alpha(alpha), - Canvas2dMsg::SetGlobalComposition(op) => painter.set_global_composition(op), - Canvas2dMsg::GetImageData(dest_rect, canvas_size, chan) - => painter.image_data(dest_rect, canvas_size, chan), - Canvas2dMsg::PutImageData( - imagedata, - offset, - image_data_size, - dirty_rect, - ) => { - painter.put_image_data( - imagedata.into(), - offset, - image_data_size, - dirty_rect, - ) - } - Canvas2dMsg::SetShadowOffsetX(value) => painter.set_shadow_offset_x(value), - Canvas2dMsg::SetShadowOffsetY(value) => painter.set_shadow_offset_y(value), - Canvas2dMsg::SetShadowBlur(value) => painter.set_shadow_blur(value), - Canvas2dMsg::SetShadowColor(ref color) => painter.set_shadow_color(color.to_azure_style()), - } - }, - CanvasMsg::Close(canvas_id) =>{ - assert!(canvas_id == painter.canvas_id); - break; - }, - CanvasMsg::Recreate(size, canvas_id) =>{ - assert!(canvas_id == painter.canvas_id); - painter.recreate(size); - }, - CanvasMsg::FromScript(message, canvas_id) => { - assert!(canvas_id == painter.canvas_id); - match message { - FromScriptMsg::SendPixels(chan) => { - painter.send_pixels(chan) - } - } - }, - CanvasMsg::FromLayout(message, canvas_id) => { - assert!(canvas_id == painter.canvas_id); - match message { - FromLayoutMsg::SendData(chan) => { - painter.send_data(chan) - } } }, + Err(e) => { + warn!("Error on CanvasPaintThread receive ({})", e); + } } } }).expect("Thread spawning failed"); @@ -256,883 +78,204 @@ impl<'a> CanvasPaintThread<'a> { sender } - fn save_context_state(&mut self) { - self.saved_states.push(self.state.clone()); - } - - fn restore_context_state(&mut self) { - if let Some(state) = self.saved_states.pop() { - mem::replace(&mut self.state, state); - self.drawtarget.set_transform(&self.state.transform); - self.drawtarget.pop_clip(); - } - } - - fn fill_text(&self, text: String, x: f64, y: f64, max_width: Option<f64>) { - error!("Unimplemented canvas2d.fillText. Values received: {}, {}, {}, {:?}.", text, x, y, max_width); - } - - fn fill_rect(&self, rect: &Rect<f32>) { - if is_zero_size_gradient(&self.state.fill_style) { - return; // Paint nothing if gradient size is zero. - } - - let draw_rect = Rect::new(rect.origin, - match self.state.fill_style { - Pattern::Surface(ref surface) => { - let surface_size = surface.size(); - match (surface.repeat_x, surface.repeat_y) { - (true, true) => rect.size, - (true, false) => Size2D::new(rect.size.width, surface_size.height as f32), - (false, true) => Size2D::new(surface_size.width as f32, rect.size.height), - (false, false) => Size2D::new(surface_size.width as f32, surface_size.height as f32), - } - }, - _ => rect.size, - } - ); - - 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>) { - self.drawtarget.clear_rect(rect); - } - - fn stroke_rect(&self, rect: &Rect<f32>) { - if is_zero_size_gradient(&self.state.stroke_style) { - return; // Paint nothing if gradient size is zero. - } - - if self.need_to_draw_shadow() { - self.draw_with_shadow(&rect, |new_draw_target: &DrawTarget| { - new_draw_target.stroke_rect(rect, self.state.stroke_style.to_pattern_ref(), - &self.state.stroke_opts, &self.state.draw_options); - }); - } else if rect.size.width == 0. || rect.size.height == 0. { - let cap = match self.state.stroke_opts.line_join { - JoinStyle::Round => CapStyle::Round, - _ => CapStyle::Butt - }; - - let stroke_opts = - StrokeOptions::new(self.state.stroke_opts.line_width, - self.state.stroke_opts.line_join, - cap, - self.state.stroke_opts.miter_limit, - self.state.stroke_opts.mDashPattern); - self.drawtarget.stroke_line(rect.origin, rect.bottom_right(), - self.state.stroke_style.to_pattern_ref(), - &stroke_opts, &self.state.draw_options); - } else { - self.drawtarget.stroke_rect(rect, self.state.stroke_style.to_pattern_ref(), - &self.state.stroke_opts, &self.state.draw_options); - } - } - - fn begin_path(&mut self) { - self.path_builder = self.drawtarget.create_path_builder() - } - - fn close_path(&self) { - self.path_builder.close() - } - - fn fill(&self) { - if is_zero_size_gradient(&self.state.fill_style) { - return; // Paint nothing if gradient size is zero. - } - - self.drawtarget.fill(&self.path_builder.finish(), - self.state.fill_style.to_pattern_ref(), - &self.state.draw_options); - } - - fn stroke(&self) { - if is_zero_size_gradient(&self.state.stroke_style) { - return; // Paint nothing if gradient size is zero. - } - - self.drawtarget.stroke(&self.path_builder.finish(), - self.state.stroke_style.to_pattern_ref(), - &self.state.stroke_opts, - &self.state.draw_options); - } - - fn clip(&self) { - self.drawtarget.push_clip(&self.path_builder.finish()); - } - - fn is_point_in_path(&mut self, x: f64, y: f64, - _fill_rule: FillRule, chan: IpcSender<bool>) { - let path = self.path_builder.finish(); - let result = path.contains_point(x, y, &self.state.transform); - self.path_builder = path.copy_to_builder(); - chan.send(result).unwrap(); - } - - fn draw_image(&self, image_data: Vec<u8>, image_size: Size2D<f64>, - dest_rect: Rect<f64>, source_rect: Rect<f64>, smoothing_enabled: bool) { - // We round up the floating pixel values to draw the pixels - 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); - - 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.composition, - self.state.draw_options.alpha); - }); - } else { - write_image(&self.drawtarget, image_data, source_rect.size, dest_rect, - smoothing_enabled, self.state.draw_options.composition, - 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 image_data = self.read_pixels(source_rect.to_i32(), 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.composition, - self.state.draw_options.alpha); - }); + pub fn create_canvas( + &mut self, + size: Size2D<i32>, + webrender_api_sender: webrender_api::RenderApiSender, + antialias: bool + ) -> CanvasId { + let antialias = if antialias { + AntialiasMode::Default } else { - // Writes on target canvas - write_image(&self.drawtarget, image_data, image_size, dest_rect, - smoothing_enabled, self.state.draw_options.composition, - self.state.draw_options.alpha); - } - } - - fn draw_image_in_other(&self, - renderer: IpcSender<CanvasMsg>, - other_canvas_id: CanvasId, - image_size: Size2D<f64>, - dest_rect: Rect<f64>, - source_rect: Rect<f64>, - smoothing_enabled: bool, - sender: IpcSender<()>) { - let mut image_data = self.read_pixels(source_rect.to_i32(), image_size); - // TODO: avoid double byte_swap. - byte_swap(&mut image_data); - - let msg = CanvasMsg::Canvas2d(Canvas2dMsg::DrawImage( - image_data.into(), - source_rect.size, - dest_rect, - source_rect, - smoothing_enabled, - ), - other_canvas_id, - ); - renderer.send(msg).unwrap(); - // We acknowledge to the caller here that the data was sent to the - // other canvas so that if JS immediately afterwards try to get the - // pixels of the other one, it won't retrieve the other values. - sender.send(()).unwrap(); - } - - fn move_to(&self, point: &Point2D<AzFloat>) { - self.path_builder.move_to(*point) - } - - fn line_to(&self, point: &Point2D<AzFloat>) { - self.path_builder.line_to(*point) - } - - fn rect(&self, rect: &Rect<f32>) { - self.path_builder.move_to(Point2D::new(rect.origin.x, rect.origin.y)); - self.path_builder.line_to(Point2D::new(rect.origin.x + rect.size.width, rect.origin.y)); - self.path_builder.line_to(Point2D::new(rect.origin.x + rect.size.width, - rect.origin.y + rect.size.height)); - self.path_builder.line_to(Point2D::new(rect.origin.x, rect.origin.y + rect.size.height)); - self.path_builder.close(); - } - - fn quadratic_curve_to(&self, - cp: &Point2D<AzFloat>, - endpoint: &Point2D<AzFloat>) { - self.path_builder.quadratic_curve_to(cp, endpoint) - } - - fn bezier_curve_to(&self, - cp1: &Point2D<AzFloat>, - cp2: &Point2D<AzFloat>, - endpoint: &Point2D<AzFloat>) { - self.path_builder.bezier_curve_to(cp1, cp2, endpoint) - } - - fn arc(&self, - center: &Point2D<AzFloat>, - radius: AzFloat, - start_angle: AzFloat, - end_angle: AzFloat, - ccw: bool) { - self.path_builder.arc(*center, radius, start_angle, end_angle, ccw) - } - - fn arc_to(&self, - cp1: &Point2D<AzFloat>, - cp2: &Point2D<AzFloat>, - radius: AzFloat) { - let cp0 = self.path_builder.get_current_point(); - let cp1 = *cp1; - let cp2 = *cp2; - - if (cp0.x == cp1.x && cp0.y == cp1.y) || cp1 == cp2 || radius == 0.0 { - self.line_to(&cp1); - return; - } - - // if all three control points lie on a single straight line, - // connect the first two by a straight line - let direction = (cp2.x - cp1.x) * (cp0.y - cp1.y) + (cp2.y - cp1.y) * (cp1.x - cp0.x); - if direction == 0.0 { - self.line_to(&cp1); - return; - } - - // otherwise, draw the Arc - let a2 = (cp0.x - cp1.x).powi(2) + (cp0.y - cp1.y).powi(2); - let b2 = (cp1.x - cp2.x).powi(2) + (cp1.y - cp2.y).powi(2); - let d = { - let c2 = (cp0.x - cp2.x).powi(2) + (cp0.y - cp2.y).powi(2); - let cosx = (a2 + b2 - c2) / (2.0 * (a2 * b2).sqrt()); - let sinx = (1.0 - cosx.powi(2)).sqrt(); - radius / ((1.0 - cosx) / sinx) + AntialiasMode::None }; - // first tangent point - let anx = (cp1.x - cp0.x) / a2.sqrt(); - let any = (cp1.y - cp0.y) / a2.sqrt(); - let tp1 = Point2D::new(cp1.x - anx * d, cp1.y - any * d); - - // second tangent point - let bnx = (cp1.x - cp2.x) / b2.sqrt(); - let bny = (cp1.y - cp2.y) / b2.sqrt(); - let tp2 = Point2D::new(cp1.x - bnx * d, cp1.y - bny * d); - - // arc center and angles - let anticlockwise = direction < 0.0; - let cx = tp1.x + any * radius * if anticlockwise { 1.0 } else { -1.0 }; - let cy = tp1.y - anx * radius * if anticlockwise { 1.0 } else { -1.0 }; - let angle_start = (tp1.y - cy).atan2(tp1.x - cx); - let angle_end = (tp2.y - cy).atan2(tp2.x - cx); - - self.line_to(&tp1); - if [cx, cy, angle_start, angle_end].iter().all(|x| x.is_finite()) { - self.arc(&Point2D::new(cx, cy), radius, - angle_start, angle_end, anticlockwise); - } - } - - fn ellipse(&mut self, - center: &Point2D<AzFloat>, - radius_x: AzFloat, - radius_y: AzFloat, - rotation_angle: AzFloat, - start_angle: AzFloat, - end_angle: AzFloat, - ccw: bool) { - self.path_builder.ellipse(*center, radius_x, radius_y, rotation_angle, start_angle, end_angle, ccw); - } - - fn set_fill_style(&mut self, style: FillOrStrokeStyle) { - if let Some(pattern) = style.to_azure_pattern(&self.drawtarget) { - self.state.fill_style = pattern - } - } - - fn set_stroke_style(&mut self, style: FillOrStrokeStyle) { - if let Some(pattern) = style.to_azure_pattern(&self.drawtarget) { - self.state.stroke_style = pattern - } - } - - fn set_line_width(&mut self, width: f32) { - self.state.stroke_opts.line_width = width; - } - - fn set_line_cap(&mut self, cap: LineCapStyle) { - self.state.stroke_opts.line_cap = cap.to_azure_style(); - } - - fn set_line_join(&mut self, join: LineJoinStyle) { - self.state.stroke_opts.line_join = join.to_azure_style(); - } - - fn set_miter_limit(&mut self, limit: f32) { - self.state.stroke_opts.miter_limit = limit; - } - - fn set_transform(&mut self, transform: &Transform2D<f32>) { - self.state.transform = transform.clone(); - self.drawtarget.set_transform(transform) - } - - fn set_global_alpha(&mut self, alpha: f32) { - self.state.draw_options.alpha = alpha; - } - - fn set_global_composition(&mut self, op: CompositionOrBlending) { - self.state.draw_options.set_composition_op(op.to_azure_style()); - } + let canvas_id = self.next_canvas_id.clone(); + self.next_canvas_id.0 += 1; - fn create(size: Size2D<i32>) -> DrawTarget { - DrawTarget::new(BackendType::Skia, size, SurfaceFormat::B8G8R8A8) - } + let canvas_data = CanvasData::new(size, webrender_api_sender, antialias, canvas_id.clone()); + self.canvases.insert(canvas_id.clone(), canvas_data); - fn recreate(&mut self, size: Size2D<i32>) { - // TODO: clear the thread state. https://github.com/servo/servo/issues/17533 - self.drawtarget = CanvasPaintThread::create(size); - self.state = CanvasPaintState::new(self.state.draw_options.antialias); - self.saved_states.clear(); - // Webrender doesn't let images change size, so we clear the webrender image key. - // TODO: there is an annying race condition here: the display list builder - // might still be using the old image key. Really, we should be scheduling the image - // for later deletion, not deleting it immediately. - // https://github.com/servo/servo/issues/17534 - if let Some(image_key) = self.image_key.take() { - // If this executes, then we are in a new epoch since we last recreated the canvas, - // so `old_image_key` must be `None`. - debug_assert!(self.old_image_key.is_none()); - self.old_image_key = Some(image_key); - } + canvas_id } - fn send_pixels(&mut self, chan: IpcSender<Option<ByteBuf>>) { - self.drawtarget.snapshot().get_data_surface().with_data(|element| { - chan.send(Some(Vec::from(element).into())).unwrap(); - }) - } - - fn send_data(&mut self, chan: IpcSender<CanvasImageData>) { - self.drawtarget.snapshot().get_data_surface().with_data(|element| { - let size = self.drawtarget.get_size(); - - let descriptor = webrender_api::ImageDescriptor { - width: size.width as u32, - height: size.height as u32, - stride: None, - format: webrender_api::ImageFormat::BGRA8, - offset: 0, - is_opaque: false, - allow_mipmaps: false, - }; - let data = webrender_api::ImageData::Raw(Arc::new(element.into())); - - let mut updates = webrender_api::ResourceUpdates::new(); - - match self.image_key { - Some(image_key) => { - debug!("Updating image {:?}.", image_key); - updates.update_image(image_key, - descriptor, - data, - None); - } - None => { - self.image_key = Some(self.webrender_api.generate_image_key()); - debug!("New image {:?}.", self.image_key); - updates.add_image(self.image_key.unwrap(), - descriptor, - data, - None); - } - } - - if let Some(image_key) = mem::replace(&mut self.very_old_image_key, self.old_image_key.take()) { - updates.delete_image(image_key); + fn process_canvas_2d_message(&mut self, message: Canvas2dMsg, canvas_id: CanvasId) { + match message { + Canvas2dMsg::FillText(text, x, y, max_width) => { + self.canvas(canvas_id).fill_text(text, x, y, max_width) + }, + Canvas2dMsg::FillRect(ref rect) => { + self.canvas(canvas_id).fill_rect(rect) + }, + Canvas2dMsg::StrokeRect(ref rect) => { + self.canvas(canvas_id).stroke_rect(rect) + }, + Canvas2dMsg::ClearRect(ref rect) => { + self.canvas(canvas_id).clear_rect(rect) + }, + Canvas2dMsg::BeginPath => { + self.canvas(canvas_id).begin_path() + }, + Canvas2dMsg::ClosePath => { + self.canvas(canvas_id).close_path() + }, + Canvas2dMsg::Fill => { + self.canvas(canvas_id).fill() + }, + Canvas2dMsg::Stroke => { + self.canvas(canvas_id).stroke() + }, + Canvas2dMsg::Clip => { + self.canvas(canvas_id).clip() + }, + Canvas2dMsg::IsPointInPath(x, y, fill_rule, chan) => { + self.canvas(canvas_id).is_point_in_path(x, y, fill_rule, chan) + }, + Canvas2dMsg::DrawImage( + imagedata, + image_size, + dest_rect, + source_rect, + smoothing_enabled, + ) => { + self.canvas(canvas_id).draw_image( + imagedata.into(), + image_size, + dest_rect, + source_rect, + smoothing_enabled, + ) + }, + Canvas2dMsg::DrawImageSelf( + image_size, + dest_rect, + source_rect, + smoothing_enabled + ) => { + self.canvas(canvas_id).draw_image_self( + image_size, + dest_rect, + source_rect, + smoothing_enabled + ) + }, + Canvas2dMsg::DrawImageInOther( + other_canvas_id, + image_size, + dest_rect, + source_rect, + smoothing + ) => { + let mut image_data = self.canvas(canvas_id).read_pixels( + source_rect.to_i32(), + image_size); + // TODO: avoid double byte_swap. + byte_swap(&mut image_data); + self.canvas(other_canvas_id).draw_image( + image_data.into(), + source_rect.size, + dest_rect, + source_rect, + smoothing, + ); + }, + Canvas2dMsg::MoveTo(ref point) => { + self.canvas(canvas_id).move_to(point) + }, + Canvas2dMsg::LineTo(ref point) => { + self.canvas(canvas_id).line_to(point) + }, + Canvas2dMsg::Rect(ref rect) => { + self.canvas(canvas_id).rect(rect) + }, + Canvas2dMsg::QuadraticCurveTo(ref cp, ref pt) => { + self.canvas(canvas_id).quadratic_curve_to(cp, pt) + } + Canvas2dMsg::BezierCurveTo(ref cp1, ref cp2, ref pt) => { + self.canvas(canvas_id).bezier_curve_to(cp1, cp2, pt) + } + Canvas2dMsg::Arc(ref center, radius, start, end, ccw) => { + self.canvas(canvas_id).arc(center, radius, start, end, ccw) + } + Canvas2dMsg::ArcTo(ref cp1, ref cp2, radius) => { + self.canvas(canvas_id).arc_to(cp1, cp2, radius) } - - self.webrender_api.update_resources(updates); - - let data = CanvasImageData { - image_key: self.image_key.unwrap(), - }; - chan.send(data).unwrap(); - }) - } - - fn image_data( - &self, - dest_rect: Rect<i32>, - canvas_size: Size2D<f64>, - chan: IpcSender<ByteBuf>, - ) { - let mut dest_data = self.read_pixels(dest_rect, canvas_size); - - // bgra -> rgba - byte_swap(&mut dest_data); - chan.send(dest_data.into()).unwrap(); - } - - // https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata - fn put_image_data(&mut self, imagedata: Vec<u8>, - offset: Vector2D<f64>, - image_data_size: Size2D<f64>, - mut dirty_rect: Rect<f64>) { - if image_data_size.width <= 0.0 || image_data_size.height <= 0.0 { - return - } - - assert_eq!(image_data_size.width * image_data_size.height * 4.0, imagedata.len() as f64); - - // Step 1. TODO (neutered data) - - // Step 2. - if dirty_rect.size.width < 0.0f64 { - dirty_rect.origin.x += dirty_rect.size.width; - dirty_rect.size.width = -dirty_rect.size.width; - } - - if dirty_rect.size.height < 0.0f64 { - dirty_rect.origin.y += dirty_rect.size.height; - dirty_rect.size.height = -dirty_rect.size.height; - } - - // Step 3. - if dirty_rect.origin.x < 0.0f64 { - dirty_rect.size.width += dirty_rect.origin.x; - dirty_rect.origin.x = 0.0f64; - } - - if dirty_rect.origin.y < 0.0f64 { - dirty_rect.size.height += dirty_rect.origin.y; - dirty_rect.origin.y = 0.0f64; - } - - // Step 4. - if dirty_rect.max_x() > image_data_size.width { - dirty_rect.size.width = image_data_size.width - dirty_rect.origin.x; - } - - if dirty_rect.max_y() > image_data_size.height { - dirty_rect.size.height = image_data_size.height - dirty_rect.origin.y; - } - - // 5) If either dirtyWidth or dirtyHeight is negative or zero, - // stop without affecting any bitmaps - if dirty_rect.size.width <= 0.0 || dirty_rect.size.height <= 0.0 { - return - } - - // Step 6. - let dest_rect = dirty_rect.translate(&offset).to_i32(); - - // azure_hl operates with integers. We need to cast the image size - let image_size = image_data_size.to_i32(); - - let first_pixel = dest_rect.origin - offset.to_i32(); - let mut src_line = (first_pixel.y * (image_size.width * 4) + first_pixel.x * 4) as usize; - - let mut dest = - Vec::with_capacity((dest_rect.size.width * dest_rect.size.height * 4) as usize); - - for _ in 0 .. dest_rect.size.height { - let mut src_offset = src_line; - for _ in 0 .. dest_rect.size.width { - let alpha = imagedata[src_offset + 3] as u16; - // add 127 before dividing for more accurate rounding - let premultiply_channel = |channel: u8| (((channel as u16 * alpha) + 127) / 255) as u8; - dest.push(premultiply_channel(imagedata[src_offset + 2])); - dest.push(premultiply_channel(imagedata[src_offset + 1])); - dest.push(premultiply_channel(imagedata[src_offset + 0])); - dest.push(imagedata[src_offset + 3]); - src_offset += 4; + Canvas2dMsg::Ellipse(ref center, radius_x, radius_y, rotation, start, end, ccw) => { + self.canvas(canvas_id).ellipse( + center, + radius_x, + radius_y, + rotation, + start, + end, + ccw + ) } - src_line += (image_size.width * 4) as usize; - } - - if let Some(source_surface) = self.drawtarget.create_source_surface_from_data( - &dest, - dest_rect.size, - dest_rect.size.width * 4, - SurfaceFormat::B8G8R8A8) { - self.drawtarget.copy_surface(source_surface, - Rect::new(Point2D::new(0, 0), dest_rect.size), - dest_rect.origin); - } - } - - fn set_shadow_offset_x(&mut self, value: f64) { - self.state.shadow_offset_x = value; - } - - fn set_shadow_offset_y(&mut self, value: f64) { - self.state.shadow_offset_y = value; - } - - fn set_shadow_blur(&mut self, value: f64) { - self.state.shadow_blur = value; - } - - fn set_shadow_color(&mut self, value: Color) { - 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 = Transform2D::identity() - .pre_translate(-source_rect.origin.to_vector().cast().unwrap()) - .pre_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, - &Vector2D::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); - } -} - -impl<'a> Drop for CanvasPaintThread<'a> { - fn drop(&mut self) { - let mut updates = webrender_api::ResourceUpdates::new(); - - if let Some(image_key) = self.old_image_key.take() { - updates.delete_image(image_key); - } - if let Some(image_key) = self.very_old_image_key.take() { - updates.delete_image(image_key); - } - - self.webrender_api.update_resources(updates); - } -} - -/// Used by drawImage to get rid of the extra pixels of the image data that -/// won't be copied to the canvas -/// image_data: Color pixel data of the image -/// image_size: Image dimensions -/// crop_rect: It determines the area of the image we want to keep -fn crop_image(image_data: Vec<u8>, - image_size: Size2D<f64>, - crop_rect: Rect<f64>) -> Vec<u8>{ - // We're going to iterate over a pixel values array so we need integers - let crop_rect = crop_rect.to_i32(); - let image_size = image_size.to_i32(); - // Assuming 4 bytes per pixel and row-major order for storage - // (consecutive elements in a pixel row of the image are contiguous in memory) - let stride = image_size.width * 4; - let image_bytes_length = image_size.height * image_size.width * 4; - let crop_area_bytes_length = crop_rect.size.height * crop_rect.size.width * 4; - // If the image size is less or equal than the crop area we do nothing - if image_bytes_length <= crop_area_bytes_length { - return image_data; - } - - let mut new_image_data = Vec::new(); - let mut src = (crop_rect.origin.y * stride + crop_rect.origin.x * 4) as usize; - for _ in 0..crop_rect.size.height { - let row = &image_data[src .. src + (4 * crop_rect.size.width) as usize]; - new_image_data.extend_from_slice(row); - src += stride as usize; - } - 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, - composition_op: CompositionOp, - global_alpha: f32) { - if image_data.is_empty() { - return - } - let image_rect = Rect::new(Point2D::zero(), image_size); - // rgba -> bgra - byte_swap(&mut image_data); - - // 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(); - - if let Some(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, composition_op, AntialiasMode::None); - - draw_target.draw_surface(source_surface, - dest_rect.to_azure_style(), - image_rect.to_azure_style(), - draw_surface_options, - draw_options); - } -} - -fn is_zero_size_gradient(pattern: &Pattern) -> bool { - if let &Pattern::LinearGradient(ref gradient) = pattern { - if gradient.is_zero_size() { - return true; - } - } - false -} - -pub trait PointToi32 { - fn to_i32(&self) -> Point2D<i32>; -} - -impl PointToi32 for Point2D<f64> { - fn to_i32(&self) -> Point2D<i32> { - Point2D::new(self.x.to_i32().unwrap(), - self.y.to_i32().unwrap()) - } -} - -pub trait SizeToi32 { - fn to_i32(&self) -> Size2D<i32>; -} - -impl SizeToi32 for Size2D<f64> { - fn to_i32(&self) -> Size2D<i32> { - Size2D::new(self.width.to_i32().unwrap(), - self.height.to_i32().unwrap()) - } -} - -pub trait RectToi32 { - fn to_i32(&self) -> Rect<i32>; - fn ceil(&self) -> Rect<f64>; -} - -impl RectToi32 for Rect<f64> { - fn to_i32(&self) -> Rect<i32> { - Rect::new(Point2D::new(self.origin.x.to_i32().unwrap(), - self.origin.y.to_i32().unwrap()), - Size2D::new(self.size.width.to_i32().unwrap(), - self.size.height.to_i32().unwrap())) - } - - fn ceil(&self) -> Rect<f64> { - Rect::new(Point2D::new(self.origin.x.ceil(), - self.origin.y.ceil()), - Size2D::new(self.size.width.ceil(), - self.size.height.ceil())) - } - -} - -pub trait ToAzureStyle { - type Target; - fn to_azure_style(self) -> Self::Target; -} - -impl ToAzureStyle for Rect<f64> { - type Target = Rect<AzFloat>; - - fn to_azure_style(self) -> Rect<AzFloat> { - Rect::new(Point2D::new(self.origin.x as AzFloat, self.origin.y as AzFloat), - Size2D::new(self.size.width as AzFloat, self.size.height as AzFloat)) - } -} - - -impl ToAzureStyle for LineCapStyle { - type Target = CapStyle; - - fn to_azure_style(self) -> CapStyle { - match self { - LineCapStyle::Butt => CapStyle::Butt, - LineCapStyle::Round => CapStyle::Round, - LineCapStyle::Square => CapStyle::Square, - } - } -} - -impl ToAzureStyle for LineJoinStyle { - type Target = JoinStyle; - - fn to_azure_style(self) -> JoinStyle { - match self { - LineJoinStyle::Round => JoinStyle::Round, - LineJoinStyle::Bevel => JoinStyle::Bevel, - LineJoinStyle::Miter => JoinStyle::Miter, - } - } -} - -impl ToAzureStyle for CompositionStyle { - type Target = CompositionOp; - - fn to_azure_style(self) -> CompositionOp { - match self { - CompositionStyle::SrcIn => CompositionOp::In, - CompositionStyle::SrcOut => CompositionOp::Out, - CompositionStyle::SrcOver => CompositionOp::Over, - CompositionStyle::SrcAtop => CompositionOp::Atop, - CompositionStyle::DestIn => CompositionOp::DestIn, - CompositionStyle::DestOut => CompositionOp::DestOut, - CompositionStyle::DestOver => CompositionOp::DestOver, - CompositionStyle::DestAtop => CompositionOp::DestAtop, - CompositionStyle::Copy => CompositionOp::Source, - CompositionStyle::Lighter => CompositionOp::Add, - CompositionStyle::Xor => CompositionOp::Xor, - } - } -} - -impl ToAzureStyle for BlendingStyle { - type Target = CompositionOp; - - fn to_azure_style(self) -> CompositionOp { - match self { - BlendingStyle::Multiply => CompositionOp::Multiply, - BlendingStyle::Screen => CompositionOp::Screen, - BlendingStyle::Overlay => CompositionOp::Overlay, - BlendingStyle::Darken => CompositionOp::Darken, - BlendingStyle::Lighten => CompositionOp::Lighten, - BlendingStyle::ColorDodge => CompositionOp::ColorDodge, - BlendingStyle::ColorBurn => CompositionOp::ColorBurn, - BlendingStyle::HardLight => CompositionOp::HardLight, - BlendingStyle::SoftLight => CompositionOp::SoftLight, - BlendingStyle::Difference => CompositionOp::Difference, - BlendingStyle::Exclusion => CompositionOp::Exclusion, - BlendingStyle::Hue => CompositionOp::Hue, - BlendingStyle::Saturation => CompositionOp::Saturation, - BlendingStyle::Color => CompositionOp::Color, - BlendingStyle::Luminosity => CompositionOp::Luminosity, - } - } -} - -impl ToAzureStyle for CompositionOrBlending { - type Target = CompositionOp; - - fn to_azure_style(self) -> CompositionOp { - match self { - CompositionOrBlending::Composition(op) => op.to_azure_style(), - CompositionOrBlending::Blending(op) => op.to_azure_style(), - } - } -} - -pub trait ToAzurePattern { - fn to_azure_pattern(&self, drawtarget: &DrawTarget) -> Option<Pattern>; -} - -impl ToAzurePattern for FillOrStrokeStyle { - fn to_azure_pattern(&self, drawtarget: &DrawTarget) -> Option<Pattern> { - match *self { - FillOrStrokeStyle::Color(ref color) => { - Some(Pattern::Color(ColorPattern::new(color.to_azure_style()))) + Canvas2dMsg::RestoreContext => { + self.canvas(canvas_id).restore_context_state() }, - FillOrStrokeStyle::LinearGradient(ref linear_gradient_style) => { - let gradient_stops: Vec<GradientStop> = linear_gradient_style.stops.iter().map(|s| { - GradientStop { - offset: s.offset as AzFloat, - color: s.color.to_azure_style() - } - }).collect(); - - Some(Pattern::LinearGradient(LinearGradientPattern::new( - &Point2D::new(linear_gradient_style.x0 as AzFloat, linear_gradient_style.y0 as AzFloat), - &Point2D::new(linear_gradient_style.x1 as AzFloat, linear_gradient_style.y1 as AzFloat), - drawtarget.create_gradient_stops(&gradient_stops, ExtendMode::Clamp), - &Transform2D::identity()))) + Canvas2dMsg::SaveContext => { + self.canvas(canvas_id).save_context_state() }, - FillOrStrokeStyle::RadialGradient(ref radial_gradient_style) => { - let gradient_stops: Vec<GradientStop> = radial_gradient_style.stops.iter().map(|s| { - GradientStop { - offset: s.offset as AzFloat, - color: s.color.to_azure_style() - } - }).collect(); - - Some(Pattern::RadialGradient(RadialGradientPattern::new( - &Point2D::new(radial_gradient_style.x0 as AzFloat, radial_gradient_style.y0 as AzFloat), - &Point2D::new(radial_gradient_style.x1 as AzFloat, radial_gradient_style.y1 as AzFloat), - radial_gradient_style.r0 as AzFloat, radial_gradient_style.r1 as AzFloat, - drawtarget.create_gradient_stops(&gradient_stops, ExtendMode::Clamp), - &Transform2D::identity()))) + Canvas2dMsg::SetFillStyle(style) => { + self.canvas(canvas_id).set_fill_style(style) + }, + Canvas2dMsg::SetStrokeStyle(style) => { + self.canvas(canvas_id).set_stroke_style(style) + }, + Canvas2dMsg::SetLineWidth(width) => { + self.canvas(canvas_id).set_line_width(width) + }, + Canvas2dMsg::SetLineCap(cap) => { + self.canvas(canvas_id).set_line_cap(cap) + }, + Canvas2dMsg::SetLineJoin(join) => { + self.canvas(canvas_id).set_line_join(join) + }, + Canvas2dMsg::SetMiterLimit(limit) => { + self.canvas(canvas_id).set_miter_limit(limit) + }, + Canvas2dMsg::SetTransform(ref matrix) => { + self.canvas(canvas_id).set_transform(matrix) + }, + Canvas2dMsg::SetGlobalAlpha(alpha) => { + self.canvas(canvas_id).set_global_alpha(alpha) + }, + Canvas2dMsg::SetGlobalComposition(op) => { + self.canvas(canvas_id).set_global_composition(op) + }, + Canvas2dMsg::GetImageData(dest_rect, canvas_size, chan) => { + self.canvas(canvas_id).image_data(dest_rect, canvas_size, chan) + }, + Canvas2dMsg::PutImageData( + imagedata, + offset, + image_data_size, + dirty_rect, + ) => { + self.canvas(canvas_id).put_image_data( + imagedata.into(), + offset, + image_data_size, + dirty_rect, + ) + }, + Canvas2dMsg::SetShadowOffsetX(value) => { + self.canvas(canvas_id).set_shadow_offset_x(value) + }, + Canvas2dMsg::SetShadowOffsetY(value) => { + self.canvas(canvas_id).set_shadow_offset_y(value) + }, + Canvas2dMsg::SetShadowBlur(value) => { + self.canvas(canvas_id).set_shadow_blur(value) + }, + Canvas2dMsg::SetShadowColor(ref color) => { + self.canvas(canvas_id).set_shadow_color(color.to_azure_style()) }, - FillOrStrokeStyle::Surface(ref surface_style) => { - drawtarget.create_source_surface_from_data(&surface_style.surface_data, - surface_style.surface_size, - surface_style.surface_size.width * 4, - SurfaceFormat::B8G8R8A8) - .map(|source_surface| { - Pattern::Surface(SurfacePattern::new( - source_surface.azure_source_surface, - surface_style.repeat_x, - surface_style.repeat_y, - &Transform2D::identity())) - }) - } } } -} - -impl ToAzureStyle for RGBA { - type Target = Color; - fn to_azure_style(self) -> Color { - Color::rgba(self.red_f32() as AzFloat, - self.green_f32() as AzFloat, - self.blue_f32() as AzFloat, - self.alpha_f32() as AzFloat) + fn canvas(&mut self, canvas_id: CanvasId) -> &mut CanvasData<'a> { + self.canvases.get_mut(&canvas_id).expect("Bogus canvas id") } } diff --git a/components/canvas/lib.rs b/components/canvas/lib.rs index f6806f533cf..8da9544de6e 100644 --- a/components/canvas/lib.rs +++ b/components/canvas/lib.rs @@ -20,6 +20,7 @@ extern crate servo_config; extern crate webrender; extern crate webrender_api; +pub mod canvas_data; pub mod canvas_paint_thread; pub mod gl_context; mod webgl_mode; diff --git a/components/canvas_traits/canvas.rs b/components/canvas_traits/canvas.rs index a8cd79cae32..6b66c88dda2 100644 --- a/components/canvas_traits/canvas.rs +++ b/components/canvas_traits/canvas.rs @@ -16,12 +16,13 @@ pub enum FillRule { Evenodd, } -#[derive(Clone, Deserialize, MallocSizeOf, PartialEq, Serialize)] +#[derive(Clone, Copy, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] pub struct CanvasId(pub u64); #[derive(Clone, Deserialize, Serialize)] pub enum CanvasMsg { Canvas2d(Canvas2dMsg, CanvasId), + Create(IpcSender<CanvasId>, Size2D<i32>, webrender_api::RenderApiSender, bool), FromLayout(FromLayoutMsg, CanvasId), FromScript(FromScriptMsg, CanvasId), Recreate(Size2D<i32>, CanvasId), @@ -40,7 +41,7 @@ pub enum Canvas2dMsg { DrawImage(ByteBuf, Size2D<f64>, Rect<f64>, Rect<f64>, bool), DrawImageSelf(Size2D<f64>, Rect<f64>, Rect<f64>, bool), DrawImageInOther( - IpcSender<CanvasMsg>, CanvasId, Size2D<f64>, Rect<f64>, Rect<f64>, bool, IpcSender<()>), + CanvasId, Size2D<f64>, Rect<f64>, Rect<f64>, bool), BeginPath, BezierCurveTo(Point2D<f32>, Point2D<f32>, Point2D<f32>), ClearRect(Rect<f32>), diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 261ed6e265b..ca8686529cb 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -330,8 +330,8 @@ pub struct Constellation<Message, LTF, STF> { /// A channel through which messages can be sent to the webvr thread. webvr_chan: Option<IpcSender<WebVRMsg>>, - /// An Id for the next canvas to use. - canvas_id: CanvasId, + /// A channel through which messages can be sent to the canvas paint thread. + canvas_chan: IpcSender<CanvasMsg>, } /// State needed to construct a constellation. @@ -630,7 +630,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> }), webgl_threads: state.webgl_threads, webvr_chan: state.webvr_chan, - canvas_id: CanvasId(0), + canvas_chan: CanvasPaintThread::start(), }; constellation.run(); @@ -2286,11 +2286,25 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> &mut self, size: &Size2D<i32>, response_sender: IpcSender<(IpcSender<CanvasMsg>, CanvasId)>) { - self.canvas_id.0 += 1; let webrender_api = self.webrender_api_sender.clone(); - let sender = CanvasPaintThread::start(*size, webrender_api, - opts::get().enable_canvas_antialiasing, self.canvas_id.clone()); - if let Err(e) = response_sender.send((sender, self.canvas_id.clone())) { + let sender = self.canvas_chan.clone(); + let (canvas_id_sender, canvas_id_receiver) = ipc::channel::<CanvasId>().expect("ipc channel failure"); + + if let Err(e) = sender.send( + CanvasMsg::Create( + canvas_id_sender, + *size, + webrender_api, + opts::get().enable_canvas_antialiasing + ) + ) { + return warn!("Create canvas paint thread failed ({})", e); + } + let canvas_id = match canvas_id_receiver.recv() { + Ok(canvas_id) => canvas_id, + Err(e) => return warn!("Create canvas paint thread id response failed ({})", e), + }; + if let Err(e) = response_sender.send((sender, canvas_id.clone())) { warn!("Create canvas paint thread response failed ({})", e); } } diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs index ee101251635..67cbceab896 100644 --- a/components/script/dom/canvasrenderingcontext2d.rs +++ b/components/script/dom/canvasrenderingcontext2d.rs @@ -371,20 +371,19 @@ impl CanvasRenderingContext2D { None => return Err(Error::InvalidState), }; - let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); - let msg = CanvasMsg::Canvas2d(Canvas2dMsg::DrawImageInOther( - self.ipc_renderer.clone(), - self.get_canvas_id(), - image_size, - dest_rect, - source_rect, - smoothing_enabled, - sender), - context.get_canvas_id()); + let msg = CanvasMsg::Canvas2d( + Canvas2dMsg::DrawImageInOther( + self.get_canvas_id(), + image_size, + dest_rect, + source_rect, + smoothing_enabled + ), + context.get_canvas_id() + ); let renderer = context.get_ipc_renderer(); renderer.send(msg).unwrap(); - receiver.recv().unwrap(); }; self.mark_as_dirty(); |