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

@ -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,

View file

@ -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);

View file

@ -113,6 +113,49 @@ 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,
@ -120,6 +163,7 @@ impl Layer {
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);

View file

@ -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);
}

View file

@ -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;
}