/* 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::{DrawTarget, SurfaceFormat, BackendType, StrokeOptions, DrawOptions, Pattern}; use azure::azure_hl::{ColorPattern, PathBuilder, JoinStyle, CapStyle, DrawSurfaceOptions, Filter}; use geom::matrix2d::Matrix2D; use geom::point::Point2D; use geom::rect::Rect; use geom::size::Size2D; use gfx::color; use util::task::spawn_named; use util::vec::byte_swap; use cssparser::RGBA; use std::borrow::ToOwned; use std::ops::Add; use std::sync::mpsc::{channel, Sender}; #[derive(Clone)] pub enum CanvasMsg { FillRect(Rect), ClearRect(Rect), StrokeRect(Rect), BeginPath, ClosePath, Fill, MoveTo(Point2D), LineTo(Point2D), BezierCurveTo(Point2D, Point2D, Point2D), Arc(Point2D, f32, f32, f32, bool), SetFillStyle(FillOrStrokeStyle), SetStrokeStyle(FillOrStrokeStyle), SetTransform(Matrix2D), Recreate(Size2D), SendPixelContents(Sender>), GetImageData(Rect, Size2D, Sender>), PutImageData(Vec, Rect, Option>, Size2D), Close, } pub struct CanvasPaintTask<'a> { drawtarget: DrawTarget, fill_style: Pattern, stroke_style: Pattern, stroke_opts: StrokeOptions<'a>, /// TODO(pcwalton): Support multiple paths. path_builder: PathBuilder, /// The current 2D transform matrix. transform: Matrix2D, } impl<'a> CanvasPaintTask<'a> { fn new(size: Size2D) -> CanvasPaintTask<'a> { let draw_target = CanvasPaintTask::create(size); let path_builder = draw_target.create_path_builder(); CanvasPaintTask { drawtarget: draw_target, 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, 1.0, &[]), path_builder: path_builder, transform: Matrix2D::identity(), } } pub fn start(size: Size2D) -> Sender { let (chan, port) = channel::(); spawn_named("CanvasTask".to_owned(), move || { let mut painter = CanvasPaintTask::new(size); loop { match port.recv().unwrap() { CanvasMsg::FillRect(ref rect) => painter.fill_rect(rect), CanvasMsg::StrokeRect(ref rect) => painter.stroke_rect(rect), CanvasMsg::ClearRect(ref rect) => painter.clear_rect(rect), CanvasMsg::BeginPath => painter.begin_path(), CanvasMsg::ClosePath => painter.close_path(), CanvasMsg::Fill => painter.fill(), CanvasMsg::MoveTo(ref point) => painter.move_to(point), CanvasMsg::LineTo(ref point) => painter.line_to(point), CanvasMsg::BezierCurveTo(ref cp1, ref cp2, ref pt) => { painter.bezier_curve_to(cp1, cp2, pt) } CanvasMsg::Arc(ref center, radius, start, end, ccw) => { painter.arc(center, radius, start, end, ccw) } CanvasMsg::SetFillStyle(style) => painter.set_fill_style(style), CanvasMsg::SetStrokeStyle(style) => painter.set_stroke_style(style), CanvasMsg::SetTransform(ref matrix) => painter.set_transform(matrix), CanvasMsg::Recreate(size) => painter.recreate(size), CanvasMsg::SendPixelContents(chan) => painter.send_pixel_contents(chan), CanvasMsg::GetImageData(dest_rect, canvas_size, chan) => painter.get_image_data(dest_rect, canvas_size, chan), CanvasMsg::PutImageData(imagedata, image_data_rect, dirty_rect, canvas_size) => painter.put_image_data(imagedata, image_data_rect, dirty_rect, canvas_size), CanvasMsg::Close => break, } } }); chan } fn fill_rect(&self, rect: &Rect) { let drawopts = DrawOptions::new(1.0, 0); self.drawtarget.fill_rect(rect, self.fill_style.to_pattern_ref(), Some(&drawopts)); } fn clear_rect(&self, rect: &Rect) { self.drawtarget.clear_rect(rect); } fn stroke_rect(&self, rect: &Rect) { let drawopts = DrawOptions::new(1.0, 0); match self.stroke_style { Pattern::Color(ref color) => { self.drawtarget.stroke_rect(rect, color, &self.stroke_opts, &drawopts) } _ => { // TODO(pcwalton) } }; } fn begin_path(&mut self) { self.path_builder = self.drawtarget.create_path_builder() } fn close_path(&self) { self.path_builder.close() } fn fill(&self) { let draw_options = DrawOptions::new(1.0, 0); match self.fill_style { Pattern::Color(ref color) => { self.drawtarget.fill(&self.path_builder.finish(), color, &draw_options); } _ => { // TODO(pcwalton) } }; } fn move_to(&self, point: &Point2D) { self.path_builder.move_to(*point) } fn line_to(&self, point: &Point2D) { self.path_builder.line_to(*point) } fn bezier_curve_to(&self, cp1: &Point2D, cp2: &Point2D, endpoint: &Point2D) { self.path_builder.bezier_curve_to(cp1, cp2, endpoint) } fn arc(&self, center: &Point2D, radius: AzFloat, start_angle: AzFloat, end_angle: AzFloat, ccw: bool) { self.path_builder.arc(*center, radius, start_angle, end_angle, ccw) } fn set_fill_style(&mut self, style: FillOrStrokeStyle) { self.fill_style = style.to_azure_pattern() } fn set_stroke_style(&mut self, style: FillOrStrokeStyle) { self.stroke_style = style.to_azure_pattern() } fn set_transform(&mut self, transform: &Matrix2D) { self.transform = *transform; self.drawtarget.set_transform(transform) } fn create(size: Size2D) -> DrawTarget { DrawTarget::new(BackendType::Skia, size, SurfaceFormat::B8G8R8A8) } fn recreate(&mut self, size: Size2D) { self.drawtarget = CanvasPaintTask::create(size); } fn send_pixel_contents(&mut self, chan: Sender>) { self.drawtarget.snapshot().get_data_surface().with_data(|element| { chan.send(element.to_vec()).unwrap(); }) } fn get_image_data(&self, mut dest_rect: Rect, canvas_size: Size2D, chan: Sender>) { if dest_rect.size.width < 0 { dest_rect.size.width = -dest_rect.size.width; dest_rect.origin.x -= dest_rect.size.width; } if dest_rect.size.height < 0 { dest_rect.size.height = -dest_rect.size.height; dest_rect.origin.y -= dest_rect.size.height; } if dest_rect.size.width == 0 { dest_rect.size.width = 1; } if dest_rect.size.height == 0 { dest_rect.size.height = 1; } let canvas_rect = Rect(Point2D(0i32, 0i32), canvas_size); let src_read_rect = canvas_rect.intersection(&dest_rect).unwrap_or(Rect::zero()); let mut dest_data = Vec::new(); //load the canvas data to the source vector if !src_read_rect.is_empty() && canvas_size.width != 0 && canvas_size.height != 0 { 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 range(0, src_read_rect.size.height) { let row = &src_data[src .. src + (4 * src_read_rect.size.width) as usize]; dest_data.push_all(row); src += stride as usize; } } // bgra -> rgba byte_swap(dest_data.as_mut_slice()); chan.send(dest_data).unwrap(); } fn put_image_data(&mut self, mut imagedata: Vec, image_data_rect: Rect, dirty_rect: Option>, canvas_size: Size2D) { if image_data_rect.size.width <= 0 || image_data_rect.size.height <= 0 { return } assert!(image_data_rect.size.width * image_data_rect.size.height * 4 == imagedata.len() as i32); // rgba -> bgra byte_swap(imagedata.as_mut_slice()); let new_image_data_rect = Rect(Point2D(0i32, 0i32), Size2D(image_data_rect.size.width, image_data_rect.size.height)); let new_dirty_rect = match dirty_rect { Some(mut dirty_rect) => { if dirty_rect.size.width < 0 { dirty_rect.origin.x = dirty_rect.origin.x + dirty_rect.size.width; dirty_rect.size.width = -dirty_rect.size.width; } if dirty_rect.size.height < 0 { dirty_rect.origin.y = dirty_rect.origin.y + dirty_rect.size.height; dirty_rect.size.height = -dirty_rect.size.height; } new_image_data_rect.intersection(&dirty_rect) }, None => Some(new_image_data_rect) }; if let Some(new_dirty_rect) = new_dirty_rect { let moved_dirty_rect = Rect(new_dirty_rect.origin.add(image_data_rect.origin), new_dirty_rect.size).intersection(&Rect(Point2D(0i32, 0i32), canvas_size)).unwrap_or(Rect::zero()); if moved_dirty_rect.is_empty() { return } let source_surface = self.drawtarget.create_source_surface_from_data(imagedata.as_slice(), image_data_rect.size, image_data_rect.size.width * 4, SurfaceFormat::B8G8R8A8); let draw_surface_options = DrawSurfaceOptions::new(Filter::Linear, true); let draw_options = DrawOptions::new(1.0f64 as AzFloat, 0); self.drawtarget.draw_surface(source_surface, Rect(Point2D(moved_dirty_rect.origin.x as AzFloat, moved_dirty_rect.origin.y as AzFloat), Size2D(moved_dirty_rect.size.width as AzFloat, moved_dirty_rect.size.height as AzFloat)), Rect(Point2D((moved_dirty_rect.origin.x - image_data_rect.origin.x) as AzFloat, (moved_dirty_rect.origin.y - image_data_rect.origin.y) as AzFloat), Size2D(moved_dirty_rect.size.width as AzFloat, moved_dirty_rect.size.height as AzFloat)), draw_surface_options, draw_options); } } } #[derive(Clone)] pub enum FillOrStrokeStyle { Color(RGBA), } impl FillOrStrokeStyle { fn to_azure_pattern(&self) -> Pattern { match *self { FillOrStrokeStyle::Color(ref color) => { Pattern::Color(ColorPattern::new(color::new(color.red, color.green, color.blue, color.alpha))) } } } }