Skip to content

Commit fbc08bd

Browse files
committed
Implement Vp8 encoder
1 parent 437144d commit fbc08bd

5 files changed

Lines changed: 180 additions & 89 deletions

File tree

src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs

Lines changed: 47 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -87,21 +87,20 @@ protected void ResizeBuffer(int maxBytes, int sizeRequired)
8787
/// <summary>
8888
/// Writes the RIFF header to the stream.
8989
/// </summary>
90-
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
9190
/// <param name="stream">The stream to write to.</param>
9291
/// <param name="riffSize">The block length.</param>
93-
protected void WriteRiffHeader(Stream stream, uint riffSize)
92+
protected static void WriteRiffHeader(Stream stream, uint riffSize)
9493
{
9594
stream.Write(WebpConstants.RiffFourCc);
96-
BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer.Span, riffSize);
97-
stream.Write(this.scratchBuffer.Span[..4]);
95+
Span<byte> buf = stackalloc byte[4];
96+
BinaryPrimitives.WriteUInt32LittleEndian(buf, riffSize);
97+
stream.Write(buf);
9898
stream.Write(WebpConstants.WebpHeader);
9999
}
100100

101101
/// <summary>
102102
/// Calculates the chunk size of EXIF, XMP or ICCP metadata.
103103
/// </summary>
104-
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
105104
/// <param name="metadataBytes">The metadata profile bytes.</param>
106105
/// <returns>The metadata chunk size in bytes.</returns>
107106
protected static uint MetadataChunkSize(byte[] metadataBytes)
@@ -125,59 +124,41 @@ protected static uint AlphaChunkSize(Span<byte> alphaBytes)
125124
/// Overwrites ides the write file size.
126125
/// </summary>
127126
/// <param name="stream">The stream to write to.</param>
128-
protected static void OverwriteFileSize(Stream stream)
129-
{
130-
uint position = (uint)stream.Position;
131-
stream.Position = 4;
132-
byte[] buffer = new byte[4];
133-
134-
// "RIFF"(4)+uint32 size(4)
135-
BinaryPrimitives.WriteUInt32LittleEndian(buffer, position - WebpConstants.ChunkHeaderSize);
136-
stream.Write(buffer);
137-
stream.Position = position;
138-
}
127+
protected static void OverwriteFileSize(Stream stream) => OverwriteFrameSize(stream, 4);
139128

140129
/// <summary>
141130
/// Write the trunks before data trunk.
142131
/// </summary>
143-
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="BitWriterBase.scratchBuffer"/></remarks>
144132
/// <param name="stream">The stream to write to.</param>
145133
/// <param name="width">The width of the image.</param>
146134
/// <param name="height">The height of the image.</param>
147135
/// <param name="exifProfile">The exif profile.</param>
148136
/// <param name="xmpProfile">The XMP profile.</param>
149137
/// <param name="iccProfile">The color profile.</param>
150138
/// <param name="hasAlpha">Flag indicating, if a alpha channel is present.</param>
151-
/// <param name="alphaData">The alpha channel data.</param>
152-
/// <param name="alphaDataIsCompressed">Indicates, if the alpha data is compressed.</param>
153-
public void WriteTrunksBeforeData(
139+
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
140+
public static void WriteTrunksBeforeData(
154141
Stream stream,
155142
uint width,
156143
uint height,
157144
ExifProfile? exifProfile,
158145
XmpProfile? xmpProfile,
159146
IccProfile? iccProfile,
160147
bool hasAlpha,
161-
Span<byte> alphaData,
162-
bool alphaDataIsCompressed)
148+
bool hasAnimation)
163149
{
164150
// Write file size later
165-
this.WriteRiffHeader(stream, 0);
151+
WriteRiffHeader(stream, 0);
166152

167153
// Write VP8X, header if necessary.
168-
bool isVp8X = exifProfile != null || xmpProfile != null || iccProfile != null || hasAlpha;
154+
bool isVp8X = exifProfile != null || xmpProfile != null || iccProfile != null || hasAlpha || hasAnimation;
169155
if (isVp8X)
170156
{
171-
this.WriteVp8XHeader(stream, exifProfile, xmpProfile, iccProfile, width, height, hasAlpha);
157+
WriteVp8XHeader(stream, exifProfile, xmpProfile, iccProfile, width, height, hasAlpha, hasAnimation);
172158

173159
if (iccProfile != null)
174160
{
175-
this.WriteColorProfile(stream, iccProfile.ToByteArray());
176-
}
177-
178-
if (hasAlpha)
179-
{
180-
this.WriteAlphaChunk(stream, alphaData, alphaDataIsCompressed);
161+
WriteColorProfile(stream, iccProfile.ToByteArray());
181162
}
182163
}
183164
}
@@ -191,23 +172,22 @@ public void WriteTrunksBeforeData(
191172
/// <summary>
192173
/// Write the trunks after data trunk.
193174
/// </summary>
194-
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="BitWriterBase.scratchBuffer"/></remarks>
195175
/// <param name="stream">The stream to write to.</param>
196176
/// <param name="exifProfile">The exif profile.</param>
197177
/// <param name="xmpProfile">The XMP profile.</param>
198-
public void WriteTrunksAfterData(
178+
public static void WriteTrunksAfterData(
199179
Stream stream,
200180
ExifProfile? exifProfile,
201181
XmpProfile? xmpProfile)
202182
{
203183
if (exifProfile != null)
204184
{
205-
this.WriteMetadataProfile(stream, exifProfile.ToByteArray(), WebpChunkType.Exif);
185+
WriteMetadataProfile(stream, exifProfile.ToByteArray(), WebpChunkType.Exif);
206186
}
207187

208188
if (xmpProfile != null)
209189
{
210-
this.WriteMetadataProfile(stream, xmpProfile.Data, WebpChunkType.Xmp);
190+
WriteMetadataProfile(stream, xmpProfile.Data, WebpChunkType.Xmp);
211191
}
212192

213193
OverwriteFileSize(stream);
@@ -216,16 +196,15 @@ public void WriteTrunksAfterData(
216196
/// <summary>
217197
/// Writes a metadata profile (EXIF or XMP) to the stream.
218198
/// </summary>
219-
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
220199
/// <param name="stream">The stream to write to.</param>
221200
/// <param name="metadataBytes">The metadata profile's bytes.</param>
222201
/// <param name="chunkType">The chuck type to write.</param>
223-
protected void WriteMetadataProfile(Stream stream, byte[]? metadataBytes, WebpChunkType chunkType)
202+
protected static void WriteMetadataProfile(Stream stream, byte[]? metadataBytes, WebpChunkType chunkType)
224203
{
225204
DebugGuard.NotNull(metadataBytes, nameof(metadataBytes));
226205

227206
uint size = (uint)metadataBytes.Length;
228-
Span<byte> buf = this.scratchBuffer.Span[..4];
207+
Span<byte> buf = stackalloc byte[4];
229208
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)chunkType);
230209
stream.Write(buf);
231210
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
@@ -242,15 +221,13 @@ protected void WriteMetadataProfile(Stream stream, byte[]? metadataBytes, WebpCh
242221
/// <summary>
243222
/// Writes the color profile(<see cref="WebpChunkType.Iccp"/>) to the stream.
244223
/// </summary>
245-
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
246224
/// <param name="stream">The stream to write to.</param>
247225
/// <param name="iccProfileBytes">The color profile bytes.</param>
248-
protected void WriteColorProfile(Stream stream, byte[] iccProfileBytes) => this.WriteMetadataProfile(stream, iccProfileBytes, WebpChunkType.Iccp);
226+
protected static void WriteColorProfile(Stream stream, byte[] iccProfileBytes) => WriteMetadataProfile(stream, iccProfileBytes, WebpChunkType.Iccp);
249227

250228
/// <summary>
251229
/// Writes the animation parameter(<see cref="WebpChunkType.AnimationParameter"/>) to the stream.
252230
/// </summary>
253-
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
254231
/// <param name="stream">The stream to write to.</param>
255232
/// <param name="background">
256233
/// The default background color of the canvas in [Blue, Green, Red, Alpha] byte order.
@@ -259,9 +236,9 @@ protected void WriteMetadataProfile(Stream stream, byte[]? metadataBytes, WebpCh
259236
/// The background color is also used when the Disposal method is 1.
260237
/// </param>
261238
/// <param name="loopCount">The number of times to loop the animation. If it is 0, this means infinitely.</param>
262-
protected void WriteAnimationParameter(Stream stream, uint background, ushort loopCount)
239+
public static void WriteAnimationParameter(Stream stream, uint background, ushort loopCount)
263240
{
264-
Span<byte> buf = this.scratchBuffer.Span[..4];
241+
Span<byte> buf = stackalloc byte[4];
265242
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.AnimationParameter);
266243
stream.Write(buf);
267244
BinaryPrimitives.WriteUInt32LittleEndian(buf, sizeof(uint) + sizeof(ushort));
@@ -275,17 +252,15 @@ protected void WriteAnimationParameter(Stream stream, uint background, ushort lo
275252
/// <summary>
276253
/// Writes the animation frame(<see cref="WebpChunkType.Animation"/>) to the stream.
277254
/// </summary>
278-
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
279255
/// <param name="stream">The stream to write to.</param>
280256
/// <param name="animation">Animation frame data.</param>
281-
/// <param name="data">Frame data.</param>
282-
protected void WriteAnimationFrame(Stream stream, AnimationFrameData animation, Span<byte> data)
257+
public static long WriteAnimationFrame(Stream stream, AnimationFrameData animation)
283258
{
284-
uint size = AnimationFrameData.HeaderSize + (uint)data.Length;
285-
Span<byte> buf = this.scratchBuffer.Span[..4];
259+
Span<byte> buf = stackalloc byte[4];
286260
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Animation);
287261
stream.Write(buf);
288-
BinaryPrimitives.WriteUInt32BigEndian(buf, size);
262+
long position = stream.Position;
263+
BinaryPrimitives.WriteUInt32BigEndian(buf, 0);
289264
stream.Write(buf);
290265
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.X);
291266
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Y);
@@ -294,20 +269,35 @@ protected void WriteAnimationFrame(Stream stream, AnimationFrameData animation,
294269
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Duration);
295270
byte flag = (byte)(((int)animation.BlendingMethod << 1) | (int)animation.DisposalMethod);
296271
stream.WriteByte(flag);
297-
stream.Write(data);
272+
return position;
273+
}
274+
275+
/// <summary>
276+
/// Overwrites ides the write frame size.
277+
/// </summary>
278+
/// <param name="stream">The stream to write to.</param>
279+
/// <param name="prevPosition">Previous position.</param>
280+
public static void OverwriteFrameSize(Stream stream, long prevPosition)
281+
{
282+
uint position = (uint)stream.Position;
283+
stream.Position = prevPosition;
284+
byte[] buffer = new byte[4];
285+
286+
BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)(position - prevPosition - 4));
287+
stream.Write(buffer);
288+
stream.Position = position;
298289
}
299290

300291
/// <summary>
301292
/// Writes the alpha chunk to the stream.
302293
/// </summary>
303-
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
304294
/// <param name="stream">The stream to write to.</param>
305295
/// <param name="dataBytes">The alpha channel data bytes.</param>
306296
/// <param name="alphaDataIsCompressed">Indicates, if the alpha channel data is compressed.</param>
307-
protected void WriteAlphaChunk(Stream stream, Span<byte> dataBytes, bool alphaDataIsCompressed)
297+
public static void WriteAlphaChunk(Stream stream, Span<byte> dataBytes, bool alphaDataIsCompressed)
308298
{
309299
uint size = (uint)dataBytes.Length + 1;
310-
Span<byte> buf = this.scratchBuffer.Span[..4];
300+
Span<byte> buf = stackalloc byte[4];
311301
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Alpha);
312302
stream.Write(buf);
313303
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
@@ -332,15 +322,15 @@ protected void WriteAlphaChunk(Stream stream, Span<byte> dataBytes, bool alphaDa
332322
/// <summary>
333323
/// Writes a VP8X header to the stream.
334324
/// </summary>
335-
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
336325
/// <param name="stream">The stream to write to.</param>
337326
/// <param name="exifProfile">A exif profile or null, if it does not exist.</param>
338327
/// <param name="xmpProfile">A XMP profile or null, if it does not exist.</param>
339328
/// <param name="iccProfile">The color profile.</param>
340329
/// <param name="width">The width of the image.</param>
341330
/// <param name="height">The height of the image.</param>
342331
/// <param name="hasAlpha">Flag indicating, if a alpha channel is present.</param>
343-
protected void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfile? xmpProfile, IccProfile? iccProfile, uint width, uint height, bool hasAlpha)
332+
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
333+
protected static void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfile? xmpProfile, IccProfile? iccProfile, uint width, uint height, bool hasAlpha, bool hasAnimation)
344334
{
345335
if (width > MaxDimension || height > MaxDimension)
346336
{
@@ -360,13 +350,11 @@ protected void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfi
360350
flags |= 8;
361351
}
362352

363-
/*
364-
if (isAnimated)
353+
if (hasAnimation)
365354
{
366355
// Set animated flag.
367356
flags |= 2;
368357
}
369-
*/
370358

371359
if (xmpProfile != null)
372360
{
@@ -386,7 +374,7 @@ protected void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfi
386374
flags |= 32;
387375
}
388376

389-
Span<byte> buf = this.scratchBuffer.Span[..4];
377+
Span<byte> buf = stackalloc byte[4];
390378
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Vp8X);
391379
stream.Write(buf);
392380
BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize);

src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,21 +267,20 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
267267
this.EncodeStream(image.Frames.RootFrame);
268268

269269
this.bitWriter.Finish();
270-
this.bitWriter.WriteTrunksBeforeData(
270+
BitWriterBase.WriteTrunksBeforeData(
271271
stream,
272272
(uint)width,
273273
(uint)height,
274274
exifProfile,
275275
xmpProfile,
276276
metadata.IccProfile,
277277
false /*hasAlpha*/,
278-
Span<byte>.Empty,
279278
false);
280279

281280
// Write bytes from the bitwriter buffer to the stream.
282281
this.bitWriter.WriteEncodedImageToStream(stream);
283282

284-
this.bitWriter.WriteTrunksAfterData(stream, exifProfile, xmpProfile);
283+
BitWriterBase.WriteTrunksAfterData(stream, exifProfile, xmpProfile);
285284
}
286285

287286
/// <summary>

0 commit comments

Comments
 (0)