Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Canvas pixel manipulation #4639

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Implement Canvas pixel manipulation

  • Loading branch information
ebalint committed Feb 19, 2015
commit 7064b6d36515e4ea57d316149ca320c5fda46966
@@ -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};
use azure::azure_hl::{ColorPattern, PatternRef, 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 CanvasPaintTask {
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 CanvasPaintTask {
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.slice(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);
}
}
}
@@ -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,48 @@ 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::from_raw_buf;
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;
from_raw_buf(&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);
};
@@ -0,0 +1,23 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/.
*
* The origin of this IDL file is
* http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#imagedata
*
* © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and Opera Software ASA.
* You are granted a license to use, reproduce and create derivative works of this document.
*/

//[Constructor(unsigned long sw, unsigned long sh),
//Constructor(Uint8ClampedArray data, unsigned long sw, optional unsigned long sh),
//Exposed=(Window,Worker)]
interface ImageData {
//[Constant]
readonly attribute unsigned long width;
//[Constant]
readonly attribute unsigned long height;
//[Constant, StoreInSlot]
readonly attribute Uint8ClampedArray data;
};
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.