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

rudimentary support for BMPs with alpha masks #43

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/header/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,14 @@ impl ChannelMasks {
blue: 0x0000FF,
alpha: 0,
};

/// Argb888 color masks.
pub const ARGB888: Self = Self {
red: 0xFF000000,
green: 0xFF0000,
blue: 0xFF00,
alpha: 0xFF,
};
}

/// Describes how the BMP file is compressed.
Expand Down
1 change: 1 addition & 0 deletions src/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ where
ColorType::Rgb555 => Rgb555::from(RawU16::from_u32(color)).into(),
ColorType::Rgb565 => Rgb565::from(RawU16::from_u32(color)).into(),
ColorType::Rgb888 | ColorType::Xrgb8888 => Rgb888::from(RawU24::from_u32(color)).into(),
ColorType::Argb8888 => Rgb888::from(RawU24::from_u32(color >> 8)).into(),
};

Some(Pixel(position, color))
Expand Down
45 changes: 45 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ pub use raw_iter::{RawPixel, RawPixels};
pub struct Bmp<'a, C> {
raw_bmp: RawBmp<'a>,
color_type: PhantomData<C>,
alpha_bg: Rgb888,
}

impl<'a, C> Bmp<'a, C>
Expand All @@ -225,9 +226,18 @@ where
Ok(Self {
raw_bmp,
color_type: PhantomData,
alpha_bg: Rgb888::BLACK,
})
}

/// If this image contains transparent pixels (pixels with an alpha channel), then blend these
/// pixels with the provided color. Note that this will only be used when drawing to a target.
/// It will not be applied when querying pixels from the image.
pub fn with_alpha_bg<BG: Into<Rgb888>>(mut self, alpha_bg: BG) -> Self {
self.alpha_bg = alpha_bg.into();
self
}

Comment on lines +233 to +240
Copy link
Member

Choose a reason for hiding this comment

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

I think this should be a regular setter (set_alpha_bg<...>(&mut self, alpha_bg: BG)) instead of this builder style method. Using a mutable reference instead of having to transfer ownership makes it more flexible.

An even better solution would be to add a wrapper type around the Bmp type, that sets the background color, but this would be a lot more work:

pub fn with_alpha_bg<BG: Into<Rgb888>>(&self, alpha_bg: BG) -> BmpWithAlphaBg<'_, C> {
    BmpWithAlphaBg {
        bmp: self,
        alpha_bg: alpha_bg.into(),
    }
}

This would allow the Bmp object to be drawn with any background color without needing a mutable reference or even ownership of the object. You could then easily implement something like:

fn draw_button<D: DrawTarget>(target: &mut D, text: &str, icon: &Bmp<'_, D::Color>, background: D::Color) -> Result<(), D::Error> {
    ....
}

This would still kind of work with a mutable reference to the icon, but wouldn't work with the original transfer of ownership in the with_alpha_bg method.

/// Returns an iterator over the pixels in this image.
///
/// The iterator always starts at the top left corner of the image, regardless of the row order
Expand Down Expand Up @@ -362,6 +372,37 @@ where
RawColors::<RawU32>::new(&self.raw_bmp)
.map(|raw| Rgb888::from(RawU24::new(raw.into_inner())).into()),
),
ColorType::Argb8888 => {
target.fill_contiguous(
&area,
RawColors::<RawU32>::new(&self.raw_bmp).map(|raw| {
// integer blending approach from https://stackoverflow.com/a/12016968
let v = raw.into_inner();
let mut alpha = v & 0xFF;
let inv_alpha = 256 - alpha;
alpha += 1;
if alpha == 0 {
// pixel is completely transparent, use bg color
self.alpha_bg
} else if alpha == 255 {
// pixel is completely opaque, just use its color
Rgb888::from(RawU24::new(v >> 8))
Comment on lines +383 to +389
Copy link
Member

Choose a reason for hiding this comment

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

I didn't have time to thoroughly check the blending code, but the checking for alpha == 0 and alpha == 255 must happen before 1 is added.

} else {
// pixel has transparency, blend with BG color
let col = Rgb888::from(RawU24::new(v >> 8));
Rgb888::new(
((alpha * col.r() as u32 + inv_alpha * self.alpha_bg.r() as u32)
>> 8) as u8,
((alpha * col.g() as u32 + inv_alpha * self.alpha_bg.g() as u32)
>> 8) as u8,
((alpha * col.b() as u32 + inv_alpha * self.alpha_bg.b() as u32)
>> 8) as u8,
)
}
.into()
}),
)
}
}
}

Expand Down Expand Up @@ -421,6 +462,10 @@ where
.raw_bmp
.pixel(p)
.map(|raw| Rgb888::from(RawU24::from_u32(raw)).into()),
ColorType::Argb8888 => self
.raw_bmp
.pixel(p)
.map(|raw| Rgb888::from(RawU24::from_u32(raw >> 8)).into()),
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/raw_bmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ pub enum ColorType {
Rgb565,
Rgb888,
Xrgb8888,
Argb8888,
}

impl ColorType {
Expand Down Expand Up @@ -184,6 +185,8 @@ impl ColorType {
if let Some(masks) = header.channel_masks {
if masks == ChannelMasks::RGB888 {
ColorType::Xrgb8888
} else if masks == ChannelMasks::ARGB888 {
ColorType::Argb8888
} else {
return Err(ParseError::UnsupportedChannelMasks);
}
Expand Down