use crate::{
kurbo::Rect,
piet::{Image as _, ImageBuf, InterpolationMode, PietImage},
widget::common::FillStrat,
widget::prelude::*,
Data,
};
pub struct Image {
image_data: ImageBuf,
paint_data: Option<PietImage>,
fill: FillStrat,
interpolation: InterpolationMode,
clip_area: Option<Rect>,
}
impl Image {
#[inline]
pub fn new(image_data: ImageBuf) -> Self {
Image {
image_data,
paint_data: None,
fill: FillStrat::default(),
interpolation: InterpolationMode::Bilinear,
clip_area: None,
}
}
#[inline]
pub fn fill_mode(mut self, mode: FillStrat) -> Self {
self.fill = mode;
self
}
#[inline]
pub fn set_fill_mode(&mut self, newfil: FillStrat) {
self.fill = newfil;
}
#[inline]
pub fn interpolation_mode(mut self, interpolation: InterpolationMode) -> Self {
self.interpolation = interpolation;
self
}
#[inline]
pub fn set_interpolation_mode(&mut self, interpolation: InterpolationMode) {
self.interpolation = interpolation;
}
#[inline]
pub fn clip_area(mut self, clip_area: Option<Rect>) -> Self {
self.clip_area = clip_area;
self
}
#[inline]
pub fn set_clip_area(&mut self, clip_area: Option<Rect>) {
self.clip_area = clip_area;
}
#[inline]
pub fn set_image_data(&mut self, image_data: ImageBuf) {
self.image_data = image_data;
self.invalidate();
}
#[inline]
fn invalidate(&mut self) {
self.paint_data = None;
}
}
impl<T: Data> Widget<T> for Image {
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");
let max = bc.max();
let image_size = self.image_data.size();
if bc.is_width_bounded() && !bc.is_height_bounded() {
let ratio = max.width / image_size.width;
Size::new(max.width, ratio * image_size.height)
} else if bc.is_height_bounded() && !bc.is_width_bounded() {
let ratio = max.height / image_size.height;
Size::new(ratio * image_size.width, max.height)
} else {
bc.constrain(self.image_data.size())
}
}
fn paint(&mut self, ctx: &mut PaintCtx, _data: &T, _env: &Env) {
let offset_matrix = self.fill.affine_to_fill(ctx.size(), self.image_data.size());
if self.fill != FillStrat::Contain {
let clip_rect = ctx.size().to_rect();
ctx.clip(clip_rect);
}
let piet_image = {
let image_data = &self.image_data;
self.paint_data
.get_or_insert_with(|| image_data.to_image(ctx.render_ctx))
};
if piet_image.size().is_empty() {
return;
}
ctx.with_save(|ctx| {
let piet_image = {
let image_data = &self.image_data;
self.paint_data
.get_or_insert_with(|| image_data.to_image(ctx.render_ctx))
};
ctx.transform(offset_matrix);
if let Some(area) = self.clip_area {
ctx.draw_image_area(
piet_image,
area,
self.image_data.size().to_rect(),
self.interpolation,
);
} else {
ctx.draw_image(
piet_image,
self.image_data.size().to_rect(),
self.interpolation,
);
}
});
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(test)]
mod tests {
use crate::piet::ImageFormat;
use super::*;
#[test]
fn empty_paint() {
use crate::{tests::harness::Harness, WidgetId};
let _id_1 = WidgetId::next();
let image_data = ImageBuf::empty();
let image_widget =
Image::new(image_data).interpolation_mode(InterpolationMode::NearestNeighbor);
Harness::create_with_render(
(),
image_widget,
Size::new(400., 600.),
|harness| {
harness.send_initial_events();
harness.just_layout();
harness.paint();
},
|_target| {
},
)
}
#[test]
fn tall_paint() {
use crate::{tests::harness::Harness, WidgetId};
let _id_1 = WidgetId::next();
let image_data = ImageBuf::from_raw(
vec![255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255],
ImageFormat::Rgb,
2,
2,
);
let image_widget =
Image::new(image_data).interpolation_mode(InterpolationMode::NearestNeighbor);
Harness::create_with_render(
(),
image_widget,
Size::new(400., 600.),
|harness| {
harness.send_initial_events();
harness.just_layout();
harness.paint();
},
|target| {
let raw_pixels = target.into_raw();
assert_eq!(raw_pixels.len(), 400 * 600 * 4);
let expecting: Vec<u8> = [
vec![0, 0, 0, 255].repeat(200),
vec![255, 255, 255, 255].repeat(200),
]
.concat();
assert_eq!(raw_pixels[400 * 300 * 4..400 * 301 * 4], expecting[..]);
let expecting: Vec<u8> = vec![41, 41, 41, 255].repeat(400 * 100);
assert_eq!(
raw_pixels[400 * 600 * 4 - 4 * 400 * 100..400 * 600 * 4],
expecting[..]
);
},
)
}
#[test]
fn wide_paint() {
use crate::{tests::harness::Harness, WidgetId};
let _id_1 = WidgetId::next();
let image_data = ImageBuf::from_raw(
vec![255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255],
ImageFormat::Rgb,
2,
2,
);
let image_widget =
Image::new(image_data).interpolation_mode(InterpolationMode::NearestNeighbor);
Harness::create_with_render(
true,
image_widget,
Size::new(600., 400.),
|harness| {
harness.send_initial_events();
harness.just_layout();
harness.paint();
},
|target| {
let raw_pixels = target.into_raw();
assert_eq!(raw_pixels.len(), 400 * 600 * 4);
let expecting: Vec<u8> = [
vec![41, 41, 41, 255].repeat(100),
vec![255, 255, 255, 255].repeat(200),
vec![0, 0, 0, 255].repeat(200),
vec![41, 41, 41, 255].repeat(100),
]
.concat();
assert_eq!(raw_pixels[199 * 600 * 4..200 * 600 * 4], expecting[..]);
let expecting: Vec<u8> = [
vec![41, 41, 41, 255].repeat(100),
vec![0, 0, 0, 255].repeat(200),
vec![255, 255, 255, 255].repeat(200),
vec![41, 41, 41, 255].repeat(100),
]
.concat();
assert_eq!(raw_pixels[399 * 600 * 4..400 * 600 * 4], expecting[..]);
},
);
}
#[test]
fn into_png() {
use crate::{
tests::{harness::Harness, temp_dir_for_test},
WidgetId,
};
let _id_1 = WidgetId::next();
let image_data = ImageBuf::from_raw(
vec![255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255],
ImageFormat::Rgb,
2,
2,
);
let image_widget =
Image::new(image_data).interpolation_mode(InterpolationMode::NearestNeighbor);
Harness::create_with_render(
true,
image_widget,
Size::new(600., 400.),
|harness| {
harness.send_initial_events();
harness.just_layout();
harness.paint();
},
|target| {
let tmp_dir = temp_dir_for_test();
target.into_png(tmp_dir.join("image.png")).unwrap();
},
);
}
#[test]
fn width_bound_layout() {
use crate::{
tests::harness::Harness,
widget::{Container, Scroll},
WidgetExt, WidgetId,
};
use float_cmp::approx_eq;
let id_1 = WidgetId::next();
let image_data = ImageBuf::from_raw(
vec![255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255],
ImageFormat::Rgb,
2,
2,
);
let image_widget =
Scroll::new(Container::new(Image::new(image_data)).with_id(id_1)).vertical();
Harness::create_simple(true, image_widget, |harness| {
harness.send_initial_events();
harness.just_layout();
let state = harness.get_state(id_1);
assert!(approx_eq!(f64, state.layout_rect().x1, 400.0));
})
}
#[test]
fn height_bound_layout() {
use crate::{
tests::harness::Harness,
widget::{Container, Scroll},
WidgetExt, WidgetId,
};
use float_cmp::approx_eq;
let id_1 = WidgetId::next();
let image_data = ImageBuf::from_raw(
vec![255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255],
ImageFormat::Rgb,
2,
2,
);
let image_widget =
Scroll::new(Container::new(Image::new(image_data)).with_id(id_1)).horizontal();
Harness::create_simple(true, image_widget, |harness| {
harness.send_initial_events();
harness.just_layout();
let state = harness.get_state(id_1);
assert!(approx_eq!(f64, state.layout_rect().x1, 400.0));
})
}
#[test]
fn image_clip_area() {
use crate::{tests::harness::Harness, WidgetId};
use std::iter;
let _id_1 = WidgetId::next();
let image_data = ImageBuf::from_raw(
vec![255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255],
ImageFormat::Rgb,
2,
2,
);
let image_widget = Image::new(image_data)
.interpolation_mode(InterpolationMode::NearestNeighbor)
.clip_area(Some(Rect::new(1., 1., 2., 2.)));
Harness::create_with_render(
true,
image_widget,
Size::new(2., 2.),
|harness| {
harness.send_initial_events();
harness.just_layout();
harness.paint();
},
|target| {
let raw_pixels = target.into_raw();
assert_eq!(raw_pixels.len(), 4 * 4);
let expecting: Vec<u8> = iter::repeat(255).take(16).collect();
assert_eq!(&*raw_pixels, &*expecting);
},
)
}
}