Skip to content

Latest commit

 

History

History
554 lines (429 loc) · 23.5 KB

MIGRATING-0.6-0.7.md

File metadata and controls

554 lines (429 loc) · 23.5 KB

Migrating from embedded-graphics 0.6.x to 0.7.0

Please note that this migration guide may be incomplete in some sections. If any missing or incorrect information is found, please open an issue or join the Matrix chatroom to bring it to our attention.

Table of contents

Macros are removed

All text, primitive and style macros have been removed. To create text, primitives and styles, use the appropriate constructors or builders instead.

For example, a styled rectangle is now built like this:

- let filled_rect: Styled<Rectangle, PrimitiveStyle<Rgb565>> = egrectangle!(
-     top_left = (10, 20),
-     bottom_right = (30, 40),
-     style = primitive_style!(stroke_color = Rgb565::RED, fill_color = Rgb565::GREEN)
- );
+ let filled_rect = Rectangle::with_corners(Point::new(10, 20), Point::new(30, 40))
+     .into_styled(
+         PrimitiveStyleBuilder::new()
+            .stroke_color(Rgb565::RED)
+            .fill_color(Rgb565::GREEN)
+            .build()
+     );

Primitives

Styling

Previously, drawing a filled shape with a transparent stroke of non-zero width would bleed the fill under the stroke. This is changed in 0.7 to honor the stroke width and alignment, even if it is the stroke color is None, allowing for filled shapes with transparent borders.

The stroke and fill color no longer affect the primitive's bounding box returned by Dimensions::bounding_box. The stroke width is now always considered even if the stroke is transparent.

Circle

A circle is now defined by it's top-left corner and diameter. This has the advantage that circles with odd diameters are now also supported.

// Create a circle centered around (30, 30) with a diameter of 20px

use embedded_graphics::{geometry::Point, primitives::Circle};

- let circle = Circle::new(Point::new(30, 30), 10);
+ let circle = Circle::new(Point::new(20, 20), 20);

To create a circle from a center point and diameter, use Circle::with_center:

use embedded_graphics::{geometry::Point, primitives::Circle};

- let circle = Circle::new(Point::new(20, 20), 5);
+ let circle = Circle::with_center(Point::new(20, 20), 10);

Rectangle

Rectangles are now defined by their top-left corner and size instead of the top-left and bottom-right corner.

use embedded_graphics::{geometry::{Point, Size}, primitives::Rectangle};

- let rectangle = Rectangle::new(Point::new(20, 30), Point::new(40, 50));
+ let rectangle = Rectangle::new(Point::new(20, 30), Size::new(20, 30));

To retain the old behavior, use Rectangle::with_corners instead:

use embedded_graphics::{geometry::Point, primitives::Rectangle};

- let rectangle = Rectangle::new(Point::new(20, 30), Point::new(40, 50));
+ let rectangle = Rectangle::with_corners(Point::new(20, 30), Point::new(40, 50));

Triangle

The vertices of a triangle are now stored in a single vertices field with the type [Point; 3]. Previously, they were stored in three separate fields p1, p2 and p3.

To access an individual vertex of a triangle, use triangle.vertices[].

use embedded_graphics::{prelude::*, primitives::Triangle};

let triangle = Triangle::new(Point::new(20, 30), Point::new(40, 50), Point::new(60, 70));

- let p1 = triangle.p1;
- let p2 = triangle.p2;
- let p3 = triangle.p3;
+ let p1 = triangle.vertices[0];
+ let p2 = triangle.vertices[1];
+ let p3 = triangle.vertices[2];

To create a triangle from a slice, use the new Triangle::from_slice method:

use embedded_graphics::{geometry::{Point}, primitives::Triangle};

let points = [Point::new(20, 30), Point::new(40, 50), Point::new(60, 70)];

let triangle = Triangle::from_slice(&points);

It is no longer possible to create a triangle from an array of Points. Instead, pass a reference to Triangle::from_slice.

Geometry

Inconsistencies in the coordinate system, like an off by one error in the size of rectangles, have been fixed.

The three methods in the Dimensions trait were replaced by a single bounding_box method. This should return a Rectangle which encompasses the entire shape.

Style module

The style module has been removed. The items in it have been moved:

  • PrimitiveStyle, PrimitiveStyleBuilder and Styled are now available in the embedded_graphics::primitives module.

  • TextStyle and TextStyleBuilder were renamed are now available under embedded_graphics::mono_font::{MonoTextStyle, MonoTextStyleBuilder}.

    Note that usage with Text has changed. See the text changes section for more.

Text and fonts

The fonts module has been split into a text and a mono_font module. The text module contains a Text drawable which can be used with different text renderers. The mono_font module contains a text renderer for monospaced fonts and the builtin fonts.

Text drawable now use two style objects to define the output format. The first style object is a character style, which defines parameters like the font and text color. This object is provided by the used text renderer and the available settings will differ between different renderers. For the builtin monospaced font support the character style is MonoTextStyle, which replaces the TextStyle object from embedded-graphics 0.6.

The second style is the new TextStyle which defines how the text should be laid out. Available settings are horizontal alignment, baseline and line height. This style is independent of the used text renderer.

The collection of builtin fonts are now sourced from public domain BDF fonts in the XOrg project. Due to this, they have slightly different dimensions and glyphs and so have changed names. Some sizes are not the same in the new set, but a rough mapping is as follows:

Old font Visually closest new font
fonts::Font6x6
Font6x6 glpyh bitmap
mono_font::ascii::FONT_4X6
FONT_4X6 glyph bitmap
fonts::Font6x8
Font6x8 glpyh bitmap
mono_font::ascii::FONT_6X10
FONT_6X10 glyph bitmap
fonts::Font6x12
Font6x12 glpyh bitmap
mono_font::ascii::FONT_6X13
FONT_6X13 glyph bitmap
fonts::Font8x16
Font8x16 glpyh bitmap
mono_font::ascii::FONT_9X15_BOLD
FONT_9X15_BOLD glyph bitmap
fonts::Font12x16
Font12x16 glpyh bitmap
mono_font::ascii::FONT_10X20
FONT_10X20 glyph bitmap
fonts::Font24x32 The largest available new font is FONT_10X20, which is significantly smaller than the old Font24x32. Larger fonts are available in external crates.

Note that all fonts are available in with different glyph subsets to support a wide variety of languages. The table above only shows the new fonts' ascii variants. The new fonts tend to use a larger glyph height for the same cap height, which improves readability but may require some layout changes.

The default baseline for fonts is now the font's alphabetic baseline instead of the top of the glyph bounding box. To retain the 0.6 behavior and position text using its top-left corner, set the baseline property to Baseline::Top:

use embedded_graphics::text::{Baseline, TextStyle, TextStyleBuilder};

let style = TextStyle::with_baseline(Baseline::Top);

// OR

let style = TextStyleBuilder::new().baseline(Baseline::Top).build();

General

Drawable

The Drawable trait now uses an associated type for its pixel color instead of a type parameters.

An associated type, Output, has also been added which can be used to return values from drawing operations. The unit type () can be used if the draw method doesn't need to return anything, e.g. type Output = ();

- impl<'a, C: 'a> Drawable<C> for &Button<'a, C>
- where
-     C: PixelColor + From<BinaryColor>,
- {
-     fn draw<D>(self, display: &mut D) -> Result<(), D::Error> where D: DrawTarget<C> {
-         // ...
-     }
- }
+ impl<C> Drawable for Button<'_, C>
+ where
+     C: PixelColor + From<BinaryColor>,
+ {
+     type Color = C;
+
+     type Output = ();
+
+     fn draw<D>(&self, display: &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(display)?;
+         Text::new(self.text, Point::new(6, 6))
+             .into_styled(TextStyle::new(Font6x8, self.fg_color))
+             .draw(display)
+     }
+ }

IntoIterator changes

Styled primitives no longer implement IntoIterator to create a pixel iterator. Use the new Styled::pixels method instead.

For example, chaining two pixel iterators together now requires explicit calls to pixels():

+ use embedded_graphics::prelude::*;

let background = Rectangle::new(...);
let text = Text::new(...);

- background.into_iter().chain(&text)
+ background.pixels().chain(text.pixels())

Mock display

The MockDisplay, used often for unit testing, now checks for pixel overdraw and out of bounds drawing by default. These additional checks can be disabled by using the set_allow_overdraw and set_allow_out_of_bounds_drawing methods, if required.

The width and height methods have been removed. Use the bounding_box method provided by the Dimensions trait instead:

// Or: use embedded_graphics::prelude::*;
use embedded_graphics::geometry::Dimensions;

use embedded_graphics::mock_display::MockDisplay;

let display = MockDisplay::new();

let width = display.bounding_box().size.width;
let height = display.bounding_box().size.height;

An advanced visual representation of failing MockDisplay assertions can be enabled by setting the EG_FANCY_PANIC environment variable to 1, for example, by calling EG_FANCY_PANIC=1 cargo test.

To use EG_FANCY_PANIC the new MockDisplay::assert_eq and assert_eq_with_message must be used instead of the assert_eq! macro.

#[test]
fn check_equality() {
    let expected = MockDisplay::from_pattern(&[ /* ... */ ]);

    let mut display = MockDisplay::new();

    Circle::new(Point::new(1, 1), 1)
        .into_styled(PrimitiveStyle::with_fill(BinaryColor::On))
        .draw(&mut display)?;

    display.assert_eq(&expected);
}

The assert_pattern and assert_pattern_with_message can be used to check the display state against a pattern without using MockDisplay::from_pattern and a separate assertion.

- #[test]
- fn tiny_circle_filled() {
-     let mut display = MockDisplay::new();
-
-     Circle::new(Point::new(1, 1), 1)
-         .into_styled(PrimitiveStyle::with_fill(BinaryColor::On))
-         .draw(&mut display)?;
-
-     assert_eq!(
-         display,
-         MockDisplay::from_pattern(&[
-             " # ",
-             "###",
-             " # "
-         ])
-     );
- }
+ #[test]
+ fn tiny_circle_filled() {
+     let mut display = MockDisplay::new();
+
+     Circle::new(Point::new(0, 0), 3)
+         .into_styled(PrimitiveStyle::with_fill(BinaryColor::On))
+         .draw(&mut display)
+         .unwrap();
+
+     display.assert_pattern(&[
+         " # ",
+         "###",
+         " # ",
+     ]);
+ }

The embedded-graphics-core crate

Types that are required by other crates that extend the functionality of embedded-graphics have been moved into the new embedded-graphics-core crate. The core crate is intended to provide a more stable interface for display drivers and image libraries to make them work across multiple major releases of embedded-graphics.

It is recommended that embedded-graphics is used for applications and embedded-graphics-core be used for crates that extend embedded graphics where possible. Note that some features required by e.g. image crates are currently only present in embedded-graphics, so using embedded-graphics-core is not always possible.

For display driver authors

DrawTarget now uses an associated type for the target color instead of a type parameter. As this can be a limitation versus older code which implements DrawTarget for e.g. C: Into<Rgb565>, the color_converted method can be used to create a draw target which converts the drawable's color format to the display's color format.

The DrawTarget trait now has an additional bound on the Dimensions trait to replace the removed size method. By using the Dimensions trait the drawable area of a draw targets can be positioned freely and is no longer limited to start in the origin at (0, 0). But for display drivers it is recommended that the drawable area does start at (0, 0). To simplify implementation and provide a type level guarantee that the drawable area starts at the origin, OriginDimensions can be implemented instead of Dimensions. The Dimensions trait is automatically implemented for all types that implement OriginDimensions.

Note that Dimensions and OriginDimensions should be imported from embedded-graphics-core, not embedded-graphics. See the relevant section for more details.

Method changes

All draw_* methods to draw specific primitives (draw_circle, draw_triangle, etc) have been removed. These methods were hard to implement correctly and consistently between different drivers. The new lower level draw methods are easier to implement and still improve performance over pixel by pixel drawing.

  • draw_iter

    Draws individual pixels to the display without a defined order. This is the only required method in this trait, however will likely be the slowest pixel drawing implementation as it cannot take advantage of hardware accelerated features (e.g. filling a given area with a solid color with fill_solid).

  • fill_contiguous

    Fills a given area with an iterator providing a contiguous stream of pixel colors. This may be used to efficiently draw an image or other non-transparent item to the display. The given pixel iterator can be assumed to be contiguous, iterating from top to bottom, each row left to right. This assumption potentially allows more efficient streaming of pixel data to a display.

  • fill_solid

    Fills a given area with a solid color.

  • clear

    Fills the entire display with a solid color.

These methods aim to be more compatible with hardware-accelerated drawing commands. Where possible, embedded-graphics drawables will use fill_contiguous and fill_solid to improve performance, however may fall back to draw_iter by default.

To reduce duplication, please search the DrawTarget documentation on https://docs.rs/embedded-graphics-core for more details on the usage and arguments of the above methods.

Example migration

The following example updates the SSD1306 driver using the BinaryColor color type.

- use crate::{
-     drawable::Pixel,
-     geometry::Size,
-     pixelcolor::{PixelColor, BinaryColor},
-     DrawTarget,
- };
-
- impl DrawTarget<BinaryColor> for Ssd1306 {
-     type Error = core::convert::Infallible;
-
-     fn draw_pixel(&mut self, pixel: Pixel<BinaryColor>) -> Result<(), Self::Error> {
-         // ...
-
-         Ok(())
-     }
-
-     fn size(&self) -> Size {
-         // ...
-     }
- }
+ use embedded_graphics_core::{
+     draw_target::DrawTarget,
+     geometry::{OriginDimensions, Size},
+     pixelcolor::{PixelColor, BinaryColor},
+     Pixel,
+ };
+
+ DrawTarget for Ssd1306 {
+     type Color = BinaryColor;
+     type Error = core::convert::Infallible;
+
+     fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
+     where
+         I: IntoIterator<Item = Pixel<Self::Color>>,
+     {
+         // ...
+
+         Ok(())
+     }
+ }
+
+ impl OriginDimensions for Ssd1306 {
+     fn size(&self) -> Size {
+         // ...
+     }
+ }

Image format support crates

Image format support crates must now implement the ImageDrawable and OriginDimensions traits from embedded-graphics-core to integrate with embedded-graphics.

The below examples shows an implementation for an imaginary MyRgb888Image which uses 24 bit RGB color.

use embedded_graphics::{
    draw_target::{DrawTarget, DrawTargetExt},
    geometry::{OriginDimensions, Size},
    image::ImageDrawable,
    pixelcolor::{PixelColor, Rgb888},
    primitives::Rectangle,
};

struct MyRgb888Image {
  // ...
}

impl ImageDrawable for MyRgb888Image {
    type Color = Rgb888;

    fn draw<D>(&self, target: &mut D) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Rgb888>,
    {
        // Draw the image to the target, e.g. by calling `target.fill_contiguous` or by using another `Drawable`.
    }

    fn draw_sub_image<D>(&self, target: &mut D, area: &Rectangle) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Self::Color>,
    {
        // Delegate to the draw() method using a reduced draw target
        self.draw(&mut target.translated(-area.top_left).clipped(area))
    }
}

impl OriginDimensions for MyRgb888Image {
    fn size(&self) -> Size {
        // Return image width and height in pixels
    }
}

For text rendering crates

Monospace fonts

Monospaced fonts no longer use a separate type per font and are now defined by using a MonoFont object. In most applications fonts will be declared as a compile time constant, but fonts can now also be loaded or generated at runtime.

- // The font bitmap has 32 character glyphs per row.
- const CHARS_PER_ROW: u32 = 32;
-
- // Map a given character to an index in the glyph bitmap
- fn char_offset_impl(c: char) -> u32 {
-     let fallback = '?' as u32 - ' ' as u32;
-     if c < ' ' {
-         return fallback;
-     }
-     if c <= '\u{007f}' {
-         return c as u32 - ' ' as u32;
-     }
-     if c < '\u{00A0}' || c > 'ÿ' {
-         return fallback;
-     }
-     c as u32 - ' ' as u32 - 32
- }
-
- #[derive(Debug, Copy, Clone)]
- pub struct ExampleFont {}
- impl Font for ExampleFont {
-     const FONT_IMAGE: &'static [u8] = include_bytes!("../data/ExampleFont.raw");
-     const CHARACTER_SIZE: Size = Size::new(5, 9);
-     const FONT_IMAGE_WIDTH: u32 = Self::CHARACTER_SIZE.width * CHARS_PER_ROW;
-
-     fn char_offset(c: char) -> u32 {
-         char_offset_impl(c)
-     }
- }
+ use embedded_graphics::{
+     geometry::Size,
+     image::ImageRaw,
+     mono_font::{mapping::ISO_8859_1, DecorationDimensions, MonoFont},
+ };
+
+ pub const EXAMPLE_FONT: MonoFont = MonoFont {
+     image: ImageRaw::new_binary(
+         // This example uses 32 characters per row, each character 5px across.
+         32 * 5,
+     ),
+     glyph_mapping: &ISO_8859_1,
+     character_size: Size::new(5, 9),
+     character_spacing: 0,
+     baseline: 7,
+     underline: DecorationDimensions::new(8, 1),
+     strikethrough: DecorationDimensions::new(4, 1),
+ };

Custom mappings between characters and glyph positions can be used by using StrGlyphMapping, using a function or implementing the GlyphMapping trait:

use embedded_graphics::{
    geometry::Size,
    image::ImageRaw,
    mono_font::{mapping::StrGlyphMapping, DecorationDimensions, MonoFont},
};

pub const EXAMPLE_FONT: MonoFont = MonoFont {
    image: ImageRaw::new_binary(
        include_bytes!("../data/digits.raw"),
        // In this example, this equals 10 characters per row, each character 15px across.
        10 * 15,
    ),
    glyph_mapping: &StrGlyphMapping::new("\009", 0),
    // or use a function:
    // glyph_mapping: &digit_mapping,
    character_size: Size::new(15, 30),
    character_spacing: 5,
    baseline: 29,
    underline: DecorationDimensions::default_underline(30),
    strikethrough: DecorationDimensions::default_strikethrough(30),
};

fn digit_mapping(c: char) -> usize {
    if c >= '0' || c <= '9' {
        c as usize - '0' as usize
    } else {
        0
    }
}

More complex fonts

Crates that handle text rendering more complex than simple monospace fonts should now implement the CharacterStyle and TextRenderer traits. These are used for both text styling and layout.

Please refer to their respective docs for implementation details.

An implementation of more complex font rendering using BDF font files is available in the eg-bdf crate, which may be useful as a reference for other implementations.