feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
@@ -0,0 +1,50 @@
|
||||
use crate::image::SubImage;
|
||||
use embedded_graphics_core::{image::ImageDrawable, primitives::Rectangle};
|
||||
|
||||
/// Extension trait for image drawables.
|
||||
pub trait ImageDrawableExt: Sized {
|
||||
/// Returns a sub image of this image drawable.
|
||||
///
|
||||
/// If any of the given `area` lies outside the bounding box of the parent image, the
|
||||
/// intersection of `area` and the bounding box will be used.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// This example loads a raw image containing multiple 16x16px sprites and draws two of them to
|
||||
/// a display, with their top-left corners positioned at `(0, 0)` and `(32, 8)`.
|
||||
///
|
||||
/// ```rust
|
||||
/// use embedded_graphics::{
|
||||
/// image::{Image, ImageRaw, ImageRawBE},
|
||||
/// pixelcolor::Rgb565,
|
||||
/// prelude::*,
|
||||
/// primitives::Rectangle,
|
||||
/// };
|
||||
/// # use embedded_graphics::mock_display::MockDisplay as Display;
|
||||
/// # let mut display: Display<Rgb565> = Display::default();
|
||||
///
|
||||
/// let data = [ 0xF8, 0x00, 0x07, 0xE0, 0xFF, 0xE0, /* ... */ ];
|
||||
/// // or: let data = include_bytes!("sprite_atlas.raw");
|
||||
///
|
||||
/// # let data = [0u8; 32 * 16 * 2];
|
||||
/// let sprite_atlas = ImageRawBE::<Rgb565>::new(&data, 32);
|
||||
///
|
||||
/// let sprite_1 = sprite_atlas.sub_image(&Rectangle::new(Point::new(0, 0), Size::new(16, 16)));
|
||||
/// let sprite_2 = sprite_atlas.sub_image(&Rectangle::new(Point::new(16, 0), Size::new(16, 16)));
|
||||
///
|
||||
/// Image::new(&sprite_1, Point::new(0, 0)).draw(&mut display)?;
|
||||
/// Image::new(&sprite_2, Point::new(32, 8)).draw(&mut display)?;
|
||||
///
|
||||
/// # Ok::<(), core::convert::Infallible>(())
|
||||
/// ```
|
||||
fn sub_image(&self, area: &Rectangle) -> SubImage<Self>;
|
||||
}
|
||||
|
||||
impl<T> ImageDrawableExt for T
|
||||
where
|
||||
T: ImageDrawable,
|
||||
{
|
||||
fn sub_image(&self, area: &Rectangle) -> SubImage<T> {
|
||||
SubImage::new(self, area)
|
||||
}
|
||||
}
|
||||
+703
@@ -0,0 +1,703 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use crate::{
|
||||
draw_target::DrawTarget,
|
||||
geometry::{Dimensions, OriginDimensions, Point, Size},
|
||||
image::{GetPixel, ImageDrawable},
|
||||
iterator::raw::RawDataSlice,
|
||||
pixelcolor::{
|
||||
raw::{BigEndian, ByteOrder, LittleEndian, RawData},
|
||||
PixelColor,
|
||||
},
|
||||
primitives::Rectangle,
|
||||
};
|
||||
|
||||
/// Image with little endian data.
|
||||
pub type ImageRawLE<'a, C> = ImageRaw<'a, C, LittleEndian>;
|
||||
|
||||
/// Image with big endian data.
|
||||
pub type ImageRawBE<'a, C> = ImageRaw<'a, C, BigEndian>;
|
||||
|
||||
/// An image constructed from a slice of raw pixel data.
|
||||
///
|
||||
/// The `ImageRaw` struct can be used to construct an image from a slice
|
||||
/// of raw image data. The storage format is determined by the [`PixelColor`]
|
||||
/// type `C` and the [`ByteOrder`] `BO`. The byteorder doesn't need to be
|
||||
/// specified for colors which aren't stored in multiple bytes.
|
||||
///
|
||||
/// For color types with less than 8 bits per pixels the start of each row is
|
||||
/// aligned to the next whole byte.
|
||||
///
|
||||
/// Details about the conversion of raw data to color types are explained in the
|
||||
/// [`raw` module documentation].
|
||||
///
|
||||
/// To draw an `ImageRaw` object it needs to be wrapped in an [`Image`] object.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ## Draw a 1BPP image
|
||||
///
|
||||
/// This example creates an image from 1 bit per pixel data.
|
||||
///
|
||||
/// ```
|
||||
/// use embedded_graphics::{
|
||||
/// image::{Image, ImageRaw},
|
||||
/// pixelcolor::BinaryColor,
|
||||
/// prelude::*,
|
||||
/// };
|
||||
/// # use embedded_graphics::mock_display::MockDisplay as Display;
|
||||
///
|
||||
/// /// 12 x 5 pixel image with 1 bit per pixel.
|
||||
/// /// The data for each row is 12 bits long and is padded with zeros on the
|
||||
/// /// end because each row needs to contain a whole number of bytes.
|
||||
/// #[rustfmt::skip]
|
||||
/// const DATA: &[u8] = &[
|
||||
/// 0b11101111, 0b0101_0000,
|
||||
/// 0b10001000, 0b0101_0000,
|
||||
/// 0b11101011, 0b0101_0000,
|
||||
/// 0b10001001, 0b0101_0000,
|
||||
/// 0b11101111, 0b0101_0000,
|
||||
/// ];
|
||||
///
|
||||
/// // The image dimensions and the format of the stored raw data must be specified
|
||||
/// // when the `new` function is called. The data format can, for example, be specified
|
||||
/// // by using the turbofish syntax. For the image dimensions only the width must be
|
||||
/// // passed to the `new` function. The image height will be calculated based on the
|
||||
/// // length of the image data and the data format.
|
||||
/// let raw_image = ImageRaw::<BinaryColor>::new(DATA, 12);
|
||||
///
|
||||
/// let image = Image::new(&raw_image, Point::zero());
|
||||
///
|
||||
/// let mut display = Display::default();
|
||||
///
|
||||
/// image.draw(&mut display)?;
|
||||
/// # Ok::<(), core::convert::Infallible>(())
|
||||
/// ```
|
||||
///
|
||||
/// ## Draw an image that uses multibyte pixel encoding
|
||||
///
|
||||
/// Colors with more than one byte per pixel need an additional type annotation for the byte order.
|
||||
/// For convenience, the [`ImageRawBE`] and [`ImageRawLE`] type aliases can be used to abbreviate
|
||||
/// the type.
|
||||
///
|
||||
/// ```
|
||||
/// use embedded_graphics::{
|
||||
/// image::{Image, ImageRaw, ImageRawBE, ImageRawLE},
|
||||
/// pixelcolor::{
|
||||
/// raw::{BigEndian, LittleEndian},
|
||||
/// Rgb565, Rgb888,
|
||||
/// },
|
||||
/// prelude::*,
|
||||
/// };
|
||||
/// # const DATA: &[u8] = &[0x55; 8 * 8 * 3];
|
||||
///
|
||||
/// // Rgb888 image with 24 bits per pixel and big endian byte order
|
||||
/// let image1 = ImageRawBE::<Rgb888>::new(DATA, 8);
|
||||
/// // or:
|
||||
/// let image2 = ImageRaw::<Rgb888, BigEndian>::new(DATA, 8);
|
||||
/// # assert_eq!(image1, image2);
|
||||
///
|
||||
/// // Rgb565 image with 16 bits per pixel and little endian byte order
|
||||
/// let image1 = ImageRawLE::<Rgb565>::new(DATA, 16);
|
||||
/// // or:
|
||||
/// let image2 = ImageRaw::<Rgb565, LittleEndian>::new(DATA, 16);
|
||||
/// # assert_eq!(image1, image2);
|
||||
/// ```
|
||||
///
|
||||
/// [`raw` module documentation]: crate::pixelcolor::raw
|
||||
/// [`Image`]: crate::image::Image
|
||||
/// [`Drawable`]: crate::Drawable
|
||||
/// [`PixelColor`]: crate::pixelcolor::PixelColor
|
||||
/// [`ByteOrder`]: crate::pixelcolor::raw::ByteOrder
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
|
||||
pub struct ImageRaw<'a, C, BO = BigEndian>
|
||||
where
|
||||
C: PixelColor + From<<C as PixelColor>::Raw>,
|
||||
BO: ByteOrder,
|
||||
{
|
||||
/// Image data, packed as dictated by raw data type `C::Raw`
|
||||
data: &'a [u8],
|
||||
|
||||
/// Image size in pixels
|
||||
size: Size,
|
||||
|
||||
pixel_type: PhantomData<C>,
|
||||
byte_order: PhantomData<BO>,
|
||||
}
|
||||
|
||||
impl<'a, C, BO> ImageRaw<'a, C, BO>
|
||||
where
|
||||
C: PixelColor + From<<C as PixelColor>::Raw>,
|
||||
BO: ByteOrder,
|
||||
{
|
||||
/// Creates a new image.
|
||||
///
|
||||
/// Only the width of the image needs to be specified. The height of the image will be
|
||||
/// calculated based on the length of the given image data. If the length of the image data
|
||||
/// isn't an integer multiple of the data length for a single row the last partial row will
|
||||
/// be ignored.
|
||||
pub const fn new(data: &'a [u8], width: u32) -> Self {
|
||||
// Prevent panic for `width == 0` by returning a zero sized image.
|
||||
if width == 0 {
|
||||
return Self {
|
||||
data: &[],
|
||||
size: Size::zero(),
|
||||
pixel_type: PhantomData,
|
||||
byte_order: PhantomData,
|
||||
};
|
||||
}
|
||||
|
||||
let height = data.len() / bytes_per_row(width, C::Raw::BITS_PER_PIXEL);
|
||||
|
||||
Self {
|
||||
data,
|
||||
size: Size::new(width, height as u32),
|
||||
pixel_type: PhantomData,
|
||||
byte_order: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the actual row width in pixels.
|
||||
///
|
||||
/// For images with less than 8 bits per pixel each row is padded to contain an integer number
|
||||
/// of bytes. This method returns the width of each row including the padding pixels.
|
||||
const fn data_width(&self) -> u32 {
|
||||
if C::Raw::BITS_PER_PIXEL < 8 {
|
||||
let pixels_per_byte = 8 / C::Raw::BITS_PER_PIXEL as u32;
|
||||
|
||||
bytes_per_row(self.size.width, C::Raw::BITS_PER_PIXEL) as u32 * pixels_per_byte
|
||||
} else {
|
||||
self.size.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the length of each row in bytes.
|
||||
const fn bytes_per_row(width: u32, bits_per_pixel: usize) -> usize {
|
||||
(width as usize * bits_per_pixel + 7) / 8
|
||||
}
|
||||
|
||||
impl<'a, C, BO> ImageDrawable for ImageRaw<'a, C, BO>
|
||||
where
|
||||
C: PixelColor + From<<C as PixelColor>::Raw>,
|
||||
BO: ByteOrder,
|
||||
RawDataSlice<'a, C::Raw, BO>: IntoIterator<Item = C::Raw>,
|
||||
{
|
||||
type Color = C;
|
||||
|
||||
fn draw<D>(&self, target: &mut D) -> Result<(), D::Error>
|
||||
where
|
||||
D: DrawTarget<Color = C>,
|
||||
{
|
||||
let row_skip = self.data_width() - self.size.width;
|
||||
|
||||
target.fill_contiguous(
|
||||
&self.bounding_box(),
|
||||
ContiguousPixels::new(self, self.size, 0, row_skip as usize),
|
||||
)
|
||||
}
|
||||
|
||||
fn draw_sub_image<D>(&self, target: &mut D, area: &Rectangle) -> Result<(), D::Error>
|
||||
where
|
||||
D: DrawTarget<Color = Self::Color>,
|
||||
{
|
||||
// Don't draw anything if `area` is zero sized or partially outside the image.
|
||||
if area.is_zero_sized()
|
||||
|| area.top_left.x < 0
|
||||
|| area.top_left.y < 0
|
||||
|| area.top_left.x as u32 + area.size.width > self.size.width
|
||||
|| area.top_left.y as u32 + area.size.height > self.size.height
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let data_width = self.data_width() as usize;
|
||||
|
||||
let initial_skip = area.top_left.y as usize * data_width + area.top_left.x as usize;
|
||||
let row_skip = data_width - area.size.width as usize;
|
||||
|
||||
target.fill_contiguous(
|
||||
&Rectangle::new(Point::zero(), area.size),
|
||||
ContiguousPixels::new(self, area.size, initial_skip, row_skip),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, BO> OriginDimensions for ImageRaw<'_, C, BO>
|
||||
where
|
||||
C: PixelColor + From<<C as PixelColor>::Raw>,
|
||||
BO: ByteOrder,
|
||||
{
|
||||
fn size(&self) -> Size {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, C, BO> GetPixel for ImageRaw<'a, C, BO>
|
||||
where
|
||||
C: PixelColor + From<<C as PixelColor>::Raw>,
|
||||
BO: ByteOrder,
|
||||
RawDataSlice<'a, C::Raw, BO>: IntoIterator<Item = C::Raw>,
|
||||
{
|
||||
type Color = C;
|
||||
|
||||
fn pixel(&self, p: Point) -> Option<Self::Color> {
|
||||
if p.x < 0 || p.y < 0 || p.x >= self.size.width as i32 || p.y >= self.size.height as i32 {
|
||||
return None;
|
||||
}
|
||||
|
||||
RawDataSlice::new(self.data)
|
||||
.into_iter()
|
||||
.nth(p.x as usize + p.y as usize * self.data_width() as usize)
|
||||
.map(|r| r.into())
|
||||
}
|
||||
}
|
||||
|
||||
struct ContiguousPixels<'a, C, BO>
|
||||
where
|
||||
C: PixelColor + From<<C as PixelColor>::Raw>,
|
||||
BO: ByteOrder,
|
||||
RawDataSlice<'a, C::Raw, BO>: IntoIterator<Item = C::Raw>,
|
||||
{
|
||||
iter: <RawDataSlice<'a, C::Raw, BO> as IntoIterator>::IntoIter,
|
||||
|
||||
remaining_x: u32,
|
||||
width: u32,
|
||||
|
||||
remaining_y: u32,
|
||||
row_skip: usize,
|
||||
}
|
||||
|
||||
impl<'a, C, BO> ContiguousPixels<'a, C, BO>
|
||||
where
|
||||
C: PixelColor + From<<C as PixelColor>::Raw>,
|
||||
BO: ByteOrder,
|
||||
RawDataSlice<'a, C::Raw, BO>: IntoIterator<Item = C::Raw>,
|
||||
{
|
||||
fn new(image: &ImageRaw<'a, C, BO>, size: Size, initial_skip: usize, row_skip: usize) -> Self {
|
||||
let mut iter = RawDataSlice::new(image.data).into_iter();
|
||||
|
||||
if initial_skip > 0 {
|
||||
iter.nth(initial_skip - 1);
|
||||
}
|
||||
|
||||
// Set `remaining_y` to `0` if `width == 0` to prevent integer underflow in `next`.
|
||||
let remaining_y = if size.width > 0 { size.height } else { 0 };
|
||||
|
||||
Self {
|
||||
iter,
|
||||
remaining_x: size.width,
|
||||
width: size.width,
|
||||
remaining_y,
|
||||
row_skip,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, C, BO> Iterator for ContiguousPixels<'a, C, BO>
|
||||
where
|
||||
C: PixelColor + From<<C as PixelColor>::Raw>,
|
||||
BO: ByteOrder,
|
||||
RawDataSlice<'a, C::Raw, BO>: IntoIterator<Item = C::Raw>,
|
||||
{
|
||||
type Item = C;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.remaining_x > 0 {
|
||||
self.remaining_x -= 1;
|
||||
|
||||
self.iter.next()
|
||||
} else {
|
||||
if self.remaining_y == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.remaining_y -= 1;
|
||||
self.remaining_x = self.width - 1;
|
||||
|
||||
self.iter.nth(self.row_skip)
|
||||
}
|
||||
.map(|c| c.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
draw_target::DrawTarget,
|
||||
geometry::Point,
|
||||
image::Image,
|
||||
iterator::PixelIteratorExt,
|
||||
mock_display::{ColorMapping, MockDisplay},
|
||||
pixelcolor::{raw::RawU32, *},
|
||||
Drawable, Pixel,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
|
||||
struct TestColorU32(RawU32);
|
||||
|
||||
impl PixelColor for TestColorU32 {
|
||||
type Raw = RawU32;
|
||||
}
|
||||
|
||||
impl From<RawU32> for TestColorU32 {
|
||||
fn from(data: RawU32) -> Self {
|
||||
Self(data)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests if the given image data matches an excepted `MockDisplay` pattern.
|
||||
fn assert_pattern<C, BO>(image_data: ImageRaw<C, BO>, expected_pattern: &[&str])
|
||||
where
|
||||
C: PixelColor + From<<C as PixelColor>::Raw> + ColorMapping,
|
||||
BO: ByteOrder,
|
||||
for<'a> RawDataSlice<'a, C::Raw, BO>: IntoIterator<Item = C::Raw>,
|
||||
{
|
||||
let image = Image::new(&image_data, Point::zero());
|
||||
let mut display = MockDisplay::new();
|
||||
image.draw(&mut display).unwrap();
|
||||
|
||||
display.assert_pattern(expected_pattern);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_dimensions() {
|
||||
let data = [
|
||||
0xAA, 0x00, //
|
||||
0x55, 0xFF, //
|
||||
0xAA, 0x80, //
|
||||
];
|
||||
let image_data: ImageRaw<BinaryColor> = ImageRaw::new(&data, 9);
|
||||
|
||||
assert_eq!(image_data.size(), Size::new(9, 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncated_data() {
|
||||
let data = [
|
||||
0xAA, 0x00, //
|
||||
0x55, 0xFF, //
|
||||
0xAA, //
|
||||
];
|
||||
let image_data: ImageRaw<BinaryColor> = ImageRaw::new(&data, 9);
|
||||
|
||||
assert_pattern(
|
||||
image_data,
|
||||
&[
|
||||
"#.#.#.#..", //
|
||||
".#.#.#.##", //
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bpp1_new() {
|
||||
let data = [
|
||||
0xAA, 0x00, //
|
||||
0x55, 0xFF, //
|
||||
0xAA, 0x80, //
|
||||
];
|
||||
let image_data: ImageRaw<BinaryColor> = ImageRaw::new(&data, 9);
|
||||
|
||||
assert_pattern(
|
||||
image_data,
|
||||
&[
|
||||
"#.#.#.#..", //
|
||||
".#.#.#.##", //
|
||||
"#.#.#.#.#", //
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bpp1_get_pixel() {
|
||||
let data = [
|
||||
0xAA, 0x00, //
|
||||
0x55, 0xFF, //
|
||||
0xAA, 0x80, //
|
||||
];
|
||||
let image_data: ImageRaw<BinaryColor> = ImageRaw::new(&data, 9);
|
||||
|
||||
assert_eq!(image_data.pixel(Point::new(0, 0)), Some(BinaryColor::On));
|
||||
assert_eq!(image_data.pixel(Point::new(8, 0)), Some(BinaryColor::Off));
|
||||
assert_eq!(image_data.pixel(Point::new(0, 1)), Some(BinaryColor::Off));
|
||||
assert_eq!(image_data.pixel(Point::new(8, 1)), Some(BinaryColor::On));
|
||||
assert_eq!(image_data.pixel(Point::new(0, 2)), Some(BinaryColor::On));
|
||||
assert_eq!(image_data.pixel(Point::new(8, 2)), Some(BinaryColor::On));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bpp2() {
|
||||
let data = [
|
||||
0b00_01_10_11, //
|
||||
0b00_00_00_00, //
|
||||
0b11_10_01_00, //
|
||||
0b11_11_11_11, //
|
||||
];
|
||||
let image_data: ImageRaw<Gray2> = ImageRaw::new(&data, 5);
|
||||
|
||||
assert_pattern(
|
||||
image_data,
|
||||
&[
|
||||
"01230", //
|
||||
"32103", //
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bpp4() {
|
||||
let data = [
|
||||
0b0001_1000, //
|
||||
0b1111_0000, //
|
||||
0b0101_1010, //
|
||||
0b0000_0000, //
|
||||
];
|
||||
let image_data: ImageRaw<Gray4> = ImageRaw::new(&data, 3);
|
||||
|
||||
assert_pattern(
|
||||
image_data,
|
||||
&[
|
||||
"18F", //
|
||||
"5A0", //
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bpp8_1() {
|
||||
let data = [
|
||||
0x11, 0x22, //
|
||||
0x33, 0x44, //
|
||||
0x55, 0x66, //
|
||||
];
|
||||
let image_data: ImageRaw<Gray8> = ImageRaw::new(&data, 2);
|
||||
|
||||
assert_pattern(
|
||||
image_data,
|
||||
&[
|
||||
"12", //
|
||||
"34", //
|
||||
"56", //
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Additional test for luma values with different low and high nibbles,
|
||||
/// which are not supported by `MockDisplay` patterns.
|
||||
#[test]
|
||||
fn bpp8_2() {
|
||||
let data = [0x01, 0x08, 0x10, 0x80];
|
||||
let image_data: ImageRaw<Gray8> = ImageRaw::new(&data, 4);
|
||||
|
||||
let mut display = MockDisplay::new();
|
||||
Image::new(&image_data, Point::zero())
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
let mut expected = MockDisplay::new();
|
||||
expected
|
||||
.fill_contiguous(
|
||||
&expected.bounding_box(),
|
||||
data.iter().copied().map(Gray8::new),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
display.assert_eq(&expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bpp16_little_endian() {
|
||||
let data = [
|
||||
0x00, 0xF8, //
|
||||
0xE0, 0x07, //
|
||||
0x1F, 0x00, //
|
||||
0x00, 0x00, //
|
||||
];
|
||||
let image_data: ImageRawLE<Rgb565> = ImageRaw::new(&data, 1);
|
||||
|
||||
assert_pattern(
|
||||
image_data,
|
||||
&[
|
||||
"R", //
|
||||
"G", //
|
||||
"B", //
|
||||
"K", //
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bpp16_big_endian() {
|
||||
let data = [
|
||||
0xF8, 0x00, //
|
||||
0x07, 0xE0, //
|
||||
0x00, 0x1F, //
|
||||
0x00, 0x00, //
|
||||
];
|
||||
let image_data: ImageRawBE<Rgb565> = ImageRaw::new(&data, 2);
|
||||
|
||||
assert_pattern(
|
||||
image_data,
|
||||
&[
|
||||
"RG", //
|
||||
"BK", //
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bpp16_big_endian_get_pixel() {
|
||||
let data = [
|
||||
0xF8, 0x00, //
|
||||
0x07, 0xE0, //
|
||||
0x00, 0x1F, //
|
||||
0x00, 0x00, //
|
||||
];
|
||||
let image_data: ImageRawBE<Rgb565> = ImageRaw::new(&data, 2);
|
||||
|
||||
assert_eq!(image_data.pixel(Point::new(0, 0)), Some(Rgb565::RED));
|
||||
assert_eq!(image_data.pixel(Point::new(1, 0)), Some(Rgb565::GREEN));
|
||||
assert_eq!(image_data.pixel(Point::new(0, 1)), Some(Rgb565::BLUE));
|
||||
assert_eq!(image_data.pixel(Point::new(1, 1)), Some(Rgb565::BLACK));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bpp24_little_endian() {
|
||||
let data = [
|
||||
0xFF, 0x00, 0x00, //
|
||||
0x00, 0xFF, 0x00, //
|
||||
0x00, 0x00, 0xFF, //
|
||||
0x00, 0x00, 0x00, //
|
||||
];
|
||||
let image_data: ImageRawLE<Bgr888> = ImageRaw::new(&data, 1);
|
||||
|
||||
assert_pattern(
|
||||
image_data,
|
||||
&[
|
||||
"R", //
|
||||
"G", //
|
||||
"B", //
|
||||
"K", //
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bpp24_big_endian() {
|
||||
let data = [
|
||||
0xFF, 0x00, 0x00, //
|
||||
0x00, 0xFF, 0x00, //
|
||||
0x00, 0x00, 0xFF, //
|
||||
0x00, 0x00, 0x00, //
|
||||
];
|
||||
let image_data: ImageRawBE<Rgb888> = ImageRaw::new(&data, 4);
|
||||
|
||||
assert_pattern(image_data, &["RGBK"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bpp32_little_endian() {
|
||||
let data = [
|
||||
0x12, 0x34, 0x56, 0x78, //
|
||||
0x9A, 0xBC, 0xDE, 0xF0, //
|
||||
0x00, 0x00, 0x00, 0x00, //
|
||||
0xFF, 0xFF, 0xFF, 0xFF, //
|
||||
];
|
||||
let image_data: ImageRawLE<TestColorU32> = ImageRaw::new(&data, 2);
|
||||
|
||||
let mut display = MockDisplay::new();
|
||||
Image::new(&image_data, Point::zero())
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
let expected = [
|
||||
Pixel(Point::new(0, 0), TestColorU32(RawU32::new(0x78563412))),
|
||||
Pixel(Point::new(1, 0), TestColorU32(RawU32::new(0xF0DEBC9A))),
|
||||
Pixel(Point::new(0, 1), TestColorU32(RawU32::new(0x00000000))),
|
||||
Pixel(Point::new(1, 1), TestColorU32(RawU32::new(0xFFFFFFFF))),
|
||||
];
|
||||
|
||||
let mut expected_display = MockDisplay::new();
|
||||
expected
|
||||
.iter()
|
||||
.copied()
|
||||
.draw(&mut expected_display)
|
||||
.unwrap();
|
||||
|
||||
// assert_eq can't be used here because ColorMapping isn't implemented for TestColorU32
|
||||
assert!(display.eq(&expected_display));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bpp32_big_endian() {
|
||||
let data = [
|
||||
0x12, 0x34, 0x56, 0x78, //
|
||||
0x9A, 0xBC, 0xDE, 0xF0, //
|
||||
0x00, 0x00, 0x00, 0x00, //
|
||||
0xFF, 0xFF, 0xFF, 0xFF, //
|
||||
];
|
||||
let image_data: ImageRawBE<TestColorU32> = ImageRaw::new(&data, 4);
|
||||
|
||||
let mut display = MockDisplay::new();
|
||||
Image::new(&image_data, Point::zero())
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
let expected = [
|
||||
Pixel(Point::new(0, 0), TestColorU32(RawU32::new(0x12345678))),
|
||||
Pixel(Point::new(1, 0), TestColorU32(RawU32::new(0x9ABCDEF0))),
|
||||
Pixel(Point::new(2, 0), TestColorU32(RawU32::new(0x00000000))),
|
||||
Pixel(Point::new(3, 0), TestColorU32(RawU32::new(0xFFFFFFFF))),
|
||||
];
|
||||
|
||||
let mut expected_display = MockDisplay::new();
|
||||
expected
|
||||
.iter()
|
||||
.copied()
|
||||
.draw(&mut expected_display)
|
||||
.unwrap();
|
||||
|
||||
// assert_eq can't be used here because ColorMapping isn't implemented for TestColorU32
|
||||
assert!(display.eq(&expected_display));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calculated_height() {
|
||||
let data = [0u8; 1];
|
||||
assert_eq!(ImageRaw::<BinaryColor>::new(&data, 12).size().height, 0);
|
||||
|
||||
let data = [0u8; 2];
|
||||
assert_eq!(ImageRaw::<BinaryColor>::new(&data, 12).size().height, 1);
|
||||
|
||||
let data = [0u8; 3];
|
||||
assert_eq!(ImageRaw::<BinaryColor>::new(&data, 12).size().height, 1);
|
||||
|
||||
let data = [0u8; 4];
|
||||
assert_eq!(ImageRaw::<BinaryColor>::new(&data, 12).size().height, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn binary_image_with_zero_width() {
|
||||
let image = ImageRaw::<BinaryColor>::new(&[], 0);
|
||||
|
||||
assert_eq!(image.size, Size::zero());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pixel_out_of_bounds() {
|
||||
let data = [
|
||||
0xAA, 0x00, //
|
||||
0x55, 0xFF, //
|
||||
0xAA, 0x80, //
|
||||
];
|
||||
let image_data = ImageRaw::<BinaryColor>::new(&data, 9);
|
||||
|
||||
assert_eq!(image_data.pixel(Point::new(-1, 0)), None);
|
||||
assert_eq!(image_data.pixel(Point::new(0, -1)), None);
|
||||
assert_eq!(image_data.pixel(Point::new(9, 0)), None);
|
||||
assert_eq!(image_data.pixel(Point::new(9, 3)), None);
|
||||
}
|
||||
}
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
//! Image support for embedded-graphics
|
||||
//!
|
||||
//! The two main types used to draw images are [`ImageDrawable`] and [`Image`].
|
||||
//!
|
||||
//! [`ImageDrawable`] is implemented to add support for different image formats. This crate includes
|
||||
//! an implementation for [raw pixel data]. Additional implementations for other image formats are
|
||||
//! provided by external crates like [tinybmp] and [tinytga].
|
||||
//!
|
||||
//! The [`Image`] object is used to specify the location at which an [`ImageDrawable`] is drawn.
|
||||
//! Images are drawn relative to their top-left corner.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ## Display an RGB565 raw data image
|
||||
//!
|
||||
//! This example displays a small image created from a raw data array. The image is RGB565 encoded,
|
||||
//! so it uses the `Rgb565` color type.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use embedded_graphics::{
|
||||
//! image::{Image, ImageRaw, ImageRawBE},
|
||||
//! pixelcolor::Rgb565,
|
||||
//! prelude::*,
|
||||
//! };
|
||||
//! # use embedded_graphics::mock_display::MockDisplay as Display;
|
||||
//!
|
||||
//! let mut display: Display<Rgb565> = Display::default();
|
||||
//!
|
||||
//! // Raw big endian image data for demonstration purposes. A real image would likely be much
|
||||
//! // larger.
|
||||
//! let data = [
|
||||
//! 0x00, 0x00, 0xF8, 0x00, 0x07, 0xE0, 0xFF, 0xE0, //
|
||||
//! 0x00, 0x1F, 0x07, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, //
|
||||
//! ];
|
||||
//!
|
||||
//! // Create a raw image instance. Other image formats will require different code to load them.
|
||||
//! // All code after loading is the same for any image format.
|
||||
//! let raw: ImageRawBE<Rgb565> = ImageRaw::new(&data, 4);
|
||||
//!
|
||||
//! // Create an `Image` object to position the image at `Point::zero()`.
|
||||
//! let image = Image::new(&raw, Point::zero());
|
||||
//!
|
||||
//! // Draw the image to the display.
|
||||
//! image.draw(&mut display)?;
|
||||
//!
|
||||
//! # Ok::<(), core::convert::Infallible>(())
|
||||
//! ```
|
||||
//!
|
||||
//! ## Sub images
|
||||
//!
|
||||
//! [`SubImage`]s are used to split a larger image drawables into multiple parts, e.g. to draw a
|
||||
//! single sprite from a sprite atlas as in this example. Use the [`sub_image`] method provided by
|
||||
//! [`ImageDrawableExt`] to get a sub image from an image drawable. [`ImageDrawableExt`] is included
|
||||
//! in the [`prelude`], which this example takes advantage of.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use embedded_graphics::{
|
||||
//! image::{Image, ImageRaw, ImageRawBE},
|
||||
//! pixelcolor::Rgb565,
|
||||
//! prelude::*,
|
||||
//! primitives::Rectangle,
|
||||
//! };
|
||||
//! # use embedded_graphics::mock_display::MockDisplay as Display;
|
||||
//!
|
||||
//! let mut display: Display<Rgb565> = Display::default();
|
||||
//!
|
||||
//! let data = [ 0xF8, 0x00, 0x07, 0xE0, 0xFF, 0xE0, /* ... */ ];
|
||||
//! // or: let data = include_bytes!("sprite_atlas.raw");
|
||||
//!
|
||||
//! # let data = [0u8; 32 * 16 * 2];
|
||||
//! let sprite_atlas: ImageRawBE<Rgb565> = ImageRaw::new(&data, 32);
|
||||
//!
|
||||
//! // Create individual sub images for each sprite in the sprite atlas.
|
||||
//! // The position and size of the sub images is defined by a `Rectangle`.
|
||||
//! let sprite_1 = sprite_atlas.sub_image(&Rectangle::new(Point::new(0, 0), Size::new(16, 16)));
|
||||
//! let sprite_2 = sprite_atlas.sub_image(&Rectangle::new(Point::new(16, 0), Size::new(16, 16)));
|
||||
//!
|
||||
//! // Create `Image` objects to draw the sprites at different positions on the display.
|
||||
//! Image::new(&sprite_1, Point::new(10, 10)).draw(&mut display)?;
|
||||
//! Image::new(&sprite_2, Point::new(40, 30)).draw(&mut display)?;
|
||||
//!
|
||||
//! # Ok::<(), core::convert::Infallible>(())
|
||||
//! ```
|
||||
//!
|
||||
//! # Implementing new image formats
|
||||
//!
|
||||
//! To add embedded-graphics support for an new image format the [`ImageDrawable`] and
|
||||
//! [`OriginDimensions`] traits must be implemented. See the [`ImageDrawable`] documentation
|
||||
//! for more information.
|
||||
//!
|
||||
//! [tinytga]: https://crates.io/crates/tinytga
|
||||
//! [tinybmp]: https://crates.io/crates/tinybmp
|
||||
//! [raw pixel data]: ImageRaw
|
||||
//! [`sub_image`]: ImageDrawableExt::sub_image
|
||||
//! [`OriginDimensions`]: super::geometry::OriginDimensions
|
||||
//! [`prelude`]: super::prelude
|
||||
|
||||
mod image_drawable_ext;
|
||||
mod image_raw;
|
||||
mod sub_image;
|
||||
|
||||
pub use embedded_graphics_core::image::{GetPixel, ImageDrawable};
|
||||
pub use image_drawable_ext::ImageDrawableExt;
|
||||
pub use image_raw::{ImageRaw, ImageRawBE, ImageRawLE};
|
||||
pub use sub_image::SubImage;
|
||||
|
||||
use crate::{
|
||||
draw_target::{DrawTarget, DrawTargetExt},
|
||||
geometry::{Dimensions, OriginDimensions, Point},
|
||||
primitives::Rectangle,
|
||||
transform::Transform,
|
||||
Drawable,
|
||||
};
|
||||
use core::fmt::Debug;
|
||||
|
||||
/// Image object.
|
||||
///
|
||||
/// The `Image` struct serves as a wrapper around an [`ImageDrawable`] that provides support for
|
||||
/// an image format (raw bytes, BMP, TGA, etc). It allows an image to be repositioned using
|
||||
/// [`Transform::translate`] or [`Transform::translate_mut`] and drawn to a display that
|
||||
/// implements the [`DrawTarget`] trait.
|
||||
///
|
||||
/// Refer to the [module documentation] for examples.
|
||||
///
|
||||
/// [module documentation]: self
|
||||
/// [`Transform::translate`]: super::transform::Transform::translate
|
||||
/// [`Transform::translate_mut`]: super::transform::Transform::translate_mut
|
||||
/// [`DrawTarget`]: super::draw_target::DrawTarget
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
|
||||
pub struct Image<'a, T> {
|
||||
image_drawable: &'a T,
|
||||
offset: Point,
|
||||
}
|
||||
|
||||
impl<'a, T> Image<'a, T>
|
||||
where
|
||||
T: ImageDrawable,
|
||||
{
|
||||
/// Creates a new `Image`.
|
||||
pub const fn new(image_drawable: &'a T, position: Point) -> Self {
|
||||
Self {
|
||||
image_drawable,
|
||||
offset: position,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `Image` centered around a given point.
|
||||
pub fn with_center(image_drawable: &'a T, center: Point) -> Self {
|
||||
let offset = Rectangle::with_center(center, image_drawable.size()).top_left;
|
||||
|
||||
Self {
|
||||
image_drawable,
|
||||
offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Transform for Image<'_, T> {
|
||||
/// Translate the image by a given delta, returning a new image
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ## Move an image around
|
||||
///
|
||||
/// This examples moves a 4x4 black and white image by `(10, 20)` pixels without mutating the
|
||||
/// original image
|
||||
///
|
||||
/// ```rust
|
||||
/// use embedded_graphics::{
|
||||
/// geometry::Point,
|
||||
/// image::{Image, ImageRaw},
|
||||
/// pixelcolor::BinaryColor,
|
||||
/// prelude::*,
|
||||
/// };
|
||||
///
|
||||
/// let image: ImageRaw<BinaryColor> = ImageRaw::new(&[0xff, 0x00, 0xff, 0x00], 4);
|
||||
///
|
||||
/// let image = Image::new(&image, Point::zero());
|
||||
///
|
||||
/// let image_moved = image.translate(Point::new(10, 20));
|
||||
///
|
||||
/// assert_eq!(image.bounding_box().top_left, Point::zero());
|
||||
/// assert_eq!(image_moved.bounding_box().top_left, Point::new(10, 20));
|
||||
/// ```
|
||||
fn translate(&self, by: Point) -> Self {
|
||||
Self {
|
||||
image_drawable: self.image_drawable,
|
||||
offset: self.offset + by,
|
||||
}
|
||||
}
|
||||
|
||||
/// Translate the image by a given delta, modifying the original object
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ## Move an image around
|
||||
///
|
||||
/// This examples moves a 4x4 black and white image by `(10, 20)` pixels by mutating the
|
||||
/// original image
|
||||
///
|
||||
/// ```rust
|
||||
/// use embedded_graphics::{
|
||||
/// geometry::Point,
|
||||
/// image::{Image, ImageRaw},
|
||||
/// pixelcolor::BinaryColor,
|
||||
/// prelude::*,
|
||||
/// };
|
||||
///
|
||||
/// let image: ImageRaw<BinaryColor> = ImageRaw::new(&[0xff, 0x00, 0xff, 0x00], 4);
|
||||
///
|
||||
/// let mut image = Image::new(&image, Point::zero());
|
||||
///
|
||||
/// image.translate_mut(Point::new(10, 20));
|
||||
///
|
||||
/// assert_eq!(image.bounding_box().top_left, Point::new(10, 20));
|
||||
/// ```
|
||||
fn translate_mut(&mut self, by: Point) -> &mut Self {
|
||||
self.offset += by;
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Drawable for Image<'a, T>
|
||||
where
|
||||
T: ImageDrawable,
|
||||
{
|
||||
type Color = T::Color;
|
||||
type Output = ();
|
||||
|
||||
fn draw<D>(&self, display: &mut D) -> Result<Self::Output, D::Error>
|
||||
where
|
||||
D: DrawTarget<Color = Self::Color>,
|
||||
{
|
||||
self.image_drawable
|
||||
.draw(&mut display.translated(self.offset))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Dimensions for Image<'a, T>
|
||||
where
|
||||
T: OriginDimensions,
|
||||
{
|
||||
fn bounding_box(&self) -> Rectangle {
|
||||
self.image_drawable.bounding_box().translate(self.offset)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{geometry::Size, mock_display::MockDisplay, pixelcolor::BinaryColor};
|
||||
|
||||
#[test]
|
||||
fn negative_top_left() {
|
||||
let image: ImageRaw<BinaryColor> = ImageRaw::new(&[0xff, 0x00, 0xff, 0x00], 4);
|
||||
|
||||
let image = Image::new(&image, Point::zero()).translate(Point::new(-1, -1));
|
||||
|
||||
assert_eq!(
|
||||
image.bounding_box(),
|
||||
Rectangle::new(Point::new(-1, -1), Size::new(4, 4))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dimensions() {
|
||||
let image: ImageRaw<BinaryColor> = ImageRaw::new(&[0xff, 0x00, 0xFF, 0x00], 4);
|
||||
|
||||
let image = Image::new(&image, Point::zero()).translate(Point::new(100, 200));
|
||||
|
||||
assert_eq!(
|
||||
image.bounding_box(),
|
||||
Rectangle::new(Point::new(100, 200), Size::new(4, 4))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn position() {
|
||||
let image_raw: ImageRaw<BinaryColor> = ImageRaw::new(&[0xAA, 0x55, 0xAA, 0x55], 4);
|
||||
|
||||
let mut display = MockDisplay::new();
|
||||
Image::new(&image_raw, Point::new(1, 2))
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
display.assert_pattern(&[
|
||||
" ", //
|
||||
" ", //
|
||||
" #.#.", //
|
||||
" .#.#", //
|
||||
" #.#.", //
|
||||
" .#.#", //
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_center() {
|
||||
let image_raw: ImageRaw<BinaryColor> = ImageRaw::new(&[0xAA, 0x55, 0xAA, 0x55], 4);
|
||||
|
||||
let mut display = MockDisplay::new();
|
||||
Image::with_center(&image_raw, Point::new(1, 2))
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
display.assert_pattern(&[
|
||||
" ", //
|
||||
"#.#.", //
|
||||
".#.#", //
|
||||
"#.#.", //
|
||||
".#.#", //
|
||||
]);
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
use crate::{
|
||||
draw_target::DrawTarget,
|
||||
geometry::{Dimensions, OriginDimensions},
|
||||
image::ImageDrawable,
|
||||
primitives::Rectangle,
|
||||
transform::Transform,
|
||||
};
|
||||
|
||||
/// Sub image.
|
||||
///
|
||||
/// A sub image is rectangular subsection of an [`ImageDrawable`]. It can, for example, be used to
|
||||
/// draw individual sprites from a larger sprite atlas.
|
||||
///
|
||||
/// To create a sub image call the [`sub_image`] method on the parent [`ImageDrawable`]. See the
|
||||
/// [module-level documentation] for an example.
|
||||
///
|
||||
/// [`sub_image`]: trait.ImageDrawableExt.html#tymethod.sub_image
|
||||
/// [module-level documentation]: super#sub-images
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
|
||||
pub struct SubImage<'a, T> {
|
||||
parent: &'a T,
|
||||
area: Rectangle,
|
||||
}
|
||||
|
||||
impl<'a, T> SubImage<'a, T>
|
||||
where
|
||||
T: ImageDrawable,
|
||||
{
|
||||
pub(super) fn new(parent: &'a T, area: &Rectangle) -> Self {
|
||||
let area = parent.bounding_box().intersection(area);
|
||||
|
||||
Self { parent, area }
|
||||
}
|
||||
|
||||
pub(crate) const fn new_unchecked(parent: &'a T, area: Rectangle) -> Self {
|
||||
Self { parent, area }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> OriginDimensions for SubImage<'_, T> {
|
||||
fn size(&self) -> crate::prelude::Size {
|
||||
self.area.size
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> ImageDrawable for SubImage<'a, T>
|
||||
where
|
||||
T: ImageDrawable,
|
||||
{
|
||||
type Color = T::Color;
|
||||
|
||||
fn draw<DT>(&self, target: &mut DT) -> Result<(), DT::Error>
|
||||
where
|
||||
DT: DrawTarget<Color = Self::Color>,
|
||||
{
|
||||
self.parent.draw_sub_image(target, &self.area)
|
||||
}
|
||||
|
||||
fn draw_sub_image<DT>(&self, target: &mut DT, area: &Rectangle) -> Result<(), DT::Error>
|
||||
where
|
||||
DT: DrawTarget<Color = Self::Color>,
|
||||
{
|
||||
let area = area.translate(self.area.top_left);
|
||||
|
||||
self.parent.draw_sub_image(target, &area)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
geometry::{Point, Size},
|
||||
image::ImageDrawableExt,
|
||||
mock_display::MockDisplay,
|
||||
pixelcolor::BinaryColor,
|
||||
};
|
||||
|
||||
struct MockImageDrawable {
|
||||
expected_area: Rectangle,
|
||||
}
|
||||
|
||||
impl ImageDrawable for MockImageDrawable {
|
||||
type Color = BinaryColor;
|
||||
|
||||
fn draw<DT>(&self, _target: &mut DT) -> Result<(), DT::Error>
|
||||
where
|
||||
DT: DrawTarget<Color = BinaryColor>,
|
||||
{
|
||||
panic!("draw shouldn't have been called on MockImageDrawable")
|
||||
}
|
||||
|
||||
fn draw_sub_image<DT>(&self, _target: &mut DT, area: &Rectangle) -> Result<(), DT::Error>
|
||||
where
|
||||
DT: DrawTarget<Color = BinaryColor>,
|
||||
{
|
||||
assert_eq!(area, &self.expected_area);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginDimensions for MockImageDrawable {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(8, 10)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_image() {
|
||||
let area = Rectangle::new(Point::new(2, 3), Size::new(3, 4));
|
||||
|
||||
let image = MockImageDrawable {
|
||||
expected_area: area,
|
||||
};
|
||||
|
||||
let mut display = MockDisplay::new();
|
||||
image.sub_image(&area).draw(&mut display).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn area_larger_than_parent() {
|
||||
let area = Rectangle::new(Point::new(-5, -5), Size::new(20, 20));
|
||||
|
||||
let image = MockImageDrawable {
|
||||
expected_area: Rectangle::new(Point::zero(), Size::new(8, 10)),
|
||||
};
|
||||
|
||||
let mut display = MockDisplay::new();
|
||||
image.sub_image(&area).draw(&mut display).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_image_of_sub_image() {
|
||||
let area1 = Rectangle::new(Point::new(2, 3), Size::new(3, 4));
|
||||
let area2 = Rectangle::new(Point::new(1, 1), Size::new(2, 2));
|
||||
|
||||
let image = MockImageDrawable {
|
||||
expected_area: Rectangle::new(area1.top_left + area2.top_left, area2.size),
|
||||
};
|
||||
|
||||
let mut display = MockDisplay::new();
|
||||
image
|
||||
.sub_image(&area1)
|
||||
.sub_image(&area2)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_image_of_sub_image_area_larger_than_parent() {
|
||||
let area1 = Rectangle::new(Point::new(2, 3), Size::new(3, 4));
|
||||
let area2 = Rectangle::new(Point::new(-10, -10), Size::new(20, 20));
|
||||
|
||||
let image = MockImageDrawable {
|
||||
expected_area: area1,
|
||||
};
|
||||
|
||||
let mut display = MockDisplay::new();
|
||||
image
|
||||
.sub_image(&area1)
|
||||
.sub_image(&area2)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user