Skip to content

Commit

Permalink
Add new member to WebpFrameMetadata
Browse files Browse the repository at this point in the history
  • Loading branch information
Poker-sang committed Oct 31, 2023
1 parent 5737e4a commit 6d7c388
Show file tree
Hide file tree
Showing 12 changed files with 100 additions and 67 deletions.
8 changes: 5 additions & 3 deletions src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,14 +231,14 @@ protected static void WriteMetadataProfile(Stream stream, byte[]? metadataBytes,
/// 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]);
Expand All @@ -249,7 +249,7 @@ public static void WriteAnimationParameter(Stream stream, uint background, ushor
/// </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);
Expand All @@ -262,6 +262,8 @@ public static long WriteAnimationFrame(Stream stream, AnimationFrameData animati
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;
Expand Down
30 changes: 17 additions & 13 deletions src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -257,7 +257,8 @@ public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAni

if (hasAnimation)
{
BitWriterBase.WriteAnimationParameter(stream, background, (ushort)loopCount);
WebpMetadata webpMetadata = metadata.GetWebpMetadata();
BitWriterBase.WriteAnimationParameter(stream, webpMetadata.AnimationBackground, webpMetadata.AnimationLoopCount);
}
}

Expand Down Expand Up @@ -304,11 +305,14 @@ public void Encode<TPixel>(ImageFrame<TPixel> frame, Stream stream, bool hasAnim

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
});
}

Expand Down Expand Up @@ -547,7 +551,7 @@ private CrunchConfig[] EncoderAnalyze(ReadOnlySpan<uint> bgra, int width, int he
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)
{
Expand Down Expand Up @@ -641,8 +645,8 @@ private void EncodeImage(int width, int height, bool useCache, CrunchConfig conf
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));
Expand Down Expand Up @@ -839,7 +843,7 @@ private void EncodeImageNoHuffman(Span<uint> bgra, Vp8LHashChain hashChain, Vp8L
refsTmp1,
refsTmp2);

List<Vp8LHistogram> histogramImage = new List<Vp8LHistogram>
List<Vp8LHistogram> histogramImage = new()
{
new Vp8LHistogram(cacheBits)
};
Expand Down Expand Up @@ -941,7 +945,7 @@ private void StoreFullHuffmanCode(Span<HuffmanTree> huffTree, HuffmanTreeToken[]
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,
Expand Down Expand Up @@ -1192,7 +1196,7 @@ private EntropyIx AnalyzeEntropy(ReadOnlySpan<uint> bgra, int width, int height,
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();
Expand Down Expand Up @@ -1318,7 +1322,7 @@ private bool AnalyzeAndCreatePalette(ReadOnlySpan<uint> bgra, int width, int hei
/// <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);
Expand Down Expand Up @@ -1870,9 +1874,9 @@ public void AllocateTransformBuffer(int width, int height)
/// </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();
}
}

Expand Down
26 changes: 15 additions & 11 deletions src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -331,7 +331,8 @@ public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAlp

if (hasAnimation)
{
BitWriterBase.WriteAnimationParameter(stream, background, (ushort)loopCount);
WebpMetadata webpMetadata = metadata.GetWebpMetadata();
BitWriterBase.WriteAnimationParameter(stream, webpMetadata.AnimationBackground, webpMetadata.AnimationLoopCount);
}
}

Expand Down Expand Up @@ -395,7 +396,7 @@ private void Encode<TPixel>(ImageFrame<TPixel> frame, Stream stream, bool hasAni
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;
Expand All @@ -416,8 +417,8 @@ private void Encode<TPixel>(ImageFrame<TPixel> frame, Stream stream, bool hasAni
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;
Expand Down Expand Up @@ -474,11 +475,14 @@ private void Encode<TPixel>(ImageFrame<TPixel> frame, Stream stream, bool hasAni

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
});
}

Expand Down Expand Up @@ -529,7 +533,7 @@ private void StatLoop(int width, int height, int yStride, int uvStride)
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.
Expand Down Expand Up @@ -597,15 +601,15 @@ private long OneStatPass(int width, int height, int yStride, int uvStride, Vp8Rd
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;
long pixelCount = nbMbs * 384;

it.Init();
this.SetLoopParams(stats.Q);
Vp8ModeScore info = new Vp8ModeScore();
Vp8ModeScore info = new();
do
{
info.Clear();
Expand Down Expand Up @@ -1167,7 +1171,7 @@ private void CodeResiduals(Vp8EncIterator it, Vp8ModeScore rd, Vp8Residual resid
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();
Expand Down
29 changes: 15 additions & 14 deletions src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, WebpFeatures feat
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];

Expand All @@ -153,7 +153,7 @@ private uint ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
}

WebpImageInfo? webpInfo = null;
WebpFeatures features = new WebpFeatures();
WebpFeatures features = new();
switch (chunkType)
{
case WebpChunkType.Vp8:
Expand All @@ -180,15 +180,15 @@ private uint ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
{
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;
}
else
{
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;
}
Expand All @@ -199,15 +199,15 @@ private uint ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
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);
}

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);
}
Expand All @@ -222,12 +222,13 @@ private uint ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
/// 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>
Expand Down Expand Up @@ -256,24 +257,24 @@ private byte ReadAlphaData(BufferedReadStream stream)
/// <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
{
Buffer2D<TPixel> pixelBufferDecoded = decodedImage.GetRootFramePixelBuffer();
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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 6d7c388

Please sign in to comment.