From 68c9a56813896c110273d5784b2bb6f553d168de Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Mon, 27 Oct 2025 16:21:58 +0100 Subject: [PATCH 1/7] fix: incorrect padding behaviour --- src/raw_iter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/raw_iter.rs b/src/raw_iter.rs index de572b7..9b269bf 100644 --- a/src/raw_iter.rs +++ b/src/raw_iter.rs @@ -419,7 +419,7 @@ impl<'a> Iterator for Rle4Pixels<'a> { remaining: param.saturating_sub(1), is_odd: (param % 2) != 0, // padding if the number of *bytes* is odd - has_padding: ((param >> 1) % 2) != 0, + has_padding: (param & 0b11).count_ones() == 1, }; } } From ee87e092be7d26c75e4224140de42085f318ee96 Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Mon, 27 Oct 2025 16:25:44 +0100 Subject: [PATCH 2/7] chore: CHANGELOG.md entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab5c1e6..a99dc51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - **(breaking)** [#49](https://github.com/embedded-graphics/tinybmp/pull/41) Use 1.81 as the MSRV. - [#46](https://github.com/embedded-graphics/tinybmp/pull/46) `Bmp::from_slice` is now `const`, so BMPs can be put in `const`s and `static`s. - [#47](https://github.com/embedded-graphics/tinybmp/pull/47) Ignore compressed data length on RGB compression method. +- Fix: incorrect handling of paddings for absolute sequences ## [0.6.0] - 2024-06-11 From 69cc777ca48df6a77f0ca9dd3ec53ab4a26e87b9 Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Mon, 27 Oct 2025 16:48:12 +0100 Subject: [PATCH 3/7] chore: add test --- tests/ethernet_port.bmp | Bin 0 -> 378 bytes tests/ethernet_port.raw | Bin 0 -> 1200 bytes tests/ethernet_port.rs | 110 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 tests/ethernet_port.bmp create mode 100644 tests/ethernet_port.raw create mode 100644 tests/ethernet_port.rs diff --git a/tests/ethernet_port.bmp b/tests/ethernet_port.bmp new file mode 100644 index 0000000000000000000000000000000000000000..9371eb161854021c9e9c8baacc8f8ddf8585de6f GIT binary patch literal 378 zcma)2!41MN47@aMk$99o7=mXc4-kn@U#yW4c(4FNunJ4?=>mLsM#=!jX<7vB2TS(d z*^U$EcnSuioi!fR_mdkCD&;iQm7>dGGUKJUJl2seL8ZDUE9_DqL#k8vQt67~Or)F&k XjCF{gvh=USX74J3ui2iCs&4QBbmX*V literal 0 HcmV?d00001 diff --git a/tests/ethernet_port.rs b/tests/ethernet_port.rs new file mode 100644 index 0000000..7d9414c --- /dev/null +++ b/tests/ethernet_port.rs @@ -0,0 +1,110 @@ +use embedded_graphics::{ + image::{Image, ImageRaw}, + iterator::raw::RawDataSlice, + pixelcolor::{raw::LittleEndian, Bgr888, Rgb555, Rgb565, Rgb888}, + prelude::*, +}; +use tinybmp::Bmp; + +const WIDTH: usize = 20; +const HEIGHT: usize = 20; + +// TODO: use e-g framebuffer when it's added +#[derive(Debug, PartialEq)] +struct Framebuffer { + pixels: [[C; WIDTH]; HEIGHT], +} + +impl + std::fmt::Debug> Framebuffer { + pub fn new() -> Self { + let color = C::from(Rgb888::BLACK); + + Self { + pixels: [[color; WIDTH]; HEIGHT], + } + } + + pub fn from_image(image: impl ImageDrawable) -> Self { + let mut framebuffer = Framebuffer::::new(); + Image::new(&image, Point::zero()) + .draw(&mut framebuffer) + .unwrap(); + framebuffer + } + + pub fn pixels(&self) -> impl Iterator + '_ { + self.pixels.iter().flatten().copied() + } + + pub fn assert_eq(&self, expected: &Self) { + let zipped = || self.pixels().zip(expected.pixels()); + + let errors = zipped().filter(|(a, b)| a != b).count(); + let first_error = zipped() + .enumerate() + .find(|(_, (a, b))| a != b) + .map(|(i, (a, b))| (Point::new((i % WIDTH) as i32, (i / WIDTH) as i32), a, b)); + + //let first_error = self.pixels() + + if self != expected { + let first_error = first_error.unwrap(); + panic!( + "framebuffer differs from expected\n{} errors\nfirst error at ({}): {:?} (expected {:?})", + errors, + first_error.0, + first_error.1, + first_error.2, + ); + } + } +} + +impl DrawTarget for Framebuffer { + type Color = C; + type Error = std::convert::Infallible; + + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + for Pixel(p, c) in pixels { + self.pixels[p.y as usize][p.x as usize] = c; + } + + Ok(()) + } +} + +impl OriginDimensions for Framebuffer { + fn size(&self) -> embedded_graphics::prelude::Size { + Size::new(WIDTH as u32, HEIGHT as u32) + } +} + +fn draw_raw(data: &[u8]) -> Framebuffer +where + C: PixelColor + From + From + std::fmt::Debug, + for<'a> RawDataSlice<'a, C::Raw, LittleEndian>: IntoIterator, +{ + let raw = ImageRaw::::new(data, WIDTH as u32); + + Framebuffer::from_image(raw) +} + +fn draw_bmp(data: &[u8]) -> Framebuffer +where + C: PixelColor + From + From + From + std::fmt::Debug, +{ + let bmp = Bmp::::from_slice(data).unwrap(); + + Framebuffer::from_image(bmp) +} + +#[test] +fn ethernet_port() { + let raw = draw_raw::(include_bytes!("ethernet_port.raw")); + let bmp = draw_bmp::(include_bytes!("ethernet_port.bmp")); + + bmp.assert_eq(&raw); +} From db425f0bffb8e5c538fc42e136f8f3ec0f8862f9 Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Mon, 27 Oct 2025 17:07:29 +0100 Subject: [PATCH 4/7] chore: reformat changelog entry a bit --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a99dc51..04ef6a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,15 @@ ## [Unreleased] - ReleaseDate +### Fixed + +- Incorrect handling of paddings for absolute sequences + ### Changed - **(breaking)** [#49](https://github.com/embedded-graphics/tinybmp/pull/41) Use 1.81 as the MSRV. - [#46](https://github.com/embedded-graphics/tinybmp/pull/46) `Bmp::from_slice` is now `const`, so BMPs can be put in `const`s and `static`s. - [#47](https://github.com/embedded-graphics/tinybmp/pull/47) Ignore compressed data length on RGB compression method. -- Fix: incorrect handling of paddings for absolute sequences ## [0.6.0] - 2024-06-11 From acb6cecff49cc3e5af40fa06b6f38dc72d49409a Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Tue, 28 Oct 2025 11:48:11 +0100 Subject: [PATCH 5/7] Update CHANGELOG.md Co-authored-by: Ralf Fuest --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04ef6a6..1902f9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ ### Fixed -- Incorrect handling of paddings for absolute sequences +- [#50](https://github.com/embedded-graphics/tinybmp/pull/50) Fixed handling of padding bytes in absolute mode for RLE4 compressed files. ### Changed From 59e5198ef2a0f87d75ed0d847ea467be4cec8aee Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Tue, 28 Oct 2025 11:54:25 +0100 Subject: [PATCH 6/7] chore: improve readability of `has_padding` calculation for 4bit rle --- src/raw_iter.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/raw_iter.rs b/src/raw_iter.rs index 9b269bf..458e3a3 100644 --- a/src/raw_iter.rs +++ b/src/raw_iter.rs @@ -413,13 +413,14 @@ impl<'a> Iterator for Rle4Pixels<'a> { // Delta encoding is unsupported. return None; } - _ => { + num_pixels => { + let num_bytes = num_pixels.div_ceil(2); // Absolute mode self.rle_state = RleState::Absolute { remaining: param.saturating_sub(1), is_odd: (param % 2) != 0, // padding if the number of *bytes* is odd - has_padding: (param & 0b11).count_ones() == 1, + has_padding: num_bytes & 1 != 0, }; } } From e743cacf932b312c7d897c788102882cf409389e Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Tue, 28 Oct 2025 12:13:01 +0100 Subject: [PATCH 7/7] chore: tidy up tests --- tests/embedded_graphics.rs | 22 +++- tests/ethernet_port.rs | 110 ------------------ ...hernet_port.bmp => pr_50_rle4_padding.bmp} | Bin ...hernet_port.raw => pr_50_rle4_padding.raw} | Bin 4 files changed, 20 insertions(+), 112 deletions(-) delete mode 100644 tests/ethernet_port.rs rename tests/{ethernet_port.bmp => pr_50_rle4_padding.bmp} (100%) rename tests/{ethernet_port.raw => pr_50_rle4_padding.raw} (100%) diff --git a/tests/embedded_graphics.rs b/tests/embedded_graphics.rs index 11e94c9..21d7b0a 100644 --- a/tests/embedded_graphics.rs +++ b/tests/embedded_graphics.rs @@ -1,7 +1,7 @@ use embedded_graphics::{ - image::Image, + image::{Image, ImageRawLE}, mock_display::{ColorMapping, MockDisplay}, - pixelcolor::{Gray8, Rgb555, Rgb565, Rgb888}, + pixelcolor::{Bgr888, Gray8, Rgb555, Rgb565, Rgb888}, prelude::*, primitives::Rectangle, }; @@ -149,3 +149,21 @@ fn issue_8_height_is_negative() { bottom_up_display.assert_pattern(&["WK", "KK"]); top_down_display.assert_eq(&bottom_up_display); } + +/// Test for PR #50 +#[test] +fn pr_50_rle4_padding() { + let bmp = Bmp::::from_slice(include_bytes!("pr_50_rle4_padding.bmp")).unwrap(); + let raw = ImageRawLE::::new(include_bytes!("pr_50_rle4_padding.raw"), 20); + + assert_eq!(raw.size(), Size::new(20, 20)); + assert_eq!(bmp.size(), Size::new(20, 20)); + + let mut display_bmp = MockDisplay::new(); + let mut display_raw = MockDisplay::new(); + + bmp.draw(&mut display_bmp).unwrap(); + raw.draw(&mut display_raw).unwrap(); + + display_bmp.assert_eq(&display_raw); +} diff --git a/tests/ethernet_port.rs b/tests/ethernet_port.rs deleted file mode 100644 index 7d9414c..0000000 --- a/tests/ethernet_port.rs +++ /dev/null @@ -1,110 +0,0 @@ -use embedded_graphics::{ - image::{Image, ImageRaw}, - iterator::raw::RawDataSlice, - pixelcolor::{raw::LittleEndian, Bgr888, Rgb555, Rgb565, Rgb888}, - prelude::*, -}; -use tinybmp::Bmp; - -const WIDTH: usize = 20; -const HEIGHT: usize = 20; - -// TODO: use e-g framebuffer when it's added -#[derive(Debug, PartialEq)] -struct Framebuffer { - pixels: [[C; WIDTH]; HEIGHT], -} - -impl + std::fmt::Debug> Framebuffer { - pub fn new() -> Self { - let color = C::from(Rgb888::BLACK); - - Self { - pixels: [[color; WIDTH]; HEIGHT], - } - } - - pub fn from_image(image: impl ImageDrawable) -> Self { - let mut framebuffer = Framebuffer::::new(); - Image::new(&image, Point::zero()) - .draw(&mut framebuffer) - .unwrap(); - framebuffer - } - - pub fn pixels(&self) -> impl Iterator + '_ { - self.pixels.iter().flatten().copied() - } - - pub fn assert_eq(&self, expected: &Self) { - let zipped = || self.pixels().zip(expected.pixels()); - - let errors = zipped().filter(|(a, b)| a != b).count(); - let first_error = zipped() - .enumerate() - .find(|(_, (a, b))| a != b) - .map(|(i, (a, b))| (Point::new((i % WIDTH) as i32, (i / WIDTH) as i32), a, b)); - - //let first_error = self.pixels() - - if self != expected { - let first_error = first_error.unwrap(); - panic!( - "framebuffer differs from expected\n{} errors\nfirst error at ({}): {:?} (expected {:?})", - errors, - first_error.0, - first_error.1, - first_error.2, - ); - } - } -} - -impl DrawTarget for Framebuffer { - type Color = C; - type Error = std::convert::Infallible; - - fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> - where - I: IntoIterator>, - { - for Pixel(p, c) in pixels { - self.pixels[p.y as usize][p.x as usize] = c; - } - - Ok(()) - } -} - -impl OriginDimensions for Framebuffer { - fn size(&self) -> embedded_graphics::prelude::Size { - Size::new(WIDTH as u32, HEIGHT as u32) - } -} - -fn draw_raw(data: &[u8]) -> Framebuffer -where - C: PixelColor + From + From + std::fmt::Debug, - for<'a> RawDataSlice<'a, C::Raw, LittleEndian>: IntoIterator, -{ - let raw = ImageRaw::::new(data, WIDTH as u32); - - Framebuffer::from_image(raw) -} - -fn draw_bmp(data: &[u8]) -> Framebuffer -where - C: PixelColor + From + From + From + std::fmt::Debug, -{ - let bmp = Bmp::::from_slice(data).unwrap(); - - Framebuffer::from_image(bmp) -} - -#[test] -fn ethernet_port() { - let raw = draw_raw::(include_bytes!("ethernet_port.raw")); - let bmp = draw_bmp::(include_bytes!("ethernet_port.bmp")); - - bmp.assert_eq(&raw); -} diff --git a/tests/ethernet_port.bmp b/tests/pr_50_rle4_padding.bmp similarity index 100% rename from tests/ethernet_port.bmp rename to tests/pr_50_rle4_padding.bmp diff --git a/tests/ethernet_port.raw b/tests/pr_50_rle4_padding.raw similarity index 100% rename from tests/ethernet_port.raw rename to tests/pr_50_rle4_padding.raw