| // Copyright 2026 The PDFium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use image::codecs::bmp::BmpDecoder; |
| use image::{ImageDecoder, ImageFormat}; |
| use std::io::Cursor; |
| |
| #[cxx::bridge(namespace = "fxcodec::rust_bmp")] |
| mod ffi { |
| pub struct BmpHeaderInfo { |
| pub width: u32, |
| pub height: u32, |
| pub components: i32, |
| } |
| |
| #[derive(Debug, PartialEq)] |
| pub enum DecodeStatus { |
| Success, |
| Continue, |
| Error, |
| } |
| |
| extern "Rust" { |
| fn read_bmp_info(src: &[u8], info: &mut BmpHeaderInfo) -> DecodeStatus; |
| fn decode_bmp_to_buf(src: &[u8], out_buf: &mut [u8], out_pitch: usize) -> DecodeStatus; |
| } |
| } |
| |
| fn get_bmp_bpp(src: &[u8]) -> u16 { |
| if src.len() < 18 { |
| return 24; |
| } |
| let header_size = u32::from_le_bytes(src[14..18].try_into().unwrap()); |
| if header_size == 12 { |
| if src.len() >= 26 { |
| return u16::from_le_bytes(src[24..26].try_into().unwrap()); |
| } |
| } else if header_size >= 16 && src.len() >= 30 { |
| return u16::from_le_bytes(src[28..30].try_into().unwrap()); |
| } |
| 24 |
| } |
| |
| const MAX_UNCOMPRESSED_BYTES: u64 = 1024 * 1024 * 1024; // 1 GiB |
| |
| fn is_32bpp_or_has_alpha(src: &[u8], has_alpha: bool) -> bool { |
| has_alpha || get_bmp_bpp(src) == 32 |
| } |
| |
| fn read_bmp_info(src: &[u8], info: &mut ffi::BmpHeaderInfo) -> ffi::DecodeStatus { |
| let decoder = match BmpDecoder::new(Cursor::new(src)) { |
| Ok(d) => d, |
| Err(image::ImageError::IoError(io_err)) |
| if io_err.kind() == std::io::ErrorKind::UnexpectedEof => |
| { |
| return ffi::DecodeStatus::Continue; |
| } |
| Err(_) => return ffi::DecodeStatus::Error, |
| }; |
| let (width, height) = decoder.dimensions(); |
| if width == 0 || height == 0 { |
| return ffi::DecodeStatus::Error; |
| } |
| let components = |
| if is_32bpp_or_has_alpha(src, decoder.color_type().has_alpha()) { 4 } else { 3 }; |
| let uncompressed_size = (width as u64) |
| .checked_mul(height as u64) |
| .and_then(|pixels| pixels.checked_mul(components as u64)); |
| if uncompressed_size.is_none_or(|size| size > MAX_UNCOMPRESSED_BYTES) { |
| return ffi::DecodeStatus::Error; |
| } |
| info.width = width; |
| info.height = height; |
| info.components = components; |
| ffi::DecodeStatus::Success |
| } |
| |
| fn decode_bmp_to_buf(src: &[u8], dst: &mut [u8], dst_pitch: usize) -> ffi::DecodeStatus { |
| let img = match image::load_from_memory_with_format(src, ImageFormat::Bmp) { |
| Ok(i) => i, |
| Err(image::ImageError::IoError(io_err)) |
| if io_err.kind() == std::io::ErrorKind::UnexpectedEof => |
| { |
| return ffi::DecodeStatus::Continue; |
| } |
| Err(_) => return ffi::DecodeStatus::Error, |
| }; |
| let width = img.width() as usize; |
| let height = img.height() as usize; |
| if width == 0 || height == 0 { |
| return ffi::DecodeStatus::Error; |
| } |
| |
| if is_32bpp_or_has_alpha(src, img.color().has_alpha()) { |
| let rgba = img.to_rgba8(); |
| let raw_pixels = rgba.as_raw(); |
| let row_bytes = width * 4; |
| if raw_pixels.len() < height * row_bytes { |
| return ffi::DecodeStatus::Error; |
| } |
| |
| for y in 0..height { |
| let src_row = &raw_pixels[y * row_bytes..(y + 1) * row_bytes]; |
| let dst_start = y * dst_pitch; |
| if dst_start + row_bytes > dst.len() { |
| return ffi::DecodeStatus::Error; |
| } |
| let dst_slice = &mut dst[dst_start..dst_start + row_bytes]; |
| for x in 0..width { |
| let r = src_row[x * 4]; |
| let g = src_row[x * 4 + 1]; |
| let b = src_row[x * 4 + 2]; |
| let a = src_row[x * 4 + 3]; |
| dst_slice[x * 4] = b; |
| dst_slice[x * 4 + 1] = g; |
| dst_slice[x * 4 + 2] = r; |
| dst_slice[x * 4 + 3] = a; |
| } |
| } |
| } else { |
| let rgb = img.to_rgb8(); |
| let raw_pixels = rgb.as_raw(); |
| let row_bytes = width * 3; |
| if raw_pixels.len() < height * row_bytes { |
| return ffi::DecodeStatus::Error; |
| } |
| |
| for y in 0..height { |
| let src_row = &raw_pixels[y * row_bytes..(y + 1) * row_bytes]; |
| let dst_start = y * dst_pitch; |
| if dst_start + row_bytes > dst.len() { |
| return ffi::DecodeStatus::Error; |
| } |
| let dst_slice = &mut dst[dst_start..dst_start + row_bytes]; |
| for x in 0..width { |
| let r = src_row[x * 3]; |
| let g = src_row[x * 3 + 1]; |
| let b = src_row[x * 3 + 2]; |
| dst_slice[x * 3] = b; |
| dst_slice[x * 3 + 1] = g; |
| dst_slice[x * 3 + 2] = r; |
| } |
| } |
| } |
| ffi::DecodeStatus::Success |
| } |