@@ -19,8 +19,9 @@ use weezl::{decode::Decoder as LzwDecoder, BitOrder, LzwError, LzwStatus};
1919/// GIF palettes are RGB
2020pub const PLTE_CHANNELS : usize = 3 ;
2121/// Headers for supported extensions.
22- const EXT_NAME_NETSCAPE : & [ u8 ] = b"\x0b NETSCAPE2.0\x03 " ;
22+ const EXT_NAME_NETSCAPE : & [ u8 ] = b"\x0b NETSCAPE2.0\x01 " ;
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.
@@ -396,6 +399,7 @@ pub enum Version {
396399struct ExtensionData {
397400 id : AnyExtension ,
398401 data : Vec < u8 > ,
402+ sub_block_lens : Vec < u8 > ,
399403 is_block_end : bool ,
400404}
401405
@@ -465,11 +469,13 @@ impl StreamingDecoder {
465469 ext : ExtensionData {
466470 id : AnyExtension ( 0 ) ,
467471 data : Vec :: with_capacity ( 256 ) , // 0xFF + 1 byte length
472+ sub_block_lens : Vec :: new ( ) ,
468473 is_block_end : true ,
469474 } ,
470475 current : None ,
471476 header_end_reached : false ,
472477 xmp_metadata : None ,
478+ icc_profile : None ,
473479 }
474480 }
475481
@@ -542,6 +548,12 @@ impl StreamingDecoder {
542548 self . xmp_metadata . as_deref ( )
543549 }
544550
551+ /// ICC profile stored in the image.
552+ #[ must_use]
553+ pub fn icc_profile ( & self ) -> Option < & [ u8 ] > {
554+ self . icc_profile . as_deref ( )
555+ }
556+
545557 /// The version number of the GIF standard used in this image.
546558 ///
547559 /// We suppose a minimum of `V87a` compatibility. This value will be reported until we have
@@ -705,6 +717,7 @@ impl StreamingDecoder {
705717 }
706718 Some ( Block :: Extension ) => {
707719 self . ext . data . clear ( ) ;
720+ self . ext . sub_block_lens . clear ( ) ;
708721 self . ext . id = AnyExtension ( b) ;
709722 if self . ext . id . into_known ( ) . is_none ( ) {
710723 if !self . allow_unknown_blocks {
@@ -766,36 +779,15 @@ impl StreamingDecoder {
766779 }
767780 }
768781 } else {
769- self . ext . data . push ( b) ;
782+ self . ext . sub_block_lens . push ( b) ;
770783 self . ext . is_block_end = false ;
771784 goto ! ( ExtensionDataBlock ( b as usize ) , emit Decoded :: SubBlockFinished ( self . ext. id) )
772785 }
773786 }
774787 ApplicationExtension => {
775788 debug_assert_eq ! ( 0 , b) ;
776- // We can consume the extension data here as the next states won't access it anymore.
777- let mut data = std:: mem:: take ( & mut self . ext . data ) ;
778-
779- // the parser removes sub-block lenghts, so app name and data are concatenated
780- if let Some ( & [ first, second, ..] ) = data. strip_prefix ( EXT_NAME_NETSCAPE ) {
781- let repeat = u16:: from ( first) | u16:: from ( second) << 8 ;
782- goto ! ( BlockEnd , emit Decoded :: Repetitions ( if repeat == 0 { Repeat :: Infinite } else { Repeat :: Finite ( repeat) } ) )
783- } else if data. starts_with ( EXT_NAME_XMP ) {
784- data. drain ( ..EXT_NAME_XMP . len ( ) ) ;
785- // XMP adds a "ramp" of 257 bytes to the end of the metadata to let the "pascal-strings"
786- // parser converge to the null byte. The ramp looks like "0x01, 0xff, .., 0x01, 0x00".
787- // For convenience and to allow consumers to not be bothered with this implementation detail,
788- // we cut the ramp.
789- const RAMP_SIZE : usize = 257 ;
790- if data. len ( ) >= RAMP_SIZE
791- && data. ends_with ( & [ 0x03 , 0x02 , 0x01 , 0x00 ] )
792- && data[ data. len ( ) - RAMP_SIZE ..] . starts_with ( & [ 0x01 , 0x0ff ] )
793- {
794- data. truncate ( data. len ( ) - RAMP_SIZE ) ;
795- }
796-
797- self . xmp_metadata = Some ( data) ;
798- goto ! ( BlockEnd )
789+ if let Some ( decoded) = self . read_application_extension ( ) {
790+ goto ! ( BlockEnd , emit decoded)
799791 } else {
800792 goto ! ( BlockEnd )
801793 }
@@ -904,6 +896,44 @@ impl StreamingDecoder {
904896 Ok ( ( ) )
905897 }
906898
899+ fn read_application_extension ( & mut self ) -> Option < Decoded > {
900+ if let Some ( & [ first, second] ) = self . ext . data . strip_prefix ( EXT_NAME_NETSCAPE ) {
901+ let repeat = u16:: from ( first) | u16:: from ( second) << 8 ;
902+ return Some ( Decoded :: Repetitions ( if repeat == 0 {
903+ Repeat :: Infinite
904+ } else {
905+ Repeat :: Finite ( repeat)
906+ } ) ) ;
907+ } else if let Some ( mut rest) = self . ext . data . strip_prefix ( EXT_NAME_XMP ) {
908+ // XMP is not written as a valid "pascal-string", so we need to stitch together
909+ // the text from our collected sublock-lengths.
910+ let mut xmp_metadata = Vec :: new ( ) ;
911+ for len in self . ext . sub_block_lens . iter ( ) {
912+ xmp_metadata. push ( * len) ;
913+ let ( sub_block, tail) = rest. split_at ( * len as usize ) ;
914+ xmp_metadata. extend_from_slice ( sub_block) ;
915+ rest = tail;
916+ }
917+
918+ // XMP adds a "ramp" of 257 bytes to the end of the metadata to let the "pascal-strings"
919+ // parser converge to the null byte. The ramp looks like "0x01, 0xff, .., 0x01, 0x00".
920+ // For convenience and to allow consumers to not be bothered with this implementation detail,
921+ // we cut the ramp.
922+ const RAMP_SIZE : usize = 257 ;
923+ if xmp_metadata. len ( ) >= RAMP_SIZE
924+ && xmp_metadata. ends_with ( & [ 0x03 , 0x02 , 0x01 , 0x00 ] )
925+ && xmp_metadata[ xmp_metadata. len ( ) - RAMP_SIZE ..] . starts_with ( & [ 0x01 , 0x0ff ] )
926+ {
927+ xmp_metadata. truncate ( xmp_metadata. len ( ) - RAMP_SIZE ) ;
928+ }
929+
930+ self . xmp_metadata = Some ( xmp_metadata) ;
931+ } else if let Some ( rest) = self . ext . data . strip_prefix ( EXT_NAME_ICC ) {
932+ self . icc_profile = Some ( rest. to_vec ( ) ) ;
933+ }
934+ None
935+ }
936+
907937 fn add_frame ( & mut self ) {
908938 if self . current . is_none ( ) {
909939 self . current = Some ( Frame :: default ( ) ) ;
0 commit comments