Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Implement drawing an image from a CSS style value into a canvas #17634

Merged
merged 1 commit into from Jul 21, 2017
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Implement drawing an image from a CSS style value into a canvas.

  • Loading branch information
asajeffrey committed Jul 21, 2017
commit 2318caf002ab13cd9c9bf30676eaca5e7b9b0dc1
@@ -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.
@@ -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,

Large diffs are not rendered by default.

@@ -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;
@@ -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 {
@@ -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())
}
}
@@ -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;
@@ -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;
@@ -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)),
}
}
@@ -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>)
@@ -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 {
@@ -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,
@@ -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,
@@ -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)
@@ -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;
@@ -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;
@@ -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;
@@ -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;

@@ -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
@@ -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);
}
}
}
@@ -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
@@ -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);
@@ -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());
@@ -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());
@@ -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> {
@@ -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())))
@@ -346,7 +356,7 @@ pub enum PaintWorkletTask {
TypedSize2D<f32, CSSPixel>,
ScaleFactor<f32, CSSPixel, DevicePixel>,
Vec<(Atom, String)>,
IpcSender<CanvasData>)
Sender<DrawAPaintImageResult>)
}

/// A paint definition
@@ -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 {
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.