Simplify image rotation API and its internals

This commit is contained in:
Héctor Ramón Jiménez 2024-05-02 15:21:22 +02:00
parent 09a6bcfffc
commit a57313b23e
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
23 changed files with 219 additions and 225 deletions

View file

@ -65,6 +65,12 @@ impl From<u8> for Radians {
} }
} }
impl From<Radians> for f32 {
fn from(radians: Radians) -> Self {
radians.0
}
}
impl From<Radians> for f64 { impl From<Radians> for f64 {
fn from(radians: Radians) -> Self { fn from(radians: Radians) -> Self {
Self::from(radians.0) Self::from(radians.0)

View file

@ -11,7 +11,7 @@ use crate::Size;
/// in CSS, see [Mozilla's docs][1], or run the `tour` example /// in CSS, see [Mozilla's docs][1], or run the `tour` example
/// ///
/// [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit /// [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, Default)]
pub enum ContentFit { pub enum ContentFit {
/// Scale as big as it can be without needing to crop or hide parts. /// Scale as big as it can be without needing to crop or hide parts.
/// ///
@ -23,6 +23,7 @@ pub enum ContentFit {
/// This is a great fit for when you need to display an image without losing /// This is a great fit for when you need to display an image without losing
/// any part of it, particularly when the image itself is the focus of the /// any part of it, particularly when the image itself is the focus of the
/// screen. /// screen.
#[default]
Contain, Contain,
/// Scale the image to cover all of the bounding box, cropping if needed. /// Scale the image to cover all of the bounding box, cropping if needed.

View file

@ -1,7 +1,7 @@
//! Load and draw raster graphics. //! Load and draw raster graphics.
pub use bytes::Bytes; pub use bytes::Bytes;
use crate::{Rectangle, Size}; use crate::{Radians, Rectangle, Size};
use rustc_hash::FxHasher; use rustc_hash::FxHasher;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
@ -173,7 +173,6 @@ pub trait Renderer: crate::Renderer {
handle: Self::Handle, handle: Self::Handle,
filter_method: FilterMethod, filter_method: FilterMethod,
bounds: Rectangle, bounds: Rectangle,
rotation: f32, rotation: Radians,
scale: Size,
); );
} }

View file

@ -65,7 +65,7 @@ pub use pixels::Pixels;
pub use point::Point; pub use point::Point;
pub use rectangle::Rectangle; pub use rectangle::Rectangle;
pub use renderer::Renderer; pub use renderer::Renderer;
pub use rotation::RotationLayout; pub use rotation::Rotation;
pub use shadow::Shadow; pub use shadow::Shadow;
pub use shell::Shell; pub use shell::Shell;
pub use size::Size; pub use size::Size;

View file

@ -227,3 +227,19 @@ where
} }
} }
} }
impl<T> std::ops::Mul<Vector<T>> for Rectangle<T>
where
T: std::ops::Mul<Output = T> + Copy,
{
type Output = Rectangle<T>;
fn mul(self, scale: Vector<T>) -> Self {
Rectangle {
x: self.x * scale.x,
y: self.y * scale.y,
width: self.width * scale.x,
height: self.height * scale.y,
}
}
}

View file

@ -4,7 +4,8 @@ use crate::renderer::{self, Renderer};
use crate::svg; use crate::svg;
use crate::text::{self, Text}; use crate::text::{self, Text};
use crate::{ use crate::{
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, Background, Color, Font, Pixels, Point, Radians, Rectangle, Size,
Transformation,
}; };
impl Renderer for () { impl Renderer for () {
@ -171,8 +172,7 @@ impl image::Renderer for () {
_handle: Self::Handle, _handle: Self::Handle,
_filter_method: image::FilterMethod, _filter_method: image::FilterMethod,
_bounds: Rectangle, _bounds: Rectangle,
_rotation: f32, _rotation: Radians,
_scale: Size,
) { ) {
} }
} }
@ -187,8 +187,7 @@ impl svg::Renderer for () {
_handle: svg::Handle, _handle: svg::Handle,
_color: Option<Color>, _color: Option<Color>,
_bounds: Rectangle, _bounds: Rectangle,
_rotation: f32, _rotation: Radians,
_scale: Size,
) { ) {
} }
} }

View file

@ -1,37 +1,68 @@
//! Control the rotation of some content (like an image) with the `RotationLayout` within a //! Control the rotation of some content (like an image) within a space.
//! space. use crate::{Radians, Size};
use crate::Size;
/// The strategy used to rotate the content. /// The strategy used to rotate the content.
/// ///
/// This is used to control the behavior of the layout when the content is rotated /// This is used to control the behavior of the layout when the content is rotated
/// by a certain angle. /// by a certain angle.
#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum RotationLayout { pub enum Rotation {
/// The layout is kept exactly as it was before the rotation. /// The element will float while rotating. The layout will be kept exactly as it was
/// before the rotation.
/// ///
/// This is especially useful when used for animations, as it will avoid the /// This is especially useful when used for animations, as it will avoid the
/// layout being shifted or resized when smoothly i.e. an icon. /// layout being shifted or resized when smoothly i.e. an icon.
Keep, ///
/// The layout is adjusted to fit the rotated content. /// This is the default.
Floating(Radians),
/// The element will be solid while rotating. The layout will be adjusted to fit
/// the rotated content.
/// ///
/// This allows you to rotate an image and have the layout adjust to fit the new /// This allows you to rotate an image and have the layout adjust to fit the new
/// size of the image. /// size of the image.
Change, Solid(Radians),
} }
impl RotationLayout { impl Rotation {
/// Applies the rotation to the layout while respecting the [`RotationLayout`] strategy. /// Returns the angle of the [`Rotation`] in [`Radians`].
/// The rotation is given in radians. pub fn radians(self) -> Radians {
pub fn apply_to_size(&self, size: Size, rotation: f32) -> Size {
match self { match self {
Self::Keep => size, Rotation::Floating(radians) | Rotation::Solid(radians) => radians,
Self::Change => Size { }
width: (size.width * rotation.cos()).abs() }
+ (size.height * rotation.sin()).abs(),
height: (size.width * rotation.sin()).abs() /// Rotates the given [`Size`].
+ (size.height * rotation.cos()).abs(), pub fn apply(self, size: Size) -> Size {
}, match self {
Self::Floating(_) => size,
Self::Solid(rotation) => {
let radians = f32::from(rotation);
Size {
width: (size.width * radians.cos()).abs()
+ (size.height * radians.sin()).abs(),
height: (size.width * radians.sin()).abs()
+ (size.height * radians.cos()).abs(),
}
}
} }
} }
} }
impl Default for Rotation {
fn default() -> Self {
Self::Floating(Radians(0.0))
}
}
impl From<Radians> for Rotation {
fn from(radians: Radians) -> Self {
Self::Floating(radians)
}
}
impl From<f32> for Rotation {
fn from(radians: f32) -> Self {
Self::Floating(Radians(radians))
}
}

View file

@ -113,3 +113,17 @@ where
} }
} }
} }
impl<T> std::ops::Mul<Vector<T>> for Size<T>
where
T: std::ops::Mul<Output = T> + Copy,
{
type Output = Size<T>;
fn mul(self, scale: Vector<T>) -> Self::Output {
Size {
width: self.width * scale.x,
height: self.height * scale.y,
}
}
}

View file

@ -1,5 +1,5 @@
//! Load and draw vector graphics. //! Load and draw vector graphics.
use crate::{Color, Rectangle, Size}; use crate::{Color, Radians, Rectangle, Size};
use rustc_hash::FxHasher; use rustc_hash::FxHasher;
use std::borrow::Cow; use std::borrow::Cow;
@ -100,7 +100,6 @@ pub trait Renderer: crate::Renderer {
handle: Handle, handle: Handle,
color: Option<Color>, color: Option<Color>,
bounds: Rectangle, bounds: Rectangle,
rotation: f32, rotation: Radians,
scale: Size,
); );
} }

View file

@ -18,6 +18,9 @@ impl<T> Vector<T> {
impl Vector { impl Vector {
/// The zero [`Vector`]. /// The zero [`Vector`].
pub const ZERO: Self = Self::new(0.0, 0.0); pub const ZERO: Self = Self::new(0.0, 0.0);
/// The unit [`Vector`].
pub const UNIT: Self = Self::new(0.0, 0.0);
} }
impl<T> std::ops::Add for Vector<T> impl<T> std::ops::Add for Vector<T>

View file

@ -2,7 +2,7 @@
#[cfg(feature = "image")] #[cfg(feature = "image")]
pub use ::image as image_rs; pub use ::image as image_rs;
use crate::core::{image, svg, Color, Rectangle, Size}; use crate::core::{image, svg, Color, Radians, Rectangle};
/// A raster or vector image. /// A raster or vector image.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -19,10 +19,7 @@ pub enum Image {
bounds: Rectangle, bounds: Rectangle,
/// The rotation of the image in radians /// The rotation of the image in radians
rotation: f32, rotation: Radians,
/// The scale of the image after rotation
scale: Size,
}, },
/// A vector image. /// A vector image.
Vector { Vector {
@ -36,10 +33,7 @@ pub enum Image {
bounds: Rectangle, bounds: Rectangle,
/// The rotation of the image in radians /// The rotation of the image in radians
rotation: f32, rotation: Radians,
/// The scale of the image after rotation
scale: Size,
}, },
} }

View file

@ -3,7 +3,7 @@ use crate::core::image;
use crate::core::renderer; use crate::core::renderer;
use crate::core::svg; use crate::core::svg;
use crate::core::{ use crate::core::{
self, Background, Color, Point, Rectangle, Size, Transformation, self, Background, Color, Point, Radians, Rectangle, Size, Transformation,
}; };
use crate::graphics; use crate::graphics;
use crate::graphics::compositor; use crate::graphics::compositor;
@ -154,13 +154,12 @@ where
handle: Self::Handle, handle: Self::Handle,
filter_method: image::FilterMethod, filter_method: image::FilterMethod,
bounds: Rectangle, bounds: Rectangle,
rotation: f32, rotation: Radians,
scale: Size,
) { ) {
delegate!( delegate!(
self, self,
renderer, renderer,
renderer.draw_image(handle, filter_method, bounds, rotation, scale) renderer.draw_image(handle, filter_method, bounds, rotation)
); );
} }
} }
@ -179,13 +178,12 @@ where
handle: svg::Handle, handle: svg::Handle,
color: Option<Color>, color: Option<Color>,
bounds: Rectangle, bounds: Rectangle,
rotation: f32, rotation: Radians,
scale: Size,
) { ) {
delegate!( delegate!(
self, self,
renderer, renderer,
renderer.draw_svg(handle, color, bounds, rotation, scale) renderer.draw_svg(handle, color, bounds, rotation)
); );
} }
} }

View file

@ -200,8 +200,8 @@ pub use crate::core::gradient;
pub use crate::core::theme; pub use crate::core::theme;
pub use crate::core::{ pub use crate::core::{
Alignment, Background, Border, Color, ContentFit, Degrees, Gradient, Alignment, Background, Border, Color, ContentFit, Degrees, Gradient,
Length, Padding, Pixels, Point, Radians, Rectangle, RotationLayout, Shadow, Length, Padding, Pixels, Point, Radians, Rectangle, Rotation, Shadow, Size,
Size, Theme, Transformation, Vector, Theme, Transformation, Vector,
}; };
pub mod clipboard { pub mod clipboard {

View file

@ -551,7 +551,6 @@ impl Engine {
filter_method, filter_method,
bounds, bounds,
rotation, rotation,
scale,
} => { } => {
let physical_bounds = *bounds * transformation; let physical_bounds = *bounds * transformation;
@ -563,11 +562,13 @@ impl Engine {
.then_some(clip_mask as &_); .then_some(clip_mask as &_);
let center = physical_bounds.center(); let center = physical_bounds.center();
let transform = into_transform(transformation) let radians = f32::from(*rotation);
.post_rotate_at(rotation.to_degrees(), center.x, center.y)
.post_translate(-center.x, -center.y) let transform = into_transform(transformation).post_rotate_at(
.post_scale(scale.width, scale.height) radians.to_degrees(),
.post_translate(center.x, center.y); center.x,
center.y,
);
self.raster_pipeline.draw( self.raster_pipeline.draw(
handle, handle,
@ -584,7 +585,6 @@ impl Engine {
color, color,
bounds, bounds,
rotation, rotation,
scale,
} => { } => {
let physical_bounds = *bounds * transformation; let physical_bounds = *bounds * transformation;
@ -596,11 +596,13 @@ impl Engine {
.then_some(clip_mask as &_); .then_some(clip_mask as &_);
let center = physical_bounds.center(); let center = physical_bounds.center();
let transform = into_transform(transformation) let radians = f32::from(*rotation);
.post_rotate_at(rotation.to_degrees(), center.x, center.y)
.post_translate(-center.x, -center.y) let transform = into_transform(transformation).post_rotate_at(
.post_scale(scale.width, scale.height) radians.to_degrees(),
.post_translate(center.x, center.y); center.x,
center.y,
);
self.vector_pipeline.draw( self.vector_pipeline.draw(
handle, handle,

View file

@ -1,5 +1,5 @@
use crate::core::{ use crate::core::{
image, renderer::Quad, svg, Background, Color, Point, Rectangle, Size, image, renderer::Quad, svg, Background, Color, Point, Radians, Rectangle,
Transformation, Transformation,
}; };
use crate::graphics::damage; use crate::graphics::damage;
@ -121,15 +121,13 @@ impl Layer {
filter_method: image::FilterMethod, filter_method: image::FilterMethod,
bounds: Rectangle, bounds: Rectangle,
transformation: Transformation, transformation: Transformation,
rotation: f32, rotation: Radians,
scale: Size,
) { ) {
let image = Image::Raster { let image = Image::Raster {
handle, handle,
filter_method, filter_method,
bounds: bounds * transformation, bounds: bounds * transformation,
rotation, rotation,
scale,
}; };
self.images.push(image); self.images.push(image);
@ -141,15 +139,13 @@ impl Layer {
color: Option<Color>, color: Option<Color>,
bounds: Rectangle, bounds: Rectangle,
transformation: Transformation, transformation: Transformation,
rotation: f32, rotation: Radians,
scale: Size,
) { ) {
let svg = Image::Vector { let svg = Image::Vector {
handle, handle,
color, color,
bounds: bounds * transformation, bounds: bounds * transformation,
rotation, rotation,
scale,
}; };
self.images.push(svg); self.images.push(svg);

View file

@ -29,7 +29,7 @@ pub use geometry::Geometry;
use crate::core::renderer; use crate::core::renderer;
use crate::core::{ use crate::core::{
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, Background, Color, Font, Pixels, Point, Radians, Rectangle, Transformation,
}; };
use crate::engine::Engine; use crate::engine::Engine;
use crate::graphics::compositor; use crate::graphics::compositor;
@ -377,8 +377,7 @@ impl core::image::Renderer for Renderer {
handle: Self::Handle, handle: Self::Handle,
filter_method: core::image::FilterMethod, filter_method: core::image::FilterMethod,
bounds: Rectangle, bounds: Rectangle,
rotation: f32, rotation: Radians,
scale: Size,
) { ) {
let (layer, transformation) = self.layers.current_mut(); let (layer, transformation) = self.layers.current_mut();
layer.draw_image( layer.draw_image(
@ -387,7 +386,6 @@ impl core::image::Renderer for Renderer {
bounds, bounds,
transformation, transformation,
rotation, rotation,
scale,
); );
} }
} }
@ -406,11 +404,10 @@ impl core::svg::Renderer for Renderer {
handle: core::svg::Handle, handle: core::svg::Handle,
color: Option<Color>, color: Option<Color>,
bounds: Rectangle, bounds: Rectangle,
rotation: f32, rotation: Radians,
scale: Size,
) { ) {
let (layer, transformation) = self.layers.current_mut(); let (layer, transformation) = self.layers.current_mut();
layer.draw_svg(handle, color, bounds, transformation, rotation, scale); layer.draw_svg(handle, color, bounds, transformation, rotation);
} }
} }

View file

@ -141,14 +141,12 @@ impl Pipeline {
2 => Float32x2, 2 => Float32x2,
// Rotation // Rotation
3 => Float32, 3 => Float32,
// Scale
4 => Float32x2,
// Atlas position // Atlas position
5 => Float32x2, 4 => Float32x2,
// Atlas scale // Atlas scale
6 => Float32x2, 5 => Float32x2,
// Layer // Layer
7 => Sint32, 6 => Sint32,
), ),
}], }],
}, },
@ -232,7 +230,6 @@ impl Pipeline {
filter_method, filter_method,
bounds, bounds,
rotation, rotation,
scale,
} => { } => {
if let Some(atlas_entry) = if let Some(atlas_entry) =
cache.upload_raster(device, encoder, handle) cache.upload_raster(device, encoder, handle)
@ -240,8 +237,7 @@ impl Pipeline {
add_instances( add_instances(
[bounds.x, bounds.y], [bounds.x, bounds.y],
[bounds.width, bounds.height], [bounds.width, bounds.height],
*rotation, f32::from(*rotation),
[scale.width, scale.height],
atlas_entry, atlas_entry,
match filter_method { match filter_method {
crate::core::image::FilterMethod::Nearest => { crate::core::image::FilterMethod::Nearest => {
@ -263,7 +259,6 @@ impl Pipeline {
color, color,
bounds, bounds,
rotation, rotation,
scale,
} => { } => {
let size = [bounds.width, bounds.height]; let size = [bounds.width, bounds.height];
@ -278,8 +273,7 @@ impl Pipeline {
add_instances( add_instances(
[bounds.x, bounds.y], [bounds.x, bounds.y],
size, size,
*rotation, f32::from(*rotation),
[scale.width, scale.height],
atlas_entry, atlas_entry,
nearest_instances, nearest_instances,
); );
@ -510,7 +504,6 @@ struct Instance {
_center: [f32; 2], _center: [f32; 2],
_size: [f32; 2], _size: [f32; 2],
_rotation: f32, _rotation: f32,
_scale: [f32; 2],
_position_in_atlas: [f32; 2], _position_in_atlas: [f32; 2],
_size_in_atlas: [f32; 2], _size_in_atlas: [f32; 2],
_layer: u32, _layer: u32,
@ -530,7 +523,6 @@ fn add_instances(
image_position: [f32; 2], image_position: [f32; 2],
image_size: [f32; 2], image_size: [f32; 2],
rotation: f32, rotation: f32,
scale: [f32; 2],
entry: &atlas::Entry, entry: &atlas::Entry,
instances: &mut Vec<Instance>, instances: &mut Vec<Instance>,
) { ) {
@ -546,7 +538,6 @@ fn add_instances(
center, center,
image_size, image_size,
rotation, rotation,
scale,
allocation, allocation,
instances, instances,
); );
@ -576,8 +567,7 @@ fn add_instances(
]; ];
add_instance( add_instance(
position, center, size, rotation, scale, allocation, position, center, size, rotation, allocation, instances,
instances,
); );
} }
} }
@ -590,7 +580,6 @@ fn add_instance(
center: [f32; 2], center: [f32; 2],
size: [f32; 2], size: [f32; 2],
rotation: f32, rotation: f32,
scale: [f32; 2],
allocation: &atlas::Allocation, allocation: &atlas::Allocation,
instances: &mut Vec<Instance>, instances: &mut Vec<Instance>,
) { ) {
@ -603,7 +592,6 @@ fn add_instance(
_center: center, _center: center,
_size: size, _size: size,
_rotation: rotation, _rotation: rotation,
_scale: scale,
_position_in_atlas: [ _position_in_atlas: [
(x as f32 + 0.5) / atlas::SIZE as f32, (x as f32 + 0.5) / atlas::SIZE as f32,
(y as f32 + 0.5) / atlas::SIZE as f32, (y as f32 + 0.5) / atlas::SIZE as f32,

View file

@ -1,5 +1,5 @@
use crate::core::{ use crate::core::{
renderer, Background, Color, Point, Rectangle, Size, Transformation, renderer, Background, Color, Point, Radians, Rectangle, Transformation,
}; };
use crate::graphics; use crate::graphics;
use crate::graphics::color; use crate::graphics::color;
@ -118,15 +118,13 @@ impl Layer {
filter_method: crate::core::image::FilterMethod, filter_method: crate::core::image::FilterMethod,
bounds: Rectangle, bounds: Rectangle,
transformation: Transformation, transformation: Transformation,
rotation: f32, rotation: Radians,
scale: Size,
) { ) {
let image = Image::Raster { let image = Image::Raster {
handle, handle,
filter_method, filter_method,
bounds: bounds * transformation, bounds: bounds * transformation,
rotation, rotation,
scale,
}; };
self.images.push(image); self.images.push(image);
@ -138,15 +136,13 @@ impl Layer {
color: Option<Color>, color: Option<Color>,
bounds: Rectangle, bounds: Rectangle,
transformation: Transformation, transformation: Transformation,
rotation: f32, rotation: Radians,
scale: Size,
) { ) {
let svg = Image::Vector { let svg = Image::Vector {
handle, handle,
color, color,
bounds: bounds * transformation, bounds: bounds * transformation,
rotation, rotation,
scale,
}; };
self.images.push(svg); self.images.push(svg);

View file

@ -61,7 +61,8 @@ pub use settings::Settings;
pub use geometry::Geometry; pub use geometry::Geometry;
use crate::core::{ use crate::core::{
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, Background, Color, Font, Pixels, Point, Radians, Rectangle, Size,
Transformation, Vector,
}; };
use crate::graphics::text::{Editor, Paragraph}; use crate::graphics::text::{Editor, Paragraph};
use crate::graphics::Viewport; use crate::graphics::Viewport;
@ -378,7 +379,6 @@ impl Renderer {
use crate::core::alignment; use crate::core::alignment;
use crate::core::text::Renderer as _; use crate::core::text::Renderer as _;
use crate::core::Renderer as _; use crate::core::Renderer as _;
use crate::core::Vector;
self.with_layer( self.with_layer(
Rectangle::with_size(viewport.logical_size()), Rectangle::with_size(viewport.logical_size()),
@ -517,8 +517,7 @@ impl core::image::Renderer for Renderer {
handle: Self::Handle, handle: Self::Handle,
filter_method: core::image::FilterMethod, filter_method: core::image::FilterMethod,
bounds: Rectangle, bounds: Rectangle,
rotation: f32, rotation: Radians,
scale: Size,
) { ) {
let (layer, transformation) = self.layers.current_mut(); let (layer, transformation) = self.layers.current_mut();
layer.draw_image( layer.draw_image(
@ -527,7 +526,6 @@ impl core::image::Renderer for Renderer {
bounds, bounds,
transformation, transformation,
rotation, rotation,
scale,
); );
} }
} }
@ -543,18 +541,10 @@ impl core::svg::Renderer for Renderer {
handle: core::svg::Handle, handle: core::svg::Handle,
color_filter: Option<Color>, color_filter: Option<Color>,
bounds: Rectangle, bounds: Rectangle,
rotation: f32, rotation: Radians,
scale: Size,
) { ) {
let (layer, transformation) = self.layers.current_mut(); let (layer, transformation) = self.layers.current_mut();
layer.draw_svg( layer.draw_svg(handle, color_filter, bounds, transformation, rotation);
handle,
color_filter,
bounds,
transformation,
rotation,
scale,
);
} }
} }

View file

@ -10,12 +10,11 @@ struct VertexInput {
@builtin(vertex_index) vertex_index: u32, @builtin(vertex_index) vertex_index: u32,
@location(0) pos: vec2<f32>, @location(0) pos: vec2<f32>,
@location(1) center: vec2<f32>, @location(1) center: vec2<f32>,
@location(2) image_size: vec2<f32>, @location(2) scale: vec2<f32>,
@location(3) rotation: f32, @location(3) rotation: f32,
@location(4) scale: vec2<f32>, @location(4) atlas_pos: vec2<f32>,
@location(5) atlas_pos: vec2<f32>, @location(5) atlas_scale: vec2<f32>,
@location(6) atlas_scale: vec2<f32>, @location(6) layer: i32,
@location(7) layer: i32,
} }
struct VertexOutput { struct VertexOutput {
@ -36,7 +35,7 @@ fn vs_main(input: VertexInput) -> VertexOutput {
out.layer = f32(input.layer); out.layer = f32(input.layer);
// Calculate the vertex position and move the center to the origin // Calculate the vertex position and move the center to the origin
v_pos = input.pos + v_pos * input.image_size - 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);
@ -48,16 +47,8 @@ fn vs_main(input: VertexInput) -> VertexOutput {
vec4<f32>(0.0, 0.0, 0.0, 1.0) vec4<f32>(0.0, 0.0, 0.0, 1.0)
); );
// Scale the image and then translate to the final position by moving the center to the position
let scale_translate = mat4x4<f32>(
vec4<f32>(input.scale.x, 0.0, 0.0, 0.0),
vec4<f32>(0.0, input.scale.y, 0.0, 0.0),
vec4<f32>(0.0, 0.0, 1.0, 0.0),
vec4<f32>(input.center, 0.0, 1.0)
);
// Calculate the final position of the vertex // Calculate the final position of the vertex
out.position = globals.transform * scale_translate * rotate * vec4<f32>(v_pos, 0.0, 1.0); out.position = globals.transform * (vec4<f32>(input.center, 0.0, 0.0) + rotate * vec4<f32>(v_pos, 0.0, 1.0));
return out; return out;
} }

View file

@ -1,6 +1,5 @@
//! Display images in your user interface. //! Display images in your user interface.
pub mod viewer; pub mod viewer;
use iced_renderer::core::{Point, RotationLayout};
pub use viewer::Viewer; pub use viewer::Viewer;
use crate::core::image; use crate::core::image;
@ -9,7 +8,8 @@ use crate::core::mouse;
use crate::core::renderer; use crate::core::renderer;
use crate::core::widget::Tree; use crate::core::widget::Tree;
use crate::core::{ use crate::core::{
ContentFit, Element, Layout, Length, Rectangle, Size, Widget, ContentFit, Element, Layout, Length, Point, Rectangle, Rotation, Size,
Vector, Widget,
}; };
pub use image::{FilterMethod, Handle}; pub use image::{FilterMethod, Handle};
@ -37,8 +37,7 @@ pub struct Image<Handle> {
height: Length, height: Length,
content_fit: ContentFit, content_fit: ContentFit,
filter_method: FilterMethod, filter_method: FilterMethod,
rotation: f32, rotation: Rotation,
rotation_layout: RotationLayout,
} }
impl<Handle> Image<Handle> { impl<Handle> Image<Handle> {
@ -48,10 +47,9 @@ impl<Handle> Image<Handle> {
handle: handle.into(), handle: handle.into(),
width: Length::Shrink, width: Length::Shrink,
height: Length::Shrink, height: Length::Shrink,
content_fit: ContentFit::Contain, content_fit: ContentFit::default(),
filter_method: FilterMethod::default(), filter_method: FilterMethod::default(),
rotation: 0.0, rotation: Rotation::default(),
rotation_layout: RotationLayout::Change,
} }
} }
@ -81,15 +79,9 @@ impl<Handle> Image<Handle> {
self self
} }
/// Rotates the [`Image`] by the given angle in radians. /// Applies the given [`Rotation`] to the [`Image`].
pub fn rotation(mut self, degrees: f32) -> Self { pub fn rotation(mut self, rotation: impl Into<Rotation>) -> Self {
self.rotation = degrees; self.rotation = rotation.into();
self
}
/// Sets the [`RotationLayout`] of the [`Image`].
pub fn rotation_layout(mut self, rotation_layout: RotationLayout) -> Self {
self.rotation_layout = rotation_layout;
self self
} }
} }
@ -102,8 +94,7 @@ pub fn layout<Renderer, Handle>(
width: Length, width: Length,
height: Length, height: Length,
content_fit: ContentFit, content_fit: ContentFit,
rotation: f32, rotation: Rotation,
rotation_layout: RotationLayout,
) -> layout::Node ) -> layout::Node
where where
Renderer: image::Renderer<Handle = Handle>, Renderer: image::Renderer<Handle = Handle>,
@ -114,7 +105,7 @@ where
Size::new(image_size.width as f32, image_size.height as f32); Size::new(image_size.width as f32, image_size.height as f32);
// The rotated size of the image // The rotated size of the image
let rotated_size = rotation_layout.apply_to_size(image_size, rotation); let rotated_size = rotation.apply(image_size);
// The size to be available to the widget prior to `Shrink`ing // The size to be available to the widget prior to `Shrink`ing
let raw_size = limits.resolve(width, height, rotated_size); let raw_size = limits.resolve(width, height, rotated_size);
@ -144,45 +135,44 @@ pub fn draw<Renderer, Handle>(
handle: &Handle, handle: &Handle,
content_fit: ContentFit, content_fit: ContentFit,
filter_method: FilterMethod, filter_method: FilterMethod,
rotation: f32, rotation: Rotation,
rotation_layout: RotationLayout,
) where ) where
Renderer: image::Renderer<Handle = Handle>, Renderer: image::Renderer<Handle = Handle>,
Handle: Clone, Handle: Clone,
{ {
let Size { width, height } = renderer.measure_image(handle); let Size { width, height } = renderer.measure_image(handle);
let image_size = Size::new(width as f32, height as f32); let image_size = Size::new(width as f32, height as f32);
let rotated_size = rotation_layout.apply_to_size(image_size, rotation); let rotated_size = rotation.apply(image_size);
let bounds = layout.bounds(); let bounds = layout.bounds();
let adjusted_fit = content_fit.fit(rotated_size, bounds.size()); let adjusted_fit = content_fit.fit(rotated_size, bounds.size());
let scale = Size::new(
let scale = Vector::new(
adjusted_fit.width / rotated_size.width, adjusted_fit.width / rotated_size.width,
adjusted_fit.height / rotated_size.height, adjusted_fit.height / rotated_size.height,
); );
let final_size = image_size * scale;
let position = match content_fit {
ContentFit::None => Point::new(
bounds.x + (rotated_size.width - adjusted_fit.width) / 2.0,
bounds.y + (rotated_size.height - adjusted_fit.height) / 2.0,
),
_ => Point::new(
bounds.center_x() - final_size.width / 2.0,
bounds.center_y() - final_size.height / 2.0,
),
};
let drawing_bounds = Rectangle::new(position, final_size);
let render = |renderer: &mut Renderer| { let render = |renderer: &mut Renderer| {
let position = match content_fit {
ContentFit::None => Point::new(
bounds.position().x
+ (rotated_size.width - image_size.width) / 2.0,
bounds.position().y
+ (rotated_size.height - image_size.height) / 2.0,
),
_ => Point::new(
bounds.center_x() - image_size.width / 2.0,
bounds.center_y() - image_size.height / 2.0,
),
};
let drawing_bounds = Rectangle::new(position, image_size);
renderer.draw_image( renderer.draw_image(
handle.clone(), handle.clone(),
filter_method, filter_method,
drawing_bounds, drawing_bounds,
rotation, rotation.radians(),
scale,
); );
}; };
@ -221,7 +211,6 @@ where
self.height, self.height,
self.content_fit, self.content_fit,
self.rotation, self.rotation,
self.rotation_layout,
) )
} }
@ -242,7 +231,6 @@ where
self.content_fit, self.content_fit,
self.filter_method, self.filter_method,
self.rotation, self.rotation,
self.rotation_layout,
); );
} }
} }

View file

@ -6,8 +6,8 @@ use crate::core::mouse;
use crate::core::renderer; use crate::core::renderer;
use crate::core::widget::tree::{self, Tree}; use crate::core::widget::tree::{self, Tree};
use crate::core::{ use crate::core::{
Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Clipboard, Element, Layout, Length, Pixels, Point, Radians, Rectangle,
Vector, Widget, Shell, Size, Vector, Widget,
}; };
/// A frame that displays an image with the ability to zoom in/out and pan. /// A frame that displays an image with the ability to zoom in/out and pan.
@ -341,8 +341,7 @@ where
y: bounds.y, y: bounds.y,
..Rectangle::with_size(image_size) ..Rectangle::with_size(image_size)
}, },
0.0, Radians(0.0),
Size::UNIT,
); );
}); });
}); });

View file

@ -5,8 +5,8 @@ use crate::core::renderer;
use crate::core::svg; use crate::core::svg;
use crate::core::widget::Tree; use crate::core::widget::Tree;
use crate::core::{ use crate::core::{
Color, ContentFit, Element, Layout, Length, Point, Rectangle, Color, ContentFit, Element, Layout, Length, Point, Rectangle, Rotation,
RotationLayout, Size, Theme, Widget, Size, Theme, Vector, Widget,
}; };
use std::path::PathBuf; use std::path::PathBuf;
@ -29,8 +29,7 @@ where
height: Length, height: Length,
content_fit: ContentFit, content_fit: ContentFit,
class: Theme::Class<'a>, class: Theme::Class<'a>,
rotation: f32, rotation: Rotation,
rotation_layout: RotationLayout,
} }
impl<'a, Theme> Svg<'a, Theme> impl<'a, Theme> Svg<'a, Theme>
@ -45,8 +44,7 @@ where
height: Length::Shrink, height: Length::Shrink,
content_fit: ContentFit::Contain, content_fit: ContentFit::Contain,
class: Theme::default(), class: Theme::default(),
rotation: 0.0, rotation: Rotation::default(),
rotation_layout: RotationLayout::Change,
} }
} }
@ -100,15 +98,9 @@ where
self self
} }
/// Rotates the [`Svg`] by the given angle in radians. /// Applies the given [`Rotation`] to the [`Svg`].
pub fn rotation(mut self, degrees: f32) -> Self { pub fn rotation(mut self, rotation: impl Into<Rotation>) -> Self {
self.rotation = degrees; self.rotation = rotation.into();
self
}
/// Sets the [`RotationLayout`] of the [`Svg`].
pub fn rotation_layout(mut self, rotation_layout: RotationLayout) -> Self {
self.rotation_layout = rotation_layout;
self self
} }
} }
@ -137,9 +129,7 @@ where
let image_size = Size::new(width as f32, height as f32); let image_size = Size::new(width as f32, height as f32);
// The rotated size of the svg // The rotated size of the svg
let rotated_size = self let rotated_size = self.rotation.apply(image_size);
.rotation_layout
.apply_to_size(image_size, self.rotation);
// The size to be available to the widget prior to `Shrink`ing // The size to be available to the widget prior to `Shrink`ing
let raw_size = limits.resolve(self.width, self.height, rotated_size); let raw_size = limits.resolve(self.width, self.height, rotated_size);
@ -174,49 +164,46 @@ where
) { ) {
let Size { width, height } = renderer.measure_svg(&self.handle); let Size { width, height } = renderer.measure_svg(&self.handle);
let image_size = Size::new(width as f32, height as f32); let image_size = Size::new(width as f32, height as f32);
let rotated_size = self let rotated_size = self.rotation.apply(image_size);
.rotation_layout
.apply_to_size(image_size, self.rotation);
let bounds = layout.bounds(); let bounds = layout.bounds();
let adjusted_fit = self.content_fit.fit(rotated_size, bounds.size()); let adjusted_fit = self.content_fit.fit(rotated_size, bounds.size());
let scale = Size::new( let scale = Vector::new(
adjusted_fit.width / rotated_size.width, adjusted_fit.width / rotated_size.width,
adjusted_fit.height / rotated_size.height, adjusted_fit.height / rotated_size.height,
); );
let final_size = image_size * scale;
let position = match self.content_fit {
ContentFit::None => Point::new(
bounds.x + (rotated_size.width - adjusted_fit.width) / 2.0,
bounds.y + (rotated_size.height - adjusted_fit.height) / 2.0,
),
_ => Point::new(
bounds.center_x() - final_size.width / 2.0,
bounds.center_y() - final_size.height / 2.0,
),
};
let drawing_bounds = Rectangle::new(position, final_size);
let is_mouse_over = cursor.is_over(bounds); let is_mouse_over = cursor.is_over(bounds);
let status = if is_mouse_over {
Status::Hovered
} else {
Status::Idle
};
let style = theme.style(&self.class, status);
let render = |renderer: &mut Renderer| { let render = |renderer: &mut Renderer| {
let position = match self.content_fit {
ContentFit::None => Point::new(
bounds.position().x
+ (rotated_size.width - image_size.width) / 2.0,
bounds.position().y
+ (rotated_size.height - image_size.height) / 2.0,
),
_ => Point::new(
bounds.center_x() - image_size.width / 2.0,
bounds.center_y() - image_size.height / 2.0,
),
};
let drawing_bounds = Rectangle::new(position, image_size);
let status = if is_mouse_over {
Status::Hovered
} else {
Status::Idle
};
let style = theme.style(&self.class, status);
renderer.draw_svg( renderer.draw_svg(
self.handle.clone(), self.handle.clone(),
style.color, style.color,
drawing_bounds, drawing_bounds,
self.rotation, self.rotation.radians(),
scale,
); );
}; };