Implement image support for canvas widget

This commit is contained in:
Héctor Ramón Jiménez 2024-08-04 03:28:43 +02:00
parent 87a613edd1
commit 0ceee1cf3a
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
16 changed files with 485 additions and 29 deletions

View file

@ -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`]. /// Returns the [`Point`] at the center of the [`Rectangle`].
pub fn center(&self) -> Point { pub fn center(&self) -> Point {
Point::new(self.center_x(), self.center_y()) Point::new(self.center_x(), self.center_y())

View file

@ -20,6 +20,7 @@ all-features = true
[features] [features]
geometry = ["lyon_path"] geometry = ["lyon_path"]
image = ["dep:image", "kamadak-exif"] image = ["dep:image", "kamadak-exif"]
svg = []
web-colors = [] web-colors = []
fira-sans = [] fira-sans = []

View file

@ -1,5 +1,7 @@
//! Draw and generate geometry. //! 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}; use crate::geometry::{self, Fill, Path, Stroke, Text};
/// The region of a surface that can be used to draw geometry. /// The region of a surface that can be used to draw geometry.
@ -75,6 +77,25 @@ where
self.raw.fill_text(text); 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 /// Stores the current transform of the [`Frame`] and executes the given
/// drawing operations, restoring the transform afterwards. /// drawing operations, restoring the transform afterwards.
/// ///
@ -116,8 +137,7 @@ where
let mut frame = self.draft(region); let mut frame = self.draft(region);
let result = f(&mut frame); let result = f(&mut frame);
self.paste(frame);
self.paste(frame, Point::new(region.x, region.y));
result result
} }
@ -134,8 +154,8 @@ where
} }
/// Draws the contents of the given [`Frame`] with origin at the given [`Point`]. /// Draws the contents of the given [`Frame`] with origin at the given [`Point`].
fn paste(&mut self, frame: Self, at: Point) { fn paste(&mut self, frame: Self) {
self.raw.paste(frame.raw, at); self.raw.paste(frame.raw);
} }
/// Applies a translation to the current transform of the [`Frame`]. /// 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 scale_nonuniform(&mut self, scale: impl Into<Vector>);
fn draft(&mut self, clip_bounds: Rectangle) -> Self; 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>>); fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>);
@ -199,6 +219,24 @@ pub trait Backend: Sized {
fill: impl Into<Fill>, 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; fn into_geometry(self) -> Self::Geometry;
} }
@ -231,7 +269,7 @@ impl Backend for () {
fn scale_nonuniform(&mut self, _scale: impl Into<Vector>) {} fn scale_nonuniform(&mut self, _scale: impl Into<Vector>) {}
fn draft(&mut self, _clip_bounds: Rectangle) -> Self {} 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>>) {} 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 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,
) {
}
} }

View file

@ -23,6 +23,12 @@ pub enum Image {
/// The opacity of the image. /// The opacity of the image.
opacity: f32, 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. /// A vector image.
Vector { Vector {

View file

@ -572,6 +572,42 @@ mod geometry {
delegate!(self, frame, frame.fill_text(text)); 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) { fn push_transform(&mut self) {
delegate!(self, frame, frame.push_transform()); 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) { match (self, frame) {
(Self::Primary(target), Self::Primary(source)) => { (Self::Primary(target), Self::Primary(source)) => {
target.paste(source, at); target.paste(source);
} }
(Self::Secondary(target), Self::Secondary(source)) => { (Self::Secondary(target), Self::Secondary(source)) => {
target.paste(source, at); target.paste(source);
} }
_ => unreachable!(), _ => unreachable!(),
} }

View file

@ -15,7 +15,7 @@ workspace = true
[features] [features]
image = ["iced_graphics/image"] image = ["iced_graphics/image"]
svg = ["resvg"] svg = ["iced_graphics/svg", "resvg"]
geometry = ["iced_graphics/geometry"] geometry = ["iced_graphics/geometry"]
[dependencies] [dependencies]

View file

@ -556,6 +556,7 @@ impl Engine {
bounds, bounds,
rotation, rotation,
opacity, opacity,
snap: _,
} => { } => {
let physical_bounds = *bounds * _transformation; let physical_bounds = *bounds * _transformation;

View file

@ -1,10 +1,12 @@
use crate::core::image;
use crate::core::svg;
use crate::core::text::LineHeight; 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::cache::{self, Cached};
use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::stroke::{self, Stroke}; use crate::graphics::geometry::stroke::{self, Stroke};
use crate::graphics::geometry::{self, Path, Style}; use crate::graphics::geometry::{self, Path, Style};
use crate::graphics::{Gradient, Text}; use crate::graphics::{Gradient, Image, Text};
use crate::Primitive; use crate::Primitive;
use std::rc::Rc; use std::rc::Rc;
@ -13,6 +15,7 @@ use std::rc::Rc;
pub enum Geometry { pub enum Geometry {
Live { Live {
text: Vec<Text>, text: Vec<Text>,
images: Vec<Image>,
primitives: Vec<Primitive>, primitives: Vec<Primitive>,
clip_bounds: Rectangle, clip_bounds: Rectangle,
}, },
@ -22,6 +25,7 @@ pub enum Geometry {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Cache { pub struct Cache {
pub text: Rc<[Text]>, pub text: Rc<[Text]>,
pub images: Rc<[Image]>,
pub primitives: Rc<[Primitive]>, pub primitives: Rc<[Primitive]>,
pub clip_bounds: Rectangle, pub clip_bounds: Rectangle,
} }
@ -37,10 +41,12 @@ impl Cached for Geometry {
match self { match self {
Self::Live { Self::Live {
primitives, primitives,
images,
text, text,
clip_bounds, clip_bounds,
} => Cache { } => Cache {
primitives: Rc::from(primitives), primitives: Rc::from(primitives),
images: Rc::from(images),
text: Rc::from(text), text: Rc::from(text),
clip_bounds, clip_bounds,
}, },
@ -55,6 +61,7 @@ pub struct Frame {
transform: tiny_skia::Transform, transform: tiny_skia::Transform,
stack: Vec<tiny_skia::Transform>, stack: Vec<tiny_skia::Transform>,
primitives: Vec<Primitive>, primitives: Vec<Primitive>,
images: Vec<Image>,
text: Vec<Text>, text: Vec<Text>,
} }
@ -68,6 +75,7 @@ impl Frame {
clip_bounds, clip_bounds,
stack: Vec::new(), stack: Vec::new(),
primitives: Vec::new(), primitives: Vec::new(),
images: Vec::new(),
text: Vec::new(), text: Vec::new(),
transform: tiny_skia::Transform::from_translate( transform: tiny_skia::Transform::from_translate(
clip_bounds.x, clip_bounds.x,
@ -238,7 +246,7 @@ impl geometry::frame::Backend for Frame {
Self::with_clip(clip_bounds) 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.primitives.extend(frame.primitives);
self.text.extend(frame.text); self.text.extend(frame.text);
} }
@ -269,10 +277,82 @@ impl geometry::frame::Backend for Frame {
fn into_geometry(self) -> Geometry { fn into_geometry(self) -> Geometry {
Geometry::Live { Geometry::Live {
primitives: self.primitives, primitives: self.primitives,
images: self.images,
text: self.text, text: self.text,
clip_bounds: self.clip_bounds, 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> { fn convert_path(path: &Path) -> Option<tiny_skia::Path> {

View file

@ -116,6 +116,48 @@ impl Layer {
} }
pub fn draw_image( 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, &mut self,
handle: image::Handle, handle: image::Handle,
filter_method: image::FilterMethod, filter_method: image::FilterMethod,
@ -130,6 +172,7 @@ impl Layer {
bounds: bounds * transformation, bounds: bounds * transformation,
rotation, rotation,
opacity, opacity,
snap: false,
}; };
self.images.push(image); self.images.push(image);

View file

@ -330,6 +330,7 @@ impl graphics::geometry::Renderer for Renderer {
match geometry { match geometry {
Geometry::Live { Geometry::Live {
primitives, primitives,
images,
text, text,
clip_bounds, clip_bounds,
} => { } => {
@ -339,6 +340,10 @@ impl graphics::geometry::Renderer for Renderer {
transformation, transformation,
); );
for image in images {
layer.draw_image(&image, transformation);
}
layer.draw_text_group(text, clip_bounds, transformation); layer.draw_text_group(text, clip_bounds, transformation);
} }
Geometry::Cache(cache) => { Geometry::Cache(cache) => {
@ -348,6 +353,10 @@ impl graphics::geometry::Renderer for Renderer {
transformation, transformation,
); );
for image in cache.images.iter() {
layer.draw_image(image, transformation);
}
layer.draw_text_cache( layer.draw_text_cache(
cache.text, cache.text,
cache.clip_bounds, cache.clip_bounds,
@ -381,7 +390,7 @@ impl core::image::Renderer for Renderer {
opacity: f32, opacity: f32,
) { ) {
let (layer, transformation) = self.layers.current_mut(); let (layer, transformation) = self.layers.current_mut();
layer.draw_image( layer.draw_raster(
handle, handle,
filter_method, filter_method,
bounds, bounds,

View file

@ -20,7 +20,7 @@ all-features = true
[features] [features]
geometry = ["iced_graphics/geometry", "lyon"] geometry = ["iced_graphics/geometry", "lyon"]
image = ["iced_graphics/image"] image = ["iced_graphics/image"]
svg = ["resvg/text"] svg = ["iced_graphics/svg", "resvg/text"]
web-colors = ["iced_graphics/web-colors"] web-colors = ["iced_graphics/web-colors"]
webgl = ["wgpu/webgl"] webgl = ["wgpu/webgl"]

View file

@ -1,7 +1,9 @@
//! Build and draw geometry. //! Build and draw geometry.
use crate::core::image;
use crate::core::svg;
use crate::core::text::LineHeight; use crate::core::text::LineHeight;
use crate::core::{ 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::cache::{self, Cached};
use crate::graphics::color; use crate::graphics::color;
@ -11,7 +13,7 @@ use crate::graphics::geometry::{
}; };
use crate::graphics::gradient::{self, Gradient}; use crate::graphics::gradient::{self, Gradient};
use crate::graphics::mesh::{self, Mesh}; use crate::graphics::mesh::{self, Mesh};
use crate::graphics::{self, Text}; use crate::graphics::{self, Image, Text};
use crate::text; use crate::text;
use crate::triangle; use crate::triangle;
@ -19,16 +21,22 @@ use lyon::geom::euclid;
use lyon::tessellation; use lyon::tessellation;
use std::borrow::Cow; use std::borrow::Cow;
use std::sync::Arc;
#[derive(Debug)] #[derive(Debug)]
pub enum Geometry { pub enum Geometry {
Live { meshes: Vec<Mesh>, text: Vec<Text> }, Live {
meshes: Vec<Mesh>,
images: Vec<Image>,
text: Vec<Text>,
},
Cached(Cache), Cached(Cache),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Cache { pub struct Cache {
pub meshes: Option<triangle::Cache>, pub meshes: Option<triangle::Cache>,
pub images: Option<Arc<[Image]>>,
pub text: Option<text::Cache>, pub text: Option<text::Cache>,
} }
@ -45,7 +53,17 @@ impl Cached for Geometry {
previous: Option<Self::Cache>, previous: Option<Self::Cache>,
) -> Self::Cache { ) -> Self::Cache {
match self { 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(mut previous) = previous {
if let Some(cache) = &mut previous.meshes { if let Some(cache) = &mut previous.meshes {
cache.update(meshes); cache.update(meshes);
@ -59,10 +77,13 @@ impl Cached for Geometry {
previous.text = text::Cache::new(group, text); previous.text = text::Cache::new(group, text);
} }
previous.images = images;
previous previous
} else { } else {
Cache { Cache {
meshes: triangle::Cache::new(meshes), meshes: triangle::Cache::new(meshes),
images,
text: text::Cache::new(group, text), text: text::Cache::new(group, text),
} }
} }
@ -78,6 +99,7 @@ pub struct Frame {
clip_bounds: Rectangle, clip_bounds: Rectangle,
buffers: BufferStack, buffers: BufferStack,
meshes: Vec<Mesh>, meshes: Vec<Mesh>,
images: Vec<Image>,
text: Vec<Text>, text: Vec<Text>,
transforms: Transforms, transforms: Transforms,
fill_tessellator: tessellation::FillTessellator, fill_tessellator: tessellation::FillTessellator,
@ -96,6 +118,7 @@ impl Frame {
clip_bounds: bounds, clip_bounds: bounds,
buffers: BufferStack::new(), buffers: BufferStack::new(),
meshes: Vec::new(), meshes: Vec::new(),
images: Vec::new(),
text: Vec::new(), text: Vec::new(),
transforms: Transforms { transforms: Transforms {
previous: Vec::new(), previous: Vec::new(),
@ -335,10 +358,11 @@ impl geometry::frame::Backend for Frame {
Frame::with_clip(clip_bounds) Frame::with_clip(clip_bounds)
} }
fn paste(&mut self, frame: Frame, _at: Point) { fn paste(&mut self, frame: Frame) {
self.meshes self.meshes
.extend(frame.buffers.into_meshes(frame.clip_bounds)); .extend(frame.buffers.into_meshes(frame.clip_bounds));
self.images.extend(frame.images);
self.text.extend(frame.text); self.text.extend(frame.text);
} }
@ -348,9 +372,51 @@ impl geometry::frame::Backend for Frame {
Geometry::Live { Geometry::Live {
meshes: self.meshes, meshes: self.meshes,
images: self.images,
text: self.text, 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 { enum Buffer {
@ -518,6 +584,21 @@ impl Transform {
gradient 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 { struct GradientVertex2DBuilder {
gradient: gradient::Packed, gradient: gradient::Packed,

View file

@ -149,6 +149,8 @@ impl Pipeline {
6 => Float32x2, 6 => Float32x2,
// Layer // Layer
7 => Sint32, 7 => Sint32,
// Snap
8 => Uint32,
), ),
}], }],
}, },
@ -212,8 +214,6 @@ impl Pipeline {
transformation: Transformation, transformation: Transformation,
scale: f32, scale: f32,
) { ) {
let transformation = transformation * Transformation::scale(scale);
let nearest_instances: &mut Vec<Instance> = &mut Vec::new(); let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
let linear_instances: &mut Vec<Instance> = &mut Vec::new(); let linear_instances: &mut Vec<Instance> = &mut Vec::new();
@ -226,6 +226,7 @@ impl Pipeline {
bounds, bounds,
rotation, rotation,
opacity, opacity,
snap,
} => { } => {
if let Some(atlas_entry) = if let Some(atlas_entry) =
cache.upload_raster(device, encoder, handle) cache.upload_raster(device, encoder, handle)
@ -235,6 +236,7 @@ impl Pipeline {
[bounds.width, bounds.height], [bounds.width, bounds.height],
f32::from(*rotation), f32::from(*rotation),
*opacity, *opacity,
*snap,
atlas_entry, atlas_entry,
match filter_method { match filter_method {
crate::core::image::FilterMethod::Nearest => { crate::core::image::FilterMethod::Nearest => {
@ -268,6 +270,7 @@ impl Pipeline {
size, size,
f32::from(*rotation), f32::from(*rotation),
*opacity, *opacity,
true,
atlas_entry, atlas_entry,
nearest_instances, nearest_instances,
); );
@ -300,6 +303,7 @@ impl Pipeline {
nearest_instances, nearest_instances,
linear_instances, linear_instances,
transformation, transformation,
scale,
); );
self.prepare_layer += 1; self.prepare_layer += 1;
@ -375,9 +379,12 @@ impl Layer {
nearest_instances: &[Instance], nearest_instances: &[Instance],
linear_instances: &[Instance], linear_instances: &[Instance],
transformation: Transformation, transformation: Transformation,
scale_factor: f32,
) { ) {
let uniforms = Uniforms { let uniforms = Uniforms {
transform: transformation.into(), transform: transformation.into(),
scale_factor,
_padding: [0.0; 3],
}; };
let bytes = bytemuck::bytes_of(&uniforms); let bytes = bytemuck::bytes_of(&uniforms);
@ -492,6 +499,7 @@ struct Instance {
_position_in_atlas: [f32; 2], _position_in_atlas: [f32; 2],
_size_in_atlas: [f32; 2], _size_in_atlas: [f32; 2],
_layer: u32, _layer: u32,
_snap: u32,
} }
impl Instance { impl Instance {
@ -502,6 +510,10 @@ impl Instance {
#[derive(Debug, Clone, Copy, Zeroable, Pod)] #[derive(Debug, Clone, Copy, Zeroable, Pod)]
struct Uniforms { struct Uniforms {
transform: [f32; 16], 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( fn add_instances(
@ -509,6 +521,7 @@ fn add_instances(
image_size: [f32; 2], image_size: [f32; 2],
rotation: f32, rotation: f32,
opacity: f32, opacity: f32,
snap: bool,
entry: &atlas::Entry, entry: &atlas::Entry,
instances: &mut Vec<Instance>, instances: &mut Vec<Instance>,
) { ) {
@ -525,6 +538,7 @@ fn add_instances(
image_size, image_size,
rotation, rotation,
opacity, opacity,
snap,
allocation, allocation,
instances, instances,
); );
@ -554,8 +568,8 @@ fn add_instances(
]; ];
add_instance( add_instance(
position, center, size, rotation, opacity, allocation, position, center, size, rotation, opacity, snap,
instances, allocation, instances,
); );
} }
} }
@ -569,6 +583,7 @@ fn add_instance(
size: [f32; 2], size: [f32; 2],
rotation: f32, rotation: f32,
opacity: f32, opacity: f32,
snap: bool,
allocation: &atlas::Allocation, allocation: &atlas::Allocation,
instances: &mut Vec<Instance>, instances: &mut Vec<Instance>,
) { ) {
@ -591,6 +606,7 @@ fn add_instance(
(height as f32 - 1.0) / atlas::SIZE as f32, (height as f32 - 1.0) / atlas::SIZE as f32,
], ],
_layer: layer as u32, _layer: layer as u32,
_snap: snap as u32,
}; };
instances.push(instance); instances.push(instance);

View file

@ -113,6 +113,49 @@ impl Layer {
} }
pub fn draw_image( 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, &mut self,
handle: crate::core::image::Handle, handle: crate::core::image::Handle,
filter_method: crate::core::image::FilterMethod, filter_method: crate::core::image::FilterMethod,
@ -120,6 +163,7 @@ impl Layer {
transformation: Transformation, transformation: Transformation,
rotation: Radians, rotation: Radians,
opacity: f32, opacity: f32,
snap: bool,
) { ) {
let image = Image::Raster { let image = Image::Raster {
handle, handle,
@ -127,6 +171,7 @@ impl Layer {
bounds: bounds * transformation, bounds: bounds * transformation,
rotation, rotation,
opacity, opacity,
snap,
}; };
self.images.push(image); self.images.push(image);

View file

@ -536,13 +536,14 @@ impl core::image::Renderer for Renderer {
opacity: f32, opacity: f32,
) { ) {
let (layer, transformation) = self.layers.current_mut(); let (layer, transformation) = self.layers.current_mut();
layer.draw_image( layer.draw_raster(
handle, handle,
filter_method, filter_method,
bounds, bounds,
transformation, transformation,
rotation, rotation,
opacity, opacity,
true,
); );
} }
} }
@ -593,8 +594,17 @@ impl graphics::geometry::Renderer for Renderer {
let (layer, transformation) = self.layers.current_mut(); let (layer, transformation) = self.layers.current_mut();
match geometry { match geometry {
Geometry::Live { meshes, text } => { Geometry::Live {
meshes,
images,
text,
} => {
layer.draw_mesh_group(meshes, transformation); layer.draw_mesh_group(meshes, transformation);
for image in images {
layer.draw_image(&image, transformation);
}
layer.draw_text_group(text, transformation); layer.draw_text_group(text, transformation);
} }
Geometry::Cached(cache) => { Geometry::Cached(cache) => {
@ -602,6 +612,12 @@ impl graphics::geometry::Renderer for Renderer {
layer.draw_mesh_cache(meshes, transformation); 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 { if let Some(text) = cache.text {
layer.draw_text_cache(text, transformation); layer.draw_text_cache(text, transformation);
} }

View file

@ -1,5 +1,6 @@
struct Globals { struct Globals {
transform: mat4x4<f32>, transform: mat4x4<f32>,
scale_factor: f32,
} }
@group(0) @binding(0) var<uniform> globals: Globals; @group(0) @binding(0) var<uniform> globals: Globals;
@ -16,6 +17,7 @@ struct VertexInput {
@location(5) atlas_pos: vec2<f32>, @location(5) atlas_pos: vec2<f32>,
@location(6) atlas_scale: vec2<f32>, @location(6) atlas_scale: vec2<f32>,
@location(7) layer: i32, @location(7) layer: i32,
@location(8) snap: u32,
} }
struct VertexOutput { struct VertexOutput {
@ -38,7 +40,7 @@ fn vs_main(input: VertexInput) -> VertexOutput {
out.opacity = input.opacity; out.opacity = input.opacity;
// Calculate the vertex position and move the center to the origin // 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 // Apply the rotation around the center of the image
let cos_rot = cos(input.rotation); let cos_rot = cos(input.rotation);
@ -51,7 +53,13 @@ fn vs_main(input: VertexInput) -> VertexOutput {
); );
// Calculate the final position of the vertex // 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; return out;
} }