Skip to content

Commit

Permalink
Preparation
Browse files Browse the repository at this point in the history
  • Loading branch information
Poker-sang committed Oct 22, 2023
1 parent d93bc6c commit 6f52a0d
Show file tree
Hide file tree
Showing 15 changed files with 248 additions and 199 deletions.
42 changes: 21 additions & 21 deletions src/ImageSharp/Formats/Webp/AlphaEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,25 @@ internal static class AlphaEncoder
/// Data is either compressed as lossless webp image or uncompressed.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="frame">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="configuration">The global configuration.</param>
/// <param name="memoryAllocator">The memory manager.</param>
/// <param name="skipMetadata">Whether to skip metadata encoding.</param>
/// <param name="compress">Indicates, if the data should be compressed with the lossless webp compression.</param>
/// <param name="size">The size in bytes of the alpha data.</param>
/// <returns>The encoded alpha data.</returns>
public static IMemoryOwner<byte> EncodeAlpha<TPixel>(
Image<TPixel> image,
ImageFrame<TPixel> frame,
Configuration configuration,
MemoryAllocator memoryAllocator,
bool skipMetadata,
bool compress,
out int size)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
IMemoryOwner<byte> alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator);
int width = frame.Width;
int height = frame.Height;
IMemoryOwner<byte> alphaData = ExtractAlphaChannel(frame, configuration, memoryAllocator);

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

size = lossLessEncoder.EncodeAlphaImageData(alphaAsImage, alphaData);
size = lossLessEncoder.EncodeAlphaImageData(alphaAsFrame, alphaData);

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

for (int y = 0; y < height; y++)
{
Memory<Rgba32> rowBuffer = alphaAsImage.DangerousGetPixelRowMemory(y);
Memory<Rgba32> rowBuffer = alphaAsFrame.DangerousGetPixelRowMemory(y);
Span<Rgba32> pixelRow = rowBuffer.Span;
Span<byte> alphaRow = alphaData.Slice(y * width, width);
for (int x = 0; x < width; x++)
{
// Leave A/R/B channels zero'd.
pixelRow[x] = new Rgba32(0, alphaRow[x], 0, 0);
pixelRow[x] = new(0, alphaRow[x], 0, 0);
}
}

return alphaAsImage;
return alphaAsFrame;
}

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

Expand Down
43 changes: 43 additions & 0 deletions src/ImageSharp/Formats/Webp/AnimationFrameData.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

using SixLabors.ImageSharp.IO;

namespace SixLabors.ImageSharp.Formats.Webp;

internal struct AnimationFrameData
Expand All @@ -10,6 +12,11 @@ internal struct AnimationFrameData
/// </summary>
public uint DataSize;

/// <summary>
/// X(3) + Y(3) + Width(3) + Height(3) + Duration(3) + 1 byte for flags.
/// </summary>
public const uint HeaderSize = 16;

/// <summary>
/// The X coordinate of the upper left corner of the frame is Frame X * 2.
/// </summary>
Expand Down Expand Up @@ -45,4 +52,40 @@ internal struct AnimationFrameData
/// Indicates how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas.
/// </summary>
public AnimationDisposalMethod DisposalMethod;

/// <summary>
/// Reads the animation frame header.
/// </summary>
/// <param name="stream">The stream to read from.</param>
/// <returns>Animation frame data.</returns>
public static AnimationFrameData Parse(BufferedReadStream stream)
{
Span<byte> buffer = stackalloc byte[4];

AnimationFrameData data = new()
{
DataSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer),

// 3 bytes for the X coordinate of the upper left corner of the frame.
X = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),

// 3 bytes for the Y coordinate of the upper left corner of the frame.
Y = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),

// Frame width Minus One.
Width = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1,

// Frame height Minus One.
Height = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1,

// Frame duration.
Duration = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer)
};

byte flags = (byte)stream.ReadByte();
data.DisposalMethod = (flags & 1) == 1 ? AnimationDisposalMethod.Dispose : AnimationDisposalMethod.DoNotDispose;
data.BlendingMethod = (flags & (1 << 1)) != 0 ? AnimationBlendingMethod.DoNotBlend : AnimationBlendingMethod.AlphaBlending;

return data;
}
}
93 changes: 63 additions & 30 deletions src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.

using System.Buffers.Binary;
using System.Drawing;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
Expand Down Expand Up @@ -92,7 +93,7 @@ protected void WriteRiffHeader(Stream stream, uint riffSize)
{
stream.Write(WebpConstants.RiffFourCc);
BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer.Span, riffSize);
stream.Write(this.scratchBuffer.Span.Slice(0, 4));
stream.Write(this.scratchBuffer.Span[..4]);
stream.Write(WebpConstants.WebpHeader);
}

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

uint size = (uint)metadataBytes.Length;
Span<byte> buf = this.scratchBuffer.Span.Slice(0, 4);
Span<byte> buf = this.scratchBuffer.Span[..4];
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)chunkType);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
Expand All @@ -143,6 +144,61 @@ protected void WriteMetadataProfile(Stream stream, byte[]? metadataBytes, WebpCh
}
}

/// <summary>
/// Writes the color profile(<see cref="WebpChunkType.Iccp"/>) to the stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="iccProfileBytes">The color profile bytes.</param>
protected void WriteColorProfile(Stream stream, byte[] iccProfileBytes) => this.WriteMetadataProfile(stream, iccProfileBytes, WebpChunkType.Iccp);

/// <summary>
/// Writes the animation parameter(<see cref="WebpChunkType.AnimationParameter"/>) to the stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="background">
/// The default background color of the canvas in [Blue, Green, Red, Alpha] byte order.
/// This color MAY be used to fill the unused space on the canvas around the frames,
/// as well as the transparent pixels of the first frame.
/// The background color is also used when the Disposal method is 1.
/// </param>
/// <param name="loopCount">The number of times to loop the animation. If it is 0, this means infinitely.</param>
protected void WriteAnimationParameter(Stream stream, uint background, ushort loopCount)
{
Span<byte> buf = this.scratchBuffer.Span[..4];
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.AnimationParameter);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, sizeof(uint) + sizeof(ushort));
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, background);
stream.Write(buf);
BinaryPrimitives.WriteUInt16LittleEndian(buf[..2], loopCount);
stream.Write(buf[..2]);
}

/// <summary>
/// Writes the animation frame(<see cref="WebpChunkType.Animation"/>) to the stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="animation">Animation frame data.</param>
/// <param name="data">Frame data.</param>
protected void WriteAnimationFrame(Stream stream, AnimationFrameData animation, byte[] data)
{
uint size = AnimationFrameData.HeaderSize + (uint)data.Length;
Span<byte> buf = this.scratchBuffer.Span[..4];
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Animation);
stream.Write(buf);
BinaryPrimitives.WriteUInt32BigEndian(buf, size);
stream.Write(buf);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.X);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Y);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Width - 1);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Height - 1);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Duration);
byte flag = (byte)(((int)animation.BlendingMethod << 1) | (int)animation.DisposalMethod);
stream.WriteByte(flag);
stream.Write(data);
}

/// <summary>
/// Writes the alpha chunk to the stream.
/// </summary>
Expand All @@ -152,7 +208,7 @@ protected void WriteMetadataProfile(Stream stream, byte[]? metadataBytes, WebpCh
protected void WriteAlphaChunk(Stream stream, Span<byte> dataBytes, bool alphaDataIsCompressed)
{
uint size = (uint)dataBytes.Length + 1;
Span<byte> buf = this.scratchBuffer.Span.Slice(0, 4);
Span<byte> buf = this.scratchBuffer.Span[..4];
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Alpha);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
Expand All @@ -161,7 +217,7 @@ protected void WriteAlphaChunk(Stream stream, Span<byte> dataBytes, bool alphaDa
byte flags = 0;
if (alphaDataIsCompressed)
{
flags |= 1;
flags = 1;
}

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

/// <summary>
/// Writes the color profile to the stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="iccProfileBytes">The color profile bytes.</param>
protected void WriteColorProfile(Stream stream, byte[] iccProfileBytes)
{
uint size = (uint)iccProfileBytes.Length;

Span<byte> buf = this.scratchBuffer.Span.Slice(0, 4);
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Iccp);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
stream.Write(buf);

stream.Write(iccProfileBytes);

// Add padding byte if needed.
if ((size & 1) == 1)
{
stream.WriteByte(0);
}
}

/// <summary>
/// Writes a VP8X header to the stream.
/// </summary>
Expand Down Expand Up @@ -246,8 +278,9 @@ protected void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfi
flags |= 32;
}

Span<byte> buf = this.scratchBuffer.Span.Slice(0, 4);
stream.Write(WebpConstants.Vp8XMagicBytes);
Span<byte> buf = this.scratchBuffer.Span[..4];
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Vp8X);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, flags);
Expand Down
11 changes: 5 additions & 6 deletions src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -701,12 +701,11 @@ private void WriteWebpHeaders(

private void WriteVp8Header(Stream stream, uint size)
{
Span<byte> vp8ChunkHeader = stackalloc byte[WebpConstants.ChunkHeaderSize];

WebpConstants.Vp8MagicBytes.AsSpan().CopyTo(vp8ChunkHeader);
BinaryPrimitives.WriteUInt32LittleEndian(vp8ChunkHeader[4..], size);

stream.Write(vp8ChunkHeader);
Span<byte> buf = stackalloc byte[WebpConstants.TagSize];
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Vp8);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
stream.Write(buf);
}

private void WriteFrameHeader(Stream stream, uint size0)
Expand Down
11 changes: 6 additions & 5 deletions src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public Vp8LBitWriter Clone()
{
byte[] clonedBuffer = new byte[this.Buffer.Length];
System.Buffer.BlockCopy(this.Buffer, 0, clonedBuffer, 0, this.cur);
return new Vp8LBitWriter(clonedBuffer, this.bits, this.used, this.cur);
return new(clonedBuffer, this.bits, this.used, this.cur);
}

/// <inheritdoc/>
Expand Down Expand Up @@ -186,12 +186,13 @@ public void WriteEncodedImageToStream(Stream stream, ExifProfile? exifProfile, X
}

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

// Write Vp8 Header.
Span<byte> scratchBuffer = stackalloc byte[8];
BinaryPrimitives.WriteUInt32LittleEndian(scratchBuffer, size);
stream.Write(scratchBuffer.Slice(0, 4));
stream.Write(scratchBuffer);
stream.WriteByte(WebpConstants.Vp8LHeaderMagicByte);

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

Span<byte> scratchBuffer = stackalloc byte[8];
BinaryPrimitives.WriteUInt64LittleEndian(scratchBuffer, this.bits);
scratchBuffer.Slice(0, 4).CopyTo(this.Buffer.AsSpan(this.cur));
scratchBuffer[..4].CopyTo(this.Buffer.AsSpan(this.cur));

this.cur += WriterBytes;
this.bits >>= WriterBits;
Expand Down
Loading

0 comments on commit 6f52a0d

Please sign in to comment.