From 5e71cb508780bd112dc2d733a6f6c3fcf6954648 Mon Sep 17 00:00:00 2001 From: Sam Day Date: Mon, 25 Mar 2024 17:27:59 +0100 Subject: [PATCH 1/2] rudimentary support for BMPs with alpha masks "rudimentary" in the sense that the format will be allowed and parsed, but the alpha channel is thrown away when drawing and getting pixels. --- src/header/mod.rs | 8 ++++++++ src/iter.rs | 1 + src/lib.rs | 9 +++++++++ src/raw_bmp.rs | 3 +++ 4 files changed, 21 insertions(+) diff --git a/src/header/mod.rs b/src/header/mod.rs index 56a10b6..aad58a2 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -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. diff --git a/src/iter.rs b/src/iter.rs index a5011e4..c6613fd 100644 --- a/src/iter.rs +++ b/src/iter.rs @@ -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)) diff --git a/src/lib.rs b/src/lib.rs index 224d9c7..f5072f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -362,6 +362,11 @@ where RawColors::::new(&self.raw_bmp) .map(|raw| Rgb888::from(RawU24::new(raw.into_inner())).into()), ), + ColorType::Argb8888 => target.fill_contiguous( + &area, + RawColors::::new(&self.raw_bmp) + .map(|raw| Rgb888::from(RawU24::new(raw.into_inner() >> 8)).into()), + ), } } @@ -421,6 +426,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()), } } } diff --git a/src/raw_bmp.rs b/src/raw_bmp.rs index 7d73192..4c640fc 100644 --- a/src/raw_bmp.rs +++ b/src/raw_bmp.rs @@ -157,6 +157,7 @@ pub enum ColorType { Rgb565, Rgb888, Xrgb8888, + Argb8888, } impl ColorType { @@ -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); } From 512c09e31acb4ebe4416b316aaa73d19ed61fd03 Mon Sep 17 00:00:00 2001 From: Sam Day Date: Wed, 27 Mar 2024 16:18:31 +0100 Subject: [PATCH 2/2] basic blending support for transparent BMPs --- src/lib.rs | 46 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f5072f8..ab558e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -209,6 +209,7 @@ pub use raw_iter::{RawPixel, RawPixels}; pub struct Bmp<'a, C> { raw_bmp: RawBmp<'a>, color_type: PhantomData, + alpha_bg: Rgb888, } impl<'a, C> Bmp<'a, C> @@ -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>(mut self, alpha_bg: BG) -> Self { + self.alpha_bg = alpha_bg.into(); + self + } + /// 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 @@ -362,11 +372,37 @@ where RawColors::::new(&self.raw_bmp) .map(|raw| Rgb888::from(RawU24::new(raw.into_inner())).into()), ), - ColorType::Argb8888 => target.fill_contiguous( - &area, - RawColors::::new(&self.raw_bmp) - .map(|raw| Rgb888::from(RawU24::new(raw.into_inner() >> 8)).into()), - ), + ColorType::Argb8888 => { + target.fill_contiguous( + &area, + RawColors::::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)) + } 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() + }), + ) + } } }