11// Copyright (c) Six Labors.
22// Licensed under the Six Labors Split License.
33
4- using System . Buffers . Binary ;
5- using System . Runtime . InteropServices ;
4+ using System . Diagnostics ;
5+ using SixLabors . ImageSharp . Common . Helpers ;
6+ using SixLabors . ImageSharp . Formats . Webp . Chunks ;
67using SixLabors . ImageSharp . Metadata . Profiles . Exif ;
78using SixLabors . ImageSharp . Metadata . Profiles . Icc ;
89using SixLabors . ImageSharp . Metadata . Profiles . Xmp ;
@@ -15,8 +16,6 @@ internal abstract class BitWriterBase
1516
1617 private const ulong MaxCanvasPixels = 4294967295ul ;
1718
18- protected const uint ExtendedFileChunkSize = WebpConstants . ChunkHeaderSize + WebpConstants . Vp8XChunkSize ;
19-
2019 /// <summary>
2120 /// Buffer to write to.
2221 /// </summary>
@@ -79,48 +78,6 @@ protected void ResizeBuffer(int maxBytes, int sizeRequired)
7978 Array . Resize ( ref this . buffer , newSize ) ;
8079 }
8180
82- /// <summary>
83- /// Writes the RIFF header to the stream.
84- /// </summary>
85- /// <param name="stream">The stream to write to.</param>
86- /// <param name="riffSize">The block length.</param>
87- protected static void WriteRiffHeader ( Stream stream , uint riffSize )
88- {
89- stream . Write ( WebpConstants . RiffFourCc ) ;
90- Span < byte > buf = stackalloc byte [ 4 ] ;
91- BinaryPrimitives . WriteUInt32LittleEndian ( buf , riffSize ) ;
92- stream . Write ( buf ) ;
93- stream . Write ( WebpConstants . WebpHeader ) ;
94- }
95-
96- /// <summary>
97- /// Calculates the chunk size of EXIF, XMP or ICCP metadata.
98- /// </summary>
99- /// <param name="metadataBytes">The metadata profile bytes.</param>
100- /// <returns>The metadata chunk size in bytes.</returns>
101- protected static uint MetadataChunkSize ( byte [ ] metadataBytes )
102- {
103- uint metaSize = ( uint ) metadataBytes . Length ;
104- return WebpConstants . ChunkHeaderSize + metaSize + ( metaSize & 1 ) ;
105- }
106-
107- /// <summary>
108- /// Calculates the chunk size of a alpha chunk.
109- /// </summary>
110- /// <param name="alphaBytes">The alpha chunk bytes.</param>
111- /// <returns>The alpha data chunk size in bytes.</returns>
112- protected static uint AlphaChunkSize ( Span < byte > alphaBytes )
113- {
114- uint alphaSize = ( uint ) alphaBytes . Length + 1 ;
115- return WebpConstants . ChunkHeaderSize + alphaSize + ( alphaSize & 1 ) ;
116- }
117-
118- /// <summary>
119- /// Overwrites ides the write file size.
120- /// </summary>
121- /// <param name="stream">The stream to write to.</param>
122- protected static void OverwriteFileSize ( Stream stream ) => OverwriteFrameSize ( stream , 4 ) ;
123-
12481 /// <summary>
12582 /// Write the trunks before data trunk.
12683 /// </summary>
@@ -143,7 +100,9 @@ public static void WriteTrunksBeforeData(
143100 bool hasAnimation )
144101 {
145102 // Write file size later
146- WriteRiffHeader ( stream , 0 ) ;
103+ long pos = RiffHelper . BeginWriteRiffFile ( stream , WebpConstants . WebpFourCc ) ;
104+
105+ Debug . Assert ( pos is 4 , "Stream should be written from position 0." ) ;
147106
148107 // Write VP8X, header if necessary.
149108 bool isVp8X = exifProfile != null || xmpProfile != null || iccProfile != null || hasAlpha || hasAnimation ;
@@ -153,7 +112,7 @@ public static void WriteTrunksBeforeData(
153112
154113 if ( iccProfile != null )
155114 {
156- WriteColorProfile ( stream , iccProfile . ToByteArray ( ) ) ;
115+ RiffHelper . WriteChunk ( stream , ( uint ) WebpChunkType . Iccp , iccProfile . ToByteArray ( ) ) ;
157116 }
158117 }
159118 }
@@ -177,49 +136,17 @@ public static void WriteTrunksAfterData(
177136 {
178137 if ( exifProfile != null )
179138 {
180- WriteMetadataProfile ( stream , exifProfile . ToByteArray ( ) , WebpChunkType . Exif ) ;
139+ RiffHelper . WriteChunk ( stream , ( uint ) WebpChunkType . Exif , exifProfile . ToByteArray ( ) ) ;
181140 }
182141
183142 if ( xmpProfile != null )
184143 {
185- WriteMetadataProfile ( stream , xmpProfile . Data , WebpChunkType . Xmp ) ;
144+ RiffHelper . WriteChunk ( stream , ( uint ) WebpChunkType . Xmp , xmpProfile . Data ) ;
186145 }
187146
188- OverwriteFileSize ( stream ) ;
189- }
190-
191- /// <summary>
192- /// Writes a metadata profile (EXIF or XMP) to the stream.
193- /// </summary>
194- /// <param name="stream">The stream to write to.</param>
195- /// <param name="metadataBytes">The metadata profile's bytes.</param>
196- /// <param name="chunkType">The chuck type to write.</param>
197- protected static void WriteMetadataProfile ( Stream stream , byte [ ] ? metadataBytes , WebpChunkType chunkType )
198- {
199- DebugGuard . NotNull ( metadataBytes , nameof ( metadataBytes ) ) ;
200-
201- uint size = ( uint ) metadataBytes . Length ;
202- Span < byte > buf = stackalloc byte [ 4 ] ;
203- BinaryPrimitives . WriteUInt32BigEndian ( buf , ( uint ) chunkType ) ;
204- stream . Write ( buf ) ;
205- BinaryPrimitives . WriteUInt32LittleEndian ( buf , size ) ;
206- stream . Write ( buf ) ;
207- stream . Write ( metadataBytes ) ;
208-
209- // Add padding byte if needed.
210- if ( ( size & 1 ) == 1 )
211- {
212- stream . WriteByte ( 0 ) ;
213- }
147+ RiffHelper . EndWriteRiffFile ( stream , 4 ) ;
214148 }
215149
216- /// <summary>
217- /// Writes the color profile(<see cref="WebpChunkType.Iccp"/>) to the stream.
218- /// </summary>
219- /// <param name="stream">The stream to write to.</param>
220- /// <param name="iccProfileBytes">The color profile bytes.</param>
221- protected static void WriteColorProfile ( Stream stream , byte [ ] iccProfileBytes ) => WriteMetadataProfile ( stream , iccProfileBytes , WebpChunkType . Iccp ) ;
222-
223150 /// <summary>
224151 /// Writes the animation parameter(<see cref="WebpChunkType.AnimationParameter"/>) to the stream.
225152 /// </summary>
@@ -233,55 +160,8 @@ protected static void WriteMetadataProfile(Stream stream, byte[]? metadataBytes,
233160 /// <param name="loopCount">The number of times to loop the animation. If it is 0, this means infinitely.</param>
234161 public static void WriteAnimationParameter ( Stream stream , Color background , ushort loopCount )
235162 {
236- Span < byte > buf = stackalloc byte [ 4 ] ;
237- BinaryPrimitives . WriteUInt32BigEndian ( buf , ( uint ) WebpChunkType . AnimationParameter ) ;
238- stream . Write ( buf ) ;
239- BinaryPrimitives . WriteUInt32LittleEndian ( buf , sizeof ( uint ) + sizeof ( ushort ) ) ;
240- stream . Write ( buf ) ;
241- BinaryPrimitives . WriteUInt32LittleEndian ( buf , background . ToRgba32 ( ) . Rgba ) ;
242- stream . Write ( buf ) ;
243- BinaryPrimitives . WriteUInt16LittleEndian ( buf [ ..2 ] , loopCount ) ;
244- stream . Write ( buf [ ..2 ] ) ;
245- }
246-
247- /// <summary>
248- /// Writes the animation frame(<see cref="WebpChunkType.Animation"/>) to the stream.
249- /// </summary>
250- /// <param name="stream">The stream to write to.</param>
251- /// <param name="animation">Animation frame data.</param>
252- public static long WriteAnimationFrame ( Stream stream , WebpFrameData animation )
253- {
254- Span < byte > buf = stackalloc byte [ 4 ] ;
255- BinaryPrimitives . WriteUInt32BigEndian ( buf , ( uint ) WebpChunkType . Animation ) ;
256- stream . Write ( buf ) ;
257- long position = stream . Position ;
258- BinaryPrimitives . WriteUInt32BigEndian ( buf , 0 ) ;
259- stream . Write ( buf ) ;
260- WebpChunkParsingUtils . WriteUInt24LittleEndian ( stream , animation . X ) ;
261- WebpChunkParsingUtils . WriteUInt24LittleEndian ( stream , animation . Y ) ;
262- WebpChunkParsingUtils . WriteUInt24LittleEndian ( stream , animation . Width - 1 ) ;
263- WebpChunkParsingUtils . WriteUInt24LittleEndian ( stream , animation . Height - 1 ) ;
264- WebpChunkParsingUtils . WriteUInt24LittleEndian ( stream , animation . Duration ) ;
265-
266- byte flag = ( byte ) ( ( ( int ) animation . BlendingMethod << 1 ) | ( int ) animation . DisposalMethod ) ;
267- stream . WriteByte ( flag ) ;
268- return position ;
269- }
270-
271- /// <summary>
272- /// Overwrites ides the write frame size.
273- /// </summary>
274- /// <param name="stream">The stream to write to.</param>
275- /// <param name="prevPosition">Previous position.</param>
276- public static void OverwriteFrameSize ( Stream stream , long prevPosition )
277- {
278- uint position = ( uint ) stream . Position ;
279- stream . Position = prevPosition ;
280- byte [ ] buffer = new byte [ 4 ] ;
281-
282- BinaryPrimitives . WriteUInt32LittleEndian ( buffer , ( uint ) ( position - prevPosition - 4 ) ) ;
283- stream . Write ( buffer ) ;
284- stream . Position = position ;
163+ WebpAnimationParameter chunk = new ( background . ToRgba32 ( ) . Rgba , loopCount ) ;
164+ chunk . WriteTo ( stream ) ;
285165 }
286166
287167 /// <summary>
@@ -292,27 +172,17 @@ public static void OverwriteFrameSize(Stream stream, long prevPosition)
292172 /// <param name="alphaDataIsCompressed">Indicates, if the alpha channel data is compressed.</param>
293173 public static void WriteAlphaChunk ( Stream stream , Span < byte > dataBytes , bool alphaDataIsCompressed )
294174 {
295- uint size = ( uint ) dataBytes . Length + 1 ;
296- Span < byte > buf = stackalloc byte [ 4 ] ;
297- BinaryPrimitives . WriteUInt32BigEndian ( buf , ( uint ) WebpChunkType . Alpha ) ;
298- stream . Write ( buf ) ;
299- BinaryPrimitives . WriteUInt32LittleEndian ( buf , size ) ;
300- stream . Write ( buf ) ;
301-
175+ long pos = RiffHelper . BeginWriteChunk ( stream , ( uint ) WebpChunkType . Alpha ) ;
302176 byte flags = 0 ;
303177 if ( alphaDataIsCompressed )
304178 {
179+ // TODO: Filtering and preprocessing
305180 flags = 1 ;
306181 }
307182
308183 stream . WriteByte ( flags ) ;
309184 stream . Write ( dataBytes ) ;
310-
311- // Add padding byte if needed.
312- if ( ( size & 1 ) == 1 )
313- {
314- stream . WriteByte ( 0 ) ;
315- }
185+ RiffHelper . EndWriteChunk ( stream , pos ) ;
316186 }
317187
318188 /// <summary>
@@ -328,66 +198,10 @@ public static void WriteAlphaChunk(Stream stream, Span<byte> dataBytes, bool alp
328198 /// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
329199 protected static void WriteVp8XHeader ( Stream stream , ExifProfile ? exifProfile , XmpProfile ? xmpProfile , IccProfile ? iccProfile , uint width , uint height , bool hasAlpha , bool hasAnimation )
330200 {
331- if ( width > MaxDimension || height > MaxDimension )
332- {
333- WebpThrowHelper . ThrowInvalidImageDimensions ( $ "Image width or height exceeds maximum allowed dimension of { MaxDimension } ") ;
334- }
201+ WebpVp8X chunk = new ( hasAnimation , xmpProfile != null , exifProfile != null , hasAlpha , iccProfile != null , width , height ) ;
335202
336- // The spec states that the product of Canvas Width and Canvas Height MUST be at most 2^32 - 1.
337- if ( width * height > MaxCanvasPixels )
338- {
339- WebpThrowHelper . ThrowInvalidImageDimensions ( "The product of image width and height MUST be at most 2^32 - 1" ) ;
340- }
341-
342- uint flags = 0 ;
343- if ( exifProfile != null )
344- {
345- // Set exif bit.
346- flags |= 8 ;
347- }
348-
349- if ( hasAnimation )
350- {
351- // Set animated flag.
352- flags |= 2 ;
353- }
354-
355- if ( xmpProfile != null )
356- {
357- // Set xmp bit.
358- flags |= 4 ;
359- }
360-
361- if ( hasAlpha )
362- {
363- // Set alpha bit.
364- flags |= 16 ;
365- }
366-
367- if ( iccProfile != null )
368- {
369- // Set iccp flag.
370- flags |= 32 ;
371- }
372-
373- Span < byte > buf = stackalloc byte [ 4 ] ;
374- BinaryPrimitives . WriteUInt32BigEndian ( buf , ( uint ) WebpChunkType . Vp8X ) ;
375- stream . Write ( buf ) ;
376- BinaryPrimitives . WriteUInt32LittleEndian ( buf , WebpConstants . Vp8XChunkSize ) ;
377- stream . Write ( buf ) ;
378- BinaryPrimitives . WriteUInt32LittleEndian ( buf , flags ) ;
379- stream . Write ( buf ) ;
380- BinaryPrimitives . WriteUInt32LittleEndian ( buf , width - 1 ) ;
381- stream . Write ( buf [ ..3 ] ) ;
382- BinaryPrimitives . WriteUInt32LittleEndian ( buf , height - 1 ) ;
383- stream . Write ( buf [ ..3 ] ) ;
384- }
385-
386- private unsafe struct ScratchBuffer
387- {
388- private const int Size = 4 ;
389- private fixed byte scratch [ Size ] ;
203+ chunk . Validate ( MaxDimension , MaxCanvasPixels ) ;
390204
391- public Span < byte > Span => MemoryMarshal . CreateSpan ( ref this . scratch [ 0 ] , Size ) ;
205+ chunk . WriteTo ( stream ) ;
392206 }
393207}
0 commit comments