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:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+425
View File
@@ -0,0 +1,425 @@
//! A target for embedded-graphics drawing operations.
use crate::{
geometry::Dimensions,
pixelcolor::PixelColor,
primitives::{PointsIter, Rectangle},
Pixel,
};
/// A target for embedded-graphics drawing operations.
///
/// The `DrawTarget` trait is used to add embedded-graphics support to a display
/// driver or similar targets like framebuffers or image files.
/// Targets are required to at least implement the [`draw_iter`] method and the [`Dimensions`]
/// trait. All other methods provide default implementations which use these methods internally.
///
/// Because the default implementations cannot use features specific to the target hardware they
/// can be overridden to improve performance. These target specific implementations might, for
/// example, use hardware accelerated drawing operations provided by a display controller or
/// specialized hardware modules in a microcontroller.
///
/// Note that some displays require a "flush" operation to write changes from a framebuffer to the
/// display. See docs associated with the chosen display driver for details on how to update the
/// display.
///
/// # Examples
///
/// ## Minimum implementation
///
/// In this example `DrawTarget` is implemented for an an imaginary 64px x 64px 8-bit grayscale display
/// that is connected using a simplified SPI interface. Because the hardware doesn't support any
/// acceleration only the [`draw_iter`] method and [`OriginDimensions`] trait need to be implemented.
///
/// To reduce the overhead caused by communicating with the display for each drawing operation
/// the display driver uses and framebuffer to store the pixel data in memory. This way all drawing
/// operations can be executed in local memory and the actual display is only updated on demand
/// by calling the `flush` method.
///
/// Because all drawing operations are using a local framebuffer no communication error can occur
/// while they are executed and the [`Error` type] can be set to `core::convert::Infallible`.
///
/// ```rust
/// use core::convert::TryInto;
/// use embedded_graphics::{
/// pixelcolor::{Gray8, GrayColor},
/// prelude::*,
/// primitives::{Circle, PrimitiveStyle},
/// };
/// #
/// # struct SPI1;
/// #
/// # impl SPI1 {
/// # pub fn send_bytes(&self, buf: &[u8]) -> Result<(), CommError> {
/// # Ok(())
/// # }
/// # }
/// #
///
/// /// SPI communication error
/// #[derive(Debug)]
/// struct CommError;
///
/// /// A fake 64px x 64px display.
/// struct ExampleDisplay {
/// /// The framebuffer with one `u8` value per pixel.
/// framebuffer: [u8; 64 * 64],
///
/// /// The interface to the display controller.
/// iface: SPI1,
/// }
///
/// impl ExampleDisplay {
/// /// Updates the display from the framebuffer.
/// pub fn flush(&self) -> Result<(), CommError> {
/// self.iface.send_bytes(&self.framebuffer)
/// }
/// }
///
/// impl DrawTarget for ExampleDisplay {
/// type Color = Gray8;
/// // `ExampleDisplay` uses a framebuffer and doesn't need to communicate with the display
/// // controller to draw pixel, which means that drawing operations can never fail. To reflect
/// // this the type `Infallible` was chosen as the `Error` type.
/// type Error = core::convert::Infallible;
///
/// fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
/// where
/// I: IntoIterator<Item = Pixel<Self::Color>>,
/// {
/// for Pixel(coord, color) in pixels.into_iter() {
/// // Check if the pixel coordinates are out of bounds (negative or greater than
/// // (63,63)). `DrawTarget` implementation are required to discard any out of bounds
/// // pixels without returning an error or causing a panic.
/// if let Ok((x @ 0..=63, y @ 0..=63)) = coord.try_into() {
/// // Calculate the index in the framebuffer.
/// let index: u32 = x + y * 64;
/// self.framebuffer[index as usize] = color.luma();
/// }
/// }
///
/// Ok(())
/// }
/// }
///
/// impl OriginDimensions for ExampleDisplay {
/// fn size(&self) -> Size {
/// Size::new(64, 64)
/// }
/// }
///
/// let mut display = ExampleDisplay {
/// framebuffer: [0; 4096],
/// iface: SPI1,
/// };
///
/// // Draw a circle with top-left at `(22, 22)` with a diameter of `20` and a white stroke
/// let circle = Circle::new(Point::new(22, 22), 20)
/// .into_styled(PrimitiveStyle::with_stroke(Gray8::WHITE, 1));
///
/// circle.draw(&mut display)?;
///
/// // Update the display
/// display.flush().unwrap();
/// # Ok::<(), core::convert::Infallible>(())
/// ```
///
/// # Hardware acceleration - solid rectangular fill
///
/// This example uses an imaginary display with 16bpp RGB565 colors and hardware support for
/// filling of rectangular areas with a solid color. A real display controller that supports this
/// operation is the SSD1331 with it's "Draw Rectangle" (`22h`) command which this example
/// is loosely based on.
///
/// To leverage this feature in a `DrawTarget`, the default implementation of [`fill_solid`] can be
/// overridden by a custom implementation. Instead of drawing individual pixels, this target
/// specific version will only send a single command to the display controller in one transaction.
/// Because the command size is independent of the filled area, all [`fill_solid`] calls will only
/// transmit 8 bytes to the display, which is far less then what is required to transmit each pixel
/// color inside the filled area.
/// ```rust
/// use core::convert::TryInto;
/// use embedded_graphics::{
/// pixelcolor::{raw::RawU16, Rgb565, RgbColor},
/// prelude::*,
/// primitives::{Circle, Rectangle, PrimitiveStyle, PrimitiveStyleBuilder},
/// };
/// #
/// # struct SPI1;
/// #
/// # impl SPI1 {
/// # pub fn send_bytes(&self, buf: &[u16]) -> Result<(), ()> {
/// # Ok(())
/// # }
/// # }
/// #
///
/// /// SPI communication error
/// #[derive(Debug)]
/// struct CommError;
///
/// /// An example display connected over SPI.
/// struct ExampleDisplay {
/// iface: SPI1,
/// }
///
/// impl ExampleDisplay {
/// /// Send a single pixel to the display
/// pub fn set_pixel(&self, x: u32, y: u32, color: u16) -> Result<(), CommError> {
/// // ...
///
/// Ok(())
/// }
///
/// /// Send commands to the display
/// pub fn send_commands(&self, commands: &[u8]) -> Result<(), CommError> {
/// // Send data marked as commands to the display.
///
/// Ok(())
/// }
/// }
///
/// impl DrawTarget for ExampleDisplay {
/// type Color = Rgb565;
/// type Error = CommError;
///
/// fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
/// where
/// I: IntoIterator<Item = Pixel<Self::Color>>,
/// {
/// for Pixel(coord, color) in pixels.into_iter() {
/// // Check if the pixel coordinates are out of bounds (negative or greater than
/// // (63,63)). `DrawTarget` implementation are required to discard any out of bounds
/// // pixels without returning an error or causing a panic.
/// if let Ok((x @ 0..=63, y @ 0..=63)) = coord.try_into() {
/// self.set_pixel(x, y, RawU16::from(color).into_inner())?;
/// }
/// }
///
/// Ok(())
/// }
///
/// fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
/// // Clamp the rectangle coordinates to the valid range by determining
/// // the intersection of the fill area and the visible display area
/// // by using Rectangle::intersection.
/// let area = area.intersection(&self.bounding_box());
///
/// // Do not send a draw rectangle command if the intersection size if zero.
/// // The size is checked by using `Rectangle::bottom_right`, which returns `None`
/// // if the size is zero.
/// let bottom_right = if let Some(bottom_right) = area.bottom_right() {
/// bottom_right
/// } else {
/// return Ok(());
/// };
///
/// self.send_commands(&[
/// // Draw rectangle command
/// 0x22,
/// // Top left X coordinate
/// area.top_left.x as u8,
/// // Top left Y coordinate
/// area.top_left.y as u8,
/// // Bottom right X coordinate
/// bottom_right.x as u8,
/// // Bottom right Y coordinate
/// bottom_right.y as u8,
/// // Fill color red channel
/// color.r(),
/// // Fill color green channel
/// color.g(),
/// // Fill color blue channel
/// color.b(),
/// ])
/// }
/// }
///
/// impl OriginDimensions for ExampleDisplay {
/// fn size(&self) -> Size {
/// Size::new(64, 64)
/// }
/// }
///
/// let mut display = ExampleDisplay { iface: SPI1 };
///
/// // Draw a rectangle with 5px red stroke and green fill.
/// // The stroke and fill can be broken down into multiple individual rectangles,
/// // so this uses `fill_solid` internally.
/// Rectangle::new(Point::new(20, 20), Size::new(50, 40))
/// .into_styled(
/// PrimitiveStyleBuilder::new()
/// .stroke_color(Rgb565::RED)
/// .stroke_width(5)
/// .fill_color(Rgb565::GREEN)
/// .build(),
/// )
/// .draw(&mut display)?;
///
/// // Draw a circle with top-left at `(5, 5)` with a diameter of `10` and a magenta stroke with
/// // cyan fill. This shape cannot be optimized by calls to `fill_solid` as it contains transparent
/// // pixels as well as pixels of different colors. It will instead delegate to `draw_iter`
/// // internally.
/// Circle::new(Point::new(5, 5), 10)
/// .into_styled(
/// PrimitiveStyleBuilder::new()
/// .stroke_color(Rgb565::MAGENTA)
/// .stroke_width(1)
/// .fill_color(Rgb565::CYAN)
/// .build(),
/// )
/// .draw(&mut display)?;
///
/// # Ok::<(), CommError>(())
/// ```
///
/// [`fill_solid`]: DrawTarget::fill_solid()
/// [`draw_iter`]: DrawTarget::draw_iter
/// [`Dimensions`]: super::geometry::Dimensions
/// [`OriginDimensions`]: super::geometry::OriginDimensions
/// [`Error` type]: DrawTarget::Error
pub trait DrawTarget: Dimensions {
/// The pixel color type the targetted display supports.
type Color: PixelColor;
/// Error type to return when a drawing operation fails.
///
/// This error is returned if an error occurred during a drawing operation. This mainly applies
/// to drivers that need to communicate with the display for each drawing operation, where a
/// communication error can occur. For drivers that use an internal framebuffer where drawing
/// operations can never fail, [`core::convert::Infallible`] can instead be used as the `Error`
/// type.
///
/// [`core::convert::Infallible`]: https://doc.rust-lang.org/stable/core/convert/enum.Infallible.html
type Error;
/// Draw individual pixels to the display without a defined order.
///
/// Due to the unordered nature of the pixel iterator, this method is likely to be the slowest
/// drawing method for a display that writes data to the hardware immediately. If possible, the
/// other methods in this trait should be implemented to improve performance when rendering
/// more contiguous pixel patterns.
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>;
/// Fill a given area with an iterator providing a contiguous stream of pixel colors.
///
/// Use this method to fill an area with contiguous, non-transparent pixel colors. Pixel
/// coordinates are iterated over from the top left to the bottom right corner of the area in
/// row-first order. The provided iterator must provide pixel color values based on this
/// ordering to produce correct output.
///
/// As seen in the example below, the [`PointsIter::points`] method can be used to get an
/// iterator over all points in the provided area.
///
/// The provided iterator is not required to provide `width * height` pixels to completely fill
/// the area. In this case, `fill_contiguous` should return without error.
///
/// This method should not attempt to draw any pixels that fall outside the drawable area of the
/// target display. The `area` argument can be clipped to the drawable area using the
/// [`Rectangle::intersection`] method.
///
/// The default implementation of this method delegates to [`draw_iter`](DrawTarget::draw_iter).
///
/// # Examples
///
/// This is an example implementation of `fill_contiguous` that delegates to [`draw_iter`]. This
/// delegation behaviour is undesirable in a real application as it will be as slow as the
/// default trait implementation, however is shown here for demonstration purposes.
///
/// The example demonstrates the usage of [`Rectangle::intersection`] on the passed `area`
/// argument to only draw visible pixels. If there is no intersection between `area` and the
/// display area, no pixels will be drawn.
///
/// ```rust
/// use embedded_graphics::{
/// pixelcolor::{Gray8, GrayColor},
/// prelude::*,
/// primitives::{ContainsPoint, Rectangle},
/// };
///
/// struct ExampleDisplay;
///
/// impl DrawTarget for ExampleDisplay {
/// type Color = Gray8;
/// type Error = core::convert::Infallible;
///
/// fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
/// where
/// I: IntoIterator<Item = Pixel<Self::Color>>,
/// {
/// // Draw pixels to the display
///
/// Ok(())
/// }
///
/// fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
/// where
/// I: IntoIterator<Item = Self::Color>,
/// {
/// // Clamp area to drawable part of the display target
/// let drawable_area = area.intersection(&self.bounding_box());
///
/// // Check that there are visible pixels to be drawn
/// if drawable_area.size != Size::zero() {
/// self.draw_iter(
/// area.points()
/// .zip(colors)
/// .filter(|(pos, _color)| drawable_area.contains(*pos))
/// .map(|(pos, color)| Pixel(pos, color)),
/// )
/// } else {
/// Ok(())
/// }
/// }
/// }
///
/// impl OriginDimensions for ExampleDisplay {
/// fn size(&self) -> Size {
/// Size::new(64, 64)
/// }
/// }
/// ```
///
/// [`draw_iter`]: DrawTarget::draw_iter
/// [`Rectangle::intersection`]: super::primitives::rectangle::Rectangle::intersection()
/// [`PointsIter::points`]: super::primitives::PointsIter::points
fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Self::Color>,
{
self.draw_iter(
area.points()
.zip(colors)
.map(|(pos, color)| Pixel(pos, color)),
)
}
/// Fill a given area with a solid color.
///
/// If the target display provides optimized hardware commands for filling a rectangular area of
/// the display with a solid color, this method should be overridden to use those commands to
/// improve performance.
///
/// The default implementation of this method calls [`fill_contiguous`](DrawTarget::fill_contiguous())
/// with an iterator that repeats the given `color` for every point in `area`.
fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
self.fill_contiguous(area, core::iter::repeat(color))
}
/// Fill the entire display with a solid color.
///
/// If the target hardware supports a more optimized way of filling the entire display with a
/// solid color, this method should be overridden to use those commands.
///
/// The default implementation of this method delegates to [`fill_solid`] to fill the
/// [`bounding_box`] returned by the [`Dimensions`] implementation.
///
/// [`Dimensions`]: super::geometry::Dimensions
/// [`bounding_box`]: super::geometry::Dimensions::bounding_box
/// [`fill_solid`]: DrawTarget::fill_solid()
fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
self.fill_solid(&self.bounding_box(), color)
}
}
+187
View File
@@ -0,0 +1,187 @@
//! `Drawable` trait and helpers
use crate::{draw_target::DrawTarget, geometry::Point, pixelcolor::PixelColor};
/// Marks an object as "drawable". Must be implemented for all graphics objects
///
/// The `Drawable` trait describes how a particular graphical object is drawn. A `Drawable` object
/// can define its `draw` method as a collection of graphical primitives or as an iterator
/// over pixels being rendered with [`DrawTarget`]'s [`draw_iter`] method.
///
/// ```rust
/// use embedded_graphics::{
/// mono_font::{ascii::FONT_6X9, MonoTextStyle},
/// pixelcolor::{BinaryColor, PixelColor, Rgb888},
/// prelude::*,
/// primitives::{Rectangle, PrimitiveStyle},
/// text::Text,
/// };
///
/// struct Button<'a, C: PixelColor> {
/// top_left: Point,
/// size: Size,
/// bg_color: C,
/// fg_color: C,
/// text: &'a str,
/// }
///
/// impl<C> Drawable for Button<'_, C>
/// where
/// C: PixelColor + From<BinaryColor>,
/// {
/// type Color = C;
/// type Output = ();
///
/// fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
/// where
/// D: DrawTarget<Color = C>,
/// {
/// Rectangle::new(self.top_left, self.size)
/// .into_styled(PrimitiveStyle::with_fill(self.bg_color))
/// .draw(target)?;
///
/// let style = MonoTextStyle::new(&FONT_6X9, self.fg_color);
///
/// Text::new(self.text, Point::new(6, 13), style).draw(target)?;
///
/// Ok(())
/// }
/// }
///
/// let mut button = Button {
/// top_left: Point::zero(),
/// size: Size::new(60, 20),
/// bg_color: Rgb888::RED,
/// fg_color: Rgb888::BLUE,
/// text: "Click me!",
/// };
///
/// # use embedded_graphics::mock_display::MockDisplay;
/// # let mut display = MockDisplay::default();
/// # display.set_allow_overdraw(true);
/// button.draw(&mut display)?;
/// # Ok::<(), core::convert::Infallible>(())
/// ```
///
/// [`DrawTarget`]: crate::draw_target::DrawTarget
/// [`draw_iter`]: crate::draw_target::DrawTarget::draw_iter
pub trait Drawable {
/// The pixel color type.
type Color: PixelColor;
/// The return type of the `draw` method.
///
/// The `Output` type can be used to return results and values produced from the drawing of the
/// current item. For example, rendering two differently styled text items next to each other
/// can make use of a returned value, allowing the next text to be positioned after the first:
///
/// ```
/// use embedded_graphics::{
/// mono_font::{
/// ascii::{FONT_10X20, FONT_6X10},
/// MonoTextStyle,
/// },
/// pixelcolor::BinaryColor,
/// prelude::*,
/// text::Text,
/// };
///
/// # let mut display = embedded_graphics::mock_display::MockDisplay::new();
/// # display.set_allow_out_of_bounds_drawing(true);
/// let label_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
/// let value_style = MonoTextStyle::new(&FONT_10X20, BinaryColor::On);
///
/// let next_point = Text::new("Label ", Point::new(10, 20), label_style)
/// .draw(&mut display)?;
///
/// Text::new("1234", next_point, value_style).draw(&mut display)?;
/// # Ok::<(), core::convert::Infallible>(())
/// ```
///
/// Use `()` if no value should be returned.
type Output;
/// Draw the graphics object using the supplied DrawTarget.
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = Self::Color>;
}
/// A single pixel.
///
/// `Pixel` objects are used to specify the position and color of drawn pixels.
///
/// # Examples
///
/// The [`Drawable`] trait is implemented for `Pixel` which allows single pixels
/// to be drawn to a [`DrawTarget`]:
/// ```
/// use embedded_graphics::{pixelcolor::BinaryColor, prelude::*};
/// # use embedded_graphics::mock_display::MockDisplay;
/// # let mut display = MockDisplay::new();
///
/// Pixel(Point::new(1, 2), BinaryColor::On).draw(&mut display)?;
/// # Ok::<(), core::convert::Infallible>(())
/// ```
///
/// Iterators with `Pixel` items can also be drawn:
///
/// ```
/// use embedded_graphics::{pixelcolor::BinaryColor, prelude::*};
/// # use embedded_graphics::mock_display::MockDisplay;
/// # let mut display = MockDisplay::new();
///
/// (0..32)
/// .map(|i| Pixel(Point::new(i, i * 2), BinaryColor::On))
/// .draw(&mut display)?;
/// # Ok::<(), core::convert::Infallible>(())
/// ```
///
/// [`DrawTarget`]: crate::draw_target::DrawTarget
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub struct Pixel<C>(pub Point, pub C)
where
C: PixelColor;
impl<C> Drawable for Pixel<C>
where
C: PixelColor,
{
type Color = C;
type Output = ();
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = C>,
{
target.draw_iter(core::iter::once(*self))
}
}
#[cfg(test)]
mod tests {
// NOTE: `crate` cannot be used here due to circular dependency resolution behavior.
use embedded_graphics::{
geometry::Point, mock_display::MockDisplay, pixelcolor::BinaryColor, Drawable, Pixel,
};
#[test]
fn draw_pixel() {
let mut display = MockDisplay::new();
Pixel(Point::new(0, 0), BinaryColor::On)
.draw(&mut display)
.unwrap();
Pixel(Point::new(2, 1), BinaryColor::On)
.draw(&mut display)
.unwrap();
Pixel(Point::new(1, 2), BinaryColor::On)
.draw(&mut display)
.unwrap();
display.assert_pattern(&[
"# ", //
" #", //
" # ", //
]);
}
}
+213
View File
@@ -0,0 +1,213 @@
//! Geometry module.
mod point;
mod size;
pub use point::Point;
pub use size::Size;
use crate::primitives::Rectangle;
/// Adds the ability to get the bounding box of an item.
///
/// The exact definition of the bounding box depends on the item:
///
/// * Primitives ([`Rectangle`], [`Circle`], ...)
///
/// For unstyled [primitives] the bounding box is defined as the smallest rectangle that surrounds the entire primitive.
/// * Styled primitives and other [`Drawable`]s ([`Image`], [`Text`], ...)
///
/// The bounding box of a drawable is defined as the smallest rectangle that contains all drawn pixels.
/// While all builtin [`Drawable`]s in embedded-graphics provide an implementation of this trait, this might
/// not be true for third party drawables.
///
/// Note that a styled primitive can have a different bounding box than the underlying unstyled primitive;
/// depending on the stroke width and alignment the bounding box of the styled primitive may be larger.
/// * [`DrawTarget`]s (displays, simulator, ...)
///
/// The bounding box of a draw target is defined as the area that should be used for drawing operations.
/// For most display drivers the top left corner of the bounding box will be at the origin but other draw targets
/// can have different positions of the top left corner.
///
/// The bounding box will be returned as a [`Rectangle`]. The methods provided by [`Rectangle`] make
/// it easy to implement additional functions like hit testing (by using [`contains`]) or drawing a focus
/// rectangle around a drawable (by converting the rectangle into a [`Styled`]).
///
/// # Implementation notes
///
/// `Dimensions` should be implemented for `Drawable`s if the bounding box is known before [`Drawable::draw`] is
/// executed. The implementation must return a rectangle that contains all drawn pixels.
/// [`MockDisplay::affected_area`] can be a used in unit tests to make sure a drawable returns a bounding box with
/// the correct dimensions.
///
/// [`DrawTarget`]s (display drivers, etc) are required to implement `Dimensions`. The
/// implementation must return a rectangle representing the drawing area. For display
/// drivers it is recommended to implement [`OriginDimensions`] instead of implementing `Dimensions` directly,
/// if the top left corner of the display area is at the origin `(0, 0)`.
///
/// The bounding box of [`ImageDrawable`]s must always start at the origin, therefore [`OriginDimensions`] must be implemented instead of this trait.
///
/// [`Drawable`]: super::Drawable
/// [`Drawable::draw`]: super::Drawable::draw
/// [`DrawTarget`]: super::draw_target::DrawTarget
/// [`ImageDrawable`]: super::image::ImageDrawable
/// [`Rectangle`]: super::primitives::rectangle::Rectangle
/// [`points`]: super::primitives::PointsIter
/// [`MockDisplay::affected_area`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/mock_display/struct.MockDisplay.html#method.affected_area
/// [`contains`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/primitives/trait.ContainsPoint.html#tymethod.contains
/// [primitives]: https://docs.rs/embedded-graphics/latest/embedded_graphics/primitives/index.html
/// [`Circle`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/primitives/circle/struct.Circle.html
/// [`Image`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/image/struct.Image.html
/// [`Text`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/fonts/struct.Text.html
/// [`Styled`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/style/styled/struct.Styled.html
pub trait Dimensions {
/// Returns the bounding box.
fn bounding_box(&self) -> Rectangle;
}
/// Dimensions with `top_left` of the bounding box at `(0, 0)`.
///
/// A blanket implementation of `Dimensions` is provided for all types that implement this trait.
/// See the [`Dimensions`] trait documentation for more information about bounding boxes.
///
/// # Implementation notes
///
/// This trait should be implemented instead of [`Dimensions`] if the top left corner of the bounding box
/// will always be at the origin, which will be the case for most display drivers. Some types, like [`ImageDrawable`],
/// require a bounding box that starts at the origin and can only be used if [`OriginDimensions`] is implemented.
///
/// [`ImageDrawable`]: super::image::ImageDrawable
pub trait OriginDimensions {
/// Returns the size of the bounding box.
fn size(&self) -> Size;
}
impl<T> Dimensions for T
where
T: OriginDimensions,
{
fn bounding_box(&self) -> Rectangle {
Rectangle::new(Point::zero(), self.size())
}
}
/// Anchor point.
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub enum AnchorPoint {
/// Top left.
TopLeft,
/// Top center.
TopCenter,
/// Top right.
TopRight,
/// Center left.
CenterLeft,
/// Center.
Center,
/// Center right.
CenterRight,
/// Bottom left.
BottomLeft,
/// Bottom center.
BottomCenter,
/// Bottom right.
BottomRight,
}
impl AnchorPoint {
/// Creates an anchor point from an X and Y component.
pub fn from_xy(x: AnchorX, y: AnchorY) -> Self {
match (y, x) {
(AnchorY::Top, AnchorX::Left) => AnchorPoint::TopLeft,
(AnchorY::Top, AnchorX::Center) => AnchorPoint::TopCenter,
(AnchorY::Top, AnchorX::Right) => AnchorPoint::TopRight,
(AnchorY::Center, AnchorX::Left) => AnchorPoint::CenterLeft,
(AnchorY::Center, AnchorX::Center) => AnchorPoint::Center,
(AnchorY::Center, AnchorX::Right) => AnchorPoint::CenterRight,
(AnchorY::Bottom, AnchorX::Left) => AnchorPoint::BottomLeft,
(AnchorY::Bottom, AnchorX::Center) => AnchorPoint::BottomCenter,
(AnchorY::Bottom, AnchorX::Right) => AnchorPoint::BottomRight,
}
}
/// Returns the X axis component.
pub fn x(self) -> AnchorX {
match self {
AnchorPoint::TopLeft | AnchorPoint::CenterLeft | AnchorPoint::BottomLeft => {
AnchorX::Left
}
AnchorPoint::TopCenter | AnchorPoint::Center | AnchorPoint::BottomCenter => {
AnchorX::Center
}
AnchorPoint::TopRight | AnchorPoint::CenterRight | AnchorPoint::BottomRight => {
AnchorX::Right
}
}
}
/// Returns the Y axis component.
pub fn y(self) -> AnchorY {
match self {
AnchorPoint::TopLeft | AnchorPoint::TopCenter | AnchorPoint::TopRight => AnchorY::Top,
AnchorPoint::CenterLeft | AnchorPoint::Center | AnchorPoint::CenterRight => {
AnchorY::Center
}
AnchorPoint::BottomLeft | AnchorPoint::BottomCenter | AnchorPoint::BottomRight => {
AnchorY::Bottom
}
}
}
}
/// X axis anchor point.
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub enum AnchorX {
/// Left.
Left,
/// Center.
Center,
/// Right.
Right,
}
/// Y axis anchor point.
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub enum AnchorY {
/// Top.
Top,
/// Center.
Center,
/// Bottom.
Bottom,
}
#[cfg(test)]
mod tests {
use super::*;
#[rustfmt::skip]
const ANCHOR_TESTS: &[((AnchorY, AnchorX), AnchorPoint)] = &[
((AnchorY::Top, AnchorX::Left), AnchorPoint::TopLeft),
((AnchorY::Top, AnchorX::Center), AnchorPoint::TopCenter),
((AnchorY::Top, AnchorX::Right), AnchorPoint::TopRight),
((AnchorY::Center, AnchorX::Left), AnchorPoint::CenterLeft),
((AnchorY::Center, AnchorX::Center), AnchorPoint::Center),
((AnchorY::Center, AnchorX::Right), AnchorPoint::CenterRight),
((AnchorY::Bottom, AnchorX::Left), AnchorPoint::BottomLeft),
((AnchorY::Bottom, AnchorX::Center), AnchorPoint::BottomCenter),
((AnchorY::Bottom, AnchorX::Right), AnchorPoint::BottomRight),
];
#[test]
fn anchor_conversion() {
for ((y, x), p) in ANCHOR_TESTS.iter().copied() {
assert_eq!(p.x(), x);
assert_eq!(p.y(), y);
assert_eq!(AnchorPoint::from_xy(x, y), p);
}
}
}
+772
View File
@@ -0,0 +1,772 @@
use core::{
convert::{TryFrom, TryInto},
fmt,
ops::{Add, AddAssign, Div, DivAssign, Index, Mul, MulAssign, Neg, Sub, SubAssign},
};
use crate::geometry::Size;
/// 2D point.
///
/// A point can be used to define the position of a graphics object. For example, a [`Rectangle`]
/// may be defined that has its top left corner at `(-1, -2)`. To specify the size of an object
/// [`Size`] should be used instead.
///
/// [Nalgebra] support can be enabled with the `nalgebra_support` feature. This implements
/// `From<Vector2<N>>` and `From<&Vector2<N>>` where `N` is `Scalar + Into<i32>`. This allows use
/// of Nalgebra's [`Vector2`] with embedded-graphics where `i8`, `i16`, `i32`, `u16` or `u8` is used
/// for value storage.
///
/// # Examples
///
/// ## Create a `Point` from two integers
///
/// ```rust
/// use embedded_graphics::geometry::Point;
///
/// // Create a coord using the `new` constructor method
/// let p = Point::new(10, 20);
/// ```
///
/// ## Create a `Point` from a Nalgebra `Vector2`
///
/// _Be sure to enable the `nalgebra_support` feature to get [Nalgebra] integration._
///
/// ```rust
/// # #[cfg(feature = "nalgebra_support")] {
/// use embedded_graphics::geometry::Point;
/// use nalgebra::Vector2;
///
/// let n_coord = Vector2::new(10i32, 20);
///
/// assert_eq!(Point::from(n_coord), Point::new(10, 20));
/// # }
/// ```
///
/// ## Convert a `Vector2<u8>` into a `Point`
///
/// _Be sure to enable the `nalgebra_support` feature to get [Nalgebra] integration._
///
/// Smaller unsigned types that can be converted to `i32` are also supported in conversions.
///
/// ```rust
/// # #[cfg(feature = "nalgebra_support")] {
/// use embedded_graphics::geometry::Point;
/// use nalgebra::Vector2;
///
/// let n_coord = Vector2::new(10u8, 20);
///
/// assert_eq!(Point::from(n_coord), Point::new(10, 20));
/// # }
/// ```
///
/// [`Rectangle`]: crate::primitives::rectangle::Rectangle
/// [`Vector2<N>`]: https://docs.rs/nalgebra/0.18.0/nalgebra/base/type.Vector2.html
/// [`Vector2`]: https://docs.rs/nalgebra/0.18.0/nalgebra/base/type.Vector2.html
/// [Nalgebra]: https://docs.rs/nalgebra
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub struct Point {
/// The x coordinate.
pub x: i32,
/// The y coordinate.
pub y: i32,
}
impl Point {
/// Creates a point from X and Y coordinates.
pub const fn new(x: i32, y: i32) -> Self {
Point { x, y }
}
/// Creates a point with X and Y values set to an equal value.
///
/// # Examples
///
/// ```rust
/// use embedded_graphics::geometry::Point;
///
/// let point = Point::new_equal(11);
///
/// assert_eq!(point, Point { x: 11, y: 11 });
/// ```
pub const fn new_equal(value: i32) -> Self {
Point { x: value, y: value }
}
/// Creates a point with X and Y equal to zero.
pub const fn zero() -> Self {
Point { x: 0, y: 0 }
}
/// Returns a point with equal `x` value and `y` set to `0`.
///
/// # Examples
///
/// ## Move a `Point` along the X axis.
///
/// ```rust
/// use embedded_graphics::geometry::Point;
///
/// let translate = Point::new(20, 30);
///
/// let point = Point::new(10, 15);
///
/// let moved_x = point + translate.x_axis();
///
/// assert_eq!(moved_x, Point::new(30, 15));
/// ```
pub const fn x_axis(self) -> Self {
Self { x: self.x, y: 0 }
}
/// Returns a point with equal `y` value and `x` set to `0`.
///
/// # Examples
///
/// ## Move a `Point` along the Y axis.
///
/// ```rust
/// use embedded_graphics::geometry::Point;
///
/// let translate = Point::new(20, 30);
///
/// let point = Point::new(10, 15);
///
/// let moved_y = point + translate.y_axis();
///
/// assert_eq!(moved_y, Point::new(10, 45));
/// ```
pub const fn y_axis(self) -> Self {
Self { x: 0, y: self.y }
}
/// Remove the sign from a coordinate
///
/// # Examples
///
/// ```
/// # use embedded_graphics::geometry::Point;
/// #
/// let point = Point::new(-5, -10);
///
/// assert_eq!(point.abs(), Point::new(5, 10));
/// ```
pub const fn abs(self) -> Self {
Point::new(self.x.abs(), self.y.abs())
}
/// Offsets a point by subtracting a size.
///
/// This method provides a workaround for the `Sub` trait not being usable in `const` contexts.
///
/// # Panics
///
/// This function will panic if `width` or `height` are too large to be represented as an `i32`
/// and debug assertions are enabled.
pub(crate) const fn sub_size(self, other: Size) -> Point {
let width = other.width as i32;
let height = other.height as i32;
debug_assert!(width >= 0, "width is too large");
debug_assert!(height >= 0, "height is too large");
Point::new(self.x - width, self.y - height)
}
/// Returns the componentwise minimum of two `Point`s
///
/// # Examples
///
/// ```rust
/// use embedded_graphics::geometry::Point;
///
/// let min = Point::new(20, 30).component_min(Point::new(15, 50));
///
/// assert_eq!(min, Point::new(15, 30));
/// ```
pub fn component_min(self, other: Self) -> Self {
Self::new(self.x.min(other.x), self.y.min(other.y))
}
/// Returns the componentwise maximum of two `Point`s
///
/// # Examples
///
/// ```rust
/// use embedded_graphics::geometry::Point;
///
/// let min = Point::new(20, 30).component_max(Point::new(15, 50));
///
/// assert_eq!(min, Point::new(20, 50));
/// ```
pub fn component_max(self, other: Self) -> Self {
Self::new(self.x.max(other.x), self.y.max(other.y))
}
/// Returns the componentwise multiplication of two `Point`s.
///
/// ```rust
/// use embedded_graphics::geometry::Point;
///
/// let result = Point::new(20, 30).component_mul(Point::new(-2, 3));
///
/// assert_eq!(result, Point::new(-40, 90));
/// ```
pub const fn component_mul(self, other: Self) -> Self {
Self::new(self.x * other.x, self.y * other.y)
}
/// Returns the componentwise division of two `Points`s.
///
/// # Panics
///
/// Panics if one of the components of `other` equals zero.
///
/// ```rust
/// use embedded_graphics::geometry::Point;
///
/// let result = Point::new(20, 30).component_div(Point::new(10, -3));
///
/// assert_eq!(result, Point::new(2, -10));
/// ```
pub const fn component_div(self, other: Self) -> Self {
Self::new(self.x / other.x, self.y / other.y)
}
}
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point::new(self.x + other.x, self.y + other.y)
}
}
impl Add<Size> for Point {
type Output = Point;
/// Offsets a point by adding a size.
///
/// # Panics
///
/// This function will panic if `width` or `height` are too large to be represented as an `i32`
/// and debug assertions are enabled.
fn add(self, other: Size) -> Point {
let width = other.width as i32;
let height = other.height as i32;
debug_assert!(width >= 0, "width is too large");
debug_assert!(height >= 0, "height is too large");
Point::new(self.x + width, self.y + height)
}
}
impl AddAssign for Point {
fn add_assign(&mut self, other: Point) {
self.x += other.x;
self.y += other.y;
}
}
impl AddAssign<Size> for Point {
/// Offsets a point by adding a size.
///
/// # Panics
///
/// This function will panic if `width` or `height` are too large to be represented as an `i32`
/// and debug assertions are enabled.
fn add_assign(&mut self, other: Size) {
let width = other.width as i32;
let height = other.height as i32;
debug_assert!(width >= 0, "width is too large");
debug_assert!(height >= 0, "height is too large");
self.x += width;
self.y += height;
}
}
impl Sub for Point {
type Output = Point;
fn sub(self, other: Point) -> Point {
Point::new(self.x - other.x, self.y - other.y)
}
}
impl Sub<Size> for Point {
type Output = Point;
/// Offsets a point by subtracting a size.
///
/// # Panics
///
/// This function will panic if `width` or `height` are too large to be represented as an `i32`
/// and debug assertions are enabled.
fn sub(self, other: Size) -> Point {
self.sub_size(other)
}
}
impl SubAssign for Point {
fn sub_assign(&mut self, other: Point) {
self.x -= other.x;
self.y -= other.y;
}
}
impl SubAssign<Size> for Point {
/// Offsets a point by subtracting a size.
///
/// # Panics
///
/// This function will panic if `width` or `height` are too large to be represented as an `i32`
/// and debug assertions are enabled.
fn sub_assign(&mut self, other: Size) {
let width = other.width as i32;
let height = other.height as i32;
debug_assert!(width >= 0, "width is too large");
debug_assert!(height >= 0, "height is too large");
self.x -= width;
self.y -= height;
}
}
impl Mul<i32> for Point {
type Output = Point;
fn mul(self, rhs: i32) -> Point {
Point::new(self.x * rhs, self.y * rhs)
}
}
impl MulAssign<i32> for Point {
fn mul_assign(&mut self, rhs: i32) {
self.x *= rhs;
self.y *= rhs;
}
}
impl Div<i32> for Point {
type Output = Point;
fn div(self, rhs: i32) -> Point {
Point::new(self.x / rhs, self.y / rhs)
}
}
impl DivAssign<i32> for Point {
fn div_assign(&mut self, rhs: i32) {
self.x /= rhs;
self.y /= rhs;
}
}
impl Index<usize> for Point {
type Output = i32;
fn index(&self, idx: usize) -> &i32 {
match idx {
0 => &self.x,
1 => &self.y,
_ => panic!("index out of bounds: the len is 2 but the index is {}", idx),
}
}
}
impl Neg for Point {
type Output = Point;
fn neg(self) -> Self::Output {
Point::new(-self.x, -self.y)
}
}
impl From<(i32, i32)> for Point {
fn from(other: (i32, i32)) -> Self {
Point::new(other.0, other.1)
}
}
impl From<[i32; 2]> for Point {
fn from(other: [i32; 2]) -> Self {
Point::new(other[0], other[1])
}
}
impl From<&[i32; 2]> for Point {
fn from(other: &[i32; 2]) -> Self {
Point::new(other[0], other[1])
}
}
impl From<Point> for (i32, i32) {
fn from(other: Point) -> (i32, i32) {
(other.x, other.y)
}
}
impl From<Point> for [i32; 2] {
fn from(other: Point) -> [i32; 2] {
[other.x, other.y]
}
}
impl From<&Point> for (i32, i32) {
fn from(other: &Point) -> (i32, i32) {
(other.x, other.y)
}
}
impl TryFrom<Point> for (u32, u32) {
type Error = core::num::TryFromIntError;
fn try_from(point: Point) -> Result<Self, Self::Error> {
Ok((point.x.try_into()?, point.y.try_into()?))
}
}
impl TryFrom<(u32, u32)> for Point {
type Error = core::num::TryFromIntError;
fn try_from(point: (u32, u32)) -> Result<Self, Self::Error> {
let x = point.0.try_into()?;
let y = point.1.try_into()?;
Ok(Point::new(x, y))
}
}
impl TryFrom<Point> for [u32; 2] {
type Error = core::num::TryFromIntError;
fn try_from(point: Point) -> Result<Self, Self::Error> {
Ok([point.x.try_into()?, point.y.try_into()?])
}
}
impl TryFrom<[u32; 2]> for Point {
type Error = core::num::TryFromIntError;
fn try_from(point: [u32; 2]) -> Result<Self, Self::Error> {
let x = point[0].try_into()?;
let y = point[1].try_into()?;
Ok(Point::new(x, y))
}
}
impl TryFrom<&[u32; 2]> for Point {
type Error = core::num::TryFromIntError;
fn try_from(point: &[u32; 2]) -> Result<Self, Self::Error> {
let x = point[0].try_into()?;
let y = point[1].try_into()?;
Ok(Point::new(x, y))
}
}
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}, {}", self.x, self.y)
}
}
#[cfg(feature = "nalgebra_support")]
use nalgebra::{base::Scalar, Vector2};
#[cfg(feature = "nalgebra_support")]
impl<N> From<Vector2<N>> for Point
where
N: Into<i32> + Scalar + Copy,
{
fn from(other: Vector2<N>) -> Self {
Self::new(other[0].into(), other[1].into())
}
}
#[cfg(feature = "nalgebra_support")]
impl<N> From<&Vector2<N>> for Point
where
N: Into<i32> + Scalar + Copy,
{
fn from(other: &Vector2<N>) -> Self {
Self::new(other[0].into(), other[1].into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::fmt::Write;
#[test]
fn convert_positive_to_u32_tuple() {
let p = Point::new(10, 20);
let tuple: (u32, u32) = p.try_into().unwrap();
let array: [u32; 2] = p.try_into().unwrap();
assert_eq!(tuple, (10, 20));
assert_eq!(array, [10, 20]);
}
#[test]
fn convert_i32_max_to_u32_tuple() {
let p = Point::new(i32::max_value(), i32::max_value());
let tuple: (u32, u32) = p.try_into().unwrap();
let array: [u32; 2] = p.try_into().unwrap();
// Literal value taken from https://doc.rust-lang.org/std/primitive.i32.html#method.max_value
assert_eq!(tuple, (2147483647, 2147483647));
assert_eq!(array, [2147483647, 2147483647]);
}
#[test]
fn convert_negative_to_u32_tuple() {
let p = Point::new(-50, -10);
let tuple: Result<(u32, u32), _> = p.try_into();
let array: Result<[u32; 2], _> = p.try_into();
assert!(tuple.is_err());
assert!(array.is_err());
}
#[test]
fn convert_i32_min_to_u32_tuple() {
let p = Point::new(i32::min_value(), i32::min_value());
let tuple: Result<(u32, u32), _> = p.try_into();
let array: Result<[u32; 2], _> = p.try_into();
assert!(tuple.is_err());
assert!(array.is_err());
}
#[test]
fn points_can_be_added() {
let mut left = Point::new(10, 20);
let right = Point::new(30, 40);
assert_eq!(left + right, Point::new(40, 60));
left += right;
assert_eq!(left, Point::new(40, 60));
}
#[test]
fn point_and_size_can_be_added() {
let mut left = Point::new(11, 21);
let right = Size::new(30, 40);
assert_eq!(left + right, Point::new(41, 61));
left += right;
assert_eq!(left, Point::new(41, 61));
}
#[test]
fn points_can_be_subtracted() {
let mut left = Point::new(30, 50);
let right = Point::new(10, 20);
assert_eq!(left - right, Point::new(20, 30));
left -= right;
assert_eq!(left, Point::new(20, 30));
}
#[test]
fn point_and_size_can_be_subtracted() {
let mut left = Point::new(30, 40);
let right = Size::new(11, 22);
assert_eq!(left - right, Point::new(19, 18));
left -= right;
assert_eq!(left, Point::new(19, 18));
}
#[test]
fn points_can_be_negative_after_subtraction() {
let left = Point::new(10, 20);
let right = Point::new(30, 50);
assert_eq!(left - right, Point::new(-20, -30));
let left = Point::new(10, 20);
let right = Size::new(31, 42);
assert_eq!(left - right, Point::new(-21, -22));
}
#[test]
fn points_can_be_multiplied_by_scalar() {
let p = Point::new(1, 2);
assert_eq!(p * 3, Point::new(3, 6));
let mut p = Point::new(3, 4);
p *= -5;
assert_eq!(p, Point::new(-15, -20));
}
#[test]
fn points_can_be_divided_by_scalar() {
let p = Point::new(10, 20);
assert_eq!(p / 2, Point::new(5, 10));
let mut p = Point::new(-10, 10);
p /= -5;
assert_eq!(p, Point::new(2, -2));
}
#[test]
#[should_panic(expected = "width is too large")]
#[cfg(debug_assertions)]
fn too_large_width_can_not_be_added() {
let p = Point::zero();
let _ = p + Size::new(u32::max_value(), 0);
}
#[test]
#[should_panic(expected = "width is too large")]
#[cfg(debug_assertions)]
fn too_large_width_can_not_be_add_assigned() {
let mut p = Point::zero();
p += Size::new(u32::max_value(), 0);
}
#[test]
#[should_panic(expected = "height is too large")]
#[cfg(debug_assertions)]
fn too_large_height_can_not_be_added() {
let p = Point::zero();
let _ = p + Size::new(0, 0x80000000);
}
#[test]
#[should_panic(expected = "height is too large")]
#[cfg(debug_assertions)]
fn too_large_height_can_not_be_add_assigned() {
let mut p = Point::zero();
p += Size::new(0, 0x80000000);
}
#[test]
#[should_panic(expected = "width is too large")]
#[cfg(debug_assertions)]
fn too_large_width_can_not_be_subtracted() {
let p = Point::zero();
let _ = p - Size::new(u32::max_value(), 0);
}
#[test]
#[should_panic(expected = "width is too large")]
#[cfg(debug_assertions)]
fn too_large_width_can_not_be_sub_assigned() {
let mut p = Point::zero();
p -= Size::new(u32::max_value(), 0);
}
#[test]
#[should_panic(expected = "height is too large")]
#[cfg(debug_assertions)]
fn too_large_height_can_not_be_subtracted() {
let p = Point::zero();
let _ = p - Size::new(0, 0x80000000);
}
#[test]
#[should_panic(expected = "height is too large")]
#[cfg(debug_assertions)]
fn too_large_height_can_not_be_sub_assigned() {
let mut p = Point::zero();
p -= Size::new(0, 0x80000000);
}
#[test]
fn from_tuple() {
assert_eq!(Point::from((20i32, 30i32)), Point::new(20, 30));
assert_eq!(Point::from((20i32, 30i32)), Point::new(20, 30));
}
#[test]
fn from_array() {
assert_eq!(Point::from([20i32, 30i32]), Point::new(20, 30));
assert_eq!(Point::from([20i32, 30i32]), Point::new(20, 30));
}
#[test]
fn from_array_ref() {
assert_eq!(Point::from(&[20i32, 30i32]), Point::new(20, 30));
assert_eq!(Point::from(&[20i32, 30i32]), Point::new(20, 30));
}
#[test]
fn neg() {
assert_eq!(-Point::new(10, 20), Point::new(-10, -20));
assert_eq!(-Point::new(-40, -50), Point::new(40, 50));
}
#[test]
fn index() {
let point = Point::new(12, -34);
assert_eq!(point.x, point[0]);
assert_eq!(point.y, point[1]);
}
#[test]
#[should_panic]
fn index_out_of_bounds() {
let point = Point::new(1, 2);
let _ = point[2];
}
#[test]
#[cfg(feature = "nalgebra_support")]
fn nalgebra_support() {
let left = Vector2::new(30, 40);
let right = Vector2::new(10, 20);
assert_eq!(Point::from(left - right), Point::new(20, 20));
}
#[test]
#[cfg(feature = "nalgebra_support")]
fn convert_ref() {
let left = Vector2::new(30, 40);
let right = Vector2::new(10, 20);
let c = left - right;
assert_eq!(Point::from(&c), Point::new(20, 20));
}
#[test]
fn component_min_max() {
let a = Point::new(20, 30);
let b = Point::new(15, 50);
assert_eq!(a.component_min(b), Point::new(15, 30));
assert_eq!(a.component_max(b), Point::new(20, 50));
}
#[test]
fn display() {
let mut buffer = arrayvec::ArrayString::<32>::new();
write!(buffer, "{}", Point::new(123, -456)).unwrap();
assert_eq!(&buffer, "123, -456");
}
}
+501
View File
@@ -0,0 +1,501 @@
use core::{
fmt,
ops::{Add, AddAssign, Div, DivAssign, Index, Mul, MulAssign, Sub, SubAssign},
};
use crate::geometry::Point;
/// 2D size.
///
/// `Size` is used to define the width and height of an object.
///
/// [Nalgebra] support can be enabled with the `nalgebra_support` feature. This implements
/// `From<Vector2<N>>` and `From<&Vector2<N>>` where `N` is `Scalar + Into<u32>`. This allows use
/// of Nalgebra's [`Vector2`] with embedded-graphics where `u32`, `u16` or `u8` is used for value
/// storage.
///
/// # Examples
///
/// ## Create a `Size` from two integers
///
///
/// ```rust
/// use embedded_graphics::geometry::Size;
///
/// // Create a size using the `new` constructor method
/// let s = Size::new(10, 20);
/// ```
///
/// ## Create a `Size` from a Nalgebra `Vector2`
///
/// _Be sure to enable the `nalgebra_support` feature to get [Nalgebra] integration._
///
/// Any `Vector2<N>` can be used where `N: Into<u32> + nalgebra::Scalar`. This includes the primitive types `u32`, `u16` and `u8`.
///
/// ```rust
/// # #[cfg(feature = "nalgebra_support")] {
/// use embedded_graphics::geometry::Size;
/// use nalgebra::Vector2;
///
/// assert_eq!(Size::from(Vector2::new(10u32, 20)), Size::new(10u32, 20));
/// assert_eq!(Size::from(Vector2::new(10u16, 20)), Size::new(10u32, 20));
/// assert_eq!(Size::from(Vector2::new(10u8, 20)), Size::new(10u32, 20));
/// # }
/// ```
///
/// `.into()` can also be used, but may require more type annotations:
///
/// ```rust
/// # #[cfg(feature = "nalgebra_support")] {
/// use embedded_graphics::geometry::Size;
/// use nalgebra::Vector2;
///
/// let c: Size = Vector2::new(10u32, 20).into();
///
/// assert_eq!(c, Size::new(10u32, 20));
/// # }
/// ```
///
/// [`Drawable`]: crate::Drawable
/// [`Vector2<N>`]: https://docs.rs/nalgebra/0.18.0/nalgebra/base/type.Vector2.html
/// [`Vector2`]: https://docs.rs/nalgebra/0.18.0/nalgebra/base/type.Vector2.html
/// [Nalgebra]: https://docs.rs/nalgebra
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub struct Size {
/// The width.
pub width: u32,
/// The height.
pub height: u32,
}
impl Size {
/// Creates a size from a width and a height.
pub const fn new(width: u32, height: u32) -> Self {
Size { width, height }
}
/// Creates a size with width and height set to an equal value.
///
/// ```rust
/// use embedded_graphics::geometry::Size;
///
/// let size = Size::new_equal(11);
///
/// assert_eq!(
/// size,
/// Size {
/// width: 11,
/// height: 11
/// }
/// );
/// ```
pub const fn new_equal(value: u32) -> Self {
Size {
width: value,
height: value,
}
}
/// Creates a size with width and height equal to zero.
pub const fn zero() -> Self {
Size {
width: 0,
height: 0,
}
}
/// Returns a size with equal `width` value and `height` set to `0`.
///
/// # Examples
///
/// ## Move a `Point` along the X axis.
///
/// ```rust
/// use embedded_graphics::geometry::{Point, Size};
///
/// let size = Size::new(20, 30);
///
/// let point = Point::new(10, 15);
///
/// let moved_x = point + size.x_axis();
///
/// assert_eq!(moved_x, Point::new(30, 15));
/// ```
pub const fn x_axis(self) -> Self {
Self {
width: self.width,
height: 0,
}
}
/// Returns a size with equal `height` value and `width` set to `0`.
///
/// # Examples
///
/// ## Move a `Point` along the Y axis.
///
/// ```rust
/// use embedded_graphics::geometry::{Point, Size};
///
/// let size = Size::new(20, 30);
///
/// let point = Point::new(10, 15);
///
/// let moved_y = point + size.y_axis();
///
/// assert_eq!(moved_y, Point::new(10, 45));
/// ```
pub const fn y_axis(self) -> Self {
Self {
width: 0,
height: self.height,
}
}
/// Saturating addition.
///
/// Returns `u32::max_value()` for `width` and/or `height` instead of overflowing.
pub const fn saturating_add(self, other: Self) -> Self {
Self {
width: self.width.saturating_add(other.width),
height: self.height.saturating_add(other.height),
}
}
/// Saturating subtraction.
///
/// Returns `0` for `width` and/or `height` instead of overflowing, if the
/// value in `other` is larger then in `self`.
pub const fn saturating_sub(self, other: Self) -> Self {
Self {
width: self.width.saturating_sub(other.width),
height: self.height.saturating_sub(other.height),
}
}
/// Division.
///
/// This method provides a workaround for the `Div` trait not being usable in `const` contexts.
pub(crate) const fn div_u32(self, rhs: u32) -> Size {
Size::new(self.width / rhs, self.height / rhs)
}
/// Creates a size from two corner points of a bounding box.
pub(crate) const fn from_bounding_box(corner_1: Point, corner_2: Point) -> Self {
let width = (corner_1.x - corner_2.x).unsigned_abs() + 1;
let height = (corner_1.y - corner_2.y).unsigned_abs() + 1;
Self { width, height }
}
/// Returns the componentwise minimum of two `Size`s.
///
/// ```rust
/// use embedded_graphics::geometry::Size;
///
/// let min = Size::new(20, 30).component_min(Size::new(15, 50));
///
/// assert_eq!(min, Size::new(15, 30));
/// ```
pub fn component_min(self, other: Self) -> Self {
Self::new(self.width.min(other.width), self.height.min(other.height))
}
/// Returns the componentwise maximum of two `Size`s.
///
/// ```rust
/// use embedded_graphics::geometry::Size;
///
/// let min = Size::new(20, 30).component_max(Size::new(15, 50));
///
/// assert_eq!(min, Size::new(20, 50));
/// ```
pub fn component_max(self, other: Self) -> Self {
Self::new(self.width.max(other.width), self.height.max(other.height))
}
/// Returns the componentwise multiplication of two `Size`s.
///
/// ```rust
/// use embedded_graphics::geometry::Size;
///
/// let result = Size::new(20, 30).component_mul(Size::new(2, 3));
///
/// assert_eq!(result, Size::new(40, 90));
/// ```
pub const fn component_mul(self, other: Self) -> Self {
Self::new(self.width * other.width, self.height * other.height)
}
/// Returns the componentwise division of two `Size`s.
///
/// # Panics
///
/// Panics if one of the components of `other` equals zero.
///
/// ```rust
/// use embedded_graphics::geometry::Size;
///
/// let result = Size::new(20, 30).component_div(Size::new(5, 10));
///
/// assert_eq!(result, Size::new(4, 3));
/// ```
pub const fn component_div(self, other: Self) -> Self {
Self::new(self.width / other.width, self.height / other.height)
}
}
impl Add for Size {
type Output = Size;
fn add(self, other: Size) -> Size {
Size::new(self.width + other.width, self.height + other.height)
}
}
impl AddAssign for Size {
fn add_assign(&mut self, other: Size) {
self.width += other.width;
self.height += other.height;
}
}
impl Sub for Size {
type Output = Size;
fn sub(self, other: Size) -> Size {
Size::new(self.width - other.width, self.height - other.height)
}
}
impl SubAssign for Size {
fn sub_assign(&mut self, other: Size) {
self.width -= other.width;
self.height -= other.height;
}
}
impl Mul<u32> for Size {
type Output = Size;
fn mul(self, rhs: u32) -> Size {
Size::new(self.width * rhs, self.height * rhs)
}
}
impl MulAssign<u32> for Size {
fn mul_assign(&mut self, rhs: u32) {
self.width *= rhs;
self.height *= rhs;
}
}
impl Div<u32> for Size {
type Output = Size;
fn div(self, rhs: u32) -> Size {
self.div_u32(rhs)
}
}
impl DivAssign<u32> for Size {
fn div_assign(&mut self, rhs: u32) {
self.width /= rhs;
self.height /= rhs;
}
}
impl Index<usize> for Size {
type Output = u32;
fn index(&self, idx: usize) -> &u32 {
match idx {
0 => &self.width,
1 => &self.height,
_ => panic!("index out of bounds: the len is 2 but the index is {}", idx),
}
}
}
impl From<(u32, u32)> for Size {
fn from(other: (u32, u32)) -> Self {
Size::new(other.0, other.1)
}
}
impl From<[u32; 2]> for Size {
fn from(other: [u32; 2]) -> Self {
Size::new(other[0], other[1])
}
}
impl From<&[u32; 2]> for Size {
fn from(other: &[u32; 2]) -> Self {
Size::new(other[0], other[1])
}
}
impl From<Size> for (u32, u32) {
fn from(other: Size) -> (u32, u32) {
(other.width, other.height)
}
}
impl From<Size> for [u32; 2] {
fn from(other: Size) -> [u32; 2] {
[other.width, other.height]
}
}
impl From<&Size> for (u32, u32) {
fn from(other: &Size) -> (u32, u32) {
(other.width, other.height)
}
}
impl fmt::Display for Size {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} x {}", self.width, self.height)
}
}
#[cfg(feature = "nalgebra_support")]
use nalgebra::{base::Scalar, Vector2};
#[cfg(feature = "nalgebra_support")]
impl<N> From<Vector2<N>> for Size
where
N: Into<u32> + Scalar + Copy,
{
fn from(other: Vector2<N>) -> Self {
Self::new(other[0].into(), other[1].into())
}
}
#[cfg(feature = "nalgebra_support")]
impl<N> From<&Vector2<N>> for Size
where
N: Into<u32> + Scalar + Copy,
{
fn from(other: &Vector2<N>) -> Self {
Self::new(other[0].into(), other[1].into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::fmt::Write;
#[test]
fn sizes_can_be_added() {
let left = Size::new(10, 20);
let right = Size::new(30, 40);
assert_eq!(left + right, Size::new(40, 60));
}
#[test]
fn sizes_can_be_subtracted() {
let left = Size::new(30, 40);
let right = Size::new(10, 20);
assert_eq!(left - right, Size::new(20, 20));
}
#[test]
fn saturating_sub() {
let p = Size::new(10, 20);
assert_eq!(p.saturating_sub(Size::new(9, 18)), Size::new(1, 2));
assert_eq!(p.saturating_sub(Size::new(11, 18)), Size::new(0, 2));
assert_eq!(p.saturating_sub(Size::new(9, 21)), Size::new(1, 0));
assert_eq!(p.saturating_sub(Size::new(11, 21)), Size::new(0, 0));
}
#[test]
fn sizes_can_be_multiplied_by_scalar() {
let s = Size::new(1, 2);
assert_eq!(s * 3, Size::new(3, 6));
let mut s = Size::new(2, 3);
s *= 4;
assert_eq!(s, Size::new(8, 12));
}
#[test]
fn sizes_can_be_divided_by_scalar() {
let s = Size::new(10, 20);
assert_eq!(s / 2, Size::new(5, 10));
let mut s = Size::new(20, 30);
s /= 5;
assert_eq!(s, Size::new(4, 6));
}
#[test]
fn from_tuple() {
assert_eq!(Size::from((20, 30)), Size::new(20, 30));
}
#[test]
fn from_array() {
assert_eq!(Size::from([20, 30]), Size::new(20, 30));
}
#[test]
fn to_array() {
let array: [u32; 2] = Size::new(20, 30).into();
assert_eq!(array, [20, 30]);
}
#[test]
fn from_array_ref() {
assert_eq!(Size::from(&[20, 30]), Size::new(20, 30));
}
#[test]
fn index() {
let size = Size::new(1, 2);
assert_eq!(size.width, size[0]);
assert_eq!(size.height, size[1]);
}
#[test]
#[should_panic]
fn index_out_of_bounds() {
let size = Size::new(1, 2);
let _ = size[2];
}
#[test]
#[cfg(feature = "nalgebra_support")]
fn nalgebra_support() {
let left = Vector2::new(30u32, 40);
let right = Vector2::new(10, 20);
assert_eq!(Size::from(left - right), Size::new(20, 20));
}
#[test]
fn component_min_max() {
let a = Size::new(20, 30);
let b = Size::new(15, 50);
assert_eq!(a.component_min(b), Size::new(15, 30));
assert_eq!(a.component_max(b), Size::new(20, 50));
}
#[test]
fn display() {
let mut buffer = arrayvec::ArrayString::<32>::new();
write!(buffer, "{}", Size::new(123, 456)).unwrap();
assert_eq!(&buffer, "123 x 456");
}
}
+70
View File
@@ -0,0 +1,70 @@
//! Image drawable.
use crate::{
draw_target::DrawTarget,
geometry::{OriginDimensions, Point},
pixelcolor::PixelColor,
primitives::Rectangle,
};
/// Image drawable.
///
/// `ImageDrawable` is implemented for types that contains image information, which makes them
/// usable with the [`Image`] object.
///
/// The methods in this trait shouldn't be called directly by user code. Instead the object
/// that implements `ImageDrawable` should be wrapped in an [`Image`] object.
///
/// # Implementing `ImageDrawable`
///
/// All image drawables are positioned at the origin and need to implement [`OriginDimensions`], in
/// addition to this trait, to define their dimensions.
///
/// [`Image`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/image/struct.Image.html
/// [`OriginDimensions`]: crate::geometry::OriginDimensions
pub trait ImageDrawable: OriginDimensions {
/// The color type.
type Color: PixelColor;
/// Draws the entire image to the target.
///
/// This method shouldn't be called directly by user code. Use an [`Image`] object instead.
///
/// # Implementation notes
///
/// The implementation of this method must draw the image inside the bounding box defined by
/// the [`OriginDimensions`] trait implementation. This means that the top left corner is at the
/// origin and no drawing operations outside the bounding box are allowed.
///
/// [`Image`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/image/struct.Image.html
/// [`OriginDimensions`]: crate::geometry::OriginDimensions
fn draw<D>(&self, target: &mut D) -> Result<(), D::Error>
where
D: DrawTarget<Color = Self::Color>;
/// Draws a part of the image to the target.
///
/// This method shouldn't be called directly by user code. Use a [`SubImage`] object instead.
///
/// # Implementation notes
///
/// The implementation of this method must draw the image inside the given `area`.
/// It must be ensured that no drawing operation outside this [`Rectangle`] occur.
///
/// [`SubImage`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/image/struct.SubImage.html
/// [`Rectangle`]: crate::primitives::rectangle::Rectangle
fn draw_sub_image<D>(&self, target: &mut D, area: &Rectangle) -> Result<(), D::Error>
where
D: DrawTarget<Color = Self::Color>;
}
/// Pixel getter.
pub trait GetPixel {
/// The color type.
type Color: PixelColor;
/// Gets the color of a pixel.
///
/// Returns `None` if `p` is outside the bounding box.
fn pixel(&self, p: Point) -> Option<Self::Color>;
}
+95
View File
@@ -0,0 +1,95 @@
//! embedded-graphics-core contains the core components of [embedded-graphics] that are required to
//! add embedded-graphics support to display drivers, image libraries, text renderers and other
//! third party crates.
//!
//! This crate should only be used by crates that extend embedded-graphics.
//! Applications should instead depend on [embedded-graphics] itself.
//!
//! Like any other crate, embedded-graphics-core will change over time, however it will change at a
//! much slower rate than embedded-graphics itself, and will likely release fewer breaking changes.
//! This will provide more stability and compatibility for the wider embedded-graphics ecosystem,
//! whilst allowing non-core features of embedded-graphics to evolve at a faster pace. The same
//! version of embedded-graphics-core may be used for multiple major versions of embedded-graphics.
//!
//! ## Core functionality
//!
//! * [`DrawTarget`] - By implementing a draw target for a display driver, all embedded-graphics drawables can be drawn to that display.
//! * [`Drawable`] - This trait can be implemented to make an object drawable to any [`DrawTarget`]. Examples include shapes, text, UI elements, etc.
//! * [`ImageDrawable`]
//! * Color types - see below.
//! * Geometry - [`Point`], [`Size`] and [`Rectangle`] provide ways of defining positions, dimensions and rectangular areas respectively.
//!
//! # Colors
//!
//! The [`pixelcolor`] module provides various standard color types, from [`BinaryColor`] to
//! [`Rgb888`]. See the [`pixelcolor`] module documentation for the complete list of color depths
//! and formats available.
//!
//! # Display drivers
//!
//! See the [`DrawTarget`] documentation for examples on how to integrate embedded-graphics with a
//! display driver using the [`DrawTarget`] trait.
//!
//! # Images
//!
//! The [`ImageDrawable`] trait should be implemented for any image or image-like item, for example
//! a spritemap.
//!
//! <!-- README-LINKS
//! [`Pixel`]: https://docs.rs/embedded-graphics-core/latest/embedded_graphics_core/drawable/struct.Pixel.html
//! [`Point`]: https://docs.rs/embedded-graphics-core/latest/embedded_graphics_core/geometry/struct.Point.html
//! [`Size`]: https://docs.rs/embedded-graphics-core/latest/embedded_graphics_core/geometry/struct.Size.html
//! [`Drawable`]: https://docs.rs/embedded-graphics-core/latest/embedded_graphics_core/drawable/trait.Drawable.html
//! [`DrawTarget`]: https://docs.rs/embedded-graphics-core/latest/embedded_graphics_core/draw_target/trait.DrawTarget.html
//! [`Rectangle`]: https://docs.rs/embedded-graphics-core/latest/embedded_graphics_core/primitives/rectangle/struct.Rectangle.html
//! [`Dimensions`]: https://docs.rs/embedded-graphics-core/latest/embedded_graphics_core/geometry/trait.Dimensions.html
//! [`OriginDimensions`]: https://docs.rs/embedded-graphics-core/latest/embedded_graphics_core/geometry/trait.OriginDimensions.html
//! [`BinaryColor`]: https://docs.rs/embedded-graphics-core/latest/embedded_graphics_core/pixelcolor/enum.BinaryColor.html
//! [`Rgb888`]: https://docs.rs/embedded-graphics-core/latest/embedded_graphics_core/pixelcolor/struct.Rgb888.html
//! [`ImageDrawable`]: https://docs.rs/embedded-graphics-core/latest/embedded_graphics_core/image/trait.ImageDrawable.html
//! [`pixelcolor`]: https://docs.rs/embedded-graphics-core/latest/embedded_graphics_core/pixelcolor/trait.PixelColor.html
//! README-LINKS -->
//!
//! # Features
//!
//! - `defmt` - implements `defmt::Format` for all types where this is possible. Note that `defmt`
//! might not work with older versions of rustc that are otherwise supported by embedded-graphics-core.
//!
//! [embedded-graphics]: https://docs.rs/embedded-graphics
//! [`Pixel`]: drawable::Pixel
//! [`Point`]: geometry::Point
//! [`Size`]: geometry::Size
//! [`Drawable`]: drawable::Drawable
//! [`DrawTarget`]: draw_target::DrawTarget
//! [`Rectangle`]: primitives::rectangle::Rectangle
//! [`Dimensions`]: geometry::Dimensions
//! [`OriginDimensions`]: geometry::OriginDimensions
//! [`BinaryColor`]: pixelcolor::BinaryColor
//! [`Rgb888`]: pixelcolor::Rgb888
//! [`ImageDrawable`]: image::ImageDrawable
#![doc(
html_logo_url = "https://raw.githubusercontent.com/embedded-graphics/embedded-graphics/b225511f390c0ed9bc065eb67d05125845312148/assets/logo_core.svg?sanitize=true"
)]
#![no_std]
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![deny(missing_copy_implementations)]
#![deny(trivial_casts)]
#![deny(trivial_numeric_casts)]
#![deny(unsafe_code)]
#![deny(unstable_features)]
#![deny(unused_import_braces)]
#![deny(unused_qualifications)]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(rustdoc::private_intra_doc_links)]
pub mod draw_target;
mod drawable;
pub mod geometry;
pub mod image;
pub mod pixelcolor;
pub mod prelude;
pub mod primitives;
pub use drawable::{Drawable, Pixel};
@@ -0,0 +1,212 @@
use crate::pixelcolor::{
raw::{RawData, RawU1},
PixelColor,
};
/// Binary color.
///
/// `BinaryColor` is used for displays and images with two possible color states.
///
/// The interpretation of active and inactive states can be different for
/// different types of displays. A `BinaryColor::On` might represent a black
/// pixel on an LCD and a white pixel on an OLED display.
///
/// To simplify the conversion from `BinaryColor` to RGB and grayscale color
/// types the default conversions assume that `BinaryColor::Off` is black and
/// `BinaryColor::On` is white.
///
/// # Conversion between BinaryColor and bool
///
/// ```
/// use embedded_graphics::pixelcolor::BinaryColor;
///
/// // A BinaryColor can be converted to a bool by using the is_on and is_off methods.
/// let color = BinaryColor::On;
/// assert!(color.is_on());
/// assert!(color.invert().is_off());
///
/// // BinaryColor implements From<bool>.
/// let bool_value = true;
/// let color: BinaryColor = bool_value.into();
/// assert_eq!(color, BinaryColor::On);
///
/// // this is equivalent to:
/// let bool_value = true;
/// let color = if bool_value {
/// BinaryColor::On
/// } else {
/// BinaryColor::Off
/// };
/// assert_eq!(color, BinaryColor::On);
/// ```
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub enum BinaryColor {
/// Inactive pixel.
Off,
/// Active pixel.
On,
}
impl Default for BinaryColor {
fn default() -> Self {
Self::Off
}
}
impl BinaryColor {
/// Inverts the color.
///
/// # Examples
///
/// ```
/// use embedded_graphics::pixelcolor::BinaryColor;
///
/// assert_eq!(BinaryColor::Off.invert(), BinaryColor::On);
/// assert_eq!(BinaryColor::On.invert(), BinaryColor::Off);
/// ```
#[inline]
pub const fn invert(self) -> Self {
match self {
BinaryColor::On => BinaryColor::Off,
BinaryColor::Off => BinaryColor::On,
}
}
/// Returns `true` if this color is `On`.
///
/// # Examples
///
/// ```
/// use embedded_graphics::pixelcolor::BinaryColor;
///
/// assert!(BinaryColor::On.is_on());
/// ```
#[inline]
pub const fn is_on(self) -> bool {
matches!(self, BinaryColor::On)
}
/// Returns `true` if this color is `Off`.
///
/// # Examples
///
/// ```
/// use embedded_graphics::pixelcolor::BinaryColor;
///
/// assert!(BinaryColor::Off.is_off());
/// ```
#[inline]
pub const fn is_off(self) -> bool {
matches!(self, BinaryColor::Off)
}
/// Maps active and inactive colors to a different type.
///
/// # Examples
///
/// ```ignore
/// use embedded_graphics::pixelcolor::{BinaryColor, Rgb565};
/// let color = BinaryColor::On;
/// assert_eq!(color.map_color(Rgb565::RED, Rgb565::GREEN), Rgb565::GREEN)
/// ```
pub(crate) fn map_color<T>(self, value_off: T, value_on: T) -> T {
match self {
BinaryColor::On => value_on,
BinaryColor::Off => value_off,
}
}
}
impl PixelColor for BinaryColor {
type Raw = RawU1;
}
impl From<RawU1> for BinaryColor {
fn from(data: RawU1) -> Self {
if data.into_inner() != 0 {
BinaryColor::On
} else {
BinaryColor::Off
}
}
}
impl From<BinaryColor> for RawU1 {
fn from(color: BinaryColor) -> Self {
RawU1::new(color.map_color(0, 1))
}
}
impl From<bool> for BinaryColor {
fn from(value: bool) -> Self {
if value {
BinaryColor::On
} else {
BinaryColor::Off
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pixelcolor::{IntoStorage, Rgb565, RgbColor};
#[test]
fn default_color_is_off() {
assert_eq!(BinaryColor::default(), BinaryColor::Off);
}
#[test]
fn invert_binary_color() {
assert_eq!(BinaryColor::Off.invert(), BinaryColor::On);
assert_eq!(BinaryColor::On.invert(), BinaryColor::Off);
}
#[test]
fn map_binary_color() {
assert_eq!(
BinaryColor::Off.map_color(Rgb565::RED, Rgb565::GREEN),
Rgb565::RED
);
assert_eq!(
BinaryColor::On.map_color(Rgb565::RED, Rgb565::GREEN),
Rgb565::GREEN
);
}
#[test]
fn from_data() {
assert_eq!(BinaryColor::from(RawU1::new(0)), BinaryColor::Off);
assert_eq!(BinaryColor::from(RawU1::new(1)), BinaryColor::On);
}
#[test]
fn into_data() {
assert_eq!(RawU1::from(BinaryColor::Off).into_inner(), 0);
assert_eq!(RawU1::from(BinaryColor::On).into_inner(), 1);
}
#[test]
fn from_bool() {
assert_eq!(BinaryColor::from(false), BinaryColor::Off);
assert_eq!(BinaryColor::from(true), BinaryColor::On);
}
#[test]
fn is_on_off() {
assert!(BinaryColor::Off.is_off());
assert!(!BinaryColor::On.is_off());
assert!(!BinaryColor::Off.is_on());
assert!(BinaryColor::On.is_on());
}
#[test]
fn into_storage() {
assert_eq!(BinaryColor::Off.into_storage(), 0u8);
assert_eq!(BinaryColor::On.into_storage(), 1u8);
}
}
@@ -0,0 +1,374 @@
use crate::pixelcolor::{binary_color::*, gray_color::*, rgb_color::*};
/// Convert color channel values from one bit depth to another.
///
/// Fixed point implementation of the conversion formula:
/// `out = round(in * from_max / to_max)`
const fn convert_channel<const FROM_MAX: u8, const TO_MAX: u8>(value: u8) -> u8 {
if TO_MAX != FROM_MAX {
const SHIFT: usize = 24;
const CONST_0_5: u32 = 1 << (SHIFT - 1);
// `value * from_max / to_max` scaled by `1 << SHIFT`.
let result = value as u32 * (((TO_MAX as u32) << SHIFT) / FROM_MAX as u32);
// Scale the result back down into an u8.
((result + CONST_0_5) >> SHIFT) as u8
} else {
value
}
}
/// Calculates the luma value based on ITU-R BT.601.
fn luma(color: Rgb888) -> u8 {
let r = u16::from(color.r());
let g = u16::from(color.g());
let b = u16::from(color.b());
// Original formula: 0.299 * R + 0.587 * G + 0.144 * B
((r * 77 + g * 150 + b * 29 + 128) / 256) as u8
}
/// Macro to implement conversion between RGB color types.
macro_rules! impl_rgb_conversion {
($from_type:ident => $($to_type:ident),+) => {
$(impl From<$from_type> for $to_type {
fn from(other: $from_type) -> Self {
Self::new(
convert_channel::<{$from_type::MAX_R}, {$to_type::MAX_R}>(other.r()),
convert_channel::<{$from_type::MAX_G}, {$to_type::MAX_G}>(other.g()),
convert_channel::<{$from_type::MAX_B}, {$to_type::MAX_B}>(other.b()),
)
}
})*
impl $from_type {
pub(crate) const fn with_rgb888(r: u8, g: u8, b: u8) -> Self {
Self::new(
convert_channel::<{Rgb888::MAX_R}, {$from_type::MAX_R}>(r),
convert_channel::<{Rgb888::MAX_G}, {$from_type::MAX_G}>(g),
convert_channel::<{Rgb888::MAX_B}, {$from_type::MAX_B}>(b),
)
}
}
};
}
impl_rgb_conversion!(Rgb555 => Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888);
impl_rgb_conversion!(Bgr555 => Rgb555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888);
impl_rgb_conversion!(Rgb565 => Rgb555, Bgr555, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888);
impl_rgb_conversion!(Bgr565 => Rgb555, Bgr555, Rgb565, Rgb666, Bgr666, Rgb888, Bgr888);
impl_rgb_conversion!(Rgb666 => Rgb555, Bgr555, Rgb565, Bgr666, Bgr565, Bgr888, Rgb888);
impl_rgb_conversion!(Bgr666 => Rgb555, Bgr555, Rgb565, Rgb666, Bgr565, Bgr888, Rgb888);
impl_rgb_conversion!(Rgb888 => Rgb555, Bgr555, Rgb565, Rgb666, Bgr666, Bgr565, Bgr888);
impl_rgb_conversion!(Bgr888 => Rgb555, Bgr555, Rgb565, Rgb666, Bgr666, Bgr565, Rgb888);
/// Macro to implement conversion between grayscale color types.
macro_rules! impl_gray_conversion {
($from_type:ident => $($to_type:ident),+) => {
$(impl From<$from_type> for $to_type {
fn from(other: $from_type) -> Self {
Self::new(convert_channel::<{$from_type::MAX_LUMA}, {$to_type::MAX_LUMA}>(other.luma()))
}
})*
};
}
impl_gray_conversion!(Gray2 => Gray4, Gray8);
impl_gray_conversion!(Gray4 => Gray2, Gray8);
impl_gray_conversion!(Gray8 => Gray2, Gray4);
/// Macro to implement conversions between grayscale and RGB color types.
macro_rules! impl_rgb_to_and_from_gray {
($($gray_type:ident),+ => $rgb_type:ident) => {
$(impl From<$gray_type> for $rgb_type {
fn from(other: $gray_type) -> Self {
Self::new(
convert_channel::<{$gray_type::MAX_LUMA}, {$rgb_type::MAX_R}>(other.luma()),
convert_channel::<{$gray_type::MAX_LUMA}, {$rgb_type::MAX_G}>(other.luma()),
convert_channel::<{$gray_type::MAX_LUMA}, {$rgb_type::MAX_B}>(other.luma()),
)
}
})+
$(impl From<$rgb_type> for $gray_type {
fn from(other: $rgb_type) -> Self {
let intensity = luma(Rgb888::from(other));
Gray8::new(intensity).into()
}
})+
};
($($gray_type:ident),+ => $rgb_type:ident, $($rest:ident),+) => {
impl_rgb_to_and_from_gray!($($gray_type),+ => $rgb_type);
impl_rgb_to_and_from_gray!($($gray_type),+ => $($rest),*);
}
}
impl_rgb_to_and_from_gray!(Gray2, Gray4, Gray8 => Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888);
/// Macro to implement conversion from `BinaryColor` to RGB and grayscale types.
macro_rules! impl_from_binary {
($($type:ident),*) => {
$(impl From<BinaryColor> for $type {
fn from(color: BinaryColor) -> Self {
color.map_color(Self::BLACK, Self::WHITE)
}
})*
};
}
impl_from_binary!(
Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888, Gray2, Gray4, Gray8
);
/// Macro to implement conversion from grayscale types to `BinaryColor`.
macro_rules! impl_gray_to_binary {
($($type:ident),* ) => {
$(impl From<$type> for BinaryColor {
fn from(color: $type) -> Self {
(color.luma() >= $type::GRAY_50.luma()).into()
}
})*
};
}
impl_gray_to_binary!(Gray2, Gray4, Gray8);
/// Macro to implement conversion from RGB types to `BinaryColor`.
macro_rules! impl_rgb_to_binary {
($($type:ident),*) => {
$(impl From<$type> for BinaryColor {
fn from(color: $type) -> Self {
(luma(Rgb888::from(color)) >= 128).into()
}
})*
};
}
impl_rgb_to_binary!(Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888);
#[cfg(test)]
mod tests {
use core::fmt::Debug;
use super::*;
#[test]
fn convert_rgb565_to_rgb888_and_back() {
for r in 0..=Rgb565::MAX_R {
let c = Rgb565::new(r, 0, 0);
let c2 = Rgb888::from(c);
let c3 = Rgb565::from(c2);
assert_eq!(c, c3);
}
for g in 0..=Rgb565::MAX_G {
let c = Rgb565::new(0, g, 0);
let c2 = Rgb888::from(c);
let c3 = Rgb565::from(c2);
assert_eq!(c, c3);
}
for b in 0..=Rgb565::MAX_B {
let c = Rgb565::new(0, 0, b);
let c2 = Rgb888::from(c);
let c3 = Rgb565::from(c2);
assert_eq!(c, c3);
}
}
/// Calls the given function with every combination of two sets of types.
///
/// If only one set of types is given the same types will be used for both sets.
macro_rules! type_matrix {
($function:ident; $from_type:ident => $to_type:ident) => {
$function::<$from_type, $to_type>();
};
($function:ident; $from_type:ident => $to_type:ident, $($to_types:ident),+ ) => {
type_matrix!($function; $from_type => $to_type);
type_matrix!($function; $from_type => $($to_types),*);
};
($function:ident; $from_type:ident, $($from_types:ident),+ => $($to_types:ident),+ ) => {
type_matrix!($function; $from_type => $($to_types),*);
type_matrix!($function; $($from_types),* => $($to_types),*);
};
($function:ident; $($types:ident),+) => {
type_matrix!($function; $($types),* => $($types),*);
};
}
#[test]
fn rgb_to_rgb() {
fn test_rgb_to_rgb<FromC: RgbColor + Debug, ToC: RgbColor + From<FromC> + Debug>() {
assert_eq!(ToC::from(FromC::BLACK), ToC::BLACK);
assert_eq!(ToC::from(FromC::RED), ToC::RED);
assert_eq!(ToC::from(FromC::GREEN), ToC::GREEN);
assert_eq!(ToC::from(FromC::BLUE), ToC::BLUE);
assert_eq!(ToC::from(FromC::YELLOW), ToC::YELLOW);
assert_eq!(ToC::from(FromC::MAGENTA), ToC::MAGENTA);
assert_eq!(ToC::from(FromC::CYAN), ToC::CYAN);
assert_eq!(ToC::from(FromC::WHITE), ToC::WHITE);
}
type_matrix!(test_rgb_to_rgb; Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888);
}
#[test]
fn rgb_to_gray() {
fn test_rgb_to_gray<FromC: RgbColor + Debug, ToC: GrayColor + From<FromC> + Debug>() {
assert_eq!(ToC::from(FromC::BLACK), ToC::BLACK);
assert_eq!(ToC::from(FromC::WHITE), ToC::WHITE);
}
type_matrix!(test_rgb_to_gray; Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888 => Gray2, Gray4, Gray8);
}
#[test]
fn rgb_to_binary() {
fn test_rgb_to_binary<FromC: RgbColor + Debug, ToC>()
where
BinaryColor: From<FromC>,
{
assert_eq!(BinaryColor::from(FromC::BLACK), BinaryColor::Off);
assert_eq!(BinaryColor::from(FromC::WHITE), BinaryColor::On);
}
type_matrix!(test_rgb_to_binary; Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888 => BinaryColor);
}
#[test]
fn gray_to_gray() {
fn test_gray_to_gray<FromC: GrayColor + Debug, ToC: GrayColor + From<FromC> + Debug>() {
assert_eq!(ToC::from(FromC::BLACK), ToC::BLACK);
assert_eq!(ToC::from(FromC::WHITE), ToC::WHITE);
}
type_matrix!(test_gray_to_gray; Gray2, Gray4, Gray8);
}
#[test]
fn gray_to_rgb() {
fn test_gray_to_rgb<FromC: GrayColor + Debug, ToC: RgbColor + From<FromC> + Debug>() {
assert_eq!(ToC::from(FromC::BLACK), ToC::BLACK);
assert_eq!(ToC::from(FromC::WHITE), ToC::WHITE);
}
type_matrix!(test_gray_to_rgb; Gray2, Gray4, Gray8 => Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888);
}
#[test]
fn gray_to_binary() {
fn test_gray_to_binary<FromC: GrayColor + Debug, ToC>()
where
BinaryColor: From<FromC>,
{
assert_eq!(BinaryColor::from(FromC::BLACK), BinaryColor::Off);
assert_eq!(BinaryColor::from(FromC::WHITE), BinaryColor::On);
}
type_matrix!(test_gray_to_binary; Gray2, Gray4, Gray8 => BinaryColor);
}
#[test]
fn binary_to_rgb() {
fn test_binary_to_rgb<FromC, ToC: RgbColor + From<BinaryColor> + Debug>() {
assert_eq!(ToC::from(BinaryColor::Off), ToC::BLACK);
assert_eq!(ToC::from(BinaryColor::On), ToC::WHITE);
}
type_matrix!(test_binary_to_rgb; BinaryColor => Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888);
}
#[test]
fn binary_to_gray() {
fn test_binary_to_gray<FromC, ToC: GrayColor + From<BinaryColor> + Debug>() {
assert_eq!(ToC::from(BinaryColor::Off), ToC::BLACK);
assert_eq!(ToC::from(BinaryColor::On), ToC::WHITE);
}
type_matrix!(test_binary_to_gray; BinaryColor => Gray2, Gray4, Gray8);
}
#[test]
fn test_luma() {
assert_eq!(luma(Rgb888::BLACK), 0);
assert_eq!(luma(Rgb888::WHITE), 255);
assert_eq!(
luma(Rgb888::new(255, 255, 254)),
255,
"should be rounded upward"
);
}
fn test_channel_conversion<const FROM_MAX: u8, const TO_MAX: u8>() {
fn convert_channel_reference(value: u8, from_max: u8, to_max: u8) -> u8 {
((value as u16 * to_max as u16 + from_max as u16 / 2) / from_max as u16) as u8
}
for value in 0..FROM_MAX {
assert_eq!(
convert_channel::<FROM_MAX, TO_MAX>(value),
convert_channel_reference(value, FROM_MAX, TO_MAX),
"from_max: {}, to_max: {}, value: {}",
FROM_MAX,
TO_MAX,
value,
);
}
}
const fn bits_to_max(bits: u8) -> u8 {
0xFF >> (8 - bits)
}
#[test]
fn channel_conversions_larger() {
test_channel_conversion::<{ bits_to_max(2) }, { bits_to_max(4) }>();
test_channel_conversion::<{ bits_to_max(2) }, { bits_to_max(5) }>();
test_channel_conversion::<{ bits_to_max(2) }, { bits_to_max(6) }>();
test_channel_conversion::<{ bits_to_max(2) }, { bits_to_max(8) }>();
test_channel_conversion::<{ bits_to_max(4) }, { bits_to_max(5) }>();
test_channel_conversion::<{ bits_to_max(4) }, { bits_to_max(6) }>();
test_channel_conversion::<{ bits_to_max(4) }, { bits_to_max(8) }>();
test_channel_conversion::<{ bits_to_max(5) }, { bits_to_max(6) }>();
test_channel_conversion::<{ bits_to_max(5) }, { bits_to_max(8) }>();
test_channel_conversion::<{ bits_to_max(6) }, { bits_to_max(8) }>();
}
#[test]
fn channel_conversions_smaller() {
test_channel_conversion::<{ bits_to_max(8) }, { bits_to_max(6) }>();
test_channel_conversion::<{ bits_to_max(8) }, { bits_to_max(5) }>();
test_channel_conversion::<{ bits_to_max(8) }, { bits_to_max(4) }>();
test_channel_conversion::<{ bits_to_max(8) }, { bits_to_max(2) }>();
test_channel_conversion::<{ bits_to_max(6) }, { bits_to_max(5) }>();
test_channel_conversion::<{ bits_to_max(6) }, { bits_to_max(4) }>();
test_channel_conversion::<{ bits_to_max(6) }, { bits_to_max(2) }>();
test_channel_conversion::<{ bits_to_max(5) }, { bits_to_max(4) }>();
test_channel_conversion::<{ bits_to_max(4) }, { bits_to_max(2) }>();
test_channel_conversion::<{ bits_to_max(4) }, { bits_to_max(2) }>();
}
#[test]
fn channel_conversions_identity() {
test_channel_conversion::<{ bits_to_max(8) }, { bits_to_max(8) }>();
test_channel_conversion::<{ bits_to_max(6) }, { bits_to_max(6) }>();
test_channel_conversion::<{ bits_to_max(5) }, { bits_to_max(5) }>();
test_channel_conversion::<{ bits_to_max(4) }, { bits_to_max(4) }>();
test_channel_conversion::<{ bits_to_max(2) }, { bits_to_max(2) }>();
}
}
@@ -0,0 +1,117 @@
use crate::pixelcolor::{
raw::{RawData, RawU2, RawU4, RawU8},
PixelColor,
};
/// Grayscale color.
pub trait GrayColor: PixelColor {
/// Returns the luma channel value.
fn luma(&self) -> u8;
/// Black color (0% luma).
const BLACK: Self;
/// White color (100% luma).
const WHITE: Self;
}
macro_rules! gray_color {
($type:ident, $raw_type:ident, $bpp_str:expr) => {
#[doc = $bpp_str]
#[doc = "grayscale color."]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub struct $type($raw_type);
impl $type {
pub(crate) const MAX_LUMA: u8 = 0xFF >> (8 - $raw_type::BITS_PER_PIXEL);
pub(crate) const GRAY_50: Self = Self::new(0x80 >> (8 - $raw_type::BITS_PER_PIXEL));
/// Creates a new grayscale color.
///
/// Too large luma values are masked to the valid range by setting
/// the upper bits to `0`.
pub const fn new(luma: u8) -> Self {
Self($raw_type::new(luma))
}
}
impl PixelColor for $type {
type Raw = $raw_type;
}
impl GrayColor for $type {
fn luma(&self) -> u8 {
self.0.into_inner()
}
const BLACK: Self = Self::new(0);
const WHITE: Self = Self::new(255);
}
impl From<$raw_type> for $type {
fn from(data: $raw_type) -> Self {
Self(data)
}
}
impl From<$type> for $raw_type {
fn from(color: $type) -> Self {
color.0
}
}
};
}
gray_color!(Gray2, RawU2, "2 bit");
gray_color!(Gray4, RawU4, "4 bit");
gray_color!(Gray8, RawU8, "8 bit");
#[cfg(test)]
mod tests {
use super::*;
use crate::pixelcolor::IntoStorage;
#[test]
pub fn new_masks_luma() {
assert_eq!(Gray2::new(255).luma(), 0x3);
assert_eq!(Gray4::new(255).luma(), 0xF);
assert_eq!(Gray8::new(255).luma(), 0xFF);
}
#[test]
pub fn color_constants() {
assert_eq!(Gray2::BLACK.luma(), 0);
assert_eq!(Gray4::BLACK.luma(), 0);
assert_eq!(Gray8::BLACK.luma(), 0);
assert_eq!(Gray2::GRAY_50.luma(), 0x2);
assert_eq!(Gray4::GRAY_50.luma(), 0x8);
assert_eq!(Gray8::GRAY_50.luma(), 0x80);
assert_eq!(Gray2::WHITE.luma(), 0x3);
assert_eq!(Gray4::WHITE.luma(), 0xF);
assert_eq!(Gray8::WHITE.luma(), 0xFF);
}
#[test]
pub fn from_data() {
assert_eq!(Gray2::from(RawU2::new(0x2)), Gray2::new(0x2));
assert_eq!(Gray4::from(RawU4::new(0x9)), Gray4::new(0x9));
assert_eq!(Gray8::from(RawU8::new(0x81)), Gray8::new(0x81));
}
#[test]
pub fn into_data() {
assert_eq!(RawU2::from(Gray2::new(0x1)), RawU2::new(0x1));
assert_eq!(RawU4::from(Gray4::new(0x6)), RawU4::new(0x6));
assert_eq!(RawU8::from(Gray8::new(0x7E)), RawU8::new(0x7E));
}
#[test]
fn convert_to_raw() {
let color = Gray8::new(0xAA);
assert_eq!(color.into_storage(), 0xAAu8);
}
}
+155
View File
@@ -0,0 +1,155 @@
//! Pixel color types.
//!
//! This module contains structs for commonly used pixel color formats and
//! conversions between them. The [`raw` module] provides additional functions
//! to convert colors to and from raw data for use with images and displays.
//!
//! # Implementing custom color types
//!
//! Custom color types can be added by implementing the [`PixelColor`] trait.
//! The following example shows how to implement a new color type for a
//! 3 color EPD display.
//!
//! ```
//! use embedded_graphics::{
//! geometry::Size, prelude::*, primitives::{Rectangle, PrimitiveStyle},
//! };
//!
//! /// Color with 3 states.
//! #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
//! pub enum EpdColor {
//! White,
//! Black,
//! Red,
//! }
//!
//! /// The `Raw` can be is set to `()` because `EpdColor` doesn't need to be
//! /// converted to raw data for the display and isn't stored in images.
//! impl PixelColor for EpdColor {
//! type Raw = ();
//! }
//!
//! /// Mock EPD display.
//! pub struct EpdDisplay {}
//!
//! impl DrawTarget for EpdDisplay {
//! type Color = EpdColor;
//! type Error = core::convert::Infallible;
//!
//! fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
//! where
//! I: IntoIterator<Item = Pixel<Self::Color>>,
//! {
//! for Pixel(point, color) in pixels.into_iter() {
//! match color {
//! EpdColor::White => {} // draw white pixel at `point`
//! EpdColor::Black => {} // draw black pixel at `point`
//! EpdColor::Red => {} // draw red pixel at `point`
//! }
//! }
//!
//! Ok(())
//! }
//! }
//!
//! impl OriginDimensions for EpdDisplay {
//! fn size(&self) -> Size {
//! Size::new(300, 300)
//! }
//! }
//!
//! let mut display = EpdDisplay {};
//!
//! Rectangle::new(Point::new(0, 0), Size::new(100, 100))
//! .into_styled(PrimitiveStyle::with_fill(EpdColor::White))
//! .draw(&mut display)?;
//!
//! Rectangle::new(Point::new(100, 0), Size::new(100, 100))
//! .into_styled(PrimitiveStyle::with_fill(EpdColor::Black))
//! .draw(&mut display)?;
//!
//! Rectangle::new(Point::new(200, 0), Size::new(100, 100))
//! .into_styled(PrimitiveStyle::with_fill(EpdColor::Red))
//! .draw(&mut display)?;
//! # Ok::<(), core::convert::Infallible>(())
//! ```
//!
//! The implementation of the [`DrawTarget`] trait uses a `match` statement to
//! draw the specified color and doesn't depend on the raw data conversions,
//! see the [`raw` module] documentation for an example that uses this feature.
//!
//! [`DrawTarget`]: super::draw_target::DrawTarget
//! [`Raw`]: PixelColor::Raw
//! [`raw` module]: raw
mod binary_color;
mod conversion;
mod gray_color;
pub mod raw;
mod rgb_color;
mod web_colors;
pub use binary_color::*;
pub use gray_color::*;
use raw::RawData;
pub use rgb_color::*;
pub use web_colors::WebColors;
/// Pixel color trait.
///
/// This trait is used to represent types that can be used in drawing methods.
///
/// See the [module-level documentation] for more details.
///
/// [module-level documentation]: self
pub trait PixelColor: Copy + PartialEq {
/// Raw data type.
///
/// Specifies the raw storage type that can be used to represent this color.
///
/// See the [`raw` module documentation] for more details.
///
/// [`raw` module documentation]: raw
type Raw: RawData;
}
/// Convert a [`PixelColor`] into its underlying storage type
///
/// This trait provides the `into_storage` method for implementors of [`PixelColor`]. This method
/// exposes the underlying storage value of a pixel color type.
///
/// # Examples
///
/// ## Get the `u16` representing an `Rgb565` color
///
/// This example converts an [`Rgb565`] color into its underlying `u16` representation.
///
/// ```rust
/// use embedded_graphics::{pixelcolor::Rgb565, prelude::*};
///
/// let color = Rgb565::new(0x1f, 0x00, 0x0a);
///
/// let raw = color.into_storage();
///
/// assert_eq!(raw, 0b11111_000000_01010u16);
/// ```
///
pub trait IntoStorage {
/// The underlying storage type for the pixel color
type Storage;
/// Convert the `PixelColor` into its raw storage form
fn into_storage(self) -> Self::Storage;
}
impl<C> IntoStorage for C
where
C: PixelColor,
C::Raw: From<C>,
{
type Storage = <<C as PixelColor>::Raw as RawData>::Storage;
fn into_storage(self) -> Self::Storage {
C::Raw::from(self).into_inner()
}
}
+277
View File
@@ -0,0 +1,277 @@
//! Raw color types.
//!
//! This module contains structs to represent the raw data used to store color
//! information. Colors that implement the [`PixelColor`] trait can use the
//! associated [`Raw`] type to define their raw data representation.
//!
//! Specifying a [`Raw`] type for a [`PixelColor`] is required to use that color
//! with the [`Image`] struct.
//!
//! # Converting colors to raw data
//!
//! Colors can be converted into raw data by using two different methods. The [`into_storage`]
//! method is used to convert a color into a single integer value. To convert a color into a byte
//! array the methods provided by the [`ToBytes`] trait can be used. By using [`to_be_bytes`] the
//! color components will have the same order in memory as in the name of the type.
//!
//! ```
//! use embedded_graphics::{pixelcolor::Rgb888, prelude::*};
//!
//! let color = Rgb888::new(0x11, 0x22, 0x33);
//!
//! assert_eq!(color.into_storage(), 0x00112233);
//!
//! assert_eq!(color.to_be_bytes(), [0x11, 0x22, 0x33]);
//! ```
//!
//! # Implementing PixelColor with Raw support
//!
//! This example shows how to implement a new [`PixelColor`] that can be used
//! with images.
//!
//! The RGBI color type uses 4 bits per pixel, one for each color channel and
//! an additional intensity bit.
//!
//! ```rust
//! use embedded_graphics::{image::ImageRaw, pixelcolor::raw::RawU4, prelude::*};
//!
//! /// RGBI color
//! #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
//! pub struct RGBI(RawU4);
//!
//! impl RGBI {
//! /// Creates a RGBI color.
//! pub fn new(red: bool, green: bool, blue: bool, intensity: bool) -> Self {
//! let mut value = 0;
//!
//! if red {
//! value |= 0b0100;
//! }
//! if green {
//! value |= 0b0010;
//! }
//! if blue {
//! value |= 0b0001;
//! }
//! if intensity {
//! value |= 0b1000;
//! }
//!
//! Self(RawU4::new(value))
//! }
//! }
//!
//! /// Implement `PixelColor` to associate a raw data type with the `RGBI` struct.
//! impl PixelColor for RGBI {
//! type Raw = RawU4;
//! }
//!
//! /// `From<RawU4>` is used by `Image` to construct RGBI colors.
//! impl From<RawU4> for RGBI {
//! fn from(data: RawU4) -> Self {
//! Self(data)
//! }
//! }
//!
//! /// Raw image data with 2 pixels per byte.
//! #[rustfmt::skip]
//! const IMAGE_DATA: &[u8] = &[
//! 0b0001_0010,
//! 0b0100_1111,
//! ];
//!
//! // Create new image with RGBI colors.
//! let image_raw: ImageRaw<RGBI> = ImageRaw::new(IMAGE_DATA, 2);
//!
//! // In a real application the image could now be drawn to a display:
//! // display.draw(&image);
//! #
//! # use embedded_graphics::{mock_display::MockDisplay, image::Image};
//! #
//! # let mut display = MockDisplay::new();
//! # Image::new(&image_raw, Point::zero()).draw(&mut display).unwrap();
//! #
//! # let expected_pixels = [
//! # Pixel(Point::new(0, 0), RGBI::new(false, false, true, false)),
//! # Pixel(Point::new(1, 0), RGBI::new(false, true, false, false)),
//! # Pixel(Point::new(0, 1), RGBI::new(true, false, false, false)),
//! # Pixel(Point::new(1, 1), RGBI::new(true, true, true, true)),
//! # ];
//! #
//! # let mut expected_display = MockDisplay::new();
//! # expected_pixels.iter().copied().draw(&mut expected_display).unwrap();
//! #
//! # // assert_eq can't be used because ColorMapping isn't implemented for RGBI
//! # assert!(display.eq(&expected_display));
//! ```
//!
//! [`PixelColor`]: super::PixelColor
//! [`Raw`]: super::PixelColor::Raw
//! [`Image`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/image/struct.Image.html
//! [`into_storage`]: super::IntoStorage::into_storage
//! [`to_be_bytes`]: ToBytes::to_be_bytes
mod to_bytes;
pub use to_bytes::ToBytes;
/// Trait implemented by all `RawUx` types.
pub trait RawData: Sized + private::Sealed + From<<Self as RawData>::Storage> + ToBytes {
/// Storage type.
///
/// A primitive unsigned integer storage type that contains at least `BITS_PER_PIXEL` bits.
type Storage;
/// Bits per pixel.
const BITS_PER_PIXEL: usize;
/// Converts this raw data into the storage type.
///
/// If the primitive integer types used as the storage type contains more bits
/// than used by this type the unused most significant bits are set to `0`.
fn into_inner(self) -> Self::Storage;
/// Converts a `u32` into a `RawData` type.
///
/// This method can be used to generically construct all `RawData` types from
/// the same integer type. If the width of the `RawData` type is less than
/// 32 bits only the least significant bits are used.
fn from_u32(value: u32) -> Self;
}
/// Dummy implementation for `()`.
///
/// `()` can be used as [`PixelColor::Raw`] if raw data conversion isn't required.
///
/// [`PixelColor::Raw`]: super::PixelColor::Raw
impl RawData for () {
type Storage = ();
const BITS_PER_PIXEL: usize = 0;
fn into_inner(self) {}
fn from_u32(_value: u32) {}
}
impl private::Sealed for () {}
macro_rules! impl_raw_data {
($type:ident : $storage_type:ident, $bpp:expr, $mask:expr, $bpp_str:expr, $doc:expr) => {
#[doc = $bpp_str]
#[doc = "per pixel raw data."]
#[doc = ""]
#[doc = $doc]
#[doc = ""]
#[doc = "See the [module-level documentation](super) for more information."]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub struct $type($storage_type);
impl $type {
/// Creates a new color from the least significant
#[doc = $bpp_str]
/// of value.
#[inline]
pub const fn new(value: $storage_type) -> Self {
$type(value & $mask)
}
}
impl RawData for $type {
type Storage = $storage_type;
const BITS_PER_PIXEL: usize = $bpp;
fn into_inner(self) -> Self::Storage {
self.0
}
fn from_u32(value: u32) -> Self {
#[allow(trivial_numeric_casts)]
Self::new(value as $storage_type)
}
}
impl From<$storage_type> for $type {
#[inline]
fn from(value: $storage_type) -> Self {
Self::new(value)
}
}
impl private::Sealed for $type {}
};
($type:ident : $storage_type:ident, $bpp:expr, $mask:expr, $bpp_str:expr) => {
impl_raw_data!(
$type: $storage_type,
$bpp,
$mask,
$bpp_str,
concat!(
"`",
stringify!($type),
"` is internally stored in an `",
stringify!($storage_type),
"`. It can be constructed from an [`",
stringify!($storage_type),
"`] by using the ",
"[`new`](Self::new) method or by calling `",
stringify!($type),
"::from(",
stringify!($storage_type),
"_value)`. ",
"To convert a `",
stringify!($type),
"` back into a [`",
stringify!($storage_type),
"`] the [`into_inner`](Self::into_inner) method can be used."
)
);
};
}
impl_raw_data!(RawU1: u8, 1, 0x01, "1 bit");
impl_raw_data!(RawU2: u8, 2, 0x03, "2 bits");
impl_raw_data!(RawU4: u8, 4, 0x0F, "4 bits");
impl_raw_data!(RawU8: u8, 8, 0xFF, "8 bits");
impl_raw_data!(RawU16: u16, 16, 0xFFFF, "16 bits");
impl_raw_data!(RawU24: u32, 24, 0xFF_FFFF, "24 bits");
impl_raw_data!(RawU32: u32, 32, 0xFFFF_FFFF, "32 bits");
/// Raw data byte order.
pub trait ByteOrder: private::Sealed {}
/// Little endian byte order marker.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub enum LittleEndian {}
impl ByteOrder for LittleEndian {}
impl private::Sealed for LittleEndian {}
/// Big endian byte order marker.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub enum BigEndian {}
impl ByteOrder for BigEndian {}
impl private::Sealed for BigEndian {}
mod private {
/// Sealed trait to prevent implementation of traits in other crates.
pub trait Sealed {}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn upper_bits_are_masked() {
assert_eq!(RawU1::new(u8::max_value()).0, 0x1);
assert_eq!(RawU2::new(u8::max_value()).0, 0x3);
assert_eq!(RawU4::new(u8::max_value()).0, 0xF);
assert_eq!(RawU24::new(u32::max_value()).0, 0xFFFFFF);
}
}
@@ -0,0 +1,416 @@
use crate::pixelcolor::{
raw::{RawU1, RawU16, RawU2, RawU24, RawU32, RawU4, RawU8},
PixelColor,
};
/// Trait to convert colors into a byte array.
///
/// See the [module-level documentation](super#converting-colors-to-raw-data) for an example.
pub trait ToBytes {
/// Return type of methods in this trait.
type Bytes;
/// Converts a color into a byte array with big endian byte order.
fn to_be_bytes(self) -> Self::Bytes;
/// Converts a color into a byte array with little endian byte order.
fn to_le_bytes(self) -> Self::Bytes;
/// Converts a color into a byte array with native byte order.
fn to_ne_bytes(self) -> Self::Bytes;
}
macro_rules! impl_to_bytes {
($type:ty, $bytes_type:ty) => {
impl ToBytes for $type {
type Bytes = $bytes_type;
fn to_be_bytes(self) -> Self::Bytes {
self.0.to_be_bytes()
}
fn to_le_bytes(self) -> Self::Bytes {
self.0.to_le_bytes()
}
fn to_ne_bytes(self) -> Self::Bytes {
self.0.to_ne_bytes()
}
}
};
}
impl_to_bytes!(RawU1, [u8; 1]);
impl_to_bytes!(RawU2, [u8; 1]);
impl_to_bytes!(RawU4, [u8; 1]);
impl_to_bytes!(RawU8, [u8; 1]);
impl_to_bytes!(RawU16, [u8; 2]);
impl_to_bytes!(RawU32, [u8; 4]);
impl ToBytes for RawU24 {
type Bytes = [u8; 3];
fn to_be_bytes(self) -> Self::Bytes {
let mut ret = [0; 3];
ret.copy_from_slice(&self.0.to_be_bytes()[1..4]);
ret
}
fn to_le_bytes(self) -> Self::Bytes {
let mut ret = [0; 3];
ret.copy_from_slice(&self.0.to_le_bytes()[0..3]);
ret
}
#[cfg(target_endian = "big")]
fn to_ne_bytes(self) -> Self::Bytes {
self.to_be_bytes()
}
#[cfg(target_endian = "little")]
fn to_ne_bytes(self) -> Self::Bytes {
self.to_le_bytes()
}
}
impl ToBytes for () {
type Bytes = [u8; 0];
fn to_be_bytes(self) -> Self::Bytes {
[]
}
fn to_le_bytes(self) -> Self::Bytes {
[]
}
fn to_ne_bytes(self) -> Self::Bytes {
[]
}
}
impl<C> ToBytes for C
where
C: PixelColor + Into<<C as PixelColor>::Raw>,
{
type Bytes = <<C as PixelColor>::Raw as ToBytes>::Bytes;
fn to_le_bytes(self) -> Self::Bytes {
self.into().to_le_bytes()
}
fn to_be_bytes(self) -> Self::Bytes {
self.into().to_be_bytes()
}
fn to_ne_bytes(self) -> Self::Bytes {
self.into().to_ne_bytes()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pixelcolor::{
Bgr565, Bgr666, Bgr888, BinaryColor, Gray2, Gray4, Gray8, Rgb565, Rgb666, Rgb888,
};
fn assert_all_orders<T>(value: T, bytes: T::Bytes)
where
T: ToBytes + Copy,
T::Bytes: PartialEq + core::fmt::Debug,
{
assert_eq!(value.to_le_bytes(), bytes);
assert_eq!(value.to_be_bytes(), bytes);
assert_eq!(value.to_ne_bytes(), bytes);
}
#[test]
fn bpp1() {
assert_all_orders(BinaryColor::Off, [0]);
assert_all_orders(BinaryColor::On, [1]);
}
#[test]
fn bpp2() {
assert_all_orders(Gray2::new(0), [0]);
assert_all_orders(Gray2::new(3), [3]);
}
#[test]
fn bpp4() {
assert_all_orders(Gray4::new(0), [0]);
assert_all_orders(Gray4::new(15), [15]);
}
#[test]
fn bpp8() {
assert_all_orders(Gray8::new(0), [0]);
assert_all_orders(Gray8::new(255), [255]);
}
#[test]
fn bpp16_rgb_be() {
assert_eq!(
Rgb565::new(255, 0, 0).to_be_bytes(),
[0b11111_000, 0b000_00000]
);
assert_eq!(
Rgb565::new(0, 255, 0).to_be_bytes(),
[0b00000_111, 0b111_00000]
);
assert_eq!(
Rgb565::new(0, 0, 255).to_be_bytes(),
[0b00000_000, 0b000_11111]
);
}
#[test]
fn bpp16_rgb_le() {
assert_eq!(
Rgb565::new(255, 0, 0).to_le_bytes(),
[0b000_00000, 0b11111_000]
);
assert_eq!(
Rgb565::new(0, 255, 0).to_le_bytes(),
[0b111_00000, 0b00000_111]
);
assert_eq!(
Rgb565::new(0, 0, 255).to_le_bytes(),
[0b000_11111, 0b00000_000]
);
}
#[test]
fn bpp16_bgr_be() {
assert_eq!(
Bgr565::new(255, 0, 0).to_be_bytes(),
[0b00000_000, 0b000_11111]
);
assert_eq!(
Bgr565::new(0, 255, 0).to_be_bytes(),
[0b00000_111, 0b111_00000]
);
assert_eq!(
Bgr565::new(0, 0, 255).to_be_bytes(),
[0b11111_000, 0b000_00000]
);
}
#[test]
fn bpp16_bgr_le() {
assert_eq!(
Bgr565::new(255, 0, 0).to_le_bytes(),
[0b000_11111, 0b00000_000]
);
assert_eq!(
Bgr565::new(0, 255, 0).to_le_bytes(),
[0b111_00000, 0b00000_111]
);
assert_eq!(
Bgr565::new(0, 0, 255).to_le_bytes(),
[0b000_00000, 0b11111_000]
);
}
#[test]
fn bpp18_bgr_be() {
assert_eq!(
Bgr666::new(0xFF, 0x00, 0x00).to_be_bytes(),
[0b000000000, 0b000000000, 0b00_111111]
);
assert_eq!(
Bgr666::new(0x0, 0xFF, 0x00).to_be_bytes(),
[0b000000000, 0b0000_1111, 0b11_000000]
);
assert_eq!(
Bgr666::new(0x00, 0x00, 0xFF).to_be_bytes(),
[0b0000000_11, 0b1111_0000, 0b000000000]
);
}
#[test]
fn bpp18_bgr_le() {
assert_eq!(
Bgr666::new(0xFF, 0x00, 0x00).to_le_bytes(),
[0b00_111111, 0b000000000, 0b000000000]
);
assert_eq!(
Bgr666::new(0x0, 0xFF, 0x00).to_le_bytes(),
[0b11_000000, 0b0000_1111, 0b000000000]
);
assert_eq!(
Bgr666::new(0x00, 0x00, 0xFF).to_le_bytes(),
[0b000000000, 0b1111_0000, 0b0000000_11]
);
}
#[test]
fn bpp18_rgb_be() {
assert_eq!(
Rgb666::new(0xFF, 0x00, 0x00).to_be_bytes(),
[0b0000000_11, 0b1111_0000, 0b000000000]
);
assert_eq!(
Rgb666::new(0x0, 0xFF, 0x00).to_be_bytes(),
[0b000000000, 0b0000_1111, 0b11_000000]
);
assert_eq!(
Rgb666::new(0x00, 0x00, 0xFF).to_be_bytes(),
[0b000000000, 0b000000000, 0b00_111111]
);
}
#[test]
fn bpp18_rgb_le() {
assert_eq!(
Rgb666::new(0xFF, 0x00, 0x00).to_le_bytes(),
[0b000000000, 0b1111_0000, 0b0000000_11]
);
assert_eq!(
Rgb666::new(0x0, 0xFF, 0x00).to_le_bytes(),
[0b11_000000, 0b0000_1111, 0b000000000]
);
assert_eq!(
Rgb666::new(0x00, 0x00, 0xFF).to_le_bytes(),
[0b00_111111, 0b000000000, 0b000000000]
);
}
#[test]
fn bpp24_rgb_be() {
assert_eq!(
Rgb888::new(0xFF, 0x00, 0x00).to_be_bytes(),
[0xFF, 0x00, 0x00]
);
assert_eq!(
Rgb888::new(0x00, 0xFF, 0x00).to_be_bytes(),
[0x00, 0xFF, 0x00]
);
assert_eq!(
Rgb888::new(0x00, 0x00, 0xFF).to_be_bytes(),
[0x00, 0x00, 0xFF]
);
}
#[test]
fn bpp24_rgb_le() {
assert_eq!(
Rgb888::new(0xFF, 0x00, 0x00).to_le_bytes(),
[0x00, 0x00, 0xFF]
);
assert_eq!(
Rgb888::new(0x00, 0xFF, 0x00).to_le_bytes(),
[0x00, 0xFF, 0x00]
);
assert_eq!(
Rgb888::new(0x00, 0x00, 0xFF).to_le_bytes(),
[0xFF, 0x00, 0x00]
);
}
#[test]
fn bpp24_bgr_be() {
assert_eq!(
Bgr888::new(0xFF, 0x00, 0x00).to_be_bytes(),
[0x00, 0x00, 0xFF]
);
assert_eq!(
Bgr888::new(0x00, 0xFF, 0x00).to_be_bytes(),
[0x00, 0xFF, 0x00]
);
assert_eq!(
Bgr888::new(0x00, 0x00, 0xFF).to_be_bytes(),
[0xFF, 0x00, 0x00]
);
}
#[test]
fn bpp24_bgr_le() {
assert_eq!(
Bgr888::new(0xFF, 0x00, 0x00).to_le_bytes(),
[0xFF, 0x00, 0x00]
);
assert_eq!(
Bgr888::new(0x00, 0xFF, 0x00).to_le_bytes(),
[0x00, 0xFF, 0x00]
);
assert_eq!(
Bgr888::new(0x00, 0x00, 0xFF).to_le_bytes(),
[0x00, 0x00, 0xFF]
);
}
#[test]
fn bpp32_be() {
// This test uses `RawU32` instead of a color, because no color included
// in this crate uses 32 bpp.
assert_eq!(
RawU32::new(0x11223344).to_be_bytes(),
[0x11, 0x22, 0x33, 0x44]
);
}
#[test]
fn bpp32_le() {
// This test uses `RawU32` instead of a color, because no color included
// in this crate uses 32 bpp.
assert_eq!(
RawU32::new(0x11223344).to_le_bytes(),
[0x44, 0x33, 0x22, 0x11]
);
}
#[test]
fn native_byte_ordering() {
#[cfg(target_endian = "big")]
{
assert_eq!(RawU1::new(0x1).to_ne_bytes(), RawU1::new(0x1).to_be_bytes());
assert_eq!(RawU2::new(0x1).to_ne_bytes(), RawU2::new(0x1).to_be_bytes());
assert_eq!(RawU4::new(0x1).to_ne_bytes(), RawU4::new(0x1).to_be_bytes());
assert_eq!(
RawU8::new(0x12).to_ne_bytes(),
RawU8::new(0x12).to_be_bytes()
);
assert_eq!(
RawU16::new(0x1234).to_ne_bytes(),
RawU16::new(0x1234).to_be_bytes()
);
assert_eq!(
RawU24::new(0x123456).to_ne_bytes(),
RawU24::new(0x123456).to_be_bytes()
);
assert_eq!(
RawU32::new(0x12345678).to_ne_bytes(),
RawU32::new(0x12345678).to_be_bytes()
);
}
#[cfg(target_endian = "little")]
{
assert_eq!(RawU1::new(0x1).to_ne_bytes(), RawU1::new(0x1).to_le_bytes());
assert_eq!(RawU2::new(0x1).to_ne_bytes(), RawU2::new(0x1).to_le_bytes());
assert_eq!(RawU4::new(0x1).to_ne_bytes(), RawU4::new(0x1).to_le_bytes());
assert_eq!(
RawU8::new(0x12).to_ne_bytes(),
RawU8::new(0x12).to_le_bytes()
);
assert_eq!(
RawU16::new(0x1234).to_ne_bytes(),
RawU16::new(0x1234).to_le_bytes()
);
assert_eq!(
RawU24::new(0x123456).to_ne_bytes(),
RawU24::new(0x123456).to_le_bytes()
);
assert_eq!(
RawU32::new(0x12345678).to_ne_bytes(),
RawU32::new(0x12345678).to_le_bytes()
);
}
}
}
@@ -0,0 +1,338 @@
use crate::pixelcolor::{
raw::{RawData, RawU16, RawU24},
PixelColor,
};
use core::fmt;
/// RGB color.
pub trait RgbColor: PixelColor {
/// Returns the red channel value.
fn r(&self) -> u8;
/// Returns the green channel value.
fn g(&self) -> u8;
/// Returns the blue channel value.
fn b(&self) -> u8;
/// The maximum value in the red channel.
const MAX_R: u8;
/// The maximum value in the green channel.
const MAX_G: u8;
/// The maximum value in the blue channel.
const MAX_B: u8;
/// Black color (R: 0%, G: 0%, B: 0%)
const BLACK: Self;
/// Red color (R: 100%, G: 0%, B: 0%)
const RED: Self;
/// Green color (R: 0%, G: 100%, B: 0%)
const GREEN: Self;
/// Blue color (R: 0%, G: 0%, B: 100%)
const BLUE: Self;
/// Yellow color (R: 100%, G: 100%, B: 0%)
const YELLOW: Self;
/// Magenta color (R: 100%, G: 0%, B: 100%)
const MAGENTA: Self;
/// Cyan color (R: 0%, G: 100%, B: 100%)
const CYAN: Self;
/// White color (R: 100%, G: 100%, B: 100%)
const WHITE: Self;
}
/// Macro to implement a RgbColor type with the given channel bit positions.
macro_rules! impl_rgb_color {
(
$type:ident,
$data_type:ty,
$storage_type:ty,
($r_bits:expr, $g_bits:expr, $b_bits:expr),
($r_pos:expr, $g_pos:expr, $b_pos:expr),
$type_str:expr
) => {
#[doc = $type_str]
#[doc = "color."]
#[doc = ""]
#[doc = "Use the methods provided by the [`RgbColor`] trait to access"]
#[doc = "individual color channels and predefined color constants."]
#[doc = ""]
#[doc = "See the [module-level documentation](super) for more information about"]
#[doc = "conversion between this type and raw data."]
#[doc = ""]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct $type($storage_type);
impl $type {
const R_MASK: $storage_type = ($type::MAX_R as $storage_type) << $r_pos;
const G_MASK: $storage_type = ($type::MAX_G as $storage_type) << $g_pos;
const B_MASK: $storage_type = ($type::MAX_B as $storage_type) << $b_pos;
const RGB_MASK: $storage_type = Self::R_MASK | Self::B_MASK | Self::G_MASK;
}
impl fmt::Debug for $type {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}(r: {}, g: {}, b: {})",
stringify!($type),
self.r(),
self.g(),
self.b()
)
}
}
#[cfg(feature = "defmt")]
impl ::defmt::Format for $type {
fn format(&self, f: ::defmt::Formatter) {
::defmt::write!(
f,
"{}(r: {=u8}, g: {=u8}, b: {=u8})",
stringify!($type),
self.r(),
self.g(),
self.b()
)
}
}
impl $type
where
Self: RgbColor,
{
#[doc = "Creates a new"]
#[doc = $type_str]
#[doc = "color.\n"]
#[doc = "Too large channel values will be limited by setting the"]
#[doc = "unused most significant bits to zero."]
pub const fn new(r: u8, g: u8, b: u8) -> Self {
let r_shifted = (r & Self::MAX_R) as $storage_type << $r_pos;
let g_shifted = (g & Self::MAX_G) as $storage_type << $g_pos;
let b_shifted = (b & Self::MAX_B) as $storage_type << $b_pos;
Self(r_shifted | g_shifted | b_shifted)
}
}
impl RgbColor for $type {
fn r(&self) -> u8 {
#![allow(trivial_numeric_casts)]
(self.0 >> $r_pos) as u8 & Self::MAX_R
}
fn g(&self) -> u8 {
#![allow(trivial_numeric_casts)]
(self.0 >> $g_pos) as u8 & Self::MAX_G
}
fn b(&self) -> u8 {
#![allow(trivial_numeric_casts)]
(self.0 >> $b_pos) as u8 & Self::MAX_B
}
const MAX_R: u8 = ((1usize << $r_bits) - 1) as u8;
const MAX_G: u8 = ((1usize << $g_bits) - 1) as u8;
const MAX_B: u8 = ((1usize << $b_bits) - 1) as u8;
const BLACK: Self = Self::new(0, 0, 0);
const RED: Self = Self::new(Self::MAX_R, 0, 0);
const GREEN: Self = Self::new(0, Self::MAX_G, 0);
const BLUE: Self = Self::new(0, 0, Self::MAX_B);
const YELLOW: Self = Self::new(Self::MAX_R, Self::MAX_G, 0);
const MAGENTA: Self = Self::new(Self::MAX_R, 0, Self::MAX_B);
const CYAN: Self = Self::new(0, Self::MAX_G, Self::MAX_B);
const WHITE: Self = Self::new(Self::MAX_R, Self::MAX_G, Self::MAX_B);
}
impl PixelColor for $type {
type Raw = $data_type;
}
impl From<$data_type> for $type {
fn from(data: $data_type) -> Self {
let data = data.into_inner();
Self(data & Self::RGB_MASK)
}
}
impl From<$type> for $data_type {
fn from(color: $type) -> Self {
Self::new(color.0)
}
}
};
// Recursive macro to stringify the type.
(
$type:ident,
$data_type:ty,
$storage_type:ty,
($r_bits:expr, $g_bits:expr, $b_bits:expr),
($r_pos:expr, $g_pos:expr, $b_pos:expr)
) => {
impl_rgb_color!(
$type,
$data_type,
$storage_type,
($r_bits, $g_bits, $b_bits),
($r_pos, $g_pos, $b_pos),
stringify!($type)
);
};
}
/// Helper macro to calculate bit positions for RGB and BGR colors
macro_rules! rgb_color {
(
$type:ident,
$data_type:ty,
$storage_type:ty,Rgb =
($r_bits:expr, $g_bits:expr, $b_bits:expr)
) => {
impl_rgb_color!(
$type,
$data_type,
$storage_type,
($r_bits, $g_bits, $b_bits),
($g_bits + $b_bits, $b_bits, 0)
);
};
(
$type:ident,
$data_type:ty,
$storage_type:ty,Bgr =
($r_bits:expr, $g_bits:expr, $b_bits:expr)
) => {
impl_rgb_color!(
$type,
$data_type,
$storage_type,
($r_bits, $g_bits, $b_bits),
(0, $r_bits, $r_bits + $g_bits)
);
};
}
rgb_color!(Rgb555, RawU16, u16, Rgb = (5, 5, 5));
rgb_color!(Bgr555, RawU16, u16, Bgr = (5, 5, 5));
rgb_color!(Rgb565, RawU16, u16, Rgb = (5, 6, 5));
rgb_color!(Bgr565, RawU16, u16, Bgr = (5, 6, 5));
rgb_color!(Rgb666, RawU24, u32, Rgb = (6, 6, 6));
rgb_color!(Bgr666, RawU24, u32, Bgr = (6, 6, 6));
rgb_color!(Rgb888, RawU24, u32, Rgb = (8, 8, 8));
rgb_color!(Bgr888, RawU24, u32, Bgr = (8, 8, 8));
#[cfg(test)]
mod tests {
use super::*;
use crate::pixelcolor::IntoStorage;
/// Convert color to integer and back again to test bit positions
fn test_bpp16<C>(color: C, value: u16)
where
C: RgbColor + From<RawU16> + Into<RawU16> + fmt::Debug,
{
let value = RawU16::new(value);
assert_eq!(color.into(), value);
assert_eq!(C::from(value), color);
}
/// Convert color to integer and back again to test bit positions
fn test_bpp24<C>(color: C, value: u32)
where
C: RgbColor + From<RawU24> + Into<RawU24> + fmt::Debug,
{
let value = RawU24::new(value);
assert_eq!(color.into(), value);
assert_eq!(C::from(value), color);
}
#[test]
pub fn bit_positions_rgb555() {
test_bpp16(Rgb555::new(0b10001, 0, 0), 0b10001 << 5 + 5);
test_bpp16(Rgb555::new(0, 0b10001, 0), 0b10001 << 5);
test_bpp16(Rgb555::new(0, 0, 0b10001), 0b10001 << 0);
}
#[test]
pub fn bit_positions_bgr555() {
test_bpp16(Bgr555::new(0b10001, 0, 0), 0b10001 << 0);
test_bpp16(Bgr555::new(0, 0b10001, 0), 0b10001 << 5);
test_bpp16(Bgr555::new(0, 0, 0b10001), 0b10001 << 5 + 5);
}
#[test]
pub fn bit_positions_rgb565() {
test_bpp16(Rgb565::new(0b10001, 0, 0), 0b10001 << 5 + 6);
test_bpp16(Rgb565::new(0, 0b100001, 0), 0b100001 << 5);
test_bpp16(Rgb565::new(0, 0, 0b10001), 0b10001 << 0);
}
#[test]
pub fn bit_positions_bgr565() {
test_bpp16(Bgr565::new(0b10001, 0, 0), 0b10001 << 0);
test_bpp16(Bgr565::new(0, 0b100001, 0), 0b100001 << 5);
test_bpp16(Bgr565::new(0, 0, 0b10001), 0b10001 << 5 + 6);
}
#[test]
pub fn bit_positions_rgb666() {
test_bpp24(Rgb666::new(0b100001, 0, 0), 0b100001 << 6 + 6);
test_bpp24(Rgb666::new(0, 0b100001, 0), 0b100001 << 6);
test_bpp24(Rgb666::new(0, 0, 0b100001), 0b100001 << 0);
}
#[test]
pub fn bit_positions_bgr666() {
test_bpp24(Bgr666::new(0b100001, 0, 0), 0b100001 << 0);
test_bpp24(Bgr666::new(0, 0b100001, 0), 0b100001 << 6);
test_bpp24(Bgr666::new(0, 0, 0b100001), 0b100001 << 6 + 6);
}
#[test]
pub fn bit_positions_rgb888() {
test_bpp24(Rgb888::new(0b10000001, 0, 0), 0b10000001 << 8 + 8);
test_bpp24(Rgb888::new(0, 0b10000001, 0), 0b10000001 << 8);
test_bpp24(Rgb888::new(0, 0, 0b10000001), 0b10000001 << 0);
}
#[test]
pub fn bit_positions_bgr888() {
test_bpp24(Bgr888::new(0b10000001, 0, 0), 0b10000001 << 0);
test_bpp24(Bgr888::new(0, 0b10000001, 0), 0b10000001 << 8);
test_bpp24(Bgr888::new(0, 0, 0b10000001), 0b10000001 << 8 + 8);
}
#[test]
pub fn unused_bits_are_ignored() {
let color: Rgb555 = RawU16::from(0xFFFF).into();
assert_eq!(RawU16::from(color).into_inner(), 0x7FFF);
let color: Bgr555 = RawU16::from(0xFFFF).into();
assert_eq!(RawU16::from(color).into_inner(), 0x7FFF);
}
#[test]
fn convert_to_raw() {
let color = Rgb888::new(0xAA, 0xBB, 0xCC);
assert_eq!(color.into_storage(), 0x00AABBCC);
}
}
@@ -0,0 +1,266 @@
//! Named colors as defined in the CSS specification.
use crate::pixelcolor::{
Bgr555, Bgr565, Bgr666, Bgr888, PixelColor, Rgb555, Rgb565, Rgb666, Rgb888, RgbColor,
};
macro_rules! color_doc {
(@internal $text:expr, $($rest:tt)*) => {
#[doc = $text]
$($rest)*
};
($name:expr, ($r: expr, $g: expr, $b:expr), $($rest:tt)*) => {
color_doc!(@internal concat!(
"<span style=\"",
"background:rgb(", $r, ",", $g, ",", $b, ");",
"border-radius:3px;",
"display:inline-block;",
"width:0.9em;",
"height:0.9em",
"\"></span> ",
$name
), $($rest)*);
};
}
macro_rules! web_colors_trait {
([$(($ident:ident, $name:expr, ($r:expr, $g:expr, $b:expr)),)*]) => {
/// Named colors as defined in the CSS specification.
///
/// This list includes the [basic color keywords] as well as all colors in the
/// [extended color keywords] list.
///
/// Note that the `grEy` variants of some colors as defined in the spec are absent from this list.
/// The `grAy` variants of these colors should be used instead.
///
/// [basic color keywords]: https://drafts.csswg.org/css-color-3/#html4
/// [extended color keywords]: https://drafts.csswg.org/css-color-3/#svg-color
#[rustfmt::skip]
pub trait WebColors: PixelColor + RgbColor {
$( color_doc! {
$name, ($r, $g, $b),
const $ident: Self;
} )*
}
};
}
macro_rules! impl_web_colors {
($type:ty, [$(($ident:ident, $name:expr, ($r:expr, $g:expr, $b:expr)),)*]) => {
#[doc = "Named web colors."]
#[allow(unused)]
impl WebColors for $type {
$( color_doc! {
$name, ($r, $g, $b),
const $ident: Self = Self::with_rgb888($r, $g, $b);
} )*
}
};
}
macro_rules! web_colors {
(($($type:ty),*), $colors:tt) => {
web_colors_trait!($colors);
$( impl_web_colors!($type, $colors); )*
};
}
#[rustfmt::skip]
web_colors!(
(Rgb555, Rgb565, Rgb666, Rgb888, Bgr555, Bgr565, Bgr666, Bgr888),
[
(CSS_ALICE_BLUE, "Alice Blue", (240, 248, 255)),
(CSS_ANTIQUE_WHITE, "Antique White", (250, 235, 215)),
(CSS_AQUA, "Aqua", (0, 255, 255)),
(CSS_AQUAMARINE, "Aquamarine", (127, 255, 212)),
(CSS_AZURE, "Azure", (240, 255, 255)),
(CSS_BEIGE, "Beige", (245, 245, 220)),
(CSS_BISQUE, "Bisque", (255, 228, 196)),
(CSS_BLACK, "Black", (0, 0, 0)),
(CSS_BLANCHED_ALMOND, "Blanched Almond", (255, 235, 205)),
(CSS_BLUE, "Blue", (0, 0, 255)),
(CSS_BLUE_VIOLET, "Blue Violet", (138, 43, 226)),
(CSS_BROWN, "Brown", (165, 42, 42)),
(CSS_BURLY_WOOD, "Burly Wood", (222, 184, 135)),
(CSS_CADET_BLUE, "Cadet Blue", (95, 158, 160)),
(CSS_CHARTREUSE, "Chartreuse", (127, 255, 0)),
(CSS_CHOCOLATE, "Chocolate", (210, 105, 30)),
(CSS_CORAL, "Coral", (255, 127, 80)),
(CSS_CORNFLOWER_BLUE, "Cornflower Blue", (100, 149, 237)),
(CSS_CORNSILK, "Cornsilk", (255, 248, 220)),
(CSS_CRIMSON, "Crimson", (220, 20, 60)),
(CSS_CYAN, "Cyan", (0, 255, 255)),
(CSS_DARK_BLUE, "Dark Blue", (0, 0, 139)),
(CSS_DARK_CYAN, "Dark Cyan", (0, 139, 139)),
(CSS_DARK_GOLDENROD, "Dark Goldenrod", (184, 134, 11)),
(CSS_DARK_GRAY, "Dark Gray", (169, 169, 169)),
(CSS_DARK_GREEN, "Dark Green", (0, 100, 0)),
(CSS_DARK_KHAKI, "Dark Khaki", (189, 183, 107)),
(CSS_DARK_MAGENTA, "Dark Magenta", (139, 0, 139)),
(CSS_DARK_OLIVE_GREEN, "Dark Olive Green", (85, 107, 47)),
(CSS_DARK_ORANGE, "Dark Orange", (255, 140, 0)),
(CSS_DARK_ORCHID, "Dark Orchid", (153, 50, 204)),
(CSS_DARK_RED, "Dark Red", (139, 0, 0)),
(CSS_DARK_SALMON, "Dark Salmon", (233, 150, 122)),
(CSS_DARK_SEA_GREEN, "Dark Sea Green", (143, 188, 143)),
(CSS_DARK_SLATE_BLUE, "Dark Slate Blue", (72, 61, 139)),
(CSS_DARK_SLATE_GRAY, "Dark Slate Gray", (47, 79, 79)),
(CSS_DARK_TURQUOISE, "Dark Turquoise", (0, 206, 209)),
(CSS_DARK_VIOLET, "Dark Violet", (148, 0, 211)),
(CSS_DEEP_PINK, "Deep Pink", (255, 20, 147)),
(CSS_DEEP_SKY_BLUE, "Deep Sky Blue", (0, 191, 255)),
(CSS_DIM_GRAY, "Dim Gray", (105, 105, 105)),
(CSS_DODGER_BLUE, "Dodger Blue", (30, 144, 255)),
(CSS_FIRE_BRICK, "Fire Brick", (178, 34, 34)),
(CSS_FLORAL_WHITE, "Floral White", (255, 250, 240)),
(CSS_FOREST_GREEN, "Forest Green", (34, 139, 34)),
(CSS_FUCHSIA, "Fuchsia", (255, 0, 255)),
(CSS_GAINSBORO, "Gainsboro", (220, 220, 220)),
(CSS_GHOST_WHITE, "Ghost White", (248, 248, 255)),
(CSS_GOLD, "Gold", (255, 215, 0)),
(CSS_GOLDENROD, "Goldenrod", (218, 165, 32)),
(CSS_GRAY, "Gray", (128, 128, 128)),
(CSS_GREEN, "Green", (0, 128, 0)),
(CSS_GREEN_YELLOW, "Green Yellow", (173, 255, 47)),
(CSS_HONEYDEW, "Honeydew", (240, 255, 240)),
(CSS_HOT_PINK, "Hot Pink", (255, 105, 180)),
(CSS_INDIAN_RED, "Indian Red", (205, 92, 92)),
(CSS_INDIGO, "Indigo", (75, 0, 130)),
(CSS_IVORY, "Ivory", (255, 255, 240)),
(CSS_KHAKI, "Khaki", (240, 230, 140)),
(CSS_LAVENDER, "Lavender", (230, 230, 250)),
(CSS_LAVENDER_BLUSH, "Lavender Blush", (255, 240, 245)),
(CSS_LAWN_GREEN, "Lawn Green", (124, 252, 0)),
(CSS_LEMON_CHIFFON, "Lemon Chiffon", (255, 250, 205)),
(CSS_LIGHT_BLUE, "Light Blue", (173, 216, 230)),
(CSS_LIGHT_CORAL, "Light Coral", (240, 128, 128)),
(CSS_LIGHT_CYAN, "Light Cyan", (224, 255, 255)),
(CSS_LIGHT_GOLDENROD_YELLOW, "Light Goldenrod Yellow", (250, 250, 210)),
(CSS_LIGHT_GRAY, "Light Gray", (211, 211, 211)),
(CSS_LIGHT_GREEN, "Light Green", (144, 238, 144)),
(CSS_LIGHT_PINK, "Light Pink", (255, 182, 193)),
(CSS_LIGHT_SALMON, "Light Salmon", (255, 160, 122)),
(CSS_LIGHT_SEA_GREEN, "Light Sea Green", (32, 178, 170)),
(CSS_LIGHT_SKY_BLUE, "Light Sky Blue", (135, 206, 250)),
(CSS_LIGHT_SLATE_GRAY, "Light Slate Gray", (119, 136, 153)),
(CSS_LIGHT_STEEL_BLUE, "Light Steel Blue", (176, 196, 222)),
(CSS_LIGHT_YELLOW, "Light Yellow", (255, 255, 224)),
(CSS_LIME, "Lime", (0, 255, 0)),
(CSS_LIME_GREEN, "Lime Green", (50, 205, 50)),
(CSS_LINEN, "Linen", (250, 240, 230)),
(CSS_MAGENTA, "Magenta", (255, 0, 255)),
(CSS_MAROON, "Maroon", (128, 0, 0)),
(CSS_MEDIUM_AQUAMARINE, "Medium Aquamarine", (102, 205, 170)),
(CSS_MEDIUM_BLUE, "Medium Blue", (0, 0, 205)),
(CSS_MEDIUM_ORCHID, "Medium Orchid", (186, 85, 211)),
(CSS_MEDIUM_PURPLE, "Medium Purple", (147, 112, 219)),
(CSS_MEDIUM_SEA_GREEN, "Medium Sea Green", (60, 179, 113)),
(CSS_MEDIUM_SLATE_BLUE, "Medium Slate Blue", (123, 104, 238)),
(CSS_MEDIUM_SPRING_GREEN, "Medium Spring Green", (0, 250, 154)),
(CSS_MEDIUM_TURQUOISE, "Medium Turquoise", (72, 209, 204)),
(CSS_MEDIUM_VIOLET_RED, "Medium Violet Red", (199, 21, 133)),
(CSS_MIDNIGHT_BLUE, "Midnight Blue", (25, 25, 112)),
(CSS_MINT_CREAM, "Mint Cream", (245, 255, 250)),
(CSS_MISTY_ROSE, "Misty Rose", (255, 228, 225)),
(CSS_MOCCASIN, "Moccasin", (255, 228, 181)),
(CSS_NAVAJO_WHITE, "Navajo White", (255, 222, 173)),
(CSS_NAVY, "Navy", (0, 0, 128)),
(CSS_OLD_LACE, "Old Lace", (253, 245, 230)),
(CSS_OLIVE, "Olive", (128, 128, 0)),
(CSS_OLIVE_DRAB, "Olive Drab", (107, 142, 35)),
(CSS_ORANGE, "Orange", (255, 165, 0)),
(CSS_ORANGE_RED, "Orange Red", (255, 69, 0)),
(CSS_ORCHID, "Orchid", (218, 112, 214)),
(CSS_PALE_GOLDENROD, "Pale Goldenrod", (238, 232, 170)),
(CSS_PALE_GREEN, "Pale Green", (152, 251, 152)),
(CSS_PALE_TURQUOISE, "Pale Turquoise", (175, 238, 238)),
(CSS_PALE_VIOLET_RED, "Pale Violet Red", (219, 112, 147)),
(CSS_PAPAYA_WHIP, "Papaya Whip", (255, 239, 213)),
(CSS_PEACH_PUFF, "Peach Puff", (255, 218, 185)),
(CSS_PERU, "Peru", (205, 133, 63)),
(CSS_PINK, "Pink", (255, 192, 203)),
(CSS_PLUM, "Plum", (221, 160, 221)),
(CSS_POWDER_BLUE, "Powder Blue", (176, 224, 230)),
(CSS_PURPLE, "Purple", (128, 0, 128)),
(CSS_REBECCAPURPLE, "Rebeccapurple", (102, 51, 153)),
(CSS_RED, "Red", (255, 0, 0)),
(CSS_ROSY_BROWN, "Rosy Brown", (188, 143, 143)),
(CSS_ROYAL_BLUE, "Royal Blue", (65, 105, 225)),
(CSS_SADDLE_BROWN, "Saddle Brown", (139, 69, 19)),
(CSS_SALMON, "Salmon", (250, 128, 114)),
(CSS_SANDY_BROWN, "Sandy Brown", (244, 164, 96)),
(CSS_SEA_GREEN, "Sea Green", (46, 139, 87)),
(CSS_SEASHELL, "Seashell", (255, 245, 238)),
(CSS_SIENNA, "Sienna", (160, 82, 45)),
(CSS_SILVER, "Silver", (192, 192, 192)),
(CSS_SKY_BLUE, "Sky Blue", (135, 206, 235)),
(CSS_SLATE_BLUE, "Slate Blue", (106, 90, 205)),
(CSS_SLATE_GRAY, "Slate Gray", (112, 128, 144)),
(CSS_SNOW, "Snow", (255, 250, 250)),
(CSS_SPRING_GREEN, "Spring Green", (0, 255, 127)),
(CSS_STEEL_BLUE, "Steel Blue", (70, 130, 180)),
(CSS_TAN, "Tan", (210, 180, 140)),
(CSS_TEAL, "Teal", (0, 128, 128)),
(CSS_THISTLE, "Thistle", (216, 191, 216)),
(CSS_TOMATO, "Tomato", (255, 99, 71)),
(CSS_TURQUOISE, "Turquoise", (64, 224, 208)),
(CSS_VIOLET, "Violet", (238, 130, 238)),
(CSS_WHEAT, "Wheat", (245, 222, 179)),
(CSS_WHITE, "White", (255, 255, 255)),
(CSS_WHITE_SMOKE, "White Smoke", (245, 245, 245)),
(CSS_YELLOW, "Yellow", (255, 255, 0)),
(CSS_YELLOW_GREEN, "Yellow Green", (154, 205, 50)),
]
);
#[cfg(test)]
mod tests {
use super::*;
use crate::pixelcolor::IntoStorage;
#[test]
fn max_channels() {
assert_eq!(Rgb555::WHITE, Rgb555::CSS_WHITE);
assert_eq!(Bgr555::WHITE, Bgr555::CSS_WHITE);
assert_eq!(Rgb565::WHITE, Rgb565::CSS_WHITE);
assert_eq!(Bgr565::WHITE, Bgr565::CSS_WHITE);
assert_eq!(Rgb666::WHITE, Rgb666::CSS_WHITE);
assert_eq!(Bgr666::WHITE, Bgr666::CSS_WHITE);
assert_eq!(Rgb888::WHITE, Rgb888::CSS_WHITE);
assert_eq!(Bgr888::WHITE, Bgr888::CSS_WHITE);
assert_eq!(Rgb555::RED, Rgb555::CSS_RED);
assert_eq!(Bgr555::RED, Bgr555::CSS_RED);
assert_eq!(Rgb565::RED, Rgb565::CSS_RED);
assert_eq!(Bgr565::RED, Bgr565::CSS_RED);
assert_eq!(Rgb666::RED, Rgb666::CSS_RED);
assert_eq!(Bgr666::RED, Bgr666::CSS_RED);
assert_eq!(Rgb888::RED, Rgb888::CSS_RED);
assert_eq!(Bgr888::RED, Bgr888::CSS_RED);
assert_eq!(Rgb555::GREEN, Rgb555::CSS_LIME);
assert_eq!(Bgr555::GREEN, Bgr555::CSS_LIME);
assert_eq!(Rgb565::GREEN, Rgb565::CSS_LIME);
assert_eq!(Bgr565::GREEN, Bgr565::CSS_LIME);
assert_eq!(Rgb666::GREEN, Rgb666::CSS_LIME);
assert_eq!(Bgr666::GREEN, Bgr666::CSS_LIME);
assert_eq!(Rgb888::GREEN, Rgb888::CSS_LIME);
assert_eq!(Bgr888::GREEN, Bgr888::CSS_LIME);
assert_eq!(Rgb555::BLUE, Rgb555::CSS_BLUE);
assert_eq!(Bgr555::BLUE, Bgr555::CSS_BLUE);
assert_eq!(Rgb565::BLUE, Rgb565::CSS_BLUE);
assert_eq!(Bgr565::BLUE, Bgr565::CSS_BLUE);
assert_eq!(Rgb666::BLUE, Rgb666::CSS_BLUE);
assert_eq!(Bgr666::BLUE, Bgr666::CSS_BLUE);
assert_eq!(Rgb888::BLUE, Rgb888::CSS_BLUE);
assert_eq!(Bgr888::BLUE, Bgr888::CSS_BLUE);
}
#[test]
fn conversion() {
// 24bit: 7FFF00
assert_eq!(Rgb565::CSS_CHARTREUSE.into_storage(), 0x7fe0);
}
}
+14
View File
@@ -0,0 +1,14 @@
//! Prelude
#[doc(no_inline)]
pub use crate::{
draw_target::DrawTarget,
drawable::{Drawable, Pixel},
geometry::{Dimensions, OriginDimensions, Point, Size},
image::ImageDrawable,
pixelcolor::{
raw::{RawData, ToBytes as _},
GrayColor, IntoStorage, PixelColor, RgbColor, WebColors,
},
primitives::PointsIter,
};
+15
View File
@@ -0,0 +1,15 @@
//! Core primitives.
pub mod rectangle;
use crate::geometry::Point;
pub use rectangle::Rectangle;
/// Create an iterator over all points in the primitive.
pub trait PointsIter {
/// Iterator over all points inside the primitive.
type Iter: Iterator<Item = Point>;
/// Returns an iterator over all points inside the primitive.
fn points(&self) -> Self::Iter;
}
@@ -0,0 +1,856 @@
//! The rectangle primitive.
mod points;
use crate::{
geometry::{AnchorPoint, AnchorX, AnchorY, Dimensions, Point, Size},
primitives::PointsIter,
};
use az::SaturatingAs;
use core::{
cmp::min,
ops::{Range, RangeInclusive},
};
pub use points::Points;
/// Rectangle primitive
///
/// # Examples
///
/// ## Create some rectangles with different styles
///
/// ```rust
/// use embedded_graphics::{
/// pixelcolor::Rgb565, prelude::*, primitives::{Rectangle, PrimitiveStyleBuilder},
/// };
/// # use embedded_graphics::mock_display::MockDisplay;
/// # let mut display = MockDisplay::default();
///
/// // Rectangle with red 3 pixel wide stroke and green fill with the top left corner at (30, 20) and
/// // a size of (10, 15)
/// let style = PrimitiveStyleBuilder::new()
/// .stroke_color(Rgb565::RED)
/// .stroke_width(3)
/// .fill_color(Rgb565::GREEN)
/// .build();
///
/// Rectangle::new(Point::new(30, 20), Size::new(10, 15))
/// .into_styled(style)
/// .draw(&mut display)?;
///
/// // Rectangle with translation applied
/// Rectangle::new(Point::new(30, 20), Size::new(10, 15))
/// .translate(Point::new(-20, -10))
/// .into_styled(style)
/// .draw(&mut display)?;
/// # Ok::<(), core::convert::Infallible>(())
/// ```
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub struct Rectangle {
/// Top left point of the rectangle.
pub top_left: Point,
/// Size of the rectangle.
pub size: Size,
}
impl Dimensions for Rectangle {
fn bounding_box(&self) -> Rectangle {
*self
}
}
impl PointsIter for Rectangle {
type Iter = Points;
fn points(&self) -> Self::Iter {
Points::new(self)
}
}
/// Returns the center offset.
///
/// The center offset is defined as the offset between the top left corner and
/// the center point of a rectangle with the given size.
const fn center_offset(size: Size) -> Size {
size.saturating_sub(Size::new_equal(1)).div_u32(2)
}
impl Rectangle {
/// Creates a new rectangle from the top left point and the size.
pub const fn new(top_left: Point, size: Size) -> Self {
Rectangle { top_left, size }
}
/// Creates a new rectangle from two corners.
pub fn with_corners(corner_1: Point, corner_2: Point) -> Self {
let left = min(corner_1.x, corner_2.x);
let top = min(corner_1.y, corner_2.y);
Rectangle {
top_left: Point::new(left, top),
size: Size::from_bounding_box(corner_1, corner_2),
}
}
/// Creates a new rectangle from the center point and the size.
///
/// For rectangles with even width and/or height the top left corner doesn't
/// align with the pixel grid. Because of this the coordinates of the top left
/// corner will be rounded up to the nearest integer coordinate.
pub const fn with_center(center: Point, size: Size) -> Self {
Rectangle {
top_left: center.sub_size(center_offset(size)),
size,
}
}
/// Returns a zero sized rectangle.
pub const fn zero() -> Rectangle {
Rectangle::new(Point::zero(), Size::zero())
}
/// Returns the center of this rectangle.
///
/// For rectangles with even width and/or height the returned value is rounded down
/// to the nearest integer coordinate.
pub fn center(&self) -> Point {
self.top_left + center_offset(self.size)
}
/// Returns the bottom right corner of this rectangle.
///
/// Because the smallest rectangle that can be represented by its corners
/// has a size of 1 x 1 pixels, this function returns `None` if the width or
/// height of the rectangle is zero.
pub fn bottom_right(&self) -> Option<Point> {
if self.size.width > 0 && self.size.height > 0 {
Some(self.top_left + self.size - Point::new(1, 1))
} else {
None
}
}
/// Return whether the rectangle contains a given point.
pub fn contains(&self, point: Point) -> bool {
if point.x >= self.top_left.x && point.y >= self.top_left.y {
self.bottom_right().map_or(false, |bottom_right| {
point.x <= bottom_right.x && point.y <= bottom_right.y
})
} else {
false
}
}
/// Returns a new `Rectangle` containing the intersection of `self` and `other`.
///
/// If no intersection is present, this method will return a zero sized rectangle.
///
/// # Examples
///
/// ## Intersection
///
/// This example draws two rectangles to a mock display using the `.` character, along with
/// their intersection shown with `#` characters.
///
/// ```rust
/// use embedded_graphics::{
/// mock_display::MockDisplay, pixelcolor::BinaryColor, prelude::*,
/// primitives::{Rectangle, PrimitiveStyle},
/// };
///
/// let mut display = MockDisplay::new();
/// # display.set_allow_overdraw(true);
///
/// let rect1 = Rectangle::new(Point::zero(), Size::new(7, 8));
/// let rect2 = Rectangle::new(Point::new(2, 3), Size::new(10, 7));
///
/// let intersection = rect1.intersection(&rect2);
///
/// rect1
/// .into_styled(PrimitiveStyle::with_stroke(BinaryColor::Off, 1))
/// .draw(&mut display)?;
///
/// rect2
/// .into_styled(PrimitiveStyle::with_stroke(BinaryColor::Off, 1))
/// .draw(&mut display)?;
///
/// intersection
/// .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
/// .draw(&mut display)?;
///
/// display.assert_pattern(&[
/// "....... ",
/// ". . ",
/// ". . ",
/// ". #####.....",
/// ". # # .",
/// ". # # .",
/// ". # # .",
/// "..##### .",
/// " . .",
/// " ..........",
/// ]);
/// # Ok::<(), core::convert::Infallible>(())
/// ```
///
/// ## No intersection
///
/// This example creates two rectangles with no intersection between them. In this case,
/// `intersection` returns a zero-sized rectangle.
///
/// ```rust
/// use embedded_graphics::{prelude::*, primitives::{Rectangle, PrimitiveStyle}};
///
/// let rect1 = Rectangle::new(Point::zero(), Size::new(7, 8));
/// let rect2 = Rectangle::new(Point::new(10, 15), Size::new(10, 7));
///
/// let intersection = rect1.intersection(&rect2);
///
/// assert!(intersection.is_zero_sized());
/// # Ok::<(), core::convert::Infallible>(())
/// ```
pub fn intersection(&self, other: &Rectangle) -> Rectangle {
match (other.bottom_right(), self.bottom_right()) {
(Some(other_bottom_right), Some(self_bottom_right)) => {
if overlaps(
self.top_left.x..=self_bottom_right.x,
other.top_left.x..=other_bottom_right.x,
) && overlaps(
self.top_left.y..=self_bottom_right.y,
other.top_left.y..=other_bottom_right.y,
) {
return Rectangle::with_corners(
self.top_left.component_max(other.top_left),
self_bottom_right.component_min(other_bottom_right),
);
}
}
(Some(_other_bottom_right), None) => {
// Check if zero sized self is inside other
if other.contains(self.top_left) {
return *self;
}
}
(None, Some(_self_bottom_right)) => {
// Check if zero sized other is inside self
if self.contains(other.top_left) {
return *other;
}
}
(None, None) => (),
};
// No overlap present
Rectangle::zero()
}
/// Returns a resized copy of this rectangle.
///
/// The rectangle is resized relative to the given anchor point.
///
/// # Examples
///
/// ```
/// use embedded_graphics::{
/// prelude::*,
/// primitives::rectangle::Rectangle,
/// geometry::AnchorPoint,
/// };
///
/// let rect = Rectangle::new(Point::new(20, 20), Size::new(10, 20));
/// let resized = rect.resized(Size::new(20, 10), AnchorPoint::Center);
///
/// assert_eq!(
/// resized,
/// Rectangle::new(Point::new(15, 25), Size::new(20, 10))
/// );
/// ```
pub fn resized(&self, size: Size, anchor_point: AnchorPoint) -> Self {
let mut resized = *self;
resized.resize_width_mut(size.width, anchor_point.x());
resized.resize_height_mut(size.height, anchor_point.y());
resized
}
/// Returns a new rectangle with the given width, resized relative to the given anchor edge.
///
/// # Examples
///
/// ```
/// use embedded_graphics::{
/// prelude::*,
/// primitives::rectangle::Rectangle,
/// geometry::AnchorX,
/// };
///
/// let rect = Rectangle::new(Point::new(20, 20), Size::new(10, 20));
/// let resized = rect.resized_width(20, AnchorX::Center);
///
/// assert_eq!(
/// resized,
/// Rectangle::new(Point::new(15, 20), Size::new(20, 20))
/// );
/// ```
pub fn resized_width(&self, width: u32, anchor_x: AnchorX) -> Self {
let mut resized = *self;
resized.resize_width_mut(width, anchor_x);
resized
}
/// Returns a new rectangle with the given height, resized relative to the given anchor edge.
///
/// # Examples
///
/// ```
/// use embedded_graphics::{
/// prelude::*,
/// primitives::rectangle::Rectangle,
/// geometry::AnchorY,
/// };
///
/// let rect = Rectangle::new(Point::new(20, 20), Size::new(10, 20));
/// let resized = rect.resized_height(10, AnchorY::Center);
///
/// assert_eq!(
/// resized,
/// Rectangle::new(Point::new(20, 25), Size::new(10, 10))
/// );
/// ```
pub fn resized_height(&self, height: u32, anchor_y: AnchorY) -> Self {
let mut resized = *self;
resized.resize_height_mut(height, anchor_y);
resized
}
fn resize_width_mut(&mut self, width: u32, anchor_x: AnchorX) {
// Assume size = 1 for zero sized dimensions.
let delta =
self.size.width.saturating_as::<i32>().max(1) - width.saturating_as::<i32>().max(1);
self.top_left.x += match anchor_x {
AnchorX::Left => 0,
AnchorX::Center => delta / 2,
AnchorX::Right => delta,
};
self.size.width = width;
}
fn resize_height_mut(&mut self, height: u32, anchor_y: AnchorY) {
// Assume size = 1 for zero sized dimensions.
let delta =
self.size.height.saturating_as::<i32>().max(1) - height.saturating_as::<i32>().max(1);
self.top_left.y += match anchor_y {
AnchorY::Top => 0,
AnchorY::Center => delta / 2,
AnchorY::Bottom => delta,
};
self.size.height = height;
}
/// Offset the rectangle by a given value.
///
/// Negative values will shrink the rectangle.
pub fn offset(&self, offset: i32) -> Self {
let size = if offset >= 0 {
self.size.saturating_add(Size::new_equal(offset as u32 * 2))
} else {
self.size
.saturating_sub(Size::new_equal((-offset) as u32 * 2))
};
Self::with_center(self.center(), size)
}
/// Returns an anchor point.
///
/// # Examples
/// ```
/// use embedded_graphics::{
/// prelude::*,
/// primitives::rectangle::Rectangle,
/// geometry::AnchorPoint,
/// };
///
/// let mut rect = Rectangle::new(Point::new(20, 20), Size::new(11, 21));
///
/// assert_eq!(rect.anchor_point(AnchorPoint::TopLeft), Point::new(20, 20));
/// assert_eq!(
/// rect.anchor_point(AnchorPoint::BottomCenter),
/// Point::new(25, 40)
/// );
/// ```
pub fn anchor_point(&self, anchor_point: AnchorPoint) -> Point {
Point::new(
self.anchor_x(anchor_point.x()),
self.anchor_y(anchor_point.y()),
)
}
/// Returns the X coordinate of a given anchor edge of the rectangle.
///
/// # Examples
///
/// ```
/// use embedded_graphics::{
/// prelude::*,
/// primitives::rectangle::Rectangle,
/// geometry::AnchorX,
/// };
///
/// let mut rect = Rectangle::new(Point::new(20, 20), Size::new(11, 21));
///
/// assert_eq!(rect.anchor_x(AnchorX::Left), 20);
/// assert_eq!(rect.anchor_x(AnchorX::Center), 25);
/// ```
pub fn anchor_x(&self, anchor_x: AnchorX) -> i32 {
// Assume size = 1 for zero sized dimensions.
let delta = self.size.width.saturating_as::<i32>().max(1) - 1;
self.top_left.x
+ match anchor_x {
AnchorX::Left => 0,
AnchorX::Center => delta / 2,
AnchorX::Right => delta,
}
}
/// Returns the Y coordinate of a given anchor edge of the rectangle.
///
/// # Examples
///
/// ```
/// use embedded_graphics::{
/// prelude::*,
/// primitives::rectangle::Rectangle,
/// geometry::AnchorY,
/// };
///
/// let mut rect = Rectangle::new(Point::new(20, 20), Size::new(11, 21));
///
/// assert_eq!(rect.anchor_y(AnchorY::Top), 20);
/// assert_eq!(rect.anchor_y(AnchorY::Bottom), 40);
/// ```
pub fn anchor_y(&self, anchor_y: AnchorY) -> i32 {
// Assume size = 1 for zero sized dimensions.
let delta = self.size.height.saturating_as::<i32>().max(1) - 1;
self.top_left.y
+ match anchor_y {
AnchorY::Top => 0,
AnchorY::Center => delta / 2,
AnchorY::Bottom => delta,
}
}
/// Returns the range of Y coordinates in this rectangle.
///
/// # Examples
///
/// ```
/// use embedded_graphics::{prelude::*, primitives::Rectangle};
///
/// let rect = Rectangle::new(Point::new(10, 20), Size::new(3, 4));
/// assert_eq!(rect.rows(), 20..24);
/// ```
///
/// By combining this method with [`columns`] it is possible to iterate over all pixels inside
/// the rectangle. This can be more flexible than using the [`points`] iterator, for example,
/// if a different iteration order is required or some operations should be called once per row.
///
/// ```
/// use embedded_graphics::{prelude::*, primitives::Rectangle};
///
/// let rect = Rectangle::new(Point::new(10, 20), Size::new(3, 4));
///
/// // Iterate over the y coordinates of the rows in reverse order.
/// for y in rect.rows().rev() {
/// for x in rect.columns() {
/// // use x, y coordinates
/// }
/// }
/// ```
///
/// [`columns`]: Rectangle::columns()
/// [`points`]: super::PointsIter::points
pub fn rows(&self) -> Range<i32> {
self.top_left.y
..self
.top_left
.y
.saturating_add(self.size.height.saturating_as())
}
/// Returns the range of X coordinates in this rectangle.
///
/// # Examples
///
/// ```
/// use embedded_graphics::{prelude::*, primitives::Rectangle};
///
/// let rect = Rectangle::new(Point::new(10, 20), Size::new(3, 4));
///
/// assert_eq!(rect.columns(), 10..13);
/// ```
///
/// By combining this method with [`rows`] it is possible to iterator over all pixels inside
/// the rectangle. This can be more flexible than using the [`points`] iterator, for example,
/// if a different iteration order is required or some operations should be called once per row.
///
/// ```
/// use embedded_graphics::{prelude::*, primitives::Rectangle};
///
/// let rect = Rectangle::new(Point::new(10, 20), Size::new(3, 4));
///
/// // Iterate over all points starting from the top right corner and advancing downwards.
/// for x in rect.columns().rev() {
/// for y in rect.rows() {
/// // use x, y coordinates
/// }
/// }
/// ```
///
/// [`rows`]: Rectangle::rows()
/// [`points`]: super::PointsIter::points
pub fn columns(&self) -> Range<i32> {
self.top_left.x
..self
.top_left
.x
.saturating_add(self.size.width.saturating_as())
}
/// Returns `true` is the rectangle is zero sized.
///
/// A rectangle is zero sized if the width or height are zero.
///
/// # Examples
/// ```
/// use embedded_graphics::{prelude::*, primitives::Rectangle};
///
/// let rect = Rectangle::new(Point::new(10, 20), Size::new(10, 20));
/// assert_eq!(rect.is_zero_sized(), false);
///
/// let rect = Rectangle::new(Point::new(10, 20), Size::zero());
/// assert_eq!(rect.is_zero_sized(), true);
/// ```
pub const fn is_zero_sized(&self) -> bool {
self.size.height == 0 || self.size.width == 0
}
}
/// Checks if the two ranges overlap.
fn overlaps(first: RangeInclusive<i32>, second: RangeInclusive<i32>) -> bool {
second.contains(first.start())
|| second.contains(first.end())
|| first.start() < second.start() && first.end() > second.end()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::{Dimensions, Point, Size};
#[test]
fn dimensions() {
let rect = Rectangle::new(Point::new(5, 10), Size::new(10, 20));
assert_eq!(
rect.bounding_box(),
Rectangle::new(Point::new(5, 10), Size::new(10, 20))
);
}
#[test]
fn center() {
let odd = Rectangle::new(Point::new(10, 20), Size::new(5, 7));
assert_eq!(odd.center(), Point::new(12, 23));
let even = Rectangle::new(Point::new(20, 30), Size::new(4, 8));
assert_eq!(even.center(), Point::new(21, 33));
}
#[test]
fn bottom_right() {
let zero = Rectangle::new(Point::new(10, 20), Size::zero());
assert_eq!(zero.bottom_right(), None);
let odd = Rectangle::new(Point::new(10, 20), Size::new(5, 7));
assert_eq!(odd.bottom_right(), Some(Point::new(14, 26)));
let even = Rectangle::new(Point::new(20, 30), Size::new(4, 8));
assert_eq!(even.bottom_right(), Some(Point::new(23, 37)));
}
#[test]
fn rectangle_intersection() {
let rect1 = Rectangle::new(Point::new_equal(10), Size::new(20, 30));
let rect2 = Rectangle::new(Point::new_equal(25), Size::new(30, 40));
assert_eq!(
rect1.intersection(&rect2),
Rectangle::new(Point::new_equal(25), Size::new(5, 15))
);
}
#[test]
fn rectangle_no_intersection() {
let rect1 = Rectangle::new(Point::new_equal(10), Size::new(20, 30));
let rect2 = Rectangle::new(Point::new_equal(35), Size::new(30, 40));
assert_eq!(
rect1.intersection(&rect2),
Rectangle::new(Point::zero(), Size::zero())
);
}
#[test]
fn rectangle_complete_intersection() {
let rect1 = Rectangle::new(Point::new_equal(10), Size::new(20, 30));
let rect2 = rect1;
assert_eq!(rect1.intersection(&rect2), rect1);
}
#[test]
fn rectangle_contained_intersection() {
let rect1 = Rectangle::with_corners(Point::new_equal(10), Point::new(20, 30));
let rect2 = Rectangle::with_corners(Point::new_equal(5), Point::new(30, 40));
assert_eq!(rect1.intersection(&rect2), rect1);
}
#[test]
fn zero_sized_intersection() {
let rect1 = Rectangle::new(Point::new(1, 2), Size::new(0, 0));
let rect2 = Rectangle::new(Point::new(-10, -10), Size::new(20, 20));
assert_eq!(rect1.intersection(&rect2), rect1);
let rect1 = Rectangle::new(Point::new(-10, -10), Size::new(20, 20));
let rect2 = Rectangle::new(Point::new(2, 3), Size::new(0, 0));
assert_eq!(rect1.intersection(&rect2), rect2);
}
/// Test for issue #452
///
/// Rectangles can intersect even if no corner of any rectangle is contained inside the other
/// rectangle.
///
/// Example:
///
/// ****
/// * *
/// ############
/// # * * #
/// # * * #
/// ############
/// * *
/// ****
#[test]
fn issue_452_broken_intersection_check() {
let rect1 = Rectangle::new(Point::new(50, 0), Size::new(75, 200));
let rect2 = Rectangle::new(Point::new(0, 75), Size::new(200, 50));
let expected = Rectangle::new(Point::new(50, 75), Size::new(75, 50));
assert_eq!(rect1.intersection(&rect2), expected);
assert_eq!(rect2.intersection(&rect1), expected);
}
#[test]
fn offset() {
let center = Point::new(10, 20);
let rect = Rectangle::with_center(center, Size::new(3, 4));
assert_eq!(rect.offset(0), rect);
assert_eq!(
rect.offset(1),
Rectangle::with_center(center, Size::new(5, 6))
);
assert_eq!(
rect.offset(2),
Rectangle::with_center(center, Size::new(7, 8))
);
assert_eq!(
rect.offset(-1),
Rectangle::with_center(center, Size::new(1, 2))
);
assert_eq!(
rect.offset(-2),
Rectangle::with_center(center, Size::new(0, 0))
);
assert_eq!(
rect.offset(-3),
Rectangle::with_center(center, Size::new(0, 0))
);
}
fn test_resized(rect: Rectangle, target_size: Size, tests: &[(AnchorPoint, Point)]) {
for &(anchor_point, expected_top_left) in tests {
let resized = rect.resized(target_size, anchor_point);
let expected = Rectangle::new(expected_top_left, target_size);
assert_eq!(resized, expected, "{:?}", anchor_point);
let resized_x = rect.resized_width(target_size.width, anchor_point.x());
assert_eq!(
resized_x.top_left,
Point::new(resized.top_left.x, rect.top_left.y)
);
assert_eq!(
resized_x.size,
Size::new(resized.size.width, rect.size.height)
);
let resized_y = rect.resized_height(target_size.height, anchor_point.y());
assert_eq!(
resized_y.top_left,
Point::new(rect.top_left.x, resized.top_left.y)
);
assert_eq!(
resized_y.size,
Size::new(rect.size.width, resized.size.height)
);
}
}
#[test]
fn resized_smaller() {
test_resized(
Rectangle::new(Point::new(10, 20), Size::new(30, 40)),
Size::new(10, 20),
&[
(AnchorPoint::TopLeft, Point::new(10, 20)),
(AnchorPoint::TopCenter, Point::new(20, 20)),
(AnchorPoint::TopRight, Point::new(30, 20)),
(AnchorPoint::CenterLeft, Point::new(10, 30)),
(AnchorPoint::Center, Point::new(20, 30)),
(AnchorPoint::CenterRight, Point::new(30, 30)),
(AnchorPoint::BottomLeft, Point::new(10, 40)),
(AnchorPoint::BottomCenter, Point::new(20, 40)),
(AnchorPoint::BottomRight, Point::new(30, 40)),
],
);
}
#[test]
fn resized_larger() {
test_resized(
Rectangle::new(Point::new(10, 20), Size::new(30, 40)),
Size::new(40, 50),
&[
(AnchorPoint::TopLeft, Point::new(10, 20)),
(AnchorPoint::TopCenter, Point::new(5, 20)),
(AnchorPoint::TopRight, Point::new(0, 20)),
(AnchorPoint::CenterLeft, Point::new(10, 15)),
(AnchorPoint::Center, Point::new(5, 15)),
(AnchorPoint::CenterRight, Point::new(0, 15)),
(AnchorPoint::BottomLeft, Point::new(10, 10)),
(AnchorPoint::BottomCenter, Point::new(5, 10)),
(AnchorPoint::BottomRight, Point::new(0, 10)),
],
);
}
#[test]
fn resized_zero_sized() {
test_resized(
Rectangle::new(Point::new(10, 20), Size::zero()),
Size::new(5, 7),
&[
(AnchorPoint::TopLeft, Point::new(10, 20)),
(AnchorPoint::TopCenter, Point::new(8, 20)),
(AnchorPoint::TopRight, Point::new(6, 20)),
(AnchorPoint::CenterLeft, Point::new(10, 17)),
(AnchorPoint::Center, Point::new(8, 17)),
(AnchorPoint::CenterRight, Point::new(6, 17)),
(AnchorPoint::BottomLeft, Point::new(10, 14)),
(AnchorPoint::BottomCenter, Point::new(8, 14)),
(AnchorPoint::BottomRight, Point::new(6, 14)),
],
);
}
#[test]
fn resized_to_zero_sized() {
test_resized(
Rectangle::new(Point::new(10, 20), Size::new(21, 31)),
Size::zero(),
&[
(AnchorPoint::TopLeft, Point::new(10, 20)),
(AnchorPoint::TopCenter, Point::new(20, 20)),
(AnchorPoint::TopRight, Point::new(30, 20)),
(AnchorPoint::CenterLeft, Point::new(10, 35)),
(AnchorPoint::Center, Point::new(20, 35)),
(AnchorPoint::CenterRight, Point::new(30, 35)),
(AnchorPoint::BottomLeft, Point::new(10, 50)),
(AnchorPoint::BottomCenter, Point::new(20, 50)),
(AnchorPoint::BottomRight, Point::new(30, 50)),
],
);
}
#[test]
fn anchor_point() {
let rect = Rectangle::new(Point::new(10, 20), Size::new(21, 31));
for &(anchor_point, expected) in &[
(AnchorPoint::TopLeft, Point::new(10, 20)),
(AnchorPoint::TopCenter, Point::new(20, 20)),
(AnchorPoint::TopRight, Point::new(30, 20)),
(AnchorPoint::CenterLeft, Point::new(10, 35)),
(AnchorPoint::Center, Point::new(20, 35)),
(AnchorPoint::CenterRight, Point::new(30, 35)),
(AnchorPoint::BottomLeft, Point::new(10, 50)),
(AnchorPoint::BottomCenter, Point::new(20, 50)),
(AnchorPoint::BottomRight, Point::new(30, 50)),
] {
assert_eq!(
rect.anchor_point(anchor_point),
expected,
"{:?}",
anchor_point,
);
assert_eq!(
rect.anchor_x(anchor_point.x()),
expected.x,
"{:?}.x()",
anchor_point
);
assert_eq!(
rect.anchor_y(anchor_point.y()),
expected.y,
"{:?}.y()",
anchor_point
);
}
}
#[test]
fn rows_and_columns_zero_sized() {
let rect = Rectangle::zero();
assert_eq!(
rect.rows().next(),
None,
"the rows iterator for a zero sized rectangle shouldn't return any items"
);
assert_eq!(
rect.columns().next(),
None,
"the columns iterator for a zero sized rectangle shouldn't return any items"
);
}
}
@@ -0,0 +1,109 @@
use core::ops::Range;
use crate::{geometry::Point, primitives::Rectangle};
/// Iterator over all points inside the rectangle.
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub struct Points {
x: Range<i32>,
y: Range<i32>,
x_start: i32,
}
impl Points {
pub(in crate::primitives::rectangle) fn new(rectangle: &Rectangle) -> Self {
// Return `Self::empty` for all zero sized rectangles.
// The iterator would behave correctly without this check, but would loop unnecessarily for
// rectangles with zero width.
if rectangle.is_zero_sized() {
return Self::empty();
}
let x = rectangle.columns();
let y = rectangle.rows();
let x_start = x.start;
Self { x, y, x_start }
}
/// Create a points iterator that returns no items.
pub const fn empty() -> Self {
Self {
x: 0..0,
y: 0..0,
x_start: 0,
}
}
}
impl Iterator for Points {
type Item = Point;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
while !self.y.is_empty() {
if let Some(x) = self.x.next() {
return Some(Point::new(x, self.y.start));
}
self.y.next();
self.x.start = self.x_start;
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
geometry::{Point, Size},
primitives::{PointsIter, Rectangle},
};
#[test]
fn points_iter() {
let rectangle = Rectangle::new(Point::new(10, 20), Size::new(2, 3));
let mut points = rectangle.points();
assert_eq!(points.next(), Some(Point::new(10, 20)));
assert_eq!(points.next(), Some(Point::new(11, 20)));
assert_eq!(points.next(), Some(Point::new(10, 21)));
assert_eq!(points.next(), Some(Point::new(11, 21)));
assert_eq!(points.next(), Some(Point::new(10, 22)));
assert_eq!(points.next(), Some(Point::new(11, 22)));
assert_eq!(points.next(), None);
}
#[test]
fn points_iter_zero_size() {
let rectangle = Rectangle::new(Point::new(1, 2), Size::zero());
let mut points = rectangle.points();
assert_eq!(points.next(), None);
}
#[test]
fn points_iter_zero_size_x() {
let rectangle = Rectangle::new(Point::new(1, 2), Size::new(0, 1));
let mut points = rectangle.points();
assert_eq!(points.next(), None);
}
#[test]
fn points_iter_zero_size_y() {
let rectangle = Rectangle::new(Point::new(1, 2), Size::new(1, 0));
let mut points = rectangle.points();
assert_eq!(points.next(), None);
}
#[test]
fn points_iter_empty() {
let mut points = Points::empty();
assert_eq!(points.next(), None);
}
}