Skip to content
Permalink
Browse files

Implement Canvas pixel manipulation

  • Loading branch information...
ebalint authored and jdm committed Jan 15, 2015
1 parent e68d6d2 commit 325400dce41486ca9bdb4fa8ea07236520e86a50
@@ -3,13 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use azure::azure_hl::{DrawTarget, SurfaceFormat, BackendType, StrokeOptions, DrawOptions};
use azure::azure_hl::{ColorPattern, PatternRef, JoinStyle, CapStyle};
use azure::azure_hl::{ColorPattern, PatternRef, JoinStyle, CapStyle, DrawSurfaceOptions, Filter};
use azure::AzFloat;
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 std::borrow::ToOwned;
use std::ops::Add;
use std::sync::mpsc::{channel, Sender};

#[derive(Clone)]
@@ -19,6 +23,8 @@ pub enum CanvasMsg {
StrokeRect(Rect<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,
}

@@ -51,6 +57,9 @@ impl<'a> CanvasPaintTask<'a> {
CanvasMsg::ClearRect(ref rect) => painter.clear_rect(rect),
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,
}
}
@@ -85,4 +94,101 @@ impl<'a> CanvasPaintTask<'a> {
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);
}
}
}
@@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#![feature(core)]
#![feature(collections)]

#![allow(missing_copy_implementations)]

@@ -5,6 +5,7 @@
use std::iter::range_step;
use stb_image::image as stb_image;
use png;
use util::vec::byte_swap;

// FIXME: Images must not be copied every frame. Instead we should atomically
// reference count them.
@@ -17,16 +18,6 @@ pub fn test_image_bin() -> Vec<u8> {
TEST_IMAGE.iter().map(|&x| x).collect()
}

// TODO(pcwalton): Speed up with SIMD, or better yet, find some way to not do this.
fn byte_swap(data: &mut [u8]) {
let length = data.len();
for i in range_step(0, length, 4) {
let r = data[i + 2];
data[i + 2] = data[i + 0];
data[i + 0] = r;
}
}

// TODO(pcwalton): Speed up with SIMD, or better yet, find some way to not do this.
fn byte_swap_and_premultiply(data: &mut [u8]) {
let length = data.len();
@@ -4,19 +4,24 @@

use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding;
use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasRenderingContext2DMethods;
use dom::bindings::codegen::Bindings::ImageDataBinding::ImageDataMethods;
use dom::bindings::error::Error::IndexSize;
use dom::bindings::error::Fallible;
use dom::bindings::global::{GlobalRef, GlobalField};
use dom::bindings::js::{JS, JSRef, LayoutJS, Temporary};
use dom::bindings::utils::{Reflector, reflect_dom_object};
use dom::htmlcanvaselement::HTMLCanvasElement;
use dom::htmlcanvaselement::{HTMLCanvasElement, HTMLCanvasElementHelpers};
use dom::imagedata::{ImageData, ImageDataHelpers};

use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;

use canvas::canvas_paint_task::{CanvasMsg, CanvasPaintTask};
use canvas::canvas_paint_task::CanvasMsg::{ClearRect, Close, FillRect, Recreate, StrokeRect};
use canvas::canvas_paint_task::CanvasMsg::{ClearRect, Close, FillRect, Recreate, StrokeRect, GetImageData, PutImageData};

use std::sync::mpsc::Sender;
use std::num::{Float, ToPrimitive};
use std::sync::mpsc::{channel, Sender};

#[dom_struct]
pub struct CanvasRenderingContext2D {
@@ -75,6 +80,52 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D>
let rect = Rect(Point2D(x as f32, y as f32), Size2D(width as f32, height as f32));
self.renderer.send(StrokeRect(rect)).unwrap();
}

fn CreateImageData(self, sw: f64, sh: f64) -> Fallible<Temporary<ImageData>> {
if sw == 0.0 || sh == 0.0 {
return Err(IndexSize)
}

Ok(ImageData::new(self.global.root().r(), sw.abs().to_u32().unwrap(), sh.abs().to_u32().unwrap(), None))
}

fn CreateImageData_(self, imagedata: JSRef<ImageData>) -> Fallible<Temporary<ImageData>> {
Ok(ImageData::new(self.global.root().r(), imagedata.Width(), imagedata.Height(), None))
}

fn GetImageData(self, sx: f64, sy: f64, sw: f64, sh: f64) -> Fallible<Temporary<ImageData>> {
if sw == 0.0 || sh == 0.0 {
return Err(IndexSize)
}

let (sender, receiver) = channel::<Vec<u8>>();
let dest_rect = Rect(Point2D(sx.to_i32().unwrap(), sy.to_i32().unwrap()), Size2D(sw.to_i32().unwrap(), sh.to_i32().unwrap()));
let canvas_size = self.canvas.root().r().get_size();
self.renderer.send(GetImageData(dest_rect, canvas_size, sender)).unwrap();
let data = receiver.recv().unwrap();
Ok(ImageData::new(self.global.root().r(), sw.abs().to_u32().unwrap(), sh.abs().to_u32().unwrap(), Some(data)))
}

fn PutImageData(self, imagedata: JSRef<ImageData>, dx: f64, dy: f64) {
let data = imagedata.get_data_array(&self.global.root().r());
let image_data_rect = Rect(Point2D(dx.to_i32().unwrap(), dy.to_i32().unwrap()), imagedata.get_size());
let dirty_rect = None;
let canvas_size = self.canvas.root().r().get_size();
self.renderer.send(PutImageData(data, image_data_rect, dirty_rect, canvas_size)).unwrap()
}

fn PutImageData_(self, imagedata: JSRef<ImageData>, dx: f64, dy: f64,
dirtyX: f64, dirtyY: f64, dirtyWidth: f64, dirtyHeight: f64) {
let data = imagedata.get_data_array(&self.global.root().r());
let image_data_rect = Rect(Point2D(dx.to_i32().unwrap(), dy.to_i32().unwrap()),
Size2D(imagedata.Width().to_i32().unwrap(),
imagedata.Height().to_i32().unwrap()));
let dirty_rect = Some(Rect(Point2D(dirtyX.to_i32().unwrap(), dirtyY.to_i32().unwrap()),
Size2D(dirtyWidth.to_i32().unwrap(),
dirtyHeight.to_i32().unwrap())));
let canvas_size = self.canvas.root().r().get_size();
self.renderer.send(PutImageData(data, image_data_rect, dirty_rect, canvas_size)).unwrap()
}
}

#[unsafe_destructor]
@@ -83,6 +83,16 @@ impl LayoutHTMLCanvasElementHelpers for LayoutJS<HTMLCanvasElement> {
}
}

pub trait HTMLCanvasElementHelpers {
fn get_size(&self) -> Size2D<i32>;
}

impl<'a> HTMLCanvasElementHelpers for JSRef<'a, HTMLCanvasElement> {
fn get_size(&self) -> Size2D<i32> {
Size2D(self.Width() as i32, self.Height() as i32)
}
}

impl<'a> HTMLCanvasElementMethods for JSRef<'a, HTMLCanvasElement> {
fn Width(self) -> u32 {
self.width.get()
@@ -0,0 +1,87 @@
/* 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 dom::bindings::codegen::Bindings::ImageDataBinding;
use dom::bindings::codegen::Bindings::ImageDataBinding::ImageDataMethods;
use dom::bindings::global::GlobalRef;
use dom::bindings::js::{JSRef, Temporary};
use dom::bindings::utils::{Reflector, reflect_dom_object};
use geom::size::Size2D;
use js::jsapi::{JSContext, JSObject};
use js::jsfriendapi::bindgen::{JS_NewUint8ClampedArray, JS_GetUint8ClampedArrayData};
use libc::uint8_t;
use std::vec::Vec;
use collections::slice;
use std::ptr;

#[dom_struct]
#[allow(raw_pointer_derive)]
pub struct ImageData {
reflector_: Reflector,
width: u32,
height: u32,
data: *mut JSObject,
}

impl ImageData {
#[allow(unsafe_blocks)]
fn new_inherited(width: u32, height: u32, data: Option<Vec<u8>>, global: GlobalRef) -> ImageData {
unsafe {
let cx = global.get_cx();
let js_object: *mut JSObject = JS_NewUint8ClampedArray(cx, width * height * 4);

if let Some(vec) = data {
let js_object_data: *mut uint8_t = JS_GetUint8ClampedArrayData(js_object, cx);
ptr::copy_nonoverlapping_memory(js_object_data, vec.as_ptr(), vec.len())
}

ImageData {
reflector_: Reflector::new(),
width: width,
height: height,
data: js_object,
}
}
}

pub fn new(global: GlobalRef, width: u32, height: u32, data: Option<Vec<u8>>) -> Temporary<ImageData> {
reflect_dom_object(box ImageData::new_inherited(width, height, data, global),
global, ImageDataBinding::Wrap)
}
}

pub trait ImageDataHelpers {
fn get_data_array(self, global: &GlobalRef) -> Vec<u8>;
fn get_size(&self) -> Size2D<i32>;
}

impl<'a> ImageDataHelpers for JSRef<'a, ImageData> {
#[allow(unsafe_blocks)]
fn get_data_array(self, global: &GlobalRef) -> Vec<u8> {
unsafe {
let cx = global.get_cx();
let data: *const uint8_t = JS_GetUint8ClampedArrayData(self.Data(cx), cx) as *const uint8_t;
let len = self.Width() * self.Height() * 4;
slice::from_raw_parts(data, len as uint).to_vec()
}
}

fn get_size(&self) -> Size2D<i32> {
Size2D(self.Width() as i32, self.Height() as i32)
}
}

impl<'a> ImageDataMethods for JSRef<'a, ImageData> {
fn Width(self) -> u32 {
self.width
}

fn Height(self) -> u32 {
self.height
}

fn Data(self, _: *mut JSContext) -> *mut JSObject {
self.data
}
}
@@ -290,6 +290,7 @@ pub mod htmltrackelement;
pub mod htmlulistelement;
pub mod htmlvideoelement;
pub mod htmlunknownelement;
pub mod imagedata;
pub mod keyboardevent;
pub mod location;
pub mod messageevent;
@@ -96,9 +96,12 @@ interface CanvasRenderingContext2D {
//void removeHitRegion(DOMString id);

// pixel manipulation
//ImageData createImageData(double sw, double sh);
//ImageData createImageData(ImageData imagedata);
//ImageData getImageData(double sx, double sy, double sw, double sh);
//void putImageData(ImageData imagedata, double dx, double dy);
//void putImageData(ImageData imagedata, double dx, double dy, double dirtyX, double dirtyY, double dirtyWidth, double dirtyHeight);
[Throws]
ImageData createImageData(double sw, double sh);
[Throws]
ImageData createImageData(ImageData imagedata);
[Throws]
ImageData getImageData(double sx, double sy, double sw, double sh);
void putImageData(ImageData imagedata, double dx, double dy);
void putImageData(ImageData imagedata, double dx, double dy, double dirtyX, double dirtyY, double dirtyWidth, double dirtyHeight);
};
Oops, something went wrong.

5 comments on commit 325400d

@bors-servo

This comment has been minimized.

Copy link
Contributor

bors-servo replied Feb 22, 2015

saw approval from jdm
at jdm@325400d

@bors-servo

This comment has been minimized.

Copy link
Contributor

bors-servo replied Feb 22, 2015

merging jdm/servo/canvas = 325400d into auto

@bors-servo

This comment has been minimized.

Copy link
Contributor

bors-servo replied Feb 22, 2015

jdm/servo/canvas = 325400d merged ok, testing candidate = 287f390

@bors-servo

This comment has been minimized.

Copy link
Contributor

bors-servo replied Feb 22, 2015

fast-forwarding master to auto = 287f390

Please sign in to comment.
You can’t perform that action at this time.