Skip to content

Commit 1b0b877

Browse files
committed
introduce RiffHelper
1 parent b33209c commit 1b0b877

File tree

11 files changed

+349
-335
lines changed

11 files changed

+349
-335
lines changed
Lines changed: 18 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
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;
67
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
78
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
89
using 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
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Buffers.Binary;
5+
using SixLabors.ImageSharp.Common.Helpers;
6+
7+
namespace SixLabors.ImageSharp.Formats.Webp.Chunks;
8+
9+
internal readonly struct WebpAnimationParameter
10+
{
11+
public WebpAnimationParameter(uint background, ushort loopCount)
12+
{
13+
this.Background = background;
14+
this.LoopCount = loopCount;
15+
}
16+
17+
/// <summary>
18+
/// Gets default background color of the canvas in [Blue, Green, Red, Alpha] byte order.
19+
/// This color MAY be used to fill the unused space on the canvas around the frames,
20+
/// as well as the transparent pixels of the first frame.
21+
/// The background color is also used when the Disposal method is 1.
22+
/// </summary>
23+
public uint Background { get; }
24+
25+
/// <summary>
26+
/// Gets number of times to loop the animation. If it is 0, this means infinitely.
27+
/// </summary>
28+
public ushort LoopCount { get; }
29+
30+
public void WriteTo(Stream stream)
31+
{
32+
Span<byte> buffer = stackalloc byte[6];
33+
BinaryPrimitives.WriteUInt32LittleEndian(buffer[..4], this.Background);
34+
BinaryPrimitives.WriteUInt16LittleEndian(buffer[4..], this.LoopCount);
35+
RiffHelper.WriteChunk(stream, (uint)WebpChunkType.AnimationParameter, buffer);
36+
}
37+
}

0 commit comments

Comments
 (0)