summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-08-04 03:28:43 +0200
committerLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-08-04 03:28:43 +0200
commit0ceee1cf3ae49f5bd0e3f2b346a4b34076e4523a (patch)
treec44e036220ea40734a00bb8e05e4afa6a9504bea
parent87a613edd186461f1a8d224394043527a372571c (diff)
downloadiced-0ceee1cf3ae49f5bd0e3f2b346a4b34076e4523a.tar.gz
iced-0ceee1cf3ae49f5bd0e3f2b346a4b34076e4523a.tar.bz2
iced-0ceee1cf3ae49f5bd0e3f2b346a4b34076e4523a.zip
Implement image support for `canvas` widget
-rw-r--r--core/src/rectangle.rs56
-rw-r--r--graphics/Cargo.toml1
-rw-r--r--graphics/src/geometry/frame.rs72
-rw-r--r--graphics/src/image.rs6
-rw-r--r--renderer/src/fallback.rs42
-rw-r--r--tiny_skia/Cargo.toml2
-rw-r--r--tiny_skia/src/engine.rs1
-rw-r--r--tiny_skia/src/geometry.rs86
-rw-r--r--tiny_skia/src/layer.rs43
-rw-r--r--tiny_skia/src/lib.rs11
-rw-r--r--wgpu/Cargo.toml2
-rw-r--r--wgpu/src/geometry.rs91
-rw-r--r--wgpu/src/image/mod.rs24
-rw-r--r--wgpu/src/layer.rs45
-rw-r--r--wgpu/src/lib.rs20
-rw-r--r--wgpu/src/shader/image.wgsl12
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;
}