diff options
author | Diego Marcos <diego.marcos@gmail.com> | 2015-03-25 17:17:18 -0700 |
---|---|---|
committer | Diego Marcos <diego.marcos@gmail.com> | 2015-04-07 11:02:50 -0700 |
commit | 88954e3e1800dee658837684f9376a321fca59b7 (patch) | |
tree | f50eaa1a97c9d77f454ce0024452d14384526356 /components/script/dom/canvasrenderingcontext2d.rs | |
parent | 184d214e260213bafc5801af33f3031289da9361 (diff) | |
download | servo-88954e3e1800dee658837684f9376a321fca59b7.tar.gz servo-88954e3e1800dee658837684f9376a321fca59b7.zip |
Implements drawImage for html image as ImageSource
Diffstat (limited to 'components/script/dom/canvasrenderingcontext2d.rs')
-rw-r--r-- | components/script/dom/canvasrenderingcontext2d.rs | 215 |
1 files changed, 176 insertions, 39 deletions
diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs index fc8573923d0..b4863d876a2 100644 --- a/components/script/dom/canvasrenderingcontext2d.rs +++ b/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<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 } |