diff --git a/Cargo.toml b/Cargo.toml index 5d46360..ef78ab9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ members = [".", "examples/split-chart"] [dependencies] plotters = { version = "0.3", default-features = false } plotters-backend = "0.3" -iced_widget = { version = "0.13", features = ["canvas"] } +iced_widget = { version = "0.13", features = ["canvas", "image"] } iced_graphics = "0.13" once_cell = "1" @@ -29,11 +29,13 @@ plotters = { version = "0.3", default-features = false, features = [ "area_series", "line_series", "point_series", + "bitmap_backend" ] } -iced = { version = "0.13", features = ["canvas", "tokio"] } +iced = { version = "0.13", features = ["canvas", "tokio", "image"] } chrono = { version = "0.4", default-features = false } rand = "0.8" tokio = { version = "1", features = ["rt"], default-features = false } +image = "0.25.2" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] -sysinfo = { version = "0.30", default-features = false } +sysinfo = { version = "0.30", default-features = false } \ No newline at end of file diff --git a/examples/image.rs b/examples/image.rs new file mode 100644 index 0000000..c76de62 --- /dev/null +++ b/examples/image.rs @@ -0,0 +1,137 @@ +extern crate iced; +extern crate plotters; +extern crate rand; +extern crate tokio; + +use std::{fs::File, io::BufReader}; + +use iced::{ + widget::{ + canvas::{Cache, Frame, Geometry}, + Column, Container, Text, + }, + Alignment, Element, Length, Size, Task, +}; + +use image::{DynamicImage, GenericImageView, ImageFormat}; +use plotters::prelude::ChartBuilder; +use plotters_backend::DrawingBackend; +use plotters_iced::{Chart, ChartWidget, Renderer}; + +fn main() { + iced::application("Image Example", State::update, State::view) + .antialiasing(true) + .run_with(State::new) + .unwrap(); +} + +#[derive(Debug)] +enum Message { + ImageLoaded(DynamicImage), +} + +struct State { + chart: Option, +} + +impl State { + fn new() -> (Self, Task) { + ( + Self { chart: None }, + Task::batch([Task::perform( + tokio::task::spawn_blocking(load_image), + |data| Message::ImageLoaded(data.unwrap()), + )]), + ) + } + + fn update(&mut self, message: Message) -> Task { + match message { + Message::ImageLoaded(data) => { + self.chart = Some(ExampleChart::new(data)); + Task::none() + } + } + } + + fn view(&self) -> Element<'_, Message> { + let content = Column::new() + .spacing(20) + .align_x(Alignment::Start) + .width(Length::Fill) + .height(Length::Fill) + .push(match self.chart { + Some(ref chart) => chart.view(), + None => Text::new("Loading...").into(), + }); + + Container::new(content) + .padding(5) + .center_x(Length::Fill) + .center_y(Length::Fill) + .into() + } +} + +struct ExampleChart { + cache: Cache, + image: DynamicImage, +} + +impl ExampleChart { + fn new(image: DynamicImage) -> Self { + Self { + cache: Cache::new(), + image, + } + } + + fn view(&self) -> Element { + let chart = ChartWidget::new(self) + .width(Length::Fill) + .height(Length::Fill); + + chart.into() + } +} + +impl Chart for ExampleChart { + type State = (); + + #[inline] + fn draw( + &self, + renderer: &R, + bounds: Size, + draw_fn: F, + ) -> Geometry { + renderer.draw_cache(&self.cache, bounds, draw_fn) + } + + fn build_chart(&self, _state: &Self::State, mut builder: ChartBuilder) { + use plotters::prelude::*; + + let mut chart = builder + .build_cartesian_2d(0.0..1.0, 0.0..1.0) + .expect("failed to build chart"); + + let (w, h) = self.image.dimensions(); + let bitmap = BitMapElement::with_ref((0.5, 0.5), (w, h), self.image.as_rgba8().unwrap()); + + chart + .draw_series(bitmap) + .expect("failed to draw chart data"); + } +} + +fn load_image() -> DynamicImage { + let file_name = format!("./examples/images/rustacean-orig-noshadow.png"); + + let image = image::load( + BufReader::new(File::open(file_name).expect("couldn't open file")), + ImageFormat::Png, + ) + .expect("loading image failed"); + + return image; +} diff --git a/examples/images/rustacean-orig-noshadow.png b/examples/images/rustacean-orig-noshadow.png new file mode 100644 index 0000000..9660859 Binary files /dev/null and b/examples/images/rustacean-orig-noshadow.png differ diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 9ff2828..1874b4f 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -4,15 +4,15 @@ // Copyright: 2022, Joylei // License: MIT -use std::collections::HashSet; - use iced_graphics::core::text::Paragraph; +use iced_widget::core::Rectangle; use iced_widget::{ canvas, core::{ alignment::{Horizontal, Vertical}, font, text, Font, Size, }, + image, text::Shaping, }; use once_cell::unsync::Lazy; @@ -28,6 +28,7 @@ use plotters_backend::{ FontFamily, FontStyle, }; +use std::collections::HashSet; use crate::error::Error; use crate::utils::{cvt_color, cvt_stroke, CvtPoint}; @@ -293,13 +294,16 @@ where #[inline] fn blit_bitmap( &mut self, - _pos: BackendCoord, - (_iw, _ih): (u32, u32), - _src: &[u8], + (x, y): BackendCoord, + (iw, ih): (u32, u32), + src: &[u8], ) -> Result<(), DrawingErrorKind> { - // Not supported yet (rendering ignored) - // Notice: currently Iced has limitations, because widgets are not rendered in the order of creation, and different primitives go to different render pipelines. + let image = image::Handle::from_rgba(iw, ih, src.to_owned()); + + let pos = (x - iw as i32 / 2, y - ih as i32 / 2) as BackendCoord; + let bounds = Rectangle::new(pos.cvt_point(), Size::new(iw as f32, ih as f32)); + self.frame.draw_image(bounds, &image); Ok(()) } }