servo: Merge #5433 - Implements drawImage for html image as ImageSource (from dmarcos:issue5290); r=jdm

Source-Repo: https://github.com/servo/servo
Source-Revision: 58637a1174f94cb1ebbb394d3ba3c8c8f2d70639

--HG--
rename : servo/tests/html/test_canvas_drawimage.html => servo/tests/html/test_canvas_drawimage_canvas.html
This commit is contained in:
Diego Marcos 2015-04-07 18:38:34 -05:00
Родитель 3dea48290b
Коммит 5cfb1364bd
12 изменённых файлов: 524 добавлений и 133 удалений

Просмотреть файл

@ -16,7 +16,7 @@ use util::vec::byte_swap;
use cssparser::RGBA;
use std::borrow::ToOwned;
use std::num::Float;
use std::num::{Float, ToPrimitive};
use std::sync::mpsc::{channel, Sender};
#[derive(Clone)]
@ -28,8 +28,8 @@ pub enum CanvasMsg {
ClosePath,
Fill,
Stroke,
DrawImage(Vec<u8>, Rect<i32>, Rect<i32>, bool),
DrawImageSelf(Size2D<i32>, Rect<i32>, Rect<i32>, bool),
DrawImage(Vec<u8>, Size2D<f64>, Rect<f64>, Rect<f64>, bool),
DrawImageSelf(Size2D<f64>, Rect<f64>, Rect<f64>, bool),
MoveTo(Point2D<f32>),
LineTo(Point2D<f32>),
QuadraticCurveTo(Point2D<f32>, Point2D<f32>),
@ -41,8 +41,8 @@ pub enum CanvasMsg {
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>>),
GetImageData(Rect<f64>, Size2D<f64>, Sender<Vec<u8>>),
PutImageData(Vec<u8>, Rect<f64>, Option<Rect<f64>>),
Close,
}
@ -50,7 +50,9 @@ impl<'a> CanvasPaintTask<'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<i32>) -> Vec<u8>{
fn read_pixels(&self, read_rect: Rect<f64>, canvas_size: Size2D<f64>) -> Vec<u8>{
let read_rect = read_rect.to_i32();
let canvas_size = canvas_size.to_i32();
let canvas_rect = Rect(Point2D(0i32, 0i32), canvas_size);
let src_read_rect = canvas_rect.intersection(&read_rect).unwrap_or(Rect::zero());
@ -81,9 +83,9 @@ impl<'a> CanvasPaintTask<'a> {
/// dest_rect: The area of the canvas where the imagedata will be copied
/// smoothing_enabled: if smoothing is applied to the copied pixels
fn write_pixels(&self, imagedata: &[u8],
image_size: Size2D<i32>,
source_rect: Rect<i32>,
dest_rect: Rect<i32>,
image_size: Size2D<f64>,
source_rect: Rect<f64>,
dest_rect: Rect<f64>,
smoothing_enabled: bool) {
// From spec https://html.spec.whatwg.org/multipage/scripting.html#dom-context-2d-drawimage
// When scaling up, if the imageSmoothingEnabled attribute is set to true, the user agent should attempt
@ -94,8 +96,11 @@ impl<'a> CanvasPaintTask<'a> {
} else {
Filter::Point
};
// azure_hl operates with integers. We need to cast the image size
let image_size = image_size.to_i32();
let source_surface = self.drawtarget.create_source_surface_from_data(imagedata,
let source_surface = self.drawtarget.create_source_surface_from_data(
&imagedata,
image_size, image_size.width * 4, SurfaceFormat::B8G8R8A8);
let draw_surface_options = DrawSurfaceOptions::new(filter, true);
@ -112,33 +117,33 @@ impl<'a> CanvasPaintTask<'a> {
/// Result: It retuns the modified dirty_rect by the rules described in
/// the spec https://html.spec.whatwg.org/#dom-context-2d-putimagedata
fn calculate_dirty_rect(&self,
mut dirty_rect: Rect<i32>,
image_data_rect: Rect<i32>) -> Rect<i32>{
mut dirty_rect: Rect<f64>,
image_data_rect: Rect<f64>) -> Rect<f64>{
// 1) If dirtyWidth is negative,
// let dirtyX be dirtyX+dirtyWidth,
// and let dirtyWidth be equal to the absolute magnitude of dirtyWidth.
if dirty_rect.size.width < 0 {
if dirty_rect.size.width < 0.0f64 {
dirty_rect.origin.x = dirty_rect.origin.x + dirty_rect.size.width;
dirty_rect.size.width = -dirty_rect.size.width;
}
// 2) If dirtyHeight is negative, let dirtyY be dirtyY+dirtyHeight,
// and let dirtyHeight be equal to the absolute magnitude of dirtyHeight.
if dirty_rect.size.height < 0 {
if dirty_rect.size.height < 0.0f64 {
dirty_rect.origin.y = dirty_rect.origin.y + dirty_rect.size.height;
dirty_rect.size.height = -dirty_rect.size.height;
}
// 3) If dirtyX is negative, let dirtyWidth be dirtyWidth+dirtyX, and let dirtyX be zero.
if dirty_rect.origin.x < 0 {
if dirty_rect.origin.x < 0.0f64 {
dirty_rect.size.width += dirty_rect.origin.x;
dirty_rect.origin.x = 0;
dirty_rect.origin.x = 0.0f64;
}
// 3) If dirtyY is negative, let dirtyHeight be dirtyHeight+dirtyY, and let dirtyY be zero.
if dirty_rect.origin.y < 0 {
if dirty_rect.origin.y < 0.0f64 {
dirty_rect.size.height += dirty_rect.origin.y;
dirty_rect.origin.y = 0;
dirty_rect.origin.y = 0.0f64;
}
// 4) If dirtyX+dirtyWidth is greater than the width attribute of the imagedata argument,
@ -157,21 +162,19 @@ impl<'a> CanvasPaintTask<'a> {
}
/// It writes an image to the destination canvas
/// imagedata: Pixel information of the image to be written
/// source_rect: Area of the source image to be copied
/// imagedata: 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 canvas where the pixels will be copied
/// smoothing_enabled: It determines if smoothing is applied to the image result
fn write_image(&self, mut imagedata: Vec<u8>,
source_rect: Rect<i32>, dest_rect: Rect<i32>, smoothing_enabled: bool) {
image_size: Size2D<f64>, dest_rect: Rect<f64>, smoothing_enabled: bool) {
if imagedata.len() == 0 {
return
}
// Image data already contains the portion of the image we want to draw
// so the source rect corresponds to the whole area of the copied imagedata
let source_rect = Rect(Point2D(0i32, 0i32), source_rect.size);
let image_rect = Rect(Point2D(0f64, 0f64), image_size);
// rgba -> bgra
byte_swap(imagedata.as_mut_slice());
self.write_pixels(&imagedata, source_rect.size, source_rect, dest_rect, smoothing_enabled);
self.write_pixels(&imagedata, image_size, image_rect, dest_rect, smoothing_enabled);
}
}
@ -215,8 +218,8 @@ impl<'a> CanvasPaintTask<'a> {
CanvasMsg::ClosePath => painter.close_path(),
CanvasMsg::Fill => painter.fill(),
CanvasMsg::Stroke => painter.stroke(),
CanvasMsg::DrawImage(imagedata, dest_rect, source_rect, smoothing_enabled) => {
painter.draw_image(imagedata, dest_rect, source_rect, smoothing_enabled)
CanvasMsg::DrawImage(imagedata, image_size, dest_rect, source_rect, smoothing_enabled) => {
painter.draw_image(imagedata, image_size, dest_rect, source_rect, smoothing_enabled)
}
CanvasMsg::DrawImageSelf(image_size, dest_rect, source_rect, smoothing_enabled) => {
painter.draw_image_self(image_size, dest_rect, source_rect, smoothing_enabled)
@ -304,19 +307,23 @@ impl<'a> CanvasPaintTask<'a> {
};
}
fn draw_image(&self, imagedata: Vec<u8>, dest_rect: Rect<i32>,
source_rect: Rect<i32>, smoothing_enabled: bool) {
self.write_image(imagedata, source_rect, dest_rect, smoothing_enabled);
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);
self.write_image(image_data, source_rect.size, dest_rect, smoothing_enabled);
}
fn draw_image_self(&self, image_size: Size2D<i32>,
dest_rect: Rect<i32>, source_rect: Rect<i32>,
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 imagedata = self.read_pixels(source_rect, image_size);
// Writes on target canvas
self.write_image(imagedata, source_rect, dest_rect, smoothing_enabled);
self.write_image(imagedata, image_size, dest_rect, smoothing_enabled);
}
fn move_to(&self, point: &Point2D<AzFloat>) {
@ -431,20 +438,20 @@ impl<'a> CanvasPaintTask<'a> {
})
}
fn get_image_data(&self, mut dest_rect: Rect<i32>, canvas_size: Size2D<i32>, chan: Sender<Vec<u8>>) {
if dest_rect.size.width < 0 {
fn get_image_data(&self, mut dest_rect: Rect<f64>, canvas_size: Size2D<f64>, chan: Sender<Vec<u8>>) {
if dest_rect.size.width < 0.0 {
dest_rect.size.width = -dest_rect.size.width;
dest_rect.origin.x -= dest_rect.size.width;
}
if dest_rect.size.height < 0 {
if dest_rect.size.height < 0.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.width == 0.0 {
dest_rect.size.width = 1.0;
}
if dest_rect.size.height == 0 {
dest_rect.size.height = 1;
if dest_rect.size.height == 0.0 {
dest_rect.size.height = 1.0;
}
let mut dest_data = self.read_pixels(dest_rect, canvas_size);
@ -455,18 +462,18 @@ impl<'a> CanvasPaintTask<'a> {
}
fn put_image_data(&mut self, mut imagedata: Vec<u8>,
image_data_rect: Rect<i32>,
dirty_rect: Option<Rect<i32>>) {
image_data_rect: Rect<f64>,
dirty_rect: Option<Rect<f64>>) {
if image_data_rect.size.width <= 0 || image_data_rect.size.height <= 0 {
if image_data_rect.size.width <= 0.0 || image_data_rect.size.height <= 0.0 {
return
}
assert!(image_data_rect.size.width * image_data_rect.size.height * 4 == imagedata.len() as i32);
assert!(image_data_rect.size.width * image_data_rect.size.height * 4.0 == imagedata.len() as f64);
// rgba -> bgra
byte_swap(imagedata.as_mut_slice());
let image_rect = Rect(Point2D(0i32, 0i32),
let image_rect = Rect(Point2D(0f64, 0f64),
Size2D(image_data_rect.size.width, image_data_rect.size.height));
// Dirty rectangle defines the area of the source image to be copied
@ -481,7 +488,7 @@ impl<'a> CanvasPaintTask<'a> {
// 5) If either dirtyWidth or dirtyHeight is negative or zero,
// stop without affecting any bitmaps
if source_rect.size.width <= 0 || source_rect.size.height <= 0 {
if source_rect.size.width <= 0.0 || source_rect.size.height <= 0.0 {
return
}
@ -603,11 +610,75 @@ impl FillOrStrokeStyle {
}
}
/// 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.height * 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.push_all(row);
src += stride as usize;
}
new_image_data
}
pub trait SizeToi32 {
fn to_i32(&self) -> Size2D<i32>;
}
impl SizeToi32 for Size2D<f64> {
fn to_i32(&self) -> Size2D<i32> {
Size2D(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(Point2D(self.origin.x.to_i32().unwrap(),
self.origin.y.to_i32().unwrap()),
Size2D(self.size.width.to_i32().unwrap(),
self.size.height.to_i32().unwrap()))
}
fn ceil(&self) -> Rect<f64> {
Rect(Point2D(self.origin.x.ceil(),
self.origin.y.ceil()),
Size2D(self.size.width.ceil(),
self.size.height.ceil()))
}
}
pub trait ToAzFloat {
fn to_azfloat(&self) -> Rect<AzFloat>;
}
impl ToAzFloat for Rect<i32> {
impl ToAzFloat for Rect<f64> {
fn to_azfloat(&self) -> Rect<AzFloat> {
Rect(Point2D(self.origin.x as AzFloat, self.origin.y as AzFloat),
Size2D(self.size.width as AzFloat, self.size.height as AzFloat))

Просмотреть файл

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#![feature(collections)]
#![feature(core)]
#![feature(std_misc)]
extern crate azure;

Просмотреть файл

@ -57,6 +57,9 @@ git = "https://github.com/servo/html5ever"
[dependencies.js]
git = "https://github.com/servo/rust-mozjs"
[dependencies.png]
git = "https://github.com/servo/rust-png"
[dependencies.uuid]
git = "https://github.com/rust-lang/uuid"

Просмотреть файл

@ -6,9 +6,9 @@ use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding;
use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasRenderingContext2DMethods;
use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasWindingRule;
use dom::bindings::codegen::Bindings::ImageDataBinding::ImageDataMethods;
use dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrCanvasRenderingContext2D;
use dom::bindings::codegen::UnionTypes::HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D;
use dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern;
use dom::bindings::error::Error::{IndexSize, NotSupported, Type};
use dom::bindings::error::Error::{IndexSize, NotSupported, Type, InvalidState};
use dom::bindings::error::Fallible;
use dom::bindings::global::{GlobalRef, GlobalField};
use dom::bindings::js::{JS, JSRef, LayoutJS, Temporary};
@ -16,7 +16,9 @@ use dom::bindings::num::Finite;
use dom::bindings::utils::{Reflector, reflect_dom_object};
use dom::canvasgradient::{CanvasGradient, CanvasGradientStyle, ToFillOrStrokeStyle};
use dom::htmlcanvaselement::{HTMLCanvasElement, HTMLCanvasElementHelpers};
use dom::htmlimageelement::{HTMLImageElement, HTMLImageElementHelpers};
use dom::imagedata::{ImageData, ImageDataHelpers};
use dom::node::{window_from_node};
use cssparser::Color as CSSColor;
use cssparser::{Parser, RGBA, ToCss};
@ -28,11 +30,19 @@ use geom::size::Size2D;
use canvas::canvas_paint_task::{CanvasMsg, CanvasPaintTask, FillOrStrokeStyle};
use canvas::canvas_paint_task::{LinearGradientStyle, RadialGradientStyle};
use net_traits::image::base::Image;
use net_traits::image_cache_task::{ImageResponseMsg, Msg};
use png::PixelsByColorType;
use std::borrow::ToOwned;
use std::cell::Cell;
use std::num::{Float, ToPrimitive};
use std::sync::{Arc};
use std::sync::mpsc::{channel, Sender};
use url::Url;
use util::vec::byte_swap;
#[dom_struct]
pub struct CanvasRenderingContext2D {
reflector_: Reflector,
@ -85,10 +95,9 @@ impl CanvasRenderingContext2D {
// source rectangle = area of the original image to be copied
// destination rectangle = area of the destination canvas where the source image is going to be drawn
fn adjust_source_dest_rects(&self,
canvas: JSRef<HTMLCanvasElement>,
image_size: Size2D<f64>,
sx: f64, sy: f64, sw: f64, sh: f64,
dx: f64, dy: f64, dw: f64, dh: f64) -> (Rect<i32>, Rect<i32>) {
let image_size = canvas.get_size();
dx: f64, dy: f64, dw: f64, dh: f64) -> (Rect<f64>, Rect<f64>) {
let image_rect = Rect(Point2D(0f64, 0f64),
Size2D(image_size.width as f64, image_size.height as f64));
@ -112,15 +121,13 @@ impl CanvasRenderingContext2D {
// The destination rectangle is the rectangle whose corners are the four points (dx, dy),
// (dx+dw, dy), (dx+dw, dy+dh), (dx, dy+dh).
let dest_rect = Rect(Point2D(dx.to_i32().unwrap(),
dy.to_i32().unwrap()),
Size2D(dest_rect_width_scaled.to_i32().unwrap(),
dest_rect_height_scaled.to_i32().unwrap()));
let dest_rect = Rect(Point2D(dx, dy),
Size2D(dest_rect_width_scaled, dest_rect_height_scaled));
let source_rect = Rect(Point2D(source_rect_clipped.origin.x.to_i32().unwrap(),
source_rect_clipped.origin.y.to_i32().unwrap()),
Size2D(source_rect_clipped.size.width.to_i32().unwrap(),
source_rect_clipped.size.height.to_i32().unwrap()));
let source_rect = Rect(Point2D(source_rect_clipped.origin.x,
source_rect_clipped.origin.y),
Size2D(source_rect_clipped.size.width,
source_rect_clipped.size.height));
return (source_rect, dest_rect)
}
@ -150,39 +157,103 @@ impl CanvasRenderingContext2D {
canvas: JSRef<HTMLCanvasElement>,
sx: f64, sy: f64, sw: f64, sh: f64,
dx: f64, dy: f64, dw: f64, dh: f64) -> Fallible<()> {
// 1. Check the usability of the image argument
if !canvas.is_valid() {
return Ok(())
return Err(InvalidState)
}
let canvas_size = canvas.get_size();
let image_size = Size2D(canvas_size.width as f64, canvas_size.height as f64);
// 2. Establish the source and destination rectangles
let (source_rect, dest_rect) = self.adjust_source_dest_rects(canvas, sx, sy, sw, sh, dx, dy, dw, dh);
let (source_rect, dest_rect) = self.adjust_source_dest_rects(image_size, sx, sy, sw, sh, dx, dy, dw, dh);
if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
return Err(IndexSize)
}
let smoothing_enabled = self.image_smoothing_enabled.get();
let canvas_size = canvas.get_size();
// If the source and target canvas are the same
let msg = if self.canvas.root().r() == canvas {
CanvasMsg::DrawImageSelf(canvas_size, dest_rect, source_rect, smoothing_enabled)
CanvasMsg::DrawImageSelf(image_size, dest_rect, source_rect, smoothing_enabled)
} else { // Source and target canvases are different
let context = canvas.get_2d_context().root();
let renderer = context.r().get_renderer();
let (sender, receiver) = channel::<Vec<u8>>();
// Reads pixels from source image
renderer.send(CanvasMsg::GetImageData(source_rect, canvas_size, sender)).unwrap();
renderer.send(CanvasMsg::GetImageData(source_rect, image_size, sender)).unwrap();
let imagedata = receiver.recv().unwrap();
// Writes pixels to destination canvas
CanvasMsg::DrawImage(imagedata, dest_rect, source_rect, smoothing_enabled)
CanvasMsg::DrawImage(imagedata, source_rect.size, dest_rect, source_rect, smoothing_enabled)
};
self.renderer.send(msg).unwrap();
Ok(())
}
fn draw_image_data(&self,
image_data: Vec<u8>,
image_size: Size2D<f64>,
sx: f64, sy: f64, sw: f64, sh: f64,
dx: f64, dy: f64, dw: f64, dh: f64) -> Fallible<()> {
// Establish the source and destination rectangles
let (source_rect, dest_rect) = self.adjust_source_dest_rects(image_size, sx, sy, sw, sh, dx, dy, dw, dh);
if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
return Err(IndexSize)
}
let smoothing_enabled = self.image_smoothing_enabled.get();
self.renderer.send(CanvasMsg::DrawImage(
image_data, image_size, dest_rect,
source_rect, smoothing_enabled)).unwrap();
Ok(())
}
fn fetch_image_data(&self,
image_element: &JSRef<HTMLImageElement>)
-> Option<(Vec<u8>, Size2D<f64>)> {
let url = match image_element.get_url() {
Some(url) => url,
None => return None,
};
let img = match self.request_image_from_cache(url) {
Some(img) => img,
None => return None,
};
let image_size = Size2D(img.width as f64, img.height as f64);
let mut image_data = match img.pixels {
PixelsByColorType::RGBA8(ref pixels) => pixels.to_vec(),
PixelsByColorType::K8(_) => panic!("K8 color type not supported"),
PixelsByColorType::RGB8(_) => panic!("RGB8 color type not supported"),
PixelsByColorType::KA8(_) => panic!("KA8 color type not supported"),
};
// Pixels come from cache in BGRA order and drawImage expects RGBA so we
// have to swap the color values
{
let mut pixel_colors = image_data.as_mut_slice();
byte_swap(pixel_colors);
}
return Some((image_data, image_size));
}
fn request_image_from_cache(&self, url: Url) -> Option<Arc<Box<Image>>> {
let canvas = self.canvas.root();
let window = window_from_node(canvas.r()).root();
let window = window.r();
let image_cache_task = window.image_cache_task().clone();
image_cache_task.send(Msg::Prefetch(url.clone()));
image_cache_task.send(Msg::Decode(url.clone()));
let (response_chan, response_port) = channel();
image_cache_task.send(Msg::WaitForImage(url, response_chan));
match response_port.recv().unwrap() {
ImageResponseMsg::ImageReady(image) => Some(image),
ImageResponseMsg::ImageFailed => None,
_ => panic!("Image Cache: Unknown Result")
}
}
}
pub trait CanvasRenderingContext2DHelpers {
@ -316,7 +387,7 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D>
}
// https://html.spec.whatwg.org/multipage/scripting.html#dom-context-2d-drawimage
fn DrawImage(self, image: HTMLCanvasElementOrCanvasRenderingContext2D,
fn DrawImage(self, image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D,
dx: f64, dy: f64) -> Fallible<()> {
if !(dx.is_finite() && dy.is_finite()) {
return Ok(());
@ -330,7 +401,7 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D>
let sy: f64 = 0f64;
match image {
HTMLCanvasElementOrCanvasRenderingContext2D::eHTMLCanvasElement(image) => {
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLCanvasElement(image) => {
let canvas = image.root();
let canvas_size = canvas.r().get_size();
let dw: f64 = canvas_size.width as f64;
@ -341,7 +412,7 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D>
sx, sy, sw, sh,
dx, dy, dw, dh)
}
HTMLCanvasElementOrCanvasRenderingContext2D::eCanvasRenderingContext2D(image) => {
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eCanvasRenderingContext2D(image) => {
let image = image.root();
let context = image.r();
let canvas = context.Canvas().root();
@ -354,11 +425,31 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D>
sx, sy, sw, sh,
dx, dy, dw, dh)
}
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLImageElement(image) => {
let image = image.root();
let image_element = image.r();
// https://html.spec.whatwg.org/multipage/embedded-content.html#img-error
// If the image argument is an HTMLImageElement object that is in the broken state,
// then throw an InvalidStateError exception
let (image_data, image_size) = match self.fetch_image_data(&image_element) {
Some((data, size)) => (data, size),
None => return Err(InvalidState),
};
let dw: f64 = image_size.width as f64;
let dh: f64 = image_size.height as f64;
let sw: f64 = dw;
let sh: f64 = dh;
return self.draw_image_data(image_data,
image_size,
sx, sy, sw, sh,
dx, dy, dw, dh)
}
}
}
// https://html.spec.whatwg.org/multipage/scripting.html#dom-context-2d-drawimage
fn DrawImage_(self, image: HTMLCanvasElementOrCanvasRenderingContext2D,
fn DrawImage_(self, image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D,
dx: f64, dy: f64, dw: f64, dh: f64) -> Fallible<()> {
if !(dx.is_finite() && dy.is_finite() &&
dw.is_finite() && dh.is_finite()) {
@ -373,7 +464,7 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D>
let sy: f64 = 0f64;
match image {
HTMLCanvasElementOrCanvasRenderingContext2D::eHTMLCanvasElement(image) => {
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLCanvasElement(image) => {
let canvas = image.root();
let canvas_size = canvas.r().get_size();
let sw: f64 = canvas_size.width as f64;
@ -382,7 +473,7 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D>
sx, sy, sw, sh,
dx, dy, dw, dh)
}
HTMLCanvasElementOrCanvasRenderingContext2D::eCanvasRenderingContext2D(image) => {
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eCanvasRenderingContext2D(image) => {
let image = image.root();
let context = image.r();
let canvas = context.Canvas().root();
@ -393,11 +484,28 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D>
sx, sy, sw, sh,
dx, dy, dw, dh)
}
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLImageElement(image) => {
let image = image.root();
let image_element = image.r();
// https://html.spec.whatwg.org/multipage/embedded-content.html#img-error
// If the image argument is an HTMLImageElement object that is in the broken state,
// then throw an InvalidStateError exception
let (image_data, image_size) = match self.fetch_image_data(&image_element) {
Some((data, size)) => (data, size),
None => return Err(InvalidState),
};
let sw: f64 = image_size.width as f64;
let sh: f64 = image_size.height as f64;
return self.draw_image_data(image_data,
image_size,
sx, sy, sw, sh,
dx, dy, dw, dh)
}
}
}
// https://html.spec.whatwg.org/multipage/scripting.html#dom-context-2d-drawimage
fn DrawImage__(self, image: HTMLCanvasElementOrCanvasRenderingContext2D,
fn DrawImage__(self, image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D,
sx: f64, sy: f64, sw: f64, sh: f64,
dx: f64, dy: f64, dw: f64, dh: f64) -> Fallible<()> {
if !(sx.is_finite() && sy.is_finite() && sw.is_finite() && sh.is_finite() &&
@ -406,13 +514,13 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D>
}
match image {
HTMLCanvasElementOrCanvasRenderingContext2D::eHTMLCanvasElement(image) => {
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLCanvasElement(image) => {
let canvas = image.root();
return self.draw_html_canvas_element(canvas.r(),
sx, sy, sw, sh,
dx, dy, dw, dh)
}
HTMLCanvasElementOrCanvasRenderingContext2D::eCanvasRenderingContext2D(image) => {
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eCanvasRenderingContext2D(image) => {
let image = image.root();
let context = image.r();
let canvas = context.Canvas().root();
@ -420,6 +528,21 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D>
sx, sy, sw, sh,
dx, dy, dw, dh)
}
HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLImageElement(image) => {
let image = image.root();
let image_element = image.r();
// https://html.spec.whatwg.org/multipage/embedded-content.html#img-error
// If the image argument is an HTMLImageElement object that is in the broken state,
// then throw an InvalidStateError exception
let (image_data, image_size) = match self.fetch_image_data(&image_element) {
Some((data, size)) => (data, size),
None => return Err(InvalidState),
};
return self.draw_image_data(image_data,
image_size,
sx, sy, sw, sh,
dx, dy, dw, dh)
}
}
}
@ -574,6 +697,8 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D>
}
fn GetImageData(self, sx: Finite<f64>, sy: Finite<f64>, sw: Finite<f64>, sh: Finite<f64>) -> Fallible<Temporary<ImageData>> {
let sx = *sx;
let sy = *sy;
let sw = *sw;
let sh = *sh;
@ -582,14 +707,18 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D>
}
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 dest_rect = Rect(Point2D(sx as f64, sy as f64), Size2D(sw as f64, sh as f64));
let canvas_size = self.canvas.root().r().get_size();
let canvas_size = Size2D(canvas_size.width as f64, canvas_size.height as f64);
self.renderer.send(CanvasMsg::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: Finite<f64>, dy: Finite<f64>) {
let dx = *dx;
let dy = *dy;
// XXX:
// By the spec: http://www.w3.org/html/wg/drafts/2dcontext/html5_canvas_CR/#dom-context-2d-putimagedata
// "If any of the arguments to the method are infinite or NaN, the method must throw a NotSupportedError exception"
@ -597,13 +726,22 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D>
// they will be TypeError by WebIDL spec before call this methods.
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 image_data_size = imagedata.get_size();
let image_data_size = Size2D(image_data_size.width as f64, image_data_size.height as f64);
let image_data_rect = Rect(Point2D(dx, dy), image_data_size);
let dirty_rect = None;
self.renderer.send(CanvasMsg::PutImageData(data, image_data_rect, dirty_rect)).unwrap()
}
fn PutImageData_(self, imagedata: JSRef<ImageData>, dx: Finite<f64>, dy: Finite<f64>,
dirtyX: Finite<f64>, dirtyY: Finite<f64>, dirtyWidth: Finite<f64>, dirtyHeight: Finite<f64>) {
let dx = *dx;
let dy = *dy;
let dirtyX = *dirtyX;
let dirtyY = *dirtyY;
let dirtyWidth = *dirtyWidth;
let dirtyHeight = *dirtyHeight;
// XXX:
// By the spec: http://www.w3.org/html/wg/drafts/2dcontext/html5_canvas_CR/#dom-context-2d-putimagedata
// "If any of the arguments to the method are infinite or NaN, the method must throw a NotSupportedError exception"
@ -611,12 +749,11 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D>
// they will be TypeError by WebIDL spec before call this methods.
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 image_data_rect = Rect(Point2D(dx, dy),
Size2D(imagedata.Width() as f64,
imagedata.Height() as f64));
let dirty_rect = Some(Rect(Point2D(dirtyX, dirtyY),
Size2D(dirtyWidth, dirtyHeight)));
self.renderer.send(CanvasMsg::PutImageData(data, image_data_rect, dirty_rect)).unwrap()
}
@ -667,6 +804,6 @@ pub fn parse_color(string: &str) -> Result<RGBA,()> {
// Used by drawImage to determine if a source or destination rectangle is valid
// Origin coordinates and size cannot be negative. Size has to be greater than zero
fn is_rect_valid(rect: Rect<i32>) -> bool {
rect.origin.x >= 0 && rect.origin.y >= 0 && rect.size.width > 0 && rect.size.height > 0
fn is_rect_valid(rect: Rect<f64>) -> bool {
rect.size.width > 0.0 && rect.size.height > 0.0
}

Просмотреть файл

@ -39,6 +39,16 @@ impl HTMLImageElementDerived for EventTarget {
}
}
pub trait HTMLImageElementHelpers {
fn get_url(&self) -> Option<Url>;
}
impl<'a> HTMLImageElementHelpers for JSRef<'a, HTMLImageElement> {
fn get_url(&self) -> Option<Url>{
self.image.borrow().clone()
}
}
trait PrivateHTMLImageElementHelpers {
fn update_image(self, value: Option<(DOMString, &Url)>);
}

Просмотреть файл

@ -6,8 +6,8 @@
enum CanvasWindingRule { "nonzero", "evenodd" };
// http://www.whatwg.org/html/#2dcontext
typedef (/* HTMLImageElement or
HTMLVideoElement or */
typedef (HTMLImageElement or
/* HTMLVideoElement or */
HTMLCanvasElement or
CanvasRenderingContext2D /* or
ImageBitmap */) CanvasImageSource;

Просмотреть файл

@ -39,6 +39,7 @@ extern crate js;
extern crate libc;
extern crate msg;
extern crate net_traits;
extern crate png;
extern crate "rustc-serialize" as rustc_serialize;
extern crate time;
extern crate canvas;

1
servo/components/servo/Cargo.lock сгенерированный
Просмотреть файл

@ -803,6 +803,7 @@ dependencies = [
"msg 0.0.1",
"net_traits 0.0.1",
"plugins 0.0.1",
"png 0.1.0 (git+https://github.com/servo/rust-png)",
"profile 0.0.1",
"rustc-serialize 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"script_traits 0.0.1",

1
servo/ports/cef/Cargo.lock сгенерированный
Просмотреть файл

@ -807,6 +807,7 @@ dependencies = [
"msg 0.0.1",
"net_traits 0.0.1",
"plugins 0.0.1",
"png 0.1.0 (git+https://github.com/servo/rust-png)",
"profile 0.0.1",
"rustc-serialize 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"script_traits 0.0.1",

1
servo/ports/gonk/Cargo.lock сгенерированный
Просмотреть файл

@ -732,6 +732,7 @@ dependencies = [
"msg 0.0.1",
"net_traits 0.0.1",
"plugins 0.0.1",
"png 0.1.0 (git+https://github.com/servo/rust-png)",
"profile 0.0.1",
"rustc-serialize 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"script_traits 0.0.1",

Просмотреть файл

@ -171,50 +171,6 @@ function renderExample(title) {
return container;
}
function drawImage() {
var args = Array.prototype.slice.call(arguments);
var div = renderExample('drawImage(' + args.toString() + ')');
var canvasEls = div.querySelectorAll('canvas');
var canvas1 = canvasEls[0];
var canvas2 = canvasEls[1];
canvas1.width = imageSource.width;
canvas1.height = imageSource.height;
var ctx1 = canvas1.getContext('2d');
ctx1.fillStyle = "#00FFFF";
ctx1.fillRect(0, 0, imageSource.width, imageSource.height);
ctx1.fillStyle = "#000000";
ctx1.fillRect(5,5,40,40);
ctx1.clearRect(10,10,30,30);
ctx1.strokeRect(15,15,20,20);
canvas2.width = destCanvas.width;
canvas2.height = destCanvas.height;
var ctx2 = canvas2.getContext('2d');
ctx2.fillStyle = "#FF0000";
ctx2.fillRect(0, 0, destCanvas.width, destCanvas.height);
ctx2.imageSmoothingEnabled = smoothingEnabled;
args.unshift(canvas1);
try {
ctx2.drawImage.apply(ctx2, args);
}
catch(err) {
var title = div.querySelector('.example-title');
var error = document.createElement('h2');
error.setAttribute('class', 'example-title error');
div.insertBefore(error, title);
error.textContent += "Call Failed: " + err.message;
}
document.body.appendChild(div);
};
function drawImage() {
var args = Array.prototype.slice.call(arguments);
@ -260,4 +216,4 @@ function drawImage() {
</script>
</body>
</html>
</html>

Просмотреть файл

@ -0,0 +1,209 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<style type=''>
html {
font-family: helvetica, sans-serif;
}
canvas, img {
margin: 10px;
border: 1px solid grey;
}
.example {
display: inline-block;
width: 280px;
margin-top: 50px;
margin-right: 50px;
}
.title {
text-align: center;
}
.description {
width: 100%;
}
.description div {
display: inline-block;
text-align: center;
font-size: 13px;
}
.description .source {
width: 100px;
}
.description .target {
width: 130px;
}
.example-title {
text-align: center;
font-size: 16px;
}
.error {
color: red;
}
</style>
<body>
<h1 class="title">DrawImage canvas to canvas</h1>
</body>
<script type="text/javascript">
var smoothingEnabled = false;
var examplesNum = 0;
var imageSource = {
width: 100,
height: 100
};
var destCanvas = {
width: 100,
height: 100
};
// 2 arguments, the dest origin is 0,0
// The source canvas will copied to the 0,0 position of the destination canvas
drawImage(0, 0);
// 2 arguments, the dest origin is not 0,0
// The source canvas will copied to the 25, 25 position of the destination canvas
drawImage(25, 25);
// 4 arguments, the source origin is not 0,0, the dest size is provided
// The source canvas will copied to the 50, 50 position of the destination canvas and
// on an area of 50x50 pixels
drawImage(50, 50, 50, 50);
// 4 arguments, the dest origin is not 0,0 and the dest size is provided but
// does not match the size of the source. The image will be distorted
// The source canvas will copied to the 50,50 position of the destination canvas
// and it will be shrunk to a and area of 25x25
drawImage(50, 50, 25, 25);
// The source canvas will copied to the 50,50 position of the destination canvas
// over an area of 50x25 pixels
// The copied image will be distorted along the x axis
drawImage(50, 50, 50, 25);
// 8 arguments, both destination and source origins are 0, 0
// An area of 25x25 pixels of the source image will be copied to
// an area of 25x25 pixels of the destination canvas
drawImage(0, 0, 25, 25, 0, 0, 25, 25);
// 8 arguments the destination origin is not 0,0
// An area of 25x25 pixels of the source image will be copied to
// an area of 25x25 pixels of the destination canvas in the position 25,25
drawImage(0, 0, 25, 25, 25, 25, 25, 25);
// The source rectangle overflows the source image
// The source area is clipped to fit the source image
// and the destination are is clipped in the same proportion
drawImage(25, 25, 50, 50, 0, 0, 50, 50);
// The destination rectangle has negative width and height
// An exception is raised and nothing is drawn
drawImage(25, 25, 50, 50, 0, 0, -100, -100);
// The destination rectangle is larger thant the destination canvas
// When the destination rectangle is outside the destination image (the scratch bitmap), the pixels
// that land outside the scratch bitmap are discarded, as if the destination was an infinite canvas
// whose rendering was clipped to the dimensions of the scratch bitmap.
drawImage(0, 0, 50, 50, 0, 0, 200, 200);
// The source rectangle is larger than the source canvas
// The source area is clipped to fit the source image
// and the destination are is clipped in the same proportion
drawImage(0, 0, 100, 100, 0, 0, 50, 50);
// Negative coorditanes of the source rectangle
// The source area is clipped to fit the source image
// and the destination are is clipped in the same proportion
drawImage(-25, -25, 50, 50, 0, 0, 50, 50);
// Example with non squared origin and source canvases
imageSource = {
width: 100,
height: 50
};
destCanvas = {
width: 100,
height: 50
};
//drawImage(0, 0);
function renderExample(title) {
var container = document.createElement('div');
container.id = 'example' + examplesNum++;
container.setAttribute('class', 'example');
var h2 = document.createElement('h2');
h2.textContent = title;
h2.setAttribute('class', 'example-title');
container.appendChild(h2);
var div1 = document.createElement('div');
var canvas1 = document.createElement('canvas');
var img = document.createElement('img');
img.src = 'rust-0.png';
div1.appendChild(img);
div1.appendChild(canvas1);
container.appendChild(div1);
var div2 = document.createElement('div');
div2.setAttribute('class', 'description');
var source = document.createElement('div');
source.textContent = ' Source (' + imageSource.width + ',' + imageSource.height + ')';
source.setAttribute('class', 'source');
var arrow = document.createElement('div');
arrow.textContent = ' -> ';
arrow.setAttribute('class', 'arrow');
var target = document.createElement('div');
target.textContent = 'Target (' + destCanvas.width + ',' + destCanvas.height + ')';
target.setAttribute('class', 'target');
div2.appendChild(source);
div2.appendChild(arrow);
div2.appendChild(target);
container.appendChild(div2);
return container;
}
function drawImage() {
var args = Array.prototype.slice.call(arguments);
var div = renderExample('drawImage(' + args.toString() + ')');
var canvas = div.querySelectorAll('canvas')[0];
var img = div.querySelectorAll('img')[0];
img.width = imageSource.width;
img.height = imageSource.height;
canvas.width = destCanvas.width;
canvas.height = destCanvas.height;
var ctx = canvas.getContext('2d');
ctx.fillStyle = "#FF0000";
ctx.fillRect(0, 0, destCanvas.width, destCanvas.height);
ctx.imageSmoothingEnabled = smoothingEnabled;
args.unshift(img);
try {
ctx.drawImage.apply(ctx, args);
}
catch(err) {
var title = div.querySelector('.example-title');
var error = document.createElement('h2');
error.setAttribute('class', 'example-title error');
div.insertBefore(error, title);
error.textContent += "Call Failed: " + err.message;
}
document.body.appendChild(div);
}
</script>
</body>
</html>