servo/components/canvas/canvas_paint_task.rs
2015-03-09 21:55:40 +01:00

311 lines
12 KiB
Rust

/* 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<f32>),
ClearRect(Rect<f32>),
StrokeRect(Rect<f32>),
BeginPath,
ClosePath,
Fill,
MoveTo(Point2D<f32>),
LineTo(Point2D<f32>),
BezierCurveTo(Point2D<f32>, Point2D<f32>, Point2D<f32>),
Arc(Point2D<f32>, f32, f32, f32, bool),
SetFillStyle(FillOrStrokeStyle),
SetStrokeStyle(FillOrStrokeStyle),
SetTransform(Matrix2D<f32>),
Recreate(Size2D<i32>),
SendPixelContents(Sender<Vec<u8>>),
GetImageData(Rect<i32>, Size2D<i32>, Sender<Vec<u8>>),
PutImageData(Vec<u8>, Rect<i32>, Option<Rect<i32>>, Size2D<i32>),
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<f32>,
}
impl<'a> CanvasPaintTask<'a> {
fn new(size: Size2D<i32>) -> 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<i32>) -> Sender<CanvasMsg> {
let (chan, port) = channel::<CanvasMsg>();
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<f32>) {
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<f32>) {
self.drawtarget.clear_rect(rect);
}
fn stroke_rect(&self, rect: &Rect<f32>) {
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<AzFloat>) {
self.path_builder.move_to(*point)
}
fn line_to(&self, point: &Point2D<AzFloat>) {
self.path_builder.line_to(*point)
}
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 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<f32>) {
self.transform = *transform;
self.drawtarget.set_transform(transform)
}
fn create(size: Size2D<i32>) -> DrawTarget {
DrawTarget::new(BackendType::Skia, size, SurfaceFormat::B8G8R8A8)
}
fn recreate(&mut self, size: Size2D<i32>) {
self.drawtarget = CanvasPaintTask::create(size);
}
fn send_pixel_contents(&mut self, chan: Sender<Vec<u8>>) {
self.drawtarget.snapshot().get_data_surface().with_data(|element| {
chan.send(element.to_vec()).unwrap();
})
}
fn get_image_data(&self, mut dest_rect: Rect<i32>, canvas_size: Size2D<i32>, chan: Sender<Vec<u8>>) {
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<u8>, image_data_rect: Rect<i32>,
dirty_rect: Option<Rect<i32>>, canvas_size: Size2D<i32>) {
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)))
}
}
}
}