// TODO: prefix paint creation functions with make_ or new_
// so that they are easier to find when autocompleting
use crate::geometry::Transform2D;
use crate::{
Align,
Baseline,
Color,
FillRule,
FontId,
ImageId,
LineCap,
LineJoin,
};
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub(crate) struct GradientStop(pub f32, pub Color);
// We use MultiStopGradient as a key since we cache them. We either need
// to define Hash (for HashMap) or Ord for (BTreeMap).
impl Eq for GradientStop {}
impl Ord for GradientStop {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
if other < self {
std::cmp::Ordering::Less
} else if self < other {
std::cmp::Ordering::Greater
} else {
std::cmp::Ordering::Equal
}
}
}
pub(crate) type MultiStopGradient = [GradientStop; 16];
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub(crate) enum GradientColors {
TwoStop {
start_color: Color,
end_color: Color,
},
MultiStop {
// We support up to 16 stops.
stops: MultiStopGradient,
},
}
impl GradientColors {
fn mul_alpha(&mut self, a: f32) {
match self {
GradientColors::TwoStop { start_color, end_color } => {
start_color.a *= a;
end_color.a *= a;
}
GradientColors::MultiStop { stops } => {
for stop in stops {
stop.1.a *= a;
}
}
}
}
fn from_stops(stops: &[(f32, Color)]) -> GradientColors {
if stops.is_empty() {
// No stops, we use black.
GradientColors::TwoStop {
start_color: Color::black(),
end_color: Color::black(),
}
} else if stops.len() == 1 {
// One stop devolves to a solid color fill (but using the gradient shader variation).
GradientColors::TwoStop {
start_color: stops[0].1,
end_color: stops[0].1,
}
} else if stops.len() == 2 && stops[0].0 <= 0.0 && stops[1].0 >= 1.0 {
// Two stops takes the classic gradient path, so long as the stop positions are at
// the extents (if the stop positions are inset then we'll fill to them).
GradientColors::TwoStop {
start_color: stops[0].1,
end_color: stops[1].1,
}
} else {
// Actual multistop gradient. We copy out the stops and then use a stop with a
// position > 1.0 as a sentinel. GradientStore ignores stop positions > 1.0
// when synthesizing the gradient texture.
let mut out_stops: [GradientStop; 16] = Default::default();
for i in 0..16 {
if i < stops.len() {
out_stops[i] = GradientStop(stops[i].0, stops[i].1);
} else {
out_stops[i] = GradientStop(2.0, Color::black());
}
}
GradientColors::MultiStop { stops: out_stops }
}
}
}
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub(crate) enum PaintFlavor {
Color(Color),
#[cfg_attr(feature = "serde", serde(skip))]
Image {
id: ImageId,
cx: f32,
cy: f32,
width: f32,
height: f32,
angle: f32,
alpha: f32,
},
LinearGradient {
start_x: f32,
start_y: f32,
end_x: f32,
end_y: f32,
colors: GradientColors,
},
BoxGradient {
x: f32,
y: f32,
width: f32,
height: f32,
radius: f32,
feather: f32,
colors: GradientColors,
},
RadialGradient {
cx: f32,
cy: f32,
in_radius: f32,
out_radius: f32,
colors: GradientColors,
},
}
// Convenience method to fetch the GradientColors out of a PaintFlavor
impl PaintFlavor {
pub(crate) fn gradient_colors(&self) -> Option<&GradientColors> {
match self {
PaintFlavor::LinearGradient { colors, .. } => Some(colors),
PaintFlavor::BoxGradient { colors, .. } => Some(colors),
PaintFlavor::RadialGradient { colors, .. } => Some(colors),
_ => None,
}
}
}
/// Reperesent glyph texture
#[derive(Copy, Clone, Debug)]
pub enum GlyphTexture {
/// Without texture
None,
/// Masked texture
AlphaMask(ImageId),
/// Color texture
ColorTexture(ImageId),
}
impl Default for GlyphTexture {
fn default() -> Self {
Self::None
}
}
/// Struct controlling how graphical shapes are rendered.
///
/// The Paint struct is a relatively lightweight object which contains all the information needed to
/// display something on the canvas. Unlike the HTML canvas where the current drawing style is stored
/// in an internal stack this paint struct is simply passed to the relevant drawing methods on the canvas.
///
/// Clients code can have as many paints as they desire for different use cases and styles. This makes
/// the internal stack in the [Canvas](struct.Canvas.html) struct much lighter since it only needs to
/// contain the transform stack and current scissor rectangle.
///
/// # Example
/// ```
/// use vg::{Paint, Path, Color, Canvas, renderer::Void};
///
/// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
///
/// let fill_paint = Paint::color(Color::hex("454545"));
/// let mut stroke_paint = Paint::color(Color::hex("bababa"));
/// stroke_paint.set_line_width(4.0);
///
/// let mut path = Path::new();
/// path.rounded_rect(10.0, 10.0, 100.0, 100.0, 20.0);
/// canvas.fill_path(&mut path, fill_paint);
/// canvas.stroke_path(&mut path, stroke_paint);
/// ```
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub struct Paint {
pub(crate) flavor: PaintFlavor,
pub(crate) transform: Transform2D,
#[cfg_attr(feature = "serialization", serde(skip))]
pub(crate) glyph_texture: GlyphTexture,
pub(crate) shape_anti_alias: bool,
pub(crate) stencil_strokes: bool,
pub(crate) miter_limit: f32,
pub(crate) line_width: f32,
pub(crate) line_cap_start: LineCap,
pub(crate) line_cap_end: LineCap,
pub(crate) line_join: LineJoin,
#[cfg_attr(feature = "serialization", serde(skip))]
pub(crate) font_ids: [Option<FontId>; 8],
pub(crate) font_size: f32,
pub(crate) letter_spacing: f32,
pub(crate) text_baseline: Baseline,
pub(crate) text_align: Align,
pub(crate) fill_rule: FillRule,
}
impl Default for Paint {
fn default() -> Self {
Self {
flavor: PaintFlavor::Color(Color::white()),
transform: Default::default(),
glyph_texture: Default::default(),
shape_anti_alias: true,
stencil_strokes: true,
miter_limit: 10.0,
line_width: 1.0,
line_cap_start: Default::default(),
line_cap_end: Default::default(),
line_join: Default::default(),
font_ids: Default::default(),
font_size: 16.0,
letter_spacing: 0.0,
text_baseline: Default::default(),
text_align: Default::default(),
fill_rule: Default::default(),
}
}
}
impl Paint {
/// Creates a new solid color paint
pub fn color(color: Color) -> Self {
Self {
flavor: PaintFlavor::Color(color),
..Default::default()
}
}
/// Creates a new image pattern paint.
///
/// * `id` - is handle to the image to render
/// * `cx` `cy` - Specify the top-left location of the image pattern
/// * `width` `height` - The size of one image
/// * `angle` - Rotation around the top-left corner
/// * `alpha` - Transparency applied on the image
///
/// # Example
/// ```
/// use vg::{Paint, Path, Color, Canvas, ImageFlags, renderer::Void};
///
/// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
///
/// let image_id = canvas.load_image_file("examples/assets/rust-logo.png", ImageFlags::GENERATE_MIPMAPS).expect("Cannot create image");
/// let fill_paint = Paint::image(image_id, 10.0, 10.0, 85.0, 85.0, 0.0, 1.0);
///
/// let mut path = Path::new();
/// path.rect(10.0, 10.0, 85.0, 85.0);
/// canvas.fill_path(&mut path, fill_paint);
/// ```
pub fn image(id: ImageId, cx: f32, cy: f32, width: f32, height: f32, angle: f32, alpha: f32) -> Self {
Self {
flavor: PaintFlavor::Image {
id,
cx,
cy,
width,
height,
angle,
alpha,
},
..Default::default()
}
}
/// Creates and returns a linear gradient paint.
///
/// The gradient is transformed by the current transform when it is passed to fill_path() or stroke_path().
///
/// # Example
/// ```
/// use vg::{Paint, Path, Color, Canvas, ImageFlags, renderer::Void};
///
/// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
///
/// let bg = Paint::linear_gradient(0.0, 0.0, 0.0, 100.0, Color::rgba(255, 255, 255, 16), Color::rgba(0, 0, 0, 16));
/// let mut path = Path::new();
/// path.rounded_rect(0.0, 0.0, 100.0, 100.0, 5.0);
/// canvas.fill_path(&mut path, bg);
/// ```
pub fn linear_gradient(
start_x: f32,
start_y: f32,
end_x: f32,
end_y: f32,
start_color: Color,
end_color: Color,
) -> Self {
Self {
flavor: PaintFlavor::LinearGradient {
start_x,
start_y,
end_x,
end_y,
colors: GradientColors::TwoStop { start_color, end_color },
},
..Default::default()
}
}
/// Creates and returns a linear gradient paint with two or more stops.
///
/// The gradient is transformed by the current transform when it is passed to fill_path() or stroke_path().
/// # Example
/// ```
/// use vg::{Paint, Path, Color, Canvas, ImageFlags, renderer::Void};
///
/// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
///
/// let bg = Paint::linear_gradient_stops(
/// 0.0, 0.0,
/// 0.0, 100.0,
/// &[
/// (0.0, Color::rgba(255, 255, 255, 16)),
/// (0.5, Color::rgba(0, 0, 0, 16)),
/// (1.0, Color::rgba(255, 0, 0, 16))
/// ]);
/// let mut path = Path::new();
/// path.rounded_rect(0.0, 0.0, 100.0, 100.0, 5.0);
/// canvas.fill_path(&mut path, bg);
/// ```
pub fn linear_gradient_stops(start_x: f32, start_y: f32, end_x: f32, end_y: f32, stops: &[(f32, Color)]) -> Self {
Self {
flavor: PaintFlavor::LinearGradient {
start_x,
start_y,
end_x,
end_y,
colors: GradientColors::from_stops(stops),
},
..Default::default()
}
}
/// Creates and returns a box gradient.
///
/// Box gradient is a feathered rounded rectangle, it is useful for rendering
/// drop shadows or highlights for boxes. Parameters (x,y) define the top-left corner of the rectangle,
/// (w,h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
/// the border of the rectangle is. Parameter inner_color specifies the inner color and outer_color the outer color of the gradient.
/// The gradient is transformed by the current transform when it is passed to fill_path() or stroke_path().
///
/// # Example
/// ```
/// use vg::{Paint, Path, Color, Canvas, ImageFlags, renderer::Void};
///
/// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
///
/// let bg = Paint::box_gradient(
/// 0.0,
/// 0.0,
/// 100.0,
/// 100.0,
/// 10.0,
/// 10.0,
/// Color::rgba(0, 0, 0, 128),
/// Color::rgba(0, 0, 0, 0),
/// );
///
/// let mut path = Path::new();
/// path.rounded_rect(0.0, 0.0, 100.0, 100.0, 5.0);
/// canvas.fill_path(&mut path, bg);
/// ```
pub fn box_gradient(
x: f32,
y: f32,
width: f32,
height: f32,
radius: f32,
feather: f32,
inner_color: Color,
outer_color: Color,
) -> Self {
Self {
flavor: PaintFlavor::BoxGradient {
x,
y,
width,
height,
radius,
feather,
colors: GradientColors::TwoStop {
start_color: inner_color,
end_color: outer_color,
},
},
..Default::default()
}
}
/// Creates and returns a radial gradient.
///
/// Parameters (cx,cy) specify the center, in_radius and out_radius specify
/// the inner and outer radius of the gradient, inner_color specifies the start color and outer_color the end color.
/// The gradient is transformed by the current transform when it is passed to fill_paint() or stroke_paint().
///
/// # Example
/// ```
/// use vg::{Paint, Path, Color, Canvas, ImageFlags, renderer::Void};
///
/// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
///
/// let bg = Paint::radial_gradient(
/// 50.0,
/// 50.0,
/// 18.0,
/// 24.0,
/// Color::rgba(0, 0, 0, 128),
/// Color::rgba(0, 0, 0, 0),
/// );
///
/// let mut path = Path::new();
/// path.circle(50.0, 50.0, 20.0);
/// canvas.fill_path(&mut path, bg);
/// ```
pub fn radial_gradient(
cx: f32,
cy: f32,
in_radius: f32,
out_radius: f32,
inner_color: Color,
outer_color: Color,
) -> Self {
Self {
flavor: PaintFlavor::RadialGradient {
cx,
cy,
in_radius,
out_radius,
colors: GradientColors::TwoStop {
start_color: inner_color,
end_color: outer_color,
},
},
..Default::default()
}
}
/// Creates and returns a multi-stop radial gradient.
///
/// Parameters (cx,cy) specify the center, in_radius and out_radius specify the inner and outer radius of the gradient,
/// colors specifies a list of color stops with offsets. The first offset should be 0.0 and the last offset should be 1.0.
/// If a gradient has more than 16 stops, then only the first 16 stops will be used.
///
/// The gradient is transformed by the current transform when it is passed to fill_paint() or stroke_paint().
///
/// # Example
/// ```
/// use vg::{Paint, Path, Color, Canvas, ImageFlags, renderer::Void};
///
/// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
///
/// let bg = Paint::radial_gradient_stops(
/// 50.0,
/// 50.0,
/// 18.0,
/// 24.0,
/// &[
/// (0.0, Color::rgba(0, 0, 0, 128)),
/// (0.5, Color::rgba(0, 0, 128, 128)),
/// (1.0, Color::rgba(0, 128, 0, 128))
/// ]
/// );
///
/// let mut path = Path::new();
/// path.circle(50.0, 50.0, 20.0);
/// canvas.fill_path(&mut path, bg);
/// ```
pub fn radial_gradient_stops(cx: f32, cy: f32, in_radius: f32, out_radius: f32, stops: &[(f32, Color)]) -> Self {
Self {
flavor: PaintFlavor::RadialGradient {
cx,
cy,
in_radius,
out_radius,
colors: GradientColors::from_stops(stops),
},
..Default::default()
}
}
/// Creates a new solid color paint
pub fn set_color(&mut self, color: Color) {
self.flavor = PaintFlavor::Color(color);
}
pub(crate) fn glyph_texture(&self) -> GlyphTexture {
self.glyph_texture
}
/// Set an alpha mask or color glyph texture; this is only used by draw_triangles which is used for text.
// This is scoped to crate visibility because fill_path and stroke_path don't propagate
// the alpha mask (so nothing draws), and the texture coordinates are used for antialiasing
// when path drawing.
pub(crate) fn set_glyph_texture(&mut self, texture: GlyphTexture) {
self.glyph_texture = texture;
}
/// Returns boolean if the shapes drawn with this paint will be antialiased.
pub fn anti_alias(&self) -> bool {
self.shape_anti_alias
}
/// Sets whether shapes drawn with this paint will be anti aliased. Enabled by default.
pub fn set_anti_alias(&mut self, value: bool) {
self.shape_anti_alias = value;
}
/// True if this paint uses higher quality stencil strokes.
pub fn stencil_strokes(&self) -> bool {
self.stencil_strokes
}
/// Sets whether to use higher quality stencil strokes.
pub fn set_stencil_strokes(&mut self, value: bool) {
self.stencil_strokes = value;
}
/// Returns the current line width.
pub fn line_width(&self) -> f32 {
self.line_width
}
/// Sets the line width for shapes stroked with this paint.
pub fn set_line_width(&mut self, width: f32) {
self.line_width = width;
}
/// Getter for the miter limit
pub fn miter_limit(&self) -> f32 {
self.miter_limit
}
/// Sets the limit at which a sharp corner is drawn beveled.
///
/// If the miter at a corner exceeds this limit, LineJoin is replaced with LineJoin::Bevel.
pub fn set_miter_limit(&mut self, limit: f32) {
self.miter_limit = limit;
}
/// Returns the current start line cap for this paint.
pub fn line_cap_start(&self) -> LineCap {
self.line_cap_start
}
/// Returns the current start line cap for this paint.
pub fn line_cap_end(&self) -> LineCap {
self.line_cap_end
}
/// Sets how the start and end of the line (cap) is drawn
///
/// By default it's set to LineCap::Butt
pub fn set_line_cap(&mut self, cap: LineCap) {
self.line_cap_start = cap;
self.line_cap_end = cap;
}
/// Sets how the beggining cap of the line is drawn
///
/// By default it's set to LineCap::Butt
pub fn set_line_cap_start(&mut self, cap: LineCap) {
self.line_cap_start = cap;
}
/// Sets how the end cap of the line is drawn
///
/// By default it's set to LineCap::Butt
pub fn set_line_cap_end(&mut self, cap: LineCap) {
self.line_cap_end = cap;
}
/// Returns the current line join for this paint.
pub fn line_join(&self) -> LineJoin {
self.line_join
}
/// Sets how sharp path corners are drawn.
///
/// By default it's set to LineJoin::Miter
pub fn set_line_join(&mut self, join: LineJoin) {
self.line_join = join;
}
/// Set the font
pub fn set_font(&mut self, font_ids: &[FontId]) {
self.font_ids = Default::default();
for (i, id) in font_ids.iter().take(8).enumerate() {
self.font_ids[i] = Some(*id);
}
}
/// Returns the current font size
///
/// Only has effect on canvas text operations
pub fn font_size(&self) -> f32 {
self.font_size
}
/// Sets the font size.
///
/// Only has effect on canvas text operations
pub fn set_font_size(&mut self, size: f32) {
self.font_size = size;
}
/// Returns the current letter spacing
pub fn letter_spacing(&self) -> f32 {
self.letter_spacing
}
/// Sets the letter spacing for this paint
///
/// Only has effect on canvas text operations
pub fn set_letter_spacing(&mut self, spacing: f32) {
self.letter_spacing = spacing;
}
/// Returns the current vertical align
pub fn text_baseline(&self) -> Baseline {
self.text_baseline
}
/// Sets the text vertical alignment for this paint
///
/// Only has effect on canvas text operations
pub fn set_text_baseline(&mut self, align: Baseline) {
self.text_baseline = align;
}
/// Returns the current horizontal align
pub fn text_align(&self) -> Align {
self.text_align
}
/// Sets the text horizontal alignment for this paint
///
/// Only has effect on canvas text operations
pub fn set_text_align(&mut self, align: Align) {
self.text_align = align;
}
/// Retrieves the current fill rule setting for this paint
pub fn fill_rule(&self) -> FillRule {
self.fill_rule
}
/// Sets the current rule to be used when filling a path
///
/// [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule][1]
///
/// [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule
pub fn set_fill_rule(&mut self, rule: FillRule) {
self.fill_rule = rule;
}
pub(crate) fn mul_alpha(&mut self, a: f32) {
match &mut self.flavor {
PaintFlavor::Color(color) => {
color.a *= a;
}
PaintFlavor::Image { alpha, .. } => {
*alpha *= a;
}
PaintFlavor::LinearGradient { colors, .. } => {
colors.mul_alpha(a);
}
PaintFlavor::BoxGradient { colors, .. } => {
colors.mul_alpha(a);
}
PaintFlavor::RadialGradient { colors, .. } => {
colors.mul_alpha(a);
}
}
}
}