feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
@@ -0,0 +1,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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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,
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user