diff options
author | 2022-12-02 18:53:21 +0100 | |
---|---|---|
committer | 2022-12-02 18:53:21 +0100 | |
commit | 4029a1cdaaac1abbdcc141b20469a49670cd99b6 (patch) | |
tree | 71fa9d9c4aa1f02ce05771db43a4bb7bc6570e77 /graphics | |
parent | 676d8efe03ebdbeeb95aef96b8097395b788b1ab (diff) | |
parent | 8b55e9b9e6ba0b83038dd491dd34d95b4f9a381b (diff) | |
download | iced-4029a1cdaaac1abbdcc141b20469a49670cd99b6.tar.gz iced-4029a1cdaaac1abbdcc141b20469a49670cd99b6.tar.bz2 iced-4029a1cdaaac1abbdcc141b20469a49670cd99b6.zip |
Merge branch 'master' into non-uniform-border-radius-for-quads
Diffstat (limited to 'graphics')
-rw-r--r-- | graphics/Cargo.toml | 44 | ||||
-rw-r--r-- | graphics/src/backend.rs | 4 | ||||
-rw-r--r-- | graphics/src/gradient.rs | 7 | ||||
-rw-r--r-- | graphics/src/gradient/linear.rs | 5 | ||||
-rw-r--r-- | graphics/src/image.rs | 10 | ||||
-rw-r--r-- | graphics/src/image/raster.rs | 242 | ||||
-rw-r--r-- | graphics/src/image/storage.rs | 31 | ||||
-rw-r--r-- | graphics/src/image/vector.rs | 176 | ||||
-rw-r--r-- | graphics/src/layer.rs | 25 | ||||
-rw-r--r-- | graphics/src/layer/mesh.rs | 94 | ||||
-rw-r--r-- | graphics/src/lib.rs | 1 | ||||
-rw-r--r-- | graphics/src/primitive.rs | 25 | ||||
-rw-r--r-- | graphics/src/renderer.rs | 4 | ||||
-rw-r--r-- | graphics/src/triangle.rs | 33 | ||||
-rw-r--r-- | graphics/src/widget/canvas.rs | 2 | ||||
-rw-r--r-- | graphics/src/widget/canvas/fill.rs | 4 | ||||
-rw-r--r-- | graphics/src/widget/canvas/frame.rs | 195 | ||||
-rw-r--r-- | graphics/src/widget/canvas/stroke.rs | 4 | ||||
-rw-r--r-- | graphics/src/widget/canvas/style.rs | 23 | ||||
-rw-r--r-- | graphics/src/window/compositor.rs | 2 | ||||
-rw-r--r-- | graphics/src/window/gl_compositor.rs | 2 |
21 files changed, 825 insertions, 108 deletions
diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 3b0e5236..b601f37c 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_graphics" -version = "0.3.1" +version = "0.4.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A bunch of backend-agnostic types that can be leveraged to build a renderer for Iced" @@ -11,28 +11,44 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] +svg = ["resvg", "usvg", "tiny-skia"] +image = ["png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"] +png = ["image_rs/png"] +jpeg = ["image_rs/jpeg"] +jpeg_rayon = ["image_rs/jpeg_rayon"] +gif = ["image_rs/gif"] +webp = ["image_rs/webp"] +pnm = ["image_rs/pnm"] +ico = ["image_rs/ico"] +bmp = ["image_rs/bmp"] +hdr = ["image_rs/hdr"] +dds = ["image_rs/dds"] +farbfeld = ["image_rs/farbfeld"] canvas = ["lyon"] qr_code = ["qrcode", "canvas"] font-source = ["font-kit"] font-fallback = [] font-icons = [] opengl = [] +image_rs = ["kamadak-exif"] [dependencies] glam = "0.21.3" +log = "0.4" raw-window-handle = "0.5" thiserror = "1.0" +bitflags = "1.2" [dependencies.bytemuck] version = "1.4" features = ["derive"] [dependencies.iced_native] -version = "0.5" +version = "0.6" path = "../native" [dependencies.iced_style] -version = "0.4" +version = "0.5" path = "../style" [dependencies.lyon] @@ -48,6 +64,28 @@ default-features = false version = "0.10" optional = true +[dependencies.image_rs] +version = "0.24" +package = "image" +default-features = false +optional = true + +[dependencies.resvg] +version = "0.18" +optional = true + +[dependencies.usvg] +version = "0.18" +optional = true + +[dependencies.tiny-skia] +version = "0.6" +optional = true + +[dependencies.kamadak-exif] +version = "0.5" +optional = true + [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] all-features = true diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 7e0af2cc..2f8e9fc3 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -66,11 +66,11 @@ pub trait Text { /// A graphics backend that supports image rendering. pub trait Image { /// Returns the dimensions of the provided image. - fn dimensions(&self, handle: &image::Handle) -> (u32, u32); + fn dimensions(&self, handle: &image::Handle) -> Size<u32>; } /// A graphics backend that supports SVG rendering. pub trait Svg { /// Returns the viewport dimensions of the provided SVG. - fn viewport_dimensions(&self, handle: &svg::Handle) -> (u32, u32); + fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32>; } diff --git a/graphics/src/gradient.rs b/graphics/src/gradient.rs index 64f9e4b3..83f25238 100644 --- a/graphics/src/gradient.rs +++ b/graphics/src/gradient.rs @@ -9,7 +9,7 @@ use crate::{Color, Point, Size}; /// A fill which transitions colors progressively along a direction, either linearly, radially (TBD), /// or conically (TBD). pub enum Gradient { - /// A linear gradient interpolates colors along a direction from its [`start`] to its [`end`] + /// A linear gradient interpolates colors along a direction from its `start` to its `end` /// point. Linear(Linear), } @@ -23,10 +23,15 @@ impl Gradient { #[derive(Debug, Clone, Copy, PartialEq)] /// A point along the gradient vector where the specified [`color`] is unmixed. +/// +/// [`color`]: Self::color pub struct ColorStop { /// Offset along the gradient vector. pub offset: f32, + /// The color of the gradient at the specified [`offset`]. + /// + /// [`offset`]: Self::offset pub color: Color, } diff --git a/graphics/src/gradient/linear.rs b/graphics/src/gradient/linear.rs index 9928c1eb..c886db47 100644 --- a/graphics/src/gradient/linear.rs +++ b/graphics/src/gradient/linear.rs @@ -2,7 +2,10 @@ use crate::gradient::{ColorStop, Gradient, Position}; use crate::{Color, Point}; -/// A linear gradient that can be used in the style of [`super::Fill`] or [`super::Stroke`]. +/// A linear gradient that can be used in the style of [`Fill`] or [`Stroke`]. +/// +/// [`Fill`]: crate::widget::canvas::Fill +/// [`Stroke`]: crate::widget::canvas::Stroke #[derive(Debug, Clone, PartialEq)] pub struct Linear { /// The point where the linear gradient begins. diff --git a/graphics/src/image.rs b/graphics/src/image.rs new file mode 100644 index 00000000..04f4ff9d --- /dev/null +++ b/graphics/src/image.rs @@ -0,0 +1,10 @@ +//! Render images. +#[cfg(feature = "image_rs")] +pub mod raster; + +#[cfg(feature = "svg")] +pub mod vector; + +pub mod storage; + +pub use storage::Storage; diff --git a/graphics/src/image/raster.rs b/graphics/src/image/raster.rs new file mode 100644 index 00000000..da46c30f --- /dev/null +++ b/graphics/src/image/raster.rs @@ -0,0 +1,242 @@ +//! Raster image loading and caching. +use crate::image::Storage; +use crate::Size; + +use iced_native::image; + +use bitflags::bitflags; +use std::collections::{HashMap, HashSet}; + +/// Entry in cache corresponding to an image handle +#[derive(Debug)] +pub enum Memory<T: Storage> { + /// Image data on host + Host(::image_rs::ImageBuffer<::image_rs::Rgba<u8>, Vec<u8>>), + /// Storage entry + Device(T::Entry), + /// Image not found + NotFound, + /// Invalid image data + Invalid, +} + +impl<T: Storage> Memory<T> { + /// Width and height of image + pub fn dimensions(&self) -> Size<u32> { + use crate::image::storage::Entry; + + match self { + Memory::Host(image) => { + let (width, height) = image.dimensions(); + + Size::new(width, height) + } + Memory::Device(entry) => entry.size(), + Memory::NotFound => Size::new(1, 1), + Memory::Invalid => Size::new(1, 1), + } + } +} + +/// Caches image raster data +#[derive(Debug)] +pub struct Cache<T: Storage> { + map: HashMap<u64, Memory<T>>, + hits: HashSet<u64>, +} + +impl<T: Storage> Cache<T> { + /// Load image + pub fn load(&mut self, handle: &image::Handle) -> &mut Memory<T> { + if self.contains(handle) { + return self.get(handle).unwrap(); + } + + let memory = match handle.data() { + image::Data::Path(path) => { + if let Ok(image) = image_rs::open(path) { + let operation = std::fs::File::open(path) + .ok() + .map(std::io::BufReader::new) + .and_then(|mut reader| { + Operation::from_exif(&mut reader).ok() + }) + .unwrap_or_else(Operation::empty); + + Memory::Host(operation.perform(image.to_rgba8())) + } else { + Memory::NotFound + } + } + image::Data::Bytes(bytes) => { + if let Ok(image) = image_rs::load_from_memory(bytes) { + let operation = + Operation::from_exif(&mut std::io::Cursor::new(bytes)) + .ok() + .unwrap_or_else(Operation::empty); + + Memory::Host(operation.perform(image.to_rgba8())) + } else { + Memory::Invalid + } + } + image::Data::Rgba { + width, + height, + pixels, + } => { + if let Some(image) = image_rs::ImageBuffer::from_vec( + *width, + *height, + pixels.to_vec(), + ) { + Memory::Host(image) + } else { + Memory::Invalid + } + } + }; + + self.insert(handle, memory); + self.get(handle).unwrap() + } + + /// Load image and upload raster data + pub fn upload( + &mut self, + handle: &image::Handle, + state: &mut T::State<'_>, + storage: &mut T, + ) -> Option<&T::Entry> { + let memory = self.load(handle); + + if let Memory::Host(image) = memory { + let (width, height) = image.dimensions(); + + let entry = storage.upload(width, height, image, state)?; + + *memory = Memory::Device(entry); + } + + if let Memory::Device(allocation) = memory { + Some(allocation) + } else { + None + } + } + + /// Trim cache misses from cache + pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) { + let hits = &self.hits; + + self.map.retain(|k, memory| { + let retain = hits.contains(k); + + if !retain { + if let Memory::Device(entry) = memory { + storage.remove(entry, state); + } + } + + retain + }); + + self.hits.clear(); + } + + fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory<T>> { + let _ = self.hits.insert(handle.id()); + + self.map.get_mut(&handle.id()) + } + + fn insert(&mut self, handle: &image::Handle, memory: Memory<T>) { + let _ = self.map.insert(handle.id(), memory); + } + + fn contains(&self, handle: &image::Handle) -> bool { + self.map.contains_key(&handle.id()) + } +} + +impl<T: Storage> Default for Cache<T> { + fn default() -> Self { + Self { + map: HashMap::new(), + hits: HashSet::new(), + } + } +} + +bitflags! { + struct Operation: u8 { + const FLIP_HORIZONTALLY = 0b001; + const ROTATE_180 = 0b010; + const FLIP_DIAGONALLY = 0b100; + } +} + +impl Operation { + // Meaning of the returned value is described e.g. at: + // https://magnushoff.com/articles/jpeg-orientation/ + fn from_exif<R>(reader: &mut R) -> Result<Self, exif::Error> + where + R: std::io::BufRead + std::io::Seek, + { + let exif = exif::Reader::new().read_from_container(reader)?; + + Ok(exif + .get_field(exif::Tag::Orientation, exif::In::PRIMARY) + .and_then(|field| field.value.get_uint(0)) + .and_then(|value| u8::try_from(value).ok()) + .and_then(|value| Self::from_bits(value.saturating_sub(1))) + .unwrap_or_else(Self::empty)) + } + + fn perform<P>( + self, + image: image_rs::ImageBuffer<P, Vec<P::Subpixel>>, + ) -> image_rs::ImageBuffer<P, Vec<P::Subpixel>> + where + P: image_rs::Pixel + 'static, + { + use image_rs::imageops; + + let mut image = if self.contains(Self::FLIP_DIAGONALLY) { + flip_diagonally(image) + } else { + image + }; + + if self.contains(Self::ROTATE_180) { + imageops::rotate180_in_place(&mut image); + } + + if self.contains(Self::FLIP_HORIZONTALLY) { + imageops::flip_horizontal_in_place(&mut image); + } + + image + } +} + +fn flip_diagonally<I>( + image: I, +) -> image_rs::ImageBuffer<I::Pixel, Vec<<I::Pixel as image_rs::Pixel>::Subpixel>> +where + I: image_rs::GenericImage, + I::Pixel: 'static, +{ + let (width, height) = image.dimensions(); + let mut out = image_rs::ImageBuffer::new(height, width); + + for x in 0..width { + for y in 0..height { + let p = image.get_pixel(x, y); + + out.put_pixel(y, x, p); + } + } + + out +} diff --git a/graphics/src/image/storage.rs b/graphics/src/image/storage.rs new file mode 100644 index 00000000..2098c7b2 --- /dev/null +++ b/graphics/src/image/storage.rs @@ -0,0 +1,31 @@ +//! Store images. +use crate::Size; + +use std::fmt::Debug; + +/// Stores cached image data for use in rendering +pub trait Storage { + /// The type of an [`Entry`] in the [`Storage`]. + type Entry: Entry; + + /// State provided to upload or remove a [`Self::Entry`]. + type State<'a>; + + /// Upload the image data of a [`Self::Entry`]. + fn upload( + &mut self, + width: u32, + height: u32, + data: &[u8], + state: &mut Self::State<'_>, + ) -> Option<Self::Entry>; + + /// Romve a [`Self::Entry`] from the [`Storage`]. + fn remove(&mut self, entry: &Self::Entry, state: &mut Self::State<'_>); +} + +/// An entry in some [`Storage`], +pub trait Entry: Debug { + /// The [`Size`] of the [`Entry`]. + fn size(&self) -> Size<u32>; +} diff --git a/graphics/src/image/vector.rs b/graphics/src/image/vector.rs new file mode 100644 index 00000000..42f4b500 --- /dev/null +++ b/graphics/src/image/vector.rs @@ -0,0 +1,176 @@ +//! Vector image loading and caching +use crate::image::Storage; + +use iced_native::svg; +use iced_native::Size; + +use std::collections::{HashMap, HashSet}; +use std::fs; + +/// Entry in cache corresponding to an svg handle +pub enum Svg { + /// Parsed svg + Loaded(usvg::Tree), + /// Svg not found or failed to parse + NotFound, +} + +impl Svg { + /// Viewport width and height + pub fn viewport_dimensions(&self) -> Size<u32> { + match self { + Svg::Loaded(tree) => { + let size = tree.svg_node().size; + + Size::new(size.width() as u32, size.height() as u32) + } + Svg::NotFound => Size::new(1, 1), + } + } +} + +/// Caches svg vector and raster data +#[derive(Debug)] +pub struct Cache<T: Storage> { + svgs: HashMap<u64, Svg>, + rasterized: HashMap<(u64, u32, u32), T::Entry>, + svg_hits: HashSet<u64>, + rasterized_hits: HashSet<(u64, u32, u32)>, +} + +impl<T: Storage> Cache<T> { + /// Load svg + pub fn load(&mut self, handle: &svg::Handle) -> &Svg { + if self.svgs.contains_key(&handle.id()) { + return self.svgs.get(&handle.id()).unwrap(); + } + + let svg = match handle.data() { + svg::Data::Path(path) => { + let tree = fs::read_to_string(path).ok().and_then(|contents| { + usvg::Tree::from_str( + &contents, + &usvg::Options::default().to_ref(), + ) + .ok() + }); + + tree.map(Svg::Loaded).unwrap_or(Svg::NotFound) + } + svg::Data::Bytes(bytes) => { + match usvg::Tree::from_data( + bytes, + &usvg::Options::default().to_ref(), + ) { + Ok(tree) => Svg::Loaded(tree), + Err(_) => Svg::NotFound, + } + } + }; + + let _ = self.svgs.insert(handle.id(), svg); + self.svgs.get(&handle.id()).unwrap() + } + + /// Load svg and upload raster data + pub fn upload( + &mut self, + handle: &svg::Handle, + [width, height]: [f32; 2], + scale: f32, + state: &mut T::State<'_>, + storage: &mut T, + ) -> Option<&T::Entry> { + let id = handle.id(); + + let (width, height) = ( + (scale * width).ceil() as u32, + (scale * height).ceil() as u32, + ); + + // TODO: Optimize! + // We currently rerasterize the SVG when its size changes. This is slow + // as heck. A GPU rasterizer like `pathfinder` may perform better. + // It would be cool to be able to smooth resize the `svg` example. + if self.rasterized.contains_key(&(id, width, height)) { + let _ = self.svg_hits.insert(id); + let _ = self.rasterized_hits.insert((id, width, height)); + + return self.rasterized.get(&(id, width, height)); + } + + match self.load(handle) { + Svg::Loaded(tree) => { + if width == 0 || height == 0 { + return None; + } + + // TODO: Optimize! + // We currently rerasterize the SVG when its size changes. This is slow + // as heck. A GPU rasterizer like `pathfinder` may perform better. + // It would be cool to be able to smooth resize the `svg` example. + let mut img = tiny_skia::Pixmap::new(width, height)?; + + resvg::render( + tree, + if width > height { + usvg::FitTo::Width(width) + } else { + usvg::FitTo::Height(height) + }, + img.as_mut(), + )?; + + let allocation = + storage.upload(width, height, img.data(), state)?; + log::debug!("allocating {} {}x{}", id, width, height); + + let _ = self.svg_hits.insert(id); + let _ = self.rasterized_hits.insert((id, width, height)); + let _ = self.rasterized.insert((id, width, height), allocation); + + self.rasterized.get(&(id, width, height)) + } + Svg::NotFound => None, + } + } + + /// Load svg and upload raster data + pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) { + let svg_hits = &self.svg_hits; + let rasterized_hits = &self.rasterized_hits; + + self.svgs.retain(|k, _| svg_hits.contains(k)); + self.rasterized.retain(|k, entry| { + let retain = rasterized_hits.contains(k); + + if !retain { + storage.remove(entry, state); + } + + retain + }); + self.svg_hits.clear(); + self.rasterized_hits.clear(); + } +} + +impl<T: Storage> Default for Cache<T> { + fn default() -> Self { + Self { + svgs: HashMap::new(), + rasterized: HashMap::new(), + svg_hits: HashSet::new(), + rasterized_hits: HashSet::new(), + } + } +} + +impl std::fmt::Debug for Svg { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Svg::Loaded(_) => write!(f, "Svg::Loaded"), + Svg::NotFound => write!(f, "Svg::NotFound"), + } + } +} diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index e95934b0..fd670f48 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -166,10 +166,27 @@ impl<'a> Layer<'a> { border_color: border_color.into_linear(), }); } - Primitive::Mesh2D { + Primitive::SolidMesh { buffers, size } => { + let layer = &mut layers[current_layer]; + + let bounds = Rectangle::new( + Point::new(translation.x, translation.y), + *size, + ); + + // Only draw visible content + if let Some(clip_bounds) = layer.bounds.intersection(&bounds) { + layer.meshes.push(Mesh::Solid { + origin: Point::new(translation.x, translation.y), + buffers, + clip_bounds, + }); + } + } + Primitive::GradientMesh { buffers, size, - style, + gradient, } => { let layer = &mut layers[current_layer]; @@ -180,11 +197,11 @@ impl<'a> Layer<'a> { // Only draw visible content if let Some(clip_bounds) = layer.bounds.intersection(&bounds) { - layer.meshes.push(Mesh { + layer.meshes.push(Mesh::Gradient { origin: Point::new(translation.x, translation.y), buffers, clip_bounds, - style, + gradient, }); } } diff --git a/graphics/src/layer/mesh.rs b/graphics/src/layer/mesh.rs index 979081f1..7661c5c9 100644 --- a/graphics/src/layer/mesh.rs +++ b/graphics/src/layer/mesh.rs @@ -1,31 +1,93 @@ //! A collection of triangle primitives. use crate::triangle; -use crate::{Point, Rectangle}; +use crate::{Gradient, Point, Rectangle}; /// A mesh of triangles. #[derive(Debug, Clone, Copy)] -pub struct Mesh<'a> { - /// The origin of the vertices of the [`Mesh`]. - pub origin: Point, +pub enum Mesh<'a> { + /// A mesh of triangles with a solid color. + Solid { + /// The origin of the vertices of the [`Mesh`]. + origin: Point, - /// The vertex and index buffers of the [`Mesh`]. - pub buffers: &'a triangle::Mesh2D, + /// The vertex and index buffers of the [`Mesh`]. + buffers: &'a triangle::Mesh2D<triangle::ColoredVertex2D>, - /// The clipping bounds of the [`Mesh`]. - pub clip_bounds: Rectangle<f32>, + /// The clipping bounds of the [`Mesh`]. + clip_bounds: Rectangle<f32>, + }, + /// A mesh of triangles with a gradient color. + Gradient { + /// The origin of the vertices of the [`Mesh`]. + origin: Point, - /// The shader of the [`Mesh`]. - pub style: &'a triangle::Style, + /// The vertex and index buffers of the [`Mesh`]. + buffers: &'a triangle::Mesh2D<triangle::Vertex2D>, + + /// The clipping bounds of the [`Mesh`]. + clip_bounds: Rectangle<f32>, + + /// The gradient to apply to the [`Mesh`]. + gradient: &'a Gradient, + }, +} + +impl Mesh<'_> { + /// Returns the origin of the [`Mesh`]. + pub fn origin(&self) -> Point { + match self { + Self::Solid { origin, .. } | Self::Gradient { origin, .. } => { + *origin + } + } + } + + /// Returns the indices of the [`Mesh`]. + pub fn indices(&self) -> &[u32] { + match self { + Self::Solid { buffers, .. } => &buffers.indices, + Self::Gradient { buffers, .. } => &buffers.indices, + } + } + + /// Returns the clip bounds of the [`Mesh`]. + pub fn clip_bounds(&self) -> Rectangle<f32> { + match self { + Self::Solid { clip_bounds, .. } + | Self::Gradient { clip_bounds, .. } => *clip_bounds, + } + } +} + +/// The result of counting the attributes of a set of meshes. +#[derive(Debug, Clone, Copy, Default)] +pub struct AttributeCount { + /// The total amount of solid vertices. + pub solid_vertices: usize, + + /// The total amount of gradient vertices. + pub gradient_vertices: usize, + + /// The total amount of indices. + pub indices: usize, } /// Returns the number of total vertices & total indices of all [`Mesh`]es. -pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> (usize, usize) { +pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> AttributeCount { meshes .iter() - .map(|Mesh { buffers, .. }| { - (buffers.vertices.len(), buffers.indices.len()) - }) - .fold((0, 0), |(total_v, total_i), (v, i)| { - (total_v + v, total_i + i) + .fold(AttributeCount::default(), |mut count, mesh| { + match mesh { + Mesh::Solid { buffers, .. } => { + count.solid_vertices += buffers.vertices.len(); + count.indices += buffers.indices.len(); + } + Mesh::Gradient { buffers, .. } => { + count.gradient_vertices += buffers.vertices.len(); + count.indices += buffers.indices.len(); + } + } + + count }) } diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index ec28ee58..d39dd90c 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -30,6 +30,7 @@ mod viewport; pub mod backend; pub mod font; pub mod gradient; +pub mod image; pub mod layer; pub mod overlay; pub mod renderer; diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 84f04624..6f1b6f26 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -3,6 +3,7 @@ use iced_native::svg; use iced_native::{Background, Color, Font, Rectangle, Size, Vector}; use crate::alignment; +use crate::gradient::Gradient; use crate::triangle; use std::sync::Arc; @@ -77,20 +78,32 @@ pub enum Primitive { /// The primitive to translate content: Box<Primitive>, }, - /// A low-level primitive to render a mesh of triangles. + /// A low-level primitive to render a mesh of triangles with a solid color. /// /// It can be used to render many kinds of geometry freely. - Mesh2D { - /// The vertex and index buffers of the mesh - buffers: triangle::Mesh2D, + SolidMesh { + /// The vertices and indices of the mesh. + buffers: triangle::Mesh2D<triangle::ColoredVertex2D>, + + /// The size of the drawable region of the mesh. + /// + /// Any geometry that falls out of this region will be clipped. + size: Size, + }, + /// A low-level primitive to render a mesh of triangles with a gradient. + /// + /// It can be used to render many kinds of geometry freely. + GradientMesh { + /// The vertices and indices of the mesh. + buffers: triangle::Mesh2D<triangle::Vertex2D>, /// The size of the drawable region of the mesh. /// /// Any geometry that falls out of this region will be clipped. size: Size, - /// The shader of the mesh - style: triangle::Style, + /// The [`Gradient`] to apply to the mesh. + gradient: Gradient, }, /// A cached primitive. /// diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index ff0dad2b..65350037 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -183,7 +183,7 @@ where { type Handle = image::Handle; - fn dimensions(&self, handle: &image::Handle) -> (u32, u32) { + fn dimensions(&self, handle: &image::Handle) -> Size<u32> { self.backend().dimensions(handle) } @@ -196,7 +196,7 @@ impl<B, T> svg::Renderer for Renderer<B, T> where B: Backend + backend::Svg, { - fn dimensions(&self, handle: &svg::Handle) -> (u32, u32) { + fn dimensions(&self, handle: &svg::Handle) -> Size<u32> { self.backend().viewport_dimensions(handle) } diff --git a/graphics/src/triangle.rs b/graphics/src/triangle.rs index 04ff6d21..f52b2339 100644 --- a/graphics/src/triangle.rs +++ b/graphics/src/triangle.rs @@ -1,13 +1,12 @@ //! Draw geometry using meshes of triangles. -use crate::{Color, Gradient}; - use bytemuck::{Pod, Zeroable}; /// A set of [`Vertex2D`] and indices representing a list of triangles. #[derive(Clone, Debug)] -pub struct Mesh2D { +pub struct Mesh2D<T> { /// The vertices of the mesh - pub vertices: Vec<Vertex2D>, + pub vertices: Vec<T>, + /// The list of vertex indices that defines the triangles of the mesh. /// /// Therefore, this list should always have a length that is a multiple of 3. @@ -22,23 +21,13 @@ pub struct Vertex2D { pub position: [f32; 2], } -#[derive(Debug, Clone, PartialEq)] -/// Supported shaders for triangle primitives. -pub enum Style { - /// Fill a primitive with a solid color. - Solid(Color), - /// Fill a primitive with an interpolated color. - Gradient(Gradient), -} - -impl From<Color> for Style { - fn from(color: Color) -> Self { - Self::Solid(color) - } -} +/// A two-dimensional vertex with a color. +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +#[repr(C)] +pub struct ColoredVertex2D { + /// The vertex position in 2D space. + pub position: [f32; 2], -impl From<Gradient> for Style { - fn from(gradient: Gradient) -> Self { - Self::Gradient(gradient) - } + /// The color of the vertex in __linear__ RGBA. + pub color: [f32; 4], } diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index a14940d9..b070d0a6 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -13,6 +13,7 @@ mod cursor; mod frame; mod geometry; mod program; +mod style; mod text; pub use crate::gradient::{self, Gradient}; @@ -25,6 +26,7 @@ pub use geometry::Geometry; pub use path::Path; pub use program::Program; pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; +pub use style::Style; pub use text::Text; use crate::{Backend, Primitive, Renderer}; diff --git a/graphics/src/widget/canvas/fill.rs b/graphics/src/widget/canvas/fill.rs index c69fc0d7..e954ebb5 100644 --- a/graphics/src/widget/canvas/fill.rs +++ b/graphics/src/widget/canvas/fill.rs @@ -1,14 +1,14 @@ //! Fill [crate::widget::canvas::Geometry] with a certain style. use crate::{Color, Gradient}; -pub use crate::triangle::Style; +pub use crate::widget::canvas::Style; /// The style used to fill geometry. #[derive(Debug, Clone)] pub struct Fill { /// The color or gradient of the fill. /// - /// By default, it is set to [`FillStyle::Solid`] `BLACK`. + /// By default, it is set to [`Style::Solid`] with [`Color::BLACK`]. pub style: Style, /// The fill rule defines how to determine what is inside and what is diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs index cf6c6928..d68548ae 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/graphics/src/widget/canvas/frame.rs @@ -1,7 +1,6 @@ use crate::gradient::Gradient; use crate::triangle; -use crate::triangle::Vertex2D; -use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, Text}; +use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, Style, Text}; use crate::Primitive; use iced_native::{Point, Rectangle, Size, Vector}; @@ -23,8 +22,16 @@ pub struct Frame { stroke_tessellator: tessellation::StrokeTessellator, } +enum Buffer { + Solid(tessellation::VertexBuffers<triangle::ColoredVertex2D, u32>), + Gradient( + tessellation::VertexBuffers<triangle::Vertex2D, u32>, + Gradient, + ), +} + struct BufferStack { - stack: Vec<(tessellation::VertexBuffers<Vertex2D, u32>, triangle::Style)>, + stack: Vec<Buffer>, } impl BufferStack { @@ -32,22 +39,64 @@ impl BufferStack { Self { stack: Vec::new() } } - fn get( - &mut self, - mesh_style: triangle::Style, - ) -> tessellation::BuffersBuilder<'_, Vertex2D, u32, Vertex2DBuilder> { - match self.stack.last_mut() { - Some((_, current_style)) if current_style == &mesh_style => {} - _ => { - self.stack - .push((tessellation::VertexBuffers::new(), mesh_style)); + fn get_mut(&mut self, style: &Style) -> &mut Buffer { + match style { + Style::Solid(_) => match self.stack.last() { + Some(Buffer::Solid(_)) => {} + _ => { + self.stack.push(Buffer::Solid( + tessellation::VertexBuffers::new(), + )); + } + }, + Style::Gradient(gradient) => match self.stack.last() { + Some(Buffer::Gradient(_, last)) if gradient == last => {} + _ => { + self.stack.push(Buffer::Gradient( + tessellation::VertexBuffers::new(), + gradient.clone(), + )); + } + }, + } + + self.stack.last_mut().unwrap() + } + + fn get_fill<'a>( + &'a mut self, + style: &Style, + ) -> Box<dyn tessellation::FillGeometryBuilder + 'a> { + match (style, self.get_mut(style)) { + (Style::Solid(color), Buffer::Solid(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + TriangleVertex2DBuilder(color.into_linear()), + )) } - }; + (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( + tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), + ), + _ => unreachable!(), + } + } - tessellation::BuffersBuilder::new( - &mut self.stack.last_mut().unwrap().0, - Vertex2DBuilder, - ) + fn get_stroke<'a>( + &'a mut self, + style: &Style, + ) -> Box<dyn tessellation::StrokeGeometryBuilder + 'a> { + match (style, self.get_mut(style)) { + (Style::Solid(color), Buffer::Solid(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + TriangleVertex2DBuilder(color.into_linear()), + )) + } + (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( + tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), + ), + _ => unreachable!(), + } } } @@ -73,11 +122,11 @@ impl Transform { point.y = transformed.y; } - fn transform_style(&self, style: triangle::Style) -> triangle::Style { + fn transform_style(&self, style: Style) -> Style { match style { - triangle::Style::Solid(color) => triangle::Style::Solid(color), - triangle::Style::Gradient(gradient) => { - triangle::Style::Gradient(self.transform_gradient(gradient)) + Style::Solid(color) => Style::Solid(color), + Style::Gradient(gradient) => { + Style::Gradient(self.transform_gradient(gradient)) } } } @@ -145,7 +194,7 @@ impl Frame { let mut buffer = self .buffers - .get(self.transforms.current.transform_style(style)); + .get_fill(&self.transforms.current.transform_style(style)); let options = tessellation::FillOptions::default().with_fill_rule(rule.into()); @@ -154,7 +203,7 @@ impl Frame { self.fill_tessellator.tessellate_path( path.raw(), &options, - &mut buffer, + buffer.as_mut(), ) } else { let path = path.transformed(&self.transforms.current.raw); @@ -162,7 +211,7 @@ impl Frame { self.fill_tessellator.tessellate_path( path.raw(), &options, - &mut buffer, + buffer.as_mut(), ) } .expect("Tessellate path."); @@ -180,7 +229,7 @@ impl Frame { let mut buffer = self .buffers - .get(self.transforms.current.transform_style(style)); + .get_fill(&self.transforms.current.transform_style(style)); let top_left = self.transforms.current.raw.transform_point( @@ -199,7 +248,7 @@ impl Frame { .tessellate_rectangle( &lyon::math::Box2D::new(top_left, top_left + size), &options, - &mut buffer, + buffer.as_mut(), ) .expect("Fill rectangle"); } @@ -211,7 +260,7 @@ impl Frame { let mut buffer = self .buffers - .get(self.transforms.current.transform_style(stroke.style)); + .get_stroke(&self.transforms.current.transform_style(stroke.style)); let mut options = tessellation::StrokeOptions::default(); options.line_width = stroke.width; @@ -229,7 +278,7 @@ impl Frame { self.stroke_tessellator.tessellate_path( path.raw(), &options, - &mut buffer, + buffer.as_mut(), ) } else { let path = path.transformed(&self.transforms.current.raw); @@ -237,7 +286,7 @@ impl Frame { self.stroke_tessellator.tessellate_path( path.raw(), &options, - &mut buffer, + buffer.as_mut(), ) } .expect("Stroke path"); @@ -382,16 +431,31 @@ impl Frame { } fn into_primitives(mut self) -> Vec<Primitive> { - for (buffer, style) in self.buffers.stack { - if !buffer.indices.is_empty() { - self.primitives.push(Primitive::Mesh2D { - buffers: triangle::Mesh2D { - vertices: buffer.vertices, - indices: buffer.indices, - }, - size: self.size, - style, - }) + for buffer in self.buffers.stack { + match buffer { + Buffer::Solid(buffer) => { + if !buffer.indices.is_empty() { + self.primitives.push(Primitive::SolidMesh { + buffers: triangle::Mesh2D { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + }) + } + } + Buffer::Gradient(buffer, gradient) => { + if !buffer.indices.is_empty() { + self.primitives.push(Primitive::GradientMesh { + buffers: triangle::Mesh2D { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + gradient, + }) + } + } } } @@ -401,25 +465,66 @@ impl Frame { struct Vertex2DBuilder; -impl tessellation::FillVertexConstructor<Vertex2D> for Vertex2DBuilder { - fn new_vertex(&mut self, vertex: tessellation::FillVertex<'_>) -> Vertex2D { +impl tessellation::FillVertexConstructor<triangle::Vertex2D> + for Vertex2DBuilder +{ + fn new_vertex( + &mut self, + vertex: tessellation::FillVertex<'_>, + ) -> triangle::Vertex2D { + let position = vertex.position(); + + triangle::Vertex2D { + position: [position.x, position.y], + } + } +} + +impl tessellation::StrokeVertexConstructor<triangle::Vertex2D> + for Vertex2DBuilder +{ + fn new_vertex( + &mut self, + vertex: tessellation::StrokeVertex<'_, '_>, + ) -> triangle::Vertex2D { + let position = vertex.position(); + + triangle::Vertex2D { + position: [position.x, position.y], + } + } +} + +struct TriangleVertex2DBuilder([f32; 4]); + +impl tessellation::FillVertexConstructor<triangle::ColoredVertex2D> + for TriangleVertex2DBuilder +{ + fn new_vertex( + &mut self, + vertex: tessellation::FillVertex<'_>, + ) -> triangle::ColoredVertex2D { let position = vertex.position(); - Vertex2D { + triangle::ColoredVertex2D { position: [position.x, position.y], + color: self.0, } } } -impl tessellation::StrokeVertexConstructor<Vertex2D> for Vertex2DBuilder { +impl tessellation::StrokeVertexConstructor<triangle::ColoredVertex2D> + for TriangleVertex2DBuilder +{ fn new_vertex( &mut self, vertex: tessellation::StrokeVertex<'_, '_>, - ) -> Vertex2D { + ) -> triangle::ColoredVertex2D { let position = vertex.position(); - Vertex2D { + triangle::ColoredVertex2D { position: [position.x, position.y], + color: self.0, } } } diff --git a/graphics/src/widget/canvas/stroke.rs b/graphics/src/widget/canvas/stroke.rs index f9b8e447..4c19251d 100644 --- a/graphics/src/widget/canvas/stroke.rs +++ b/graphics/src/widget/canvas/stroke.rs @@ -1,5 +1,5 @@ //! Create lines from a [crate::widget::canvas::Path] and assigns them various attributes/styles. -pub use crate::triangle::Style; +pub use crate::widget::canvas::Style; use iced_native::Color; @@ -8,7 +8,7 @@ use iced_native::Color; pub struct Stroke<'a> { /// The color or gradient of the stroke. /// - /// By default, it is set to [`StrokeStyle::Solid`] `BLACK`. + /// By default, it is set to a [`Style::Solid`] with [`Color::BLACK`]. pub style: Style, /// The distance between the two edges of the stroke. pub width: f32, diff --git a/graphics/src/widget/canvas/style.rs b/graphics/src/widget/canvas/style.rs new file mode 100644 index 00000000..6794f2e7 --- /dev/null +++ b/graphics/src/widget/canvas/style.rs @@ -0,0 +1,23 @@ +use crate::{Color, Gradient}; + +/// The coloring style of some drawing. +#[derive(Debug, Clone, PartialEq)] +pub enum Style { + /// A solid [`Color`]. + Solid(Color), + + /// A [`Gradient`] color. + Gradient(Gradient), +} + +impl From<Color> for Style { + fn from(color: Color) -> Self { + Self::Solid(color) + } +} + +impl From<Gradient> for Style { + fn from(gradient: Gradient) -> Self { + Self::Gradient(gradient) + } +} diff --git a/graphics/src/window/compositor.rs b/graphics/src/window/compositor.rs index 52255666..db4ba45d 100644 --- a/graphics/src/window/compositor.rs +++ b/graphics/src/window/compositor.rs @@ -40,7 +40,7 @@ pub trait Compositor: Sized { height: u32, ); - /// Returns [`GraphicsInformation`] used by this [`Compositor`]. + /// Returns [`Information`] used by this [`Compositor`]. fn fetch_information(&self) -> Information; /// Presents the [`Renderer`] primitives to the next frame of the given [`Surface`]. diff --git a/graphics/src/window/gl_compositor.rs b/graphics/src/window/gl_compositor.rs index 722e4d9c..a45a7ca1 100644 --- a/graphics/src/window/gl_compositor.rs +++ b/graphics/src/window/gl_compositor.rs @@ -54,7 +54,7 @@ pub trait GLCompositor: Sized { /// Resizes the viewport of the [`GLCompositor`]. fn resize_viewport(&mut self, physical_size: Size<u32>); - /// Returns [`GraphicsInformation`] used by this [`Compositor`]. + /// Returns [`Information`] used by this [`GLCompositor`]. fn fetch_information(&self) -> Information; /// Presents the primitives of the [`Renderer`] to the next frame of the |