Skip to content

Commit 5117fef

Browse files
authored
Merge pull request #2675 from telecos/BmpIccProfileSupport
Implement BMP icc profile support
2 parents 1a3c255 + 35ae807 commit 5117fef

File tree

3 files changed

+71
-0
lines changed

3 files changed

+71
-0
lines changed

src/codecs/bmp/decoder.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ const RLE_ESCAPE_DELTA: u8 = 2;
6464
/// The maximum width/height the decoder will process.
6565
const MAX_WIDTH_HEIGHT: i32 = 0xFFFF;
6666

67+
/// The value of the V5 header field indicating an embedded ICC profile ("MBED").
68+
const PROFILE_EMBEDDED: u32 = 0x4D424544;
69+
6770
#[derive(PartialEq, Copy, Clone)]
6871
enum ImageType {
6972
Palette,
@@ -490,6 +493,7 @@ pub struct BmpDecoder<R> {
490493
colors_used: u32,
491494
palette: Option<Vec<[u8; 3]>>,
492495
bitfields: Option<Bitfields>,
496+
icc_profile: Option<Vec<u8>>,
493497
}
494498

495499
enum RLEInsn {
@@ -521,6 +525,7 @@ impl<R: BufRead + Seek> BmpDecoder<R> {
521525
colors_used: 0,
522526
palette: None,
523527
bitfields: None,
528+
icc_profile: None,
524529
}
525530
}
526531

@@ -778,6 +783,38 @@ impl<R: BufRead + Seek> BmpDecoder<R> {
778783
Ok(())
779784
}
780785

786+
/// Read ICC profile data from BITMAPV5HEADER if present.
787+
fn read_icc_profile(&mut self, bmp_header_offset: u64) -> ImageResult<()> {
788+
// Seek to bV5CSType field (56 bytes from header start)
789+
self.reader.seek(SeekFrom::Start(bmp_header_offset + 56))?;
790+
let cs_type = self.reader.read_u32::<LittleEndian>()?;
791+
792+
// Only embedded profiles are supported (not linked profiles)
793+
if cs_type != PROFILE_EMBEDDED {
794+
return Ok(());
795+
}
796+
797+
// Seek to bV5ProfileData at offset 112
798+
self.reader.seek(SeekFrom::Start(bmp_header_offset + 112))?;
799+
800+
let profile_offset = self.reader.read_u32::<LittleEndian>()?;
801+
let profile_size = self.reader.read_u32::<LittleEndian>()?;
802+
803+
if profile_size == 0 || profile_offset == 0 {
804+
return Ok(());
805+
}
806+
807+
// Profile offset is from the beginning of the file
808+
self.reader
809+
.seek(SeekFrom::Start(u64::from(profile_offset)))?;
810+
let mut profile_data = vec![0u8; profile_size as usize];
811+
self.reader.read_exact(&mut profile_data)?;
812+
813+
self.icc_profile = Some(profile_data);
814+
815+
Ok(())
816+
}
817+
781818
fn read_metadata(&mut self) -> ImageResult<()> {
782819
if !self.has_loaded_metadata {
783820
self.read_file_header()?;
@@ -842,6 +879,11 @@ impl<R: BufRead + Seek> BmpDecoder<R> {
842879
}
843880
};
844881

882+
// Read ICC profile if present (V5 header or later)
883+
if bmp_header_size >= BITMAPV5HEADER_SIZE {
884+
self.read_icc_profile(bmp_header_offset)?;
885+
}
886+
845887
self.reader
846888
.seek(SeekFrom::Start(bmp_header_end + bitmask_bytes_offset))?;
847889

@@ -1359,6 +1401,10 @@ impl<R: BufRead + Seek> ImageDecoder for BmpDecoder<R> {
13591401
}
13601402
}
13611403

1404+
fn icc_profile(&mut self) -> ImageResult<Option<Vec<u8>>> {
1405+
Ok(self.icc_profile.clone())
1406+
}
1407+
13621408
fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
13631409
assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
13641410
self.read_image_data(buf)
@@ -1477,4 +1523,29 @@ mod test {
14771523
assert_eq!(ref_img, no_hdr_img);
14781524
}
14791525
}
1526+
1527+
#[test]
1528+
fn test_icc_profile() {
1529+
// V5 header file without embedded ICC profile
1530+
let f =
1531+
BufReader::new(std::fs::File::open("tests/images/bmp/images/V5_24_Bit.bmp").unwrap());
1532+
let mut decoder = BmpDecoder::new(f).unwrap();
1533+
let profile = decoder.icc_profile().unwrap();
1534+
assert!(profile.is_none());
1535+
1536+
// Test files with embedded ICC profiles
1537+
let f =
1538+
BufReader::new(std::fs::File::open("tests/images/bmp/images/rgb24prof.bmp").unwrap());
1539+
let mut decoder = BmpDecoder::new(f).unwrap();
1540+
let profile = decoder.icc_profile().unwrap();
1541+
assert!(profile.is_some());
1542+
assert_eq!(profile.unwrap().len(), 3048);
1543+
1544+
let f =
1545+
BufReader::new(std::fs::File::open("tests/images/bmp/images/rgb24prof2.bmp").unwrap());
1546+
let mut decoder = BmpDecoder::new(f).unwrap();
1547+
let profile = decoder.icc_profile().unwrap();
1548+
assert!(profile.is_some());
1549+
assert_eq!(profile.unwrap().len(), 540);
1550+
}
14801551
}
27.1 KB
Binary file not shown.
24.7 KB
Binary file not shown.

0 commit comments

Comments
 (0)