summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/src/image.rs17
-rw-r--r--examples/tour/src/main.rs44
-rw-r--r--graphics/src/primitive.rs2
-rw-r--r--graphics/src/renderer.rs13
-rw-r--r--renderer/src/lib.rs9
-rw-r--r--tiny_skia/src/backend.rs16
-rw-r--r--tiny_skia/src/raster.rs12
-rw-r--r--wgpu/src/image.rs129
-rw-r--r--wgpu/src/layer.rs7
-rw-r--r--wgpu/src/layer/image.rs3
-rw-r--r--widget/src/image.rs29
-rw-r--r--widget/src/image/viewer.rs5
12 files changed, 232 insertions, 54 deletions
diff --git a/core/src/image.rs b/core/src/image.rs
index 85d9d475..e9675316 100644
--- a/core/src/image.rs
+++ b/core/src/image.rs
@@ -164,6 +164,16 @@ impl std::fmt::Debug for Data {
}
}
+/// Image filtering strategy.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
+pub enum FilterMethod {
+ /// Bilinear interpolation.
+ #[default]
+ Linear,
+ /// Nearest neighbor.
+ Nearest,
+}
+
/// A [`Renderer`] that can render raster graphics.
///
/// [renderer]: crate::renderer
@@ -178,5 +188,10 @@ pub trait Renderer: crate::Renderer {
/// Draws an image with the given [`Handle`] and inside the provided
/// `bounds`.
- fn draw(&mut self, handle: Self::Handle, bounds: Rectangle);
+ fn draw(
+ &mut self,
+ handle: Self::Handle,
+ filter_method: FilterMethod,
+ bounds: Rectangle,
+ );
}
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
index d46e40d1..7003d8ae 100644
--- a/examples/tour/src/main.rs
+++ b/examples/tour/src/main.rs
@@ -1,4 +1,4 @@
-use iced::alignment;
+use iced::alignment::{self, Alignment};
use iced::theme;
use iced::widget::{
checkbox, column, container, horizontal_space, image, radio, row,
@@ -126,7 +126,10 @@ impl Steps {
Step::Toggler {
can_continue: false,
},
- Step::Image { width: 300 },
+ Step::Image {
+ width: 300,
+ filter_method: image::FilterMethod::Linear,
+ },
Step::Scrollable,
Step::TextInput {
value: String::new(),
@@ -195,6 +198,7 @@ enum Step {
},
Image {
width: u16,
+ filter_method: image::FilterMethod,
},
Scrollable,
TextInput {
@@ -215,6 +219,7 @@ pub enum StepMessage {
TextColorChanged(Color),
LanguageSelected(Language),
ImageWidthChanged(u16),
+ ImageUseNearestToggled(bool),
InputChanged(String),
ToggleSecureInput(bool),
ToggleTextInputIcon(bool),
@@ -265,6 +270,15 @@ impl<'a> Step {
*width = new_width;
}
}
+ StepMessage::ImageUseNearestToggled(use_nearest) => {
+ if let Step::Image { filter_method, .. } = self {
+ *filter_method = if use_nearest {
+ image::FilterMethod::Nearest
+ } else {
+ image::FilterMethod::Linear
+ };
+ }
+ }
StepMessage::InputChanged(new_value) => {
if let Step::TextInput { value, .. } = self {
*value = new_value;
@@ -330,7 +344,10 @@ impl<'a> Step {
Step::Toggler { can_continue } => Self::toggler(*can_continue),
Step::Slider { value } => Self::slider(*value),
Step::Text { size, color } => Self::text(*size, *color),
- Step::Image { width } => Self::image(*width),
+ Step::Image {
+ width,
+ filter_method,
+ } => Self::image(*width, *filter_method),
Step::RowsAndColumns { layout, spacing } => {
Self::rows_and_columns(*layout, *spacing)
}
@@ -525,16 +542,25 @@ impl<'a> Step {
)
}
- fn image(width: u16) -> Column<'a, StepMessage> {
+ fn image(
+ width: u16,
+ filter_method: image::FilterMethod,
+ ) -> Column<'a, StepMessage> {
Self::container("Image")
.push("An image that tries to keep its aspect ratio.")
- .push(ferris(width))
+ .push(ferris(width, filter_method))
.push(slider(100..=500, width, StepMessage::ImageWidthChanged))
.push(
text(format!("Width: {width} px"))
.width(Length::Fill)
.horizontal_alignment(alignment::Horizontal::Center),
)
+ .push(checkbox(
+ "Use nearest interpolation",
+ filter_method == image::FilterMethod::Nearest,
+ StepMessage::ImageUseNearestToggled,
+ ))
+ .align_items(Alignment::Center)
}
fn scrollable() -> Column<'a, StepMessage> {
@@ -555,7 +581,7 @@ impl<'a> Step {
.horizontal_alignment(alignment::Horizontal::Center),
)
.push(vertical_space(4096))
- .push(ferris(300))
+ .push(ferris(300, image::FilterMethod::Linear))
.push(
text("You made it!")
.width(Length::Fill)
@@ -646,7 +672,10 @@ impl<'a> Step {
}
}
-fn ferris<'a>(width: u16) -> Container<'a, StepMessage> {
+fn ferris<'a>(
+ width: u16,
+ filter_method: image::FilterMethod,
+) -> Container<'a, StepMessage> {
container(
// This should go away once we unify resource loading on native
// platforms
@@ -655,6 +684,7 @@ fn ferris<'a>(width: u16) -> Container<'a, StepMessage> {
} else {
image(format!("{}/images/ferris.png", env!("CARGO_MANIFEST_DIR")))
}
+ .filter_method(filter_method)
.width(width),
)
.width(Length::Fill)
diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs
index ce0b734b..4ed512c1 100644
--- a/graphics/src/primitive.rs
+++ b/graphics/src/primitive.rs
@@ -68,6 +68,8 @@ pub enum Primitive<T> {
Image {
/// The handle of the image
handle: image::Handle,
+ /// The filter method of the image
+ filter_method: image::FilterMethod,
/// The bounds of the image
bounds: Rectangle,
},
diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs
index 93fff3b7..d7613e36 100644
--- a/graphics/src/renderer.rs
+++ b/graphics/src/renderer.rs
@@ -215,8 +215,17 @@ where
self.backend().dimensions(handle)
}
- fn draw(&mut self, handle: image::Handle, bounds: Rectangle) {
- self.primitives.push(Primitive::Image { handle, bounds });
+ fn draw(
+ &mut self,
+ handle: image::Handle,
+ filter_method: image::FilterMethod,
+ bounds: Rectangle,
+ ) {
+ self.primitives.push(Primitive::Image {
+ handle,
+ filter_method,
+ bounds,
+ });
}
}
diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs
index cc81c6e2..43f9794b 100644
--- a/renderer/src/lib.rs
+++ b/renderer/src/lib.rs
@@ -214,8 +214,13 @@ impl<T> crate::core::image::Renderer for Renderer<T> {
delegate!(self, renderer, renderer.dimensions(handle))
}
- fn draw(&mut self, handle: crate::core::image::Handle, bounds: Rectangle) {
- delegate!(self, renderer, renderer.draw(handle, bounds));
+ fn draw(
+ &mut self,
+ handle: crate::core::image::Handle,
+ filter_method: crate::core::image::FilterMethod,
+ bounds: Rectangle,
+ ) {
+ delegate!(self, renderer, renderer.draw(handle, filter_method, bounds));
}
}
diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs
index 3c6fe288..f2905b00 100644
--- a/tiny_skia/src/backend.rs
+++ b/tiny_skia/src/backend.rs
@@ -445,7 +445,11 @@ impl Backend {
);
}
#[cfg(feature = "image")]
- Primitive::Image { handle, bounds } => {
+ Primitive::Image {
+ handle,
+ filter_method,
+ bounds,
+ } => {
let physical_bounds = (*bounds + translation) * scale_factor;
if !clip_bounds.intersects(&physical_bounds) {
@@ -461,8 +465,14 @@ impl Backend {
)
.post_scale(scale_factor, scale_factor);
- self.raster_pipeline
- .draw(handle, *bounds, pixels, transform, clip_mask);
+ self.raster_pipeline.draw(
+ handle,
+ *filter_method,
+ *bounds,
+ pixels,
+ transform,
+ clip_mask,
+ );
}
#[cfg(not(feature = "image"))]
Primitive::Image { .. } => {
diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs
index d13b1167..5f17ae60 100644
--- a/tiny_skia/src/raster.rs
+++ b/tiny_skia/src/raster.rs
@@ -28,6 +28,7 @@ impl Pipeline {
pub fn draw(
&mut self,
handle: &raster::Handle,
+ filter_method: raster::FilterMethod,
bounds: Rectangle,
pixels: &mut tiny_skia::PixmapMut<'_>,
transform: tiny_skia::Transform,
@@ -39,12 +40,21 @@ impl Pipeline {
let transform = transform.pre_scale(width_scale, height_scale);
+ let quality = match filter_method {
+ raster::FilterMethod::Linear => {
+ tiny_skia::FilterQuality::Bilinear
+ }
+ raster::FilterMethod::Nearest => {
+ tiny_skia::FilterQuality::Nearest
+ }
+ };
+
pixels.draw_pixmap(
(bounds.x / width_scale) as i32,
(bounds.y / height_scale) as i32,
image,
&tiny_skia::PixmapPaint {
- quality: tiny_skia::FilterQuality::Bilinear,
+ quality,
..Default::default()
},
transform,
diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs
index 553ba330..b78802c7 100644
--- a/wgpu/src/image.rs
+++ b/wgpu/src/image.rs
@@ -37,7 +37,8 @@ pub struct Pipeline {
pipeline: wgpu::RenderPipeline,
vertices: wgpu::Buffer,
indices: wgpu::Buffer,
- sampler: wgpu::Sampler,
+ nearest_sampler: wgpu::Sampler,
+ linear_sampler: wgpu::Sampler,
texture: wgpu::BindGroup,
texture_version: usize,
texture_atlas: Atlas,
@@ -51,16 +52,16 @@ pub struct Pipeline {
#[derive(Debug)]
struct Layer {
uniforms: wgpu::Buffer,
- constants: wgpu::BindGroup,
- instances: Buffer<Instance>,
- instance_count: usize,
+ nearest: Data,
+ linear: Data,
}
impl Layer {
fn new(
device: &wgpu::Device,
constant_layout: &wgpu::BindGroupLayout,
- sampler: &wgpu::Sampler,
+ nearest_sampler: &wgpu::Sampler,
+ linear_sampler: &wgpu::Sampler,
) -> Self {
let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("iced_wgpu::image uniforms buffer"),
@@ -69,6 +70,59 @@ impl Layer {
mapped_at_creation: false,
});
+ let nearest =
+ Data::new(device, constant_layout, nearest_sampler, &uniforms);
+
+ let linear =
+ Data::new(device, constant_layout, linear_sampler, &uniforms);
+
+ Self {
+ uniforms,
+ nearest,
+ linear,
+ }
+ }
+
+ fn prepare(
+ &mut self,
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ nearest_instances: &[Instance],
+ linear_instances: &[Instance],
+ transformation: Transformation,
+ ) {
+ queue.write_buffer(
+ &self.uniforms,
+ 0,
+ bytemuck::bytes_of(&Uniforms {
+ transform: transformation.into(),
+ }),
+ );
+
+ self.nearest.upload(device, queue, nearest_instances);
+ self.linear.upload(device, queue, linear_instances);
+ }
+
+ fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
+ self.nearest.render(render_pass);
+ self.linear.render(render_pass);
+ }
+}
+
+#[derive(Debug)]
+struct Data {
+ constants: wgpu::BindGroup,
+ instances: Buffer<Instance>,
+ instance_count: usize,
+}
+
+impl Data {
+ pub fn new(
+ device: &wgpu::Device,
+ constant_layout: &wgpu::BindGroupLayout,
+ sampler: &wgpu::Sampler,
+ uniforms: &wgpu::Buffer,
+ ) -> Self {
let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::image constants bind group"),
layout: constant_layout,
@@ -77,7 +131,7 @@ impl Layer {
binding: 0,
resource: wgpu::BindingResource::Buffer(
wgpu::BufferBinding {
- buffer: &uniforms,
+ buffer: uniforms,
offset: 0,
size: None,
},
@@ -98,28 +152,18 @@ impl Layer {
);
Self {
- uniforms,
constants,
instances,
instance_count: 0,
}
}
- fn prepare(
+ fn upload(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
instances: &[Instance],
- transformation: Transformation,
) {
- queue.write_buffer(
- &self.uniforms,
- 0,
- bytemuck::bytes_of(&Uniforms {
- transform: transformation.into(),
- }),
- );
-
let _ = self.instances.resize(device, instances.len());
let _ = self.instances.write(queue, 0, instances);
@@ -142,12 +186,22 @@ impl Pipeline {
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
use wgpu::util::DeviceExt;
- let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+ let nearest_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+ address_mode_u: wgpu::AddressMode::ClampToEdge,
+ address_mode_v: wgpu::AddressMode::ClampToEdge,
+ address_mode_w: wgpu::AddressMode::ClampToEdge,
+ min_filter: wgpu::FilterMode::Nearest,
+ mag_filter: wgpu::FilterMode::Nearest,
+ mipmap_filter: wgpu::FilterMode::Nearest,
+ ..Default::default()
+ });
+
+ let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
- mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
+ mag_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Linear,
..Default::default()
});
@@ -312,7 +366,8 @@ impl Pipeline {
pipeline,
vertices,
indices,
- sampler,
+ nearest_sampler,
+ linear_sampler,
texture,
texture_version: texture_atlas.layer_count(),
texture_atlas,
@@ -355,7 +410,8 @@ impl Pipeline {
#[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Image", "DRAW").entered();
- let instances: &mut Vec<Instance> = &mut Vec::new();
+ let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
+ let linear_instances: &mut Vec<Instance> = &mut Vec::new();
#[cfg(feature = "image")]
let mut raster_cache = self.raster_cache.borrow_mut();
@@ -366,7 +422,11 @@ impl Pipeline {
for image in images {
match &image {
#[cfg(feature = "image")]
- layer::Image::Raster { handle, bounds } => {
+ layer::Image::Raster {
+ handle,
+ filter_method,
+ bounds,
+ } => {
if let Some(atlas_entry) = raster_cache.upload(
device,
encoder,
@@ -377,7 +437,12 @@ impl Pipeline {
[bounds.x, bounds.y],
[bounds.width, bounds.height],
atlas_entry,
- instances,
+ match filter_method {
+ image::FilterMethod::Nearest => {
+ nearest_instances
+ }
+ image::FilterMethod::Linear => linear_instances,
+ },
);
}
}
@@ -405,7 +470,7 @@ impl Pipeline {
[bounds.x, bounds.y],
size,
atlas_entry,
- instances,
+ nearest_instances,
);
}
}
@@ -414,7 +479,7 @@ impl Pipeline {
}
}
- if instances.is_empty() {
+ if nearest_instances.is_empty() && linear_instances.is_empty() {
return;
}
@@ -442,12 +507,20 @@ impl Pipeline {
self.layers.push(Layer::new(
device,
&self.constant_layout,
- &self.sampler,
+ &self.nearest_sampler,
+ &self.linear_sampler,
));
}
let layer = &mut self.layers[self.prepare_layer];
- layer.prepare(device, queue, instances, transformation);
+
+ layer.prepare(
+ device,
+ queue,
+ nearest_instances,
+ linear_instances,
+ transformation,
+ );
self.prepare_layer += 1;
}
@@ -524,7 +597,7 @@ struct Instance {
}
impl Instance {
- pub const INITIAL: usize = 1_000;
+ pub const INITIAL: usize = 20;
}
#[repr(C)]
diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs
index b251538e..286801e6 100644
--- a/wgpu/src/layer.rs
+++ b/wgpu/src/layer.rs
@@ -186,11 +186,16 @@ impl<'a> Layer<'a> {
layer.quads.add(quad, background);
}
- Primitive::Image { handle, bounds } => {
+ Primitive::Image {
+ handle,
+ filter_method,
+ bounds,
+ } => {
let layer = &mut layers[current_layer];
layer.images.push(Image::Raster {
handle: handle.clone(),
+ filter_method: *filter_method,
bounds: *bounds + translation,
});
}
diff --git a/wgpu/src/layer/image.rs b/wgpu/src/layer/image.rs
index 0de589f8..facbe192 100644
--- a/wgpu/src/layer/image.rs
+++ b/wgpu/src/layer/image.rs
@@ -10,6 +10,9 @@ pub enum Image {
/// The handle of a raster image.
handle: image::Handle,
+ /// The filter method of a raster image.
+ filter_method: image::FilterMethod,
+
/// The bounds of the image.
bounds: Rectangle,
},
diff --git a/widget/src/image.rs b/widget/src/image.rs
index a0e89920..67699102 100644
--- a/widget/src/image.rs
+++ b/widget/src/image.rs
@@ -13,7 +13,7 @@ use crate::core::{
use std::hash::Hash;
-pub use image::Handle;
+pub use image::{FilterMethod, Handle};
/// Creates a new [`Viewer`] with the given image `Handle`.
pub fn viewer<Handle>(handle: Handle) -> Viewer<Handle> {
@@ -37,6 +37,7 @@ pub struct Image<Handle> {
width: Length,
height: Length,
content_fit: ContentFit,
+ filter_method: FilterMethod,
}
impl<Handle> Image<Handle> {
@@ -47,6 +48,7 @@ impl<Handle> Image<Handle> {
width: Length::Shrink,
height: Length::Shrink,
content_fit: ContentFit::Contain,
+ filter_method: FilterMethod::default(),
}
}
@@ -65,11 +67,15 @@ impl<Handle> Image<Handle> {
/// Sets the [`ContentFit`] of the [`Image`].
///
/// Defaults to [`ContentFit::Contain`]
- pub fn content_fit(self, content_fit: ContentFit) -> Self {
- Self {
- content_fit,
- ..self
- }
+ pub fn content_fit(mut self, content_fit: ContentFit) -> Self {
+ self.content_fit = content_fit;
+ self
+ }
+
+ /// Sets the [`FilterMethod`] of the [`Image`].
+ pub fn filter_method(mut self, filter_method: FilterMethod) -> Self {
+ self.filter_method = filter_method;
+ self
}
}
@@ -119,6 +125,7 @@ pub fn draw<Renderer, Handle>(
layout: Layout<'_>,
handle: &Handle,
content_fit: ContentFit,
+ filter_method: FilterMethod,
) where
Renderer: image::Renderer<Handle = Handle>,
Handle: Clone + Hash,
@@ -141,7 +148,7 @@ pub fn draw<Renderer, Handle>(
..bounds
};
- renderer.draw(handle.clone(), drawing_bounds + offset);
+ renderer.draw(handle.clone(), filter_method, drawing_bounds + offset);
};
if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height
@@ -191,7 +198,13 @@ where
_cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
- draw(renderer, layout, &self.handle, self.content_fit);
+ draw(
+ renderer,
+ layout,
+ &self.handle,
+ self.content_fit,
+ self.filter_method,
+ );
}
}
diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs
index 44624fc8..68015ba8 100644
--- a/widget/src/image/viewer.rs
+++ b/widget/src/image/viewer.rs
@@ -22,19 +22,21 @@ pub struct Viewer<Handle> {
max_scale: f32,
scale_step: f32,
handle: Handle,
+ filter_method: image::FilterMethod,
}
impl<Handle> Viewer<Handle> {
/// Creates a new [`Viewer`] with the given [`State`].
pub fn new(handle: Handle) -> Self {
Viewer {
+ handle,
padding: 0.0,
width: Length::Shrink,
height: Length::Shrink,
min_scale: 0.25,
max_scale: 10.0,
scale_step: 0.10,
- handle,
+ filter_method: image::FilterMethod::default(),
}
}
@@ -329,6 +331,7 @@ where
image::Renderer::draw(
renderer,
self.handle.clone(),
+ self.filter_method,
Rectangle {
x: bounds.x,
y: bounds.y,