1
1
// Copyright (c) Six Labors.
2
2
// Licensed under the Six Labors Split License.
3
3
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 ;
6
7
using SixLabors . ImageSharp . Metadata . Profiles . Exif ;
7
8
using SixLabors . ImageSharp . Metadata . Profiles . Icc ;
8
9
using SixLabors . ImageSharp . Metadata . Profiles . Xmp ;
@@ -15,8 +16,6 @@ internal abstract class BitWriterBase
15
16
16
17
private const ulong MaxCanvasPixels = 4294967295ul ;
17
18
18
- protected const uint ExtendedFileChunkSize = WebpConstants . ChunkHeaderSize + WebpConstants . Vp8XChunkSize ;
19
-
20
19
/// <summary>
21
20
/// Buffer to write to.
22
21
/// </summary>
@@ -79,48 +78,6 @@ protected void ResizeBuffer(int maxBytes, int sizeRequired)
79
78
Array . Resize ( ref this . buffer , newSize ) ;
80
79
}
81
80
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
-
124
81
/// <summary>
125
82
/// Write the trunks before data trunk.
126
83
/// </summary>
@@ -143,7 +100,9 @@ public static void WriteTrunksBeforeData(
143
100
bool hasAnimation )
144
101
{
145
102
// 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." ) ;
147
106
148
107
// Write VP8X, header if necessary.
149
108
bool isVp8X = exifProfile != null || xmpProfile != null || iccProfile != null || hasAlpha || hasAnimation ;
@@ -153,7 +112,7 @@ public static void WriteTrunksBeforeData(
153
112
154
113
if ( iccProfile != null )
155
114
{
156
- WriteColorProfile ( stream , iccProfile . ToByteArray ( ) ) ;
115
+ RiffHelper . WriteChunk ( stream , ( uint ) WebpChunkType . Iccp , iccProfile . ToByteArray ( ) ) ;
157
116
}
158
117
}
159
118
}
@@ -177,49 +136,17 @@ public static void WriteTrunksAfterData(
177
136
{
178
137
if ( exifProfile != null )
179
138
{
180
- WriteMetadataProfile ( stream , exifProfile . ToByteArray ( ) , WebpChunkType . Exif ) ;
139
+ RiffHelper . WriteChunk ( stream , ( uint ) WebpChunkType . Exif , exifProfile . ToByteArray ( ) ) ;
181
140
}
182
141
183
142
if ( xmpProfile != null )
184
143
{
185
- WriteMetadataProfile ( stream , xmpProfile . Data , WebpChunkType . Xmp ) ;
144
+ RiffHelper . WriteChunk ( stream , ( uint ) WebpChunkType . Xmp , xmpProfile . Data ) ;
186
145
}
187
146
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 ) ;
214
148
}
215
149
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
-
223
150
/// <summary>
224
151
/// Writes the animation parameter(<see cref="WebpChunkType.AnimationParameter"/>) to the stream.
225
152
/// </summary>
@@ -233,55 +160,8 @@ protected static void WriteMetadataProfile(Stream stream, byte[]? metadataBytes,
233
160
/// <param name="loopCount">The number of times to loop the animation. If it is 0, this means infinitely.</param>
234
161
public static void WriteAnimationParameter ( Stream stream , Color background , ushort loopCount )
235
162
{
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 ) ;
285
165
}
286
166
287
167
/// <summary>
@@ -292,27 +172,17 @@ public static void OverwriteFrameSize(Stream stream, long prevPosition)
292
172
/// <param name="alphaDataIsCompressed">Indicates, if the alpha channel data is compressed.</param>
293
173
public static void WriteAlphaChunk ( Stream stream , Span < byte > dataBytes , bool alphaDataIsCompressed )
294
174
{
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 ) ;
302
176
byte flags = 0 ;
303
177
if ( alphaDataIsCompressed )
304
178
{
179
+ // TODO: Filtering and preprocessing
305
180
flags = 1 ;
306
181
}
307
182
308
183
stream . WriteByte ( flags ) ;
309
184
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 ) ;
316
186
}
317
187
318
188
/// <summary>
@@ -328,66 +198,10 @@ public static void WriteAlphaChunk(Stream stream, Span<byte> dataBytes, bool alp
328
198
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
329
199
protected static void WriteVp8XHeader ( Stream stream , ExifProfile ? exifProfile , XmpProfile ? xmpProfile , IccProfile ? iccProfile , uint width , uint height , bool hasAlpha , bool hasAnimation )
330
200
{
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 ) ;
335
202
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 ) ;
390
204
391
- public Span < byte > Span => MemoryMarshal . CreateSpan ( ref this . scratch [ 0 ] , Size ) ;
205
+ chunk . WriteTo ( stream ) ;
392
206
}
393
207
}
0 commit comments