Skip to content

Commit 6f52a0d

Browse files
committed
Preparation
1 parent d93bc6c commit 6f52a0d

File tree

15 files changed

+248
-199
lines changed

15 files changed

+248
-199
lines changed

src/ImageSharp/Formats/Webp/AlphaEncoder.cs

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,25 @@ internal static class AlphaEncoder
1919
/// Data is either compressed as lossless webp image or uncompressed.
2020
/// </summary>
2121
/// <typeparam name="TPixel">The pixel format.</typeparam>
22-
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
22+
/// <param name="frame">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
2323
/// <param name="configuration">The global configuration.</param>
2424
/// <param name="memoryAllocator">The memory manager.</param>
2525
/// <param name="skipMetadata">Whether to skip metadata encoding.</param>
2626
/// <param name="compress">Indicates, if the data should be compressed with the lossless webp compression.</param>
2727
/// <param name="size">The size in bytes of the alpha data.</param>
2828
/// <returns>The encoded alpha data.</returns>
2929
public static IMemoryOwner<byte> EncodeAlpha<TPixel>(
30-
Image<TPixel> image,
30+
ImageFrame<TPixel> frame,
3131
Configuration configuration,
3232
MemoryAllocator memoryAllocator,
3333
bool skipMetadata,
3434
bool compress,
3535
out int size)
3636
where TPixel : unmanaged, IPixel<TPixel>
3737
{
38-
int width = image.Width;
39-
int height = image.Height;
40-
IMemoryOwner<byte> alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator);
38+
int width = frame.Width;
39+
int height = frame.Height;
40+
IMemoryOwner<byte> alphaData = ExtractAlphaChannel(frame, configuration, memoryAllocator);
4141

4242
if (compress)
4343
{
@@ -58,9 +58,9 @@ public static IMemoryOwner<byte> EncodeAlpha<TPixel>(
5858
// The transparency information will be stored in the green channel of the ARGB quadruplet.
5959
// The green channel is allowed extra transformation steps in the specification -- unlike the other channels,
6060
// that can improve compression.
61-
using Image<Rgba32> alphaAsImage = DispatchAlphaToGreen(image, alphaData.GetSpan());
61+
using ImageFrame<Rgba32> alphaAsFrame = DispatchAlphaToGreen(frame, alphaData.GetSpan());
6262

63-
size = lossLessEncoder.EncodeAlphaImageData(alphaAsImage, alphaData);
63+
size = lossLessEncoder.EncodeAlphaImageData(alphaAsFrame, alphaData);
6464

6565
return alphaData;
6666
}
@@ -73,45 +73,45 @@ public static IMemoryOwner<byte> EncodeAlpha<TPixel>(
7373
/// Store the transparency in the green channel.
7474
/// </summary>
7575
/// <typeparam name="TPixel">The pixel format.</typeparam>
76-
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
76+
/// <param name="frame">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
7777
/// <param name="alphaData">A byte sequence of length width * height, containing all the 8-bit transparency values in scan order.</param>
78-
/// <returns>The transparency image.</returns>
79-
private static Image<Rgba32> DispatchAlphaToGreen<TPixel>(Image<TPixel> image, Span<byte> alphaData)
78+
/// <returns>The transparency frame.</returns>
79+
private static ImageFrame<Rgba32> DispatchAlphaToGreen<TPixel>(ImageFrame<TPixel> frame, Span<byte> alphaData)
8080
where TPixel : unmanaged, IPixel<TPixel>
8181
{
82-
int width = image.Width;
83-
int height = image.Height;
84-
Image<Rgba32> alphaAsImage = new(width, height);
82+
int width = frame.Width;
83+
int height = frame.Height;
84+
ImageFrame<Rgba32> alphaAsFrame = new(Configuration.Default, width, height);
8585

8686
for (int y = 0; y < height; y++)
8787
{
88-
Memory<Rgba32> rowBuffer = alphaAsImage.DangerousGetPixelRowMemory(y);
88+
Memory<Rgba32> rowBuffer = alphaAsFrame.DangerousGetPixelRowMemory(y);
8989
Span<Rgba32> pixelRow = rowBuffer.Span;
9090
Span<byte> alphaRow = alphaData.Slice(y * width, width);
9191
for (int x = 0; x < width; x++)
9292
{
9393
// Leave A/R/B channels zero'd.
94-
pixelRow[x] = new Rgba32(0, alphaRow[x], 0, 0);
94+
pixelRow[x] = new(0, alphaRow[x], 0, 0);
9595
}
9696
}
9797

98-
return alphaAsImage;
98+
return alphaAsFrame;
9999
}
100100

101101
/// <summary>
102102
/// Extract the alpha data of the image.
103103
/// </summary>
104104
/// <typeparam name="TPixel">The pixel format.</typeparam>
105-
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
105+
/// <param name="frame">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
106106
/// <param name="configuration">The global configuration.</param>
107107
/// <param name="memoryAllocator">The memory manager.</param>
108108
/// <returns>A byte sequence of length width * height, containing all the 8-bit transparency values in scan order.</returns>
109-
private static IMemoryOwner<byte> ExtractAlphaChannel<TPixel>(Image<TPixel> image, Configuration configuration, MemoryAllocator memoryAllocator)
109+
private static IMemoryOwner<byte> ExtractAlphaChannel<TPixel>(ImageFrame<TPixel> frame, Configuration configuration, MemoryAllocator memoryAllocator)
110110
where TPixel : unmanaged, IPixel<TPixel>
111111
{
112-
Buffer2D<TPixel> imageBuffer = image.Frames.RootFrame.PixelBuffer;
113-
int height = image.Height;
114-
int width = image.Width;
112+
Buffer2D<TPixel> imageBuffer = frame.PixelBuffer;
113+
int height = frame.Height;
114+
int width = frame.Width;
115115
IMemoryOwner<byte> alphaDataBuffer = memoryAllocator.Allocate<byte>(width * height);
116116
Span<byte> alphaData = alphaDataBuffer.GetSpan();
117117

src/ImageSharp/Formats/Webp/AnimationFrameData.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4+
using SixLabors.ImageSharp.IO;
5+
46
namespace SixLabors.ImageSharp.Formats.Webp;
57

68
internal struct AnimationFrameData
@@ -10,6 +12,11 @@ internal struct AnimationFrameData
1012
/// </summary>
1113
public uint DataSize;
1214

15+
/// <summary>
16+
/// X(3) + Y(3) + Width(3) + Height(3) + Duration(3) + 1 byte for flags.
17+
/// </summary>
18+
public const uint HeaderSize = 16;
19+
1320
/// <summary>
1421
/// The X coordinate of the upper left corner of the frame is Frame X * 2.
1522
/// </summary>
@@ -45,4 +52,40 @@ internal struct AnimationFrameData
4552
/// Indicates how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas.
4653
/// </summary>
4754
public AnimationDisposalMethod DisposalMethod;
55+
56+
/// <summary>
57+
/// Reads the animation frame header.
58+
/// </summary>
59+
/// <param name="stream">The stream to read from.</param>
60+
/// <returns>Animation frame data.</returns>
61+
public static AnimationFrameData Parse(BufferedReadStream stream)
62+
{
63+
Span<byte> buffer = stackalloc byte[4];
64+
65+
AnimationFrameData data = new()
66+
{
67+
DataSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer),
68+
69+
// 3 bytes for the X coordinate of the upper left corner of the frame.
70+
X = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
71+
72+
// 3 bytes for the Y coordinate of the upper left corner of the frame.
73+
Y = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
74+
75+
// Frame width Minus One.
76+
Width = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1,
77+
78+
// Frame height Minus One.
79+
Height = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1,
80+
81+
// Frame duration.
82+
Duration = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer)
83+
};
84+
85+
byte flags = (byte)stream.ReadByte();
86+
data.DisposalMethod = (flags & 1) == 1 ? AnimationDisposalMethod.Dispose : AnimationDisposalMethod.DoNotDispose;
87+
data.BlendingMethod = (flags & (1 << 1)) != 0 ? AnimationBlendingMethod.DoNotBlend : AnimationBlendingMethod.AlphaBlending;
88+
89+
return data;
90+
}
4891
}

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

Lines changed: 63 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Six Labors Split License.
33

44
using System.Buffers.Binary;
5+
using System.Drawing;
56
using System.Runtime.InteropServices;
67
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
78
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
@@ -92,7 +93,7 @@ protected void WriteRiffHeader(Stream stream, uint riffSize)
9293
{
9394
stream.Write(WebpConstants.RiffFourCc);
9495
BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer.Span, riffSize);
95-
stream.Write(this.scratchBuffer.Span.Slice(0, 4));
96+
stream.Write(this.scratchBuffer.Span[..4]);
9697
stream.Write(WebpConstants.WebpHeader);
9798
}
9899

@@ -129,7 +130,7 @@ protected void WriteMetadataProfile(Stream stream, byte[]? metadataBytes, WebpCh
129130
DebugGuard.NotNull(metadataBytes, nameof(metadataBytes));
130131

131132
uint size = (uint)metadataBytes.Length;
132-
Span<byte> buf = this.scratchBuffer.Span.Slice(0, 4);
133+
Span<byte> buf = this.scratchBuffer.Span[..4];
133134
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)chunkType);
134135
stream.Write(buf);
135136
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
@@ -143,6 +144,61 @@ protected void WriteMetadataProfile(Stream stream, byte[]? metadataBytes, WebpCh
143144
}
144145
}
145146

147+
/// <summary>
148+
/// Writes the color profile(<see cref="WebpChunkType.Iccp"/>) to the stream.
149+
/// </summary>
150+
/// <param name="stream">The stream to write to.</param>
151+
/// <param name="iccProfileBytes">The color profile bytes.</param>
152+
protected void WriteColorProfile(Stream stream, byte[] iccProfileBytes) => this.WriteMetadataProfile(stream, iccProfileBytes, WebpChunkType.Iccp);
153+
154+
/// <summary>
155+
/// Writes the animation parameter(<see cref="WebpChunkType.AnimationParameter"/>) to the stream.
156+
/// </summary>
157+
/// <param name="stream">The stream to write to.</param>
158+
/// <param name="background">
159+
/// The default background color of the canvas in [Blue, Green, Red, Alpha] byte order.
160+
/// This color MAY be used to fill the unused space on the canvas around the frames,
161+
/// as well as the transparent pixels of the first frame.
162+
/// The background color is also used when the Disposal method is 1.
163+
/// </param>
164+
/// <param name="loopCount">The number of times to loop the animation. If it is 0, this means infinitely.</param>
165+
protected void WriteAnimationParameter(Stream stream, uint background, ushort loopCount)
166+
{
167+
Span<byte> buf = this.scratchBuffer.Span[..4];
168+
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.AnimationParameter);
169+
stream.Write(buf);
170+
BinaryPrimitives.WriteUInt32LittleEndian(buf, sizeof(uint) + sizeof(ushort));
171+
stream.Write(buf);
172+
BinaryPrimitives.WriteUInt32LittleEndian(buf, background);
173+
stream.Write(buf);
174+
BinaryPrimitives.WriteUInt16LittleEndian(buf[..2], loopCount);
175+
stream.Write(buf[..2]);
176+
}
177+
178+
/// <summary>
179+
/// Writes the animation frame(<see cref="WebpChunkType.Animation"/>) to the stream.
180+
/// </summary>
181+
/// <param name="stream">The stream to write to.</param>
182+
/// <param name="animation">Animation frame data.</param>
183+
/// <param name="data">Frame data.</param>
184+
protected void WriteAnimationFrame(Stream stream, AnimationFrameData animation, byte[] data)
185+
{
186+
uint size = AnimationFrameData.HeaderSize + (uint)data.Length;
187+
Span<byte> buf = this.scratchBuffer.Span[..4];
188+
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Animation);
189+
stream.Write(buf);
190+
BinaryPrimitives.WriteUInt32BigEndian(buf, size);
191+
stream.Write(buf);
192+
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.X);
193+
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Y);
194+
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Width - 1);
195+
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Height - 1);
196+
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Duration);
197+
byte flag = (byte)(((int)animation.BlendingMethod << 1) | (int)animation.DisposalMethod);
198+
stream.WriteByte(flag);
199+
stream.Write(data);
200+
}
201+
146202
/// <summary>
147203
/// Writes the alpha chunk to the stream.
148204
/// </summary>
@@ -152,7 +208,7 @@ protected void WriteMetadataProfile(Stream stream, byte[]? metadataBytes, WebpCh
152208
protected void WriteAlphaChunk(Stream stream, Span<byte> dataBytes, bool alphaDataIsCompressed)
153209
{
154210
uint size = (uint)dataBytes.Length + 1;
155-
Span<byte> buf = this.scratchBuffer.Span.Slice(0, 4);
211+
Span<byte> buf = this.scratchBuffer.Span[..4];
156212
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Alpha);
157213
stream.Write(buf);
158214
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
@@ -161,7 +217,7 @@ protected void WriteAlphaChunk(Stream stream, Span<byte> dataBytes, bool alphaDa
161217
byte flags = 0;
162218
if (alphaDataIsCompressed)
163219
{
164-
flags |= 1;
220+
flags = 1;
165221
}
166222

167223
stream.WriteByte(flags);
@@ -174,30 +230,6 @@ protected void WriteAlphaChunk(Stream stream, Span<byte> dataBytes, bool alphaDa
174230
}
175231
}
176232

177-
/// <summary>
178-
/// Writes the color profile to the stream.
179-
/// </summary>
180-
/// <param name="stream">The stream to write to.</param>
181-
/// <param name="iccProfileBytes">The color profile bytes.</param>
182-
protected void WriteColorProfile(Stream stream, byte[] iccProfileBytes)
183-
{
184-
uint size = (uint)iccProfileBytes.Length;
185-
186-
Span<byte> buf = this.scratchBuffer.Span.Slice(0, 4);
187-
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Iccp);
188-
stream.Write(buf);
189-
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
190-
stream.Write(buf);
191-
192-
stream.Write(iccProfileBytes);
193-
194-
// Add padding byte if needed.
195-
if ((size & 1) == 1)
196-
{
197-
stream.WriteByte(0);
198-
}
199-
}
200-
201233
/// <summary>
202234
/// Writes a VP8X header to the stream.
203235
/// </summary>
@@ -246,8 +278,9 @@ protected void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfi
246278
flags |= 32;
247279
}
248280

249-
Span<byte> buf = this.scratchBuffer.Span.Slice(0, 4);
250-
stream.Write(WebpConstants.Vp8XMagicBytes);
281+
Span<byte> buf = this.scratchBuffer.Span[..4];
282+
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Vp8X);
283+
stream.Write(buf);
251284
BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize);
252285
stream.Write(buf);
253286
BinaryPrimitives.WriteUInt32LittleEndian(buf, flags);

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -701,12 +701,11 @@ private void WriteWebpHeaders(
701701

702702
private void WriteVp8Header(Stream stream, uint size)
703703
{
704-
Span<byte> vp8ChunkHeader = stackalloc byte[WebpConstants.ChunkHeaderSize];
705-
706-
WebpConstants.Vp8MagicBytes.AsSpan().CopyTo(vp8ChunkHeader);
707-
BinaryPrimitives.WriteUInt32LittleEndian(vp8ChunkHeader[4..], size);
708-
709-
stream.Write(vp8ChunkHeader);
704+
Span<byte> buf = stackalloc byte[WebpConstants.TagSize];
705+
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Vp8);
706+
stream.Write(buf);
707+
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
708+
stream.Write(buf);
710709
}
711710

712711
private void WriteFrameHeader(Stream stream, uint size0)

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public Vp8LBitWriter Clone()
105105
{
106106
byte[] clonedBuffer = new byte[this.Buffer.Length];
107107
System.Buffer.BlockCopy(this.Buffer, 0, clonedBuffer, 0, this.cur);
108-
return new Vp8LBitWriter(clonedBuffer, this.bits, this.used, this.cur);
108+
return new(clonedBuffer, this.bits, this.used, this.cur);
109109
}
110110

111111
/// <inheritdoc/>
@@ -186,12 +186,13 @@ public void WriteEncodedImageToStream(Stream stream, ExifProfile? exifProfile, X
186186
}
187187

188188
// Write magic bytes indicating its a lossless webp.
189-
stream.Write(WebpConstants.Vp8LMagicBytes);
189+
Span<byte> scratchBuffer = stackalloc byte[WebpConstants.TagSize];
190+
BinaryPrimitives.WriteUInt32BigEndian(scratchBuffer, (uint)WebpChunkType.Vp8L);
191+
stream.Write(scratchBuffer);
190192

191193
// Write Vp8 Header.
192-
Span<byte> scratchBuffer = stackalloc byte[8];
193194
BinaryPrimitives.WriteUInt32LittleEndian(scratchBuffer, size);
194-
stream.Write(scratchBuffer.Slice(0, 4));
195+
stream.Write(scratchBuffer);
195196
stream.WriteByte(WebpConstants.Vp8LHeaderMagicByte);
196197

197198
// Write the encoded bytes of the image to the stream.
@@ -226,7 +227,7 @@ private void PutBitsFlushBits()
226227

227228
Span<byte> scratchBuffer = stackalloc byte[8];
228229
BinaryPrimitives.WriteUInt64LittleEndian(scratchBuffer, this.bits);
229-
scratchBuffer.Slice(0, 4).CopyTo(this.Buffer.AsSpan(this.cur));
230+
scratchBuffer[..4].CopyTo(this.Buffer.AsSpan(this.cur));
230231

231232
this.cur += WriterBytes;
232233
this.bits >>= WriterBits;

0 commit comments

Comments
 (0)