22// Licensed under the Six Labors Split License.
33
44using System . Runtime . InteropServices ;
5- using SixLabors . ImageSharp ;
5+ using SixLabors . ImageSharp . Formats . Bmp ;
6+ using SixLabors . ImageSharp . Formats . Png ;
67using SixLabors . ImageSharp . IO ;
78using SixLabors . ImageSharp . Metadata ;
89using SixLabors . ImageSharp . PixelFormats ;
@@ -30,50 +31,63 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
3031 long basePosition = stream . Position ;
3132 this . ReadHeader ( stream ) ;
3233
33- Span < byte > flag = stackalloc byte [ Png . PngConstants . HeaderBytes . Length ] ;
34+ Span < byte > flag = stackalloc byte [ PngConstants . HeaderBytes . Length ] ;
35+ Image < TPixel > result = new ( this . Dimensions . Width , this . Dimensions . Height ) ;
3436
35- List < ( Image < TPixel > Image , bool IsPng , int Index ) > frames = new ( this . Entries . Length ) ;
3637 for ( int i = 0 ; i < this . Entries . Length ; i ++ )
3738 {
38- _ = IconAssert . EndOfStream ( stream . Seek ( basePosition + this . Entries [ i ] . ImageOffset , SeekOrigin . Begin ) , basePosition + this . Entries [ i ] . ImageOffset ) ;
39- _ = IconAssert . EndOfStream ( stream . Read ( flag ) , Png . PngConstants . HeaderBytes . Length ) ;
40- _ = stream . Seek ( - Png . PngConstants . HeaderBytes . Length , SeekOrigin . Current ) ;
39+ ref IconDirEntry entry = ref this . Entries [ i ] ;
4140
42- bool isPng = flag . SequenceEqual ( Png . PngConstants . HeaderBytes ) ;
41+ // If we hit the end of the stream we should break.
42+ if ( stream . Seek ( basePosition + entry . ImageOffset , SeekOrigin . Begin ) >= stream . Length )
43+ {
44+ break ;
45+ }
4346
44- Image < TPixel > img = this . GetDecoder ( isPng ) . Decode < TPixel > ( stream , cancellationToken ) ;
45- IconAssert . NotSquare ( img . Size ) ;
46- frames . Add ( ( img , isPng , i ) ) ;
47- if ( isPng && img . Size . Width > this . Dimensions . Width )
47+ // There should always be enough bytes for this regardless of the entry type.
48+ if ( stream . Read ( flag ) != PngConstants . HeaderBytes . Length )
4849 {
49- this . Dimensions = img . Size ;
50+ break ;
5051 }
51- }
5252
53- ImageMetadata metadata = new ( ) ;
54- return new ( this . Options . Configuration , metadata , frames . Select ( i =>
55- {
56- ImageFrame < TPixel > target = new ( this . Options . Configuration , this . Dimensions ) ;
57- ImageFrame < TPixel > source = i . Image . Frames . RootFrameUnsafe ;
53+ // Reset the stream position.
54+ stream . Seek ( - PngConstants . HeaderBytes . Length , SeekOrigin . Current ) ;
55+
56+ bool isPng = flag . SequenceEqual ( PngConstants . HeaderBytes ) ;
57+ using Image < TPixel > temp = this . GetDecoder ( isPng ) . Decode < TPixel > ( stream , cancellationToken ) ;
58+
59+ ImageFrame < TPixel > source = temp . Frames . RootFrameUnsafe ;
60+ ImageFrame < TPixel > target = i == 0 ? result . Frames . RootFrameUnsafe : result . Frames . CreateFrame ( ) ;
61+
62+ // Draw the new frame at position 0,0. We capture the dimensions for cropping during encoding
63+ // via the icon entry.
5864 for ( int h = 0 ; h < source . Height ; h ++ )
5965 {
6066 source . PixelBuffer . DangerousGetRowSpan ( h ) . CopyTo ( target . PixelBuffer . DangerousGetRowSpan ( h ) ) ;
6167 }
6268
63- if ( i . IsPng )
69+ // Copy the format specific metadata to the image.
70+ if ( isPng )
6471 {
65- target . Metadata . UnsafeSetFormatMetadata ( Png . PngFormat . Instance , i . Image . Metadata . GetPngMetadata ( ) ) ;
72+ if ( i == 0 )
73+ {
74+ result . Metadata . SetFormatMetadata ( PngFormat . Instance , temp . Metadata . GetPngMetadata ( ) ) ;
75+ }
76+
77+ target . Metadata . SetFormatMetadata ( PngFormat . Instance , target . Metadata . GetPngFrameMetadata ( ) ) ;
6678 }
67- else
79+ else if ( i == 0 )
6880 {
69- target . Metadata . UnsafeSetFormatMetadata ( Bmp . BmpFormat . Instance , i . Image . Metadata . GetBmpMetadata ( ) ) ;
81+ // Bmp does not contain frame specific metadata.
82+ result . Metadata . SetFormatMetadata ( BmpFormat . Instance , temp . Metadata . GetBmpMetadata ( ) ) ;
7083 }
7184
72- this . GetFrameMetadata ( target . Metadata ) . FromIconDirEntry ( this . Entries [ i . Index ] ) ;
85+ // TODO: The inheriting decoder should be responsible for setting the actual data (FromIconDirEntry)
86+ // so we can avoid the protected Field1 and Field2 properties and use strong typing.
87+ this . GetFrameMetadata ( target . Metadata ) . FromIconDirEntry ( entry ) ;
88+ }
7389
74- i . Image . Dispose ( ) ;
75- return target ;
76- } ) . ToArray ( ) ) ;
90+ return result ;
7791 }
7892
7993 public ImageInfo Identify ( BufferedReadStream stream , CancellationToken cancellationToken )
@@ -84,34 +98,62 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
8498 ImageFrameMetadata [ ] frames = new ImageFrameMetadata [ this . FileHeader . Count ] ;
8599 for ( int i = 0 ; i < frames . Length ; i ++ )
86100 {
101+ // TODO: Use the Identify methods in each decoder to return the
102+ // format specific metadata for the image and frame.
87103 frames [ i ] = new ( ) ;
88104 IconFrameMetadata icoFrameMetadata = this . GetFrameMetadata ( frames [ i ] ) ;
89105 icoFrameMetadata . FromIconDirEntry ( this . Entries [ i ] ) ;
90106 }
91107
108+ // TODO: Use real values from the metadata.
92109 return new ( new ( 32 ) , new ( 0 ) , metadata , frames ) ;
93110 }
94111
95112 protected abstract IconFrameMetadata GetFrameMetadata ( ImageFrameMetadata metadata ) ;
96113
97114 protected void ReadHeader ( Stream stream )
98115 {
116+ // TODO: Check length and throw if the header cannot be read.
99117 _ = Read ( stream , out this . fileHeader , IconDir . Size ) ;
100118 this . Entries = new IconDirEntry [ this . FileHeader . Count ] ;
101119 for ( int i = 0 ; i < this . Entries . Length ; i ++ )
102120 {
103121 _ = Read ( stream , out this . Entries [ i ] , IconDirEntry . Size ) ;
104122 }
105123
106- this . Dimensions = new (
107- this . Entries . Max ( i => i . Width ) ,
108- this . Entries . Max ( i => i . Height ) ) ;
124+ int width = 0 ;
125+ int height = 0 ;
126+ foreach ( IconDirEntry entry in this . Entries )
127+ {
128+ if ( entry . Width == 0 )
129+ {
130+ width = 256 ;
131+ }
132+
133+ if ( entry . Height == 0 )
134+ {
135+ height = 256 ;
136+ }
137+
138+ if ( width == 256 && height == 256 )
139+ {
140+ break ;
141+ }
142+
143+ width = Math . Max ( width , entry . Width ) ;
144+ height = Math . Max ( height , entry . Height ) ;
145+ }
146+
147+ this . Dimensions = new ( width , height ) ;
109148 }
110149
111150 private static int Read < T > ( Stream stream , out T data , int size )
112151 where T : unmanaged
113152 {
153+ // TODO: Use explicit parsing methods for each T type.
154+ // See PngHeader.Parse() for example.
114155 Span < byte > buffer = stackalloc byte [ size ] ;
156+
115157 _ = IconAssert . EndOfStream ( stream . Read ( buffer ) , size ) ;
116158 data = MemoryMarshal . Cast < byte , T > ( buffer ) [ 0 ] ;
117159 return size ;
@@ -121,15 +163,16 @@ private IImageDecoderInternals GetDecoder(bool isPng)
121163 {
122164 if ( isPng )
123165 {
124- return new Png . PngDecoderCore ( this . Options ) ;
166+ return new PngDecoderCore ( this . Options ) ;
125167 }
126168 else
127169 {
128- return new Bmp . BmpDecoderCore ( new ( )
170+ return new BmpDecoderCore ( new ( )
129171 {
172+ GeneralOptions = this . Options ,
130173 ProcessedAlphaMask = true ,
131174 SkipFileHeader = true ,
132- IsDoubleHeight = true ,
175+ UseDoubleHeight = true ,
133176 } ) ;
134177 }
135178 }
0 commit comments