Skip to content

Commit

Permalink
Implement drawing an image from a CSS style value into a canvas.
Browse files Browse the repository at this point in the history
  • Loading branch information
Alan Jeffrey committed Jul 21, 2017
1 parent 992c647 commit 2318caf
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 125 deletions.
28 changes: 11 additions & 17 deletions components/layout/display_list_builder.rs
Expand Up @@ -1173,7 +1173,6 @@ impl FragmentDisplayListBuilding for Fragment {
let device_pixel_ratio = state.layout_context.style_context.device_pixel_ratio();
let size_in_au = unbordered_box.size.to_physical(style.writing_mode);
let size_in_px = TypedSize2D::new(size_in_au.width.to_f32_px(), size_in_au.height.to_f32_px());
let size_in_dpx = size_in_px * device_pixel_ratio;
let name = paint_worklet.name.clone();

// Get the painter, and the computed values for its properties.
Expand All @@ -1192,24 +1191,19 @@ impl FragmentDisplayListBuilding for Fragment {
// TODO: add a one-place cache to avoid drawing the paint image every time.
// https://github.com/servo/servo/issues/17369
debug!("Drawing a paint image {}({},{}).", name, size_in_px.width, size_in_px.height);
let (sender, receiver) = ipc::channel().unwrap();
painter.draw_a_paint_image(size_in_px, device_pixel_ratio, properties, sender);

// TODO: timeout
let webrender_image = match receiver.recv() {
Ok(CanvasData::Image(canvas_data)) => {
WebRenderImageInfo {
// TODO: it would be nice to get this data back from the canvas
width: size_in_dpx.width as u32,
height: size_in_dpx.height as u32,
format: PixelFormat::BGRA8,
key: Some(canvas_data.image_key),
}
},
Ok(CanvasData::WebGL(_)) => return warn!("Paint worklet generated WebGL."),
Err(err) => return warn!("Paint worklet recv generated error ({}).", err),
let mut draw_result = painter.draw_a_paint_image(size_in_px, device_pixel_ratio, properties);
let webrender_image = WebRenderImageInfo {
width: draw_result.width,
height: draw_result.height,
format: draw_result.format,
key: draw_result.image_key,
};

for url in draw_result.missing_image_urls.drain(..) {
debug!("Requesting missing image URL {}.", url);
state.layout_context.get_webrender_image_for_url(self.node, url, UsePlaceholder::No);
}

self.build_display_list_for_webrender_image(state,
style,
display_list_section,
Expand Down
170 changes: 117 additions & 53 deletions components/script/dom/canvasrenderingcontext2d.rs

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions components/script/dom/cssstylevalue.rs
Expand Up @@ -2,6 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use cssparser::Parser;
use cssparser::ParserInput;
use dom::bindings::codegen::Bindings::CSSStyleValueBinding::CSSStyleValueMethods;
use dom::bindings::codegen::Bindings::CSSStyleValueBinding::Wrap;
use dom::bindings::js::Root;
Expand All @@ -10,6 +12,7 @@ use dom::bindings::reflector::reflect_dom_object;
use dom::bindings::str::DOMString;
use dom::globalscope::GlobalScope;
use dom_struct::dom_struct;
use servo_url::ServoUrl;

#[dom_struct]
pub struct CSSStyleValue {
Expand Down Expand Up @@ -44,3 +47,16 @@ impl CSSStyleValueMethods for CSSStyleValue {
self.Stringifier()
}
}

impl CSSStyleValue {
/// Parse the value as a `url()`.
/// TODO: This should really always be an absolute URL, but we currently
/// return relative URLs for computed values, so we pass in a base.
/// https://github.com/servo/servo/issues/17625
pub fn get_url(&self, base_url: ServoUrl) -> Option<ServoUrl> {
let mut input = ParserInput::new(&*self.value);
let mut parser = Parser::new(&mut input);
parser.expect_url().ok()
.and_then(|string| base_url.join(&*string).ok())
}
}
20 changes: 14 additions & 6 deletions components/script/dom/paintrenderingcontext2d.rs
Expand Up @@ -11,7 +11,7 @@ use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLin
use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasRenderingContext2DMethods;
use dom::bindings::codegen::Bindings::PaintRenderingContext2DBinding;
use dom::bindings::codegen::Bindings::PaintRenderingContext2DBinding::PaintRenderingContext2DMethods;
use dom::bindings::codegen::UnionTypes::HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D;
use dom::bindings::codegen::UnionTypes::HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2DOrCSSStyleValue;
use dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern;
use dom::bindings::error::ErrorResult;
use dom::bindings::error::Fallible;
Expand All @@ -24,11 +24,13 @@ use dom::canvasgradient::CanvasGradient;
use dom::canvaspattern::CanvasPattern;
use dom::canvasrenderingcontext2d::CanvasRenderingContext2D;
use dom::paintworkletglobalscope::PaintWorkletGlobalScope;
use dom::workletglobalscope::WorkletGlobalScope;
use dom_struct::dom_struct;
use euclid::ScaleFactor;
use euclid::Size2D;
use euclid::TypedSize2D;
use ipc_channel::ipc::IpcSender;
use servo_url::ServoUrl;
use std::cell::Cell;
use style_traits::CSSPixel;
use style_traits::DevicePixel;
Expand All @@ -42,8 +44,10 @@ pub struct PaintRenderingContext2D {
impl PaintRenderingContext2D {
fn new_inherited(global: &PaintWorkletGlobalScope) -> PaintRenderingContext2D {
let size = Size2D::zero();
let image_cache = global.image_cache();
let base_url = global.upcast::<WorkletGlobalScope>().base_url();
PaintRenderingContext2D {
context: CanvasRenderingContext2D::new_inherited(global.upcast(), None, size),
context: CanvasRenderingContext2D::new_inherited(global.upcast(), None, image_cache, base_url, size),
device_pixel_ratio: Cell::new(ScaleFactor::new(1.0)),
}
}
Expand All @@ -59,6 +63,10 @@ impl PaintRenderingContext2D {
let _ = self.context.ipc_renderer().send(msg);
}

pub fn take_missing_image_urls(&self) -> Vec<ServoUrl> {
self.context.take_missing_image_urls()
}

pub fn set_bitmap_dimensions(&self,
size: TypedSize2D<f32, CSSPixel>,
device_pixel_ratio: ScaleFactor<f32, CSSPixel, DevicePixel>)
Expand Down Expand Up @@ -187,7 +195,7 @@ impl PaintRenderingContext2DMethods for PaintRenderingContext2D {

// https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
fn DrawImage(&self,
image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D,
image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2DOrCSSStyleValue,
dx: f64,
dy: f64)
-> ErrorResult {
Expand All @@ -196,7 +204,7 @@ impl PaintRenderingContext2DMethods for PaintRenderingContext2D {

// https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
fn DrawImage_(&self,
image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D,
image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2DOrCSSStyleValue,
dx: f64,
dy: f64,
dw: f64,
Expand All @@ -207,7 +215,7 @@ impl PaintRenderingContext2DMethods for PaintRenderingContext2D {

// https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
fn DrawImage__(&self,
image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D,
image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2DOrCSSStyleValue,
sx: f64,
sy: f64,
sw: f64,
Expand Down Expand Up @@ -309,7 +317,7 @@ impl PaintRenderingContext2DMethods for PaintRenderingContext2D {

// https://html.spec.whatwg.org/multipage/#dom-context-2d-createpattern
fn CreatePattern(&self,
image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D,
image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2DOrCSSStyleValue,
repetition: DOMString)
-> Fallible<Root<CanvasPattern>> {
self.context.CreatePattern(image, repetition)
Expand Down
90 changes: 50 additions & 40 deletions components/script/dom/paintworkletglobalscope.rs
Expand Up @@ -3,7 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use canvas_traits::CanvasData;
use canvas_traits::CanvasImageData;
use dom::bindings::callback::CallbackContainer;
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::PaintWorkletGlobalScopeBinding;
Expand All @@ -28,8 +27,7 @@ use dom::workletglobalscope::WorkletTask;
use dom_struct::dom_struct;
use euclid::ScaleFactor;
use euclid::TypedSize2D;
use ipc_channel::ipc::IpcSender;
use ipc_channel::ipc::IpcSharedMemory;
use ipc_channel::ipc;
use js::jsapi::Call;
use js::jsapi::Construct1;
use js::jsapi::HandleValue;
Expand All @@ -45,10 +43,10 @@ use js::jsval::ObjectValue;
use js::jsval::UndefinedValue;
use js::rust::Runtime;
use msg::constellation_msg::PipelineId;
use net_traits::image::base::Image;
use net_traits::image::base::PixelFormat;
use net_traits::image_cache::ImageCache;
use script_layout_interface::message::Msg;
use script_traits::DrawAPaintImageResult;
use script_traits::Painter;
use servo_atoms::Atom;
use servo_url::ServoUrl;
Expand All @@ -59,6 +57,8 @@ use std::ptr::null_mut;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::mpsc;
use std::sync::mpsc::Sender;
use style_traits::CSSPixel;
use style_traits::DevicePixel;

Expand All @@ -67,7 +67,7 @@ use style_traits::DevicePixel;
pub struct PaintWorkletGlobalScope {
/// The worklet global for this object
worklet_global: WorkletGlobalScope,
/// The image cache (used for generating invalid images).
/// The image cache
#[ignore_heap_size_of = "Arc"]
image_cache: Arc<ImageCache>,
/// https://drafts.css-houdini.org/css-paint-api/#paint-definitions
Expand All @@ -94,11 +94,16 @@ impl PaintWorkletGlobalScope {
unsafe { PaintWorkletGlobalScopeBinding::Wrap(runtime.cx(), global) }
}

pub fn image_cache(&self) -> Arc<ImageCache> {
self.image_cache.clone()
}

pub fn perform_a_worklet_task(&self, task: PaintWorkletTask) {
match task {
PaintWorkletTask::DrawAPaintImage(name, size, device_pixel_ratio, properties, sender) => {
PaintWorkletTask::DrawAPaintImage(name, size_in_px, device_pixel_ratio, properties, sender) => {
let properties = StylePropertyMapReadOnly::from_iter(self.upcast(), properties);
self.draw_a_paint_image(name, size, device_pixel_ratio, &*properties, sender);
let result = self.draw_a_paint_image(name, size_in_px, device_pixel_ratio, &*properties);
let _ = sender.send(result);
}
}
}
Expand All @@ -108,11 +113,11 @@ impl PaintWorkletGlobalScope {
name: Atom,
size_in_px: TypedSize2D<f32, CSSPixel>,
device_pixel_ratio: ScaleFactor<f32, CSSPixel, DevicePixel>,
properties: &StylePropertyMapReadOnly,
sender: IpcSender<CanvasData>)
properties: &StylePropertyMapReadOnly)
-> DrawAPaintImageResult
{
// TODO: document paint definitions.
self.invoke_a_paint_callback(name, size_in_px, device_pixel_ratio, properties, sender);
self.invoke_a_paint_callback(name, size_in_px, device_pixel_ratio, properties)
}

/// https://drafts.css-houdini.org/css-paint-api/#invoke-a-paint-callback
Expand All @@ -121,8 +126,8 @@ impl PaintWorkletGlobalScope {
name: Atom,
size_in_px: TypedSize2D<f32, CSSPixel>,
device_pixel_ratio: ScaleFactor<f32, CSSPixel, DevicePixel>,
properties: &StylePropertyMapReadOnly,
sender: IpcSender<CanvasData>)
properties: &StylePropertyMapReadOnly)
-> DrawAPaintImageResult
{
let size_in_dpx = size_in_px * device_pixel_ratio;
let size_in_dpx = TypedSize2D::new(size_in_dpx.width.abs() as u32, size_in_dpx.height.abs() as u32);
Expand All @@ -140,13 +145,13 @@ impl PaintWorkletGlobalScope {
None => {
// Step 2.2.
warn!("Drawing un-registered paint definition {}.", name);
return self.send_invalid_image(size_in_dpx, sender);
return self.invalid_image(size_in_dpx, vec![]);
}
Some(definition) => {
// Step 5.1
if !definition.constructor_valid_flag.get() {
debug!("Drawing invalid paint definition {}.", name);
return self.send_invalid_image(size_in_dpx, sender);
return self.invalid_image(size_in_dpx, vec![]);
}
class_constructor.set(definition.class_constructor.get());
paint_function.set(definition.paint_function.get());
Expand Down Expand Up @@ -174,7 +179,7 @@ impl PaintWorkletGlobalScope {
self.paint_definitions.borrow_mut().get_mut(&name)
.expect("Vanishing paint definition.")
.constructor_valid_flag.set(false);
return self.send_invalid_image(size_in_dpx, sender);
return self.invalid_image(size_in_dpx, vec![]);
}
// Step 5.4
entry.insert(Box::new(Heap::default())).set(paint_instance.get());
Expand Down Expand Up @@ -202,39 +207,42 @@ impl PaintWorkletGlobalScope {

rooted!(in(cx) let mut result = UndefinedValue());
unsafe { Call(cx, paint_instance.handle(), paint_function.handle(), &args, result.handle_mut()); }
let missing_image_urls = rendering_context.take_missing_image_urls();

// Step 13.
if unsafe { JS_IsExceptionPending(cx) } {
debug!("Paint function threw an exception {}.", name);
unsafe { JS_ClearPendingException(cx); }
return self.send_invalid_image(size_in_dpx, sender);
return self.invalid_image(size_in_dpx, missing_image_urls);
}

let (sender, receiver) = ipc::channel().expect("IPC channel creation.");
rendering_context.send_data(sender);
let image_key = match receiver.recv() {
Ok(CanvasData::Image(data)) => Some(data.image_key),
_ => None,
};

DrawAPaintImageResult {
width: size_in_dpx.width,
height: size_in_dpx.height,
format: PixelFormat::BGRA8,
image_key: image_key,
missing_image_urls: missing_image_urls,
}
}

fn send_invalid_image(&self,
size: TypedSize2D<u32, DevicePixel>,
sender: IpcSender<CanvasData>)
{
debug!("Sending an invalid image.");
let width = size.width as u32;
let height = size.height as u32;
let len = (width as usize) * (height as usize) * 4;
let pixel = [0x00, 0x00, 0x00, 0x00];
let bytes: Vec<u8> = pixel.iter().cloned().cycle().take(len).collect();
let mut image = Image {
width: width,
height: height,
// https://drafts.csswg.org/css-images-4/#invalid-image
fn invalid_image(&self, size: TypedSize2D<u32, DevicePixel>, missing_image_urls: Vec<ServoUrl>)
-> DrawAPaintImageResult {
debug!("Returning an invalid image.");
DrawAPaintImageResult {
width: size.width as u32,
height: size.height as u32,
format: PixelFormat::BGRA8,
bytes: IpcSharedMemory::from_bytes(&*bytes),
id: None,
};
self.image_cache.set_webrender_image_key(&mut image);
let image_key = image.id.expect("Image cache should set image key.");
let image_data = CanvasImageData { image_key: image_key };
let canvas_data = CanvasData::Image(image_data);
let _ = sender.send(canvas_data);
image_key: None,
missing_image_urls: missing_image_urls,
}
}

fn painter(&self, name: Atom) -> Arc<Painter> {
Expand All @@ -244,13 +252,15 @@ impl PaintWorkletGlobalScope {
fn draw_a_paint_image(&self,
size: TypedSize2D<f32, CSSPixel>,
device_pixel_ratio: ScaleFactor<f32, CSSPixel, DevicePixel>,
properties: Vec<(Atom, String)>,
sender: IpcSender<CanvasData>)
properties: Vec<(Atom, String)>)
-> DrawAPaintImageResult
{
let name = self.0.clone();
let (sender, receiver) = mpsc::channel();
let task = PaintWorkletTask::DrawAPaintImage(name, size, device_pixel_ratio, properties, sender);
self.1.lock().expect("Locking a painter.")
.schedule_a_worklet_task(WorkletTask::Paint(task));
receiver.recv().expect("Worklet thread died?")
}
}
Arc::new(WorkletPainter(name, Mutex::new(self.worklet_global.executor())))
Expand Down Expand Up @@ -346,7 +356,7 @@ pub enum PaintWorkletTask {
TypedSize2D<f32, CSSPixel>,
ScaleFactor<f32, CSSPixel, DevicePixel>,
Vec<(Atom, String)>,
IpcSender<CanvasData>)
Sender<DrawAPaintImageResult>)
}

/// A paint definition
Expand Down
7 changes: 5 additions & 2 deletions components/script/dom/webidls/CanvasRenderingContext2D.webidl
Expand Up @@ -8,8 +8,11 @@ enum CanvasFillRule { "nonzero", "evenodd" };
typedef (HTMLImageElement or
/* HTMLVideoElement or */
HTMLCanvasElement or
CanvasRenderingContext2D /* or
ImageBitmap */) CanvasImageSource;
CanvasRenderingContext2D or
/* ImageBitmap or */
// This should probably be a CSSImageValue
// https://github.com/w3c/css-houdini-drafts/issues/416
CSSStyleValue) CanvasImageSource;

//[Constructor(optional unsigned long width, unsigned long height)]
interface CanvasRenderingContext2D {
Expand Down

0 comments on commit 2318caf

Please sign in to comment.