diff options
Diffstat (limited to 'components/canvas/canvas_data.rs')
-rw-r--r-- | components/canvas/canvas_data.rs | 1001 |
1 files changed, 1001 insertions, 0 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) + } +} |