@@ -21,6 +21,7 @@ pub const PLTE_CHANNELS: usize = 3;
2121/// Headers for supported extensions.
2222const EXT_NAME_NETSCAPE : & [ u8 ] = b"\x0b NETSCAPE2.0\x03 " ;
2323const EXT_NAME_XMP : & [ u8 ] = b"\x0b XMP DataXMP" ;
24+ const EXT_NAME_ICC : & [ u8 ] = b"\x0b ICCRGBG1012" ;
2425
2526/// An error returned in the case of the image not being formatted properly.
2627#[ derive( Debug ) ]
@@ -382,6 +383,8 @@ pub struct StreamingDecoder {
382383 header_end_reached : bool ,
383384 /// XMP metadata bytes.
384385 xmp_metadata : Option < Vec < u8 > > ,
386+ /// ICC profile bytes.
387+ icc_profile : Option < Vec < u8 > > ,
385388}
386389
387390/// One version number of the GIF standard.
@@ -470,6 +473,7 @@ impl StreamingDecoder {
470473 current : None ,
471474 header_end_reached : false ,
472475 xmp_metadata : None ,
476+ icc_profile : None ,
473477 }
474478 }
475479
@@ -542,6 +546,12 @@ impl StreamingDecoder {
542546 self . xmp_metadata . as_deref ( )
543547 }
544548
549+ /// ICC profile stored in the image.
550+ #[ must_use]
551+ pub fn icc_profile ( & self ) -> Option < & [ u8 ] > {
552+ self . icc_profile . as_deref ( )
553+ }
554+
545555 /// The version number of the GIF standard used in this image.
546556 ///
547557 /// We suppose a minimum of `V87a` compatibility. This value will be reported until we have
@@ -773,26 +783,8 @@ impl StreamingDecoder {
773783 }
774784 ApplicationExtension => {
775785 debug_assert_eq ! ( 0 , b) ;
776-
777- // the parser removes sub-block lenghts, so app name and data are concatenated
778- if let Some ( & [ first, second, ..] ) = self . ext . data . strip_prefix ( EXT_NAME_NETSCAPE ) {
779- let repeat = u16:: from ( first) | u16:: from ( second) << 8 ;
780- goto ! ( BlockEnd , emit Decoded :: Repetitions ( if repeat == 0 { Repeat :: Infinite } else { Repeat :: Finite ( repeat) } ) )
781- } else if let Some ( mut rest) = self . ext . data . strip_prefix ( EXT_NAME_XMP ) {
782- // XMP adds a "ramp" of 257 bytes to the end of the metadata to let the "pascal-strings"
783- // parser converge to the null byte. The ramp looks like "0x01, 0xff, .., 0x01, 0x00".
784- // For convenience and to allow consumers to not be bothered with this implementation detail,
785- // we cut the ramp.
786- const RAMP_SIZE : usize = 257 ;
787- if rest. len ( ) >= RAMP_SIZE
788- && rest. ends_with ( & [ 0x03 , 0x02 , 0x01 , 0x00 ] )
789- && rest[ rest. len ( ) - RAMP_SIZE ..] . starts_with ( & [ 0x01 , 0x0ff ] )
790- {
791- rest = & rest[ ..( rest. len ( ) - RAMP_SIZE ) ] ;
792- }
793-
794- self . xmp_metadata = Some ( rest. to_vec ( ) ) ;
795- goto ! ( BlockEnd )
786+ if let Some ( decoded) = self . read_application_extension ( ) {
787+ goto ! ( BlockEnd , emit decoded)
796788 } else {
797789 goto ! ( BlockEnd )
798790 }
@@ -901,6 +893,50 @@ impl StreamingDecoder {
901893 Ok ( ( ) )
902894 }
903895
896+ fn read_application_extension ( & mut self ) -> Option < Decoded > {
897+ // the parser removes sub-block lenghts, so app name and data are concatenated
898+ if let Some ( rest) = self . ext . data . strip_prefix ( EXT_NAME_NETSCAPE ) {
899+ if rest. len ( ) != 3 {
900+ return None ;
901+ }
902+ let repeat = u16:: from ( rest[ 0 ] ) | u16:: from ( rest[ 1 ] ) << 8 ;
903+ return Some ( Decoded :: Repetitions ( if repeat == 0 {
904+ Repeat :: Infinite
905+ } else {
906+ Repeat :: Finite ( repeat)
907+ } ) ) ;
908+ } else if let Some ( mut rest) = self . ext . data . strip_prefix ( EXT_NAME_XMP ) {
909+ // XMP adds a "ramp" of 257 bytes to the end of the metadata to let the "pascal-strings"
910+ // parser converge to the null byte. The ramp looks like "0x01, 0xff, .., 0x01, 0x00".
911+ // For convenience and to allow consumers to not be bothered with this implementation detail,
912+ // we cut the ramp.
913+ const RAMP_SIZE : usize = 257 ;
914+ if rest. len ( ) >= RAMP_SIZE
915+ && rest. ends_with ( & [ 0x03 , 0x02 , 0x01 , 0x00 ] )
916+ && rest[ rest. len ( ) - RAMP_SIZE ..] . starts_with ( & [ 0x01 , 0x0ff ] )
917+ {
918+ rest = & rest[ ..( rest. len ( ) - RAMP_SIZE ) ] ;
919+ }
920+
921+ self . xmp_metadata = Some ( rest. to_vec ( ) ) ;
922+ } else if let Some ( mut rest) = self . ext . data . strip_prefix ( EXT_NAME_ICC ) {
923+ // The extension data always contains the length byte (see the XMP metadata extension
924+ // for why that is necessary), so we need to remove it again for the ICC profile.
925+ let mut icc_profile = Vec :: new ( ) ;
926+ loop {
927+ let block_length = * rest. first ( ) . unwrap_or ( & 0 ) as usize ;
928+ if block_length == 0 {
929+ break ;
930+ }
931+ let ( subblock, tail) = rest. split_at ( 1 + block_length) ;
932+ icc_profile. extend_from_slice ( & subblock[ 1 ..] ) ;
933+ rest = tail;
934+ }
935+ self . icc_profile = Some ( icc_profile) ;
936+ }
937+ None
938+ }
939+
904940 fn add_frame ( & mut self ) {
905941 if self . current . is_none ( ) {
906942 self . current = Some ( Frame :: default ( ) ) ;
0 commit comments