bernii / embedded-graphics-framebuf

Generic framebuffer implementation in Rust for use with embedded-graphics library
MIT License
26 stars 7 forks source link

Support for drawing Sprites with transparent pixels - filtering specific color #16

Closed georgik closed 1 year ago

georgik commented 1 year ago

Hello. I want to open a discussion on how to add support for drawing sprites with transparent pixels.

Right now, Embedded Graphics and FrameBuf do not allow filtering out specific color (or alpha) when drawing an image. This results in always transferring all pixels. What would it take to implement an option to filter colors?

E.g., in the days of old DOS games, pink color (RGB = 255,0,255) was not transferred during blit operation, resulting in a "transparent" background of the sprite.

I made an experimental implementation, basically just copying FrameBuf code and fixing it to a specific type, Rgb565, because generic C does not have the option to check for color. And I've added one condition to match the color value, which is ignored. The implementation works. I'm curious whether it would be possible to do it generically without enforcing the Rgb565 type.

https://github.com/georgik/esp32-spooky-maze-game/pull/18/files#diff-bbfc2130868c076ad19c8df25d7e8cc8ba13539606e23ad0d0e02a2120e47c51R68

Another option would be to use the alpha channel, but that seems to be a slightly more complex operation.

I would appreciate your input @bernii @pyaillet

ivmarkov commented 1 year ago

@georgik I don't think this solution needs to be part of FrameBuf. You need to define a wrapper type, let's call it Transparent which impls DrawTarget and wraps another DrawTarget (D) and which takes a custom color of type Option<D::Color>.

Then implement DrawTarget for your Transparent wrapper by each DrawTarget method delegating to D only if the passed color is is_some and by unwrapping the passed color then.

jounathaen commented 1 year ago

I also don't think that this is part of this crate. But if you have a solution, feel free to link it here. I'm curious as well!

georgik commented 1 year ago

@jounathaen The solution is here: https://github.com/georgik/esp32-spooky-maze-game/blob/main/spooky-core/src/spritebuf.rs#L68

impl<B: FrameBufferBackend<Color = Rgb565>> DrawTarget for SpriteBuf<B> {
    type Color = Rgb565;
    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() {
            if color.g() == 0 && color.b() == 31 && color.r() == 31 {
                continue;
            }

In the example above, I'm filtering pink color used in old DOS games as indicator of transparency. Here is whole working example: https://github.com/georgik/esp32-spooky-maze-game

The question is if there is more optimal solution to add filters or chain them for color processing.