Refactor texture image filtering

- Support only `Linear` or `Nearest`
- Simplify `Layer` groups
- Move `FilterMethod` to `Image` and `image::Viewer`
This commit is contained in:
Héctor Ramón Jiménez 2023-11-11 07:02:01 +01:00
parent 75c9afc608
commit a5125d6fea
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
12 changed files with 250 additions and 160 deletions

View file

@ -5,31 +5,11 @@ use std::hash::{Hash, Hasher as _};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
/// Image filter method
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FilterMethod {
/// Bilinear interpolation
#[default]
Linear,
/// Nearest Neighbor
Nearest,
}
/// Texture filter settings
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
pub struct TextureFilter {
/// Filter for scaling the image down.
pub min: FilterMethod,
/// Filter for scaling the image up.
pub mag: FilterMethod,
}
/// A handle of some image data. /// A handle of some image data.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Handle { pub struct Handle {
id: u64, id: u64,
data: Data, data: Data,
filter: TextureFilter,
} }
impl Handle { impl Handle {
@ -76,7 +56,6 @@ impl Handle {
Handle { Handle {
id: hasher.finish(), id: hasher.finish(),
data, data,
filter: TextureFilter::default(),
} }
} }
@ -89,17 +68,6 @@ impl Handle {
pub fn data(&self) -> &Data { pub fn data(&self) -> &Data {
&self.data &self.data
} }
/// Returns a reference to the [`TextureFilter`].
pub fn filter(&self) -> &TextureFilter {
&self.filter
}
/// Sets the texture filtering methods.
pub fn set_filter(mut self, filter: TextureFilter) -> Self {
self.filter = filter;
self
}
} }
impl<T> From<T> for Handle impl<T> From<T> for Handle
@ -196,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. /// A [`Renderer`] that can render raster graphics.
/// ///
/// [renderer]: crate::renderer /// [renderer]: crate::renderer
@ -210,5 +188,10 @@ pub trait Renderer: crate::Renderer {
/// Draws an image with the given [`Handle`] and inside the provided /// Draws an image with the given [`Handle`] and inside the provided
/// `bounds`. /// `bounds`.
fn draw(&mut self, handle: Self::Handle, bounds: Rectangle); fn draw(
&mut self,
handle: Self::Handle,
filter_method: FilterMethod,
bounds: Rectangle,
);
} }

View file

@ -1,4 +1,4 @@
use iced::alignment; use iced::alignment::{self, Alignment};
use iced::theme; use iced::theme;
use iced::widget::{ use iced::widget::{
checkbox, column, container, horizontal_space, image, radio, row, checkbox, column, container, horizontal_space, image, radio, row,
@ -126,7 +126,10 @@ impl Steps {
Step::Toggler { Step::Toggler {
can_continue: false, can_continue: false,
}, },
Step::Image { width: 300 }, Step::Image {
width: 300,
filter_method: image::FilterMethod::Linear,
},
Step::Scrollable, Step::Scrollable,
Step::TextInput { Step::TextInput {
value: String::new(), value: String::new(),
@ -195,6 +198,7 @@ enum Step {
}, },
Image { Image {
width: u16, width: u16,
filter_method: image::FilterMethod,
}, },
Scrollable, Scrollable,
TextInput { TextInput {
@ -215,6 +219,7 @@ pub enum StepMessage {
TextColorChanged(Color), TextColorChanged(Color),
LanguageSelected(Language), LanguageSelected(Language),
ImageWidthChanged(u16), ImageWidthChanged(u16),
ImageUseNearestToggled(bool),
InputChanged(String), InputChanged(String),
ToggleSecureInput(bool), ToggleSecureInput(bool),
ToggleTextInputIcon(bool), ToggleTextInputIcon(bool),
@ -265,6 +270,15 @@ impl<'a> Step {
*width = new_width; *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) => { StepMessage::InputChanged(new_value) => {
if let Step::TextInput { value, .. } = self { if let Step::TextInput { value, .. } = self {
*value = new_value; *value = new_value;
@ -330,7 +344,10 @@ impl<'a> Step {
Step::Toggler { can_continue } => Self::toggler(*can_continue), Step::Toggler { can_continue } => Self::toggler(*can_continue),
Step::Slider { value } => Self::slider(*value), Step::Slider { value } => Self::slider(*value),
Step::Text { size, color } => Self::text(*size, *color), 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 } => { Step::RowsAndColumns { layout, spacing } => {
Self::rows_and_columns(*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") Self::container("Image")
.push("An image that tries to keep its aspect ratio.") .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(slider(100..=500, width, StepMessage::ImageWidthChanged))
.push( .push(
text(format!("Width: {width} px")) text(format!("Width: {width} px"))
.width(Length::Fill) .width(Length::Fill)
.horizontal_alignment(alignment::Horizontal::Center), .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> { fn scrollable() -> Column<'a, StepMessage> {
@ -555,7 +581,7 @@ impl<'a> Step {
.horizontal_alignment(alignment::Horizontal::Center), .horizontal_alignment(alignment::Horizontal::Center),
) )
.push(vertical_space(4096)) .push(vertical_space(4096))
.push(ferris(300)) .push(ferris(300, image::FilterMethod::Linear))
.push( .push(
text("You made it!") text("You made it!")
.width(Length::Fill) .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( container(
// This should go away once we unify resource loading on native // This should go away once we unify resource loading on native
// platforms // platforms
@ -655,6 +684,7 @@ fn ferris<'a>(width: u16) -> Container<'a, StepMessage> {
} else { } else {
image(format!("{}/images/ferris.png", env!("CARGO_MANIFEST_DIR"))) image(format!("{}/images/ferris.png", env!("CARGO_MANIFEST_DIR")))
} }
.filter_method(filter_method)
.width(width), .width(width),
) )
.width(Length::Fill) .width(Length::Fill)

View file

@ -68,6 +68,8 @@ pub enum Primitive<T> {
Image { Image {
/// The handle of the image /// The handle of the image
handle: image::Handle, handle: image::Handle,
/// The filter method of the image
filter_method: image::FilterMethod,
/// The bounds of the image /// The bounds of the image
bounds: Rectangle, bounds: Rectangle,
}, },

View file

@ -215,8 +215,17 @@ where
self.backend().dimensions(handle) self.backend().dimensions(handle)
} }
fn draw(&mut self, handle: image::Handle, bounds: Rectangle) { fn draw(
self.primitives.push(Primitive::Image { handle, bounds }); &mut self,
handle: image::Handle,
filter_method: image::FilterMethod,
bounds: Rectangle,
) {
self.primitives.push(Primitive::Image {
handle,
filter_method,
bounds,
});
} }
} }

View file

@ -214,8 +214,13 @@ impl<T> crate::core::image::Renderer for Renderer<T> {
delegate!(self, renderer, renderer.dimensions(handle)) delegate!(self, renderer, renderer.dimensions(handle))
} }
fn draw(&mut self, handle: crate::core::image::Handle, bounds: Rectangle) { fn draw(
delegate!(self, renderer, renderer.draw(handle, bounds)); &mut self,
handle: crate::core::image::Handle,
filter_method: crate::core::image::FilterMethod,
bounds: Rectangle,
) {
delegate!(self, renderer, renderer.draw(handle, filter_method, bounds));
} }
} }

View file

@ -445,7 +445,11 @@ impl Backend {
); );
} }
#[cfg(feature = "image")] #[cfg(feature = "image")]
Primitive::Image { handle, bounds } => { Primitive::Image {
handle,
filter_method,
bounds,
} => {
let physical_bounds = (*bounds + translation) * scale_factor; let physical_bounds = (*bounds + translation) * scale_factor;
if !clip_bounds.intersects(&physical_bounds) { if !clip_bounds.intersects(&physical_bounds) {
@ -461,8 +465,14 @@ impl Backend {
) )
.post_scale(scale_factor, scale_factor); .post_scale(scale_factor, scale_factor);
self.raster_pipeline self.raster_pipeline.draw(
.draw(handle, *bounds, pixels, transform, clip_mask); handle,
*filter_method,
*bounds,
pixels,
transform,
clip_mask,
);
} }
#[cfg(not(feature = "image"))] #[cfg(not(feature = "image"))]
Primitive::Image { .. } => { Primitive::Image { .. } => {

View file

@ -28,6 +28,7 @@ impl Pipeline {
pub fn draw( pub fn draw(
&mut self, &mut self,
handle: &raster::Handle, handle: &raster::Handle,
filter_method: raster::FilterMethod,
bounds: Rectangle, bounds: Rectangle,
pixels: &mut tiny_skia::PixmapMut<'_>, pixels: &mut tiny_skia::PixmapMut<'_>,
transform: tiny_skia::Transform, transform: tiny_skia::Transform,
@ -39,7 +40,7 @@ impl Pipeline {
let transform = transform.pre_scale(width_scale, height_scale); let transform = transform.pre_scale(width_scale, height_scale);
let quality = match handle.filter().mag { let quality = match filter_method {
raster::FilterMethod::Linear => { raster::FilterMethod::Linear => {
tiny_skia::FilterQuality::Bilinear tiny_skia::FilterQuality::Bilinear
} }

View file

@ -7,8 +7,6 @@ mod raster;
mod vector; mod vector;
use atlas::Atlas; use atlas::Atlas;
use iced_graphics::core::image::{FilterMethod, TextureFilter};
use wgpu::Sampler;
use crate::core::{Rectangle, Size}; use crate::core::{Rectangle, Size};
use crate::graphics::Transformation; use crate::graphics::Transformation;
@ -29,8 +27,6 @@ use crate::core::svg;
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
use tracing::info_span; use tracing::info_span;
const SAMPLER_COUNT: usize = 4;
#[derive(Debug)] #[derive(Debug)]
pub struct Pipeline { pub struct Pipeline {
#[cfg(feature = "image")] #[cfg(feature = "image")]
@ -41,30 +37,31 @@ pub struct Pipeline {
pipeline: wgpu::RenderPipeline, pipeline: wgpu::RenderPipeline,
vertices: wgpu::Buffer, vertices: wgpu::Buffer,
indices: wgpu::Buffer, indices: wgpu::Buffer,
sampler: [wgpu::Sampler; SAMPLER_COUNT], nearest_sampler: wgpu::Sampler,
linear_sampler: wgpu::Sampler,
texture: wgpu::BindGroup, texture: wgpu::BindGroup,
texture_version: usize, texture_version: usize,
texture_atlas: Atlas, texture_atlas: Atlas,
texture_layout: wgpu::BindGroupLayout, texture_layout: wgpu::BindGroupLayout,
constant_layout: wgpu::BindGroupLayout, constant_layout: wgpu::BindGroupLayout,
layers: Vec<[Option<Layer>; SAMPLER_COUNT]>, layers: Vec<Layer>,
prepare_layer: usize, prepare_layer: usize,
} }
#[derive(Debug)] #[derive(Debug)]
struct Layer { struct Layer {
uniforms: wgpu::Buffer, uniforms: wgpu::Buffer,
constants: wgpu::BindGroup, nearest: Data,
instances: Buffer<Instance>, linear: Data,
instance_count: usize,
} }
impl Layer { impl Layer {
fn new( fn new(
device: &wgpu::Device, device: &wgpu::Device,
constant_layout: &wgpu::BindGroupLayout, constant_layout: &wgpu::BindGroupLayout,
sampler: &wgpu::Sampler, nearest_sampler: &wgpu::Sampler,
linear_sampler: &wgpu::Sampler,
) -> Self { ) -> Self {
let uniforms = device.create_buffer(&wgpu::BufferDescriptor { let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("iced_wgpu::image uniforms buffer"), label: Some("iced_wgpu::image uniforms buffer"),
@ -73,6 +70,59 @@ impl Layer {
mapped_at_creation: false, 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 { let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::image constants bind group"), label: Some("iced_wgpu::image constants bind group"),
layout: constant_layout, layout: constant_layout,
@ -102,28 +152,18 @@ impl Layer {
); );
Self { Self {
uniforms,
constants, constants,
instances, instances,
instance_count: 0, instance_count: 0,
} }
} }
fn prepare( fn upload(
&mut self, &mut self,
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, queue: &wgpu::Queue,
instances: &[Instance], 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.resize(device, instances.len());
let _ = self.instances.write(queue, 0, instances); let _ = self.instances.write(queue, 0, instances);
@ -146,37 +186,25 @@ impl Pipeline {
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self { pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
let to_wgpu = |method: FilterMethod| match method { let nearest_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
FilterMethod::Linear => wgpu::FilterMode::Linear, address_mode_u: wgpu::AddressMode::ClampToEdge,
FilterMethod::Nearest => wgpu::FilterMode::Nearest, 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 mut sampler: [Option<Sampler>; SAMPLER_COUNT] = let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
[None, None, None, None]; address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
let filter = [FilterMethod::Linear, FilterMethod::Nearest]; address_mode_w: wgpu::AddressMode::ClampToEdge,
for min in 0..filter.len() { min_filter: wgpu::FilterMode::Linear,
for mag in 0..filter.len() { mag_filter: wgpu::FilterMode::Linear,
sampler[to_index(&TextureFilter { mipmap_filter: wgpu::FilterMode::Linear,
min: filter[min], ..Default::default()
mag: filter[mag], });
})] = Some(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: to_wgpu(filter[mag]),
min_filter: to_wgpu(filter[min]),
mipmap_filter: wgpu::FilterMode::Linear,
..Default::default()
}));
}
}
let sampler = [
sampler[0].take().unwrap(),
sampler[1].take().unwrap(),
sampler[2].take().unwrap(),
sampler[3].take().unwrap(),
];
let constant_layout = let constant_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
@ -338,7 +366,8 @@ impl Pipeline {
pipeline, pipeline,
vertices, vertices,
indices, indices,
sampler, nearest_sampler,
linear_sampler,
texture, texture,
texture_version: texture_atlas.layer_count(), texture_version: texture_atlas.layer_count(),
texture_atlas, texture_atlas,
@ -381,8 +410,8 @@ impl Pipeline {
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Image", "DRAW").entered(); let _ = info_span!("Wgpu::Image", "DRAW").entered();
let mut instances: [Vec<Instance>; SAMPLER_COUNT] = let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
[Vec::new(), Vec::new(), Vec::new(), Vec::new()]; let linear_instances: &mut Vec<Instance> = &mut Vec::new();
#[cfg(feature = "image")] #[cfg(feature = "image")]
let mut raster_cache = self.raster_cache.borrow_mut(); let mut raster_cache = self.raster_cache.borrow_mut();
@ -393,7 +422,11 @@ impl Pipeline {
for image in images { for image in images {
match &image { match &image {
#[cfg(feature = "image")] #[cfg(feature = "image")]
layer::Image::Raster { handle, bounds } => { layer::Image::Raster {
handle,
filter_method,
bounds,
} => {
if let Some(atlas_entry) = raster_cache.upload( if let Some(atlas_entry) = raster_cache.upload(
device, device,
encoder, encoder,
@ -404,7 +437,12 @@ impl Pipeline {
[bounds.x, bounds.y], [bounds.x, bounds.y],
[bounds.width, bounds.height], [bounds.width, bounds.height],
atlas_entry, atlas_entry,
&mut instances[to_index(handle.filter())], match filter_method {
image::FilterMethod::Nearest => {
nearest_instances
}
image::FilterMethod::Linear => linear_instances,
},
); );
} }
} }
@ -432,7 +470,7 @@ impl Pipeline {
[bounds.x, bounds.y], [bounds.x, bounds.y],
size, size,
atlas_entry, atlas_entry,
&mut instances[to_index(&TextureFilter::default())], nearest_instances,
); );
} }
} }
@ -441,7 +479,7 @@ impl Pipeline {
} }
} }
if instances.is_empty() { if nearest_instances.is_empty() && linear_instances.is_empty() {
return; return;
} }
@ -466,24 +504,24 @@ impl Pipeline {
} }
if self.layers.len() <= self.prepare_layer { if self.layers.len() <= self.prepare_layer {
self.layers.push([None, None, None, None]); self.layers.push(Layer::new(
device,
&self.constant_layout,
&self.nearest_sampler,
&self.linear_sampler,
));
} }
for (i, instances) in instances.iter_mut().enumerate() {
let layer = &mut self.layers[self.prepare_layer][i];
if !instances.is_empty() {
if layer.is_none() {
*layer = Some(Layer::new(
device,
&self.constant_layout,
&self.sampler[i],
))
}
}
if let Some(layer) = layer { 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; self.prepare_layer += 1;
} }
@ -493,28 +531,24 @@ impl Pipeline {
bounds: Rectangle<u32>, bounds: Rectangle<u32>,
render_pass: &mut wgpu::RenderPass<'a>, render_pass: &mut wgpu::RenderPass<'a>,
) { ) {
if let Some(layer_group) = self.layers.get(layer) { if let Some(layer) = self.layers.get(layer) {
for layer in layer_group.iter() { render_pass.set_pipeline(&self.pipeline);
if let Some(layer) = layer {
render_pass.set_pipeline(&self.pipeline);
render_pass.set_scissor_rect( render_pass.set_scissor_rect(
bounds.x, bounds.x,
bounds.y, bounds.y,
bounds.width, bounds.width,
bounds.height, bounds.height,
); );
render_pass.set_bind_group(1, &self.texture, &[]); render_pass.set_bind_group(1, &self.texture, &[]);
render_pass.set_index_buffer( render_pass.set_index_buffer(
self.indices.slice(..), self.indices.slice(..),
wgpu::IndexFormat::Uint16, wgpu::IndexFormat::Uint16,
); );
render_pass.set_vertex_buffer(0, self.vertices.slice(..)); render_pass.set_vertex_buffer(0, self.vertices.slice(..));
layer.render(render_pass); layer.render(render_pass);
}
}
} }
} }
@ -529,14 +563,6 @@ impl Pipeline {
} }
} }
fn to_index(filter: &TextureFilter) -> usize {
let to_index = |m| match m {
FilterMethod::Linear => 0,
FilterMethod::Nearest => 1,
};
return (to_index(filter.mag) << 1) | (to_index(filter.min));
}
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Zeroable, Pod)] #[derive(Clone, Copy, Zeroable, Pod)]
pub struct Vertex { pub struct Vertex {
@ -571,7 +597,7 @@ struct Instance {
} }
impl Instance { impl Instance {
pub const INITIAL: usize = 1_000; pub const INITIAL: usize = 20;
} }
#[repr(C)] #[repr(C)]

View file

@ -186,11 +186,16 @@ impl<'a> Layer<'a> {
layer.quads.add(quad, background); layer.quads.add(quad, background);
} }
Primitive::Image { handle, bounds } => { Primitive::Image {
handle,
filter_method,
bounds,
} => {
let layer = &mut layers[current_layer]; let layer = &mut layers[current_layer];
layer.images.push(Image::Raster { layer.images.push(Image::Raster {
handle: handle.clone(), handle: handle.clone(),
filter_method: *filter_method,
bounds: *bounds + translation, bounds: *bounds + translation,
}); });
} }

View file

@ -10,6 +10,9 @@ pub enum Image {
/// The handle of a raster image. /// The handle of a raster image.
handle: image::Handle, handle: image::Handle,
/// The filter method of a raster image.
filter_method: image::FilterMethod,
/// The bounds of the image. /// The bounds of the image.
bounds: Rectangle, bounds: Rectangle,
}, },

View file

@ -13,7 +13,7 @@ use crate::core::{
use std::hash::Hash; use std::hash::Hash;
pub use image::{FilterMethod, Handle, TextureFilter}; pub use image::{FilterMethod, Handle};
/// Creates a new [`Viewer`] with the given image `Handle`. /// Creates a new [`Viewer`] with the given image `Handle`.
pub fn viewer<Handle>(handle: Handle) -> Viewer<Handle> { pub fn viewer<Handle>(handle: Handle) -> Viewer<Handle> {
@ -37,6 +37,7 @@ pub struct Image<Handle> {
width: Length, width: Length,
height: Length, height: Length,
content_fit: ContentFit, content_fit: ContentFit,
filter_method: FilterMethod,
} }
impl<Handle> Image<Handle> { impl<Handle> Image<Handle> {
@ -47,6 +48,7 @@ impl<Handle> Image<Handle> {
width: Length::Shrink, width: Length::Shrink,
height: Length::Shrink, height: Length::Shrink,
content_fit: ContentFit::Contain, content_fit: ContentFit::Contain,
filter_method: FilterMethod::default(),
} }
} }
@ -65,11 +67,15 @@ impl<Handle> Image<Handle> {
/// Sets the [`ContentFit`] of the [`Image`]. /// Sets the [`ContentFit`] of the [`Image`].
/// ///
/// Defaults to [`ContentFit::Contain`] /// Defaults to [`ContentFit::Contain`]
pub fn content_fit(self, content_fit: ContentFit) -> Self { pub fn content_fit(mut self, content_fit: ContentFit) -> Self {
Self { self.content_fit = content_fit;
content_fit, self
..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<'_>, layout: Layout<'_>,
handle: &Handle, handle: &Handle,
content_fit: ContentFit, content_fit: ContentFit,
filter_method: FilterMethod,
) where ) where
Renderer: image::Renderer<Handle = Handle>, Renderer: image::Renderer<Handle = Handle>,
Handle: Clone + Hash, Handle: Clone + Hash,
@ -141,7 +148,7 @@ pub fn draw<Renderer, Handle>(
..bounds ..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 if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height
@ -191,7 +198,13 @@ where
_cursor: mouse::Cursor, _cursor: mouse::Cursor,
_viewport: &Rectangle, _viewport: &Rectangle,
) { ) {
draw(renderer, layout, &self.handle, self.content_fit); draw(
renderer,
layout,
&self.handle,
self.content_fit,
self.filter_method,
);
} }
} }

View file

@ -22,19 +22,21 @@ pub struct Viewer<Handle> {
max_scale: f32, max_scale: f32,
scale_step: f32, scale_step: f32,
handle: Handle, handle: Handle,
filter_method: image::FilterMethod,
} }
impl<Handle> Viewer<Handle> { impl<Handle> Viewer<Handle> {
/// Creates a new [`Viewer`] with the given [`State`]. /// Creates a new [`Viewer`] with the given [`State`].
pub fn new(handle: Handle) -> Self { pub fn new(handle: Handle) -> Self {
Viewer { Viewer {
handle,
padding: 0.0, padding: 0.0,
width: Length::Shrink, width: Length::Shrink,
height: Length::Shrink, height: Length::Shrink,
min_scale: 0.25, min_scale: 0.25,
max_scale: 10.0, max_scale: 10.0,
scale_step: 0.10, scale_step: 0.10,
handle, filter_method: image::FilterMethod::default(),
} }
} }
@ -329,6 +331,7 @@ where
image::Renderer::draw( image::Renderer::draw(
renderer, renderer,
self.handle.clone(), self.handle.clone(),
self.filter_method,
Rectangle { Rectangle {
x: bounds.x, x: bounds.x,
y: bounds.y, y: bounds.y,