Implement the `drawImage()` method on canvas.

Closes #223.
This commit is contained in:
Patrick Walton 2020-04-02 16:30:49 -07:00
parent ba7fe4be39
commit cd37791d72
4 changed files with 167 additions and 16 deletions

View File

@ -112,13 +112,23 @@ pub struct CanvasRenderingContext2D {
}
impl CanvasRenderingContext2D {
// Finalization
// Canvas accessors
#[inline]
pub fn canvas(&self) -> &Canvas {
&self.canvas
}
#[inline]
pub fn into_canvas(self) -> Canvas {
self.canvas
}
#[inline]
pub fn font_context(&self) -> CanvasFontContext {
self.font_context.clone()
}
// Drawing rectangles
#[inline]
@ -413,6 +423,30 @@ impl CanvasRenderingContext2D {
self.current_state.global_composite_operation = new_composite_operation;
}
// Drawing images
#[inline]
pub fn draw_image<I, L>(&mut self, image: I, dest_location: L)
where I: CanvasImageSource, L: CanvasImageDestLocation {
let pattern = image.to_pattern(self, Transform2F::default());
let src_rect = RectF::new(vec2f(0.0, 0.0), pattern.size().to_f32());
self.draw_subimage(pattern, src_rect, dest_location)
}
pub fn draw_subimage<I, L>(&mut self, image: I, src_location: RectF, dest_location: L)
where I: CanvasImageSource, L: CanvasImageDestLocation {
let dest_size = dest_location.size().unwrap_or(src_location.size());
let scale = dest_size / src_location.size();
let offset = dest_location.origin() - src_location.origin();
let transform = Transform2F::from_scale(scale).translate(offset);
let pattern = image.to_pattern(self, transform);
let old_fill_paint = self.current_state.fill_paint.clone();
self.set_fill_style(pattern);
self.fill_rect(RectF::new(dest_location.origin(), dest_size));
self.current_state.fill_paint = old_fill_paint;
}
// Image smoothing
#[inline]
@ -451,15 +485,19 @@ impl CanvasRenderingContext2D {
// Extensions
pub fn create_pattern_from_canvas(&mut self, canvas: Canvas) -> Pattern {
pub fn create_pattern_from_canvas(&mut self, canvas: Canvas, transform: Transform2F)
-> Pattern {
let subscene = canvas.into_scene();
let subscene_size = subscene.view_box().size().ceil().to_i32();
let render_target = RenderTarget::new(subscene_size, String::new());
let render_target_id = self.canvas.scene.push_render_target(render_target);
self.canvas.scene.append_scene(subscene);
self.canvas.scene.pop_render_target();
let pattern_source = PatternSource::RenderTarget(render_target_id);
Pattern::new(pattern_source, Transform2F::default(), PatternFlags::empty())
let pattern_source = PatternSource::RenderTarget {
id: render_target_id,
size: subscene_size,
};
Pattern::new(pattern_source, transform, PatternFlags::empty())
}
}
@ -782,6 +820,54 @@ pub enum ImageSmoothingQuality {
High,
}
pub trait CanvasImageSource {
fn to_pattern(self, dest_context: &mut CanvasRenderingContext2D, transform: Transform2F)
-> Pattern;
}
pub trait CanvasImageDestLocation {
fn origin(&self) -> Vector2F;
fn size(&self) -> Option<Vector2F>;
}
impl CanvasImageSource for Pattern {
#[inline]
fn to_pattern(mut self, _: &mut CanvasRenderingContext2D, transform: Transform2F) -> Pattern {
self.transform(transform);
self
}
}
impl CanvasImageSource for Canvas {
#[inline]
fn to_pattern(self, dest_context: &mut CanvasRenderingContext2D, transform: Transform2F)
-> Pattern {
dest_context.create_pattern_from_canvas(self, transform)
}
}
impl CanvasImageDestLocation for RectF {
#[inline]
fn origin(&self) -> Vector2F {
RectF::origin(*self)
}
#[inline]
fn size(&self) -> Option<Vector2F> {
Some(RectF::size(*self))
}
}
impl CanvasImageDestLocation for Vector2F {
#[inline]
fn origin(&self) -> Vector2F {
*self
}
#[inline]
fn size(&self) -> Option<Vector2F> {
None
}
}
impl Debug for Path2D {
fn fmt(&self, formatter: &mut Formatter) -> Result<(), FmtError> {
self.clone().into_outline().fmt(formatter)

View File

@ -34,7 +34,10 @@ pub struct Pattern {
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum PatternSource {
Image(Image),
RenderTarget(RenderTargetId),
RenderTarget {
id: RenderTargetId,
size: Vector2I,
}
}
/// RGBA, non-premultiplied.
@ -61,6 +64,19 @@ impl Pattern {
pub fn new(source: PatternSource, transform: Transform2F, flags: PatternFlags) -> Pattern {
Pattern { source, transform, flags }
}
#[inline]
pub fn transform(&mut self, transform: Transform2F) {
self.transform *= transform
}
#[inline]
pub fn size(&self) -> Vector2I {
match self.source {
PatternSource::Image(ref image) => image.size(),
PatternSource::RenderTarget { size, .. } => size,
}
}
}
impl Image {
@ -104,7 +120,7 @@ impl PatternSource {
pub fn is_opaque(&self) -> bool {
match *self {
PatternSource::Image(ref image) => image.is_opaque(),
PatternSource::RenderTarget(_) => {
PatternSource::RenderTarget { .. } => {
// TODO(pcwalton): Maybe do something smarter here?
false
}

View File

@ -236,7 +236,7 @@ impl Palette {
}
Paint::Pattern(ref pattern) => {
match pattern.source {
PatternSource::RenderTarget(render_target_id) => {
PatternSource::RenderTarget { id: render_target_id, .. } => {
texture_location =
render_target_metadata[render_target_id.0 as usize].location;
}
@ -311,7 +311,7 @@ impl Palette {
transform.inverse() * render_transform
}
Paint::Pattern(Pattern {
source: PatternSource::RenderTarget(_),
source: PatternSource::RenderTarget { .. },
transform,
..
}) => {

View File

@ -18,6 +18,7 @@ use crate::paint::{Paint, PaintId, PaintInfo, Palette};
use pathfinder_content::effects::{BlendMode, Effects};
use pathfinder_content::fill::FillRule;
use pathfinder_content::outline::Outline;
use pathfinder_content::pattern::{Pattern, PatternSource};
use pathfinder_content::render_target::RenderTargetId;
use pathfinder_geometry::rect::RectF;
use pathfinder_geometry::transform2d::Transform2F;
@ -48,19 +49,23 @@ impl Scene {
}
pub fn push_path(&mut self, path: DrawPath) {
self.bounds = self.bounds.union_rect(path.outline.bounds());
let path_index = self.paths.len() as u32;
self.paths.push(path);
self.push_path_with_index(path_index);
}
fn push_path_with_index(&mut self, path_index: u32) {
self.bounds = self.bounds.union_rect(self.paths[path_index as usize].outline.bounds());
let new_path_count = self.paths.len() as u32;
if let Some(DisplayItem::DrawPaths {
start_index: _,
ref mut end_index
}) = self.display_list.last_mut() {
*end_index = new_path_count;
*end_index = path_index + 1;
} else {
self.display_list.push(DisplayItem::DrawPaths {
start_index: new_path_count - 1,
end_index: new_path_count,
start_index: path_index,
end_index: path_index + 1,
});
}
}
@ -100,9 +105,25 @@ impl Scene {
// Merge paints.
let mut paint_mapping = HashMap::new();
for (old_paint_index, paint) in scene.palette.paints.iter().enumerate() {
for (old_paint_index, old_paint) in scene.palette.paints.iter().enumerate() {
let old_paint_id = PaintId(old_paint_index as u16);
let new_paint_id = self.palette.push_paint(&paint);
let new_paint_id = match old_paint {
Paint::Pattern(Pattern {
source: PatternSource::RenderTarget { id: old_render_target_id, size },
transform,
flags
}) => {
self.palette.push_paint(&Paint::Pattern(Pattern {
source: PatternSource::RenderTarget {
id: render_target_mapping[old_render_target_id],
size: *size,
},
transform: *transform,
flags: *flags,
}))
}
paint => self.palette.push_paint(paint),
};
paint_mapping.insert(old_paint_id, new_paint_id);
}
@ -116,7 +137,7 @@ impl Scene {
// Merge draw paths.
let mut draw_path_mapping = Vec::with_capacity(scene.paths.len());
for draw_path in scene.paths {
draw_path_mapping.push(self.paths.len());
draw_path_mapping.push(self.paths.len() as u32);
self.paths.push(DrawPath {
outline: draw_path.outline,
paint: paint_mapping[&draw_path.paint],
@ -129,6 +150,34 @@ impl Scene {
name: draw_path.name,
});
}
// Merge display items.
for display_item in scene.display_list {
match display_item {
DisplayItem::DrawRenderTarget {
render_target: old_render_target_id,
effects,
} => {
let new_render_target_id = render_target_mapping[&old_render_target_id];
self.draw_render_target(new_render_target_id, effects)
}
DisplayItem::PushRenderTarget(old_render_target_id) => {
let new_render_target_id = render_target_mapping[&old_render_target_id];
self.display_list.push(DisplayItem::PushRenderTarget(new_render_target_id));
}
DisplayItem::PopRenderTarget => {
self.display_list.push(DisplayItem::PopRenderTarget);
}
DisplayItem::DrawPaths {
start_index: old_start_path_index,
end_index: old_end_path_index,
} => {
for old_path_index in old_start_path_index..old_end_path_index {
self.push_path_with_index(draw_path_mapping[old_path_index as usize])
}
}
}
}
}
#[inline]