Giter VIP home page Giter VIP logo

tinybmp's Introduction

TinyBMP

Build Status Crates.io Docs.rs embedded-graphics on Matrix

A small BMP parser primarily for embedded, no-std environments but usable anywhere.

This crate is primarily targeted at drawing BMP images to embedded_graphics DrawTargets, but can also be used to parse BMP files for other applications.

Examples

Draw a BMP image to an embedded-graphics draw target

The Bmp struct is used together with embedded_graphics' Image struct to display BMP files on any draw target.

use embedded_graphics::{image::Image, prelude::*};
use tinybmp::Bmp;

// Include the BMP file data.
let bmp_data = include_bytes!("../tests/chessboard-8px-color-16bit.bmp");

// Parse the BMP file.
let bmp = Bmp::from_slice(bmp_data).unwrap();

// Draw the image with the top left corner at (10, 20) by wrapping it in
// an embedded-graphics `Image`.
Image::new(&bmp, Point::new(10, 20)).draw(&mut display)?;

Using the pixel iterator

To access the image data for other applications the Bmp::pixels method returns an iterator over all pixels in the BMP file. The colors inside the BMP file will automatically converted to one of the color types in embedded_graphics.

use embedded_graphics::{pixelcolor::Rgb888, prelude::*};
use tinybmp::Bmp;

// Include the BMP file data.
let bmp_data = include_bytes!("../tests/chessboard-8px-24bit.bmp");

// Parse the BMP file.
// Note that it is necessary to explicitly specify the color type which the colors in the BMP
// file will be converted into.
let bmp = Bmp::<Rgb888>::from_slice(bmp_data).unwrap();

for Pixel(position, color) in bmp.pixels() {
    println!("R: {}, G: {}, B: {} @ ({})", color.r(), color.g(), color.b(), position);
}

Accessing individual pixels

Bmp::pixel can be used to get the color of individual pixels. The returned color will be automatically converted to one of the color types in embedded_graphics.

use embedded_graphics::{pixelcolor::Rgb888, image::GetPixel, prelude::*};
use tinybmp::Bmp;

// Include the BMP file data.
let bmp_data = include_bytes!("../tests/chessboard-8px-24bit.bmp");

// Parse the BMP file.
// Note that it is necessary to explicitly specify the color type which the colors in the BMP
// file will be converted into.
let bmp = Bmp::<Rgb888>::from_slice(bmp_data).unwrap();

let pixel = bmp.pixel(Point::new(3, 2));

assert_eq!(pixel, Some(Rgb888::WHITE));

Note that you currently cannot access invidual pixels when working with RLE4 or RLE8 compressed indexed bitmaps. With these formats the pixel() function will always return None.

Accessing the raw image data

For most applications the higher level access provided by Bmp is sufficient. But in case lower level access is necessary the RawBmp struct can be used to access BMP header information and the color table. A RawBmp object can be created directly from image data by using from_slice or by accessing the underlying raw object of a Bmp object with Bmp::as_raw.

Similar to Bmp::pixel, RawBmp::pixel can be used to get raw pixel color values as a u32.

use embedded_graphics::prelude::*;
use tinybmp::{RawBmp, Bpp, Header, RawPixel, RowOrder, CompressionMethod};

let bmp = RawBmp::from_slice(include_bytes!("../tests/chessboard-8px-24bit.bmp"))
    .expect("Failed to parse BMP image");

// Read the BMP header
assert_eq!(
    bmp.header(),
    &Header {
        file_size: 314,
        image_data_start: 122,
        bpp: Bpp::Bits24,
        image_size: Size::new(8, 8),
        image_data_len: 192,
        channel_masks: None,
        row_order: RowOrder::BottomUp,
        compression_method: CompressionMethod::Rgb,
    }
);

// Get an iterator over the pixel coordinates and values in this image and load into a vec
let pixels: Vec<RawPixel> = bmp.pixels().collect();

// Loaded example image is 8x8px
assert_eq!(pixels.len(), 8 * 8);

// Individual raw pixel values can also be read
let pixel = bmp.pixel(Point::new(3, 2));

// The raw value for a white pixel in the source image
assert_eq!(pixel, Some(0xFFFFFFu32));

Minimum supported Rust version

The minimum supported Rust version for tinybmp is 1.71 or greater. Ensure you have the correct version of Rust installed, preferably through https://rustup.rs.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

tinybmp's People

Contributors

hpux735 avatar jamwaffles avatar rfuest avatar thejpster avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

tinybmp's Issues

Implement the ImagePixelGetter trait

PR #34 did add a pixel getter method to Bmp and RawBmp, but didn't yet implement the e-g ImagePixelGetter trait. The Bmp::pixel method should be moved to a ImagePixelGetter trait impl when a new e-g version with this trait is released.

Crash / Out of Bounds read in RawBmp

  • Version of tinybmp in use (if applicable): tinybmp 0.3.3

Crash / Out Of Bounds Read in image_data when a malformed BMP is provided

└─$ ./target/release/tinypoc 
thread 'main' panicked at 'range start index 536871106 out of range for slice of length 54', library/core/src/slice/index.rs:52:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

And the PoC in Rust:

use embedded_graphics::prelude::*;
use tinybmp::{RawBmp, Bpp, Header, RawPixel, RowOrder};

fn main() {
    let bmp = RawBmp::from_slice(include_bytes!("./crasher.bmp"))
    .expect("Failed to parse BMP image");
}

I've discovered this issue while fuzzing the RawBmp functions - the issue here is that there is no user validation/sanitasation within the header values and as such when a malformed BMP such as the following is provided

> hexdump -C /Users/symeonp/Desktop/fuzz_target_1/crasher.bmp
00000000  42 4d 00 00 c2 c2 c2 c2  c2 c2 c2 00 00 20 28 00  |BM..�������.. (.|
00000010  00 00 20 00 00 00 00 20  28 00 00 00 20 00 00 00  |.. .... (... ...|
00000020  00 00 00 00 08 00 03 00  00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 cb 08 08                                 |...�..|
00000036

after parsing methods the following can be observed:

●    35      /// [`pixels`]: #method.pixels
     36      pub fn from_slice(bytes: &'a [u8]) -> Result<Self, ParseError> {
     37          let (_remaining, (header, color_table)) =
●    38              Header::parse(bytes).map_err(|_| ParseError::Header)?;
     39  
●→   40          let image_data = &bytes[header.image_data_start..];
     41  
     42          Ok(Self {
     43              header,
     44              color_table,
     45              image_data,
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "tinypoc", stopped 0x55555555cde4 in tinybmp::raw_bmp::RawBmp::from_slice (), reason: BREAKPOINT
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x55555555cde4 → tinybmp::raw_bmp::RawBmp::from_slice(bytes=&[u8] {
  data_ptr: 0x55555559a000,
  length: 0x36
})
[#1] 0x55555555cc2e → tinypoc::main()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  p bytes
$11 = &[u8] {
  data_ptr: 0x55555559a000,
  length: 0x36
}
gef➤  p header
$12 = tinybmp::header::Header {
  file_size: 0xc2c20000,
  image_data_start: 0x200000c2,
  image_size: embedded_graphics_core::geometry::size::Size {
    width: 0x20,
    height: 0x282000
  },
  bpp: tinybmp::header::Bpp::Bits32,
  image_data_len: 0x80000,
  channel_masks: core::option::Option<tinybmp::header::ChannelMasks>::None,
  row_order: tinybmp::header::RowOrder::BottomUp
}
gef➤  p/d bytes
$13 = &[u8] {
  data_ptr: 93824992518144,
  length: 54
}
gef➤  p/d header.image_data_start
$14 = 536871106
gef➤  c

So image_data_start now holds the user controlled value 0x200000c2 / 536871106 when the total bytes is 54, thus assigning image_data leads to an out of bounds read where sure enough Rust does protect accessing it:

gef➤  c
Continuing.
thread 'main' panicked at 'range start index 536871106 out of range for slice of length 54', library/core/src/slice/index.rs:52:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

This might lead to a Denial-Of-Service condition though - let me know your thoughts.
Thanks

Test case

crasher.bmp.zip(See attached)

Branch rename

Can we change the default branch on this repo to be called main instead of the term currently used?

CI is broken

The 1.61 pinned build pulls in a crate that requires 1.63. This broke #41.

Perhaps the pinned CI build should use a lock file?

`uefi-rs` integration?

  • Version of tinybmp in use (if applicable): 0.4.0

Description of the problem/feature request/other

Hi there, I am using tinybmp to render a bootsplash in a bootloader in a UEFI environment, I wonder if you are interested in a PR to add support for blt-ting on a EFI Graphics Output Protocol, the uefi-rs provides a "nice" high level API: https://docs.rs/uefi/latest/uefi/proto/console/gop/struct.GraphicsOutput.html which could be used to integrate a proper DrawTarget I imagine.

(also, the doc link to DrawTarget seems to be dead.)

Test case (if applicable)

fn graphics_splash(boot_services: &BootServices, contents: &[u8]) -> uefi::Result {
    let mut gop = boot_services.open_protocol_exclusive::<GraphicsOutput>(
        boot_services.get_handle_for_protocol::<GraphicsOutput>()?
    )?;
    let current_mode = gop.current_mode_info();

    let splash_bmp = tinybmp::RawBmp::from_slice(contents)
        .map_err(|_err| uefi::Status::LOAD_ERROR)?;

    let splash_header = splash_bmp.header();

    ensure_supported_bmp(&splash_bmp)?;

    // Center it if possible.
    let cur_resolution = current_mode.resolution();
    let splash_size: (usize, usize) = (splash_header.image_size.width.try_into().unwrap(), splash_header.image_size.height.try_into().unwrap());
    // FIXME: properly error handle usize < u32... :)
    let x_pos = (cur_resolution.0 - core::cmp::min(splash_size.0, cur_resolution.0)) / 2;
    let y_pos = (cur_resolution.1 - core::cmp::min(splash_size.1, cur_resolution.1)) / 2;

    let background = BltOp::VideoFill {
            color: BltPixel::new(0x0, 0x0, 0x0),
            dest: (0, 0),
            dims: current_mode.resolution()
    };

    // Blit the background
    gop.blt(background)?;

    // Read the current contents to do alpha blending
    let mut current_contents = vec![BltPixel::new(0, 0, 0); splash_size.0 * splash_size.1].into_boxed_slice();

    gop.blt(BltOp::VideoToBltBuffer {
        buffer: &mut (*current_contents),
        src: (x_pos, y_pos),
        dest: BltRegion::Full,
        dims: splash_size
    });

    // Blit the BMP

    Ok(())
}

Here's some code to give you an idea, ideally, I'd like to replace the dance of doing alpha blending myself and blt'ing the final BMP if possible by just replacing it by some Draw or something, what do you think?

Add MSRV

We never had an MSRV for tinybmp IIRC, but it might be worth adding a changelog entry to this PR to state the MSRV as the one which introduced min-const-generics (or 1.57 in line with e-g).

Originally posted by @jamwaffles in #23 (review)

tinybmp does not support negative height

  • Version of tinybmp in use (if applicable): 0.2.3

Description of the problem/feature request/other

Apparently BMP files can have a negative height if the image data is stored top-down, rather than bottom up (as is typical). https://issues.apache.org/jira/browse/IMAGING-162 The Tinybmp library interprets this as a u32, and predictibly gets upset when the expected large amount of data isn't there.

Test case (if applicable)

fn main() -> Result<()> { 
    color_eyre::install()?;

    // #[cfg(feature = "simulator")]
    let mut display: SimulatorDisplay<Rgb888> = SimulatorDisplay::new(Size::new(240, 135));

    let image = Bmp::from_slice(include_bytes!("../resources/rust_pride.bmp")).unwrap();
    let image_raw: ImageRaw<Rgb888> = ImageRaw::new(image.image_data(), image.width(), image.height());
    let object = Image::new(&image_raw, Point::zero());
    object.draw(&mut display);

    let output_settings = OutputSettingsBuilder::new()
        .build();

    Window::new("Hello World", &output_settings).show_static(&display);

    Ok(())
}

rust_pride.bmp.zip

RFC: Loading images from SPI flash

RFC: Loading images from SPI flash

I'm using tinybmp on a STM32 micro with limited RAM. In my use case, I cannot afford to load an image into RAM and then copy it over onto a second RAM buffer, when draw() is invoked.

What would work (methinks) is if I could just read the flash memory from inside the raw pixels iterator, that is, replacing this get() call with a spi_memory::Read::read() call instead. I believe this would cut the RAM requirements in half, which might be of interest to other users of tinybmp.

At first I attempted to create a wrapper struct around spi_memory::Flash that would present a non-mutable slice interface to tinybmp (SliceIndex). The beauty of that is that I would not have to modify tinybmp nor add a new dependency (spi-memory) to it. But I failed: I could not find a way to invoke the flash read function from within the index() implementation. index() is passed a non-mutable self, whereas spi_memory::read() takes a mutable self.

So I'll try and implement this by forking the project. Before implementing this, I wanted to ask if this functionality is something you might be interesting in merging back into the project. If so, please let me know how would you like the API to be modified for it. Currently I don't have a clean interface change in mind: I'll just try to make it work by any means necessary, and then worry about whether this is something worth sharing.

Thanks!

Improve crate error handling

From #19 (comment)

Errors returned during parsing are quite vague and not very useful to the end user. This should be improved so it's easier to give better feedback and debug image parsing.

Unable to get the compression info

My device does not support compressed bmp images and I want to check whether the images are compressed or not. The compression info is not stored in the headers struct

pub fn parse_header(input: &[u8]) -> IResult<&[u8], Header> {
   ...
    let (input, bpp) = le_u16(input)?;
    let (input, _compression_method) = le_u32(input)?;  // The issue
    let (input, image_data_len) = le_u32(input)?;  
    ...

How can I get the compression info, and why the _compression_method is not stored in the struct?

Accessing individual pixels without copying the buffer

Is there a way to directly access random pixel data with a non-iterative random access pattern without copying into a Vec?

I want to use the bitmap as a kind of simple data structure.
For that I need random access to pixels. The iterator approach doesn't work and collect()ing the iterator wastes memory.

I'm thinking of an API such as:

impl RawBmp {
    fn get_pixel(&self, x: u32; y: u32) -> Pixel {
        ...
    }
}

Thanks a lot for your help.

Color palette not parsed correctly for 1BPP images

  • Version of tinybmp in use (if applicable): 0.3.1

Description of the problem/feature request/other

tinybmp does not display correctly negative images of depth 1. (I did not test other depths).
If you take an 1bpp image and negate it (for instance, with ImageMagick -negate command), the new image will appear identical to the original when displayed by tinybmp. The problems seems to be in the parsing of the color table, as negating an image just "flips" the colors in the table, leaving the individual pixels unchanged.

Test case

  1. Create two test images with ImageMagick
convert -size 200x200 canvas:black -type bilevel  images/black.bmp
convert -size 200x200 canvas:white -type bilevel  images/white.bmp
  1. Modify the tinybmp example to display these images. Below are the changes to show white.bmp
diff --git a/eg-0.7/examples/image-bmp.rs b/eg-0.7/examples/image-bmp.rs
index bef1103..f65941d 100644
--- a/eg-0.7/examples/image-bmp.rs
+++ b/eg-0.7/examples/image-bmp.rs
@@ -8,17 +8,17 @@
 //! The `graphics` feature of `tinybmp` needs to be enabled in `Cargo.toml` to use the `Bmp` object
 //! with embedded-graphics.
 
-use embedded_graphics::{image::Image, pixelcolor::Rgb565, prelude::*};
+use embedded_graphics::{image::Image, pixelcolor::BinaryColor, prelude::*};
 use embedded_graphics_simulator::{OutputSettingsBuilder, SimulatorDisplay, Window};
 use tinybmp::Bmp;
 
 fn main() -> Result<(), core::convert::Infallible> {
-    let mut display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new(128, 128));
+    let mut display: SimulatorDisplay<BinaryColor> = SimulatorDisplay::new(Size::new(128, 128));
 
     // Load the BMP image.
     // The color type must be specified explicitly to match the color format used by the image,
     // otherwise the compiler may infer an incorrect type.
-    let bmp: Bmp<Rgb565> = Bmp::from_slice(include_bytes!("./assets/rust-pride.bmp")).unwrap();
+    let bmp: Bmp<BinaryColor> = Bmp::from_slice(include_bytes!("./assets/white.bmp")).unwrap();
 
     // To draw the `bmp` object to the display it needs to be wrapped in an `Image` object to set
     // the position at which it should drawn. Here, the top left corner of the image is set to
  1. Observe a black image instead of the expected white.
    image
  2. Compare the images and observe that they only differ in the color table (offset 0x7a in header)
    image

Support RLE (Run Length Encoding)

  • Version of tinybmp in use (if applicable): 0.3.1 and master

Description of the problem/feature request/other

RawBmp::from_slice fails on this file.

Test case (if applicable)

    let mut bmp_file = File::open("david.bmp").unwrap();
    let mut bytes = vec![];
    bmp_file.read_to_end(&mut bytes).unwrap();
    let bmp = RawBmp::from_slice(&bytes).unwrap();

File gzipped because github doesn't allow BMP uploads: david.bmp.gz

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.