diff --git a/servo/components/canvas/canvas_paint_task.rs b/servo/components/canvas/canvas_paint_task.rs index 403e4b2a3c74..aa4ac307f83f 100644 --- a/servo/components/canvas/canvas_paint_task.rs +++ b/servo/components/canvas/canvas_paint_task.rs @@ -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, Rect, Rect, bool), - DrawImageSelf(Size2D, Rect, Rect, bool), + DrawImage(Vec, Size2D, Rect, Rect, bool), + DrawImageSelf(Size2D, Rect, Rect, bool), MoveTo(Point2D), LineTo(Point2D), QuadraticCurveTo(Point2D, Point2D), @@ -41,8 +41,8 @@ pub enum CanvasMsg { SetTransform(Matrix2D), Recreate(Size2D), SendPixelContents(Sender>), - GetImageData(Rect, Size2D, Sender>), - PutImageData(Vec, Rect, Option>), + GetImageData(Rect, Size2D, Sender>), + PutImageData(Vec, Rect, Option>), 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, canvas_size: Size2D) -> Vec{ + fn read_pixels(&self, read_rect: Rect, canvas_size: Size2D) -> Vec{ + 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, - source_rect: Rect, - dest_rect: Rect, + image_size: Size2D, + source_rect: Rect, + dest_rect: Rect, 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, - image_data_rect: Rect) -> Rect{ + mut dirty_rect: Rect, + image_data_rect: Rect) -> Rect{ // 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, - source_rect: Rect, dest_rect: Rect, smoothing_enabled: bool) { + image_size: Size2D, dest_rect: Rect, 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, dest_rect: Rect, - source_rect: Rect, smoothing_enabled: bool) { - self.write_image(imagedata, source_rect, dest_rect, smoothing_enabled); + fn draw_image(&self, image_data: Vec, image_size: Size2D, + dest_rect: Rect, source_rect: Rect, 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, - dest_rect: Rect, source_rect: Rect, + fn draw_image_self(&self, image_size: Size2D, + dest_rect: Rect, source_rect: Rect, 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) { @@ -431,20 +438,20 @@ impl<'a> CanvasPaintTask<'a> { }) } - fn get_image_data(&self, mut dest_rect: Rect, canvas_size: Size2D, chan: Sender>) { - if dest_rect.size.width < 0 { + fn get_image_data(&self, mut dest_rect: Rect, canvas_size: Size2D, chan: Sender>) { + 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, - image_data_rect: Rect, - dirty_rect: Option>) { + image_data_rect: Rect, + dirty_rect: Option>) { - 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, + image_size: Size2D, + crop_rect: Rect) -> Vec{ + // 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; +} + +impl SizeToi32 for Size2D { + fn to_i32(&self) -> Size2D { + Size2D(self.width.to_i32().unwrap(), + self.height.to_i32().unwrap()) + } +} + +pub trait RectToi32 { + fn to_i32(&self) -> Rect; + fn ceil(&self) -> Rect; +} + +impl RectToi32 for Rect { + fn to_i32(&self) -> Rect { + 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 { + 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; } -impl ToAzFloat for Rect { +impl ToAzFloat for Rect { fn to_azfloat(&self) -> Rect { Rect(Point2D(self.origin.x as AzFloat, self.origin.y as AzFloat), Size2D(self.size.width as AzFloat, self.size.height as AzFloat)) diff --git a/servo/components/canvas/lib.rs b/servo/components/canvas/lib.rs index dba859dc9df0..2b5c51feec14 100644 --- a/servo/components/canvas/lib.rs +++ b/servo/components/canvas/lib.rs @@ -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; diff --git a/servo/components/script/Cargo.toml b/servo/components/script/Cargo.toml index 9e7db847a861..63eaea80142e 100644 --- a/servo/components/script/Cargo.toml +++ b/servo/components/script/Cargo.toml @@ -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" diff --git a/servo/components/script/dom/canvasrenderingcontext2d.rs b/servo/components/script/dom/canvasrenderingcontext2d.rs index fc8573923d0b..b4863d876a20 100644 --- a/servo/components/script/dom/canvasrenderingcontext2d.rs +++ b/servo/components/script/dom/canvasrenderingcontext2d.rs @@ -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, + image_size: Size2D, sx: f64, sy: f64, sw: f64, sh: f64, - dx: f64, dy: f64, dw: f64, dh: f64) -> (Rect, Rect) { - let image_size = canvas.get_size(); + dx: f64, dy: f64, dw: f64, dh: f64) -> (Rect, Rect) { 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, 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::>(); // 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, + image_size: Size2D, + 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) + -> Option<(Vec, Size2D)> { + 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>> { + 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, sy: Finite, sw: Finite, sh: Finite) -> Fallible> { + 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::>(); - 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, dx: Finite, dy: Finite) { + 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, dx: Finite, dy: Finite, dirtyX: Finite, dirtyY: Finite, dirtyWidth: Finite, dirtyHeight: Finite) { + 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 { // 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) -> bool { - rect.origin.x >= 0 && rect.origin.y >= 0 && rect.size.width > 0 && rect.size.height > 0 +fn is_rect_valid(rect: Rect) -> bool { + rect.size.width > 0.0 && rect.size.height > 0.0 } diff --git a/servo/components/script/dom/htmlimageelement.rs b/servo/components/script/dom/htmlimageelement.rs index dadcd7762382..f93d525f1fc4 100644 --- a/servo/components/script/dom/htmlimageelement.rs +++ b/servo/components/script/dom/htmlimageelement.rs @@ -39,6 +39,16 @@ impl HTMLImageElementDerived for EventTarget { } } +pub trait HTMLImageElementHelpers { + fn get_url(&self) -> Option; +} + +impl<'a> HTMLImageElementHelpers for JSRef<'a, HTMLImageElement> { + fn get_url(&self) -> Option{ + self.image.borrow().clone() + } +} + trait PrivateHTMLImageElementHelpers { fn update_image(self, value: Option<(DOMString, &Url)>); } diff --git a/servo/components/script/dom/webidls/CanvasRenderingContext2D.webidl b/servo/components/script/dom/webidls/CanvasRenderingContext2D.webidl index 511a26ae66be..44f6d8d32514 100644 --- a/servo/components/script/dom/webidls/CanvasRenderingContext2D.webidl +++ b/servo/components/script/dom/webidls/CanvasRenderingContext2D.webidl @@ -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; diff --git a/servo/components/script/lib.rs b/servo/components/script/lib.rs index 75699c419cc7..c51219cb11d5 100644 --- a/servo/components/script/lib.rs +++ b/servo/components/script/lib.rs @@ -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; diff --git a/servo/components/servo/Cargo.lock b/servo/components/servo/Cargo.lock index acf5eb433771..67fac783c241 100644 --- a/servo/components/servo/Cargo.lock +++ b/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", diff --git a/servo/ports/cef/Cargo.lock b/servo/ports/cef/Cargo.lock index e274f8ba6b49..7cd06499343e 100644 --- a/servo/ports/cef/Cargo.lock +++ b/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", diff --git a/servo/ports/gonk/Cargo.lock b/servo/ports/gonk/Cargo.lock index 7f07df266849..ddc5afd7988a 100644 --- a/servo/ports/gonk/Cargo.lock +++ b/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", diff --git a/servo/tests/html/test_canvas_drawimage.html b/servo/tests/html/test_canvas_drawimage_canvas.html similarity index 83% rename from servo/tests/html/test_canvas_drawimage.html rename to servo/tests/html/test_canvas_drawimage_canvas.html index 54a7082f9f33..fdf00daee39b 100644 --- a/servo/tests/html/test_canvas_drawimage.html +++ b/servo/tests/html/test_canvas_drawimage_canvas.html @@ -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() { - \ No newline at end of file + diff --git a/servo/tests/html/test_canvas_drawimage_html_image.html b/servo/tests/html/test_canvas_drawimage_html_image.html new file mode 100644 index 000000000000..c317f7895df8 --- /dev/null +++ b/servo/tests/html/test_canvas_drawimage_html_image.html @@ -0,0 +1,209 @@ + + + + + + + +

DrawImage canvas to canvas

+ + + + +