Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add example on docs and /examples folder #66

Merged
merged 7 commits into from
Jul 10, 2023
Merged

Conversation

Slushee-a
Copy link
Contributor

Why

As mentioned in #65, two improvements that could be made were adding another example project and expanding documentation on set_pixels()

Changes included

  • Adds another example to the /examples folder.

    • Made for the esp32-c3
    • Using the ili9486 display with SPI
    • Shows a basic example of howset_pixels() can be used
  • Adds an example to the set_pixels() on the docs

@Slushee-a Slushee-a requested a review from almindor as a code owner July 8, 2023 12:32
examples/spi-ili9486-esp32-c3/src/main.rs Outdated Show resolved Hide resolved
Comment on lines 95 to 99
// Turn SPRITE into an array of pixels in the right colorspace
let sprite: [Rgb565; 256] = SPRITE.map(|(r, g, b)| Rgb565::new(r, g, b));

// Draw the 16*16 sprite on the top left corner of the screen
display.set_pixels(0, 0, 15, 15, sprite).unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty inefficient, because the sprite array needs to be build in RAM. I'm biased and would simply use embedded-graphics to draw an image, but if you want to demonstrate set_pixels I would suggest to change the SPRITE constant to be SPRITE: [Rgb565; 256] instead.

In addition to that the SPRITE constant seems to use values between 0 and 255 while Rgb565 expects 5 or 6 bit values depending on the channel.

Copy link
Contributor Author

@Slushee-a Slushee-a Jul 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did want to demonstrate how map can be used to turn a regular array into an array of RgbXYZ colors, but it does make more sense to just use embedded-graphics I will change this.

I will also add more to the example, drawing a smily face or something similar to demonstrate the use of embedded-graphics instead

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to make this an example of using set_pixels I'm OK with that, but the example should make it clear that there are other options available. For more memory constrained targets the array in RAM could be a problem for larger images.

You don't need an array, because set_pixels takes an iter: display.set_pixels(0, 0, 15, 15, SPRITE.map(|(r, g, b)| Rgb565::new(r, g, b))).unwrap(); should also work, without the memory overhead.

Copy link
Contributor Author

@Slushee-a Slushee-a Jul 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opted to use const SPRITE: [Rgb565; 256] = [Rgb565::RED, Rgb565::ORANGE ... instead with fill_contiguous, since I just wanted to demonstrate how it is possible to use a custom sprite. If this is okay you can resolve the conversation. Otherwise do suggest something

src/lib.rs Outdated
@@ -179,6 +179,23 @@ where
/// * `ey` - y coordinate end
/// * `colors` - anything that can provide `IntoIterator<Item = u16>` to iterate over pixel data
///
/// # Example
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to improve the docs for set_pixel further:

One thing that is important to document IMO is the behavior of set_pixels if the colors iterator contains more pixels than required by the rectangle bounds. If I remember correctly there is no bounds checking and the drawing operation would just wrap back to the top left of the rectangle.

This is different to embedded-graphics's similar fill_contiguous method and should be highlighted. Because this makes it impossible to just use something like display.set_pixel(0, 0, 10, 10, core::iter::repeat(Rgb565::RED)), which works in e-g.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is different to embedded-graphics's similar fill_contiguous method and should be highlighted

I added a # Warning here » mentioning that using fill_contiguous instead is recommended. Does this sound good or should I highlight that in another way?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move it to above the argument list and rephrase it a bit. It's quite important to know the behavior of and IMO it's harder to notice the warning if it is below the example.

Instead of a warning I would just describe the behavior of this method. A rough draft of a description:

No bounds checking is performed on the colors iterator, which can lead to unexpected behavior if the iterator returns more or less color values than the number of pixels in the given update region. Use fill_contiguous from embedded-graphics as an alternative with bounds checking.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One note here is that set_pixels is essentially a driver primitive. It's not meant to be used directly and is exposed so frameworks other than embedded-graphics can be used.

@Slushee-a
Copy link
Contributor Author

Changes included on the latest commit:

  • Removed map example from set_pixels, swapped directly with an Rgb666 array instead
  • Removed map + set_pixels segment from spi-ili9486-esp32-c3 example in favor of fill_contiguous with an Rgb565 array
  • Added an example drawing of a basic smiley face on the spi-ili9486-esp32-c3 example
  • Fix typo
  • Fix dependency usage

examples/spi-ili9486-esp32-c3/src/main.rs Outdated Show resolved Hide resolved
display.clear(Rgb565::BLACK).unwrap();

// draw the 16*16 sprite on the top left corner of the screen
display.fill_contiguous(&sprite_rectangle, SPRITE).unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't normally recommend to use the methods in DrawTarget directly in user code, but I'm not sure what the best option is in this case. The "embedded-graphics way" of drawing an image would be to use ImageRaw and Image, but that wouldn't work with the way you initialize the SPRITE constant, because ImageRaw expects a byte array. Perhaps we just leave it is now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is best to make the examples as 'correct' on the usage as possible, since they're made to demonstrate how things are intended to be done.
Maybe this example can be left as a 'possibility', but I think an example using ImageRaw and Image is important, since drawing custom sprites to the screen is probably a common use for the crate.
To be fair though, this is done by embedded-graphics so maybe it's out of scope.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would either remove the image drawing from this example or change it to something that is easy to use. Drawing pixels from an array that is manually added to the code isn't very common IMO.

Using the tinybmp instead of ImageRaw would make it much easier for a user to generate the necessary image data, because they can just export a BMP file from an image editor like Gimp. The BMP format does also natively support RGB565 colors, which makes it a good fit for the used display. See the tinybmp for an example how to use it: https://github.com/embedded-graphics/tinybmp#draw-a-bmp-image-to-an-embedded-graphics-draw-target

Regardless of whether we decide to remove the image drawing or not, a link to the embedded-graphics example would be a good idea to get an overview of the possible drawing operations: https://docs.rs/embedded-graphics/latest/embedded_graphics/examples/index.html

examples/spi-ili9486-esp32-c3/src/main.rs Outdated Show resolved Hide resolved
examples/spi-ili9486-esp32-c3/src/main.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated
@@ -179,6 +179,23 @@ where
/// * `ey` - y coordinate end
/// * `colors` - anything that can provide `IntoIterator<Item = u16>` to iterate over pixel data
///
/// # Example
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move it to above the argument list and rephrase it a bit. It's quite important to know the behavior of and IMO it's harder to notice the warning if it is below the example.

Instead of a warning I would just describe the behavior of this method. A rough draft of a description:

No bounds checking is performed on the colors iterator, which can lead to unexpected behavior if the iterator returns more or less color values than the number of pixels in the given update region. Use fill_contiguous from embedded-graphics as an alternative with bounds checking.

@Slushee-a
Copy link
Contributor Author

Slushee-a commented Jul 8, 2023

Changes included on the latest commit:

  • Switched away from Rectangle::with_corners in favor of Rectangle::new
  • Move smiley face drawing from main to a separate function
  • Remove outdated comment (Sorry!)
  • Explain set_pixels() behavior instead of warning

Copy link
Owner

@almindor almindor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but I'll wait for @rfuest to +1 as well

src/lib.rs Outdated
@@ -179,6 +179,23 @@ where
/// * `ey` - y coordinate end
/// * `colors` - anything that can provide `IntoIterator<Item = u16>` to iterate over pixel data
///
/// # Example
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One note here is that set_pixels is essentially a driver primitive. It's not meant to be used directly and is exposed so frameworks other than embedded-graphics can be used.

examples/spi-ili9486-esp32-c3/src/main.rs Outdated Show resolved Hide resolved
display.clear(Rgb565::BLACK).unwrap();

// draw the 16*16 sprite on the top left corner of the screen
display.fill_contiguous(&sprite_rectangle, SPRITE).unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would either remove the image drawing from this example or change it to something that is easy to use. Drawing pixels from an array that is manually added to the code isn't very common IMO.

Using the tinybmp instead of ImageRaw would make it much easier for a user to generate the necessary image data, because they can just export a BMP file from an image editor like Gimp. The BMP format does also natively support RGB565 colors, which makes it a good fit for the used display. See the tinybmp for an example how to use it: https://github.com/embedded-graphics/tinybmp#draw-a-bmp-image-to-an-embedded-graphics-draw-target

Regardless of whether we decide to remove the image drawing or not, a link to the embedded-graphics example would be a good idea to get an overview of the possible drawing operations: https://docs.rs/embedded-graphics/latest/embedded_graphics/examples/index.html

src/lib.rs Outdated Show resolved Hide resolved
@Slushee-a
Copy link
Contributor Author

Slushee-a commented Jul 9, 2023

I do agree with the fact that the fill_contiguous part of the example should be removed, since drawing basic shapes is already enough for the example.

Adding a link to the embedded-graphics examples is a great idea. Should it be added to the docs homepage or to the README?

Copy link
Collaborator

@rfuest rfuest left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, except for a missing word in one of the comments. Thanks for your efforts to improve the examples and docs. It's sometimes hard to know what is missing if you are already very familiar with the create.

src/lib.rs Outdated Show resolved Hide resolved
@rfuest
Copy link
Collaborator

rfuest commented Jul 10, 2023

Adding a link to the embedded-graphics examples is a great idea. Should it be added to the docs homepage or to the README?

Both locations would make sense, some users might learn about the crate via the README while others read the docs first. The README already contains a reference to e-g with this sentence: "embedded-graphics-core is used to provide the drawing API.", but that isn't very useful for users of the crate IMO. The distinction between embedded-graphics-core and embedded-graphics should be considered an implementation detail and not something that is important for users. A short description what e-g is and links to the relevant docs would be more useful than a link to e-g-core.

Co-authored-by: Ralf Fuest <[email protected]>
@rfuest rfuest merged commit d3c3347 into almindor:master Jul 10, 2023
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants