mirror of
https://github.com/servo/servo.git
synced 2025-07-24 15:50:21 +01:00
Abstract some stuff common to ctx.getImageData and ctx.putImageData
This commit is contained in:
parent
e62dbabb46
commit
77c28bdfc9
4 changed files with 97 additions and 147 deletions
|
@ -441,24 +441,20 @@ impl<'a> CanvasData<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata
|
// https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata
|
||||||
pub fn put_image_data(
|
pub fn put_image_data(&mut self, mut imagedata: Vec<u8>, rect: Rect<u32>) {
|
||||||
&mut self,
|
assert_eq!(imagedata.len() % 4, 0);
|
||||||
mut imagedata: Vec<u8>,
|
assert_eq!(rect.size.area() as usize, imagedata.len() / 4);
|
||||||
offset: Vector2D<i32>,
|
|
||||||
imagedata_size: Size2D<i32>,
|
|
||||||
) {
|
|
||||||
assert_eq!(imagedata_size.area() * 4, imagedata.len() as i32);
|
|
||||||
pixels::byte_swap_and_premultiply_inplace(&mut imagedata);
|
pixels::byte_swap_and_premultiply_inplace(&mut imagedata);
|
||||||
let source_surface = self.drawtarget.create_source_surface_from_data(
|
let source_surface = self.drawtarget.create_source_surface_from_data(
|
||||||
&imagedata,
|
&imagedata,
|
||||||
imagedata_size,
|
rect.size.to_i32(),
|
||||||
imagedata_size.width * 4,
|
rect.size.width as i32 * 4,
|
||||||
SurfaceFormat::B8G8R8A8,
|
SurfaceFormat::B8G8R8A8,
|
||||||
).unwrap();
|
).unwrap();
|
||||||
self.drawtarget.copy_surface(
|
self.drawtarget.copy_surface(
|
||||||
source_surface,
|
source_surface,
|
||||||
Rect::from_size(imagedata_size),
|
Rect::from_size(rect.size.to_i32()),
|
||||||
offset.to_point(),
|
rect.origin.to_i32(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -517,14 +513,11 @@ impl<'a> CanvasData<'a> {
|
||||||
/// canvas_size: The size of the canvas we're reading from
|
/// canvas_size: The size of the canvas we're reading from
|
||||||
/// read_rect: The area of the canvas we want to read from
|
/// read_rect: The area of the canvas we want to read from
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
pub fn read_pixels(&self, read_rect: Rect<i32>, canvas_size: Size2D<i32>) -> Vec<u8> {
|
pub fn read_pixels(&self, read_rect: Rect<u32>, canvas_size: Size2D<u32>) -> Vec<u8> {
|
||||||
let canvas_rect = Rect::from_size(canvas_size);
|
let canvas_rect = Rect::from_size(canvas_size);
|
||||||
let src_read_rect = canvas_rect.intersection(&read_rect).unwrap_or(Rect::zero());
|
if canvas_rect.intersection(&read_rect).map_or(true, |rect| rect.is_empty()) {
|
||||||
|
return vec![];
|
||||||
if src_read_rect.is_empty() || canvas_size.width <= 0 && canvas_size.height <= 0 {
|
|
||||||
return vec![];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let data_surface = self.drawtarget.snapshot().get_data_surface();
|
let data_surface = self.drawtarget.snapshot().get_data_surface();
|
||||||
pixels::get_rect(unsafe { data_surface.data() }, canvas_size.to_u32(), read_rect.to_u32()).into_owned()
|
pixels::get_rect(unsafe { data_surface.data() }, canvas_size.to_u32(), read_rect.to_u32()).into_owned()
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,8 +162,8 @@ impl<'a> CanvasPaintThread <'a> {
|
||||||
smoothing
|
smoothing
|
||||||
) => {
|
) => {
|
||||||
let image_data = self.canvas(canvas_id).read_pixels(
|
let image_data = self.canvas(canvas_id).read_pixels(
|
||||||
source_rect.to_i32(),
|
source_rect.to_u32(),
|
||||||
image_size.to_i32(),
|
image_size.to_u32(),
|
||||||
);
|
);
|
||||||
self.canvas(other_canvas_id).draw_image(
|
self.canvas(other_canvas_id).draw_image(
|
||||||
image_data.into(),
|
image_data.into(),
|
||||||
|
@ -242,12 +242,8 @@ impl<'a> CanvasPaintThread <'a> {
|
||||||
let pixels = self.canvas(canvas_id).read_pixels(dest_rect, canvas_size);
|
let pixels = self.canvas(canvas_id).read_pixels(dest_rect, canvas_size);
|
||||||
sender.send(&pixels).unwrap();
|
sender.send(&pixels).unwrap();
|
||||||
},
|
},
|
||||||
Canvas2dMsg::PutImageData(receiver, offset, imagedata_size) => {
|
Canvas2dMsg::PutImageData(rect, receiver) => {
|
||||||
self.canvas(canvas_id).put_image_data(
|
self.canvas(canvas_id).put_image_data(receiver.recv().unwrap(), rect);
|
||||||
receiver.recv().unwrap(),
|
|
||||||
offset,
|
|
||||||
imagedata_size,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
Canvas2dMsg::SetShadowOffsetX(value) => {
|
Canvas2dMsg::SetShadowOffsetX(value) => {
|
||||||
self.canvas(canvas_id).set_shadow_offset_x(value)
|
self.canvas(canvas_id).set_shadow_offset_x(value)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use cssparser::RGBA;
|
use cssparser::RGBA;
|
||||||
use euclid::{Transform2D, Point2D, Vector2D, Rect, Size2D};
|
use euclid::{Transform2D, Point2D, Rect, Size2D};
|
||||||
use ipc_channel::ipc::{IpcBytesReceiver, IpcBytesSender, IpcSender};
|
use ipc_channel::ipc::{IpcBytesReceiver, IpcBytesSender, IpcSender};
|
||||||
use serde_bytes::ByteBuf;
|
use serde_bytes::ByteBuf;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
|
@ -50,11 +50,11 @@ pub enum Canvas2dMsg {
|
||||||
Fill,
|
Fill,
|
||||||
FillText(String, f64, f64, Option<f64>),
|
FillText(String, f64, f64, Option<f64>),
|
||||||
FillRect(Rect<f32>),
|
FillRect(Rect<f32>),
|
||||||
GetImageData(Rect<i32>, Size2D<i32>, IpcBytesSender),
|
GetImageData(Rect<u32>, Size2D<u32>, IpcBytesSender),
|
||||||
IsPointInPath(f64, f64, FillRule, IpcSender<bool>),
|
IsPointInPath(f64, f64, FillRule, IpcSender<bool>),
|
||||||
LineTo(Point2D<f32>),
|
LineTo(Point2D<f32>),
|
||||||
MoveTo(Point2D<f32>),
|
MoveTo(Point2D<f32>),
|
||||||
PutImageData(IpcBytesReceiver, Vector2D<i32>, Size2D<i32>),
|
PutImageData(Rect<u32>, IpcBytesReceiver),
|
||||||
QuadraticCurveTo(Point2D<f32>, Point2D<f32>),
|
QuadraticCurveTo(Point2D<f32>, Point2D<f32>),
|
||||||
Rect(Rect<f32>),
|
Rect(Rect<f32>),
|
||||||
RestoreContext,
|
RestoreContext,
|
||||||
|
|
|
@ -1130,13 +1130,7 @@ impl CanvasRenderingContext2DMethods for CanvasRenderingContext2D {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-getimagedata
|
// https://html.spec.whatwg.org/multipage/#dom-context-2d-getimagedata
|
||||||
fn GetImageData(
|
fn GetImageData(&self, sx: i32, sy: i32, sw: i32, sh: i32) -> Fallible<DomRoot<ImageData>> {
|
||||||
&self,
|
|
||||||
mut sx: i32,
|
|
||||||
mut sy: i32,
|
|
||||||
mut sw: i32,
|
|
||||||
mut sh: i32,
|
|
||||||
) -> Fallible<DomRoot<ImageData>> {
|
|
||||||
// FIXME(nox): There are many arithmetic operations here that can
|
// FIXME(nox): There are many arithmetic operations here that can
|
||||||
// overflow or underflow, this should probably be audited.
|
// overflow or underflow, this should probably be audited.
|
||||||
|
|
||||||
|
@ -1148,57 +1142,30 @@ impl CanvasRenderingContext2DMethods for CanvasRenderingContext2D {
|
||||||
return Err(Error::Security);
|
return Err(Error::Security);
|
||||||
}
|
}
|
||||||
|
|
||||||
if sw < 0 {
|
let (origin, size) = adjust_size_sign(Point2D::new(sx, sy), Size2D::new(sw, sh));
|
||||||
sw = -sw;
|
|
||||||
sx -= sw;
|
|
||||||
}
|
|
||||||
if sh < 0 {
|
|
||||||
sh = -sh;
|
|
||||||
sy -= sh;
|
|
||||||
}
|
|
||||||
|
|
||||||
let data_width = sw;
|
|
||||||
let data_height = sh;
|
|
||||||
|
|
||||||
if sx < 0 {
|
|
||||||
sw += sx;
|
|
||||||
sx = 0;
|
|
||||||
}
|
|
||||||
if sy < 0 {
|
|
||||||
sh += sy;
|
|
||||||
sy = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if sw <= 0 || sh <= 0 {
|
|
||||||
// All the pixels are before the start of the canvas surface.
|
|
||||||
return ImageData::new(&self.global(), data_width as u32, data_height as u32, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (sender, receiver) = ipc::bytes_channel().unwrap();
|
|
||||||
let src_rect = Rect::new(Point2D::new(sx, sy), Size2D::new(sw, sh));
|
|
||||||
// FIXME(nox): This is probably wrong when this is a context for an
|
// FIXME(nox): This is probably wrong when this is a context for an
|
||||||
// offscreen canvas.
|
// offscreen canvas.
|
||||||
let canvas_size = self.canvas
|
let canvas_size = self.canvas.as_ref().map_or(Size2D::zero(), |c| c.get_size());
|
||||||
.as_ref()
|
let read_rect = match clip(origin, size, canvas_size) {
|
||||||
.map_or(Size2D::zero(), |c| c.get_size())
|
Some(rect) => rect,
|
||||||
.try_cast().unwrap();
|
None => {
|
||||||
let canvas_rect = Rect::from_size(canvas_size);
|
// All the pixels are outside the canvas surface.
|
||||||
let read_rect = match src_rect.intersection(&canvas_rect) {
|
return ImageData::new(&self.global(), size.width, size.height, None);
|
||||||
Some(rect) if !rect.is_empty() => rect,
|
},
|
||||||
_ => {
|
|
||||||
// All the pixels are past the end of the canvas surface.
|
|
||||||
return ImageData::new(&self.global(), data_width as u32, data_height as u32, None);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (sender, receiver) = ipc::bytes_channel().unwrap();
|
||||||
self.send_canvas_2d_msg(Canvas2dMsg::GetImageData(read_rect, canvas_size, sender));
|
self.send_canvas_2d_msg(Canvas2dMsg::GetImageData(read_rect, canvas_size, sender));
|
||||||
let mut pixels = receiver.recv().unwrap();
|
let mut pixels = receiver.recv().unwrap().to_vec();
|
||||||
|
|
||||||
for chunk in pixels.chunks_mut(4) {
|
for chunk in pixels.chunks_mut(4) {
|
||||||
let b = chunk[0];
|
let b = chunk[0];
|
||||||
chunk[0] = UNPREMULTIPLY_TABLE[256 * (chunk[3] as usize) + chunk[2] as usize];
|
chunk[0] = UNPREMULTIPLY_TABLE[256 * (chunk[3] as usize) + chunk[2] as usize];
|
||||||
chunk[1] = UNPREMULTIPLY_TABLE[256 * (chunk[3] as usize) + chunk[1] as usize];
|
chunk[1] = UNPREMULTIPLY_TABLE[256 * (chunk[3] as usize) + chunk[1] as usize];
|
||||||
chunk[2] = UNPREMULTIPLY_TABLE[256 * (chunk[3] as usize) + b as usize];
|
chunk[2] = UNPREMULTIPLY_TABLE[256 * (chunk[3] as usize) + b as usize];
|
||||||
}
|
}
|
||||||
ImageData::new(&self.global(), sw as u32, sh as u32, Some(pixels.to_vec()))
|
|
||||||
|
ImageData::new(&self.global(), size.width, size.height, Some(pixels))
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata
|
// https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata
|
||||||
|
@ -1213,17 +1180,17 @@ impl CanvasRenderingContext2DMethods for CanvasRenderingContext2D {
|
||||||
imagedata: &ImageData,
|
imagedata: &ImageData,
|
||||||
dx: i32,
|
dx: i32,
|
||||||
dy: i32,
|
dy: i32,
|
||||||
mut dirty_x: i32,
|
dirty_x: i32,
|
||||||
mut dirty_y: i32,
|
dirty_y: i32,
|
||||||
mut dirty_width: i32,
|
dirty_width: i32,
|
||||||
mut dirty_height: i32,
|
dirty_height: i32,
|
||||||
) {
|
) {
|
||||||
// FIXME(nox): There are many arithmetic operations here that can
|
// FIXME(nox): There are many arithmetic operations here that can
|
||||||
// overflow or underflow, this should probably be audited.
|
// overflow or underflow, this should probably be audited.
|
||||||
|
|
||||||
|
|
||||||
let imagedata_size = Size2D::new(imagedata.Width() as i32, imagedata.Height() as i32);
|
let imagedata_size = Size2D::new(imagedata.Width(), imagedata.Height());
|
||||||
if imagedata_size.width <= 0 || imagedata_size.height <= 0 {
|
if imagedata_size.area() == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1233,76 +1200,37 @@ impl CanvasRenderingContext2DMethods for CanvasRenderingContext2D {
|
||||||
// Step 2.
|
// Step 2.
|
||||||
// TODO: throw InvalidState if buffer is detached.
|
// TODO: throw InvalidState if buffer is detached.
|
||||||
|
|
||||||
// Step 3.
|
|
||||||
if dirty_width < 0 {
|
|
||||||
dirty_x += dirty_width;
|
|
||||||
dirty_width = -dirty_width;
|
|
||||||
}
|
|
||||||
if dirty_height < 0 {
|
|
||||||
dirty_y += dirty_height;
|
|
||||||
dirty_height = -dirty_height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore any pixel that would be drawn before the beginning of the
|
|
||||||
// canvas surface.
|
|
||||||
let mut dest_x = dx + dirty_x;
|
|
||||||
let mut dest_y = dy + dirty_y;
|
|
||||||
if dest_x < 0 {
|
|
||||||
dirty_x -= dest_x;
|
|
||||||
dirty_width += dest_x;
|
|
||||||
dest_x = 0;
|
|
||||||
}
|
|
||||||
if dest_y < 0 {
|
|
||||||
dirty_y -= dest_y;
|
|
||||||
dirty_height += dest_y;
|
|
||||||
dest_y = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 4.
|
|
||||||
if dirty_x < 0 {
|
|
||||||
dirty_width += dirty_x;
|
|
||||||
dirty_x = 0;
|
|
||||||
}
|
|
||||||
if dirty_y < 0 {
|
|
||||||
dirty_height += dirty_y;
|
|
||||||
dirty_y = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 5.
|
|
||||||
if dirty_x + dirty_width > imagedata_size.width {
|
|
||||||
dirty_width = imagedata_size.width - dirty_x;
|
|
||||||
}
|
|
||||||
if dirty_y + dirty_height > imagedata_size.height {
|
|
||||||
dirty_height = imagedata_size.height - dirty_y;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME(nox): This is probably wrong when this is a context for an
|
// FIXME(nox): This is probably wrong when this is a context for an
|
||||||
// offscreen canvas.
|
// offscreen canvas.
|
||||||
let canvas_size = self.canvas.as_ref().map_or(Size2D::zero(), |c| c.get_size()).to_i32();
|
let canvas_size = self.canvas.as_ref().map_or(Size2D::zero(), |c| c.get_size());
|
||||||
let origin = Point2D::new(dest_x, dest_y);
|
|
||||||
let drawable_size = (origin - canvas_size.to_vector().to_point()).to_size().abs();
|
|
||||||
|
|
||||||
// We take care of ignoring any pixel that would be drawn after the end
|
// Steps 3-6.
|
||||||
// of the canvas surface.
|
let (src_origin, src_size) = adjust_size_sign(
|
||||||
dirty_width = dirty_width.min(drawable_size.width);
|
Point2D::new(dirty_x, dirty_y),
|
||||||
dirty_height = dirty_height.min(drawable_size.height);
|
Size2D::new(dirty_width, dirty_height),
|
||||||
|
);
|
||||||
// Step 6.
|
let src_rect = match clip(src_origin, src_size, imagedata_size) {
|
||||||
if dirty_width <= 0 || dirty_height <= 0 {
|
Some(rect) => rect,
|
||||||
return;
|
None => return,
|
||||||
}
|
};
|
||||||
|
let (dst_origin, _) = adjust_size_sign(
|
||||||
let dirty_size = Size2D::new(dirty_width, dirty_height);
|
Point2D::new(dirty_x.saturating_add(dx), dirty_y.saturating_add(dy)),
|
||||||
let dirty_rect = Rect::new(Point2D::new(dirty_x, dirty_y), dirty_size);
|
Size2D::new(dirty_width, dirty_height),
|
||||||
|
);
|
||||||
|
// By clipping to the canvas surface, we avoid sending any pixel
|
||||||
|
// that would fall outside it.
|
||||||
|
let dst_rect = match clip(dst_origin, src_rect.size, canvas_size) {
|
||||||
|
Some(rect) => rect,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
// Step 7.
|
// Step 7.
|
||||||
let (sender, receiver) = ipc::bytes_channel().unwrap();
|
let (sender, receiver) = ipc::bytes_channel().unwrap();
|
||||||
self.send_canvas_2d_msg(Canvas2dMsg::PutImageData(
|
let pixels = unsafe {
|
||||||
receiver,
|
&imagedata.get_rect(Rect::new(src_rect.origin, dst_rect.size))
|
||||||
origin.to_vector(),
|
};
|
||||||
dirty_size,
|
self.send_canvas_2d_msg(Canvas2dMsg::PutImageData(dst_rect, receiver));
|
||||||
));
|
sender.send(pixels).unwrap();
|
||||||
sender.send(unsafe { &imagedata.get_rect(dirty_rect.try_cast().unwrap()) }).unwrap();
|
|
||||||
self.mark_as_dirty();
|
self.mark_as_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1552,7 +1480,7 @@ fn is_rect_valid(rect: Rect<f64>) -> bool {
|
||||||
rect.size.width > 0.0 && rect.size.height > 0.0
|
rect.size.width > 0.0 && rect.size.height > 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#serialisation-of-a-colour
|
// https://html.spec.whatwg.org/multipage/#serialisation-of-a-color
|
||||||
fn serialize<W>(color: &RGBA, dest: &mut W) -> fmt::Result
|
fn serialize<W>(color: &RGBA, dest: &mut W) -> fmt::Result
|
||||||
where
|
where
|
||||||
W: fmt::Write,
|
W: fmt::Write,
|
||||||
|
@ -1583,3 +1511,36 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn adjust_size_sign(
|
||||||
|
mut origin: Point2D<i32>,
|
||||||
|
mut size: Size2D<i32>,
|
||||||
|
) -> (Point2D<i32>, Size2D<u32>) {
|
||||||
|
if size.width < 0 {
|
||||||
|
size.width = -size.width;
|
||||||
|
origin.x = origin.x.saturating_sub(size.width);
|
||||||
|
}
|
||||||
|
if size.height < 0 {
|
||||||
|
size.height = -size.height;
|
||||||
|
origin.y = origin.y.saturating_sub(size.height);
|
||||||
|
}
|
||||||
|
(origin, size.to_u32())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clip(
|
||||||
|
mut origin: Point2D<i32>,
|
||||||
|
mut size: Size2D<u32>,
|
||||||
|
surface: Size2D<u32>,
|
||||||
|
) -> Option<Rect<u32>> {
|
||||||
|
if origin.x < 0 {
|
||||||
|
size.width = size.width.saturating_sub(-origin.x as u32);
|
||||||
|
origin.x = 0;
|
||||||
|
}
|
||||||
|
if origin.y < 0 {
|
||||||
|
size.height = size.height.saturating_sub(-origin.y as u32);
|
||||||
|
origin.y = 0;
|
||||||
|
}
|
||||||
|
Rect::new(origin.to_u32(), size)
|
||||||
|
.intersection(&Rect::from_size(surface))
|
||||||
|
.filter(|rect| !rect.is_empty())
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue