diff options
| -rw-r--r-- | core/src/image.rs | 17 | ||||
| -rw-r--r-- | examples/tour/src/main.rs | 44 | ||||
| -rw-r--r-- | graphics/src/primitive.rs | 2 | ||||
| -rw-r--r-- | graphics/src/renderer.rs | 13 | ||||
| -rw-r--r-- | renderer/src/lib.rs | 9 | ||||
| -rw-r--r-- | tiny_skia/src/backend.rs | 16 | ||||
| -rw-r--r-- | tiny_skia/src/raster.rs | 12 | ||||
| -rw-r--r-- | wgpu/src/image.rs | 129 | ||||
| -rw-r--r-- | wgpu/src/layer.rs | 7 | ||||
| -rw-r--r-- | wgpu/src/layer/image.rs | 3 | ||||
| -rw-r--r-- | widget/src/image.rs | 29 | ||||
| -rw-r--r-- | widget/src/image/viewer.rs | 5 | 
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, | 
