1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
//! A small BMP parser designed for embedded, no-std environments but usable anywhere. Beyond
//! parsing the image header, no other allocations are made.
//!
//! To use `tinybmp` without [`embedded-graphics`] the raw data for individual pixels in an image
//! can be accessed using the methods provided by the [`RawBmp`] struct.
//!
//! # Examples
//!
//! ## Using `Bmp` to draw a BMP image
//!
//! If the color format inside the BMP file is known at compile time the [`Bmp`] type can be used
//! to draw an image to an [`embedded-graphics`] draw target. The BMP file used in this example
//! uses 16 bits per pixel with a RGB565 format.
//!
//! ```rust
//! # fn main() -> Result<(), core::convert::Infallible> {
//! use embedded_graphics::{image::Image, prelude::*};
//! use tinybmp::Bmp;
//! # use embedded_graphics::mock_display::MockDisplay;
//! # use embedded_graphics::pixelcolor::Rgb565;
//! # let mut display: MockDisplay<Rgb565> = MockDisplay::default();
//!
//! let bmp_data = include_bytes!("../tests/chessboard-8px-color-16bit.bmp");
//!
//! // Load 16 BPP 8x8px image.
//! // Note: The color type is specified explicitly to match the format used by the BMP image.
//! let bmp = Bmp::<Rgb565>::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)?;
//! # Ok::<(), core::convert::Infallible>(()) }
//! ```
//!
//! ## Using `DynamicBmp` to draw a BMP image
//!
//! If the exact color format used in the BMP file isn't known at compile time, for example to read
//! user supplied images, the [`DynamicBmp`] can be used. Because automatic color conversion will
//! be used the drawing performance might be degraded in comparison to [`Bmp`].
//!
//! ```rust
//! # fn main() -> Result<(), core::convert::Infallible> {
//! use embedded_graphics::{image::Image, prelude::*};
//! use tinybmp::DynamicBmp;
//! # use embedded_graphics::mock_display::MockDisplay;
//! # use embedded_graphics::pixelcolor::Rgb565;
//! # let mut display: MockDisplay<Rgb565> = MockDisplay::default();
//!
//! let bmp_data = include_bytes!("../tests/chessboard-8px-color-16bit.bmp");
//!
//! // Load BMP image with unknown color format.
//! // Note: There is no need to explicitly specify the color type.
//! let bmp = DynamicBmp::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)?;
//! # Ok::<(), core::convert::Infallible>(()) }
//! ```
//!
//! ## Accessing the raw image data
//!
//! The [`RawBmp`] struct provides methods to access lower level information about a BMP file,
//! like the BMP header or the raw image data. An instance of this type can be created by using
//! [`from_slice`] or by accessing the underlying raw object of a [`Bmp`] or [`DynamicBmp`] object
//! by using [`as_raw`].
//!
//! ```rust
//! use embedded_graphics::prelude::*;
//! use tinybmp::{RawBmp, Bpp, Header, RawPixel, RowOrder};
//!
//! 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,
//! }
//! );
//!
//! // Check that raw image data slice is the correct length (according to parsed header)
//! assert_eq!(bmp.image_data().len(), bmp.header().image_data_len as usize);
//!
//! // 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);
//! ```
//!
//! [`embedded-graphics`]: https://crates.io/crates/embedded-graphics
//! [`Header`]: ./header/struct.Header.html
//! [`Bmp`]: ./struct.Bmp.html
//! [`as_raw`]: ./struct.Bmp.html#method.as_raw
//! [`DynamicBmp`]: ./struct.DynamicBmp.html
//! [`RawBmp`]: ./struct.RawBmp.html
//! [`from_slice`]: ./struct.RawBmp.html#method.from_slice
//! [`pixels`]: ./struct.RawBmp.html#method.pixels
//! [`image_data`]: ./struct.RawBmp.html#method.image_data
#![no_std]
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
mod color_table;
mod dynamic_bmp;
mod header;
mod pixels;
mod raw_bmp;
mod raw_pixels;
use core::marker::PhantomData;
use embedded_graphics::{prelude::*, primitives::Rectangle};
pub use crate::{
dynamic_bmp::DynamicBmp,
header::{Bpp, ChannelMasks, Header, RowOrder},
pixels::Pixels,
raw_bmp::RawBmp,
raw_pixels::{RawPixel, RawPixels},
};
/// A BMP-format bitmap
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct Bmp<'a, C> {
raw_bmp: RawBmp<'a>,
color_type: PhantomData<C>,
}
impl<'a, C> Bmp<'a, C>
where
C: PixelColor,
{
/// Creates a bitmap object from a byte slice.
///
/// The created object keeps a shared reference to the input and does not dynamically allocate
/// memory.
///
/// The color type must be explicitly specified when this method is called, for example by
/// using the turbofish syntax. An error is returned if the bit depth of the specified color
/// type doesn't match the bit depth of the BMP file.
pub fn from_slice(bytes: &'a [u8]) -> Result<Self, ParseError> {
let raw_bmp = RawBmp::from_slice(bytes)?;
if C::Raw::BITS_PER_PIXEL != usize::from(raw_bmp.color_bpp().bits()) {
if raw_bmp.color_bpp() == Bpp::Bits32 && C::Raw::BITS_PER_PIXEL == 24 {
// Allow 24BPP color types for 32BPP images to support RGB888 BMP files with
// 4 bytes per pixel.
// This check could be improved by using the bit masks available in BMP headers
// with version >= 4, but we don't currently parse this information.
} else if (raw_bmp.color_bpp() == Bpp::Bits1 || raw_bmp.color_bpp() == Bpp::Bits8)
&& raw_bmp.color_table().is_some()
{
// Allow 1BPP and 8BPP images with color tables to be mapped to other color types.
} else {
return Err(ParseError::MismatchedBpp(raw_bmp.color_bpp().bits()));
}
}
Ok(Self {
raw_bmp,
color_type: PhantomData,
})
}
/// Returns an iterator over the pixels in this image.
pub fn pixels<'b>(&'b self) -> Pixels<'b, 'a, C> {
Pixels::new(self.raw_bmp.pixels())
}
/// Returns a reference to the raw BMP image.
///
/// The [`RawBmp`] instance can be used to access lower level information about the BMP file.
///
/// [`RawBmp`]: struct.RawBmp.html
pub fn as_raw(&self) -> &RawBmp<'a> {
&self.raw_bmp
}
}
impl<C> ImageDrawable for Bmp<'_, C>
where
C: PixelColor + From<<C as PixelColor>::Raw>,
{
type Color = C;
fn draw<D>(&self, target: &mut D) -> Result<(), D::Error>
where
D: DrawTarget<Color = C>,
{
self.as_raw().draw(target)
}
fn draw_sub_image<D>(&self, target: &mut D, area: &Rectangle) -> Result<(), D::Error>
where
D: DrawTarget<Color = Self::Color>,
{
self.draw(&mut target.translated(-area.top_left).clipped(area))
}
}
impl<C> OriginDimensions for Bmp<'_, C>
where
C: PixelColor,
{
fn size(&self) -> Size {
self.raw_bmp.size()
}
}
/// Parse error.
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum ParseError {
/// An error occurred while parsing the header.
Header,
/// The image uses a bit depth that isn't supported by tinybmp.
UnsupportedBpp(u16),
/// The image bit depth doesn't match the specified color type.
MismatchedBpp(u16),
/// The image format isn't supported by `DynamicBmp`.
UnsupportedDynamicBmpFormat,
}