Browse Source

Add new member to WebpFrameMetadata

pull/2569/head
Poker 2 years ago
parent
commit
b89bc54aa2
No known key found for this signature in database GPG Key ID: C65A6AD457D5C8F8
  1. 8
      src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
  2. 30
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  3. 26
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  4. 29
      src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
  5. 2
      src/ImageSharp/Formats/Webp/WebpBlendingMethod.cs
  6. 16
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
  7. 2
      src/ImageSharp/Formats/Webp/WebpDisposalMethod.cs
  8. 4
      src/ImageSharp/Formats/Webp/WebpEncoder.cs
  9. 8
      src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
  10. 14
      src/ImageSharp/Formats/Webp/WebpFrameData.cs
  11. 19
      src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs
  12. 9
      src/ImageSharp/Formats/Webp/WebpMetadata.cs
  13. 4
      tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs

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

@ -231,14 +231,14 @@ internal abstract class BitWriterBase
/// 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>
public static void WriteAnimationParameter(Stream stream, uint background, ushort loopCount)
public static void WriteAnimationParameter(Stream stream, Color background, ushort loopCount)
{
Span<byte> buf = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.AnimationParameter);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, sizeof(uint) + sizeof(ushort));
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, background);
BinaryPrimitives.WriteUInt32LittleEndian(buf, background.ToRgba32().Rgba);
stream.Write(buf);
BinaryPrimitives.WriteUInt16LittleEndian(buf[..2], loopCount);
stream.Write(buf[..2]);
@ -249,7 +249,7 @@ internal abstract class BitWriterBase
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="animation">Animation frame data.</param>
public static long WriteAnimationFrame(Stream stream, AnimationFrameData animation)
public static long WriteAnimationFrame(Stream stream, WebpFrameData animation)
{
Span<byte> buf = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Animation);
@ -262,6 +262,8 @@ internal abstract class BitWriterBase
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Width - 1);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Height - 1);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Duration);
// TODO: If we can clip the indexed frame for transparent bounds we can set properties here.
byte flag = (byte)(((int)animation.BlendingMethod << 1) | (int)animation.DisposalMethod);
stream.WriteByte(flag);
return position;

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

@ -235,7 +235,7 @@ internal class Vp8LEncoder : IDisposable
/// </summary>
public Vp8LHashChain HashChain { get; }
public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAnimation, uint background = 0, uint loopCount = 0)
public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAnimation)
where TPixel : unmanaged, IPixel<TPixel>
{
// Write bytes from the bitwriter buffer to the stream.
@ -257,7 +257,8 @@ internal class Vp8LEncoder : IDisposable
if (hasAnimation)
{
BitWriterBase.WriteAnimationParameter(stream, background, (ushort)loopCount);
WebpMetadata webpMetadata = metadata.GetWebpMetadata();
BitWriterBase.WriteAnimationParameter(stream, webpMetadata.AnimationBackground, webpMetadata.AnimationLoopCount);
}
}
@ -304,11 +305,14 @@ internal class Vp8LEncoder : IDisposable
if (hasAnimation)
{
prevPosition = BitWriterBase.WriteAnimationFrame(stream, new AnimationFrameData
WebpFrameMetadata frameMetadata = frame.Metadata.GetWebpMetadata();
prevPosition = BitWriterBase.WriteAnimationFrame(stream, new WebpFrameData
{
Width = (uint)frame.Width,
Height = (uint)frame.Height,
Duration = frame.Metadata.GetWebpMetadata().FrameDuration
Duration = frameMetadata.FrameDelay,
BlendingMethod = frameMetadata.BlendMethod,
DisposalMethod = frameMetadata.DisposalMethod
});
}
@ -547,7 +551,7 @@ internal class Vp8LEncoder : IDisposable
EntropyIx entropyIdx = this.AnalyzeEntropy(bgra, width, height, usePalette, this.PaletteSize, this.TransformBits, out redAndBlueAlwaysZero);
bool doNotCache = false;
List<CrunchConfig> crunchConfigs = new List<CrunchConfig>();
List<CrunchConfig> crunchConfigs = new();
if (this.method == WebpEncodingMethod.BestQuality && this.quality == 100)
{
@ -641,8 +645,8 @@ internal class Vp8LEncoder : IDisposable
Vp8LBackwardRefs refsTmp = this.Refs[refsBest.Equals(this.Refs[0]) ? 1 : 0];
this.bitWriter.Reset(bwInit);
Vp8LHistogram tmpHisto = new Vp8LHistogram(cacheBits);
List<Vp8LHistogram> histogramImage = new List<Vp8LHistogram>(histogramImageXySize);
Vp8LHistogram tmpHisto = new(cacheBits);
List<Vp8LHistogram> histogramImage = new(histogramImageXySize);
for (int i = 0; i < histogramImageXySize; i++)
{
histogramImage.Add(new Vp8LHistogram(cacheBits));
@ -839,7 +843,7 @@ internal class Vp8LEncoder : IDisposable
refsTmp1,
refsTmp2);
List<Vp8LHistogram> histogramImage = new List<Vp8LHistogram>
List<Vp8LHistogram> histogramImage = new()
{
new Vp8LHistogram(cacheBits)
};
@ -941,7 +945,7 @@ internal class Vp8LEncoder : IDisposable
int i;
byte[] codeLengthBitDepth = new byte[WebpConstants.CodeLengthCodes];
short[] codeLengthBitDepthSymbols = new short[WebpConstants.CodeLengthCodes];
HuffmanTreeCode huffmanCode = new HuffmanTreeCode
HuffmanTreeCode huffmanCode = new()
{
NumSymbols = WebpConstants.CodeLengthCodes,
CodeLengths = codeLengthBitDepth,
@ -1192,7 +1196,7 @@ internal class Vp8LEncoder : IDisposable
histo[(int)HistoIx.HistoBluePred * 256]++;
histo[(int)HistoIx.HistoAlphaPred * 256]++;
Vp8LBitEntropy bitEntropy = new Vp8LBitEntropy();
Vp8LBitEntropy bitEntropy = new();
for (int j = 0; j < (int)HistoIx.HistoTotal; j++)
{
bitEntropy.Init();
@ -1318,7 +1322,7 @@ internal class Vp8LEncoder : IDisposable
/// <returns>The number of palette entries.</returns>
private static int GetColorPalette(ReadOnlySpan<uint> bgra, int width, int height, Span<uint> palette)
{
HashSet<uint> colors = new HashSet<uint>();
HashSet<uint> colors = new();
for (int y = 0; y < height; y++)
{
ReadOnlySpan<uint> bgraRow = bgra.Slice(y * width, width);
@ -1870,9 +1874,9 @@ internal class Vp8LEncoder : IDisposable
/// </summary>
public void ClearRefs()
{
for (int i = 0; i < this.Refs.Length; i++)
foreach (Vp8LBackwardRefs t in this.Refs)
{
this.Refs[i].Refs.Clear();
t.Refs.Clear();
}
}

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

@ -309,7 +309,7 @@ internal class Vp8Encoder : IDisposable
/// </summary>
private int MbHeaderLimit { get; }
public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAlpha, bool hasAnimation, uint background = 0, uint loopCount = 0)
public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAlpha, bool hasAnimation)
where TPixel : unmanaged, IPixel<TPixel>
{
// Write bytes from the bitwriter buffer to the stream.
@ -331,7 +331,8 @@ internal class Vp8Encoder : IDisposable
if (hasAnimation)
{
BitWriterBase.WriteAnimationParameter(stream, background, (ushort)loopCount);
WebpMetadata webpMetadata = metadata.GetWebpMetadata();
BitWriterBase.WriteAnimationParameter(stream, webpMetadata.AnimationBackground, webpMetadata.AnimationLoopCount);
}
}
@ -395,7 +396,7 @@ internal class Vp8Encoder : IDisposable
int yStride = width;
int uvStride = (yStride + 1) >> 1;
Vp8EncIterator it = new Vp8EncIterator(this);
Vp8EncIterator it = new(this);
Span<int> alphas = stackalloc int[WebpConstants.MaxAlpha + 1];
this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha);
int totalMb = this.Mbw * this.Mbw;
@ -416,8 +417,8 @@ internal class Vp8Encoder : IDisposable
this.StatLoop(width, height, yStride, uvStride);
it.Init();
Vp8EncIterator.InitFilter();
Vp8ModeScore info = new Vp8ModeScore();
Vp8Residual residual = new Vp8Residual();
Vp8ModeScore info = new();
Vp8Residual residual = new();
do
{
bool dontUseSkip = !this.Proba.UseSkipProba;
@ -474,11 +475,14 @@ internal class Vp8Encoder : IDisposable
if (hasAnimation)
{
prevPosition = BitWriterBase.WriteAnimationFrame(stream, new AnimationFrameData
WebpFrameMetadata frameMetadata = frame.Metadata.GetWebpMetadata();
prevPosition = BitWriterBase.WriteAnimationFrame(stream, new WebpFrameData
{
Width = (uint)frame.Width,
Height = (uint)frame.Height,
Duration = frame.Metadata.GetWebpMetadata().FrameDuration
Duration = frameMetadata.FrameDelay,
BlendingMethod = frameMetadata.BlendMethod,
DisposalMethod = frameMetadata.DisposalMethod
});
}
@ -529,7 +533,7 @@ internal class Vp8Encoder : IDisposable
Vp8RdLevel rdOpt = this.method >= WebpEncodingMethod.Level3 || doSearch ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone;
int nbMbs = this.Mbw * this.Mbh;
PassStats stats = new PassStats(targetSize, targetPsnr, QMin, QMax, this.quality);
PassStats stats = new(targetSize, targetPsnr, QMin, QMax, this.quality);
this.Proba.ResetTokenStats();
// Fast mode: quick analysis pass over few mbs. Better than nothing.
@ -597,7 +601,7 @@ internal class Vp8Encoder : IDisposable
Span<byte> y = this.Y.GetSpan();
Span<byte> u = this.U.GetSpan();
Span<byte> v = this.V.GetSpan();
Vp8EncIterator it = new Vp8EncIterator(this);
Vp8EncIterator it = new(this);
long size = 0;
long sizeP0 = 0;
long distortion = 0;
@ -605,7 +609,7 @@ internal class Vp8Encoder : IDisposable
it.Init();
this.SetLoopParams(stats.Q);
Vp8ModeScore info = new Vp8ModeScore();
Vp8ModeScore info = new();
do
{
info.Clear();
@ -1167,7 +1171,7 @@ internal class Vp8Encoder : IDisposable
private void RecordResiduals(Vp8EncIterator it, Vp8ModeScore rd)
{
int x, y, ch;
Vp8Residual residual = new Vp8Residual();
Vp8Residual residual = new();
bool i16 = it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16;
it.NzToBytes();

29
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 = AnimationFrameData.Parse(stream);
WebpFrameData frameData = WebpFrameData.Parse(stream);
long streamStartPosition = stream.Position;
Span<byte> buffer = stackalloc byte[4];
@ -153,7 +153,7 @@ internal class WebpAnimationDecoder : IDisposable
}
WebpImageInfo? webpInfo = null;
WebpFeatures features = new WebpFeatures();
WebpFeatures features = new();
switch (chunkType)
{
case WebpChunkType.Vp8:
@ -180,7 +180,7 @@ internal class WebpAnimationDecoder : IDisposable
{
image = new Image<TPixel>(this.configuration, (int)width, (int)height, backgroundColor.ToPixel<TPixel>(), this.metadata);
SetFrameMetadata(image.Frames.RootFrame.Metadata, frameData.Duration);
SetFrameMetadata(image.Frames.RootFrame.Metadata, frameData);
imageFrame = image.Frames.RootFrame;
}
@ -188,7 +188,7 @@ internal class WebpAnimationDecoder : IDisposable
{
currentFrame = image!.Frames.AddFrame(previousFrame); // This clones the frame and adds it the collection.
SetFrameMetadata(currentFrame.Metadata, frameData.Duration);
SetFrameMetadata(currentFrame.Metadata, frameData);
imageFrame = currentFrame;
}
@ -199,7 +199,7 @@ internal class WebpAnimationDecoder : IDisposable
int frameHeight = (int)frameData.Height;
Rectangle regionRectangle = Rectangle.FromLTRB(frameX, frameY, frameX + frameWidth, frameY + frameHeight);
if (frameData.DisposalMethod is AnimationDisposalMethod.Dispose)
if (frameData.DisposalMethod is WebpDisposalMethod.Dispose)
{
this.RestoreToBackground(imageFrame, backgroundColor);
}
@ -207,7 +207,7 @@ internal class WebpAnimationDecoder : IDisposable
using Buffer2D<TPixel> decodedImage = this.DecodeImageData<TPixel>(frameData, webpInfo);
DrawDecodedImageOnCanvas(decodedImage, imageFrame, frameX, frameY, frameWidth, frameHeight);
if (previousFrame != null && frameData.BlendingMethod is AnimationBlendingMethod.AlphaBlending)
if (previousFrame != null && frameData.BlendingMethod is WebpBlendingMethod.AlphaBlending)
{
this.AlphaBlend(previousFrame, imageFrame, frameX, frameY, frameWidth, frameHeight);
}
@ -222,12 +222,13 @@ internal class WebpAnimationDecoder : IDisposable
/// Sets the frames metadata.
/// </summary>
/// <param name="meta">The metadata.</param>
/// <param name="duration">The frame duration.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void SetFrameMetadata(ImageFrameMetadata meta, uint duration)
/// <param name="frameData">The frame data.</param>
private static void SetFrameMetadata(ImageFrameMetadata meta, WebpFrameData frameData)
{
WebpFrameMetadata frameMetadata = meta.GetWebpMetadata();
frameMetadata.FrameDuration = duration;
frameMetadata.FrameDelay = frameData.Duration;
frameMetadata.BlendMethod = frameData.BlendingMethod;
frameMetadata.DisposalMethod = frameData.DisposalMethod;
}
/// <summary>
@ -256,10 +257,10 @@ internal class WebpAnimationDecoder : IDisposable
/// <param name="frameData">The frame data.</param>
/// <param name="webpInfo">The webp information.</param>
/// <returns>A decoded image.</returns>
private Buffer2D<TPixel> DecodeImageData<TPixel>(AnimationFrameData frameData, WebpImageInfo webpInfo)
private Buffer2D<TPixel> DecodeImageData<TPixel>(WebpFrameData frameData, WebpImageInfo webpInfo)
where TPixel : unmanaged, IPixel<TPixel>
{
Image<TPixel> decodedImage = new Image<TPixel>((int)frameData.Width, (int)frameData.Height);
Image<TPixel> decodedImage = new((int)frameData.Width, (int)frameData.Height);
try
{
@ -267,13 +268,13 @@ internal class WebpAnimationDecoder : IDisposable
if (webpInfo.IsLossless)
{
WebpLosslessDecoder losslessDecoder =
new WebpLosslessDecoder(webpInfo.Vp8LBitReader, this.memoryAllocator, this.configuration);
new(webpInfo.Vp8LBitReader, this.memoryAllocator, this.configuration);
losslessDecoder.Decode(pixelBufferDecoded, (int)webpInfo.Width, (int)webpInfo.Height);
}
else
{
WebpLossyDecoder lossyDecoder =
new WebpLossyDecoder(webpInfo.Vp8BitReader, this.memoryAllocator, this.configuration);
new(webpInfo.Vp8BitReader, this.memoryAllocator, this.configuration);
lossyDecoder.Decode(pixelBufferDecoded, (int)webpInfo.Width, (int)webpInfo.Height, webpInfo, this.alphaData);
}

2
src/ImageSharp/Formats/Webp/AnimationBlendingMethod.cs → src/ImageSharp/Formats/Webp/WebpBlendingMethod.cs

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Webp;
/// <summary>
/// Indicates how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas.
/// </summary>
internal enum AnimationBlendingMethod
public enum WebpBlendingMethod
{
/// <summary>
/// Use alpha blending. After disposing of the previous frame, render the current frame on the canvas using alpha-blending.

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

@ -73,7 +73,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
public DecoderOptions Options { get; }
/// <inheritdoc/>
public Size Dimensions => new Size((int)this.webImageInfo!.Width, (int)this.webImageInfo.Height);
public Size Dimensions => new((int)this.webImageInfo!.Width, (int)this.webImageInfo.Height);
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
@ -82,7 +82,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
Image<TPixel>? image = null;
try
{
ImageMetadata metadata = new ImageMetadata();
ImageMetadata metadata = new();
Span<byte> buffer = stackalloc byte[4];
uint fileSize = ReadImageHeader(stream, buffer);
@ -91,7 +91,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
{
if (this.webImageInfo.Features is { Animation: true })
{
using WebpAnimationDecoder animationDecoder = new WebpAnimationDecoder(
using WebpAnimationDecoder animationDecoder = new(
this.memoryAllocator,
this.configuration,
this.maxFrames,
@ -103,7 +103,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (this.webImageInfo.IsLossless)
{
WebpLosslessDecoder losslessDecoder = new WebpLosslessDecoder(
WebpLosslessDecoder losslessDecoder = new(
this.webImageInfo.Vp8LBitReader,
this.memoryAllocator,
this.configuration);
@ -111,7 +111,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
}
else
{
WebpLossyDecoder lossyDecoder = new WebpLossyDecoder(
WebpLossyDecoder lossyDecoder = new(
this.webImageInfo.Vp8BitReader,
this.memoryAllocator,
this.configuration);
@ -139,7 +139,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
{
ReadImageHeader(stream, stackalloc byte[4]);
ImageMetadata metadata = new ImageMetadata();
ImageMetadata metadata = new();
using (this.webImageInfo = this.ReadVp8Info(stream, metadata, true))
{
return new ImageInfo(
@ -185,7 +185,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
Span<byte> buffer = stackalloc byte[4];
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer);
WebpFeatures features = new WebpFeatures();
WebpFeatures features = new();
switch (chunkType)
{
case WebpChunkType.Vp8:
@ -392,7 +392,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the iccp chunk");
}
IccProfile profile = new IccProfile(iccpData);
IccProfile profile = new(iccpData);
if (profile.CheckIsValid())
{
metadata.IccProfile = profile;

2
src/ImageSharp/Formats/Webp/AnimationDisposalMethod.cs → src/ImageSharp/Formats/Webp/WebpDisposalMethod.cs

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Webp;
/// <summary>
/// Indicates how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas.
/// </summary>
internal enum AnimationDisposalMethod
public enum WebpDisposalMethod
{
/// <summary>
/// Do not dispose. Leave the canvas as is.

4
src/ImageSharp/Formats/Webp/WebpEncoder.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Advanced;
namespace SixLabors.ImageSharp.Formats.Webp;
/// <summary>
@ -82,7 +80,7 @@ public sealed class WebpEncoder : ImageEncoder
/// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
WebpEncoderCore encoder = new WebpEncoderCore(this, image.Configuration);
WebpEncoderCore encoder = new(this, image.Configuration);
encoder.Encode(image, stream, cancellationToken);
}
}

8
src/ImageSharp/Formats/Webp/WebpEncoderCore.cs

@ -129,7 +129,7 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
if (lossless)
{
using Vp8LEncoder encoder = new Vp8LEncoder(
using Vp8LEncoder encoder = new(
this.memoryAllocator,
this.configuration,
image.Width,
@ -147,7 +147,7 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
{
foreach (ImageFrame<TPixel> imageFrame in image.Frames)
{
using Vp8LEncoder enc = new Vp8LEncoder(
using Vp8LEncoder enc = new(
this.memoryAllocator,
this.configuration,
image.Width,
@ -171,7 +171,7 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
}
else
{
using Vp8Encoder encoder = new Vp8Encoder(
using Vp8Encoder encoder = new(
this.memoryAllocator,
this.configuration,
image.Width,
@ -189,7 +189,7 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
foreach (ImageFrame<TPixel> imageFrame in image.Frames)
{
using Vp8Encoder enc = new Vp8Encoder(
using Vp8Encoder enc = new(
this.memoryAllocator,
this.configuration,
image.Width,

14
src/ImageSharp/Formats/Webp/AnimationFrameData.cs → src/ImageSharp/Formats/Webp/WebpFrameData.cs

@ -5,7 +5,7 @@ using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Webp;
internal struct AnimationFrameData
internal struct WebpFrameData
{
/// <summary>
/// The animation chunk size.
@ -46,23 +46,23 @@ internal struct AnimationFrameData
/// <summary>
/// Indicates how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas.
/// </summary>
public AnimationBlendingMethod BlendingMethod;
public WebpBlendingMethod BlendingMethod;
/// <summary>
/// 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;
public WebpDisposalMethod 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)
public static WebpFrameData Parse(BufferedReadStream stream)
{
Span<byte> buffer = stackalloc byte[4];
AnimationFrameData data = new AnimationFrameData
WebpFrameData data = new()
{
DataSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer),
@ -83,8 +83,8 @@ internal struct AnimationFrameData
};
byte flags = (byte)stream.ReadByte();
data.DisposalMethod = (flags & 1) == 1 ? AnimationDisposalMethod.Dispose : AnimationDisposalMethod.DoNotDispose;
data.BlendingMethod = (flags & (1 << 1)) != 0 ? AnimationBlendingMethod.DoNotBlend : AnimationBlendingMethod.AlphaBlending;
data.DisposalMethod = (flags & 1) == 1 ? WebpDisposalMethod.Dispose : WebpDisposalMethod.DoNotDispose;
data.BlendingMethod = (flags & (1 << 1)) != 0 ? WebpBlendingMethod.DoNotBlend : WebpBlendingMethod.AlphaBlending;
return data;
}

19
src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs

@ -19,13 +19,28 @@ public class WebpFrameMetadata : IDeepCloneable
/// Initializes a new instance of the <see cref="WebpFrameMetadata"/> class.
/// </summary>
/// <param name="other">The metadata to create an instance from.</param>
private WebpFrameMetadata(WebpFrameMetadata other) => this.FrameDuration = other.FrameDuration;
private WebpFrameMetadata(WebpFrameMetadata other)
{
this.FrameDelay = other.FrameDelay;
this.DisposalMethod = other.DisposalMethod;
this.BlendMethod = other.BlendMethod;
}
/// <summary>
/// Gets or sets how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas.
/// </summary>
public WebpBlendingMethod BlendMethod { get; set; }
/// <summary>
/// Gets or sets how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas.
/// </summary>
public WebpDisposalMethod DisposalMethod { get; set; }
/// <summary>
/// Gets or sets the frame duration. The time to wait before displaying the next frame,
/// in 1 millisecond units. Note the interpretation of frame duration of 0 (and often smaller and equal to 10) is implementation defined.
/// </summary>
public uint FrameDuration { get; set; }
public uint FrameDelay { get; set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new WebpFrameMetadata(this);

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

@ -23,6 +23,7 @@ public class WebpMetadata : IDeepCloneable
{
this.FileFormat = other.FileFormat;
this.AnimationLoopCount = other.AnimationLoopCount;
this.AnimationBackground = other.AnimationBackground;
}
/// <summary>
@ -35,6 +36,14 @@ public class WebpMetadata : IDeepCloneable
/// </summary>
public ushort AnimationLoopCount { get; set; } = 1;
/// <summary>
/// Gets or sets 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.
/// </summary>
public Color AnimationBackground { get; set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new WebpMetadata(this);
}

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

@ -308,7 +308,7 @@ public class WebpDecoderTests
image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact);
Assert.Equal(0, webpMetaData.AnimationLoopCount);
Assert.Equal(150U, frameMetaData.FrameDuration);
Assert.Equal(150U, frameMetaData.FrameDelay);
Assert.Equal(12, image.Frames.Count);
}
@ -325,7 +325,7 @@ public class WebpDecoderTests
image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Tolerant(0.04f));
Assert.Equal(0, webpMetaData.AnimationLoopCount);
Assert.Equal(150U, frameMetaData.FrameDuration);
Assert.Equal(150U, frameMetaData.FrameDelay);
Assert.Equal(12, image.Frames.Count);
}

Loading…
Cancel
Save