Add border radius support for image
This commit is contained in:
parent
c896cd8d31
commit
44e68aa4b6
15 changed files with 256 additions and 188 deletions
|
|
@ -241,9 +241,9 @@ impl From<u8> for Radius {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<u16> for Radius {
|
||||
fn from(w: u16) -> Self {
|
||||
Self::from(f32::from(w))
|
||||
impl From<u32> for Radius {
|
||||
fn from(w: u32) -> Self {
|
||||
Self::from(w as f32)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
//! Load and draw raster graphics.
|
||||
pub use bytes::Bytes;
|
||||
|
||||
use crate::border;
|
||||
use crate::{Radians, Rectangle, Size};
|
||||
|
||||
use rustc_hash::FxHasher;
|
||||
|
|
@ -15,6 +16,16 @@ pub struct Image<H = Handle> {
|
|||
/// The handle of the image.
|
||||
pub handle: H,
|
||||
|
||||
/// The clip bounds of the [`Image`].
|
||||
///
|
||||
/// Anything outside this [`Rectangle`] will not be drawn.
|
||||
pub clip_bounds: Rectangle,
|
||||
|
||||
/// The border radius of the [`Image`].
|
||||
///
|
||||
/// Currently, this will only be applied to the `clip_bounds`.
|
||||
pub border_radius: border::Radius,
|
||||
|
||||
/// The filter method of the image.
|
||||
pub filter_method: FilterMethod,
|
||||
|
||||
|
|
@ -38,6 +49,8 @@ impl Image<Handle> {
|
|||
pub fn new(handle: impl Into<Handle>) -> Self {
|
||||
Self {
|
||||
handle: handle.into(),
|
||||
clip_bounds: Rectangle::INFINITE,
|
||||
border_radius: border::Radius::default(),
|
||||
filter_method: FilterMethod::default(),
|
||||
rotation: Radians(0.0),
|
||||
opacity: 1.0,
|
||||
|
|
|
|||
|
|
@ -81,7 +81,9 @@ impl Image {
|
|||
.url
|
||||
.split("/")
|
||||
.map(|part| {
|
||||
if part.starts_with("width=") {
|
||||
if part.starts_with("width=")
|
||||
|| part.starts_with("original=")
|
||||
{
|
||||
format!("width={}", width * 2) // High DPI
|
||||
} else {
|
||||
part.to_owned()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ mod civitai;
|
|||
use crate::civitai::{Bytes, Error, Id, Image, Rgba, Size};
|
||||
|
||||
use iced::animation;
|
||||
use iced::border;
|
||||
use iced::time::{Instant, milliseconds};
|
||||
use iced::widget::{
|
||||
button, container, float, grid, image, mouse_area, opaque, scrollable,
|
||||
|
|
@ -283,7 +284,8 @@ fn card<'a>(
|
|||
image(allocation.handle())
|
||||
.width(Fill)
|
||||
.content_fit(ContentFit::Cover)
|
||||
.opacity(thumbnail.fade_in.interpolate(0.0, 1.0, now)),
|
||||
.opacity(thumbnail.fade_in.interpolate(0.0, 1.0, now))
|
||||
.border_radius(BORDER_RADIUS),
|
||||
)
|
||||
.scale(thumbnail.zoom.interpolate(1.0, 1.1, now))
|
||||
.translate(move |bounds, viewport| {
|
||||
|
|
@ -298,7 +300,7 @@ fn card<'a>(
|
|||
blur_radius: thumbnail.zoom.interpolate(0.0, 20.0, now),
|
||||
..Shadow::default()
|
||||
},
|
||||
..float::Style::default()
|
||||
shadow_border_radius: border::radius(BORDER_RADIUS),
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
|
|
@ -309,7 +311,8 @@ fn card<'a>(
|
|||
let blurhash = image(&blurhash.handle)
|
||||
.width(Fill)
|
||||
.content_fit(ContentFit::Cover)
|
||||
.opacity(blurhash.fade_in.interpolate(0.0, 1.0, now));
|
||||
.opacity(blurhash.fade_in.interpolate(0.0, 1.0, now))
|
||||
.border_radius(BORDER_RADIUS);
|
||||
|
||||
stack![blurhash, thumbnail].into()
|
||||
} else {
|
||||
|
|
@ -319,7 +322,7 @@ fn card<'a>(
|
|||
space::horizontal().into()
|
||||
};
|
||||
|
||||
let card = mouse_area(container(image).style(container::dark))
|
||||
let card = mouse_area(container(image).style(rounded))
|
||||
.on_enter(Message::ThumbnailHovered(metadata.id, true))
|
||||
.on_exit(Message::ThumbnailHovered(metadata.id, false));
|
||||
|
||||
|
|
@ -342,7 +345,7 @@ fn card<'a>(
|
|||
}
|
||||
|
||||
fn placeholder<'a>() -> Element<'a, Message> {
|
||||
container(space()).style(container::dark).into()
|
||||
container(space()).style(rounded).into()
|
||||
}
|
||||
|
||||
enum Preview {
|
||||
|
|
@ -554,10 +557,8 @@ impl Viewer {
|
|||
}
|
||||
|
||||
fn to_rgba(bytes: Bytes) -> Task<image::Handle> {
|
||||
use tokio::task;
|
||||
|
||||
Task::future(async move {
|
||||
task::spawn_blocking(move || {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
match ::image::load_from_memory(bytes.as_slice()) {
|
||||
Ok(image) => {
|
||||
let rgba = image.to_rgba8();
|
||||
|
|
@ -575,3 +576,9 @@ fn to_rgba(bytes: Bytes) -> Task<image::Handle> {
|
|||
.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
fn rounded(theme: &Theme) -> container::Style {
|
||||
container::dark(theme).border(border::rounded(BORDER_RADIUS))
|
||||
}
|
||||
|
||||
const BORDER_RADIUS: u32 = 20;
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ impl<T: bytemuck::Pod> Buffer<T> {
|
|||
}
|
||||
|
||||
pub fn resize(&mut self, device: &wgpu::Device, new_count: usize) -> bool {
|
||||
let new_size = (std::mem::size_of::<T>() * new_count) as u64;
|
||||
let new_size = next_copy_size::<T>(new_count);
|
||||
|
||||
if self.size < new_size {
|
||||
self.raw = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
|
|
|
|||
|
|
@ -16,13 +16,15 @@ pub const MAX_SIZE: u32 = 2048;
|
|||
use crate::core::Size;
|
||||
use crate::graphics::color;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Atlas {
|
||||
size: u32,
|
||||
backend: wgpu::Backend,
|
||||
texture: wgpu::Texture,
|
||||
texture_view: wgpu::TextureView,
|
||||
texture_bind_group: wgpu::BindGroup,
|
||||
texture_bind_group: Arc<wgpu::BindGroup>,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
layers: Vec<Layer>,
|
||||
}
|
||||
|
|
@ -95,13 +97,13 @@ impl Atlas {
|
|||
backend,
|
||||
texture,
|
||||
texture_view,
|
||||
texture_bind_group,
|
||||
texture_bind_group: Arc::new(texture_bind_group),
|
||||
texture_layout,
|
||||
layers,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind_group(&self) -> &wgpu::BindGroup {
|
||||
pub fn bind_group(&self) -> &Arc<wgpu::BindGroup> {
|
||||
&self.texture_bind_group
|
||||
}
|
||||
|
||||
|
|
@ -466,7 +468,7 @@ impl Atlas {
|
|||
});
|
||||
|
||||
self.texture_bind_group =
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
Arc::new(device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::image texture atlas bind group"),
|
||||
layout: &self.texture_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
|
|
@ -475,6 +477,6 @@ impl Atlas {
|
|||
&self.texture_view,
|
||||
),
|
||||
}],
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ use std::collections::HashMap;
|
|||
#[cfg(feature = "image")]
|
||||
use std::sync::mpsc;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct Cache {
|
||||
atlas: Atlas,
|
||||
#[cfg(feature = "image")]
|
||||
|
|
@ -127,7 +129,7 @@ impl Cache {
|
|||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
handle: &core::image::Handle,
|
||||
) -> Option<(&atlas::Entry, &wgpu::BindGroup)> {
|
||||
) -> Option<(&atlas::Entry, &Arc<wgpu::BindGroup>)> {
|
||||
use crate::image::raster::Memory;
|
||||
|
||||
self.receive();
|
||||
|
|
@ -198,9 +200,9 @@ impl Cache {
|
|||
belt: &mut wgpu::util::StagingBelt,
|
||||
handle: &core::svg::Handle,
|
||||
color: Option<core::Color>,
|
||||
size: [f32; 2],
|
||||
size: Size,
|
||||
scale: f32,
|
||||
) -> Option<(&atlas::Entry, &wgpu::BindGroup)> {
|
||||
) -> Option<(&atlas::Entry, &Arc<wgpu::BindGroup>)> {
|
||||
// TODO: Concurrency
|
||||
self.vector
|
||||
.upload(
|
||||
|
|
@ -321,6 +323,7 @@ fn load_image<'a>(
|
|||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
#[derive(Debug)]
|
||||
enum Job {
|
||||
Load(core::image::Handle),
|
||||
Upload {
|
||||
|
|
@ -329,7 +332,7 @@ enum Job {
|
|||
width: u32,
|
||||
height: u32,
|
||||
},
|
||||
Drop(wgpu::BindGroup),
|
||||
Drop(Arc<wgpu::BindGroup>),
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
|
|
@ -337,7 +340,7 @@ enum Work {
|
|||
Upload {
|
||||
handle: core::image::Handle,
|
||||
entry: atlas::Entry,
|
||||
bind_group: wgpu::BindGroup,
|
||||
bind_group: Arc<wgpu::BindGroup>,
|
||||
},
|
||||
Error {
|
||||
handle: core::image::Handle,
|
||||
|
|
|
|||
|
|
@ -10,12 +10,14 @@ mod raster;
|
|||
mod vector;
|
||||
|
||||
use crate::Buffer;
|
||||
use crate::core::border;
|
||||
use crate::core::{Rectangle, Size, Transformation};
|
||||
use crate::graphics::Shell;
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use crate::graphics::Image;
|
||||
|
||||
|
|
@ -131,24 +133,26 @@ impl Pipeline {
|
|||
array_stride: mem::size_of::<Instance>() as u64,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &wgpu::vertex_attr_array!(
|
||||
// Position
|
||||
0 => Float32x2,
|
||||
// Center
|
||||
1 => Float32x2,
|
||||
// Scale
|
||||
2 => Float32x2,
|
||||
0 => Float32x2,
|
||||
// Clip bounds
|
||||
1 => Float32x4,
|
||||
// Border radius
|
||||
2 => Float32x4,
|
||||
// Tile
|
||||
3 => Float32x4,
|
||||
// Rotation
|
||||
3 => Float32,
|
||||
// Opacity
|
||||
4 => Float32,
|
||||
// Opacity
|
||||
5 => Float32,
|
||||
// Atlas position
|
||||
5 => Float32x2,
|
||||
// Atlas scale
|
||||
6 => Float32x2,
|
||||
// Atlas scale
|
||||
7 => Float32x2,
|
||||
// Layer
|
||||
7 => Sint32,
|
||||
8 => Sint32,
|
||||
// Snap
|
||||
8 => Uint32,
|
||||
9 => Uint32,
|
||||
),
|
||||
}],
|
||||
compilation_options:
|
||||
|
|
@ -221,6 +225,8 @@ impl Pipeline {
|
|||
pub struct State {
|
||||
layers: Vec<Layer>,
|
||||
prepare_layer: usize,
|
||||
nearest_instances: Vec<Instance>,
|
||||
linear_instances: Vec<Instance>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
|
|
@ -249,11 +255,8 @@ impl State {
|
|||
}
|
||||
|
||||
let layer = &mut self.layers[self.prepare_layer];
|
||||
layer.prepare(device, encoder, belt, transformation, scale);
|
||||
|
||||
let mut atlas = None;
|
||||
let nearest_instances = &mut Vec::new();
|
||||
let linear_instances = &mut Vec::new();
|
||||
let mut atlas: Option<Arc<wgpu::BindGroup>> = None;
|
||||
|
||||
for image in images {
|
||||
match &image {
|
||||
|
|
@ -268,34 +271,30 @@ impl State {
|
|||
}
|
||||
Some(atlas) if atlas != bind_group => {
|
||||
layer.push(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
atlas,
|
||||
nearest_instances,
|
||||
linear_instances,
|
||||
&self.nearest_instances,
|
||||
&self.linear_instances,
|
||||
);
|
||||
|
||||
*atlas = bind_group.clone();
|
||||
nearest_instances.clear();
|
||||
linear_instances.clear();
|
||||
*atlas = Arc::clone(bind_group);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
add_instances(
|
||||
[bounds.x, bounds.y],
|
||||
[bounds.width, bounds.height],
|
||||
*bounds,
|
||||
image.clip_bounds,
|
||||
image.border_radius,
|
||||
f32::from(image.rotation),
|
||||
image.opacity,
|
||||
image.snap,
|
||||
atlas_entry,
|
||||
match image.filter_method {
|
||||
crate::core::image::FilterMethod::Nearest => {
|
||||
nearest_instances
|
||||
&mut self.nearest_instances
|
||||
}
|
||||
crate::core::image::FilterMethod::Linear => {
|
||||
linear_instances
|
||||
&mut self.linear_instances
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
@ -306,8 +305,6 @@ impl State {
|
|||
|
||||
#[cfg(feature = "svg")]
|
||||
Image::Vector(svg, bounds) => {
|
||||
let size = [bounds.width, bounds.height];
|
||||
|
||||
if let Some((atlas_entry, bind_group)) = cache
|
||||
.upload_vector(
|
||||
device,
|
||||
|
|
@ -315,7 +312,7 @@ impl State {
|
|||
belt,
|
||||
&svg.handle,
|
||||
svg.color,
|
||||
size,
|
||||
bounds.size(),
|
||||
scale,
|
||||
)
|
||||
{
|
||||
|
|
@ -325,29 +322,25 @@ impl State {
|
|||
}
|
||||
Some(atlas) if atlas != bind_group => {
|
||||
layer.push(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
atlas,
|
||||
nearest_instances,
|
||||
linear_instances,
|
||||
&self.nearest_instances,
|
||||
&self.linear_instances,
|
||||
);
|
||||
|
||||
*atlas = bind_group.clone();
|
||||
nearest_instances.clear();
|
||||
linear_instances.clear();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
add_instances(
|
||||
[bounds.x, bounds.y],
|
||||
size,
|
||||
*bounds,
|
||||
Rectangle::INFINITE,
|
||||
border::radius(0),
|
||||
f32::from(svg.rotation),
|
||||
svg.opacity,
|
||||
true,
|
||||
atlas_entry,
|
||||
nearest_instances,
|
||||
&mut self.nearest_instances,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -356,18 +349,23 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
if !nearest_instances.is_empty() || !linear_instances.is_empty() {
|
||||
layer.push(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
&atlas.expect("atlas should be defined"),
|
||||
nearest_instances,
|
||||
linear_instances,
|
||||
);
|
||||
if let Some(atlas) = &atlas {
|
||||
layer.push(atlas, &self.nearest_instances, &self.linear_instances);
|
||||
}
|
||||
|
||||
layer.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
transformation,
|
||||
scale,
|
||||
&self.nearest_instances,
|
||||
&self.linear_instances,
|
||||
);
|
||||
|
||||
self.prepare_layer += 1;
|
||||
self.nearest_instances.clear();
|
||||
self.linear_instances.clear();
|
||||
}
|
||||
|
||||
pub fn render<'a>(
|
||||
|
|
@ -404,16 +402,17 @@ impl State {
|
|||
struct Layer {
|
||||
uniforms: wgpu::Buffer,
|
||||
instances: Buffer<Instance>,
|
||||
total: usize,
|
||||
nearest: Vec<Group>,
|
||||
nearest_layout: wgpu::BindGroup,
|
||||
nearest_total: usize,
|
||||
linear: Vec<Group>,
|
||||
linear_layout: wgpu::BindGroup,
|
||||
linear_total: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Group {
|
||||
atlas: wgpu::BindGroup,
|
||||
atlas: Arc<wgpu::BindGroup>,
|
||||
instance_count: usize,
|
||||
}
|
||||
|
||||
|
|
@ -489,11 +488,12 @@ impl Layer {
|
|||
Self {
|
||||
uniforms,
|
||||
instances,
|
||||
total: 0,
|
||||
nearest: Vec::new(),
|
||||
nearest_layout,
|
||||
nearest_total: 0,
|
||||
linear: Vec::new(),
|
||||
linear_layout,
|
||||
linear_total: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -504,6 +504,8 @@ impl Layer {
|
|||
belt: &mut wgpu::util::StagingBelt,
|
||||
transformation: Transformation,
|
||||
scale_factor: f32,
|
||||
nearest: &[Instance],
|
||||
linear: &[Instance],
|
||||
) {
|
||||
let uniforms = Uniforms {
|
||||
transform: transformation.into(),
|
||||
|
|
@ -521,41 +523,48 @@ impl Layer {
|
|||
device,
|
||||
)
|
||||
.copy_from_slice(bytes);
|
||||
|
||||
let _ = self
|
||||
.instances
|
||||
.resize(device, self.nearest_total + self.linear_total);
|
||||
|
||||
let mut offset = 0;
|
||||
|
||||
if !nearest.is_empty() {
|
||||
offset += self.instances.write(device, encoder, belt, 0, nearest);
|
||||
}
|
||||
|
||||
if !linear.is_empty() {
|
||||
let _ = self.instances.write(device, encoder, belt, offset, linear);
|
||||
}
|
||||
}
|
||||
|
||||
fn push(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
atlas: &wgpu::BindGroup,
|
||||
atlas: &Arc<wgpu::BindGroup>,
|
||||
nearest: &[Instance],
|
||||
linear: &[Instance],
|
||||
) {
|
||||
let new = nearest.len() + linear.len();
|
||||
|
||||
let _ = self.instances.resize(device, self.total + new);
|
||||
|
||||
if !nearest.is_empty() {
|
||||
self.total += self
|
||||
.instances
|
||||
.write(device, encoder, belt, self.total, nearest);
|
||||
let new_nearest = nearest.len() - self.nearest_total;
|
||||
|
||||
if new_nearest > 0 {
|
||||
self.nearest.push(Group {
|
||||
atlas: atlas.clone(),
|
||||
instance_count: nearest.len(),
|
||||
instance_count: new_nearest,
|
||||
});
|
||||
|
||||
self.nearest_total = nearest.len();
|
||||
}
|
||||
|
||||
if !linear.is_empty() {
|
||||
self.total += self
|
||||
.instances
|
||||
.write(device, encoder, belt, self.total, linear);
|
||||
let new_linear = linear.len() - self.linear_total;
|
||||
|
||||
if new_linear > 0 {
|
||||
self.linear.push(Group {
|
||||
atlas: atlas.clone(),
|
||||
instance_count: linear.len(),
|
||||
instance_count: new_linear,
|
||||
});
|
||||
|
||||
self.linear_total = linear.len();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -568,7 +577,7 @@ impl Layer {
|
|||
render_pass.set_bind_group(0, &self.nearest_layout, &[]);
|
||||
|
||||
for group in &self.nearest {
|
||||
render_pass.set_bind_group(1, &group.atlas, &[]);
|
||||
render_pass.set_bind_group(1, group.atlas.as_ref(), &[]);
|
||||
render_pass
|
||||
.draw(0..6, offset..offset + group.instance_count as u32);
|
||||
|
||||
|
|
@ -580,7 +589,7 @@ impl Layer {
|
|||
render_pass.set_bind_group(0, &self.linear_layout, &[]);
|
||||
|
||||
for group in &self.linear {
|
||||
render_pass.set_bind_group(1, &group.atlas, &[]);
|
||||
render_pass.set_bind_group(1, group.atlas.as_ref(), &[]);
|
||||
render_pass
|
||||
.draw(0..6, offset..offset + group.instance_count as u32);
|
||||
|
||||
|
|
@ -590,19 +599,21 @@ impl Layer {
|
|||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.instances.clear();
|
||||
self.nearest.clear();
|
||||
self.nearest_total = 0;
|
||||
|
||||
self.linear.clear();
|
||||
self.total = 0;
|
||||
self.linear_total = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
struct Instance {
|
||||
_position: [f32; 2],
|
||||
_center: [f32; 2],
|
||||
_size: [f32; 2],
|
||||
_clip_bounds: [f32; 4],
|
||||
_border_radius: [f32; 4],
|
||||
_tile: [f32; 4],
|
||||
_rotation: f32,
|
||||
_opacity: f32,
|
||||
_position_in_atlas: [f32; 2],
|
||||
|
|
@ -626,8 +637,9 @@ struct Uniforms {
|
|||
}
|
||||
|
||||
fn add_instances(
|
||||
image_position: [f32; 2],
|
||||
image_size: [f32; 2],
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
border_radius: border::Radius,
|
||||
rotation: f32,
|
||||
opacity: f32,
|
||||
snap: bool,
|
||||
|
|
@ -635,16 +647,26 @@ fn add_instances(
|
|||
instances: &mut Vec<Instance>,
|
||||
) {
|
||||
let center = [
|
||||
image_position[0] + image_size[0] / 2.0,
|
||||
image_position[1] + image_size[1] / 2.0,
|
||||
bounds.x + bounds.width / 2.0,
|
||||
bounds.y + bounds.height / 2.0,
|
||||
];
|
||||
|
||||
let clip_bounds = [
|
||||
clip_bounds.x,
|
||||
clip_bounds.y,
|
||||
clip_bounds.width,
|
||||
clip_bounds.height,
|
||||
];
|
||||
|
||||
let border_radius = border_radius.into();
|
||||
|
||||
match entry {
|
||||
atlas::Entry::Contiguous(allocation) => {
|
||||
add_instance(
|
||||
image_position,
|
||||
center,
|
||||
image_size,
|
||||
clip_bounds,
|
||||
border_radius,
|
||||
[bounds.x, bounds.y, bounds.width, bounds.height],
|
||||
rotation,
|
||||
opacity,
|
||||
snap,
|
||||
|
|
@ -653,32 +675,35 @@ fn add_instances(
|
|||
);
|
||||
}
|
||||
atlas::Entry::Fragmented { fragments, size } => {
|
||||
let scaling_x = image_size[0] / size.width as f32;
|
||||
let scaling_y = image_size[1] / size.height as f32;
|
||||
let scaling_x = bounds.width / size.width as f32;
|
||||
let scaling_y = bounds.height / size.height as f32;
|
||||
|
||||
for fragment in fragments {
|
||||
let allocation = &fragment.allocation;
|
||||
|
||||
let [x, y] = image_position;
|
||||
let (fragment_x, fragment_y) = fragment.position;
|
||||
|
||||
let Size {
|
||||
width: fragment_width,
|
||||
height: fragment_height,
|
||||
} = allocation.size();
|
||||
|
||||
let position = [
|
||||
x + fragment_x as f32 * scaling_x,
|
||||
y + fragment_y as f32 * scaling_y,
|
||||
];
|
||||
|
||||
let size = [
|
||||
let tile = [
|
||||
bounds.x + fragment_x as f32 * scaling_x,
|
||||
bounds.y + fragment_y as f32 * scaling_y,
|
||||
fragment_width as f32 * scaling_x,
|
||||
fragment_height as f32 * scaling_y,
|
||||
];
|
||||
|
||||
add_instance(
|
||||
position, center, size, rotation, opacity, snap,
|
||||
allocation, instances,
|
||||
center,
|
||||
clip_bounds,
|
||||
border_radius,
|
||||
tile,
|
||||
rotation,
|
||||
opacity,
|
||||
snap,
|
||||
allocation,
|
||||
instances,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -687,9 +712,10 @@ fn add_instances(
|
|||
|
||||
#[inline]
|
||||
fn add_instance(
|
||||
position: [f32; 2],
|
||||
center: [f32; 2],
|
||||
size: [f32; 2],
|
||||
clip_bounds: [f32; 4],
|
||||
border_radius: [f32; 4],
|
||||
tile: [f32; 4],
|
||||
rotation: f32,
|
||||
opacity: f32,
|
||||
snap: bool,
|
||||
|
|
@ -702,9 +728,10 @@ fn add_instance(
|
|||
let atlas_size = allocation.atlas_size();
|
||||
|
||||
let instance = Instance {
|
||||
_position: position,
|
||||
_center: center,
|
||||
_size: size,
|
||||
_clip_bounds: clip_bounds,
|
||||
_border_radius: border_radius,
|
||||
_tile: tile,
|
||||
_rotation: rotation,
|
||||
_opacity: opacity,
|
||||
_position_in_atlas: [
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::graphics::image::image_rs;
|
|||
use crate::image::atlas::{self, Atlas};
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::sync::Weak;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
type Image = image_rs::ImageBuffer<image_rs::Rgba<u8>, image::Bytes>;
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ pub enum Memory {
|
|||
/// Storage entry
|
||||
Device {
|
||||
entry: atlas::Entry,
|
||||
bind_group: Option<wgpu::BindGroup>,
|
||||
bind_group: Option<Arc<wgpu::BindGroup>>,
|
||||
allocation: Option<Weak<image::Memory>>,
|
||||
},
|
||||
/// Image not found
|
||||
|
|
@ -90,7 +90,7 @@ impl Cache {
|
|||
pub fn trim(
|
||||
&mut self,
|
||||
atlas: &mut Atlas,
|
||||
on_drop: impl Fn(wgpu::BindGroup),
|
||||
on_drop: impl Fn(Arc<wgpu::BindGroup>),
|
||||
) {
|
||||
// Only trim if new entries have landed in the `Cache`
|
||||
if !self.should_trim {
|
||||
|
|
|
|||
|
|
@ -97,15 +97,15 @@ impl Cache {
|
|||
belt: &mut wgpu::util::StagingBelt,
|
||||
handle: &svg::Handle,
|
||||
color: Option<Color>,
|
||||
[width, height]: [f32; 2],
|
||||
size: Size,
|
||||
scale: f32,
|
||||
atlas: &mut Atlas,
|
||||
) -> Option<&atlas::Entry> {
|
||||
let id = handle.id();
|
||||
|
||||
let (width, height) = (
|
||||
(scale * width).ceil() as u32,
|
||||
(scale * height).ceil() as u32,
|
||||
(scale * size.width).ceil() as u32,
|
||||
(scale * size.height).ceil() as u32,
|
||||
);
|
||||
|
||||
let color = color.map(Color::into_rgba8);
|
||||
|
|
|
|||
|
|
@ -143,7 +143,13 @@ impl Layer {
|
|||
bounds: Rectangle,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
let image = Image::Raster(image, bounds * transformation);
|
||||
let image = Image::Raster(
|
||||
core::Image {
|
||||
clip_bounds: image.clip_bounds * transformation,
|
||||
..image
|
||||
},
|
||||
bounds * transformation,
|
||||
);
|
||||
|
||||
self.images.push(image);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -311,8 +311,10 @@ impl Renderer {
|
|||
self.layers.merge();
|
||||
|
||||
for layer in self.layers.iter() {
|
||||
let clip_bounds = layer.bounds * scale_factor;
|
||||
|
||||
if physical_bounds
|
||||
.intersection(&(layer.bounds * scale_factor))
|
||||
.intersection(&clip_bounds)
|
||||
.and_then(Rectangle::snap)
|
||||
.is_none()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,22 +9,25 @@ struct Globals {
|
|||
|
||||
struct VertexInput {
|
||||
@builtin(vertex_index) vertex_index: u32,
|
||||
@location(0) pos: vec2<f32>,
|
||||
@location(1) center: vec2<f32>,
|
||||
@location(2) scale: vec2<f32>,
|
||||
@location(3) rotation: f32,
|
||||
@location(4) opacity: f32,
|
||||
@location(5) atlas_pos: vec2<f32>,
|
||||
@location(6) atlas_scale: vec2<f32>,
|
||||
@location(7) layer: i32,
|
||||
@location(8) snap: u32,
|
||||
@location(0) center: vec2<f32>,
|
||||
@location(1) clip_bounds: vec4<f32>,
|
||||
@location(2) border_radius: vec4<f32>,
|
||||
@location(3) tile: vec4<f32>,
|
||||
@location(4) rotation: f32,
|
||||
@location(5) opacity: f32,
|
||||
@location(6) atlas_pos: vec2<f32>,
|
||||
@location(7) atlas_scale: vec2<f32>,
|
||||
@location(8) layer: i32,
|
||||
@location(9) snap: u32,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
@location(1) layer: f32, // this should be an i32, but naga currently reads that as requiring interpolation.
|
||||
@location(2) opacity: f32,
|
||||
@location(0) clip_bounds: vec4<f32>,
|
||||
@location(1) border_radius: vec4<f32>,
|
||||
@location(2) uv: vec2<f32>,
|
||||
@location(3) layer: f32, // this should be an i32, but naga currently reads that as requiring interpolation.
|
||||
@location(4) opacity: f32,
|
||||
}
|
||||
|
||||
@vertex
|
||||
|
|
@ -39,8 +42,11 @@ fn vs_main(input: VertexInput) -> VertexOutput {
|
|||
out.layer = f32(input.layer);
|
||||
out.opacity = input.opacity;
|
||||
|
||||
let tile = input.tile;
|
||||
let center = input.center;
|
||||
|
||||
// Calculate the vertex position and move the center to the origin
|
||||
v_pos = input.pos + v_pos * input.scale - input.center;
|
||||
v_pos = tile.xy + v_pos * tile.zw - center;
|
||||
|
||||
// Apply the rotation around the center of the image
|
||||
let cos_rot = cos(input.rotation);
|
||||
|
|
@ -53,19 +59,39 @@ fn vs_main(input: VertexInput) -> VertexOutput {
|
|||
);
|
||||
|
||||
// Calculate the final position of the vertex
|
||||
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));
|
||||
out.position = vec4(vec2(globals.scale_factor), 1.0, 1.0) * (vec4<f32>(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;
|
||||
out.clip_bounds = globals.scale_factor * input.clip_bounds;
|
||||
out.border_radius = globals.scale_factor * input.border_radius;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// Sample the texture at the given UV coordinate and layer.
|
||||
return textureSample(u_texture, u_sampler, input.uv, i32(input.layer)) * vec4<f32>(1.0, 1.0, 1.0, input.opacity);
|
||||
let fragment = input.position.xy;
|
||||
let position = input.clip_bounds.xy;
|
||||
let scale = input.clip_bounds.zw;
|
||||
|
||||
let d = rounded_box_sdf(
|
||||
2.0 * (fragment - position - scale / 2.0),
|
||||
scale,
|
||||
input.border_radius * 2.0,
|
||||
);
|
||||
|
||||
let antialias: f32 = clamp(0.5 - d, 0.0, 1.0);
|
||||
|
||||
return textureSample(u_texture, u_sampler, input.uv, i32(input.layer)) * vec4<f32>(1.0, 1.0, 1.0, antialias * input.opacity);
|
||||
}
|
||||
|
||||
fn rounded_box_sdf(p: vec2<f32>, size: vec2<f32>, corners: vec4<f32>) -> f32 {
|
||||
var box_half = select(corners.yz, corners.xw, p.x > 0.0);
|
||||
var corner = select(box_half.y, box_half.x, p.y > 0.0);
|
||||
var q = abs(p) - size + corner;
|
||||
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - corner;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
pub mod viewer;
|
||||
pub use viewer::Viewer;
|
||||
|
||||
use crate::core::border;
|
||||
use crate::core::image;
|
||||
use crate::core::layout;
|
||||
use crate::core::mouse;
|
||||
|
|
@ -59,6 +60,7 @@ pub struct Image<Handle = image::Handle> {
|
|||
width: Length,
|
||||
height: Length,
|
||||
crop: Option<Rectangle<u32>>,
|
||||
border_radius: border::Radius,
|
||||
content_fit: ContentFit,
|
||||
filter_method: FilterMethod,
|
||||
rotation: Rotation,
|
||||
|
|
@ -75,6 +77,7 @@ impl<Handle> Image<Handle> {
|
|||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
crop: None,
|
||||
border_radius: border::Radius::default(),
|
||||
content_fit: ContentFit::default(),
|
||||
filter_method: FilterMethod::default(),
|
||||
rotation: Rotation::default(),
|
||||
|
|
@ -164,6 +167,18 @@ impl<Handle> Image<Handle> {
|
|||
self.crop = Some(region);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`border::Radius`] of the [`Image`].
|
||||
///
|
||||
/// Currently, it will only be applied around the rectangular bounding box
|
||||
/// of the [`Image`].
|
||||
pub fn border_radius(
|
||||
mut self,
|
||||
border_radius: impl Into<border::Radius>,
|
||||
) -> Self {
|
||||
self.border_radius = border_radius.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the layout of an [`Image`].
|
||||
|
|
@ -281,10 +296,6 @@ where
|
|||
Rectangle::new(position + crop_offset, final_size)
|
||||
}
|
||||
|
||||
fn must_clip(bounds: Rectangle, drawing_bounds: Rectangle) -> bool {
|
||||
drawing_bounds.width > bounds.width || drawing_bounds.height > bounds.height
|
||||
}
|
||||
|
||||
fn crop(size: Size<u32>, region: Option<Rectangle<u32>>) -> Size<f32> {
|
||||
if let Some(region) = region {
|
||||
Size::new(
|
||||
|
|
@ -300,9 +311,9 @@ fn crop(size: Size<u32>, region: Option<Rectangle<u32>>) -> Size<f32> {
|
|||
pub fn draw<Renderer, Handle>(
|
||||
renderer: &mut Renderer,
|
||||
layout: Layout<'_>,
|
||||
viewport: &Rectangle,
|
||||
handle: &Handle,
|
||||
crop: Option<Rectangle<u32>>,
|
||||
border_radius: border::Radius,
|
||||
content_fit: ContentFit,
|
||||
filter_method: FilterMethod,
|
||||
rotation: Rotation,
|
||||
|
|
@ -323,45 +334,11 @@ pub fn draw<Renderer, Handle>(
|
|||
scale,
|
||||
);
|
||||
|
||||
if must_clip(bounds, drawing_bounds) {
|
||||
if let Some(bounds) = bounds.intersection(viewport) {
|
||||
renderer.with_layer(bounds, |renderer| {
|
||||
render(
|
||||
renderer,
|
||||
handle,
|
||||
filter_method,
|
||||
rotation,
|
||||
opacity,
|
||||
drawing_bounds,
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
render(
|
||||
renderer,
|
||||
handle,
|
||||
filter_method,
|
||||
rotation,
|
||||
opacity,
|
||||
drawing_bounds,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn render<Renderer, Handle>(
|
||||
renderer: &mut Renderer,
|
||||
handle: &Handle,
|
||||
filter_method: FilterMethod,
|
||||
rotation: Rotation,
|
||||
opacity: f32,
|
||||
drawing_bounds: Rectangle,
|
||||
) where
|
||||
Renderer: image::Renderer<Handle = Handle>,
|
||||
Handle: Clone,
|
||||
{
|
||||
renderer.draw_image(
|
||||
image::Image {
|
||||
handle: handle.clone(),
|
||||
clip_bounds: bounds,
|
||||
border_radius,
|
||||
filter_method,
|
||||
rotation: rotation.radians(),
|
||||
opacity,
|
||||
|
|
@ -411,14 +388,14 @@ where
|
|||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
draw(
|
||||
renderer,
|
||||
layout,
|
||||
viewport,
|
||||
&self.handle,
|
||||
self.crop,
|
||||
self.border_radius,
|
||||
self.content_fit,
|
||||
self.filter_method,
|
||||
self.rotation,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
//! Zoom and pan on an image.
|
||||
use crate::core::border;
|
||||
use crate::core::image::{self, FilterMethod};
|
||||
use crate::core::layout;
|
||||
use crate::core::mouse;
|
||||
|
|
@ -347,6 +348,8 @@ where
|
|||
renderer.draw_image(
|
||||
Image {
|
||||
handle: self.handle.clone(),
|
||||
clip_bounds: Rectangle::INFINITE,
|
||||
border_radius: border::Radius::default(),
|
||||
filter_method: self.filter_method,
|
||||
rotation: Radians(0.0),
|
||||
opacity: 1.0,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue