Merge pull request #2537 from iced-rs/feature/canvas-image-support

`image` and `svg` support for `canvas`
This commit is contained in:
Héctor Ramón 2024-08-04 14:52:29 +02:00 committed by GitHub
commit 145c3dc8fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 627 additions and 400 deletions

View file

@ -7,6 +7,73 @@ use rustc_hash::FxHasher;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
/// A raster image that can be drawn.
#[derive(Debug, Clone, PartialEq)]
pub struct Image<H = Handle> {
/// The handle of the image.
pub handle: H,
/// The filter method of the image.
pub filter_method: FilterMethod,
/// The rotation to be applied to the image; on its center.
pub rotation: Radians,
/// The opacity of the image.
///
/// 0 means transparent. 1 means opaque.
pub opacity: f32,
/// If set to `true`, the image will be snapped to the pixel grid.
///
/// This can avoid graphical glitches, specially when using
/// [`FilterMethod::Nearest`].
pub snap: bool,
}
impl Image<Handle> {
/// Creates a new [`Image`] with the given handle.
pub fn new(handle: impl Into<Handle>) -> Self {
Self {
handle: handle.into(),
filter_method: FilterMethod::default(),
rotation: Radians(0.0),
opacity: 1.0,
snap: false,
}
}
/// Sets the filter method of the [`Image`].
pub fn filter_method(mut self, filter_method: FilterMethod) -> Self {
self.filter_method = filter_method;
self
}
/// Sets the rotation of the [`Image`].
pub fn rotation(mut self, rotation: impl Into<Radians>) -> Self {
self.rotation = rotation.into();
self
}
/// Sets the opacity of the [`Image`].
pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
self.opacity = opacity.into();
self
}
/// Sets whether the [`Image`] should be snapped to the pixel grid.
pub fn snap(mut self, snap: bool) -> Self {
self.snap = snap;
self
}
}
impl From<&Handle> for Image {
fn from(handle: &Handle) -> Self {
Image::new(handle.clone())
}
}
/// A handle of some image data.
#[derive(Clone, PartialEq, Eq)]
pub enum Handle {
@ -101,6 +168,12 @@ where
}
}
impl From<&Handle> for Handle {
fn from(value: &Handle) -> Self {
value.clone()
}
}
impl std::fmt::Debug for Handle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@ -166,14 +239,6 @@ pub trait Renderer: crate::Renderer {
/// Returns the dimensions of an image for the given [`Handle`].
fn measure_image(&self, handle: &Self::Handle) -> Size<u32>;
/// Draws an image with the given [`Handle`] and inside the provided
/// `bounds`.
fn draw_image(
&mut self,
handle: Self::Handle,
filter_method: FilterMethod,
bounds: Rectangle,
rotation: Radians,
opacity: f32,
);
/// Draws an [`Image`] inside the provided `bounds`.
fn draw_image(&mut self, image: Image<Self::Handle>, bounds: Rectangle);
}

View file

@ -57,6 +57,7 @@ pub use element::Element;
pub use event::Event;
pub use font::Font;
pub use gradient::Gradient;
pub use image::Image;
pub use layout::Layout;
pub use length::Length;
pub use overlay::Overlay;
@ -69,6 +70,7 @@ pub use rotation::Rotation;
pub use shadow::Shadow;
pub use shell::Shell;
pub use size::Size;
pub use svg::Svg;
pub use text::Text;
pub use theme::Theme;
pub use transformation::Transformation;

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`].
pub fn center(&self) -> Point {
Point::new(self.center_x(), self.center_y())

View file

@ -1,11 +1,10 @@
use crate::alignment;
use crate::image;
use crate::image::{self, Image};
use crate::renderer::{self, Renderer};
use crate::svg;
use crate::text::{self, Text};
use crate::{
Background, Color, Font, Pixels, Point, Radians, Rectangle, Size,
Transformation,
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
};
impl Renderer for () {
@ -178,21 +177,13 @@ impl text::Editor for () {
}
impl image::Renderer for () {
type Handle = ();
type Handle = image::Handle;
fn measure_image(&self, _handle: &Self::Handle) -> Size<u32> {
Size::default()
}
fn draw_image(
&mut self,
_handle: Self::Handle,
_filter_method: image::FilterMethod,
_bounds: Rectangle,
_rotation: Radians,
_opacity: f32,
) {
}
fn draw_image(&mut self, _image: Image, _bounds: Rectangle) {}
}
impl svg::Renderer for () {
@ -200,13 +191,5 @@ impl svg::Renderer for () {
Size::default()
}
fn draw_svg(
&mut self,
_handle: svg::Handle,
_color: Option<Color>,
_bounds: Rectangle,
_rotation: Radians,
_opacity: f32,
) {
}
fn draw_svg(&mut self, _svg: svg::Svg, _bounds: Rectangle) {}
}

View file

@ -7,6 +7,66 @@ use std::hash::{Hash, Hasher as _};
use std::path::PathBuf;
use std::sync::Arc;
/// A raster image that can be drawn.
#[derive(Debug, Clone, PartialEq)]
pub struct Svg<H = Handle> {
/// The handle of the [`Svg`].
pub handle: H,
/// The [`Color`] filter to be applied to the [`Svg`].
///
/// If some [`Color`] is set, the whole [`Svg`] will be
/// painted with it—ignoring any intrinsic colors.
///
/// This can be useful for coloring icons programmatically
/// (e.g. with a theme).
pub color: Option<Color>,
/// The rotation to be applied to the image; on its center.
pub rotation: Radians,
/// The opacity of the [`Svg`].
///
/// 0 means transparent. 1 means opaque.
pub opacity: f32,
}
impl Svg<Handle> {
/// Creates a new [`Svg`] with the given handle.
pub fn new(handle: impl Into<Handle>) -> Self {
Self {
handle: handle.into(),
color: None,
rotation: Radians(0.0),
opacity: 1.0,
}
}
/// Sets the [`Color`] filter of the [`Svg`].
pub fn color(mut self, color: impl Into<Color>) -> Self {
self.color = Some(color.into());
self
}
/// Sets the rotation of the [`Svg`].
pub fn rotation(mut self, rotation: impl Into<Radians>) -> Self {
self.rotation = rotation.into();
self
}
/// Sets the opacity of the [`Svg`].
pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
self.opacity = opacity.into();
self
}
}
impl From<&Handle> for Svg {
fn from(handle: &Handle) -> Self {
Svg::new(handle.clone())
}
}
/// A handle of Svg data.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Handle {
@ -95,12 +155,5 @@ pub trait Renderer: crate::Renderer {
fn measure_svg(&self, handle: &Handle) -> Size<u32>;
/// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`.
fn draw_svg(
&mut self,
handle: Handle,
color: Option<Color>,
bounds: Rectangle,
rotation: Radians,
opacity: f32,
);
fn draw_svg(&mut self, svg: Svg, bounds: Rectangle);
}

View file

@ -20,7 +20,7 @@ fn main() -> iced::Result {
#[derive(Default)]
struct Example {
screenshot: Option<Screenshot>,
screenshot: Option<(Screenshot, image::Handle)>,
saved_png_path: Option<Result<String, PngError>>,
png_saving: bool,
crop_error: Option<screenshot::CropError>,
@ -52,10 +52,17 @@ impl Example {
.map(Message::Screenshotted);
}
Message::Screenshotted(screenshot) => {
self.screenshot = Some(screenshot);
self.screenshot = Some((
screenshot.clone(),
image::Handle::from_rgba(
screenshot.size.width,
screenshot.size.height,
screenshot.bytes,
),
));
}
Message::Png => {
if let Some(screenshot) = &self.screenshot {
if let Some((screenshot, _handle)) = &self.screenshot {
self.png_saving = true;
return Task::perform(
@ -81,7 +88,7 @@ impl Example {
self.height_input_value = new_value;
}
Message::Crop => {
if let Some(screenshot) = &self.screenshot {
if let Some((screenshot, _handle)) = &self.screenshot {
let cropped = screenshot.crop(Rectangle::<u32> {
x: self.x_input_value.unwrap_or(0),
y: self.y_input_value.unwrap_or(0),
@ -91,7 +98,14 @@ impl Example {
match cropped {
Ok(screenshot) => {
self.screenshot = Some(screenshot);
self.screenshot = Some((
screenshot.clone(),
image::Handle::from_rgba(
screenshot.size.width,
screenshot.size.height,
screenshot.bytes,
),
));
self.crop_error = None;
}
Err(crop_error) => {
@ -106,20 +120,16 @@ impl Example {
}
fn view(&self) -> Element<'_, Message> {
let image: Element<Message> = if let Some(screenshot) = &self.screenshot
{
image(image::Handle::from_rgba(
screenshot.size.width,
screenshot.size.height,
screenshot.clone(),
))
.content_fit(ContentFit::Contain)
.width(Fill)
.height(Fill)
.into()
} else {
text("Press the button to take a screenshot!").into()
};
let image: Element<Message> =
if let Some((_screenshot, handle)) = &self.screenshot {
image(handle)
.content_fit(ContentFit::Contain)
.width(Fill)
.height(Fill)
.into()
} else {
text("Press the button to take a screenshot!").into()
};
let image = container(image)
.center_y(FillPortion(2))

View file

@ -7,7 +7,7 @@ publish = false
[dependencies]
iced.workspace = true
iced.features = ["debug", "canvas", "tokio"]
iced.features = ["debug", "canvas", "image", "tokio"]
rand = "0.8.3"
tracing-subscriber = "0.3"

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View file

@ -7,10 +7,9 @@
//!
//! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system
use iced::mouse;
use iced::widget::canvas;
use iced::widget::canvas::gradient;
use iced::widget::canvas::stroke::{self, Stroke};
use iced::widget::canvas::{Geometry, Path};
use iced::widget::{canvas, image};
use iced::window;
use iced::{
Color, Element, Fill, Point, Rectangle, Renderer, Size, Subscription,
@ -66,6 +65,9 @@ impl SolarSystem {
#[derive(Debug)]
struct State {
sun: image::Handle,
earth: image::Handle,
moon: image::Handle,
space_cache: canvas::Cache,
system_cache: canvas::Cache,
start: Instant,
@ -85,6 +87,15 @@ impl State {
let size = window::Settings::default().size;
State {
sun: image::Handle::from_bytes(
include_bytes!("../assets/sun.png").as_slice(),
),
earth: image::Handle::from_bytes(
include_bytes!("../assets/earth.png").as_slice(),
),
moon: image::Handle::from_bytes(
include_bytes!("../assets/moon.png").as_slice(),
),
space_cache: canvas::Cache::default(),
system_cache: canvas::Cache::default(),
start: now,
@ -132,6 +143,8 @@ impl<Message> canvas::Program<Message> for State {
let background =
self.space_cache.draw(renderer, bounds.size(), |frame| {
frame.fill_rectangle(Point::ORIGIN, frame.size(), Color::BLACK);
let stars = Path::new(|path| {
for (p, size) in &self.stars {
path.rectangle(*p, Size::new(*size, *size));
@ -144,17 +157,18 @@ impl<Message> canvas::Program<Message> for State {
let system = self.system_cache.draw(renderer, bounds.size(), |frame| {
let center = frame.center();
frame.translate(Vector::new(center.x, center.y));
let sun = Path::circle(center, Self::SUN_RADIUS);
let orbit = Path::circle(center, Self::ORBIT_RADIUS);
frame.draw_image(
Rectangle::with_radius(Self::SUN_RADIUS),
&self.sun,
);
frame.fill(&sun, Color::from_rgb8(0xF9, 0xD7, 0x1C));
let orbit = Path::circle(Point::ORIGIN, Self::ORBIT_RADIUS);
frame.stroke(
&orbit,
Stroke {
style: stroke::Style::Solid(Color::from_rgba8(
0, 153, 255, 0.1,
)),
style: stroke::Style::Solid(Color::WHITE.scale_alpha(0.1)),
width: 1.0,
line_dash: canvas::LineDash {
offset: 0,
@ -168,30 +182,21 @@ impl<Message> canvas::Program<Message> for State {
let rotation = (2.0 * PI / 60.0) * elapsed.as_secs() as f32
+ (2.0 * PI / 60_000.0) * elapsed.subsec_millis() as f32;
frame.with_save(|frame| {
frame.translate(Vector::new(center.x, center.y));
frame.rotate(rotation);
frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0));
frame.rotate(rotation);
frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0));
let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS);
frame.draw_image(
Rectangle::with_radius(Self::EARTH_RADIUS),
canvas::Image::new(&self.earth).rotation(-rotation * 20.0),
);
let earth_fill = gradient::Linear::new(
Point::new(-Self::EARTH_RADIUS, 0.0),
Point::new(Self::EARTH_RADIUS, 0.0),
)
.add_stop(0.2, Color::from_rgb(0.15, 0.50, 1.0))
.add_stop(0.8, Color::from_rgb(0.0, 0.20, 0.47));
frame.rotate(rotation * 10.0);
frame.translate(Vector::new(0.0, Self::MOON_DISTANCE));
frame.fill(&earth, earth_fill);
frame.with_save(|frame| {
frame.rotate(rotation * 10.0);
frame.translate(Vector::new(0.0, Self::MOON_DISTANCE));
let moon = Path::circle(Point::ORIGIN, Self::MOON_RADIUS);
frame.fill(&moon, Color::WHITE);
});
});
frame.draw_image(
Rectangle::with_radius(Self::MOON_RADIUS),
&self.moon,
);
});
vec![background, system]

View file

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

View file

@ -16,6 +16,7 @@ pub use stroke::{LineCap, LineDash, LineJoin, Stroke};
pub use style::Style;
pub use text::Text;
pub use crate::core::{Image, Svg};
pub use crate::gradient::{self, Gradient};
use crate::cache::Cached;

View file

@ -1,6 +1,6 @@
//! Draw and generate geometry.
use crate::core::{Point, Radians, Rectangle, Size, Vector};
use crate::geometry::{self, Fill, Path, Stroke, Text};
use crate::geometry::{self, Fill, Image, Path, Stroke, Svg, Text};
/// The region of a surface that can be used to draw geometry.
#[allow(missing_debug_implementations)]
@ -75,6 +75,18 @@ 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, bounds: Rectangle, image: impl Into<Image>) {
self.raw.draw_image(bounds, image);
}
/// Draws the given [`Svg`] on the [`Frame`] inside the given bounds.
#[cfg(feature = "svg")]
pub fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
self.raw.draw_svg(bounds, svg);
}
/// Stores the current transform of the [`Frame`] and executes the given
/// drawing operations, restoring the transform afterwards.
///
@ -116,8 +128,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 +145,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 +197,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 +210,9 @@ pub trait Backend: Sized {
fill: impl Into<Fill>,
);
fn draw_image(&mut self, bounds: Rectangle, image: impl Into<Image>);
fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>);
fn into_geometry(self) -> Self::Geometry;
}
@ -231,7 +245,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>>) {}
@ -245,5 +259,8 @@ impl Backend for () {
) {
}
fn draw_image(&mut self, _bounds: Rectangle, _image: impl Into<Image>) {}
fn draw_svg(&mut self, _bounds: Rectangle, _svg: impl Into<Svg>) {}
fn into_geometry(self) -> Self::Geometry {}
}

View file

@ -2,57 +2,26 @@
#[cfg(feature = "image")]
pub use ::image as image_rs;
use crate::core::{image, svg, Color, Radians, Rectangle};
use crate::core::image;
use crate::core::svg;
use crate::core::Rectangle;
/// A raster or vector image.
#[derive(Debug, Clone, PartialEq)]
pub enum Image {
/// A raster image.
Raster {
/// The handle of a raster image.
handle: image::Handle,
Raster(image::Image, Rectangle),
/// The filter method of a raster image.
filter_method: image::FilterMethod,
/// The bounds of the image.
bounds: Rectangle,
/// The rotation of the image.
rotation: Radians,
/// The opacity of the image.
opacity: f32,
},
/// A vector image.
Vector {
/// The handle of a vector image.
handle: svg::Handle,
/// The [`Color`] filter
color: Option<Color>,
/// The bounds of the image.
bounds: Rectangle,
/// The rotation of the image.
rotation: Radians,
/// The opacity of the image.
opacity: f32,
},
Vector(svg::Svg, Rectangle),
}
impl Image {
/// Returns the bounds of the [`Image`].
pub fn bounds(&self) -> Rectangle {
match self {
Image::Raster {
bounds, rotation, ..
}
| Image::Vector {
bounds, rotation, ..
} => bounds.rotate(*rotation),
Image::Raster(image, bounds) => bounds.rotate(image.rotation),
Image::Vector(svg, bounds) => bounds.rotate(svg.rotation),
}
}
}

View file

@ -3,7 +3,7 @@ use crate::core::image;
use crate::core::renderer;
use crate::core::svg;
use crate::core::{
self, Background, Color, Point, Radians, Rectangle, Size, Transformation,
self, Background, Color, Image, Point, Rectangle, Size, Svg, Transformation,
};
use crate::graphics;
use crate::graphics::compositor;
@ -149,25 +149,8 @@ where
delegate!(self, renderer, renderer.measure_image(handle))
}
fn draw_image(
&mut self,
handle: Self::Handle,
filter_method: image::FilterMethod,
bounds: Rectangle,
rotation: Radians,
opacity: f32,
) {
delegate!(
self,
renderer,
renderer.draw_image(
handle,
filter_method,
bounds,
rotation,
opacity
)
);
fn draw_image(&mut self, image: Image<A::Handle>, bounds: Rectangle) {
delegate!(self, renderer, renderer.draw_image(image, bounds));
}
}
@ -180,19 +163,8 @@ where
delegate!(self, renderer, renderer.measure_svg(handle))
}
fn draw_svg(
&mut self,
handle: svg::Handle,
color: Option<Color>,
bounds: Rectangle,
rotation: Radians,
opacity: f32,
) {
delegate!(
self,
renderer,
renderer.draw_svg(handle, color, bounds, rotation, opacity)
);
fn draw_svg(&mut self, svg: Svg, bounds: Rectangle) {
delegate!(self, renderer, renderer.draw_svg(svg, bounds));
}
}
@ -441,9 +413,9 @@ where
#[cfg(feature = "geometry")]
mod geometry {
use super::Renderer;
use crate::core::{Point, Radians, Rectangle, Size, Vector};
use crate::core::{Point, Radians, Rectangle, Size, Svg, Vector};
use crate::graphics::cache::{self, Cached};
use crate::graphics::geometry::{self, Fill, Path, Stroke, Text};
use crate::graphics::geometry::{self, Fill, Image, Path, Stroke, Text};
impl<A, B> geometry::Renderer for Renderer<A, B>
where
@ -572,6 +544,14 @@ mod geometry {
delegate!(self, frame, frame.fill_text(text));
}
fn draw_image(&mut self, bounds: Rectangle, image: impl Into<Image>) {
delegate!(self, frame, frame.draw_image(bounds, image));
}
fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
delegate!(self, frame, frame.draw_svg(bounds, svg));
}
fn push_transform(&mut self) {
delegate!(self, frame, frame.push_transform());
}
@ -587,13 +567,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!(),
}

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

@ -550,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) {
@ -567,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(),
@ -576,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) {
@ -603,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(),
@ -612,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,10 +1,10 @@
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::graphics::{self, Gradient, Image, Text};
use crate::Primitive;
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,
@ -238,7 +244,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 +275,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,6 +1,6 @@
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;
@ -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

@ -178,6 +178,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(
@ -190,16 +200,6 @@ impl Renderer {
);
}
}
for image in &layer.images {
self.engine.draw_image(
image,
Transformation::scale(scale_factor),
pixels,
clip_mask,
clip_bounds,
);
}
}
if !overlay.is_empty() {
@ -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.clone(), transformation);
}
layer.draw_text_cache(
cache.text,
cache.clip_bounds,
@ -372,23 +381,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);
}
}
@ -401,23 +396,9 @@ 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);
}
}

View file

@ -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"]

View file

@ -1,7 +1,7 @@
//! Build and draw geometry.
use crate::core::text::LineHeight;
use crate::core::{
Pixels, Point, Radians, Rectangle, Size, Transformation, Vector,
self, Pixels, Point, Radians, Rectangle, Size, Svg, Transformation, Vector,
};
use crate::graphics::cache::{self, Cached};
use crate::graphics::color;
@ -11,7 +11,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::{Image, Text};
use crate::text;
use crate::triangle;
@ -19,16 +19,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 +51,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 +75,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 +97,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 +116,7 @@ impl Frame {
clip_bounds: bounds,
buffers: BufferStack::new(),
meshes: Vec::new(),
images: Vec::new(),
text: Vec::new(),
transforms: Transforms {
previous: Vec::new(),
@ -270,7 +291,7 @@ impl geometry::frame::Backend for Frame {
height: f32::INFINITY,
};
self.text.push(graphics::Text::Cached {
self.text.push(Text::Cached {
content: text.content,
bounds,
color: text.color,
@ -335,10 +356,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 +370,32 @@ impl geometry::frame::Backend for Frame {
Geometry::Live {
meshes: self.meshes,
images: self.images,
text: self.text,
}
}
fn draw_image(&mut self, bounds: Rectangle, image: impl Into<core::Image>) {
let mut image = image.into();
let (bounds, external_rotation) =
self.transforms.current.transform_rectangle(bounds);
image.rotation += external_rotation;
self.images.push(Image::Raster(image, bounds));
}
fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
let mut svg = svg.into();
let (bounds, external_rotation) =
self.transforms.current.transform_rectangle(bounds);
svg.rotation += external_rotation;
self.images.push(Image::Vector(svg, bounds));
}
}
enum Buffer {
@ -518,6 +563,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,31 +214,24 @@ 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();
for image in images {
match &image {
#[cfg(feature = "image")]
Image::Raster {
handle,
filter_method,
bounds,
rotation,
opacity,
} => {
Image::Raster(image, bounds) => {
if let Some(atlas_entry) =
cache.upload_raster(device, encoder, handle)
cache.upload_raster(device, encoder, &image.handle)
{
add_instances(
[bounds.x, bounds.y],
[bounds.width, bounds.height],
f32::from(*rotation),
*opacity,
f32::from(image.rotation),
image.opacity,
image.snap,
atlas_entry,
match filter_method {
match image.filter_method {
crate::core::image::FilterMethod::Nearest => {
nearest_instances
}
@ -251,23 +246,23 @@ impl Pipeline {
Image::Raster { .. } => {}
#[cfg(feature = "svg")]
Image::Vector {
handle,
color,
bounds,
rotation,
opacity,
} => {
Image::Vector(svg, bounds) => {
let size = [bounds.width, bounds.height];
if let Some(atlas_entry) = cache.upload_vector(
device, encoder, handle, *color, size, scale,
device,
encoder,
&svg.handle,
svg.color,
size,
scale,
) {
add_instances(
[bounds.x, bounds.y],
size,
f32::from(*rotation),
*opacity,
f32::from(svg.rotation),
svg.opacity,
true,
atlas_entry,
nearest_instances,
);
@ -300,6 +295,7 @@ impl Pipeline {
nearest_instances,
linear_instances,
transformation,
scale,
);
self.prepare_layer += 1;
@ -375,9 +371,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 +491,7 @@ struct Instance {
_position_in_atlas: [f32; 2],
_size_in_atlas: [f32; 2],
_layer: u32,
_snap: u32,
}
impl Instance {
@ -502,6 +502,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 +513,7 @@ fn add_instances(
image_size: [f32; 2],
rotation: f32,
opacity: f32,
snap: bool,
entry: &atlas::Entry,
instances: &mut Vec<Instance>,
) {
@ -525,6 +530,7 @@ fn add_instances(
image_size,
rotation,
opacity,
snap,
allocation,
instances,
);
@ -554,8 +560,8 @@ fn add_instances(
];
add_instance(
position, center, size, rotation, opacity, allocation,
instances,
position, center, size, rotation, opacity, snap,
allocation, instances,
);
}
}
@ -569,6 +575,7 @@ fn add_instance(
size: [f32; 2],
rotation: f32,
opacity: f32,
snap: bool,
allocation: &atlas::Allocation,
instances: &mut Vec<Instance>,
) {
@ -591,6 +598,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

@ -1,5 +1,5 @@
use crate::core::{
renderer, Background, Color, Point, Radians, Rectangle, Transformation,
self, renderer, Background, Color, Point, Rectangle, Svg, Transformation,
};
use crate::graphics;
use crate::graphics::color;
@ -20,8 +20,8 @@ pub struct Layer {
pub quads: quad::Batch,
pub triangles: triangle::Batch,
pub primitives: primitive::Batch,
pub text: text::Batch,
pub images: image::Batch,
pub text: text::Batch,
pending_meshes: Vec<Mesh>,
pending_text: Vec<Text>,
}
@ -112,42 +112,35 @@ impl Layer {
self.pending_text.push(text);
}
pub fn draw_image(
pub fn draw_image(&mut self, image: Image, transformation: Transformation) {
match image {
Image::Raster(image, bounds) => {
self.draw_raster(image, bounds, transformation);
}
Image::Vector(svg, bounds) => {
self.draw_svg(svg, bounds, transformation);
}
}
}
pub fn draw_raster(
&mut self,
handle: crate::core::image::Handle,
filter_method: crate::core::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: crate::core::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);
}

View file

@ -182,19 +182,6 @@ impl Renderer {
}
}
if !layer.text.is_empty() {
engine.text_pipeline.prepare(
device,
queue,
&self.text_viewport,
encoder,
&mut self.text_storage,
&layer.text,
layer.bounds,
Transformation::scale(scale_factor),
);
}
#[cfg(any(feature = "svg", feature = "image"))]
if !layer.images.is_empty() {
engine.image_pipeline.prepare(
@ -207,6 +194,19 @@ impl Renderer {
scale_factor,
);
}
if !layer.text.is_empty() {
engine.text_pipeline.prepare(
device,
queue,
&self.text_viewport,
encoder,
&mut self.text_storage,
&layer.text,
layer.bounds,
Transformation::scale(scale_factor),
);
}
}
}
@ -359,17 +359,6 @@ impl Renderer {
));
}
if !layer.text.is_empty() {
text_layer += engine.text_pipeline.render(
&self.text_viewport,
&self.text_storage,
text_layer,
&layer.text,
scissor_rect,
&mut render_pass,
);
}
#[cfg(any(feature = "svg", feature = "image"))]
if !layer.images.is_empty() {
engine.image_pipeline.render(
@ -381,6 +370,17 @@ impl Renderer {
image_layer += 1;
}
if !layer.text.is_empty() {
text_layer += engine.text_pipeline.render(
&self.text_viewport,
&self.text_storage,
text_layer,
&layer.text,
scissor_rect,
&mut render_pass,
);
}
}
let _ = ManuallyDrop::into_inner(render_pass);
@ -527,23 +527,9 @@ impl core::image::Renderer for Renderer {
self.image_cache.borrow_mut().measure_image(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);
}
}
@ -553,23 +539,9 @@ impl core::svg::Renderer for Renderer {
self.image_cache.borrow_mut().measure_svg(handle)
}
fn draw_svg(
&mut self,
handle: core::svg::Handle,
color_filter: 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_filter,
bounds,
transformation,
rotation,
opacity,
);
layer.draw_svg(svg, bounds, transformation);
}
}
@ -593,8 +565,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 +583,12 @@ impl graphics::geometry::Renderer for Renderer {
layer.draw_mesh_cache(meshes, transformation);
}
if let Some(images) = cache.images {
for image in images.iter().cloned() {
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;
}

View file

@ -8,8 +8,8 @@ pub use program::Program;
pub use crate::graphics::cache::Group;
pub use crate::graphics::geometry::{
fill, gradient, path, stroke, Fill, Gradient, LineCap, LineDash, LineJoin,
Path, Stroke, Style, Text,
fill, gradient, path, stroke, Fill, Gradient, Image, LineCap, LineDash,
LineJoin, Path, Stroke, Style, Text,
};
use crate::core;

View file

@ -43,7 +43,7 @@ pub struct Image<Handle> {
impl<Handle> Image<Handle> {
/// Creates a new [`Image`] with the given path.
pub fn new<T: Into<Handle>>(handle: T) -> Self {
pub fn new(handle: impl Into<Handle>) -> Self {
Image {
handle: handle.into(),
width: Length::Shrink,
@ -181,11 +181,14 @@ pub fn draw<Renderer, Handle>(
let render = |renderer: &mut Renderer| {
renderer.draw_image(
handle.clone(),
filter_method,
image::Image {
handle: handle.clone(),
filter_method,
rotation: rotation.radians(),
opacity,
snap: true,
},
drawing_bounds,
rotation.radians(),
opacity,
);
};

View file

@ -6,8 +6,8 @@ use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::tree::{self, Tree};
use crate::core::{
Clipboard, ContentFit, Element, Layout, Length, Pixels, Point, Radians,
Rectangle, Shell, Size, Vector, Widget,
Clipboard, ContentFit, Element, Image, Layout, Length, Pixels, Point,
Radians, Rectangle, Shell, Size, Vector, Widget,
};
/// A frame that displays an image with the ability to zoom in/out and pan.
@ -349,11 +349,14 @@ where
let render = |renderer: &mut Renderer| {
renderer.with_translation(translation, |renderer| {
renderer.draw_image(
self.handle.clone(),
self.filter_method,
Image {
handle: self.handle.clone(),
filter_method: self.filter_method,
rotation: Radians(0.0),
opacity: 1.0,
snap: true,
},
drawing_bounds,
Radians(0.0),
1.0,
);
});
};

View file

@ -211,11 +211,13 @@ where
let render = |renderer: &mut Renderer| {
renderer.draw_svg(
self.handle.clone(),
style.color,
svg::Svg {
handle: self.handle.clone(),
color: style.color,
rotation: self.rotation.radians(),
opacity: self.opacity,
},
drawing_bounds,
self.rotation.radians(),
self.opacity,
);
};