use std::collections::HashMap; use crate::shapes::{Corners, Fill, ImageFill, Kind, Path, Shape, Stroke, StrokeCap, StrokeKind}; use skia::Rect; use skia_safe::{self as skia, RRect}; use super::RenderState; fn draw_stroke_on_rect( canvas: &skia::Canvas, stroke: &Stroke, rect: &Rect, selrect: &Rect, corners: &Option, svg_attrs: &HashMap, scale: f32, ) { // Draw the different kind of strokes for a rect is straightforward, we just need apply a stroke to: // - The same rect if it's a center stroke // - A bigger rect if it's an outer stroke // - A smaller rect if it's an outer stroke let stroke_rect = stroke.outer_rect(rect); let paint = stroke.to_paint(selrect, svg_attrs, scale); match corners { Some(radii) => { let radii = stroke.outer_corners(radii); let rrect = RRect::new_rect_radii(stroke_rect, &radii); canvas.draw_rrect(rrect, &paint); } None => { canvas.draw_rect(&stroke_rect, &paint); } } } fn draw_stroke_on_circle( canvas: &skia::Canvas, stroke: &Stroke, rect: &Rect, selrect: &Rect, svg_attrs: &HashMap, scale: f32, ) { // Draw the different kind of strokes for an oval is straightforward, we just need apply a stroke to: // - The same oval if it's a center stroke // - A bigger oval if it's an outer stroke // - A smaller oval if it's an outer stroke let stroke_rect = stroke.outer_rect(rect); canvas.draw_oval(&stroke_rect, &stroke.to_paint(selrect, svg_attrs, scale)); } fn draw_stroke_on_path( canvas: &skia::Canvas, stroke: &Stroke, path: &Path, selrect: &Rect, path_transform: Option<&skia::Matrix>, svg_attrs: &HashMap, scale: f32, ) { let mut skia_path = path.to_skia_path(); skia_path.transform(path_transform.unwrap()); let is_open = path.is_open(); let paint_stroke = stroke.to_stroked_paint(is_open, selrect, svg_attrs, scale); // Draw the different kind of strokes for a path requires different strategies: match stroke.render_kind(is_open) { // For inner stroke we draw a center stroke (with double width) and clip to the original path (that way the extra outer stroke is removed) StrokeKind::InnerStroke => { canvas.clip_path(&skia_path, skia::ClipOp::Intersect, true); canvas.draw_path(&skia_path, &paint_stroke); } // For center stroke we don't need to do anything extra StrokeKind::CenterStroke => { canvas.draw_path(&skia_path, &paint_stroke); } // For outer stroke we draw a center stroke (with double width) and use another path with blend mode clear to remove the inner stroke added StrokeKind::OuterStroke => { let mut paint = skia::Paint::default(); paint.set_blend_mode(skia::BlendMode::SrcOver); paint.set_anti_alias(true); let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint); canvas.save_layer(&layer_rec); canvas.draw_path(&skia_path, &paint_stroke); let mut clear_paint = skia::Paint::default(); clear_paint.set_blend_mode(skia::BlendMode::Clear); clear_paint.set_anti_alias(true); canvas.draw_path(&skia_path, &clear_paint); canvas.restore(); } } handle_stroke_caps( &mut skia_path, stroke, &selrect, canvas, is_open, svg_attrs, scale, ); } fn handle_stroke_cap( canvas: &skia::Canvas, cap: StrokeCap, width: f32, paint: &mut skia::Paint, p1: &skia::Point, p2: &skia::Point, ) { paint.set_style(skia::PaintStyle::Fill); paint.set_blend_mode(skia::BlendMode::Src); match cap { StrokeCap::None => {} StrokeCap::Line => { // We also draw this square cap to fill the gap between the path and the arrow draw_square_cap(canvas, &paint, p1, p2, width, 0.); paint.set_style(skia::PaintStyle::Stroke); draw_arrow_cap(canvas, &paint, p1, p2, width * 4.); } StrokeCap::Triangle => { draw_triangle_cap(canvas, &paint, p1, p2, width * 4.); } StrokeCap::Rectangle => { draw_square_cap(canvas, &paint, p1, p2, width * 4., 0.); } StrokeCap::Circle => { canvas.draw_circle((p1.x, p1.y), width * 2., &paint); } StrokeCap::Diamond => { draw_square_cap(canvas, &paint, p1, p2, width * 4., 45.); } StrokeCap::Round => { canvas.draw_circle((p1.x, p1.y), width / 2.0, &paint); } StrokeCap::Square => { draw_square_cap(canvas, &paint, p1, p2, width, 0.); } } } fn handle_stroke_caps( path: &mut skia::Path, stroke: &Stroke, selrect: &Rect, canvas: &skia::Canvas, is_open: bool, svg_attrs: &HashMap, dpr_scale: f32, ) { let points_count = path.count_points(); let mut points = vec![skia::Point::default(); points_count]; let c_points = path.get_points(&mut points); // Closed shapes don't have caps if c_points >= 2 && is_open { let first_point = points.first().unwrap(); let last_point = points.last().unwrap(); let mut paint_stroke = stroke.to_stroked_paint(is_open, selrect, svg_attrs, dpr_scale); handle_stroke_cap( canvas, stroke.cap_start, stroke.width, &mut paint_stroke, first_point, &points[1], ); handle_stroke_cap( canvas, stroke.cap_end, stroke.width, &mut paint_stroke, last_point, &points[points_count - 2], ); } } fn draw_square_cap( canvas: &skia::Canvas, paint: &skia::Paint, center: &skia::Point, direction: &skia::Point, size: f32, extra_rotation: f32, ) { let dx = direction.x - center.x; let dy = direction.y - center.y; let angle = dy.atan2(dx); let mut matrix = skia::Matrix::new_identity(); matrix.pre_rotate( angle.to_degrees() + extra_rotation, skia::Point::new(center.x, center.y), ); let half_size = size / 2.0; let rect = skia::Rect::from_xywh(center.x - half_size, center.y - half_size, size, size); let points = [ skia::Point::new(rect.left(), rect.top()), skia::Point::new(rect.right(), rect.top()), skia::Point::new(rect.right(), rect.bottom()), skia::Point::new(rect.left(), rect.bottom()), ]; let mut transformed_points = points.clone(); matrix.map_points(&mut transformed_points, &points); let mut path = skia::Path::new(); path.move_to(skia::Point::new(center.x, center.y)); path.move_to(transformed_points[0]); path.line_to(transformed_points[1]); path.line_to(transformed_points[2]); path.line_to(transformed_points[3]); path.close(); canvas.draw_path(&path, paint); } fn draw_arrow_cap( canvas: &skia::Canvas, paint: &skia::Paint, center: &skia::Point, direction: &skia::Point, size: f32, ) { let dx = direction.x - center.x; let dy = direction.y - center.y; let angle = dy.atan2(dx); let mut matrix = skia::Matrix::new_identity(); matrix.pre_rotate( angle.to_degrees() - 90., skia::Point::new(center.x, center.y), ); let half_height = size / 2.; let points = [ skia::Point::new(center.x, center.y - half_height), skia::Point::new(center.x - size, center.y + half_height), skia::Point::new(center.x + size, center.y + half_height), ]; let mut transformed_points = points.clone(); matrix.map_points(&mut transformed_points, &points); let mut path = skia::Path::new(); path.move_to(transformed_points[1]); path.line_to(transformed_points[0]); path.line_to(transformed_points[2]); path.move_to(skia::Point::new(center.x, center.y)); path.line_to(transformed_points[0]); canvas.draw_path(&path, paint); } fn draw_triangle_cap( canvas: &skia::Canvas, paint: &skia::Paint, center: &skia::Point, direction: &skia::Point, size: f32, ) { let dx = direction.x - center.x; let dy = direction.y - center.y; let angle = dy.atan2(dx); let mut matrix = skia::Matrix::new_identity(); matrix.pre_rotate( angle.to_degrees() - 90., skia::Point::new(center.x, center.y), ); let half_height = size / 2.; let points = [ skia::Point::new(center.x, center.y - half_height), skia::Point::new(center.x - size, center.y + half_height), skia::Point::new(center.x + size, center.y + half_height), ]; let mut transformed_points = points.clone(); matrix.map_points(&mut transformed_points, &points); let mut path = skia::Path::new(); path.move_to(transformed_points[0]); path.line_to(transformed_points[1]); path.line_to(transformed_points[2]); path.close(); canvas.draw_path(&path, paint); } fn calculate_scaled_rect(size: (i32, i32), container: &Rect, delta: f32) -> Rect { let (width, height) = (size.0 as f32, size.1 as f32); let image_aspect_ratio = width / height; // Container size let container_width = container.width(); let container_height = container.height(); let container_aspect_ratio = container_width / container_height; let scale = if image_aspect_ratio > container_aspect_ratio { container_height / height } else { container_width / width }; let scaled_width = width * scale; let scaled_height = height * scale; Rect::from_xywh( container.left - delta - (scaled_width - container_width) / 2.0, container.top - delta - (scaled_height - container_height) / 2.0, scaled_width + (2. * delta) + (scaled_width - container_width), scaled_height + (2. * delta) + (scaled_width - container_width), ) } fn draw_image_stroke_in_container( render_state: &mut RenderState, shape: &Shape, stroke: &Stroke, image_fill: &ImageFill, ) { let image = render_state.images.get(&image_fill.id()); if image.is_none() { return; } let size = image_fill.size(); let canvas = render_state.surfaces.shape.canvas(); let kind = &shape.kind; let container = &shape.selrect; let path_transform = shape.to_path_transform(); let svg_attrs = &shape.svg_attrs; let dpr_scale = render_state.viewbox.zoom * render_state.options.dpr(); // Save canvas and layer state let mut pb = skia::Paint::default(); pb.set_blend_mode(skia::BlendMode::SrcOver); pb.set_anti_alias(true); let layer_rec = skia::canvas::SaveLayerRec::default().paint(&pb); canvas.save_layer(&layer_rec); // Draw the stroke based on the kind, we are using this stroke as a "selector" of the area of the image we want to show. let outer_rect = stroke.outer_rect(container); match kind { Kind::Rect(rect, corners) => draw_stroke_on_rect( canvas, stroke, rect, &outer_rect, corners, svg_attrs, dpr_scale, ), Kind::Circle(rect) => { draw_stroke_on_circle(canvas, stroke, rect, &outer_rect, svg_attrs, dpr_scale) } Kind::SVGRaw(_) | Kind::Group(_) => unreachable!("This shape should not have strokes"), Kind::Path(p) | Kind::Bool(_, p) => { canvas.save(); let mut path = p.to_skia_path(); path.transform(&path_transform.unwrap()); let stroke_kind = stroke.render_kind(p.is_open()); match stroke_kind { StrokeKind::InnerStroke => { canvas.clip_path(&path, skia::ClipOp::Intersect, true); } StrokeKind::CenterStroke => {} StrokeKind::OuterStroke => { canvas.clip_path(&path, skia::ClipOp::Difference, true); } } let is_open = p.is_open(); let mut paint = stroke.to_stroked_paint(is_open, &outer_rect, svg_attrs, dpr_scale); canvas.draw_path(&path, &paint); canvas.restore(); if stroke.render_kind(is_open) == StrokeKind::OuterStroke { // Small extra inner stroke to overlap with the fill and avoid unnecesary artifacts paint.set_stroke_width(1. / dpr_scale); canvas.draw_path(&path, &paint); } handle_stroke_caps( &mut path, stroke, &outer_rect, canvas, is_open, svg_attrs, dpr_scale, ); } } // Draw the image. We are using now the SrcIn blend mode, so the rendered piece of image will the area of the stroke over the image. let mut image_paint = skia::Paint::default(); image_paint.set_blend_mode(skia::BlendMode::SrcIn); image_paint.set_anti_alias(true); // Compute scaled rect and clip to it let dest_rect = calculate_scaled_rect(size, container, stroke.delta()); canvas.clip_rect(dest_rect, skia::ClipOp::Intersect, true); canvas.draw_image_rect(image.unwrap(), None, dest_rect, &image_paint); // Clear outer stroke for paths if necessary. When adding an outer stroke we need to empty the stroke added too in the inner area. if let Kind::Path(p) = kind { if stroke.render_kind(p.is_open()) == StrokeKind::OuterStroke { let mut path = p.to_skia_path(); path.transform(&path_transform.unwrap()); let mut clear_paint = skia::Paint::default(); clear_paint.set_blend_mode(skia::BlendMode::Clear); clear_paint.set_anti_alias(true); canvas.draw_path(&path, &clear_paint); } } // Restore canvas state canvas.restore(); } /** * This SHOULD be the only public function in this module. */ pub fn render(render_state: &mut RenderState, shape: &Shape, stroke: &Stroke) { let canvas = render_state.surfaces.shape.canvas(); let dpr_scale = render_state.viewbox.zoom * render_state.options.dpr(); let selrect = shape.selrect; let path_transform = shape.to_path_transform(); let kind = &shape.kind; let svg_attrs = &shape.svg_attrs; if let Fill::Image(image_fill) = &stroke.fill { draw_image_stroke_in_container(render_state, shape, stroke, image_fill); } else { match kind { Kind::Rect(rect, corners) => draw_stroke_on_rect( canvas, stroke, rect, &selrect, corners, svg_attrs, dpr_scale, ), Kind::Circle(rect) => { draw_stroke_on_circle(canvas, stroke, rect, &selrect, &svg_attrs, dpr_scale) } Kind::Path(path) | Kind::Bool(_, path) => { let svg_attrs = &shape.svg_attrs; draw_stroke_on_path( canvas, stroke, path, &selrect, path_transform.as_ref(), svg_attrs, dpr_scale, ); } Kind::SVGRaw(_) | Kind::Group(_) => unreachable!("This shape should not have strokes"), } } }