use std::ops::{Deref, DerefMut};
use ribir_algo::Resource;
use ribir_types::{Angle, DeviceRect, Point, Rect, Size, Transform, Vector};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
pub use crate::FontFaceId as FaceId;
use crate::{
Brush, Color, GlyphRasterSource, PixelImage, Svg, TextDrawPayload,
color::{ColorFilterMatrix, LinearGradient, RadialGradient},
filter::{Filter, FilterLayer, FilterOp},
path::*,
path_builder::PathBuilder,
};
pub struct Painter {
init_state: PainterState,
state_stack: Vec<PainterState>,
commands: Vec<PaintCommand>,
path_builder: PathBuilder,
}
pub struct PainterResult<'a>(&'a mut Vec<PaintCommand>);
pub trait PainterBackend {
type Texture;
fn begin_frame(&mut self, surface: Color);
fn draw_commands(
&mut self, viewport: DeviceRect, commands: &[PaintCommand], global_matrix: &Transform,
output: &mut Self::Texture, glyph_provider: &dyn GlyphRasterSource,
);
fn end_frame(&mut self);
}
#[derive(Debug, Clone)]
pub struct TextCommand {
pub paint_bounds: Rect,
pub transform: Transform,
pub payload: Resource<TextDrawPayload>,
pub default_brush: CommandBrush,
pub color_filter: ColorMatrix,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PaintPath {
Share(Resource<Path>),
Own(Path),
PixelImage(Resource<PixelImage>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PaintPathAction {
Paint {
brush: CommandBrush,
painting_style: PaintingStyle,
},
Clip,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CommandBrush {
Color(Color),
Image { img: Resource<PixelImage>, color_filter: ColorMatrix },
Radial(Resource<RadialGradient>),
Linear(Resource<LinearGradient>),
}
#[repr(u32)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq)]
pub enum SpreadMethod {
#[default]
Pad,
Reflect,
Repeat,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PathCommand {
pub path: PaintPath,
pub paint_bounds: Rect,
pub transform: Transform,
pub action: PaintPathAction,
}
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub enum PaintingStyle {
#[default]
Fill,
Stroke(StrokeOptions),
}
#[derive(Debug, Clone, Copy, Default)]
pub enum PathStyle {
#[default]
Fill,
Stroke,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PaintCommand {
Path(PathCommand),
PopClip,
Bundle {
transform: Transform,
color_filter: ColorMatrix,
bounds: Rect,
cmds: Resource<Box<[PaintCommand]>>,
},
Filter {
path: PaintPath,
transform: Transform,
filter_bounds: Rect,
filters: Vec<FilterLayer>,
},
#[serde(skip)]
Text(TextCommand),
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum ColorMatrix {
Opacity(f32),
Matrix(ColorFilterMatrix),
}
impl From<ColorFilterMatrix> for ColorMatrix {
fn from(m: ColorFilterMatrix) -> Self { ColorMatrix::Matrix(m) }
}
impl Default for ColorMatrix {
fn default() -> Self { ColorMatrix::Opacity(1.) }
}
impl ColorMatrix {
pub fn identity() -> Self { ColorMatrix::Opacity(1.) }
pub fn from_opacity(a: f32) -> Self { ColorMatrix::Opacity(a) }
pub fn from_matrix(m: ColorFilterMatrix) -> Self { ColorMatrix::Matrix(m) }
fn apply_alpha(&mut self, alpha: f32) {
match self {
ColorMatrix::Opacity(a) => {
*a *= alpha;
}
ColorMatrix::Matrix(matrix) => {
matrix.apply_alpha(alpha);
}
};
}
fn apply_color_filter(&mut self, mut matrix: ColorFilterMatrix) {
let v = match self {
ColorMatrix::Opacity(a) => {
matrix.apply_alpha(*a);
ColorMatrix::Matrix(matrix)
}
ColorMatrix::Matrix(m) => ColorMatrix::Matrix(m.chains(&matrix)),
};
*self = v;
}
pub fn chains(&mut self, next: &ColorMatrix) {
match next {
ColorMatrix::Opacity(a) => self.apply_alpha(*a),
ColorMatrix::Matrix(m) => self.apply_color_filter(*m),
}
}
pub fn apply_to(&self, color: &Color) -> Color {
match self {
ColorMatrix::Opacity(a) => color.apply_alpha(*a),
ColorMatrix::Matrix(m) => m.apply_to(color),
}
}
fn is_transparent(&self) -> bool {
match self {
ColorMatrix::Opacity(a) => *a == 0.,
ColorMatrix::Matrix(m) => m.is_transparent(),
}
}
pub fn to_matrix(&self) -> ColorFilterMatrix {
match self {
ColorMatrix::Opacity(a) => ColorFilterMatrix::only_alpha(*a),
ColorMatrix::Matrix(m) => *m,
}
}
}
#[derive(Clone)]
struct PainterState {
stroke_options: StrokeOptions,
stroke_brush: Brush,
fill_brush: Brush,
style: PathStyle,
transform: Transform,
color_filter: ColorMatrix,
clip_cnt: usize,
bounds: Rect,
filters: SmallVec<[FilterState; 0]>,
}
#[derive(Clone)]
struct FilterState {
filter: Filter,
filter_start_idx: usize,
color_filter: ColorMatrix,
transform: Transform,
}
impl PainterState {
fn new(bounds: Rect) -> PainterState {
PainterState {
bounds,
stroke_options: <_>::default(),
stroke_brush: Color::BLACK.into(),
fill_brush: Color::GRAY.into(),
transform: Transform::identity(),
clip_cnt: 0,
color_filter: ColorMatrix::Opacity(1.),
style: PathStyle::Fill,
filters: SmallVec::new(),
}
}
}
impl Painter {
pub fn new(viewport: Rect) -> Self {
assert!(viewport.is_finite(), "viewport must be finite!");
let init_state = PainterState::new(viewport);
Self {
state_stack: vec![init_state.clone()],
init_state,
commands: vec![],
path_builder: Path::builder(),
}
}
pub fn fork(&self) -> Self {
let init_state = self.current_state().clone();
Painter {
state_stack: vec![init_state.clone()],
init_state,
commands: vec![],
path_builder: Path::builder(),
}
}
pub fn merge(&mut self, forked: &mut Painter) { self.commands.append(forked.finish().0); }
pub fn set_init_state(&mut self, brush: Brush) {
self.init_state.fill_brush = brush.clone();
self.init_state.stroke_brush = brush;
self.reset();
}
pub fn viewport(&self) -> &Rect { &self.init_state.bounds }
pub fn set_viewport(&mut self, bounds: Rect) { self.init_state.bounds = bounds; }
pub fn intersection_paint_bounds(&self, rect: &Rect) -> Option<Rect> {
self.paint_bounds().intersection(rect)
}
pub fn intersect_paint_bounds(&self, rect: &Rect) -> bool { self.paint_bounds().intersects(rect) }
pub fn paint_bounds(&self) -> Rect {
let s = self.current_state();
s.transform
.inverse()
.map_or_else(Rect::zero, |t| t.outer_transformed_rect(&s.bounds))
}
#[inline]
pub fn finish(&mut self) -> PainterResult<'_> {
self
.state_stack
.insert(0, self.init_state.clone());
while self.state_stack.len() > 1 {
self.restore();
}
PainterResult(&mut self.commands)
}
#[must_use]
pub fn save_guard(&mut self) -> PainterGuard<'_> {
self.save();
PainterGuard(self)
}
pub fn save(&mut self) -> &mut Self {
let mut new_state = self.current_state().clone();
new_state.filters = SmallVec::new();
self.state_stack.push(new_state);
self
}
#[inline]
pub fn restore(&mut self) {
let state = self
.state_stack
.pop()
.expect("Must have one state in stack!");
let clip_cnt = state.clip_cnt;
self.push_n_pop_cmd(clip_cnt - self.current_state().clip_cnt);
for filter in state.filters.into_iter().rev() {
self.generate_filter_bundle(
filter.transform,
filter.filter_start_idx,
filter.color_filter,
filter.filter.into_layers(),
);
}
}
pub fn reset(&mut self) {
self.fill_all_pop_clips();
self.commands.clear();
self.state_stack.clear();
self.state_stack.push(self.init_state.clone());
}
#[inline]
pub fn stroke_brush(&self) -> &Brush { &self.current_state().stroke_brush }
#[inline]
pub fn set_stroke_brush(&mut self, brush: impl Into<Brush>) -> &mut Self {
self.current_state_mut().stroke_brush = brush.into();
self
}
#[inline]
pub fn fill_brush(&self) -> &Brush { &self.current_state().fill_brush }
#[inline]
pub fn set_fill_brush(&mut self, brush: impl Into<Brush>) -> &mut Self {
self.current_state_mut().fill_brush = brush.into();
self
}
pub fn style(&self) -> PathStyle { self.current_state().style }
pub fn set_style(&mut self, style: PathStyle) -> &mut Self {
self.current_state_mut().style = style;
self
}
pub fn apply_alpha(&mut self, alpha: f32) -> &mut Self {
self
.current_state_mut()
.color_filter
.apply_alpha(alpha);
self
}
pub fn is_transparent(&self) -> bool { self.current_state().color_filter.is_transparent() }
pub fn current_color_filter(&self) -> &ColorMatrix { &self.current_state().color_filter }
#[inline]
pub fn set_strokes(&mut self, strokes: StrokeOptions) -> &mut Self {
self.current_state_mut().stroke_options = strokes;
self
}
#[inline]
pub fn line_width(&self) -> f32 { self.stroke_options().width }
#[inline]
pub fn set_line_width(&mut self, line_width: f32) -> &mut Self {
self.current_state_mut().stroke_options.width = line_width;
self
}
#[inline]
pub fn line_join(&self) -> LineJoin { self.stroke_options().line_join }
#[inline]
pub fn set_line_join(&mut self, line_join: LineJoin) -> &mut Self {
self.current_state_mut().stroke_options.line_join = line_join;
self
}
#[inline]
pub fn line_cap(&mut self) -> LineCap { self.stroke_options().line_cap }
#[inline]
pub fn set_line_cap(&mut self, line_cap: LineCap) -> &mut Self {
self.current_state_mut().stroke_options.line_cap = line_cap;
self
}
#[inline]
pub fn miter_limit(&self) -> f32 { self.stroke_options().miter_limit }
#[inline]
pub fn set_miter_limit(&mut self, miter_limit: f32) -> &mut Self {
self
.current_state_mut()
.stroke_options
.miter_limit = miter_limit;
self
}
#[inline]
pub fn transform(&self) -> &Transform { &self.current_state().transform }
#[inline]
fn color_filter(&self) -> &ColorMatrix { &self.current_state().color_filter }
#[inline]
pub fn set_transform(&mut self, transform: Transform) -> &mut Self {
self.current_state_mut().transform = transform;
self
}
pub fn apply_transform(&mut self, transform: &Transform) -> &mut Self {
let t = transform.then(self.transform());
self.set_transform(t);
self
}
pub fn clip(&mut self, path: PaintPath) -> &mut Self {
invisible_return!(self);
let p_bounds = path.bounds(None);
if locatable_bounds(&p_bounds) {
let intersect = self.intersection_paint_bounds(&p_bounds);
let s = self.current_state_mut();
if let Some(bounds) = intersect {
s.bounds = s.transform.outer_transformed_rect(&bounds);
let cmd = PathCommand::new(path, PaintPathAction::Clip, s.transform);
self.commands.push(PaintCommand::Path(cmd));
self.current_state_mut().clip_cnt += 1;
} else {
s.bounds = Rect::zero();
}
}
self
}
pub fn fill_path(&mut self, path: PaintPath) -> &mut Self {
self.inner_draw_path(path, PathStyle::Fill)
}
pub fn draw_path(&mut self, path: PaintPath) -> &mut Self {
self.inner_draw_path(path, self.current_state().style)
}
pub fn stroke_path(&mut self, path: PaintPath) -> &mut Self {
self.inner_draw_path(path, PathStyle::Stroke)
}
pub fn stroke(&mut self) -> &mut Self {
let builder = std::mem::take(&mut self.path_builder);
self.stroke_path(builder.build().into())
}
pub fn fill(&mut self) -> &mut Self {
let builder = std::mem::take(&mut self.path_builder);
self.fill_path(builder.build().into())
}
fn apply_color_matrix(&mut self, matrix: ColorFilterMatrix) -> &mut Self {
self
.current_state_mut()
.color_filter
.apply_color_filter(matrix);
self
}
pub fn draw(&mut self) -> &mut Self {
let builder = std::mem::take(&mut self.path_builder);
self.draw_path(builder.build().into())
}
pub fn translate(&mut self, x: f32, y: f32) -> &mut Self {
let t = self.transform().pre_translate(Vector::new(x, y));
self.set_transform(t);
self
}
pub fn scale(&mut self, x: f32, y: f32) -> &mut Self {
let t = self.transform().pre_scale(x, y);
self.set_transform(t);
self
}
#[inline]
pub fn begin_path(&mut self, at: Point) -> &mut Self {
self.path_builder.begin_path(at);
self
}
#[inline]
pub fn end_path(&mut self, close: bool) -> &mut Self {
self.path_builder.end_path(close);
self
}
#[inline]
pub fn line_to(&mut self, to: Point) -> &mut Self {
self.path_builder.line_to(to);
self
}
#[inline]
pub fn bezier_curve_to(&mut self, ctrl1: Point, ctrl2: Point, to: Point) -> &mut Self {
self
.path_builder
.bezier_curve_to(ctrl1, ctrl2, to);
self
}
#[inline]
pub fn quadratic_curve_to(&mut self, ctrl: Point, to: Point) -> &mut Self {
self.path_builder.quadratic_curve_to(ctrl, to);
self
}
#[inline]
pub fn arc_to(
&mut self, center: Point, radius: f32, start_angle: Angle, end_angle: Angle,
) -> &mut Self {
self
.path_builder
.arc_to(center, radius, start_angle, end_angle);
self
}
#[inline]
pub fn ellipse_to(
&mut self, center: Point, radius: Vector, start_angle: Angle, end_angle: Angle,
) -> &mut Self {
self
.path_builder
.ellipse_to(center, radius, start_angle, end_angle);
self
}
#[inline]
pub fn rect(&mut self, rect: &Rect, is_positive: bool) -> &mut Self {
self.path_builder.rect(rect, is_positive);
self
}
#[inline]
pub fn circle(&mut self, center: Point, radius: f32, is_positive: bool) -> &mut Self {
self
.path_builder
.circle(center, radius, is_positive);
self
}
#[inline]
pub fn ellipse(
&mut self, center: Point, radius: Vector, rotation: f32, is_positive: bool,
) -> &mut Self {
self
.path_builder
.ellipse(center, radius, rotation, is_positive);
self
}
#[inline]
pub fn rect_round(&mut self, rect: &Rect, radius: &Radius, is_positive: bool) -> &mut Self {
self
.path_builder
.rect_round(rect, radius, is_positive);
self
}
pub fn draw_bundle_commands(
&mut self, bounds: Rect, cmds: Resource<Box<[PaintCommand]>>,
) -> &mut Self {
invisible_return!(self);
if self.intersection_paint_bounds(&bounds).is_none() {
return self;
}
let transform = *self.transform();
let color_filter = *self.color_filter();
let cmd = PaintCommand::Bundle { transform, color_filter, bounds, cmds };
self.commands.push(cmd);
self
}
pub fn draw_svg(&mut self, svg: &Svg) -> &mut Self {
invisible_return!(self);
let rect = Rect::from_size(svg.size());
if self.intersection_paint_bounds(&rect).is_none() {
return self;
}
let commands = svg.commands(self.fill_brush(), self.stroke_brush());
if commands.len() <= 16 {
let transform = *self.transform();
for cmd in commands.iter() {
let cmd = match cmd.clone() {
PaintCommand::Path(mut path) => {
path.transform(&transform);
if let PaintPathAction::Paint { ref mut brush, .. } = path.action {
brush.apply_color_filter(self.color_filter());
}
PaintCommand::Path(path)
}
PaintCommand::PopClip => PaintCommand::PopClip,
PaintCommand::Bundle { transform: b_ts, mut color_filter, bounds, cmds } => {
color_filter.chains(self.color_filter());
PaintCommand::Bundle { transform: transform.then(&b_ts), color_filter, bounds, cmds }
}
PaintCommand::Filter { .. } => cmd.clone(),
PaintCommand::Text(mut text_cmd) => {
text_cmd.transform = text_cmd.transform.then(&transform);
text_cmd
.default_brush
.apply_color_filter(self.color_filter());
text_cmd.color_filter.chains(self.color_filter());
PaintCommand::Text(text_cmd)
}
};
self.commands.push(cmd);
}
} else {
self.draw_bundle_commands(rect, commands.clone());
}
self
}
pub fn draw_img(
&mut self, img: Resource<PixelImage>, dst_rect: &Rect, src_rect: &Option<Rect>,
) -> &mut Self {
{
let mut painter = self.save_guard();
painter.translate(dst_rect.min_x(), dst_rect.min_y());
let m_width = img.width() as f32;
let m_height = img.height() as f32;
let mut paint_rect = Rect::from_size(Size::new(m_width, m_height));
if let Some(rc) = src_rect {
assert!(paint_rect.contains_rect(rc));
if paint_rect.width() != rc.width() || paint_rect.height() != rc.height() {
painter.clip(Path::rect(&Rect::from_size(dst_rect.size)).into());
}
paint_rect = *rc;
}
painter
.scale(dst_rect.width() / paint_rect.width(), dst_rect.height() / paint_rect.height())
.translate(-paint_rect.min_x(), -paint_rect.min_y())
.rect(&Rect::from_size(Size::new(m_width, m_height)), true)
.set_fill_brush(img)
.fill();
}
self
}
pub fn draw_text_payload(
&mut self, payload: Resource<TextDrawPayload>, paint_bounds: Rect,
) -> &mut Self {
invisible_return!(self);
let mut default_brush = CommandBrush::from(self.fill_brush().clone());
default_brush.apply_color_filter(self.current_color_filter());
self
.commands
.push(PaintCommand::Text(TextCommand {
paint_bounds,
transform: *self.transform(),
payload,
default_brush,
color_filter: *self.current_color_filter(),
}));
self
}
pub fn filter_path(&mut self, path: PaintPath, filter: Filter) -> &mut Self {
invisible_return!(self);
let p_bounds = path.bounds(None);
if p_bounds.is_empty() || !locatable_bounds(&p_bounds) {
return self;
}
if !self.intersect_paint_bounds(&p_bounds) {
return self;
}
let transform = *self.transform();
let mut filter_bounds = transform.outer_transformed_rect(&p_bounds);
let filters: Vec<_> = filter
.into_layers()
.into_iter()
.map(|mut layer| {
filter_bounds = transform_filter_layer(&mut layer, &transform, filter_bounds);
layer
})
.collect();
self
.commands
.push(PaintCommand::Filter { path, filter_bounds, transform, filters });
self
}
pub fn filter(&mut self, filter: Filter) -> &mut Self {
let (color_filter, filter) = filter.extract_color_and_convolution();
if !filter.is_empty() {
let state = FilterState {
transform: *self.transform(),
filter_start_idx: self.commands.len(),
color_filter: *self.color_filter(),
filter,
};
self.current_state_mut().filters.push(state);
}
if let Some(color_filter) = color_filter {
self.apply_color_matrix(color_filter);
}
self
}
fn inner_draw_path(&mut self, path: PaintPath, path_style: PathStyle) -> &mut Self {
invisible_return!(self);
let line_width = matches!(path_style, PathStyle::Stroke).then(|| self.line_width());
let p_bounds = path.bounds(line_width);
if p_bounds.is_empty()
|| !locatable_bounds(&p_bounds)
|| !self.intersect_paint_bounds(&p_bounds)
{
return self;
}
let brush = match path_style {
PathStyle::Fill => self.fill_brush().clone(),
PathStyle::Stroke => self.stroke_brush().clone(),
};
if brush.is_visible() {
let mut brush = CommandBrush::from(brush);
let painting_style = match path_style {
PathStyle::Fill => PaintingStyle::Fill,
PathStyle::Stroke => PaintingStyle::Stroke(self.stroke_options().clone()),
};
brush.apply_color_filter(self.color_filter());
let ts = *self.transform();
let action = PaintPathAction::Paint { brush, painting_style };
let cmd = PathCommand::new(path, action, ts);
self.commands.push(PaintCommand::Path(cmd));
}
self
}
}
impl PaintingStyle {
pub fn line_width(&self) -> Option<f32> {
match self {
PaintingStyle::Fill => None,
PaintingStyle::Stroke(stroke) => Some(stroke.width),
}
}
}
impl Painter {
fn current_state(&self) -> &PainterState {
self
.state_stack
.last()
.expect("Must have one state in stack!")
}
fn current_state_mut(&mut self) -> &mut PainterState {
self
.state_stack
.last_mut()
.expect("Must have one state in stack!")
}
fn stroke_options(&self) -> &StrokeOptions { &self.current_state().stroke_options }
fn push_n_pop_cmd(&mut self, n: usize) {
for _ in 0..n {
if matches!(
self.commands.last(),
Some(PaintCommand::Path(PathCommand { action: PaintPathAction::Clip, .. }))
) {
self.commands.pop();
} else {
self.commands.push(PaintCommand::PopClip)
}
}
}
fn fill_all_pop_clips(&mut self) {
let clip_cnt = self.current_state().clip_cnt;
self
.state_stack
.iter_mut()
.for_each(|s| s.clip_cnt = 0);
self.push_n_pop_cmd(clip_cnt);
}
fn is_visible_canvas(&self) -> bool {
let t = self.current_state().transform;
!self.is_transparent()
&& locatable_bounds(self.viewport())
&& t.m11.is_finite()
&& t.m12.is_finite()
&& t.m21.is_finite()
&& t.m22.is_finite()
&& t.m31.is_finite()
&& t.m32.is_finite()
}
fn generate_filter_bundle(
&mut self, transform: Transform, cmd_start_idx: usize, color_filter: ColorMatrix,
filters: Vec<FilterLayer>,
) {
if cmd_start_idx >= self.commands.len() {
return;
}
let mut cmds: Vec<PaintCommand> = self.commands.drain(cmd_start_idx..).collect();
if cmds.is_empty() {
return;
}
let mut current_bounds = match Self::compute_commands_bounds(&cmds) {
Some(b) if locatable_bounds(&b) => b,
_ => return,
};
let len = filters.len();
for (i, mut layer) in filters.into_iter().enumerate() {
let new_bounds = transform_filter_layer(&mut layer, &transform, current_bounds);
let path = Path::rect(&new_bounds).into();
cmds.push(PaintCommand::Filter {
path,
transform: Transform::identity(),
filter_bounds: new_bounds,
filters: vec![layer],
});
let color_filter = if i == len - 1 { color_filter } else { ColorMatrix::default() };
let bundle = PaintCommand::Bundle {
transform: Transform::identity(),
color_filter,
bounds: new_bounds,
cmds: Resource::new(cmds.into_boxed_slice()),
};
cmds = vec![bundle];
current_bounds = new_bounds;
}
self.commands.extend(cmds);
}
fn compute_commands_bounds(cmds: &[PaintCommand]) -> Option<Rect> {
let mut bounds: Option<Rect> = None;
for cmd in cmds {
let cmd_bounds = match cmd {
PaintCommand::Path(path_cmd) => path_cmd.paint_bounds,
PaintCommand::Bundle { bounds: b, transform, .. } => transform.outer_transformed_rect(b),
PaintCommand::Filter { filter_bounds, .. } => *filter_bounds,
PaintCommand::Text(text_cmd) => text_cmd
.transform
.outer_transformed_rect(&text_cmd.paint_bounds),
PaintCommand::PopClip => continue,
};
bounds = Some(bounds.map_or(cmd_bounds, |b| b.union(&cmd_bounds)));
}
bounds
}
}
impl From<Brush> for CommandBrush {
fn from(brush: Brush) -> Self {
match brush {
Brush::Color(color) => CommandBrush::Color(color),
Brush::Image(img) => CommandBrush::Image { img, color_filter: ColorMatrix::default() },
Brush::RadialGradient(radial_gradient) => CommandBrush::Radial(radial_gradient),
Brush::LinearGradient(linear_gradient) => CommandBrush::Linear(linear_gradient),
}
}
}
impl Drop for PainterResult<'_> {
fn drop(&mut self) { self.0.clear() }
}
impl<'a> Deref for PainterResult<'a> {
type Target = [PaintCommand];
fn deref(&self) -> &Self::Target { self.0 }
}
impl<'a> DerefMut for PainterResult<'a> {
fn deref_mut(&mut self) -> &mut Self::Target { self.0 }
}
pub struct PainterGuard<'a>(&'a mut Painter);
impl<'a> Drop for PainterGuard<'a> {
#[inline]
fn drop(&mut self) {
debug_assert!(!self.0.state_stack.is_empty());
self.0.restore();
}
}
impl<'a> Deref for PainterGuard<'a> {
type Target = Painter;
#[inline]
fn deref(&self) -> &Self::Target { self.0 }
}
impl<'a> DerefMut for PainterGuard<'a> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target { self.0 }
}
impl Deref for PaintPath {
type Target = Path;
fn deref(&self) -> &Self::Target {
match self {
PaintPath::Share(p) => p.deref(),
PaintPath::Own(p) => p,
PaintPath::PixelImage(_) => {
panic!("PaintPath::PixelImage cannot be dereferenced to Path");
}
}
}
}
impl PaintPath {
pub fn path_kind(&self) -> PathKind {
match self {
PaintPath::Share(p) => p.path_kind(),
PaintPath::Own(p) => p.path_kind(),
PaintPath::PixelImage(_) => PathKind::Complex,
}
}
pub fn bounds(&self, line_width: Option<f32>) -> Rect {
match self {
PaintPath::Share(p) => p.bounds(line_width),
PaintPath::Own(p) => p.bounds(line_width),
PaintPath::PixelImage(img) => {
let size = img.size();
Rect::from_size(Size::new(size.width as f32, size.height as f32))
}
}
}
}
impl From<Path> for PaintPath {
fn from(p: Path) -> Self { PaintPath::Own(p) }
}
impl From<Resource<Path>> for PaintPath {
fn from(p: Resource<Path>) -> Self { PaintPath::Share(p) }
}
impl PathCommand {
pub fn new(path: PaintPath, action: PaintPathAction, transform: Transform) -> Self {
let line_width = if let PaintPathAction::Paint { painting_style, .. } = &action {
painting_style.line_width()
} else {
None
};
let paint_bounds = transform.outer_transformed_rect(&path.bounds(line_width));
Self { path, transform, paint_bounds, action }
}
pub fn scale(&mut self, scale: f32) {
self.transform = self.transform.then_scale(scale, scale);
self.paint_bounds = self.paint_bounds.scale(scale, scale);
}
pub fn transform(&mut self, transform: &Transform) {
self.transform = self.transform.then(transform);
self.paint_bounds = self
.transform
.outer_transformed_rect(&self.path.bounds(None));
}
}
impl CommandBrush {
pub fn apply_color_filter(&mut self, filter: &ColorMatrix) -> &mut Self {
match self {
CommandBrush::Color(color) => *color = filter.apply_to(color),
CommandBrush::Image { color_filter, .. } => color_filter.chains(filter),
CommandBrush::Radial(gradient) => {
let mut gradient = (**gradient).clone();
gradient
.stops
.iter_mut()
.for_each(|s| s.color = filter.apply_to(&s.color));
*self = CommandBrush::Radial(Resource::new(gradient));
}
CommandBrush::Linear(gradient) => {
let mut gradient = (**gradient).clone();
gradient
.stops
.iter_mut()
.for_each(|s| s.color = filter.apply_to(&s.color));
*self = CommandBrush::Linear(Resource::new(gradient));
}
}
self
}
}
impl From<usvg::SpreadMethod> for SpreadMethod {
fn from(value: usvg::SpreadMethod) -> Self {
match value {
usvg::SpreadMethod::Pad => SpreadMethod::Pad,
usvg::SpreadMethod::Reflect => SpreadMethod::Reflect,
usvg::SpreadMethod::Repeat => SpreadMethod::Repeat,
}
}
}
fn locatable_bounds(bounds: &Rect) -> bool {
bounds.origin.is_finite() && !bounds.width().is_nan() && !bounds.height().is_nan()
}
fn transform_filter_layer(
layer: &mut FilterLayer, transform: &Transform, current_bounds: Rect,
) -> Rect {
use crate::filter::FlattenMatrix;
let (w, h) = layer
.ops
.iter()
.fold((1, 1), |(w, h), op| match op {
FilterOp::Convolution(FlattenMatrix { width, height, .. }) => (w + width - 1, h + height - 1),
_ => (w, h),
});
let expand = Size::new(w as f32 - 1., h as f32 - 1.);
let expand = transform
.outer_transformed_rect(&Rect::from_size(expand))
.size;
let expanded_bounds =
Rect::new(current_bounds.origin - expand / 2., current_bounds.size + expand);
let offset = transform.transform_vector(Vector::new(layer.offset[0], layer.offset[1]));
layer.offset = [offset.x, offset.y];
let shift_bounds = expanded_bounds.translate(offset);
expanded_bounds.union(&shift_bounds)
}
macro_rules! invisible_return {
($this:ident) => {
if !$this.is_visible_canvas() {
return $this;
}
};
}
use invisible_return;
#[cfg(test)]
mod test {
use ribir_types::rect;
use super::*;
fn painter() -> Painter { Painter::new(Rect::from_size(Size::new(512., 512.))) }
#[test]
fn save_guard() {
let mut painter = painter();
{
let mut guard = painter.save_guard();
let t = Transform::new(1., 1., 1., 1., 1., 1.);
guard.set_transform(t);
assert_eq!(&t, guard.transform());
{
let mut p2 = guard.save_guard();
let t2 = Transform::new(2., 2., 2., 2., 2., 2.);
p2.set_transform(t2);
assert_eq!(&t2, p2.transform());
}
assert_eq!(&t, guard.transform());
}
assert_eq!(&Transform::new(1., 0., 0., 1., 0., 0.), painter.transform());
}
#[test]
fn fix_clip_pop_without_restore() {
let mut painter = painter();
let commands = painter
.save()
.clip(Path::rect(&rect(0., 0., 100., 100.)).into())
.rect(&rect(0., 0., 10., 10.), true)
.fill()
.save()
.clip(Path::rect(&rect(0., 0., 50., 50.)).into())
.rect(&rect(0., 0., 10., 10.), true)
.fill()
.finish();
assert!(matches!(commands[commands.len() - 1], PaintCommand::PopClip));
assert!(matches!(commands[commands.len() - 2], PaintCommand::PopClip));
std::mem::drop(commands);
assert_eq!(painter.current_state().clip_cnt, 0);
}
#[test]
fn filter_invalid_clip() {
let mut painter = painter();
painter
.save()
.set_transform(Transform::translation(f32::NAN, f32::INFINITY))
.clip(Path::rect(&rect(0., 0., 10., 10.)).into());
assert_eq!(painter.commands.len(), 0);
}
#[test]
fn filter_invalid_commands() {
let mut painter = painter();
let svg =
Svg::parse_from_bytes(include_bytes!("../../tests/assets/test1.svg"), true, false).unwrap();
painter
.save()
.set_transform(Transform::translation(f32::NAN, f32::INFINITY))
.draw_svg(&svg);
assert_eq!(painter.commands.len(), 0);
}
#[test]
fn draw_svg_gradient() {
let mut painter = Painter::new(Rect::from_size(Size::new(64., 64.)));
let svg = Svg::parse_from_bytes(
include_bytes!("../../tests/assets/fill_with_gradient.svg"),
true,
false,
)
.unwrap();
painter.draw_svg(&svg);
}
#[test]
fn fix_incorrect_bounds_axis() {
let mut painter = painter();
painter
.save()
.clip(Path::rect(&rect(0., 0., 100., 100.)).into())
.set_transform(Transform::translation(500., 500.))
.rect(&rect(-500., -500., 10., 10.), true)
.fill();
assert_eq!(painter.commands.len(), 2);
}
#[test]
fn fix_scale_zero_crash() {
let mut painter = painter();
painter
.scale(0., 0.)
.rect(&rect(0., 0., 10., 10.), true)
.fill();
}
#[test]
fn clip_zero() {
let mut painter = painter();
painter
.clip(Path::rect(&rect(0., 0., 0., 0.)).into())
.rect(&rect(0., 0., 10., 10.), true)
.fill();
assert_eq!(painter.commands.len(), 0);
painter.draw_bundle_commands(
Rect::from_size(Size::new(10., 10.)),
Resource::new(Box::new([PaintCommand::Path(PathCommand::new(
Path::rect(&rect(0., 0., 10., 10.)).into(),
PaintPathAction::Paint {
painting_style: PaintingStyle::Fill,
brush: Brush::Color(Color::BLACK).into(),
},
Transform::identity(),
))])),
);
assert_eq!(painter.commands.len(), 0);
}
}