#[cfg(feature = "serde")]
#[macro_use]
extern crate serde;
use std::{cell::RefCell, ops::Range, path::Path as FilePath, rc::Rc};
use imgref::ImgVec;
use rgb::RGBA8;
mod utils;
mod text;
mod error;
pub use error::ErrorKind;
pub use text::{
Align, Atlas, Baseline, DrawCommand, FontId, FontMetrics, GlyphDrawCommands, Quad, RenderMode, TextContext,
TextMetrics,
};
use text::{GlyphAtlas, TextContextImpl};
mod image;
use crate::image::ImageStore;
pub use crate::image::{ImageFilter, ImageFlags, ImageId, ImageInfo, ImageSource, PixelFormat};
mod color;
pub use color::Color;
pub mod renderer;
pub use renderer::{RenderTarget, Renderer};
use renderer::{Command, CommandType, Drawable, Params, ShaderType, Vertex};
pub(crate) mod geometry;
pub use geometry::Transform2D;
use geometry::*;
mod paint;
pub use paint::Paint;
use paint::{GlyphTexture, PaintFlavor, StrokeSettings};
mod path;
use path::Convexity;
pub use path::{Path, PathIter, Solidity, Verb};
mod gradient_store;
use gradient_store::GradientStore;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum FillRule {
EvenOdd,
NonZero,
}
impl Default for FillRule {
fn default() -> Self {
Self::NonZero
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)]
pub enum BlendFactor {
Zero,
One,
SrcColor,
OneMinusSrcColor,
DstColor,
OneMinusDstColor,
SrcAlpha,
OneMinusSrcAlpha,
DstAlpha,
OneMinusDstAlpha,
SrcAlphaSaturate,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)]
pub enum CompositeOperation {
SourceOver,
SourceIn,
SourceOut,
Atop,
DestinationOver,
DestinationIn,
DestinationOut,
DestinationAtop,
Lighter,
Copy,
Xor,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)]
pub struct CompositeOperationState {
src_rgb: BlendFactor,
src_alpha: BlendFactor,
dst_rgb: BlendFactor,
dst_alpha: BlendFactor,
}
impl CompositeOperationState {
pub fn new(op: CompositeOperation) -> Self {
let (sfactor, dfactor) = match op {
CompositeOperation::SourceOver => (BlendFactor::One, BlendFactor::OneMinusSrcAlpha),
CompositeOperation::SourceIn => (BlendFactor::DstAlpha, BlendFactor::Zero),
CompositeOperation::SourceOut => (BlendFactor::OneMinusDstAlpha, BlendFactor::Zero),
CompositeOperation::Atop => (BlendFactor::DstAlpha, BlendFactor::OneMinusSrcAlpha),
CompositeOperation::DestinationOver => (BlendFactor::OneMinusDstAlpha, BlendFactor::One),
CompositeOperation::DestinationIn => (BlendFactor::Zero, BlendFactor::SrcAlpha),
CompositeOperation::DestinationOut => (BlendFactor::Zero, BlendFactor::OneMinusSrcAlpha),
CompositeOperation::DestinationAtop => (BlendFactor::OneMinusDstAlpha, BlendFactor::SrcAlpha),
CompositeOperation::Lighter => (BlendFactor::One, BlendFactor::One),
CompositeOperation::Copy => (BlendFactor::One, BlendFactor::Zero),
CompositeOperation::Xor => (BlendFactor::OneMinusDstAlpha, BlendFactor::OneMinusSrcAlpha),
};
Self {
src_rgb: sfactor,
src_alpha: sfactor,
dst_rgb: dfactor,
dst_alpha: dfactor,
}
}
pub fn with_blend_factors(src_factor: BlendFactor, dst_factor: BlendFactor) -> Self {
Self {
src_rgb: src_factor,
src_alpha: src_factor,
dst_rgb: dst_factor,
dst_alpha: dst_factor,
}
}
}
impl Default for CompositeOperationState {
fn default() -> Self {
Self::new(CompositeOperation::SourceOver)
}
}
#[derive(Copy, Clone, Debug, Default)]
struct Scissor {
transform: Transform2D,
extent: Option<[f32; 2]>,
}
impl Scissor {
fn as_rect(&self, canvas_width: f32, canvas_height: f32) -> Option<Rect> {
let extent = match self.extent {
Some(extent) => extent,
None => return Some(Rect::new(0., 0., canvas_width, canvas_height)),
};
if self.transform[1] != 0.0 || self.transform[2] != 0.0 {
return None;
}
if self.transform[0] != 1.0 || self.transform[3] != 1.0 {
return None;
}
let half_width = extent[0];
let half_height = extent[1];
Some(Rect::new(
self.transform[4] - half_width,
self.transform[5] - half_height,
half_width * 2.0,
half_height * 2.0,
))
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum LineCap {
Butt,
Round,
Square,
}
impl Default for LineCap {
fn default() -> Self {
Self::Butt
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum LineJoin {
Miter,
Round,
Bevel,
}
impl Default for LineJoin {
fn default() -> Self {
Self::Miter
}
}
#[derive(Copy, Clone, Debug)]
struct State {
composite_operation: CompositeOperationState,
transform: Transform2D,
scissor: Scissor,
alpha: f32,
}
impl Default for State {
fn default() -> Self {
Self {
composite_operation: Default::default(),
transform: Transform2D::identity(),
scissor: Default::default(),
alpha: 1.0,
}
}
}
pub struct Canvas<T: Renderer> {
width: u32,
height: u32,
renderer: T,
text_context: Rc<RefCell<TextContextImpl>>,
glyph_atlas: Rc<GlyphAtlas>,
ephemeral_glyph_atlas: Option<Rc<GlyphAtlas>>,
current_render_target: RenderTarget,
state_stack: Vec<State>,
commands: Vec<Command>,
verts: Vec<Vertex>,
images: ImageStore<T::Image>,
fringe_width: f32,
device_px_ratio: f32,
tess_tol: f32,
dist_tol: f32,
gradients: GradientStore,
}
impl<T> Canvas<T>
where
T: Renderer,
{
pub fn new(renderer: T) -> Result<Self, ErrorKind> {
let mut canvas = Self {
width: 0,
height: 0,
renderer,
text_context: Default::default(),
glyph_atlas: Default::default(),
ephemeral_glyph_atlas: Default::default(),
current_render_target: RenderTarget::Screen,
state_stack: Default::default(),
commands: Default::default(),
verts: Default::default(),
images: ImageStore::new(),
fringe_width: 1.0,
device_px_ratio: 1.0,
tess_tol: 0.25,
dist_tol: 0.01,
gradients: GradientStore::new(),
};
canvas.save();
Ok(canvas)
}
pub fn new_with_text_context(renderer: T, text_context: TextContext) -> Result<Self, ErrorKind> {
let mut canvas = Self {
width: 0,
height: 0,
renderer,
text_context: text_context.0,
glyph_atlas: Default::default(),
ephemeral_glyph_atlas: Default::default(),
current_render_target: RenderTarget::Screen,
state_stack: Default::default(),
commands: Default::default(),
verts: Default::default(),
images: ImageStore::new(),
fringe_width: 1.0,
device_px_ratio: 1.0,
tess_tol: 0.25,
dist_tol: 0.01,
gradients: GradientStore::new(),
};
canvas.save();
Ok(canvas)
}
pub fn set_size(&mut self, width: u32, height: u32, dpi: f32) {
self.width = width;
self.height = height;
self.fringe_width = 1.0 / dpi;
self.tess_tol = 0.25 / dpi;
self.dist_tol = 0.01 / dpi;
self.device_px_ratio = dpi;
self.renderer.set_size(width, height, dpi);
self.append_cmd(Command::new(CommandType::SetRenderTarget(RenderTarget::Screen)));
}
pub fn clear_rect(&mut self, x: u32, y: u32, width: u32, height: u32, color: Color) {
let cmd = Command::new(CommandType::ClearRect {
x,
y,
width,
height,
color,
});
self.append_cmd(cmd);
}
pub fn width(&self) -> u32 {
match self.current_render_target {
RenderTarget::Image(id) => self.image_info(id).map(|info| info.width() as u32).unwrap_or(0),
RenderTarget::Screen => self.width,
}
}
pub fn height(&self) -> u32 {
match self.current_render_target {
RenderTarget::Image(id) => self.image_info(id).map(|info| info.height() as u32).unwrap_or(0),
RenderTarget::Screen => self.height,
}
}
pub fn flush(&mut self) {
self.renderer
.render(&mut self.images, &self.verts, std::mem::take(&mut self.commands));
self.verts.clear();
self.gradients
.release_old_gradients(&mut self.images, &mut self.renderer);
if let Some(atlas) = self.ephemeral_glyph_atlas.take() {
atlas.clear(self);
}
}
pub fn screenshot(&mut self) -> Result<ImgVec<RGBA8>, ErrorKind> {
self.flush();
self.renderer.screenshot()
}
pub fn save(&mut self) {
let state = self.state_stack.last().map_or_else(State::default, |state| *state);
self.state_stack.push(state);
}
pub fn restore(&mut self) {
if self.state_stack.len() > 1 {
self.state_stack.pop();
} else {
self.reset();
}
}
pub fn reset(&mut self) {
*self.state_mut() = Default::default();
}
pub fn save_with(&mut self, mut callback: impl FnMut(&mut Self)) {
self.save();
callback(self);
self.restore();
}
pub fn set_global_alpha(&mut self, alpha: f32) {
self.state_mut().alpha = alpha;
}
pub fn global_composite_operation(&mut self, op: CompositeOperation) {
self.state_mut().composite_operation = CompositeOperationState::new(op);
}
pub fn global_composite_blend_func(&mut self, src_factor: BlendFactor, dst_factor: BlendFactor) {
self.global_composite_blend_func_separate(src_factor, dst_factor, src_factor, dst_factor);
}
pub fn global_composite_blend_func_separate(
&mut self,
src_rgb: BlendFactor,
dst_rgb: BlendFactor,
src_alpha: BlendFactor,
dst_alpha: BlendFactor,
) {
self.state_mut().composite_operation = CompositeOperationState {
src_rgb,
src_alpha,
dst_rgb,
dst_alpha,
}
}
pub fn set_render_target(&mut self, target: RenderTarget) {
if self.current_render_target != target {
self.append_cmd(Command::new(CommandType::SetRenderTarget(target)));
self.current_render_target = target;
}
}
fn append_cmd(&mut self, cmd: Command) {
self.commands.push(cmd);
}
pub fn create_image_empty(
&mut self,
width: usize,
height: usize,
format: PixelFormat,
flags: ImageFlags,
) -> Result<ImageId, ErrorKind> {
let info = ImageInfo::new(flags, width, height, format);
self.images.alloc(&mut self.renderer, info)
}
pub fn create_image_from_native_texture(
&mut self,
texture: T::NativeTexture,
info: ImageInfo,
) -> Result<ImageId, ErrorKind> {
self.images.register_native_texture(&mut self.renderer, texture, info)
}
pub fn create_image<'a, S: Into<ImageSource<'a>>>(
&mut self,
src: S,
flags: ImageFlags,
) -> Result<ImageId, ErrorKind> {
let src = src.into();
let size = src.dimensions();
let id = self.create_image_empty(size.width, size.height, src.format(), flags)?;
self.images.update(&mut self.renderer, id, src, 0, 0)?;
Ok(id)
}
pub fn get_native_texture(&self, id: ImageId) -> Result<T::NativeTexture, ErrorKind> {
self.get_image(id)
.ok_or(ErrorKind::ImageIdNotFound)
.and_then(|image| self.renderer.get_native_texture(image))
}
pub fn get_image(&self, id: ImageId) -> Option<&T::Image> {
self.images.get(id)
}
pub fn get_image_mut(&mut self, id: ImageId) -> Option<&mut T::Image> {
self.images.get_mut(id)
}
pub fn realloc_image(
&mut self,
id: ImageId,
width: usize,
height: usize,
format: PixelFormat,
flags: ImageFlags,
) -> Result<(), ErrorKind> {
let info = ImageInfo::new(flags, width, height, format);
self.images.realloc(&mut self.renderer, id, info)
}
#[cfg(feature = "image-loading")]
pub fn load_image_file<P: AsRef<FilePath>>(
&mut self,
filename: P,
flags: ImageFlags,
) -> Result<ImageId, ErrorKind> {
let image = ::image::open(filename)?;
let src = ImageSource::try_from(&image)?;
self.create_image(src, flags)
}
#[cfg(feature = "image-loading")]
pub fn load_image_mem(&mut self, data: &[u8], flags: ImageFlags) -> Result<ImageId, ErrorKind> {
let image = ::image::load_from_memory(data)?;
let src = ImageSource::try_from(&image)?;
self.create_image(src, flags)
}
pub fn update_image<'a, S: Into<ImageSource<'a>>>(
&mut self,
id: ImageId,
src: S,
x: usize,
y: usize,
) -> Result<(), ErrorKind> {
self.images.update(&mut self.renderer, id, src.into(), x, y)
}
pub fn delete_image(&mut self, id: ImageId) {
self.images.remove(&mut self.renderer, id);
}
pub fn image_info(&self, id: ImageId) -> Result<ImageInfo, ErrorKind> {
if let Some(info) = self.images.info(id) {
Ok(info)
} else {
Err(ErrorKind::ImageIdNotFound)
}
}
pub fn image_size(&self, id: ImageId) -> Result<(usize, usize), ErrorKind> {
let info = self.image_info(id)?;
Ok((info.width(), info.height()))
}
pub fn filter_image(&mut self, target_image: ImageId, filter: ImageFilter, source_image: ImageId) {
let (image_width, image_height) = match self.image_size(source_image) {
Ok((w, h)) => (w, h),
Err(_) => return,
};
let mut cmd = Command::new(CommandType::RenderFilteredImage { target_image, filter });
cmd.image = Some(source_image);
let vertex_offset = self.verts.len();
let image_width = image_width as f32;
let image_height = image_height as f32;
let quad_x0 = 0.0;
let quad_y0 = -image_height;
let quad_x1 = image_width;
let quad_y1 = image_height;
let texture_x0 = -(image_width / 2.);
let texture_y0 = -(image_height / 2.);
let texture_x1 = (image_width) / 2.;
let texture_y1 = (image_height) / 2.;
self.verts.push(Vertex::new(quad_x0, quad_y0, texture_x0, texture_y0));
self.verts.push(Vertex::new(quad_x1, quad_y1, texture_x1, texture_y1));
self.verts.push(Vertex::new(quad_x1, quad_y0, texture_x1, texture_y0));
self.verts.push(Vertex::new(quad_x0, quad_y0, texture_x0, texture_y0));
self.verts.push(Vertex::new(quad_x0, quad_y1, texture_x0, texture_y1));
self.verts.push(Vertex::new(quad_x1, quad_y1, texture_x1, texture_y1));
cmd.triangles_verts = Some((vertex_offset, 6));
self.append_cmd(cmd)
}
pub fn reset_transform(&mut self) {
self.state_mut().transform = Transform2D::identity();
}
#[allow(clippy::many_single_char_names)]
pub fn set_transform(&mut self, transform: &Transform2D) {
self.state_mut().transform.premultiply(transform);
}
pub fn translate(&mut self, x: f32, y: f32) {
let mut t = Transform2D::identity();
t.translate(x, y);
self.state_mut().transform.premultiply(&t);
}
pub fn rotate(&mut self, angle: f32) {
let mut t = Transform2D::identity();
t.rotate(angle);
self.state_mut().transform.premultiply(&t);
}
pub fn skew_x(&mut self, angle: f32) {
let mut t = Transform2D::identity();
t.skew_x(angle);
self.state_mut().transform.premultiply(&t);
}
pub fn skew_y(&mut self, angle: f32) {
let mut t = Transform2D::identity();
t.skew_y(angle);
self.state_mut().transform.premultiply(&t);
}
pub fn scale(&mut self, x: f32, y: f32) {
let mut t = Transform2D::identity();
t.scale(x, y);
self.state_mut().transform.premultiply(&t);
}
pub fn transform(&self) -> Transform2D {
self.state().transform
}
pub fn scissor(&mut self, x: f32, y: f32, w: f32, h: f32) {
let state = self.state_mut();
let w = w.max(0.0);
let h = h.max(0.0);
let mut transform = Transform2D::new_translation(x + w * 0.5, y + h * 0.5);
transform.multiply(&state.transform);
state.scissor.transform = transform;
state.scissor.extent = Some([w * 0.5, h * 0.5]);
}
pub fn intersect_scissor(&mut self, x: f32, y: f32, w: f32, h: f32) {
let state = self.state_mut();
if state.scissor.extent.is_none() {
self.scissor(x, y, w, h);
return;
}
let extent = state.scissor.extent.unwrap();
let mut pxform = state.scissor.transform;
let mut invxform = state.transform;
invxform.inverse();
pxform.multiply(&invxform);
let ex = extent[0];
let ey = extent[1];
let tex = ex * pxform[0].abs() + ey * pxform[2].abs();
let tey = ex * pxform[1].abs() + ey * pxform[3].abs();
let rect = Rect::new(pxform[4] - tex, pxform[5] - tey, tex * 2.0, tey * 2.0);
let res = rect.intersect(Rect::new(x, y, w, h));
self.scissor(res.x, res.y, res.w, res.h);
}
pub fn reset_scissor(&mut self) {
self.state_mut().scissor = Scissor::default();
}
pub fn contains_point(&self, path: &Path, x: f32, y: f32, fill_rule: FillRule) -> bool {
let transform = self.state().transform;
let path_cache = path.cache(&transform, self.tess_tol, self.dist_tol);
if path_cache.bounds.maxx < 0.0
|| path_cache.bounds.minx > self.width() as f32
|| path_cache.bounds.maxy < 0.0
|| path_cache.bounds.miny > self.height() as f32
{
return false;
}
path_cache.contains_point(x, y, fill_rule)
}
pub fn path_bbox(&self, path: &Path) -> Bounds {
let transform = self.state().transform;
let path_cache = path.cache(&transform, self.tess_tol, self.dist_tol);
path_cache.bounds
}
pub fn fill_path(&mut self, path: &Path, paint: &Paint) {
self.fill_path_internal(path, &paint.flavor, paint.shape_anti_alias, paint.fill_rule);
}
fn fill_path_internal(&mut self, path: &Path, paint_flavor: &PaintFlavor, anti_alias: bool, fill_rule: FillRule) {
let mut paint_flavor = paint_flavor.clone();
let transform = self.state().transform;
let mut path_cache = path.cache(&transform, self.tess_tol, self.dist_tol);
let canvas_width = self.width();
let canvas_height = self.height();
if path_cache.bounds.maxx < 0.0
|| path_cache.bounds.minx > canvas_width as f32
|| path_cache.bounds.maxy < 0.0
|| path_cache.bounds.miny > canvas_height as f32
{
return;
}
paint_flavor.mul_alpha(self.state().alpha);
let scissor = self.state().scissor;
let fringe_width = if anti_alias { self.fringe_width } else { 0.0 };
path_cache.expand_fill(fringe_width, LineJoin::Miter, 2.4);
if let (Some(path_rect), Some(scissor_rect), true) = (
path_cache.path_fill_is_rect(),
scissor.as_rect(canvas_width as f32, canvas_height as f32),
paint_flavor.is_straight_tinted_image(anti_alias),
) {
if scissor_rect.contains_rect(&path_rect) {
self.render_unclipped_image_blit(&path_rect, &transform, &paint_flavor);
return;
} else if let Some(intersection) = path_rect.intersection(&scissor_rect) {
self.render_unclipped_image_blit(&intersection, &transform, &paint_flavor);
return;
} else {
return;
}
}
let flavor = if path_cache.contours.len() == 1 && path_cache.contours[0].convexity == Convexity::Convex {
let params = Params::new(
&self.images,
&transform,
&paint_flavor,
&Default::default(),
&scissor,
self.fringe_width,
self.fringe_width,
-1.0,
);
CommandType::ConvexFill { params }
} else {
let stencil_params = Params {
stroke_thr: -1.0,
shader_type: ShaderType::Stencil,
..Params::default()
};
let fill_params = Params::new(
&self.images,
&transform,
&paint_flavor,
&Default::default(),
&scissor,
self.fringe_width,
self.fringe_width,
-1.0,
);
CommandType::ConcaveFill {
stencil_params,
fill_params,
}
};
let mut cmd = Command::new(flavor);
cmd.fill_rule = fill_rule;
cmd.composite_operation = self.state().composite_operation;
if let PaintFlavor::Image { id, .. } = paint_flavor {
cmd.image = Some(id);
} else if let Some(paint::GradientColors::MultiStop { stops }) = paint_flavor.gradient_colors() {
cmd.image = self
.gradients
.lookup_or_add(stops, &mut self.images, &mut self.renderer)
.ok();
}
let mut offset = self.verts.len();
cmd.drawables.reserve_exact(path_cache.contours.len());
for contour in &path_cache.contours {
let mut drawable = Drawable::default();
if !contour.fill.is_empty() {
drawable.fill_verts = Some((offset, contour.fill.len()));
self.verts.extend_from_slice(&contour.fill);
offset += contour.fill.len();
}
if !contour.stroke.is_empty() {
drawable.stroke_verts = Some((offset, contour.stroke.len()));
self.verts.extend_from_slice(&contour.stroke);
offset += contour.stroke.len();
}
cmd.drawables.push(drawable);
}
if let CommandType::ConcaveFill { .. } = cmd.cmd_type {
self.verts.push(Vertex::new(
path_cache.bounds.maxx + fringe_width,
path_cache.bounds.maxy + fringe_width,
0.5,
1.0,
));
self.verts.push(Vertex::new(
path_cache.bounds.maxx + fringe_width,
path_cache.bounds.miny - fringe_width,
0.5,
1.0,
));
self.verts.push(Vertex::new(
path_cache.bounds.minx - fringe_width,
path_cache.bounds.maxy + fringe_width,
0.5,
1.0,
));
self.verts.push(Vertex::new(
path_cache.bounds.minx - fringe_width,
path_cache.bounds.miny,
0.5,
1.0,
));
cmd.triangles_verts = Some((offset, 4));
}
self.append_cmd(cmd);
}
pub fn stroke_path(&mut self, path: &Path, paint: &Paint) {
self.stroke_path_internal(path, &paint.flavor, paint.shape_anti_alias, &paint.stroke);
}
fn stroke_path_internal(
&mut self,
path: &Path,
paint_flavor: &PaintFlavor,
anti_alias: bool,
stroke: &StrokeSettings,
) {
let mut paint_flavor = paint_flavor.clone();
let transform = self.state().transform;
let mut path_cache = path.cache(&transform, self.tess_tol, self.dist_tol);
if path_cache.bounds.maxx < 0.0
|| path_cache.bounds.minx > self.width() as f32
|| path_cache.bounds.maxy < 0.0
|| path_cache.bounds.miny > self.height() as f32
{
return;
}
let scissor = self.state().scissor;
let mut line_width = (stroke.line_width * transform.average_scale()).max(0.0);
if line_width < self.fringe_width {
let alpha = (line_width / self.fringe_width).max(0.0).min(1.0);
paint_flavor.mul_alpha(alpha * alpha);
line_width = self.fringe_width;
}
paint_flavor.mul_alpha(self.state().alpha);
let fringe_with = if anti_alias { self.fringe_width } else { 0.0 };
path_cache.expand_stroke(
line_width * 0.5,
fringe_with,
stroke.line_cap_start,
stroke.line_cap_end,
stroke.line_join,
stroke.miter_limit,
self.tess_tol,
);
let params = Params::new(
&self.images,
&transform,
&paint_flavor,
&Default::default(),
&scissor,
line_width,
self.fringe_width,
-1.0,
);
let flavor = if stroke.stencil_strokes {
let params2 = Params::new(
&self.images,
&transform,
&paint_flavor,
&Default::default(),
&scissor,
line_width,
self.fringe_width,
1.0 - 0.5 / 255.0,
);
CommandType::StencilStroke {
params1: params,
params2,
}
} else {
CommandType::Stroke { params }
};
let mut cmd = Command::new(flavor);
cmd.composite_operation = self.state().composite_operation;
if let PaintFlavor::Image { id, .. } = paint_flavor {
cmd.image = Some(id);
} else if let Some(paint::GradientColors::MultiStop { stops }) = paint_flavor.gradient_colors() {
cmd.image = self
.gradients
.lookup_or_add(stops, &mut self.images, &mut self.renderer)
.ok();
}
let mut offset = self.verts.len();
cmd.drawables.reserve_exact(path_cache.contours.len());
for contour in &path_cache.contours {
let mut drawable = Drawable::default();
if !contour.stroke.is_empty() {
drawable.stroke_verts = Some((offset, contour.stroke.len()));
self.verts.extend_from_slice(&contour.stroke);
offset += contour.stroke.len();
}
cmd.drawables.push(drawable);
}
self.append_cmd(cmd);
}
fn render_unclipped_image_blit(&mut self, target_rect: &Rect, transform: &Transform2D, paint_flavor: &PaintFlavor) {
let scissor = self.state().scissor;
let mut params = Params::new(
&self.images,
transform,
paint_flavor,
&Default::default(),
&scissor,
0.,
0.,
-1.0,
);
params.shader_type = ShaderType::TextureCopyUnclipped;
let mut cmd = Command::new(CommandType::Triangles { params });
cmd.composite_operation = self.state().composite_operation;
let x0 = target_rect.x;
let y0 = target_rect.y;
let x1 = x0 + target_rect.w;
let y1 = y0 + target_rect.h;
let (p0, p1) = (x0, y0);
let (p2, p3) = (x1, y0);
let (p4, p5) = (x1, y1);
let (p6, p7) = (x0, y1);
let mut to_texture_space_transform = Transform2D::identity();
to_texture_space_transform.scale(1. / params.extent[0], 1. / params.extent[1]);
to_texture_space_transform.premultiply(&Transform2D([
params.paint_mat[0],
params.paint_mat[1],
params.paint_mat[4],
params.paint_mat[5],
params.paint_mat[8],
params.paint_mat[9],
]));
let (s0, t0) = to_texture_space_transform.transform_point(target_rect.x, target_rect.y);
let (s1, t1) =
to_texture_space_transform.transform_point(target_rect.x + target_rect.w, target_rect.y + target_rect.h);
let verts = [
Vertex::new(p0, p1, s0, t0),
Vertex::new(p4, p5, s1, t1),
Vertex::new(p2, p3, s1, t0),
Vertex::new(p0, p1, s0, t0),
Vertex::new(p6, p7, s0, t1),
Vertex::new(p4, p5, s1, t1),
];
if let &PaintFlavor::Image { id, .. } = paint_flavor {
cmd.image = Some(id);
}
cmd.triangles_verts = Some((self.verts.len(), verts.len()));
self.append_cmd(cmd);
self.verts.extend_from_slice(&verts);
}
pub fn add_font<P: AsRef<FilePath>>(&mut self, file_path: P) -> Result<FontId, ErrorKind> {
self.text_context.borrow_mut().add_font_file(file_path)
}
pub fn add_font_mem(&mut self, data: &[u8]) -> Result<FontId, ErrorKind> {
self.text_context.borrow_mut().add_font_mem(data)
}
pub fn add_font_dir<P: AsRef<FilePath>>(&mut self, dir_path: P) -> Result<Vec<FontId>, ErrorKind> {
self.text_context.borrow_mut().add_font_dir(dir_path)
}
pub fn measure_text<S: AsRef<str>>(
&self,
x: f32,
y: f32,
text: S,
paint: &Paint,
) -> Result<TextMetrics, ErrorKind> {
let scale = self.font_scale() * self.device_px_ratio;
let mut text_settings = paint.text.clone();
text_settings.font_size *= scale;
text_settings.letter_spacing *= scale;
let scale = self.font_scale() * self.device_px_ratio;
let invscale = 1.0 / scale;
self.text_context
.borrow_mut()
.measure_text(x * scale, y * scale, text, &text_settings)
.map(|mut metrics| {
metrics.scale(invscale);
metrics
})
}
pub fn measure_font(&self, paint: &Paint) -> Result<FontMetrics, ErrorKind> {
let scale = self.font_scale() * self.device_px_ratio;
self.text_context
.borrow_mut()
.measure_font(paint.text.font_size * scale, &paint.text.font_ids)
}
pub fn break_text<S: AsRef<str>>(&self, max_width: f32, text: S, paint: &Paint) -> Result<usize, ErrorKind> {
let scale = self.font_scale() * self.device_px_ratio;
let mut text_settings = paint.text.clone();
text_settings.font_size *= scale;
text_settings.letter_spacing *= scale;
let max_width = max_width * scale;
self.text_context
.borrow_mut()
.break_text(max_width, text, &text_settings)
}
pub fn break_text_vec<S: AsRef<str>>(
&self,
max_width: f32,
text: S,
paint: &Paint,
) -> Result<Vec<Range<usize>>, ErrorKind> {
let scale = self.font_scale() * self.device_px_ratio;
let mut text_settings = paint.text.clone();
text_settings.font_size *= scale;
text_settings.letter_spacing *= scale;
let max_width = max_width * scale;
self.text_context
.borrow_mut()
.break_text_vec(max_width, text, &text_settings)
}
pub fn fill_text<S: AsRef<str>>(
&mut self,
x: f32,
y: f32,
text: S,
paint: &Paint,
) -> Result<TextMetrics, ErrorKind> {
self.draw_text(x, y, text.as_ref(), paint, RenderMode::Fill)
}
pub fn stroke_text<S: AsRef<str>>(
&mut self,
x: f32,
y: f32,
text: S,
paint: &Paint,
) -> Result<TextMetrics, ErrorKind> {
self.draw_text(x, y, text.as_ref(), paint, RenderMode::Stroke)
}
pub fn draw_glyph_commands(&mut self, draw_commands: GlyphDrawCommands, paint: &Paint, scale: f32) {
let transform = self.state().transform;
let invscale = 1.0 / scale;
let create_vertices = |quads: &Vec<text::Quad>| {
let mut verts = Vec::with_capacity(quads.len() * 6);
for quad in quads {
let (p0, p1) = transform.transform_point(quad.x0 * invscale, quad.y0 * invscale);
let (p2, p3) = transform.transform_point(quad.x1 * invscale, quad.y0 * invscale);
let (p4, p5) = transform.transform_point(quad.x1 * invscale, quad.y1 * invscale);
let (p6, p7) = transform.transform_point(quad.x0 * invscale, quad.y1 * invscale);
verts.push(Vertex::new(p0, p1, quad.s0, quad.t0));
verts.push(Vertex::new(p4, p5, quad.s1, quad.t1));
verts.push(Vertex::new(p2, p3, quad.s1, quad.t0));
verts.push(Vertex::new(p0, p1, quad.s0, quad.t0));
verts.push(Vertex::new(p6, p7, quad.s0, quad.t1));
verts.push(Vertex::new(p4, p5, quad.s1, quad.t1));
}
verts
};
let mut paint_flavor = paint.flavor.clone();
paint_flavor.mul_alpha(self.state().alpha);
for cmd in draw_commands.alpha_glyphs {
let verts = create_vertices(&cmd.quads);
self.render_triangles(&verts, &transform, &paint_flavor, GlyphTexture::AlphaMask(cmd.image_id));
}
for cmd in draw_commands.color_glyphs {
let verts = create_vertices(&cmd.quads);
self.render_triangles(
&verts,
&transform,
&paint_flavor,
GlyphTexture::ColorTexture(cmd.image_id),
);
}
}
fn draw_text(
&mut self,
x: f32,
y: f32,
text: &str,
paint: &Paint,
render_mode: RenderMode,
) -> Result<TextMetrics, ErrorKind> {
let scale = self.font_scale() * self.device_px_ratio;
let invscale = 1.0 / scale;
let mut stroke = paint.stroke.clone();
stroke.line_width *= scale;
let mut text_settings = paint.text.clone();
text_settings.font_size *= scale;
text_settings.letter_spacing *= scale;
let mut layout = text::shape(
x * scale,
y * scale,
&mut self.text_context.borrow_mut(),
&text_settings,
text,
None,
)?;
let bitmap_glyphs = layout.has_bitmap_glyphs();
let need_direct_rendering = text_settings.font_size > 92.0;
if need_direct_rendering && !bitmap_glyphs {
text::render_direct(
self,
&layout,
&paint.flavor,
paint.shape_anti_alias,
&stroke,
text_settings.font_size,
render_mode,
invscale,
)?;
} else {
let atlas = if bitmap_glyphs && need_direct_rendering {
self.ephemeral_glyph_atlas.get_or_insert_with(Default::default).clone()
} else {
self.glyph_atlas.clone()
};
let draw_commands =
atlas.render_atlas(self, &layout, text_settings.font_size, stroke.line_width, render_mode)?;
self.draw_glyph_commands(draw_commands, paint, scale);
}
layout.scale(invscale);
Ok(layout)
}
fn render_triangles(
&mut self,
verts: &[Vertex],
transform: &Transform2D,
paint_flavor: &PaintFlavor,
glyph_texture: GlyphTexture,
) {
let scissor = self.state().scissor;
let params = Params::new(
&self.images,
transform,
paint_flavor,
&glyph_texture,
&scissor,
1.0,
1.0,
-1.0,
);
let mut cmd = Command::new(CommandType::Triangles { params });
cmd.composite_operation = self.state().composite_operation;
cmd.glyph_texture = glyph_texture;
if let &PaintFlavor::Image { id, .. } = paint_flavor {
cmd.image = Some(id);
} else if let Some(paint::GradientColors::MultiStop { stops }) = paint_flavor.gradient_colors() {
cmd.image = self
.gradients
.lookup_or_add(stops, &mut self.images, &mut self.renderer)
.ok();
}
cmd.triangles_verts = Some((self.verts.len(), verts.len()));
self.append_cmd(cmd);
self.verts.extend_from_slice(verts);
}
fn font_scale(&self) -> f32 {
let avg_scale = self.state().transform.average_scale();
geometry::quantize(avg_scale, 0.1).min(7.0)
}
fn state(&self) -> &State {
self.state_stack.last().unwrap()
}
fn state_mut(&mut self) -> &mut State {
self.state_stack.last_mut().unwrap()
}
#[cfg(feature = "debug_inspector")]
pub fn debug_inspector_get_font_textures(&self) -> Vec<ImageId> {
self.glyph_atlas
.glyph_textures
.borrow()
.iter()
.map(|t| t.image_id)
.collect()
}
#[cfg(feature = "debug_inspector")]
pub fn debug_inspector_draw_image(&mut self, id: ImageId) {
if let Ok(size) = self.image_size(id) {
let width = size.0 as f32;
let height = size.1 as f32;
let mut path = Path::new();
path.rect(0f32, 0f32, width, height);
self.fill_path(&path, &Paint::image(id, 0f32, 0f32, width, height, 0f32, 1f32));
}
}
}
impl<T: Renderer> Drop for Canvas<T> {
fn drop(&mut self) {
self.images.clear(&mut self.renderer);
}
}
#[cfg(feature = "image-loading")]
pub use ::image as img;
pub use imgref;
pub use rgb;
#[cfg(test)]
#[derive(Default)]
pub struct RecordingRenderer {
pub last_commands: Rc<RefCell<Vec<renderer::Command>>>,
}
#[cfg(test)]
impl Renderer for RecordingRenderer {
type Image = DummyImage;
type NativeTexture = ();
fn set_size(&mut self, _width: u32, _height: u32, _dpi: f32) {}
fn render(
&mut self,
_images: &mut ImageStore<Self::Image>,
_verts: &[renderer::Vertex],
commands: Vec<renderer::Command>,
) {
*self.last_commands.borrow_mut() = commands;
}
fn alloc_image(&mut self, info: crate::ImageInfo) -> Result<Self::Image, ErrorKind> {
Ok(Self::Image { info })
}
fn create_image_from_native_texture(
&mut self,
_native_texture: Self::NativeTexture,
_info: crate::ImageInfo,
) -> Result<Self::Image, ErrorKind> {
Err(ErrorKind::UnsupportedImageFormat)
}
fn update_image(
&mut self,
image: &mut Self::Image,
data: crate::ImageSource,
x: usize,
y: usize,
) -> Result<(), ErrorKind> {
let size = data.dimensions();
if x + size.width > image.info.width() {
return Err(ErrorKind::ImageUpdateOutOfBounds);
}
if y + size.height > image.info.height() {
return Err(ErrorKind::ImageUpdateOutOfBounds);
}
Ok(())
}
fn delete_image(&mut self, _image: Self::Image, _image_id: crate::ImageId) {}
fn screenshot(&mut self) -> Result<imgref::ImgVec<rgb::RGBA8>, ErrorKind> {
Ok(imgref::ImgVec::new(Vec::new(), 0, 0))
}
}
#[cfg(test)]
pub struct DummyImage {
info: ImageInfo,
}
#[test]
fn test_image_blit_fast_path() {
use renderer::{Command, CommandType};
let renderer = RecordingRenderer::default();
let recorded_commands = renderer.last_commands.clone();
let mut canvas = Canvas::new(renderer).unwrap();
canvas.set_size(100, 100, 1.);
let mut path = Path::new();
path.rect(10., 10., 50., 50.);
let image = canvas
.create_image_empty(30, 30, PixelFormat::Rgba8, ImageFlags::empty())
.unwrap();
let paint = Paint::image(image, 0., 0., 30., 30., 0., 0.).with_anti_alias(false);
canvas.fill_path(&path, &paint);
canvas.flush();
let commands = recorded_commands.borrow();
let mut commands = commands.iter();
assert!(matches!(
commands.next(),
Some(Command {
cmd_type: CommandType::SetRenderTarget(..),
..
})
));
assert!(matches!(
commands.next(),
Some(Command {
cmd_type: CommandType::Triangles {
params: Params {
shader_type: renderer::ShaderType::TextureCopyUnclipped,
..
}
},
..
})
));
}