Skip to content

Commit

Permalink
Add generic image widget
Browse files Browse the repository at this point in the history
  • Loading branch information
fishrockz committed Feb 15, 2020
1 parent d7a7a6e commit 293f750
Show file tree
Hide file tree
Showing 8 changed files with 579 additions and 2 deletions.
286 changes: 286 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions druid/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ log = "0.4.8"
usvg = {version = "0.9.0", optional = true}
fnv = "1.0.3"
xi-unicode = "0.2.0"
image = {version = "0.22.4", optional = true}

[dependencies.simple_logger]
version = "1.3.0"
Expand Down
55 changes: 55 additions & 0 deletions druid/examples/image.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright 2020 The xi-editor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! This example shows how to draw an png image.
//!
//! Requires the non-default "image" feature to be enabled:
//! `cargo run --example image --features "images"`
//!

#[cfg(not(feature = "image"))]
fn main() {
eprintln!("This examples requires the \"image\" feature to be enabled:");
eprintln!("cargo run --example image --features \"image\"");
}

#[cfg(feature = "image")]
fn main() {
use std::str::FromStr;

use druid::{
widget::{FillStrat, Flex, Image, ImageData, WidgetExt},
AppLauncher, Widget, WindowDesc,
};

fn ui_builder() -> impl Widget<u32> {
let png_data = ImageData::from_str("examples/pngexample.png").unwrap();

let mut col = Flex::column();

col.add_child(Image::new(png_data.clone()).fix_width(100.0).center(), 1.0);

let mut otherimage = Image::new(png_data);
otherimage.set_fill(FillStrat::FitWidth);
col.add_child(otherimage, 1.0);
col
};

let main_window = WindowDesc::new(ui_builder);
let data = 0_u32;
AppLauncher::with_window(main_window)
.use_simple_logger()
.launch(data)
.expect("launch failed");
}
Binary file added druid/examples/pngexample.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion druid/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ impl std::fmt::Display for ArgumentError {
ArgumentError::WrongVariant => write!(
f,
"Incorrect access method for argument type; \
check Command::one_shot docs for more detail."
check Command::one_shot docs for more detail."
),
}
}
Expand Down
2 changes: 1 addition & 1 deletion druid/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
if self.old_data.is_none() {
log::error!(
"widget {:?} is receiving an event without having first \
recieved WidgetAdded.",
recieved WidgetAdded.",
ctx.widget_id()
);
}
Expand Down
229 changes: 229 additions & 0 deletions druid/src/widget/images.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// Copyright 2020 The xi-editor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! An Image widget.
//! Please consider using SVG and the SVG wideget as it scales much better.

use std::error::Error;
use std::marker::PhantomData;
use std::str::FromStr;

use image;

use crate::{
Affine, BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx,
PaintCtx, Point, Rect, RenderContext, Size, UpdateCtx, Widget,
};

use crate::piet::{ImageFormat, InterpolationMode};

#[derive(PartialEq)]
pub enum FillStrat {
Contain,
Cover,
Fill,
FitHeight,
FitWidth,
None,
ScaleDown,
}

impl Default for FillStrat {
fn default() -> Self {
FillStrat::Fill
}
}

fn get_scale_offset(parent: Size, fit_box: Size, fit_type: &FillStrat) -> (Point, Point) {
let scalex = parent.width / fit_box.width;
let scaley = parent.height / fit_box.height;

let scale: Point = match fit_type {
FillStrat::Contain => {
let scale = scalex.min(scaley);
Point { x: scale, y: scale }
}
FillStrat::Cover => {
let scale = scalex.max(scaley);
Point { x: scale, y: scale }
}
FillStrat::Fill => Point {
x: scalex,
y: scaley,
},
FillStrat::FitHeight => Point {
x: scaley,
y: scaley,
},
FillStrat::FitWidth => Point {
x: scalex,
y: scalex,
},
FillStrat::ScaleDown => {
let scale = scalex.min(scaley).min(1.0);
Point { x: scale, y: scale }
}
FillStrat::None => Point { x: 1.0, y: 1.0 },
};

let origin_x = (parent.width - (fit_box.width * scale.x)) / 2.0;
let origin_y = (parent.height - (fit_box.height * scale.y)) / 2.0;
let origin = Point::new(origin_x, origin_y);

(scale, origin)
}

/// A widget that renders an Image
pub struct Image<T> {
image_data: ImageData,
phantom: PhantomData<T>,
fill: FillStrat,
}

impl<T: Data> Image<T> {
/// Create an Image-drawing widget from ImageData.
///
/// The Image will scale to fit its box constraints.
pub fn new(image_data: ImageData) -> Self {
Image {
image_data,
phantom: Default::default(),
fill: FillStrat::default(),
}
}

pub fn set_fill(&mut self, newfil: FillStrat) {
self.fill = newfil;
}
}

impl<T: Data> Widget<T> for Image<T> {
fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut T, _env: &Env) {}

fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle, _data: &T, _env: &Env) {}

fn update(&mut self, _ctx: &mut UpdateCtx, _old_data: &T, _data: &T, _env: &Env) {}

fn layout(
&mut self,
_layout_ctx: &mut LayoutCtx,
bc: &BoxConstraints,
_data: &T,
_env: &Env,
) -> Size {
bc.debug_check("Image");

if bc.is_width_bounded() {
bc.max()
} else {
bc.constrain(self.image_data.get_size())
}
}
fn paint(&mut self, paint_ctx: &mut PaintCtx, _data: &T, _env: &Env) {
let bob = get_scale_offset(paint_ctx.size(), self.image_data.get_size(), &self.fill);

// The ImageData's to_piet function does not clip to the image's size
// CairoRenderContext is very like druids but with some extra goodies like clip
if self.fill == FillStrat::Contain {
} else {
let clip_rect = Rect::ZERO.with_size(paint_ctx.size());
paint_ctx.clip(clip_rect);
}
self.image_data.to_piet(bob.0.x, bob.1, paint_ctx);
}
}

/// Stored Image data.
/// Implements `FromStr` and can be converted to piet draw instructions.
#[derive(Clone)]
pub struct ImageData {
pixels: Vec<u8>,
x_pixels: u32,
y_pixels: u32,
}

impl ImageData {
/// Create an empty Image
pub fn empty() -> Self {
ImageData {
pixels: [].to_vec(),
x_pixels: 0,
y_pixels: 0,
}
}

pub fn from_data(raw_image: &[u8]) -> Result<Self, Box<dyn Error>> {
let dec = image::load_from_memory(raw_image).map_err(|e| e)?.to_rgb();

let sizeofimage = dec.dimensions();
Ok(ImageData {
pixels: dec.to_vec(),
x_pixels: sizeofimage.0,
y_pixels: sizeofimage.1,
})
}

fn get_size(&self) -> Size {
Size::new(self.x_pixels as f64, self.y_pixels as f64)
}

/// Convert ImageData into Piet draw instructions
pub fn to_piet(&self, scale: f64, offset: Point, paint_ctx: &mut PaintCtx) {
let offset_matrix = Affine::new([scale, 0., 0., scale, offset.x, offset.y]);

paint_ctx
.with_save(|ctx| {
ctx.transform(offset_matrix);

let im = ctx
.make_image(
self.x_pixels as usize,
self.y_pixels as usize,
&self.pixels,
ImageFormat::Rgb,
)
.unwrap();
let rec = Rect::from_origin_size(
(0.0, 0.0),
(self.x_pixels as f64, self.y_pixels as f64),
);
ctx.draw_image(&im, rec, InterpolationMode::Bilinear);

Ok(())
})
.unwrap();
}
}

impl Default for ImageData {
fn default() -> Self {
ImageData::empty()
}
}

impl FromStr for ImageData {
type Err = Box<dyn Error>;

fn from_str(image_str: &str) -> Result<Self, Self::Err> {
let image_data = image::open(image_str).unwrap().to_rgb();
// catch unrap

let sizeofimage = image_data.dimensions();
Ok(ImageData {
pixels: image_data.to_vec(),
x_pixels: sizeofimage.0,
y_pixels: sizeofimage.1,
})
}
}
6 changes: 6 additions & 0 deletions druid/src/widget/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ mod either;
mod env_scope;
mod flex;
mod identity_wrapper;
#[cfg(feature = "image")]
#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
mod images;
mod label;
mod list;
mod padding;
Expand Down Expand Up @@ -49,6 +52,9 @@ pub use either::Either;
pub use env_scope::EnvScope;
pub use flex::Flex;
pub use identity_wrapper::IdentityWrapper;
#[cfg(feature = "image")]
#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
pub use images::{FillStrat, Image, ImageData};
pub use label::{Label, LabelText};
pub use list::{List, ListIter};
pub use padding::Padding;
Expand Down

0 comments on commit 293f750

Please sign in to comment.