Browse Source

Preparation

pull/2569/head
Poker 3 years ago
parent
commit
6f52a0d13c
No known key found for this signature in database GPG Key ID: C65A6AD457D5C8F8
  1. 42
      src/ImageSharp/Formats/Webp/AlphaEncoder.cs
  2. 43
      src/ImageSharp/Formats/Webp/AnimationFrameData.cs
  3. 93
      src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
  4. 11
      src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
  5. 11
      src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
  6. 80
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  7. 4
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  8. 6
      src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
  9. 42
      src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
  10. 12
      src/ImageSharp/Formats/Webp/WebpAnimationEncoder.cs
  11. 52
      src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
  12. 9
      src/ImageSharp/Formats/Webp/WebpChunkType.cs
  13. 33
      src/ImageSharp/Formats/Webp/WebpConstants.cs
  14. 5
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
  15. 4
      tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs

42
src/ImageSharp/Formats/Webp/AlphaEncoder.cs

@ -19,7 +19,7 @@ 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>
@ -27,7 +27,7 @@ internal static class AlphaEncoder
/// <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,
@ -35,9 +35,9 @@ internal static class AlphaEncoder
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)
{
@ -58,9 +58,9 @@ internal static class AlphaEncoder
// 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;
}
@ -73,45 +73,45 @@ internal static class AlphaEncoder
/// 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();

43
src/ImageSharp/Formats/Webp/AnimationFrameData.cs

@ -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
@ -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>
@ -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
src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs

@ -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;
@ -92,7 +93,7 @@ internal abstract class BitWriterBase
{
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);
}
@ -129,7 +130,7 @@ internal abstract class BitWriterBase
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);
@ -143,6 +144,61 @@ internal abstract class BitWriterBase
}
}
/// <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>
@ -152,7 +208,7 @@ internal abstract class BitWriterBase
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);
@ -161,7 +217,7 @@ internal abstract class BitWriterBase
byte flags = 0;
if (alphaDataIsCompressed)
{
flags |= 1;
flags = 1;
}
stream.WriteByte(flags);
@ -174,30 +230,6 @@ internal abstract class BitWriterBase
}
}
/// <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>
@ -246,8 +278,9 @@ internal abstract class BitWriterBase
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);

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

@ -701,12 +701,11 @@ internal class Vp8BitWriter : BitWriterBase
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)

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

@ -105,7 +105,7 @@ internal class Vp8LBitWriter : BitWriterBase
{
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/>
@ -186,12 +186,13 @@ internal class Vp8LBitWriter : BitWriterBase
}
// 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.
@ -226,7 +227,7 @@ internal class Vp8LBitWriter : BitWriterBase
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;

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

@ -254,7 +254,7 @@ internal class Vp8LEncoder : IDisposable
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
// Convert image pixels to bgra array.
bool hasAlpha = this.ConvertPixelsToBgra(image, width, height);
bool hasAlpha = this.ConvertPixelsToBgra(image.Frames.RootFrame, width, height);
// Write the image size.
this.WriteImageSize(width, height);
@ -263,7 +263,7 @@ internal class Vp8LEncoder : IDisposable
this.WriteAlphaAndVersion(hasAlpha);
// Encode the main image stream.
this.EncodeStream(image);
this.EncodeStream(image.Frames.RootFrame);
// Write bytes from the bitwriter buffer to the stream.
this.bitWriter.WriteEncodedImageToStream(stream, exifProfile, xmpProfile, metadata.IccProfile, (uint)width, (uint)height, hasAlpha);
@ -273,23 +273,23 @@ internal class Vp8LEncoder : IDisposable
/// Encodes the alpha image data using the webp lossless compression.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="frame">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="alphaData">The destination buffer to write the encoded alpha data to.</param>
/// <returns>The size of the compressed data in bytes.
/// If the size of the data is the same as the pixel count, the compression would not yield in smaller data and is left uncompressed.
/// </returns>
public int EncodeAlphaImageData<TPixel>(Image<TPixel> image, IMemoryOwner<byte> alphaData)
public int EncodeAlphaImageData<TPixel>(ImageFrame<TPixel> frame, IMemoryOwner<byte> alphaData)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
int width = frame.Width;
int height = frame.Height;
int pixelCount = width * height;
// Convert image pixels to bgra array.
this.ConvertPixelsToBgra(image, width, height);
this.ConvertPixelsToBgra(frame, width, height);
// The image-stream will NOT contain any headers describing the image dimension, the dimension is already known.
this.EncodeStream(image);
this.EncodeStream(frame);
this.bitWriter.Finish();
int size = this.bitWriter.NumBytes();
if (size >= pixelCount)
@ -333,12 +333,12 @@ internal class Vp8LEncoder : IDisposable
/// Encodes the image stream using lossless webp format.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="image">The image to encode.</param>
private void EncodeStream<TPixel>(Image<TPixel> image)
/// <param name="frame">The frame to encode.</param>
private void EncodeStream<TPixel>(ImageFrame<TPixel> frame)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
int width = frame.Width;
int height = frame.Height;
Span<uint> bgra = this.Bgra.GetSpan();
Span<uint> encodedData = this.EncodedData.GetSpan();
@ -447,14 +447,14 @@ internal class Vp8LEncoder : IDisposable
/// Converts the pixels of the image to bgra.
/// </summary>
/// <typeparam name="TPixel">The type of the pixels.</typeparam>
/// <param name="image">The image to convert.</param>
/// <param name="frame">The frame to convert.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <returns>true, if the image is non opaque.</returns>
private bool ConvertPixelsToBgra<TPixel>(Image<TPixel> image, int width, int height)
private bool ConvertPixelsToBgra<TPixel>(ImageFrame<TPixel> frame, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> imageBuffer = image.Frames.RootFrame.PixelBuffer;
Buffer2D<TPixel> imageBuffer = frame.PixelBuffer;
bool nonOpaque = false;
Span<uint> bgra = this.Bgra.GetSpan();
Span<byte> bgraBytes = MemoryMarshal.Cast<uint, byte>(bgra);
@ -1149,35 +1149,41 @@ internal class Vp8LEncoder : IDisposable
entropyComp[j] = bitEntropy.BitsEntropyRefine();
}
entropy[(int)EntropyIx.Direct] = entropyComp[(int)HistoIx.HistoAlpha] +
entropyComp[(int)HistoIx.HistoRed] +
entropyComp[(int)HistoIx.HistoGreen] +
entropyComp[(int)HistoIx.HistoBlue];
entropy[(int)EntropyIx.Spatial] = entropyComp[(int)HistoIx.HistoAlphaPred] +
entropyComp[(int)HistoIx.HistoRedPred] +
entropyComp[(int)HistoIx.HistoGreenPred] +
entropyComp[(int)HistoIx.HistoBluePred];
entropy[(int)EntropyIx.SubGreen] = entropyComp[(int)HistoIx.HistoAlpha] +
entropyComp[(int)HistoIx.HistoRedSubGreen] +
entropyComp[(int)HistoIx.HistoGreen] +
entropyComp[(int)HistoIx.HistoBlueSubGreen];
entropy[(int)EntropyIx.SpatialSubGreen] = entropyComp[(int)HistoIx.HistoAlphaPred] +
entropyComp[(int)HistoIx.HistoRedPredSubGreen] +
entropyComp[(int)HistoIx.HistoGreenPred] +
entropyComp[(int)HistoIx.HistoBluePredSubGreen];
entropy[(int)EntropyIx.Direct] =
entropyComp[(int)HistoIx.HistoAlpha] +
entropyComp[(int)HistoIx.HistoRed] +
entropyComp[(int)HistoIx.HistoGreen] +
entropyComp[(int)HistoIx.HistoBlue];
entropy[(int)EntropyIx.Spatial] =
entropyComp[(int)HistoIx.HistoAlphaPred] +
entropyComp[(int)HistoIx.HistoRedPred] +
entropyComp[(int)HistoIx.HistoGreenPred] +
entropyComp[(int)HistoIx.HistoBluePred];
entropy[(int)EntropyIx.SubGreen] =
entropyComp[(int)HistoIx.HistoAlpha] +
entropyComp[(int)HistoIx.HistoRedSubGreen] +
entropyComp[(int)HistoIx.HistoGreen] +
entropyComp[(int)HistoIx.HistoBlueSubGreen];
entropy[(int)EntropyIx.SpatialSubGreen] =
entropyComp[(int)HistoIx.HistoAlphaPred] +
entropyComp[(int)HistoIx.HistoRedPredSubGreen] +
entropyComp[(int)HistoIx.HistoGreenPred] +
entropyComp[(int)HistoIx.HistoBluePredSubGreen];
entropy[(int)EntropyIx.Palette] = entropyComp[(int)HistoIx.HistoPalette];
// When including transforms, there is an overhead in bits from
// storing them. This overhead is small but matters for small images.
// For spatial, there are 14 transformations.
entropy[(int)EntropyIx.Spatial] += LosslessUtils.SubSampleSize(width, transformBits) *
LosslessUtils.SubSampleSize(height, transformBits) *
LosslessUtils.FastLog2(14);
entropy[(int)EntropyIx.Spatial] +=
LosslessUtils.SubSampleSize(width, transformBits) *
LosslessUtils.SubSampleSize(height, transformBits) *
LosslessUtils.FastLog2(14);
// For color transforms: 24 as only 3 channels are considered in a ColorTransformElement.
entropy[(int)EntropyIx.SpatialSubGreen] += LosslessUtils.SubSampleSize(width, transformBits) *
LosslessUtils.SubSampleSize(height, transformBits) *
LosslessUtils.FastLog2(24);
entropy[(int)EntropyIx.SpatialSubGreen] +=
LosslessUtils.SubSampleSize(width, transformBits) *
LosslessUtils.SubSampleSize(height, transformBits) *
LosslessUtils.FastLog2(24);
// For palettes, add the cost of storing the palette.
// We empirically estimate the cost of a compressed entry as 8 bits.

4
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs

@ -323,7 +323,7 @@ internal class Vp8Encoder : IDisposable
Span<byte> y = this.Y.GetSpan();
Span<byte> u = this.U.GetSpan();
Span<byte> v = this.V.GetSpan();
bool hasAlpha = YuvConversion.ConvertRgbToYuv(image, this.configuration, this.memoryAllocator, y, u, v);
bool hasAlpha = YuvConversion.ConvertRgbToYuv(image.Frames.RootFrame, this.configuration, this.memoryAllocator, y, u, v);
int yStride = width;
int uvStride = (yStride + 1) >> 1;
@ -393,7 +393,7 @@ internal class Vp8Encoder : IDisposable
{
// TODO: This can potentially run in an separate task.
encodedAlphaData = AlphaEncoder.EncodeAlpha(
image,
image.Frames.RootFrame,
this.configuration,
this.memoryAllocator,
this.skipMetadata,

6
src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs

@ -262,17 +262,17 @@ internal static class YuvConversion
/// Converts the RGB values of the image to YUV.
/// </summary>
/// <typeparam name="TPixel">The pixel type of the image.</typeparam>
/// <param name="image">The image to convert.</param>
/// <param name="frame">The frame to convert.</param>
/// <param name="configuration">The global configuration.</param>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="y">Span to store the luma component of the image.</param>
/// <param name="u">Span to store the u component of the image.</param>
/// <param name="v">Span to store the v component of the image.</param>
/// <returns>true, if the image contains alpha data.</returns>
public static bool ConvertRgbToYuv<TPixel>(Image<TPixel> image, Configuration configuration, MemoryAllocator memoryAllocator, Span<byte> y, Span<byte> u, Span<byte> v)
public static bool ConvertRgbToYuv<TPixel>(ImageFrame<TPixel> frame, Configuration configuration, MemoryAllocator memoryAllocator, Span<byte> y, Span<byte> u, Span<byte> v)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> imageBuffer = image.Frames.RootFrame.PixelBuffer;
Buffer2D<TPixel> imageBuffer = frame.PixelBuffer;
int width = imageBuffer.Width;
int height = imageBuffer.Height;
int uvWidth = (width + 1) >> 1;

42
src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs

@ -138,7 +138,7 @@ internal class WebpAnimationDecoder : IDisposable
private uint ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? image, ref ImageFrame<TPixel>? previousFrame, uint width, uint height, Color backgroundColor)
where TPixel : unmanaged, IPixel<TPixel>
{
AnimationFrameData frameData = this.ReadFrameHeader(stream);
AnimationFrameData frameData = AnimationFrameData.Parse(stream);
long streamStartPosition = stream.Position;
Span<byte> buffer = stackalloc byte[4];
@ -173,7 +173,7 @@ internal class WebpAnimationDecoder : IDisposable
ImageFrame<TPixel> imageFrame;
if (previousFrame is null)
{
image = new Image<TPixel>(this.configuration, (int)width, (int)height, backgroundColor.ToPixel<TPixel>(), this.metadata);
image = new(this.configuration, (int)width, (int)height, backgroundColor.ToPixel<TPixel>(), this.metadata);
SetFrameMetadata(image.Frames.RootFrame.Metadata, frameData.Duration);
@ -258,7 +258,7 @@ internal class WebpAnimationDecoder : IDisposable
try
{
Buffer2D<TPixel> pixelBufferDecoded = decodedImage.Frames.RootFrame.PixelBuffer;
Buffer2D<TPixel> pixelBufferDecoded = decodedImage.GetRootFramePixelBuffer();
if (webpInfo.IsLossless)
{
WebpLosslessDecoder losslessDecoder = new(webpInfo.Vp8LBitReader, this.memoryAllocator, this.configuration);
@ -353,42 +353,6 @@ internal class WebpAnimationDecoder : IDisposable
pixelRegion.Fill(backgroundPixel);
}
/// <summary>
/// Reads the animation frame header.
/// </summary>
/// <param name="stream">The stream to read from.</param>
/// <returns>Animation frame data.</returns>
private AnimationFrameData ReadFrameHeader(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.ReadUnsignedInt24Bit(stream, buffer),
// 3 bytes for the Y coordinate of the upper left corner of the frame.
Y = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, buffer),
// Frame width Minus One.
Width = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, buffer) + 1,
// Frame height Minus One.
Height = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, buffer) + 1,
// Frame duration.
Duration = WebpChunkParsingUtils.ReadUnsignedInt24Bit(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;
}
/// <inheritdoc/>
public void Dispose() => this.alphaData?.Dispose();
}

12
src/ImageSharp/Formats/Webp/WebpAnimationEncoder.cs

@ -0,0 +1,12 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Webp;
/// <summary>
/// Encoder for animated webp images.
/// </summary>
public class WebpAnimationEncoder
{
// 可能不需要这个屌东西
}

52
src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs

@ -2,13 +2,12 @@
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
using System.Drawing;
using SixLabors.ImageSharp.Formats.Webp.BitReader;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
namespace SixLabors.ImageSharp.Formats.Webp;
@ -91,7 +90,7 @@ internal static class WebpChunkParsingUtils
uint tmp = BinaryPrimitives.ReadUInt16LittleEndian(buffer);
uint width = tmp & 0x3fff;
sbyte xScale = (sbyte)(tmp >> 6);
tmp = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(2));
tmp = BinaryPrimitives.ReadUInt16LittleEndian(buffer[2..]);
uint height = tmp & 0x3fff;
sbyte yScale = (sbyte)(tmp >> 6);
remaining -= 7;
@ -105,14 +104,14 @@ internal static class WebpChunkParsingUtils
WebpThrowHelper.ThrowImageFormatException("bad partition length");
}
var vp8FrameHeader = new Vp8FrameHeader()
Vp8FrameHeader vp8FrameHeader = new()
{
KeyFrame = true,
Profile = (sbyte)version,
PartitionLength = partitionLength
};
var bitReader = new Vp8BitReader(
Vp8BitReader bitReader = new(
stream,
remaining,
memoryAllocator,
@ -121,7 +120,7 @@ internal static class WebpChunkParsingUtils
Remaining = remaining
};
return new WebpImageInfo()
return new()
{
Width = width,
Height = height,
@ -145,7 +144,7 @@ internal static class WebpChunkParsingUtils
// VP8 data size.
uint imageDataSize = ReadChunkSize(stream, buffer);
var bitReader = new Vp8LBitReader(stream, imageDataSize, memoryAllocator);
Vp8LBitReader bitReader = new(stream, imageDataSize, memoryAllocator);
// One byte signature, should be 0x2f.
uint signature = bitReader.ReadValue(8);
@ -174,7 +173,7 @@ internal static class WebpChunkParsingUtils
WebpThrowHelper.ThrowNotSupportedException($"Unexpected version number {version} found in VP8L header");
}
return new WebpImageInfo()
return new()
{
Width = width,
Height = height,
@ -231,13 +230,13 @@ internal static class WebpChunkParsingUtils
}
// 3 bytes for the width.
uint width = ReadUnsignedInt24Bit(stream, buffer) + 1;
uint width = ReadUInt24LittleEndian(stream, buffer) + 1;
// 3 bytes for the height.
uint height = ReadUnsignedInt24Bit(stream, buffer) + 1;
uint height = ReadUInt24LittleEndian(stream, buffer) + 1;
// Read all the chunks in the order they occur.
var info = new WebpImageInfo()
WebpImageInfo info = new()
{
Width = width,
Height = height,
@ -253,7 +252,7 @@ internal static class WebpChunkParsingUtils
/// <param name="stream">The stream to read from.</param>
/// <param name="buffer">The buffer to store the read data into.</param>
/// <returns>A unsigned 24 bit integer.</returns>
public static uint ReadUnsignedInt24Bit(BufferedReadStream stream, Span<byte> buffer)
public static uint ReadUInt24LittleEndian(BufferedReadStream stream, Span<byte> buffer)
{
if (stream.Read(buffer, 0, 3) == 3)
{
@ -261,7 +260,28 @@ internal static class WebpChunkParsingUtils
return BinaryPrimitives.ReadUInt32LittleEndian(buffer);
}
throw new ImageFormatException("Invalid Webp data, could not read unsigned integer.");
throw new ImageFormatException("Invalid Webp data, could not read unsigned 24 bit integer.");
}
/// <summary>
/// Writes a unsigned 24 bit integer.
/// </summary>
/// <param name="stream">The stream to read from.</param>
/// <param name="data">The uint24 data to write.</param>
public static unsafe void WriteUInt24LittleEndian(Stream stream, uint data)
{
if (data >= 1 << 24)
{
throw new InvalidDataException($"Invalid data, {data} is not a unsigned 24 bit integer.");
}
uint* ptr = &data;
byte* b = (byte*)ptr;
// Write the data in little endian.
stream.WriteByte(b[0]);
stream.WriteByte(b[1]);
stream.WriteByte(b[2]);
}
/// <summary>
@ -298,7 +318,7 @@ internal static class WebpChunkParsingUtils
if (stream.Read(buffer) == 4)
{
var chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer);
WebpChunkType chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer);
return chunkType;
}
@ -335,7 +355,7 @@ internal static class WebpChunkParsingUtils
if (metadata.ExifProfile != null)
{
metadata.ExifProfile = new ExifProfile(exifData);
metadata.ExifProfile = new(exifData);
}
break;
@ -349,7 +369,7 @@ internal static class WebpChunkParsingUtils
if (metadata.XmpProfile != null)
{
metadata.XmpProfile = new XmpProfile(xmpData);
metadata.XmpProfile = new(xmpData);
}
break;

9
src/ImageSharp/Formats/Webp/WebpChunkType.cs

@ -12,45 +12,54 @@ internal enum WebpChunkType : uint
/// <summary>
/// Header signaling the use of the VP8 format.
/// </summary>
/// <remarks>VP8 (Single)</remarks>
Vp8 = 0x56503820U,
/// <summary>
/// Header signaling the image uses lossless encoding.
/// </summary>
/// <remarks>VP8L (Single)</remarks>
Vp8L = 0x5650384CU,
/// <summary>
/// Header for a extended-VP8 chunk.
/// </summary>
/// <remarks>VP8X (Single)</remarks>
Vp8X = 0x56503858U,
/// <summary>
/// Chunk contains information about the alpha channel.
/// </summary>
/// <remarks>ALPH (Single)</remarks>
Alpha = 0x414C5048U,
/// <summary>
/// Chunk which contains a color profile.
/// </summary>
/// <remarks>ICCP (Single)</remarks>
Iccp = 0x49434350U,
/// <summary>
/// Chunk which contains EXIF metadata about the image.
/// </summary>
/// <remarks>EXIF (Single)</remarks>
Exif = 0x45584946U,
/// <summary>
/// Chunk contains XMP metadata about the image.
/// </summary>
/// <remarks>XMP (Single)</remarks>
Xmp = 0x584D5020U,
/// <summary>
/// For an animated image, this chunk contains the global parameters of the animation.
/// </summary>
/// <remarks>ANIM (Single)</remarks>
AnimationParameter = 0x414E494D,
/// <summary>
/// For animated images, this chunk contains information about a single frame. If the Animation flag is not set, then this chunk SHOULD NOT be present.
/// </summary>
/// <remarks>ANMF (Multiple)</remarks>
Animation = 0x414E4D46,
}

33
src/ImageSharp/Formats/Webp/WebpConstants.cs

@ -33,39 +33,6 @@ internal static class WebpConstants
/// </summary>
public const byte Vp8LHeaderMagicByte = 0x2F;
/// <summary>
/// Signature bytes identifying a lossy image.
/// </summary>
public static readonly byte[] Vp8MagicBytes =
{
0x56, // V
0x50, // P
0x38, // 8
0x20 // ' '
};
/// <summary>
/// Signature bytes identifying a lossless image.
/// </summary>
public static readonly byte[] Vp8LMagicBytes =
{
0x56, // V
0x50, // P
0x38, // 8
0x4C // L
};
/// <summary>
/// Signature bytes identifying a VP8X header.
/// </summary>
public static readonly byte[] Vp8XMagicBytes =
{
0x56, // V
0x50, // P
0x38, // 8
0x58 // X
};
/// <summary>
/// The header bytes identifying RIFF file.
/// </summary>

5
src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

@ -93,11 +93,6 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
return animationDecoder.Decode<TPixel>(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize);
}
if (this.webImageInfo.Features is { Animation: true })
{
WebpThrowHelper.ThrowNotSupportedException("Animations are not supported");
}
image = new Image<TPixel>(this.configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (this.webImageInfo.IsLossless)

4
tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs

@ -143,7 +143,7 @@ public class YuvConversionTests
};
// act
YuvConversion.ConvertRgbToYuv(image, config, memoryAllocator, y, u, v);
YuvConversion.ConvertRgbToYuv(image.Frames.RootFrame, config, memoryAllocator, y, u, v);
// assert
Assert.True(expectedY.AsSpan().SequenceEqual(y));
@ -249,7 +249,7 @@ public class YuvConversionTests
};
// act
YuvConversion.ConvertRgbToYuv(image, config, memoryAllocator, y, u, v);
YuvConversion.ConvertRgbToYuv(image.Frames.RootFrame, config, memoryAllocator, y, u, v);
// assert
Assert.True(expectedY.AsSpan().SequenceEqual(y));

Loading…
Cancel
Save