From 28481b4de4d56c68aea199ba07cef3d9dbfe8533 Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Mon, 27 Oct 2025 16:59:13 +0100 Subject: [PATCH 1/9] feat: expose more structs to the user --- src/lib.rs | 3 +-- src/raw_iter.rs | 7 ++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3677562..62ae73a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -205,14 +205,13 @@ macro_rules! try_const { pub(crate) use try_const; use raw_bmp::ColorType; -use raw_iter::{RawColors, Rle4Pixels, Rle8Pixels}; pub use color_table::ColorTable; pub use header::CompressionMethod; pub use header::{Bpp, ChannelMasks, Header, RowOrder}; pub use iter::Pixels; pub use raw_bmp::RawBmp; -pub use raw_iter::{RawPixel, RawPixels}; +pub use raw_iter::{RawColors, RawPixel, RawPixels, Rle4Pixels, Rle8Pixels}; /// A BMP-format bitmap. /// diff --git a/src/raw_iter.rs b/src/raw_iter.rs index 458e3a3..53b8941 100644 --- a/src/raw_iter.rs +++ b/src/raw_iter.rs @@ -28,7 +28,8 @@ impl<'a, R> RawColors<'a, R> where RawDataSlice<'a, R, LittleEndian>: IntoIterator, { - pub(crate) fn new(raw_bmp: &'a RawBmp<'a>) -> Self { + /// Create a new raw color iterator. + pub fn new(raw_bmp: &'a RawBmp<'a>) -> Self { let header = raw_bmp.header(); let width = header.image_size.width as usize; @@ -129,7 +130,7 @@ pub struct Rle8Pixels<'a> { impl<'a> Rle8Pixels<'a> { /// Create a new RLE pixel iterator. - pub(crate) fn new(raw_bmp: &RawBmp<'a>) -> Rle8Pixels<'a> { + pub fn new(raw_bmp: &RawBmp<'a>) -> Rle8Pixels<'a> { let header = raw_bmp.header(); Rle8Pixels { data: raw_bmp.image_data(), @@ -273,7 +274,7 @@ pub struct Rle4Pixels<'a> { impl<'a> Rle4Pixels<'a> { /// Create a new RLE pixel iterator. - pub(crate) fn new(raw_bmp: &RawBmp<'a>) -> Rle4Pixels<'a> { + pub fn new(raw_bmp: &RawBmp<'a>) -> Rle4Pixels<'a> { let header = raw_bmp.header(); Rle4Pixels { data: raw_bmp.image_data(), From 22b7dc46acacaa48feedb2ea8ba70984fe7e517f Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Mon, 27 Oct 2025 17:06:02 +0100 Subject: [PATCH 2/9] chore: changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1902f9b..6915ccf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ - [#50](https://github.com/embedded-graphics/tinybmp/pull/50) Fixed handling of padding bytes in absolute mode for RLE4 compressed files. +### Added + +- Make `RawColors`, `Rle4Pixels` and `Rle8Pixels` public. + ### Changed - **(breaking)** [#49](https://github.com/embedded-graphics/tinybmp/pull/41) Use 1.81 as the MSRV. From a5b62fb7a108950f046f98f98689b49c702041fa Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Wed, 5 Nov 2025 12:16:48 +0100 Subject: [PATCH 3/9] chore: disable warning for `header_type` of `DibHeader` `header_type` is never read and `DibHeader` is private, so the compiler emits a warning. I added `#[expect(unused)]` so if it gets used at some point, compiler will ask you to remove it. `#[allow(dead_code)]` is worse because it will itself become a dead code if `header_type` gets used. --- src/header/dib_header.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/header/dib_header.rs b/src/header/dib_header.rs index b62c282..5216f94 100644 --- a/src/header/dib_header.rs +++ b/src/header/dib_header.rs @@ -21,6 +21,7 @@ pub struct DibHeader { pub compression: CompressionMethod, pub image_data_len: u32, pub channel_masks: Option, + #[expect(unused)] pub header_type: HeaderType, pub row_order: RowOrder, pub color_table_num_entries: u32, From e4530f80edbf654e8f79ec9104a571d5877debbd Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Wed, 5 Nov 2025 11:12:01 +0100 Subject: [PATCH 4/9] refactor: group logic for rle pixel coordinates In preparation of remove that logic from them and moving it up. --- src/raw_iter.rs | 88 ++++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/src/raw_iter.rs b/src/raw_iter.rs index 53b8941..83af9a1 100644 --- a/src/raw_iter.rs +++ b/src/raw_iter.rs @@ -113,6 +113,13 @@ enum RleState { EndOfBitmap, } +struct RlePixelPoints { + /// The location of the next pixel + next_pixel: Point, + /// The width of a line in pixels + width: u32, +} + /// Iterator over individual BMP RLE8 encoded pixels. /// /// Each pixel is returned as a `u32` regardless of the bit depth of the source image. @@ -122,29 +129,17 @@ pub struct Rle8Pixels<'a> { data: &'a [u8], /// Our state rle_state: RleState, - /// The width of a line in pixels - width: u32, - /// The location of the next pixel - next_pixel: Point, + points: RlePixelPoints, } -impl<'a> Rle8Pixels<'a> { - /// Create a new RLE pixel iterator. - pub fn new(raw_bmp: &RawBmp<'a>) -> Rle8Pixels<'a> { - let header = raw_bmp.header(); - Rle8Pixels { - data: raw_bmp.image_data(), - rle_state: RleState::Starting, - width: header.image_size.width, - // RLE encoded bitmaps are upside down - next_pixel: Point::new(0, (header.image_size.height - 1) as i32), - } +impl RlePixelPoints { + fn peek(&self) -> Point { + self.next_pixel } - /// Bump the cursor to the next position in the bitmap. /// /// Note that RLE bitmaps are upside down. - fn move_position(&mut self) -> Point { + fn advance(&mut self) -> Point { let old_position = self.next_pixel; self.next_pixel.x += 1; if self.next_pixel.x == self.width as i32 { @@ -155,6 +150,22 @@ impl<'a> Rle8Pixels<'a> { } } +impl<'a> Rle8Pixels<'a> { + /// Create a new RLE pixel iterator. + pub(crate) fn new(raw_bmp: &RawBmp<'a>) -> Rle8Pixels<'a> { + let header = raw_bmp.header(); + Rle8Pixels { + data: raw_bmp.image_data(), + rle_state: RleState::Starting, + points: RlePixelPoints { + width: header.image_size.width, + // RLE encoded bitmaps are upside down + next_pixel: Point::new(0, (header.image_size.height - 1) as i32), + }, + } + } +} + impl<'a> Iterator for Rle8Pixels<'a> { type Item = RawPixel; @@ -184,7 +195,7 @@ impl<'a> Iterator for Rle8Pixels<'a> { } else { self.data = self.data.get(1..)?; } - let this_pixel = self.move_position(); + let this_pixel = self.points.advance(); return Some(RawPixel::new(this_pixel, u32::from(value))); } RleState::Running { @@ -201,7 +212,7 @@ impl<'a> Iterator for Rle8Pixels<'a> { is_odd, }; } - let this_pixel = self.move_position(); + let this_pixel = self.points.advance(); return Some(RawPixel::new(this_pixel, u32::from(value))); } RleState::Starting => { @@ -219,7 +230,7 @@ impl<'a> Iterator for Rle8Pixels<'a> { match param { 0 => { // End of line - if self.next_pixel.x != 0 { + if self.points.peek().x != 0 { return None; } } @@ -266,36 +277,23 @@ pub struct Rle4Pixels<'a> { data: &'a [u8], /// Our state rle_state: RleState, - /// The width of a line in pixels - width: u32, - /// The location of the next pixel - next_pixel: Point, + points: RlePixelPoints, } + impl<'a> Rle4Pixels<'a> { /// Create a new RLE pixel iterator. - pub fn new(raw_bmp: &RawBmp<'a>) -> Rle4Pixels<'a> { + pub(crate) fn new(raw_bmp: &RawBmp<'a>) -> Rle4Pixels<'a> { let header = raw_bmp.header(); Rle4Pixels { data: raw_bmp.image_data(), rle_state: RleState::Starting, - width: header.image_size.width, - // RLE encoded bitmaps are upside down - next_pixel: Point::new(0, (header.image_size.height - 1) as i32), - } - } - - /// Bump the cursor to the next position in the bitmap. - /// - /// Note that RLE bitmaps are upside down. - fn move_position(&mut self) -> Point { - let old_position = self.next_pixel; - self.next_pixel.x += 1; - if self.next_pixel.x == self.width as i32 { - self.next_pixel.x = 0; - self.next_pixel.y = self.next_pixel.y.saturating_sub(1); + points: RlePixelPoints { + width: header.image_size.width, + // RLE encoded bitmaps are upside down + next_pixel: Point::new(0, (header.image_size.height - 1) as i32), + }, } - old_position } } @@ -349,7 +347,7 @@ impl<'a> Iterator for Rle4Pixels<'a> { // remove the padding byte too self.data = self.data.get(1..)?; } - let this_pixel = self.move_position(); + let this_pixel = self.points.advance(); return Some(RawPixel::new(this_pixel, u32::from(nibble_value))); } RleState::Running { @@ -384,7 +382,7 @@ impl<'a> Iterator for Rle4Pixels<'a> { }; } - let this_pixel = self.move_position(); + let this_pixel = self.points.advance(); return Some(RawPixel::new(this_pixel, u32::from(nibble_value))); } RleState::Starting => { @@ -402,7 +400,7 @@ impl<'a> Iterator for Rle4Pixels<'a> { match param { 0 => { // End of line - if self.next_pixel.x != 0 { + if self.points.peek().x != 0 { return None; } } @@ -496,7 +494,7 @@ pub struct RawPixel { impl RawPixel { /// Creates a new raw pixel. - pub const fn new(position: Point, color: u32) -> Self { + pub(crate) const fn new(position: Point, color: u32) -> Self { Self { position, color } } } From 42560e5867602a560c2f98ce9ebc964dc1aee726 Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Wed, 5 Nov 2025 12:09:54 +0100 Subject: [PATCH 5/9] feat: `RawBmp::colors` Additional work that was required by `RawBmp::colors`: - Making `DynamicRawColors` public. - Making `DynamicRawColors` implement iterator. - Refactoring logic. - Rename `Rle{4,8}Pixels` to `Rle{4,8}Colors` - Due to removal of position handling in `Rle{4,8}Pixels`, they can no longer assert the the new line corresponds with the width. I left the assertion functionally the same, but with slightly different approach. Personally, I don't think it is necessary that that assert there at all - like, either way the image would be broken and developer will have to fix the `bmp` file, that assertion isn't helpful at all. --- src/lib.rs | 32 ++++--- src/raw_bmp.rs | 7 +- src/raw_iter.rs | 247 ++++++++++++++++++++++++++++++------------------ 3 files changed, 176 insertions(+), 110 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 62ae73a..03fec0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -211,7 +211,7 @@ pub use header::CompressionMethod; pub use header::{Bpp, ChannelMasks, Header, RowOrder}; pub use iter::Pixels; pub use raw_bmp::RawBmp; -pub use raw_iter::{RawColors, RawPixel, RawPixels, Rle4Pixels, Rle8Pixels}; +pub use raw_iter::{DynamicRawColors, RawColors, RawPixel, RawPixels, Rle4Colors, Rle8Colors}; /// A BMP-format bitmap. /// @@ -293,19 +293,20 @@ where let fallback_color = C::from(Rgb888::BLACK); if let Some(color_table) = self.raw_bmp.color_table() { if header.compression_method == CompressionMethod::Rle4 { - let mut colors = Rle4Pixels::new(&self.raw_bmp).map(|raw_pixel| { + let (mut colors, _points) = Rle4Colors::new(&self.raw_bmp); + let map_color = |color: RawU4| { color_table - .get(raw_pixel.color) + .get(color.into_inner() as u32) .map(Into::into) .unwrap_or(fallback_color) - }); + }; // RLE produces pixels in bottom-up order, so we draw them line by line rather than the entire bitmap at once. for y in (0..area.size.height).rev() { + colors.start_a_line(); + let row = Rectangle::new(Point::new(0, y as i32), slice_size); - target.fill_contiguous( - &row, - colors.by_ref().take(area.size.width as usize), - )?; + let colors = colors.by_ref().map(map_color); + target.fill_contiguous(&row, colors.take(area.size.width as usize))?; } Ok(()) } else { @@ -327,19 +328,20 @@ where let fallback_color = C::from(Rgb888::BLACK); if let Some(color_table) = self.raw_bmp.color_table() { if header.compression_method == CompressionMethod::Rle8 { - let mut colors = Rle8Pixels::new(&self.raw_bmp).map(|raw_pixel| { + let (mut colors, _points) = Rle8Colors::new(&self.raw_bmp); + let map_color = |color: RawU8| { color_table - .get(raw_pixel.color) + .get(color.into_inner() as u32) .map(Into::into) .unwrap_or(fallback_color) - }); + }; // RLE produces pixels in bottom-up order, so we draw them line by line rather than the entire bitmap at once. for y in (0..area.size.height).rev() { + colors.start_a_line(); + let row = Rectangle::new(Point::new(0, y as i32), slice_size); - target.fill_contiguous( - &row, - colors.by_ref().take(area.size.width as usize), - )?; + let colors = colors.by_ref().map(map_color); + target.fill_contiguous(&row, colors.take(area.size.width as usize))?; } Ok(()) } else { diff --git a/src/raw_bmp.rs b/src/raw_bmp.rs index d61685f..9db3b2b 100644 --- a/src/raw_bmp.rs +++ b/src/raw_bmp.rs @@ -8,7 +8,7 @@ use embedded_graphics::{ use crate::{ color_table::ColorTable, header::{Bpp, Header}, - raw_iter::RawPixels, + raw_iter::{DynamicRawColors, RawPixels}, try_const, ChannelMasks, ParseError, RowOrder, }; @@ -103,6 +103,11 @@ impl<'a> RawBmp<'a> { RawPixels::new(self) } + /// Return an iterator over the raw colors in the image. Positions are dependent on the row order. + pub fn colors(&self) -> DynamicRawColors<'_> { + self.pixels().colors + } + /// Returns the raw color of a pixel. /// /// Returns `None` if `p` is outside the image bounding box. Note that this function doesn't diff --git a/src/raw_iter.rs b/src/raw_iter.rs index 83af9a1..03c7412 100644 --- a/src/raw_iter.rs +++ b/src/raw_iter.rs @@ -4,7 +4,6 @@ use embedded_graphics::{ iterator::raw::RawDataSlice, pixelcolor::raw::{LittleEndian, RawU1, RawU16, RawU24, RawU32, RawU4, RawU8}, prelude::*, - primitives::{rectangle, Rectangle}, }; use crate::{ @@ -63,31 +62,70 @@ where } } -enum DynamicRawColors<'a> { +/// Iterator over dynamic color. Positions are dependent on the row order. +#[allow(missing_debug_implementations)] +pub enum DynamicRawColors<'a> { + /// 1 bit per pixel Bpp1(RawColors<'a, RawU1>), + /// 4 bit per pixel Bpp4(RawColors<'a, RawU4>), + /// 8 bit per pixel Bpp8(RawColors<'a, RawU8>), + /// 16 bit per pixel Bpp16(RawColors<'a, RawU16>), + /// 24 bit per pixel Bpp24(RawColors<'a, RawU24>), + /// 32 bit per pixel Bpp32(RawColors<'a, RawU32>), - Bpp4Rle(Rle4Pixels<'a>), - Bpp8Rle(Rle8Pixels<'a>), + /// 4 bit per pixel RLE encoded + Bpp4Rle(Rle4Colors<'a>), + /// 8 bit per pixel RLE encoded + Bpp8Rle(Rle8Colors<'a>), } -impl<'a> DynamicRawColors<'a> { - pub fn new(raw_bmp: &'a RawBmp<'a>) -> Self { - let header = raw_bmp.header(); - match header.compression_method { - CompressionMethod::Rle4 => DynamicRawColors::Bpp4Rle(Rle4Pixels::new(raw_bmp)), - CompressionMethod::Rle8 => DynamicRawColors::Bpp8Rle(Rle8Pixels::new(raw_bmp)), - CompressionMethod::Rgb | CompressionMethod::Bitfields => match header.bpp { - Bpp::Bits1 => DynamicRawColors::Bpp1(RawColors::new(raw_bmp)), - Bpp::Bits4 => DynamicRawColors::Bpp4(RawColors::new(raw_bmp)), - Bpp::Bits8 => DynamicRawColors::Bpp8(RawColors::new(raw_bmp)), - Bpp::Bits16 => DynamicRawColors::Bpp16(RawColors::new(raw_bmp)), - Bpp::Bits24 => DynamicRawColors::Bpp24(RawColors::new(raw_bmp)), - Bpp::Bits32 => DynamicRawColors::Bpp32(RawColors::new(raw_bmp)), - }, +impl core::fmt::Debug for DynamicRawColors<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + DynamicRawColors::Bpp1(_) => f.debug_tuple("DynamicRawColors::Bpp1").finish(), + DynamicRawColors::Bpp4(_) => f.debug_tuple("DynamicRawColors::Bpp4").finish(), + DynamicRawColors::Bpp8(_) => f.debug_tuple("DynamicRawColors::Bpp8").finish(), + DynamicRawColors::Bpp16(_) => f.debug_tuple("DynamicRawColors::Bpp16").finish(), + DynamicRawColors::Bpp24(_) => f.debug_tuple("DynamicRawColors::Bpp24").finish(), + DynamicRawColors::Bpp32(_) => f.debug_tuple("DynamicRawColors::Bpp32").finish(), + DynamicRawColors::Bpp4Rle(_) => f.debug_tuple("DynamicRawColors::Bpp4Rle").finish(), + DynamicRawColors::Bpp8Rle(_) => f.debug_tuple("DynamicRawColors::Bpp8Rle").finish(), + } + } +} + +impl DynamicRawColors<'_> { + /// Get the row order of the bitmap. + pub fn row_order(&self) -> RowOrder { + match self { + DynamicRawColors::Bpp1(_) + | DynamicRawColors::Bpp4(_) + | DynamicRawColors::Bpp8(_) + | DynamicRawColors::Bpp16(_) + | DynamicRawColors::Bpp24(_) + | DynamicRawColors::Bpp32(_) => RowOrder::TopDown, + DynamicRawColors::Bpp4Rle(_) | DynamicRawColors::Bpp8Rle(_) => RowOrder::BottomUp, + } + } +} + +impl Iterator for DynamicRawColors<'_> { + type Item = u32; + + fn next(&mut self) -> Option { + match self { + DynamicRawColors::Bpp1(colors) => colors.next().map(|r| u32::from(r.into_inner())), + DynamicRawColors::Bpp4(colors) => colors.next().map(|r| u32::from(r.into_inner())), + DynamicRawColors::Bpp8(colors) => colors.next().map(|r| u32::from(r.into_inner())), + DynamicRawColors::Bpp16(colors) => colors.next().map(|r| u32::from(r.into_inner())), + DynamicRawColors::Bpp24(colors) => colors.next().map(|r| r.into_inner()), + DynamicRawColors::Bpp32(colors) => colors.next().map(|r| r.into_inner()), + DynamicRawColors::Bpp4Rle(state) => state.next().map(|r| u32::from(r.into_inner())), + DynamicRawColors::Bpp8Rle(state) => state.next().map(|r| u32::from(r.into_inner())), } } } @@ -113,7 +151,7 @@ enum RleState { EndOfBitmap, } -struct RlePixelPoints { +pub struct PixelPoints { /// The location of the next pixel next_pixel: Point, /// The width of a line in pixels @@ -124,22 +162,16 @@ struct RlePixelPoints { /// /// Each pixel is returned as a `u32` regardless of the bit depth of the source image. #[derive(Debug)] -pub struct Rle8Pixels<'a> { +pub struct Rle8Colors<'a> { /// Our source data data: &'a [u8], /// Our state rle_state: RleState, - points: RlePixelPoints, + start_of_line: bool, } -impl RlePixelPoints { - fn peek(&self) -> Point { - self.next_pixel - } - /// Bump the cursor to the next position in the bitmap. - /// - /// Note that RLE bitmaps are upside down. - fn advance(&mut self) -> Point { +impl PixelPoints { + fn next_up_dir(&mut self) -> Point { let old_position = self.next_pixel; self.next_pixel.x += 1; if self.next_pixel.x == self.width as i32 { @@ -148,26 +180,43 @@ impl RlePixelPoints { } old_position } + + fn next_down_dir(&mut self) -> Point { + let old_position = self.next_pixel; + self.next_pixel.x += 1; + if self.next_pixel.x == self.width as i32 { + self.next_pixel.x = 0; + self.next_pixel.y += 1; + } + old_position + } } -impl<'a> Rle8Pixels<'a> { +impl<'a> Rle8Colors<'a> { /// Create a new RLE pixel iterator. - pub(crate) fn new(raw_bmp: &RawBmp<'a>) -> Rle8Pixels<'a> { + pub(crate) fn new(raw_bmp: &RawBmp<'a>) -> (Rle8Colors<'a>, PixelPoints) { let header = raw_bmp.header(); - Rle8Pixels { + let points = PixelPoints { + width: header.image_size.width, + // RLE encoded bitmaps are upside down + next_pixel: Point::new(0, (header.image_size.height - 1) as i32), + }; + let this = Rle8Colors { data: raw_bmp.image_data(), rle_state: RleState::Starting, - points: RlePixelPoints { - width: header.image_size.width, - // RLE encoded bitmaps are upside down - next_pixel: Point::new(0, (header.image_size.height - 1) as i32), - }, - } + start_of_line: false, + }; + (this, points) + } + + /// Indicate that a new line is starting. Required for correct RLE decoding. + pub fn start_a_line(&mut self) { + self.start_of_line = true; } } -impl<'a> Iterator for Rle8Pixels<'a> { - type Item = RawPixel; +impl<'a> Iterator for Rle8Colors<'a> { + type Item = RawU8; fn next(&mut self) -> Option { loop { @@ -195,8 +244,7 @@ impl<'a> Iterator for Rle8Pixels<'a> { } else { self.data = self.data.get(1..)?; } - let this_pixel = self.points.advance(); - return Some(RawPixel::new(this_pixel, u32::from(value))); + return Some(RawU8::from(value)); } RleState::Running { remaining, @@ -212,8 +260,7 @@ impl<'a> Iterator for Rle8Pixels<'a> { is_odd, }; } - let this_pixel = self.points.advance(); - return Some(RawPixel::new(this_pixel, u32::from(value))); + return Some(RawU8::from(value)); } RleState::Starting => { let length = *self.data.get(0)?; @@ -229,8 +276,7 @@ impl<'a> Iterator for Rle8Pixels<'a> { // the pair, which can be one of the following values. match param { 0 => { - // End of line - if self.points.peek().x != 0 { + if !self.start_of_line { return None; } } @@ -272,33 +318,39 @@ impl<'a> Iterator for Rle8Pixels<'a> { /// /// Each pixel is returned as a `u32` regardless of the bit depth of the source image. #[derive(Debug)] -pub struct Rle4Pixels<'a> { +pub struct Rle4Colors<'a> { /// Our source data data: &'a [u8], /// Our state rle_state: RleState, - points: RlePixelPoints, + start_of_line: bool, } - -impl<'a> Rle4Pixels<'a> { +impl<'a> Rle4Colors<'a> { /// Create a new RLE pixel iterator. - pub(crate) fn new(raw_bmp: &RawBmp<'a>) -> Rle4Pixels<'a> { + pub(crate) fn new(raw_bmp: &RawBmp<'a>) -> (Rle4Colors<'a>, PixelPoints) { let header = raw_bmp.header(); - Rle4Pixels { + let points = PixelPoints { + width: header.image_size.width, + // RLE encoded bitmaps are upside down + next_pixel: Point::new(0, (header.image_size.height - 1) as i32), + }; + let this = Rle4Colors { data: raw_bmp.image_data(), rle_state: RleState::Starting, - points: RlePixelPoints { - width: header.image_size.width, - // RLE encoded bitmaps are upside down - next_pixel: Point::new(0, (header.image_size.height - 1) as i32), - }, - } + start_of_line: false, + }; + (this, points) + } + + /// Indicate that a new line is starting. Required for correct RLE decoding. + pub fn start_a_line(&mut self) { + self.start_of_line = true; } } -impl<'a> Iterator for Rle4Pixels<'a> { - type Item = RawPixel; +impl<'a> Iterator for Rle4Colors<'a> { + type Item = RawU4; fn next(&mut self) -> Option { loop { @@ -347,8 +399,7 @@ impl<'a> Iterator for Rle4Pixels<'a> { // remove the padding byte too self.data = self.data.get(1..)?; } - let this_pixel = self.points.advance(); - return Some(RawPixel::new(this_pixel, u32::from(nibble_value))); + return Some(RawU4::from(nibble_value)); } RleState::Running { remaining, @@ -382,8 +433,7 @@ impl<'a> Iterator for Rle4Pixels<'a> { }; } - let this_pixel = self.points.advance(); - return Some(RawPixel::new(this_pixel, u32::from(nibble_value))); + return Some(RawU4::from(nibble_value)); } RleState::Starting => { let length = *self.data.get(0)?; @@ -399,8 +449,7 @@ impl<'a> Iterator for Rle4Pixels<'a> { // the pair, which can be one of the following values. match param { 0 => { - // End of line - if self.points.peek().x != 0 { + if !self.start_of_line { return None; } } @@ -444,15 +493,43 @@ impl<'a> Iterator for Rle4Pixels<'a> { /// Each pixel is returned as a `u32` regardless of the bit depth of the source image. #[allow(missing_debug_implementations)] pub struct RawPixels<'a> { - colors: DynamicRawColors<'a>, - points: rectangle::Points, + pub(crate) colors: DynamicRawColors<'a>, + pub(crate) points: PixelPoints, } impl<'a> RawPixels<'a> { pub(crate) fn new(raw_bmp: &'a RawBmp<'a>) -> Self { - Self { - colors: DynamicRawColors::new(raw_bmp), - points: Rectangle::new(Point::zero(), raw_bmp.header().image_size).points(), + let header = raw_bmp.header(); + match header.compression_method { + CompressionMethod::Rle4 => { + let (colors, points) = Rle4Colors::new(raw_bmp); + Self { + colors: DynamicRawColors::Bpp4Rle(colors), + points, + } + } + CompressionMethod::Rle8 => { + let (colors, points) = Rle8Colors::new(raw_bmp); + Self { + colors: DynamicRawColors::Bpp8Rle(colors), + points, + } + } + CompressionMethod::Rgb | CompressionMethod::Bitfields => { + let points = PixelPoints { + width: header.image_size.width, + next_pixel: Point::zero(), + }; + let colors = match header.bpp { + Bpp::Bits1 => DynamicRawColors::Bpp1(RawColors::new(raw_bmp)), + Bpp::Bits4 => DynamicRawColors::Bpp4(RawColors::new(raw_bmp)), + Bpp::Bits8 => DynamicRawColors::Bpp8(RawColors::new(raw_bmp)), + Bpp::Bits16 => DynamicRawColors::Bpp16(RawColors::new(raw_bmp)), + Bpp::Bits24 => DynamicRawColors::Bpp24(RawColors::new(raw_bmp)), + Bpp::Bits32 => DynamicRawColors::Bpp32(RawColors::new(raw_bmp)), + }; + Self { colors, points } + } } } } @@ -461,22 +538,11 @@ impl Iterator for RawPixels<'_> { type Item = RawPixel; fn next(&mut self) -> Option { - let color = match &mut self.colors { - DynamicRawColors::Bpp1(colors) => colors.next().map(|r| u32::from(r.into_inner())), - DynamicRawColors::Bpp4(colors) => colors.next().map(|r| u32::from(r.into_inner())), - DynamicRawColors::Bpp8(colors) => colors.next().map(|r| u32::from(r.into_inner())), - DynamicRawColors::Bpp16(colors) => colors.next().map(|r| u32::from(r.into_inner())), - DynamicRawColors::Bpp24(colors) => colors.next().map(|r| r.into_inner()), - DynamicRawColors::Bpp32(colors) => colors.next().map(|r| r.into_inner()), - DynamicRawColors::Bpp4Rle(state) => { - return state.next(); - } - DynamicRawColors::Bpp8Rle(state) => { - return state.next(); - } - }?; - - let position = self.points.next()?; + let color = self.colors.next()?; + let position = match self.colors.row_order() { + RowOrder::TopDown => self.points.next_down_dir(), + RowOrder::BottomUp => self.points.next_up_dir(), + }; Some(RawPixel { position, color }) } @@ -491,10 +557,3 @@ pub struct RawPixel { /// The raw pixel color. pub color: u32, } - -impl RawPixel { - /// Creates a new raw pixel. - pub(crate) const fn new(position: Point, color: u32) -> Self { - Self { position, color } - } -} From a038e548cb6ec52b5d5a5ccb0fc40b70a7baede6 Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Wed, 5 Nov 2025 13:04:16 +0100 Subject: [PATCH 6/9] chore: changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6915ccf..9dbfa3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ ### Added -- Make `RawColors`, `Rle4Pixels` and `Rle8Pixels` public. +- [#51](https://github.com/embedded-graphics/tinybmp/pull/51) Added `RawBmp::colors` method to iterator of the raw color values in a file. +- [#51](https://github.com/embedded-graphics/tinybmp/pull/51) Added `DynamicRawColors`, `RawColors`, `Rle4Colors`, and `Rle8Colors` iterators. ### Changed From 44c5e4c845e75ff40048d20a9e44d8ff34eed691 Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Mon, 10 Nov 2025 11:59:20 +0100 Subject: [PATCH 7/9] fix: remove unused `header_type` from private `DibHeader` --- src/header/dib_header.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/header/dib_header.rs b/src/header/dib_header.rs index 5216f94..652570e 100644 --- a/src/header/dib_header.rs +++ b/src/header/dib_header.rs @@ -21,8 +21,6 @@ pub struct DibHeader { pub compression: CompressionMethod, pub image_data_len: u32, pub channel_masks: Option, - #[expect(unused)] - pub header_type: HeaderType, pub row_order: RowOrder, pub color_table_num_entries: u32, } @@ -106,7 +104,6 @@ impl DibHeader { Ok(( input, Self { - header_type, image_size: Size::new(image_width.unsigned_abs(), image_height.unsigned_abs()), image_data_len, bpp, From 732f35b43a06d91c0a3660d60b0fa28b7e3ecaf7 Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Mon, 10 Nov 2025 12:13:12 +0100 Subject: [PATCH 8/9] fix: review comments --- src/lib.rs | 8 +-- src/raw_bmp.rs | 6 +- src/raw_iter.rs | 160 +++++++++++++++++++++++------------------------- 3 files changed, 86 insertions(+), 88 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 03fec0d..32fb510 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -293,7 +293,7 @@ where let fallback_color = C::from(Rgb888::BLACK); if let Some(color_table) = self.raw_bmp.color_table() { if header.compression_method == CompressionMethod::Rle4 { - let (mut colors, _points) = Rle4Colors::new(&self.raw_bmp); + let mut colors = Rle4Colors::new(&self.raw_bmp); let map_color = |color: RawU4| { color_table .get(color.into_inner() as u32) @@ -302,7 +302,7 @@ where }; // RLE produces pixels in bottom-up order, so we draw them line by line rather than the entire bitmap at once. for y in (0..area.size.height).rev() { - colors.start_a_line(); + colors.start_row(); let row = Rectangle::new(Point::new(0, y as i32), slice_size); let colors = colors.by_ref().map(map_color); @@ -328,7 +328,7 @@ where let fallback_color = C::from(Rgb888::BLACK); if let Some(color_table) = self.raw_bmp.color_table() { if header.compression_method == CompressionMethod::Rle8 { - let (mut colors, _points) = Rle8Colors::new(&self.raw_bmp); + let mut colors = Rle8Colors::new(&self.raw_bmp); let map_color = |color: RawU8| { color_table .get(color.into_inner() as u32) @@ -337,7 +337,7 @@ where }; // RLE produces pixels in bottom-up order, so we draw them line by line rather than the entire bitmap at once. for y in (0..area.size.height).rev() { - colors.start_a_line(); + colors.start_row(); let row = Rectangle::new(Point::new(0, y as i32), slice_size); let colors = colors.by_ref().map(map_color); diff --git a/src/raw_bmp.rs b/src/raw_bmp.rs index 9db3b2b..ac0b01f 100644 --- a/src/raw_bmp.rs +++ b/src/raw_bmp.rs @@ -103,7 +103,11 @@ impl<'a> RawBmp<'a> { RawPixels::new(self) } - /// Return an iterator over the raw colors in the image. Positions are dependent on the row order. + /// Returns an iterator over the raw colors in the image. + /// + /// The iterator returns the color value in the order the pixels are stored in the file. + /// Use [`row_order`](DynamicRawColors::row_order) to determine the correct + /// pixel arrangement. pub fn colors(&self) -> DynamicRawColors<'_> { self.pixels().colors } diff --git a/src/raw_iter.rs b/src/raw_iter.rs index 03c7412..168e528 100644 --- a/src/raw_iter.rs +++ b/src/raw_iter.rs @@ -28,7 +28,7 @@ where RawDataSlice<'a, R, LittleEndian>: IntoIterator, { /// Create a new raw color iterator. - pub fn new(raw_bmp: &'a RawBmp<'a>) -> Self { + pub(crate) fn new(raw_bmp: &'a RawBmp<'a>) -> Self { let header = raw_bmp.header(); let width = header.image_size.width as usize; @@ -62,24 +62,25 @@ where } } -/// Iterator over dynamic color. Positions are dependent on the row order. -#[allow(missing_debug_implementations)] +/// Iterator over the raw colors in the image. +/// +/// See [`RawBmp::colors`](RawBmp::colors) for more information. pub enum DynamicRawColors<'a> { /// 1 bit per pixel Bpp1(RawColors<'a, RawU1>), - /// 4 bit per pixel + /// 4 bits per pixel Bpp4(RawColors<'a, RawU4>), - /// 8 bit per pixel + /// 8 bits per pixel Bpp8(RawColors<'a, RawU8>), - /// 16 bit per pixel + /// 16 bits per pixel Bpp16(RawColors<'a, RawU16>), - /// 24 bit per pixel + /// 24 bits per pixel Bpp24(RawColors<'a, RawU24>), - /// 32 bit per pixel + /// 32 bits per pixel Bpp32(RawColors<'a, RawU32>), - /// 4 bit per pixel RLE encoded + /// RLE encoded with 4 bits per pixel Bpp4Rle(Rle4Colors<'a>), - /// 8 bit per pixel RLE encoded + /// RLE encoded with 8 bits per pixel Bpp8Rle(Rle8Colors<'a>), } @@ -102,13 +103,14 @@ impl DynamicRawColors<'_> { /// Get the row order of the bitmap. pub fn row_order(&self) -> RowOrder { match self { - DynamicRawColors::Bpp1(_) - | DynamicRawColors::Bpp4(_) - | DynamicRawColors::Bpp8(_) - | DynamicRawColors::Bpp16(_) - | DynamicRawColors::Bpp24(_) - | DynamicRawColors::Bpp32(_) => RowOrder::TopDown, - DynamicRawColors::Bpp4Rle(_) | DynamicRawColors::Bpp8Rle(_) => RowOrder::BottomUp, + DynamicRawColors::Bpp1(colors) => colors.row_order, + DynamicRawColors::Bpp4(colors) => colors.row_order, + DynamicRawColors::Bpp8(colors) => colors.row_order, + DynamicRawColors::Bpp16(colors) => colors.row_order, + DynamicRawColors::Bpp24(colors) => colors.row_order, + DynamicRawColors::Bpp32(colors) => colors.row_order, + DynamicRawColors::Bpp4Rle(_) => RowOrder::BottomUp, + DynamicRawColors::Bpp8Rle(_) => RowOrder::BottomUp, } } } @@ -124,8 +126,8 @@ impl Iterator for DynamicRawColors<'_> { DynamicRawColors::Bpp16(colors) => colors.next().map(|r| u32::from(r.into_inner())), DynamicRawColors::Bpp24(colors) => colors.next().map(|r| r.into_inner()), DynamicRawColors::Bpp32(colors) => colors.next().map(|r| r.into_inner()), - DynamicRawColors::Bpp4Rle(state) => state.next().map(|r| u32::from(r.into_inner())), - DynamicRawColors::Bpp8Rle(state) => state.next().map(|r| u32::from(r.into_inner())), + DynamicRawColors::Bpp4Rle(colors) => colors.next().map(|r| u32::from(r.into_inner())), + DynamicRawColors::Bpp8Rle(colors) => colors.next().map(|r| u32::from(r.into_inner())), } } } @@ -152,66 +154,70 @@ enum RleState { } pub struct PixelPoints { - /// The location of the next pixel + /// The location of the next pixel. next_pixel: Point, - /// The width of a line in pixels + /// The number of pixels in a row. width: u32, -} - -/// Iterator over individual BMP RLE8 encoded pixels. -/// -/// Each pixel is returned as a `u32` regardless of the bit depth of the source image. -#[derive(Debug)] -pub struct Rle8Colors<'a> { - /// Our source data - data: &'a [u8], - /// Our state - rle_state: RleState, - start_of_line: bool, + /// Delta for row movement. + delta_y: i32, } impl PixelPoints { - fn next_up_dir(&mut self) -> Point { - let old_position = self.next_pixel; - self.next_pixel.x += 1; - if self.next_pixel.x == self.width as i32 { - self.next_pixel.x = 0; - self.next_pixel.y = self.next_pixel.y.saturating_sub(1); + pub(crate) fn new(image_size: Size, row_order: RowOrder) -> Self { + let next_pixel = match row_order { + RowOrder::TopDown => Point::new(0, 0), + RowOrder::BottomUp => Point::new(0, (image_size.height - 1) as i32), + }; + let delta_y = match row_order { + RowOrder::TopDown => 1, + RowOrder::BottomUp => -1, + }; + Self { + next_pixel, + width: image_size.width, + delta_y, } - old_position } +} - fn next_down_dir(&mut self) -> Point { +impl Iterator for PixelPoints { + type Item = Point; + fn next(&mut self) -> Option { let old_position = self.next_pixel; self.next_pixel.x += 1; if self.next_pixel.x == self.width as i32 { self.next_pixel.x = 0; - self.next_pixel.y += 1; + self.next_pixel.y += self.delta_y; } - old_position + Some(old_position) } } +/// Iterator over individual BMP RLE8 encoded pixels. +/// +/// Each pixel is returned as a `u32` regardless of the bit depth of the source image. +#[derive(Debug)] +pub struct Rle8Colors<'a> { + /// Our source data + data: &'a [u8], + /// Our state + rle_state: RleState, + start_of_row: bool, +} + impl<'a> Rle8Colors<'a> { /// Create a new RLE pixel iterator. - pub(crate) fn new(raw_bmp: &RawBmp<'a>) -> (Rle8Colors<'a>, PixelPoints) { - let header = raw_bmp.header(); - let points = PixelPoints { - width: header.image_size.width, - // RLE encoded bitmaps are upside down - next_pixel: Point::new(0, (header.image_size.height - 1) as i32), - }; - let this = Rle8Colors { + pub(crate) fn new(raw_bmp: &RawBmp<'a>) -> Rle8Colors<'a> { + Rle8Colors { data: raw_bmp.image_data(), rle_state: RleState::Starting, - start_of_line: false, - }; - (this, points) + start_of_row: false, + } } /// Indicate that a new line is starting. Required for correct RLE decoding. - pub fn start_a_line(&mut self) { - self.start_of_line = true; + pub fn start_row(&mut self) { + self.start_of_row = true; } } @@ -276,7 +282,7 @@ impl<'a> Iterator for Rle8Colors<'a> { // the pair, which can be one of the following values. match param { 0 => { - if !self.start_of_line { + if !self.start_of_row { return None; } } @@ -323,29 +329,22 @@ pub struct Rle4Colors<'a> { data: &'a [u8], /// Our state rle_state: RleState, - start_of_line: bool, + start_of_row: bool, } impl<'a> Rle4Colors<'a> { /// Create a new RLE pixel iterator. - pub(crate) fn new(raw_bmp: &RawBmp<'a>) -> (Rle4Colors<'a>, PixelPoints) { - let header = raw_bmp.header(); - let points = PixelPoints { - width: header.image_size.width, - // RLE encoded bitmaps are upside down - next_pixel: Point::new(0, (header.image_size.height - 1) as i32), - }; - let this = Rle4Colors { + pub(crate) fn new(raw_bmp: &RawBmp<'a>) -> Rle4Colors<'a> { + Rle4Colors { data: raw_bmp.image_data(), rle_state: RleState::Starting, - start_of_line: false, - }; - (this, points) + start_of_row: false, + } } /// Indicate that a new line is starting. Required for correct RLE decoding. - pub fn start_a_line(&mut self) { - self.start_of_line = true; + pub fn start_row(&mut self) { + self.start_of_row = true; } } @@ -449,7 +448,7 @@ impl<'a> Iterator for Rle4Colors<'a> { // the pair, which can be one of the following values. match param { 0 => { - if !self.start_of_line { + if !self.start_of_row { return None; } } @@ -502,24 +501,23 @@ impl<'a> RawPixels<'a> { let header = raw_bmp.header(); match header.compression_method { CompressionMethod::Rle4 => { - let (colors, points) = Rle4Colors::new(raw_bmp); + let colors = Rle4Colors::new(raw_bmp); + let points = PixelPoints::new(header.image_size, RowOrder::BottomUp); Self { colors: DynamicRawColors::Bpp4Rle(colors), points, } } CompressionMethod::Rle8 => { - let (colors, points) = Rle8Colors::new(raw_bmp); + let colors = Rle8Colors::new(raw_bmp); + let points = PixelPoints::new(header.image_size, RowOrder::BottomUp); Self { colors: DynamicRawColors::Bpp8Rle(colors), points, } } CompressionMethod::Rgb | CompressionMethod::Bitfields => { - let points = PixelPoints { - width: header.image_size.width, - next_pixel: Point::zero(), - }; + let points = PixelPoints::new(header.image_size, header.row_order); let colors = match header.bpp { Bpp::Bits1 => DynamicRawColors::Bpp1(RawColors::new(raw_bmp)), Bpp::Bits4 => DynamicRawColors::Bpp4(RawColors::new(raw_bmp)), @@ -539,11 +537,7 @@ impl Iterator for RawPixels<'_> { fn next(&mut self) -> Option { let color = self.colors.next()?; - let position = match self.colors.row_order() { - RowOrder::TopDown => self.points.next_down_dir(), - RowOrder::BottomUp => self.points.next_up_dir(), - }; - + let position = self.points.next()?; Some(RawPixel { position, color }) } } From efe1fc2a8077530520a0a7f9e80a749907934020 Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Mon, 10 Nov 2025 16:30:05 +0100 Subject: [PATCH 9/9] fix: incorrect test, image has bottom-up order but expectation is top-down order --- tests/coordinates.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/coordinates.rs b/tests/coordinates.rs index 6e16487..3bf0cc3 100644 --- a/tests/coordinates.rs +++ b/tests/coordinates.rs @@ -12,14 +12,15 @@ fn coordinates() { #[rustfmt::skip] let expected = vec![ - (0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0), (7, 0), // - (0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1), // - (0, 2), (1, 2), (2, 2), (3, 2), (4, 2), (5, 2), (6, 2), (7, 2), // - (0, 3), (1, 3), (2, 3), (3, 3), (4, 3), (5, 3), (6, 3), (7, 3), // - (0, 4), (1, 4), (2, 4), (3, 4), (4, 4), (5, 4), (6, 4), (7, 4), // - (0, 5), (1, 5), (2, 5), (3, 5), (4, 5), (5, 5), (6, 5), (7, 5), // + (0, 7), (1, 7), (2, 7), (3, 7), (4, 7), (5, 7), (6, 7), (7, 7), // (0, 6), (1, 6), (2, 6), (3, 6), (4, 6), (5, 6), (6, 6), (7, 6), // - (0, 7), (1, 7), (2, 7), (3, 7), (4, 7), (5, 7), (6, 7), (7, 7), ]; + (0, 5), (1, 5), (2, 5), (3, 5), (4, 5), (5, 5), (6, 5), (7, 5), // + (0, 4), (1, 4), (2, 4), (3, 4), (4, 4), (5, 4), (6, 4), (7, 4), // + (0, 3), (1, 3), (2, 3), (3, 3), (4, 3), (5, 3), (6, 3), (7, 3), // + (0, 2), (1, 2), (2, 2), (3, 2), (4, 2), (5, 2), (6, 2), (7, 2), // + (0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1), // + (0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0), (7, 0), // + ]; assert_eq!(pixels, expected); }