22// Licensed under the Apache License, Version 2.0.
33
44using System ;
5- using System . Collections . Generic ;
65using System . IO ;
7- using SixLabors . ImageSharp . Metadata . Profiles . Xmp ;
6+ using SixLabors . ImageSharp . IO ;
7+ using SixLabors . ImageSharp . Memory ;
88
99namespace SixLabors . ImageSharp . Formats . Gif
1010{
@@ -14,7 +14,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
1414
1515 public byte Label => GifConstants . ApplicationExtensionLabel ;
1616
17- public int ContentLength => this . Data . Length + 269 ; // 12 + Data Length + 1 + 256
17+ // size : 1
18+ // identifier : 11
19+ // magic trailer : 257
20+ public int ContentLength => ( this . Data . Length > 0 ) ? this . Data . Length + 269 : 0 ;
1821
1922 /// <summary>
2023 /// Gets the raw Data.
@@ -25,51 +28,28 @@ namespace SixLabors.ImageSharp.Formats.Gif
2528 /// Reads the XMP metadata from the specified stream.
2629 /// </summary>
2730 /// <param name="stream">The stream to read from.</param>
31+ /// <param name="allocator">The memory allocator.</param>
2832 /// <returns>The XMP metadata</returns>
2933 /// <exception cref="ImageFormatException">Thrown if the XMP block is not properly terminated.</exception>
30- public static GifXmpApplicationExtension Read ( Stream stream )
34+ public static GifXmpApplicationExtension Read ( Stream stream , MemoryAllocator allocator )
3135 {
32- // Read data in blocks, until an \0 character is encountered.
33- // We overshoot, indicated by the terminatorIndex variable.
34- const int bufferSize = 256 ;
35- var list = new List < byte [ ] > ( ) ;
36- int terminationIndex = - 1 ;
37- while ( terminationIndex < 0 )
38- {
39- byte [ ] temp = new byte [ bufferSize ] ;
40- int bytesRead = stream . Read ( temp ) ;
41- list . Add ( temp ) ;
42- terminationIndex = Array . IndexOf ( temp , ( byte ) 1 ) ;
43- }
36+ byte [ ] xmpBytes = ReadXmpData ( stream , allocator ) ;
4437
45- // Pack all the blocks (except magic trailer) into one single array again.
46- int dataSize = ( ( list . Count - 1 ) * bufferSize ) + terminationIndex ;
47- byte [ ] buffer = new byte [ dataSize ] ;
48- Span < byte > bufferSpan = buffer ;
49- int pos = 0 ;
50- for ( int j = 0 ; j < list . Count - 1 ; j ++ )
38+ // Exclude the "magic trailer", see XMP Specification Part 3, 1.1.2 GIF
39+ int xmpLength = xmpBytes . Length - 256 ; // 257 - unread 0x0
40+ byte [ ] buffer = Array . Empty < byte > ( ) ;
41+ if ( xmpLength > 0 )
5142 {
52- list [ j ] . CopyTo ( bufferSpan . Slice ( pos ) ) ;
53- pos += bufferSize ;
43+ buffer = new byte [ xmpLength ] ;
44+ xmpBytes . AsSpan ( 0 , xmpLength ) . CopyTo ( buffer ) ;
45+ stream . Skip ( 1 ) ; // Skip the terminator.
5446 }
5547
56- // Last one only needs the portion until terminationIndex copied over.
57- Span < byte > lastBytes = list [ list . Count - 1 ] ;
58- lastBytes . Slice ( 0 , terminationIndex ) . CopyTo ( bufferSpan . Slice ( pos ) ) ;
59-
60- // Skip the remainder of the magic trailer.
61- stream . Skip ( 258 - ( bufferSize - terminationIndex ) ) ;
6248 return new GifXmpApplicationExtension ( buffer ) ;
6349 }
6450
6551 public int WriteTo ( Span < byte > buffer )
6652 {
67- int totalSize = this . ContentLength ;
68- if ( buffer . Length < totalSize )
69- {
70- throw new InsufficientMemoryException ( "Unable to write XMP metadata to GIF image" ) ;
71- }
72-
7353 int bytesWritten = 0 ;
7454 buffer [ bytesWritten ++ ] = GifConstants . ApplicationBlockSize ;
7555
@@ -91,7 +71,28 @@ public int WriteTo(Span<byte> buffer)
9171
9272 buffer [ bytesWritten ++ ] = 0x00 ;
9373
94- return totalSize ;
74+ return this . ContentLength ;
75+ }
76+
77+ private static byte [ ] ReadXmpData ( Stream stream , MemoryAllocator allocator )
78+ {
79+ using ChunkedMemoryStream bytes = new ( allocator ) ;
80+
81+ // XMP data doesn't have a fixed length nor is there an indicator of the length.
82+ // So we simply read one byte at a time until we hit the 0x0 value at the end
83+ // of the magic trailer or the end of the stream.
84+ // Using ChunkedMemoryStream reduces the array resize allocation normally associated
85+ // with writing from a non fixed-size buffer.
86+ while ( true )
87+ {
88+ int b = stream . ReadByte ( ) ;
89+ if ( b <= 0 )
90+ {
91+ return bytes . ToArray ( ) ;
92+ }
93+
94+ bytes . WriteByte ( ( byte ) b ) ;
95+ }
9596 }
9697 }
9798}
0 commit comments