Merge branch 'master' into beacon

This commit is contained in:
Héctor Ramón Jiménez 2025-03-04 19:11:37 +01:00
commit 8bd5de72ea
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
371 changed files with 33138 additions and 12950 deletions

View file

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

View file

@ -1,10 +1,10 @@
use crate::Primitive;
use crate::core::renderer::Quad;
use crate::core::{
Background, Color, Gradient, Rectangle, Size, Transformation, Vector,
};
use crate::graphics::{Image, Text};
use crate::text;
use crate::Primitive;
#[derive(Debug)]
pub struct Engine {
@ -439,9 +439,13 @@ impl Engine {
let transformation = transformation * *local_transformation;
let (width, height) = buffer.size();
let physical_bounds =
Rectangle::new(raw.position, Size::new(width, height))
* transformation;
let physical_bounds = Rectangle::new(
raw.position,
Size::new(
width.unwrap_or(clip_bounds.width),
height.unwrap_or(clip_bounds.height),
),
) * transformation;
if !clip_bounds.intersects(&physical_bounds) {
return;
@ -546,13 +550,7 @@ impl Engine {
) {
match image {
#[cfg(feature = "image")]
Image::Raster {
handle,
filter_method,
bounds,
rotation,
opacity,
} => {
Image::Raster(raster, bounds) => {
let physical_bounds = *bounds * _transformation;
if !_clip_bounds.intersects(&physical_bounds) {
@ -563,7 +561,7 @@ impl Engine {
.then_some(_clip_mask as &_);
let center = physical_bounds.center();
let radians = f32::from(*rotation);
let radians = f32::from(raster.rotation);
let transform = into_transform(_transformation).post_rotate_at(
radians.to_degrees(),
@ -572,23 +570,17 @@ impl Engine {
);
self.raster_pipeline.draw(
handle,
*filter_method,
&raster.handle,
raster.filter_method,
*bounds,
*opacity,
raster.opacity,
_pixels,
transform,
clip_mask,
);
}
#[cfg(feature = "svg")]
Image::Vector {
handle,
color,
bounds,
rotation,
opacity,
} => {
Image::Vector(svg, bounds) => {
let physical_bounds = *bounds * _transformation;
if !_clip_bounds.intersects(&physical_bounds) {
@ -599,7 +591,7 @@ impl Engine {
.then_some(_clip_mask as &_);
let center = physical_bounds.center();
let radians = f32::from(*rotation);
let radians = f32::from(svg.rotation);
let transform = into_transform(_transformation).post_rotate_at(
radians.to_degrees(),
@ -608,10 +600,10 @@ impl Engine {
);
self.vector_pipeline.draw(
handle,
*color,
&svg.handle,
svg.color,
physical_bounds,
*opacity,
svg.opacity,
_pixels,
transform,
clip_mask,

View file

@ -1,11 +1,11 @@
use crate::Primitive;
use crate::core::text::LineHeight;
use crate::core::{Pixels, Point, Radians, Rectangle, Size, Vector};
use crate::core::{self, Pixels, Point, Radians, Rectangle, Size, Svg, 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::Primitive;
use crate::graphics::{self, Gradient, Image, Text};
use std::rc::Rc;
@ -13,6 +13,7 @@ use std::rc::Rc;
pub enum Geometry {
Live {
text: Vec<Text>,
images: Vec<graphics::Image>,
primitives: Vec<Primitive>,
clip_bounds: Rectangle,
},
@ -22,6 +23,7 @@ pub enum Geometry {
#[derive(Debug, Clone)]
pub struct Cache {
pub text: Rc<[Text]>,
pub images: Rc<[graphics::Image]>,
pub primitives: Rc<[Primitive]>,
pub clip_bounds: Rectangle,
}
@ -37,10 +39,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 +59,7 @@ pub struct Frame {
transform: tiny_skia::Transform,
stack: Vec<tiny_skia::Transform>,
primitives: Vec<Primitive>,
images: Vec<graphics::Image>,
text: Vec<Text>,
}
@ -68,6 +73,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,
@ -162,6 +168,15 @@ impl geometry::frame::Backend for Frame {
});
}
fn stroke_rectangle<'a>(
&mut self,
top_left: Point,
size: Size,
stroke: impl Into<Stroke<'a>>,
) {
self.stroke(&Path::rectangle(top_left, size), stroke);
}
fn fill_text(&mut self, text: impl Into<geometry::Text>) {
let text = text.into();
@ -238,9 +253,10 @@ 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);
self.images.extend(frame.images);
}
fn translate(&mut self, translation: Vector) {
@ -269,10 +285,63 @@ 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, bounds: Rectangle, image: impl Into<core::Image>) {
let mut image = image.into();
let (bounds, external_rotation) =
transform_rectangle(bounds, self.transform);
image.rotation += external_rotation;
self.images.push(graphics::Image::Raster(image, bounds));
}
fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
let mut svg = svg.into();
let (bounds, external_rotation) =
transform_rectangle(bounds, self.transform);
svg.rotation += external_rotation;
self.images.push(Image::Vector(svg, bounds));
}
}
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> {

View file

@ -1,12 +1,12 @@
use crate::Primitive;
use crate::core::renderer::Quad;
use crate::core::{
image, renderer::Quad, svg, Background, Color, Point, Radians, Rectangle,
Transformation,
self, Background, Color, Point, Rectangle, Svg, Transformation,
};
use crate::graphics::damage;
use crate::graphics::layer;
use crate::graphics::text::{Editor, Paragraph, Text};
use crate::graphics::{self, Image};
use crate::Primitive;
use std::rc::Rc;
@ -72,7 +72,7 @@ impl Layer {
pub fn draw_text(
&mut self,
text: crate::core::Text,
text: core::Text,
position: Point,
color: Color,
clip_bounds: Rectangle,
@ -115,42 +115,35 @@ impl Layer {
.push(Item::Cached(text, clip_bounds, transformation));
}
pub fn draw_image(
pub fn draw_image(&mut self, image: Image, transformation: Transformation) {
match image {
Image::Raster(raster, bounds) => {
self.draw_raster(raster, bounds, transformation);
}
Image::Vector(svg, bounds) => {
self.draw_svg(svg, bounds, transformation);
}
}
}
pub fn draw_raster(
&mut self,
handle: image::Handle,
filter_method: image::FilterMethod,
image: core::Image,
bounds: Rectangle,
transformation: Transformation,
rotation: Radians,
opacity: f32,
) {
let image = Image::Raster {
handle,
filter_method,
bounds: bounds * transformation,
rotation,
opacity,
};
let image = Image::Raster(image, bounds * transformation);
self.images.push(image);
}
pub fn draw_svg(
&mut self,
handle: svg::Handle,
color: Option<Color>,
svg: Svg,
bounds: Rectangle,
transformation: Transformation,
rotation: Radians,
opacity: f32,
) {
let svg = Image::Vector {
handle,
color,
bounds: bounds * transformation,
rotation,
opacity,
};
let svg = Image::Vector(svg, bounds * transformation);
self.images.push(svg);
}
@ -293,7 +286,7 @@ impl graphics::Layer for Layer {
fn flush(&mut self) {}
fn resize(&mut self, bounds: graphics::core::Rectangle) {
fn resize(&mut self, bounds: Rectangle) {
self.bounds = bounds;
}

View file

@ -29,12 +29,12 @@ pub use geometry::Geometry;
use crate::core::renderer;
use crate::core::{
Background, Color, Font, Pixels, Point, Rectangle, Transformation,
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
};
use crate::engine::Engine;
use crate::graphics::Viewport;
use crate::graphics::compositor;
use crate::graphics::text::{Editor, Paragraph};
use crate::graphics::Viewport;
/// A [`tiny-skia`] graphics renderer for [`iced`].
///
@ -147,6 +147,16 @@ impl Renderer {
engine::adjust_clip_mask(clip_mask, clip_bounds);
}
for image in &layer.images {
self.engine.draw_image(
image,
Transformation::scale(scale_factor),
pixels,
clip_mask,
clip_bounds,
);
}
for group in &layer.text {
for text in group.as_slice() {
self.engine.draw_text(
@ -159,16 +169,6 @@ impl Renderer {
);
}
}
for image in &layer.images {
self.engine.draw_image(
image,
Transformation::scale(scale_factor),
pixels,
clip_mask,
clip_bounds,
);
}
}
}
@ -280,6 +280,7 @@ impl graphics::geometry::Renderer for Renderer {
match geometry {
Geometry::Live {
primitives,
images,
text,
clip_bounds,
} => {
@ -289,6 +290,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) => {
@ -298,6 +303,10 @@ impl graphics::geometry::Renderer for Renderer {
transformation,
);
for image in cache.images.iter() {
layer.draw_image(image.clone(), transformation);
}
layer.draw_text_cache(
cache.text,
cache.clip_bounds,
@ -322,23 +331,9 @@ impl core::image::Renderer for Renderer {
self.engine.raster_pipeline.dimensions(handle)
}
fn draw_image(
&mut self,
handle: Self::Handle,
filter_method: core::image::FilterMethod,
bounds: Rectangle,
rotation: core::Radians,
opacity: f32,
) {
fn draw_image(&mut self, image: core::Image, bounds: Rectangle) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_image(
handle,
filter_method,
bounds,
transformation,
rotation,
opacity,
);
layer.draw_raster(image, bounds, transformation);
}
}
@ -351,26 +346,30 @@ impl core::svg::Renderer for Renderer {
self.engine.vector_pipeline.viewport_dimensions(handle)
}
fn draw_svg(
&mut self,
handle: core::svg::Handle,
color: Option<Color>,
bounds: Rectangle,
rotation: core::Radians,
opacity: f32,
) {
fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_svg(
handle,
color,
bounds,
transformation,
rotation,
opacity,
);
layer.draw_svg(svg, bounds, transformation);
}
}
impl compositor::Default for Renderer {
type Compositor = window::Compositor;
}
impl renderer::Headless for Renderer {
fn new(default_font: Font, default_text_size: Pixels) -> Self {
Self::new(default_font, default_text_size)
}
fn screenshot(
&mut self,
size: Size<u32>,
scale_factor: f32,
background_color: Color,
) -> Vec<u8> {
let viewport =
Viewport::with_physical_size(size, f64::from(scale_factor));
window::compositor::screenshot(self, &viewport, background_color)
}
}

View file

@ -169,7 +169,13 @@ impl Pipeline {
font_system.raw(),
&mut self.glyph_cache,
buffer,
Rectangle::new(position, Size::new(width, height)),
Rectangle::new(
position,
Size::new(
width.unwrap_or(pixels.width() as f32),
height.unwrap_or(pixels.height() as f32),
),
),
color,
alignment::Horizontal::Left,
alignment::Vertical::Top,

View file

@ -1,14 +1,14 @@
use crate::core::svg::{Data, Handle};
use crate::core::{Color, Rectangle, Size};
use crate::graphics::text;
use resvg::usvg::{self, TreeTextToPath};
use resvg::usvg;
use rustc_hash::{FxHashMap, FxHashSet};
use tiny_skia::Transform;
use std::cell::RefCell;
use std::collections::hash_map;
use std::fs;
use std::sync::Arc;
#[derive(Debug)]
pub struct Pipeline {
@ -69,6 +69,7 @@ struct Cache {
tree_hits: FxHashSet<u64>,
rasters: FxHashMap<RasterKey, tiny_skia::Pixmap>,
raster_hits: FxHashSet<RasterKey>,
fontdb: Option<Arc<usvg::fontdb::Database>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -80,35 +81,37 @@ struct RasterKey {
impl Cache {
fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> {
use usvg::TreeParsing;
let id = handle.id();
// TODO: Reuse `cosmic-text` font database
if self.fontdb.is_none() {
let mut fontdb = usvg::fontdb::Database::new();
fontdb.load_system_fonts();
self.fontdb = Some(Arc::new(fontdb));
}
let options = usvg::Options {
fontdb: self
.fontdb
.as_ref()
.expect("fontdb must be initialized")
.clone(),
..usvg::Options::default()
};
if let hash_map::Entry::Vacant(entry) = self.trees.entry(id) {
let mut svg = match handle.data() {
let svg = match handle.data() {
Data::Path(path) => {
fs::read_to_string(path).ok().and_then(|contents| {
usvg::Tree::from_str(
&contents,
&usvg::Options::default(),
)
.ok()
usvg::Tree::from_str(&contents, &options).ok()
})
}
Data::Bytes(bytes) => {
usvg::Tree::from_data(bytes, &usvg::Options::default()).ok()
usvg::Tree::from_data(bytes, &options).ok()
}
};
if let Some(svg) = &mut svg {
if svg.has_text_nodes() {
let mut font_system =
text::font_system().write().expect("Write font system");
svg.convert_text(font_system.raw().db_mut());
}
}
let _ = entry.insert(svg);
}
@ -118,11 +121,9 @@ impl Cache {
fn viewport_dimensions(&mut self, handle: &Handle) -> Option<Size<u32>> {
let tree = self.load(handle)?;
let size = tree.size();
Some(Size::new(
tree.size.width() as u32,
tree.size.height() as u32,
))
Some(Size::new(size.width() as u32, size.height() as u32))
}
fn draw(
@ -147,7 +148,7 @@ impl Cache {
let mut image = tiny_skia::Pixmap::new(size.width, size.height)?;
let tree_size = tree.size.to_int_size();
let tree_size = tree.size().to_int_size();
let target_size = if size.width > size.height {
tree_size.scale_to_width(size.width)
@ -167,7 +168,7 @@ impl Cache {
tiny_skia::Transform::default()
};
resvg::Tree::from_usvg(tree).render(transform, &mut image.as_mut());
resvg::render(tree, transform, &mut image.as_mut());
if let Some([r, g, b, _]) = key.color {
// Apply color filter

View file

@ -120,11 +120,10 @@ impl crate::graphics::Compositor for Compositor {
fn screenshot(
&mut self,
renderer: &mut Self::Renderer,
surface: &mut Self::Surface,
viewport: &Viewport,
background_color: Color,
) -> Vec<u8> {
screenshot(renderer, surface, viewport, background_color)
screenshot(renderer, viewport, background_color)
}
}
@ -208,7 +207,6 @@ pub fn present(
pub fn screenshot(
renderer: &mut Renderer,
surface: &mut Surface,
viewport: &Viewport,
background_color: Color,
) -> Vec<u8> {
@ -217,6 +215,9 @@ pub fn screenshot(
let mut offscreen_buffer: Vec<u32> =
vec![0; size.width as usize * size.height as usize];
let mut clip_mask = tiny_skia::Mask::new(size.width, size.height)
.expect("Create clip mask");
renderer.draw(
&mut tiny_skia::PixmapMut::from_bytes(
bytemuck::cast_slice_mut(&mut offscreen_buffer),
@ -224,7 +225,7 @@ pub fn screenshot(
size.height,
)
.expect("Create offscreen pixel map"),
&mut surface.clip_mask,
&mut clip_mask,
viewport,
&[Rectangle::with_size(Size::new(
size.width as f32,