use skia_safe::{
BlendMode as SkBlendMode, Canvas as SkCanvas, ColorSpace as SkColorSpace,
ColorType, ImageInfo, Matrix, Paint as SkPaint, Point as SkPoint, RRect,
Rect as SkRect,
canvas::{SaveLayerRec, SrcRectConstraint},
};
use crate::{
backend::resolve_engine,
color::{
RgbaLinear, linear_srgb_color_space, rgba_linear_to_unpremul_color4f,
},
context::page::{ExportOptions, PageRecorder},
error::Error,
filter::ImageFilter,
geometry::{Affine, Point, Rect},
image::Image,
paint::Paint,
path::Path,
pixels::{RawFrame, RawFrameOptions, SamplingMode, SurfaceOptions},
surface::Surface,
text::{TextAlign, TextBoxOptions, TextLayout, VerticalAlign},
};
pub struct Recorder {
recorder: PageRecorder,
bounds: Rect,
}
pub struct Canvas<'a> {
canvas: &'a SkCanvas,
working_color_space: SkColorSpace,
}
#[derive(Default)]
pub struct SaveLayerOptions<'a> {
pub paint: Option<&'a Paint>,
pub bounds: Option<Rect>,
pub backdrop: Option<&'a ImageFilter>,
}
impl Recorder {
pub fn new(bounds: Rect) -> Result<Self, Error> {
if bounds.is_empty()
|| !bounds.width().is_finite()
|| !bounds.height().is_finite()
{
return Err(Error::InvalidDimensions {
width: bounds.width(),
height: bounds.height(),
});
}
let sk_bounds = to_sk_rect(bounds);
let recorder = PageRecorder::new(sk_bounds);
Ok(Self { recorder, bounds })
}
pub fn record(&mut self, f: impl FnOnce(&mut Canvas<'_>)) {
let working_cs = linear_srgb_color_space();
self.recorder.append(|skia_canvas| {
let mut canvas = Canvas::new(skia_canvas, working_cs.clone());
f(&mut canvas);
});
}
pub fn render_raw(
&mut self,
surface_options: SurfaceOptions,
frame_options: RawFrameOptions,
) -> Result<RawFrame, Error> {
let surface_color_space =
surface_options.color_space.to_skia_color_space()?;
let dst_color_type = frame_options.pixel_format.to_skia_color_type()?;
let dst_alpha_type = frame_options.pixel_format.to_skia_alpha_type();
let dst_color_space =
frame_options.color_space.to_skia_color_space()?;
let density = if surface_options.density.is_finite()
&& surface_options.density > 0.0
{
surface_options.density
} else {
1.0
};
let scaled_w = (self.bounds.width() * density).floor().max(0.0) as i32;
let scaled_h = (self.bounds.height() * density).floor().max(0.0) as i32;
if scaled_w <= 0 || scaled_h <= 0 {
return Err(Error::InvalidDimensions {
width: self.bounds.width(),
height: self.bounds.height(),
});
}
let dst_info = ImageInfo::new(
(scaled_w, scaled_h),
dst_color_type,
dst_alpha_type,
dst_color_space,
);
let export_options = ExportOptions {
density,
color_type: ColorType::RGBAF16,
color_space: surface_color_space,
msaa: surface_options.msaa,
..ExportOptions::default()
};
let internal_engine = resolve_engine(surface_options.engine)?;
let page = self.recorder.get_page();
let pixels = page
.render_raw(export_options, dst_info, internal_engine)
.map_err(|reason| Error::Render { reason })?;
let stride =
(scaled_w as usize) * frame_options.pixel_format.bytes_per_pixel();
Ok(RawFrame::new(
scaled_w as u32,
scaled_h as u32,
stride,
frame_options.pixel_format,
frame_options.color_space,
pixels,
))
}
pub fn bounds(&self) -> Rect {
self.bounds
}
}
impl Canvas<'_> {
pub(crate) fn new(
canvas: &SkCanvas,
working_color_space: SkColorSpace,
) -> Canvas<'_> {
Canvas {
canvas,
working_color_space,
}
}
pub fn clear(&mut self, color: RgbaLinear) {
let mut paint = SkPaint::default();
paint.set_color4f(
rgba_linear_to_unpremul_color4f(color),
Some(&self.working_color_space),
);
paint.set_blend_mode(SkBlendMode::Src);
self.canvas.draw_paint(&paint);
}
pub fn save(&mut self) {
self.canvas.save();
}
pub fn restore(&mut self) {
self.canvas.restore();
}
pub fn translate(&mut self, point: Point) {
self.canvas.translate(SkPoint::new(point.x, point.y));
}
pub fn rotate_degrees(&mut self, degrees: f32, pivot: Option<Point>) {
let pivot = pivot.map(|p| SkPoint::new(p.x, p.y));
self.canvas.rotate(degrees, pivot);
}
pub fn scale(&mut self, sx: f32, sy: f32) {
self.canvas.scale((sx, sy));
}
pub fn concat_transform(&mut self, transform: Affine) {
let matrix = Matrix::from_affine(&[
transform.a,
transform.b,
transform.c,
transform.d,
transform.tx,
transform.ty,
]);
self.canvas.concat(&matrix);
}
pub fn save_layer(&mut self, paint: Option<&Paint>) {
if let Some(p) = paint {
let sk_paint = p.to_skia_paint(&self.working_color_space);
let rec = SaveLayerRec::default().paint(&sk_paint);
self.canvas.save_layer(&rec);
} else {
let rec = SaveLayerRec::default();
self.canvas.save_layer(&rec);
}
}
pub fn save_layer_with(&mut self, options: SaveLayerOptions) {
let sk_paint = options
.paint
.map(|p| p.to_skia_paint(&self.working_color_space));
let sk_bounds = options.bounds.map(to_sk_rect);
let mut rec = SaveLayerRec::default();
if let Some(p) = sk_paint.as_ref() {
rec = rec.paint(p);
}
if let Some(b) = sk_bounds.as_ref() {
rec = rec.bounds(b);
}
if let Some(backdrop) = options.backdrop {
rec = rec.backdrop(&backdrop.inner);
}
self.canvas.save_layer(&rec);
}
pub fn clip_rect(&mut self, rect: Rect) {
self.canvas.clip_rect(to_sk_rect(rect), None, true);
}
pub fn clip_rrect(&mut self, rect: Rect, radius: f32) {
let rrect = RRect::new_rect_xy(to_sk_rect(rect), radius, radius);
self.canvas.clip_rrect(rrect, None, true);
}
pub fn clip_path(&mut self, path: &Path) {
self.canvas.clip_path(&path.inner, None, true);
}
pub fn draw_path(&mut self, path: &Path, paint: &Paint) {
self.canvas.draw_path(
&path.inner,
&paint.to_skia_paint(&self.working_color_space),
);
}
pub fn draw_line(&mut self, p1: Point, p2: Point, paint: &Paint) {
self.canvas.draw_line(
SkPoint::new(p1.x, p1.y),
SkPoint::new(p2.x, p2.y),
&paint.to_skia_paint(&self.working_color_space),
);
}
pub fn draw_image_src(
&mut self,
image: &Image,
src: Rect,
dst: Rect,
paint: Option<&Paint>,
sampling: SamplingMode,
) {
let src_rect = to_sk_rect(src);
let dst_rect = to_sk_rect(dst);
let sk_paint =
paint.map(|p| p.to_skia_paint(&self.working_color_space));
let default_paint = SkPaint::default();
let p_ref = sk_paint.as_ref().unwrap_or(&default_paint);
self.canvas.draw_image_rect_with_sampling_options(
&image.inner,
Some((&src_rect, SrcRectConstraint::Strict)),
dst_rect,
sampling.to_skia(),
p_ref,
);
}
pub fn draw_surface(
&mut self,
source: &mut Surface,
x: f32,
y: f32,
paint: Option<&Paint>,
) {
let image = source.snapshot();
let sk_paint =
paint.map(|p| p.to_skia_paint(&self.working_color_space));
self.canvas.draw_image(
&image.inner,
SkPoint::new(x, y),
sk_paint.as_ref(),
);
}
pub fn draw_rect(&mut self, rect: Rect, paint: &Paint) {
self.canvas.draw_rect(
to_sk_rect(rect),
&paint.to_skia_paint(&self.working_color_space),
);
}
pub fn draw_rounded_rect(
&mut self,
rect: Rect,
radius: f32,
paint: &Paint,
) {
let rrect = RRect::new_rect_xy(to_sk_rect(rect), radius, radius);
self.canvas
.draw_rrect(rrect, &paint.to_skia_paint(&self.working_color_space));
}
pub fn draw_oval(&mut self, rect: Rect, paint: &Paint) {
self.canvas.draw_oval(
to_sk_rect(rect),
&paint.to_skia_paint(&self.working_color_space),
);
}
pub fn draw_image_rect(&mut self, image: &Image, dst: Rect, opacity: f32) {
let dst_rect = to_sk_rect(dst);
let mut paint = SkPaint::default();
paint.set_anti_alias(true);
paint.set_alpha_f(opacity.clamp(0.0, 1.0));
self.canvas
.draw_image_rect(&image.inner, None, dst_rect, &paint);
}
pub fn draw_text_layout(&mut self, layout: &TextLayout, x: f32, y: f32) {
layout.paragraph.paint(self.canvas, (x, y));
}
pub fn draw_text_box(
&mut self,
text: &str,
rect: Rect,
options: &TextBoxOptions,
) {
use skia_safe::{
FontMgr, FontStyle,
font_style::{Slant, Weight, Width},
textlayout::{
FontCollection, ParagraphBuilder, ParagraphStyle,
TextAlign as SkTextAlign, TextStyle,
},
};
let mut paint = SkPaint::default();
let modulated = options.color.with_opacity(options.opacity);
paint.set_color4f(
rgba_linear_to_unpremul_color4f(modulated),
Some(&self.working_color_space),
);
paint.set_anti_alias(true);
let font_mgr = FontMgr::new();
let mut font_collection = FontCollection::new();
font_collection.set_default_font_manager(font_mgr, None);
let mut text_style = TextStyle::new();
text_style.set_foreground_paint(&paint);
text_style.set_font_size(options.font_size);
if let Some(family) = &options.font_family {
text_style.set_font_families(&[family.as_str()]);
}
text_style.set_font_style(FontStyle::new(
Weight::from(options.font_weight),
Width::NORMAL,
Slant::Upright,
));
let mut paragraph_style = ParagraphStyle::new();
paragraph_style.set_text_align(match options.horizontal_align {
TextAlign::Left => SkTextAlign::Left,
TextAlign::Center => SkTextAlign::Center,
TextAlign::Right => SkTextAlign::Right,
});
paragraph_style.set_text_style(&text_style);
let mut builder =
ParagraphBuilder::new(¶graph_style, font_collection);
builder.add_text(text);
let mut paragraph = builder.build();
paragraph.layout(rect.width());
let y_offset = match options.vertical_align {
VerticalAlign::Top => 0.0,
VerticalAlign::Center => {
(rect.height() - paragraph.height()).max(0.0) / 2.0
}
VerticalAlign::Bottom => {
(rect.height() - paragraph.height()).max(0.0)
}
};
paragraph.paint(self.canvas, (rect.left, rect.top + y_offset));
}
}
fn to_sk_rect(rect: Rect) -> SkRect {
SkRect::from_ltrb(rect.left, rect.top, rect.right, rect.bottom)
}