diff options
-rw-r--r-- | core/src/rectangle.rs | 56 | ||||
-rw-r--r-- | graphics/Cargo.toml | 1 | ||||
-rw-r--r-- | graphics/src/geometry/frame.rs | 72 | ||||
-rw-r--r-- | graphics/src/image.rs | 6 | ||||
-rw-r--r-- | renderer/src/fallback.rs | 42 | ||||
-rw-r--r-- | tiny_skia/Cargo.toml | 2 | ||||
-rw-r--r-- | tiny_skia/src/engine.rs | 1 | ||||
-rw-r--r-- | tiny_skia/src/geometry.rs | 86 | ||||
-rw-r--r-- | tiny_skia/src/layer.rs | 43 | ||||
-rw-r--r-- | tiny_skia/src/lib.rs | 11 | ||||
-rw-r--r-- | wgpu/Cargo.toml | 2 | ||||
-rw-r--r-- | wgpu/src/geometry.rs | 91 | ||||
-rw-r--r-- | wgpu/src/image/mod.rs | 24 | ||||
-rw-r--r-- | wgpu/src/layer.rs | 45 | ||||
-rw-r--r-- | wgpu/src/lib.rs | 20 | ||||
-rw-r--r-- | wgpu/src/shader/image.wgsl | 12 |
16 files changed, 485 insertions, 29 deletions
diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 1556e072..99c8d55d 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -47,6 +47,62 @@ impl Rectangle<f32> { } } + /// Creates a new square [`Rectangle`] with the center at the origin and + /// with the given radius. + pub fn with_radius(radius: f32) -> Self { + Self { + x: -radius, + y: -radius, + width: radius * 2.0, + height: radius * 2.0, + } + } + + /// Creates a new axis-aligned [`Rectangle`] from the given vertices; returning the + /// rotation in [`Radians`] that must be applied to the axis-aligned [`Rectangle`] + /// to obtain the desired result. + pub fn with_vertices( + top_left: Point, + top_right: Point, + bottom_left: Point, + ) -> (Rectangle, Radians) { + let width = (top_right.x - top_left.x).hypot(top_right.y - top_left.y); + + let height = + (bottom_left.x - top_left.x).hypot(bottom_left.y - top_left.y); + + let rotation = + (top_right.y - top_left.y).atan2(top_right.x - top_left.x); + + let rotation = if rotation < 0.0 { + 2.0 * std::f32::consts::PI + rotation + } else { + rotation + }; + + let position = { + let center = Point::new( + (top_right.x + bottom_left.x) / 2.0, + (top_right.y + bottom_left.y) / 2.0, + ); + + let rotation = -rotation - std::f32::consts::PI * 2.0; + + Point::new( + center.x + (top_left.x - center.x) * rotation.cos() + - (top_left.y - center.y) * rotation.sin(), + center.y + + (top_left.x - center.x) * rotation.sin() + + (top_left.y - center.y) * rotation.cos(), + ) + }; + + ( + Rectangle::new(position, Size::new(width, height)), + Radians(rotation), + ) + } + /// Returns the [`Point`] at the center of the [`Rectangle`]. pub fn center(&self) -> Point { Point::new(self.center_x(), self.center_y()) diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index e8d27d07..7e2d767b 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -20,6 +20,7 @@ all-features = true [features] geometry = ["lyon_path"] image = ["dep:image", "kamadak-exif"] +svg = [] web-colors = [] fira-sans = [] diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs index 377589d7..d53d1331 100644 --- a/graphics/src/geometry/frame.rs +++ b/graphics/src/geometry/frame.rs @@ -1,5 +1,7 @@ //! Draw and generate geometry. -use crate::core::{Point, Radians, Rectangle, Size, Vector}; +use crate::core::image; +use crate::core::svg; +use crate::core::{Color, Point, Radians, Rectangle, Size, Vector}; use crate::geometry::{self, Fill, Path, Stroke, Text}; /// The region of a surface that can be used to draw geometry. @@ -75,6 +77,25 @@ where self.raw.fill_text(text); } + /// Draws the given image on the [`Frame`] inside the given bounds. + #[cfg(feature = "image")] + pub fn draw_image( + &mut self, + handle: &image::Handle, + bounds: Rectangle, + filter_method: image::FilterMethod, + rotation: impl Into<Radians>, + opacity: f32, + ) { + self.raw.draw_image( + handle, + bounds, + filter_method, + rotation.into(), + opacity, + ); + } + /// Stores the current transform of the [`Frame`] and executes the given /// drawing operations, restoring the transform afterwards. /// @@ -116,8 +137,7 @@ where let mut frame = self.draft(region); let result = f(&mut frame); - - self.paste(frame, Point::new(region.x, region.y)); + self.paste(frame); result } @@ -134,8 +154,8 @@ where } /// Draws the contents of the given [`Frame`] with origin at the given [`Point`]. - fn paste(&mut self, frame: Self, at: Point) { - self.raw.paste(frame.raw, at); + fn paste(&mut self, frame: Self) { + self.raw.paste(frame.raw); } /// Applies a translation to the current transform of the [`Frame`]. @@ -186,7 +206,7 @@ pub trait Backend: Sized { fn scale_nonuniform(&mut self, scale: impl Into<Vector>); fn draft(&mut self, clip_bounds: Rectangle) -> Self; - fn paste(&mut self, frame: Self, at: Point); + fn paste(&mut self, frame: Self); fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>); @@ -199,6 +219,24 @@ pub trait Backend: Sized { fill: impl Into<Fill>, ); + fn draw_image( + &mut self, + handle: &image::Handle, + bounds: Rectangle, + filter_method: image::FilterMethod, + rotation: Radians, + opacity: f32, + ); + + fn draw_svg( + &mut self, + handle: &svg::Handle, + bounds: Rectangle, + color: Option<Color>, + rotation: Radians, + opacity: f32, + ); + fn into_geometry(self) -> Self::Geometry; } @@ -231,7 +269,7 @@ impl Backend for () { fn scale_nonuniform(&mut self, _scale: impl Into<Vector>) {} fn draft(&mut self, _clip_bounds: Rectangle) -> Self {} - fn paste(&mut self, _frame: Self, _at: Point) {} + fn paste(&mut self, _frame: Self) {} fn stroke<'a>(&mut self, _path: &Path, _stroke: impl Into<Stroke<'a>>) {} @@ -246,4 +284,24 @@ impl Backend for () { } fn into_geometry(self) -> Self::Geometry {} + + fn draw_image( + &mut self, + _handle: &image::Handle, + _bounds: Rectangle, + _filter_method: image::FilterMethod, + _rotation: Radians, + _opacity: f32, + ) { + } + + fn draw_svg( + &mut self, + _handle: &svg::Handle, + _bounds: Rectangle, + _color: Option<Color>, + _rotation: Radians, + _opacity: f32, + ) { + } } diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 318592be..0e8f2fe3 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -23,6 +23,12 @@ pub enum Image { /// The opacity of the image. opacity: f32, + + /// If set to `true`, the image will be snapped to the pixel grid. + /// + /// This can avoid graphical glitches, specially when using a + /// [`image::FilterMethod::Nearest`]. + snap: bool, }, /// A vector image. Vector { diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index 6a169692..ddf7fd95 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -572,6 +572,42 @@ mod geometry { delegate!(self, frame, frame.fill_text(text)); } + fn draw_image( + &mut self, + handle: &iced_wgpu::core::image::Handle, + bounds: Rectangle, + filter_method: iced_wgpu::core::image::FilterMethod, + rotation: Radians, + opacity: f32, + ) { + delegate!( + self, + frame, + frame.draw_image( + handle, + bounds, + filter_method, + rotation, + opacity + ) + ); + } + + fn draw_svg( + &mut self, + handle: &iced_wgpu::core::svg::Handle, + bounds: Rectangle, + color: Option<iced_wgpu::core::Color>, + rotation: Radians, + opacity: f32, + ) { + delegate!( + self, + frame, + frame.draw_svg(handle, bounds, color, rotation, opacity) + ); + } + fn push_transform(&mut self) { delegate!(self, frame, frame.push_transform()); } @@ -587,13 +623,13 @@ mod geometry { } } - fn paste(&mut self, frame: Self, at: Point) { + fn paste(&mut self, frame: Self) { match (self, frame) { (Self::Primary(target), Self::Primary(source)) => { - target.paste(source, at); + target.paste(source); } (Self::Secondary(target), Self::Secondary(source)) => { - target.paste(source, at); + target.paste(source); } _ => unreachable!(), } diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index 32ead3e0..323233f0 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -15,7 +15,7 @@ workspace = true [features] image = ["iced_graphics/image"] -svg = ["resvg"] +svg = ["iced_graphics/svg", "resvg"] geometry = ["iced_graphics/geometry"] [dependencies] diff --git a/tiny_skia/src/engine.rs b/tiny_skia/src/engine.rs index 898657c8..c5c4d494 100644 --- a/tiny_skia/src/engine.rs +++ b/tiny_skia/src/engine.rs @@ -556,6 +556,7 @@ impl Engine { bounds, rotation, opacity, + snap: _, } => { let physical_bounds = *bounds * _transformation; diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 02b6e1b9..398b54f7 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -1,10 +1,12 @@ +use crate::core::image; +use crate::core::svg; use crate::core::text::LineHeight; -use crate::core::{Pixels, Point, Radians, Rectangle, Size, Vector}; +use crate::core::{Color, Pixels, Point, Radians, Rectangle, Size, Vector}; use crate::graphics::cache::{self, Cached}; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::stroke::{self, Stroke}; use crate::graphics::geometry::{self, Path, Style}; -use crate::graphics::{Gradient, Text}; +use crate::graphics::{Gradient, Image, Text}; use crate::Primitive; use std::rc::Rc; @@ -13,6 +15,7 @@ use std::rc::Rc; pub enum Geometry { Live { text: Vec<Text>, + images: Vec<Image>, primitives: Vec<Primitive>, clip_bounds: Rectangle, }, @@ -22,6 +25,7 @@ pub enum Geometry { #[derive(Debug, Clone)] pub struct Cache { pub text: Rc<[Text]>, + pub images: Rc<[Image]>, pub primitives: Rc<[Primitive]>, pub clip_bounds: Rectangle, } @@ -37,10 +41,12 @@ impl Cached for Geometry { match self { Self::Live { primitives, + images, text, clip_bounds, } => Cache { primitives: Rc::from(primitives), + images: Rc::from(images), text: Rc::from(text), clip_bounds, }, @@ -55,6 +61,7 @@ pub struct Frame { transform: tiny_skia::Transform, stack: Vec<tiny_skia::Transform>, primitives: Vec<Primitive>, + images: Vec<Image>, text: Vec<Text>, } @@ -68,6 +75,7 @@ impl Frame { clip_bounds, stack: Vec::new(), primitives: Vec::new(), + images: Vec::new(), text: Vec::new(), transform: tiny_skia::Transform::from_translate( clip_bounds.x, @@ -238,7 +246,7 @@ impl geometry::frame::Backend for Frame { Self::with_clip(clip_bounds) } - fn paste(&mut self, frame: Self, _at: Point) { + fn paste(&mut self, frame: Self) { self.primitives.extend(frame.primitives); self.text.extend(frame.text); } @@ -269,10 +277,82 @@ impl geometry::frame::Backend for Frame { fn into_geometry(self) -> Geometry { Geometry::Live { primitives: self.primitives, + images: self.images, text: self.text, clip_bounds: self.clip_bounds, } } + + fn draw_image( + &mut self, + handle: &image::Handle, + bounds: Rectangle, + filter_method: image::FilterMethod, + rotation: Radians, + opacity: f32, + ) { + let (bounds, external_rotation) = + transform_rectangle(bounds, self.transform); + + self.images.push(Image::Raster { + handle: handle.clone(), + filter_method, + bounds, + rotation: rotation + external_rotation, + opacity, + snap: false, + }); + } + + fn draw_svg( + &mut self, + handle: &svg::Handle, + bounds: Rectangle, + color: Option<Color>, + rotation: Radians, + opacity: f32, + ) { + let (bounds, external_rotation) = + transform_rectangle(bounds, self.transform); + + self.images.push(Image::Vector { + handle: handle.clone(), + bounds, + color, + rotation: rotation + external_rotation, + opacity, + }); + } +} + +fn transform_rectangle( + rectangle: Rectangle, + transform: tiny_skia::Transform, +) -> (Rectangle, Radians) { + let mut top_left = tiny_skia::Point { + x: rectangle.x, + y: rectangle.y, + }; + + let mut top_right = tiny_skia::Point { + x: rectangle.x + rectangle.width, + y: rectangle.y, + }; + + let mut bottom_left = tiny_skia::Point { + x: rectangle.x, + y: rectangle.y + rectangle.height, + }; + + transform.map_point(&mut top_left); + transform.map_point(&mut top_right); + transform.map_point(&mut bottom_left); + + Rectangle::with_vertices( + Point::new(top_left.x, top_left.y), + Point::new(top_right.x, top_right.y), + Point::new(bottom_left.x, bottom_left.y), + ) } fn convert_path(path: &Path) -> Option<tiny_skia::Path> { diff --git a/tiny_skia/src/layer.rs b/tiny_skia/src/layer.rs index 48fca1d8..9a169f46 100644 --- a/tiny_skia/src/layer.rs +++ b/tiny_skia/src/layer.rs @@ -117,6 +117,48 @@ impl Layer { pub fn draw_image( &mut self, + image: &Image, + transformation: Transformation, + ) { + match image { + Image::Raster { + handle, + filter_method, + bounds, + rotation, + opacity, + snap: _, + } => { + self.draw_raster( + handle.clone(), + *filter_method, + *bounds, + transformation, + *rotation, + *opacity, + ); + } + Image::Vector { + handle, + color, + bounds, + rotation, + opacity, + } => { + self.draw_svg( + handle.clone(), + *color, + *bounds, + transformation, + *rotation, + *opacity, + ); + } + } + } + + pub fn draw_raster( + &mut self, handle: image::Handle, filter_method: image::FilterMethod, bounds: Rectangle, @@ -130,6 +172,7 @@ impl Layer { bounds: bounds * transformation, rotation, opacity, + snap: false, }; self.images.push(image); diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index 6ec60158..f09e5aa3 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -330,6 +330,7 @@ impl graphics::geometry::Renderer for Renderer { match geometry { Geometry::Live { primitives, + images, text, clip_bounds, } => { @@ -339,6 +340,10 @@ impl graphics::geometry::Renderer for Renderer { transformation, ); + for image in images { + layer.draw_image(&image, transformation); + } + layer.draw_text_group(text, clip_bounds, transformation); } Geometry::Cache(cache) => { @@ -348,6 +353,10 @@ impl graphics::geometry::Renderer for Renderer { transformation, ); + for image in cache.images.iter() { + layer.draw_image(image, transformation); + } + layer.draw_text_cache( cache.text, cache.clip_bounds, @@ -381,7 +390,7 @@ impl core::image::Renderer for Renderer { opacity: f32, ) { let (layer, transformation) = self.layers.current_mut(); - layer.draw_image( + layer.draw_raster( handle, filter_method, bounds, diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 30545fa2..b13ecb36 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -20,7 +20,7 @@ all-features = true [features] geometry = ["iced_graphics/geometry", "lyon"] image = ["iced_graphics/image"] -svg = ["resvg/text"] +svg = ["iced_graphics/svg", "resvg/text"] web-colors = ["iced_graphics/web-colors"] webgl = ["wgpu/webgl"] diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index f6213e1d..cb629b3e 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -1,7 +1,9 @@ //! Build and draw geometry. +use crate::core::image; +use crate::core::svg; use crate::core::text::LineHeight; use crate::core::{ - Pixels, Point, Radians, Rectangle, Size, Transformation, Vector, + Color, Pixels, Point, Radians, Rectangle, Size, Transformation, Vector, }; use crate::graphics::cache::{self, Cached}; use crate::graphics::color; @@ -11,7 +13,7 @@ use crate::graphics::geometry::{ }; use crate::graphics::gradient::{self, Gradient}; use crate::graphics::mesh::{self, Mesh}; -use crate::graphics::{self, Text}; +use crate::graphics::{self, Image, Text}; use crate::text; use crate::triangle; @@ -19,16 +21,22 @@ use lyon::geom::euclid; use lyon::tessellation; use std::borrow::Cow; +use std::sync::Arc; #[derive(Debug)] pub enum Geometry { - Live { meshes: Vec<Mesh>, text: Vec<Text> }, + Live { + meshes: Vec<Mesh>, + images: Vec<Image>, + text: Vec<Text>, + }, Cached(Cache), } #[derive(Debug, Clone)] pub struct Cache { pub meshes: Option<triangle::Cache>, + pub images: Option<Arc<[Image]>>, pub text: Option<text::Cache>, } @@ -45,7 +53,17 @@ impl Cached for Geometry { previous: Option<Self::Cache>, ) -> Self::Cache { match self { - Self::Live { meshes, text } => { + Self::Live { + meshes, + images, + text, + } => { + let images = if images.is_empty() { + None + } else { + Some(Arc::from(images)) + }; + if let Some(mut previous) = previous { if let Some(cache) = &mut previous.meshes { cache.update(meshes); @@ -59,10 +77,13 @@ impl Cached for Geometry { previous.text = text::Cache::new(group, text); } + previous.images = images; + previous } else { Cache { meshes: triangle::Cache::new(meshes), + images, text: text::Cache::new(group, text), } } @@ -78,6 +99,7 @@ pub struct Frame { clip_bounds: Rectangle, buffers: BufferStack, meshes: Vec<Mesh>, + images: Vec<Image>, text: Vec<Text>, transforms: Transforms, fill_tessellator: tessellation::FillTessellator, @@ -96,6 +118,7 @@ impl Frame { clip_bounds: bounds, buffers: BufferStack::new(), meshes: Vec::new(), + images: Vec::new(), text: Vec::new(), transforms: Transforms { previous: Vec::new(), @@ -335,10 +358,11 @@ impl geometry::frame::Backend for Frame { Frame::with_clip(clip_bounds) } - fn paste(&mut self, frame: Frame, _at: Point) { + fn paste(&mut self, frame: Frame) { self.meshes .extend(frame.buffers.into_meshes(frame.clip_bounds)); + self.images.extend(frame.images); self.text.extend(frame.text); } @@ -348,9 +372,51 @@ impl geometry::frame::Backend for Frame { Geometry::Live { meshes: self.meshes, + images: self.images, text: self.text, } } + + fn draw_image( + &mut self, + handle: &image::Handle, + bounds: Rectangle, + filter_method: image::FilterMethod, + rotation: Radians, + opacity: f32, + ) { + let (bounds, external_rotation) = + self.transforms.current.transform_rectangle(bounds); + + self.images.push(Image::Raster { + handle: handle.clone(), + filter_method, + bounds, + rotation: rotation + external_rotation, + opacity, + snap: false, + }); + } + + fn draw_svg( + &mut self, + handle: &svg::Handle, + bounds: Rectangle, + color: Option<Color>, + rotation: Radians, + opacity: f32, + ) { + let (bounds, external_rotation) = + self.transforms.current.transform_rectangle(bounds); + + self.images.push(Image::Vector { + handle: handle.clone(), + color, + bounds, + rotation: rotation + external_rotation, + opacity, + }); + } } enum Buffer { @@ -518,6 +584,21 @@ impl Transform { gradient } + + fn transform_rectangle( + &self, + rectangle: Rectangle, + ) -> (Rectangle, Radians) { + let top_left = self.transform_point(rectangle.position()); + let top_right = self.transform_point( + rectangle.position() + Vector::new(rectangle.width, 0.0), + ); + let bottom_left = self.transform_point( + rectangle.position() + Vector::new(0.0, rectangle.height), + ); + + Rectangle::with_vertices(top_left, top_right, bottom_left) + } } struct GradientVertex2DBuilder { gradient: gradient::Packed, diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs index daa2fe16..ea34e4ec 100644 --- a/wgpu/src/image/mod.rs +++ b/wgpu/src/image/mod.rs @@ -149,6 +149,8 @@ impl Pipeline { 6 => Float32x2, // Layer 7 => Sint32, + // Snap + 8 => Uint32, ), }], }, @@ -212,8 +214,6 @@ impl Pipeline { transformation: Transformation, scale: f32, ) { - let transformation = transformation * Transformation::scale(scale); - let nearest_instances: &mut Vec<Instance> = &mut Vec::new(); let linear_instances: &mut Vec<Instance> = &mut Vec::new(); @@ -226,6 +226,7 @@ impl Pipeline { bounds, rotation, opacity, + snap, } => { if let Some(atlas_entry) = cache.upload_raster(device, encoder, handle) @@ -235,6 +236,7 @@ impl Pipeline { [bounds.width, bounds.height], f32::from(*rotation), *opacity, + *snap, atlas_entry, match filter_method { crate::core::image::FilterMethod::Nearest => { @@ -268,6 +270,7 @@ impl Pipeline { size, f32::from(*rotation), *opacity, + true, atlas_entry, nearest_instances, ); @@ -300,6 +303,7 @@ impl Pipeline { nearest_instances, linear_instances, transformation, + scale, ); self.prepare_layer += 1; @@ -375,9 +379,12 @@ impl Layer { nearest_instances: &[Instance], linear_instances: &[Instance], transformation: Transformation, + scale_factor: f32, ) { let uniforms = Uniforms { transform: transformation.into(), + scale_factor, + _padding: [0.0; 3], }; let bytes = bytemuck::bytes_of(&uniforms); @@ -492,6 +499,7 @@ struct Instance { _position_in_atlas: [f32; 2], _size_in_atlas: [f32; 2], _layer: u32, + _snap: u32, } impl Instance { @@ -502,6 +510,10 @@ impl Instance { #[derive(Debug, Clone, Copy, Zeroable, Pod)] struct Uniforms { transform: [f32; 16], + scale_factor: f32, + // Uniforms must be aligned to their largest member, + // this uses a mat4x4<f32> which aligns to 16, so align to that + _padding: [f32; 3], } fn add_instances( @@ -509,6 +521,7 @@ fn add_instances( image_size: [f32; 2], rotation: f32, opacity: f32, + snap: bool, entry: &atlas::Entry, instances: &mut Vec<Instance>, ) { @@ -525,6 +538,7 @@ fn add_instances( image_size, rotation, opacity, + snap, allocation, instances, ); @@ -554,8 +568,8 @@ fn add_instances( ]; add_instance( - position, center, size, rotation, opacity, allocation, - instances, + position, center, size, rotation, opacity, snap, + allocation, instances, ); } } @@ -569,6 +583,7 @@ fn add_instance( size: [f32; 2], rotation: f32, opacity: f32, + snap: bool, allocation: &atlas::Allocation, instances: &mut Vec<Instance>, ) { @@ -591,6 +606,7 @@ fn add_instance( (height as f32 - 1.0) / atlas::SIZE as f32, ], _layer: layer as u32, + _snap: snap as u32, }; instances.push(instance); diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index df289e0e..e714e281 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -114,12 +114,56 @@ impl Layer { pub fn draw_image( &mut self, + image: &Image, + transformation: Transformation, + ) { + match image { + Image::Raster { + handle, + filter_method, + bounds, + rotation, + opacity, + snap, + } => { + self.draw_raster( + handle.clone(), + *filter_method, + *bounds, + transformation, + *rotation, + *opacity, + *snap, + ); + } + Image::Vector { + handle, + color, + bounds, + rotation, + opacity, + } => { + self.draw_svg( + handle.clone(), + *color, + *bounds, + transformation, + *rotation, + *opacity, + ); + } + } + } + + pub fn draw_raster( + &mut self, handle: crate::core::image::Handle, filter_method: crate::core::image::FilterMethod, bounds: Rectangle, transformation: Transformation, rotation: Radians, opacity: f32, + snap: bool, ) { let image = Image::Raster { handle, @@ -127,6 +171,7 @@ impl Layer { bounds: bounds * transformation, rotation, opacity, + snap, }; self.images.push(image); diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 954340ec..24e60979 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -536,13 +536,14 @@ impl core::image::Renderer for Renderer { opacity: f32, ) { let (layer, transformation) = self.layers.current_mut(); - layer.draw_image( + layer.draw_raster( handle, filter_method, bounds, transformation, rotation, opacity, + true, ); } } @@ -593,8 +594,17 @@ impl graphics::geometry::Renderer for Renderer { let (layer, transformation) = self.layers.current_mut(); match geometry { - Geometry::Live { meshes, text } => { + Geometry::Live { + meshes, + images, + text, + } => { layer.draw_mesh_group(meshes, transformation); + + for image in images { + layer.draw_image(&image, transformation); + } + layer.draw_text_group(text, transformation); } Geometry::Cached(cache) => { @@ -602,6 +612,12 @@ impl graphics::geometry::Renderer for Renderer { layer.draw_mesh_cache(meshes, transformation); } + if let Some(images) = cache.images { + for image in images.iter() { + layer.draw_image(image, transformation); + } + } + if let Some(text) = cache.text { layer.draw_text_cache(text, transformation); } diff --git a/wgpu/src/shader/image.wgsl b/wgpu/src/shader/image.wgsl index 0eeb100f..bc922838 100644 --- a/wgpu/src/shader/image.wgsl +++ b/wgpu/src/shader/image.wgsl @@ -1,5 +1,6 @@ struct Globals { transform: mat4x4<f32>, + scale_factor: f32, } @group(0) @binding(0) var<uniform> globals: Globals; @@ -16,6 +17,7 @@ struct VertexInput { @location(5) atlas_pos: vec2<f32>, @location(6) atlas_scale: vec2<f32>, @location(7) layer: i32, + @location(8) snap: u32, } struct VertexOutput { @@ -38,7 +40,7 @@ fn vs_main(input: VertexInput) -> VertexOutput { out.opacity = input.opacity; // Calculate the vertex position and move the center to the origin - v_pos = round(input.pos) + v_pos * input.scale - input.center; + v_pos = input.pos + v_pos * input.scale - input.center; // Apply the rotation around the center of the image let cos_rot = cos(input.rotation); @@ -51,7 +53,13 @@ fn vs_main(input: VertexInput) -> VertexOutput { ); // Calculate the final position of the vertex - out.position = globals.transform * (vec4<f32>(input.center, 0.0, 0.0) + rotate * vec4<f32>(v_pos, 0.0, 1.0)); + out.position = vec4(vec2(globals.scale_factor), 1.0, 1.0) * (vec4<f32>(input.center, 0.0, 0.0) + rotate * vec4<f32>(v_pos, 0.0, 1.0)); + + if bool(input.snap) { + out.position = round(out.position); + } + + out.position = globals.transform * out.position; return out; } |