aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/canvas/canvas_data.rs1001
-rw-r--r--components/canvas/canvas_paint_thread.rs1323
-rw-r--r--components/canvas/lib.rs1
-rw-r--r--components/canvas_traits/canvas.rs5
-rw-r--r--components/constellation/constellation.rs28
-rw-r--r--components/script/dom/canvasrenderingcontext2d.rs21
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();