@@ -64,6 +64,9 @@ const RLE_ESCAPE_DELTA: u8 = 2;
6464/// The maximum width/height the decoder will process.
6565const 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 ) ]
6871enum 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
495499enum 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}
0 commit comments