diff --git a/src/ImageSharp/Formats/WebP/LoopFilter.cs b/src/ImageSharp/Formats/WebP/LoopFilter.cs
index acf7a1114d..2f67661080 100644
--- a/src/ImageSharp/Formats/WebP/LoopFilter.cs
+++ b/src/ImageSharp/Formats/WebP/LoopFilter.cs
@@ -5,8 +5,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
{
internal enum LoopFilter
{
- Complex,
- Simple,
- None
+ None = 0,
+ Simple = 1,
+ Complex = 2,
}
}
diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs
index 4efa1b4126..303fd1e467 100644
--- a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs
+++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs
@@ -11,6 +11,129 @@ namespace SixLabors.ImageSharp.Formats.WebP
public Vp8Decoder()
{
this.DeQuantMatrices = new Vp8QuantMatrix[WebPConstants.NumMbSegments];
+ this.FilterStrength = new Vp8FilterInfo[WebPConstants.NumMbSegments, 2];
+ }
+
+ public void Init(Vp8Io io)
+ {
+ int extraPixels = WebPConstants.FilterExtraRows[(int)this.Filter];
+ if (this.Filter is LoopFilter.Complex)
+ {
+ // For complex filter, we need to preserve the dependency chain.
+ this.TopLeftMbX = 0;
+ this.TopLeftMbY = 0;
+ }
+ else
+ {
+ // For simple filter, we can filter only the cropped region. We include 'extraPixels' on
+ // the other side of the boundary, since vertical or horizontal filtering of the previous
+ // macroblock can modify some abutting pixels.
+ this.TopLeftMbX = (io.CropLeft - extraPixels) >> 4;
+ this.TopLeftMbY = (io.CropTop - extraPixels) >> 4;
+ if (this.TopLeftMbX < 0)
+ {
+ this.TopLeftMbX = 0;
+ }
+
+ if (this.TopLeftMbY < 0)
+ {
+ this.TopLeftMbY = 0;
+ }
+ }
+
+ // We need some 'extra' pixels on the right/bottom.
+ this.BottomRightMbY = (io.CropBottom + 15 + extraPixels) >> 4;
+ this.BotomRightMbX = (io.CropRight + 15 + extraPixels) >> 4;
+ if (this.BotomRightMbX > this.MbWidth)
+ {
+ this.BotomRightMbX = this.MbWidth;
+ }
+
+ if (this.BottomRightMbY > this.MbHeight)
+ {
+ this.BottomRightMbY = this.MbHeight;
+ }
+
+ this.PrecomputeFilterStrengths();
+ }
+
+ private void PrecomputeFilterStrengths()
+ {
+ if (this.Filter is LoopFilter.None)
+ {
+ return;
+ }
+
+ Vp8FilterHeader hdr = this.FilterHeader;
+ for (int s = 0; s < WebPConstants.NumMbSegments; ++s)
+ {
+ int baseLevel;
+
+ // First, compute the initial level
+ if (this.SegmentHeader.UseSegment)
+ {
+ baseLevel = this.SegmentHeader.FilterStrength[s];
+ if (!this.SegmentHeader.Delta)
+ {
+ baseLevel += hdr.Level;
+ }
+ }
+ else
+ {
+ baseLevel = hdr.Level;
+ }
+
+ for (int i4x4 = 0; i4x4 <= 1; ++i4x4)
+ {
+ Vp8FilterInfo info = this.FilterStrength[s, i4x4];
+ int level = baseLevel;
+ if (hdr.UseLfDelta)
+ {
+ level += hdr.RefLfDelta[0];
+ if (i4x4 > 0)
+ {
+ level += hdr.ModeLfDelta[0];
+ }
+ }
+
+ level = (level < 0) ? 0 : (level > 63) ? 63 : level;
+ if (level > 0)
+ {
+ int iLevel = level;
+ if (hdr.Sharpness > 0)
+ {
+ if (hdr.Sharpness > 4)
+ {
+ iLevel >>= 2;
+ }
+ else
+ {
+ iLevel >>= 1;
+ }
+
+ if (iLevel > 9 - hdr.Sharpness)
+ {
+ iLevel = 9 - hdr.Sharpness;
+ }
+ }
+
+ if (iLevel < 1)
+ {
+ iLevel = 1;
+ }
+
+ info.InnerLevel = (byte)iLevel;
+ info.Limit = (byte)((2 * level) + iLevel);
+ info.HighEdgeVarianceThreshold = (byte)((level >= 40) ? 2 : (level >= 15) ? 1 : 0);
+ }
+ else
+ {
+ info.Limit = 0; // no filtering
+ }
+
+ info.InnerLevel = (byte)i4x4;
+ }
+ }
}
public Vp8FrameHeader FrameHeader { get; set; }
@@ -28,6 +151,10 @@ namespace SixLabors.ImageSharp.Formats.WebP
///
public Vp8QuantMatrix[] DeQuantMatrices { get; private set; }
+ public bool UseSkipProba { get; set; }
+
+ public byte SkipProbability { get; set; }
+
public Vp8Proba Probabilities { get; set; }
///
@@ -40,6 +167,26 @@ namespace SixLabors.ImageSharp.Formats.WebP
///
public int MbHeight { get; set; }
+ ///
+ /// Gets or sets the top-left x index of the macroblock that must be in-loop filtered.
+ ///
+ public int TopLeftMbX { get; set; }
+
+ ///
+ /// Gets or sets the top-left y index of the macroblock that must be in-loop filtered.
+ ///
+ public int TopLeftMbY { get; set; }
+
+ ///
+ /// Gets or sets the last bottom-right x index of the macroblock that must be decoded.
+ ///
+ public int BotomRightMbX { get; set; }
+
+ ///
+ /// Gets or sets the last bottom-right y index of the macroblock that must be decoded.
+ ///
+ public int BottomRightMbY { get; set; }
+
///
/// Gets or sets the current x position in macroblock units.
///
@@ -60,6 +207,12 @@ namespace SixLabors.ImageSharp.Formats.WebP
///
public Vp8MacroBlock[] MacroBlockInfo { get; set; }
+ public int MacroBlockPos { get; set; }
+
+ public LoopFilter Filter { get; set; }
+
+ public Vp8FilterInfo[,] FilterStrength { get; }
+
///
/// Gets or sets filter strength info.
///
diff --git a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs
index 0cfaf1e041..4204386bc8 100644
--- a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs
+++ b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs
@@ -11,12 +11,12 @@ namespace SixLabors.ImageSharp.Formats.WebP
///
/// Gets or sets the filter limit in [3..189], or 0 if no filtering.
///
- public sbyte Limit { get; set; }
+ public byte Limit { get; set; }
///
/// Gets or sets the inner limit in [1..63].
///
- public sbyte InnerLevel { get; set; }
+ public byte InnerLevel { get; set; }
///
/// Gets or sets a value indicating whether to do inner filtering.
@@ -26,6 +26,6 @@ namespace SixLabors.ImageSharp.Formats.WebP
///
/// Gets or sets the high edge variance threshold in [0..2].
///
- public sbyte HighEdgeVarianceThreshold { get; set; }
+ public byte HighEdgeVarianceThreshold { get; set; }
}
}
diff --git a/src/ImageSharp/Formats/WebP/Vp8Io.cs b/src/ImageSharp/Formats/WebP/Vp8Io.cs
index f1ab5c7493..dfcf73a486 100644
--- a/src/ImageSharp/Formats/WebP/Vp8Io.cs
+++ b/src/ImageSharp/Formats/WebP/Vp8Io.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -62,6 +62,14 @@ namespace SixLabors.ImageSharp.Formats.WebP
///
public int UvStride { get; set; }
+ public int CropLeft { get; set; }
+
+ public int CropRight { get; set; }
+
+ public int CropTop { get; set; }
+
+ public int CropBottom { get; set; }
+
///
/// User data
///
diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs
index fd6fb1d252..5db5a0bbc8 100644
--- a/src/ImageSharp/Formats/WebP/WebPConstants.cs
+++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs
@@ -121,6 +121,13 @@ namespace SixLabors.ImageSharp.Formats.WebP
public const int NumCtx = 3;
+ ///
+ /// How many extra lines are needed on the MB boundary for caching, given a filtering level.
+ /// Simple filter: up to 2 luma samples are read and 1 is written.
+ /// Complex filter: up to 4 luma samples are read and 3 are written. Same for U/V, so it's 8 samples total (because of the 2x upsampling).
+ ///
+ public static readonly byte[] FilterExtraRows = { 0, 2, 8 };
+
// Paragraph 9.9
public static readonly int[] Bands =
{
diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs
index f5a39c0167..364b4b3be1 100644
--- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs
+++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs
@@ -79,6 +79,33 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
+ private void DecodeMacroBlock(Vp8Decoder dec)
+ {
+ Vp8MacroBlock left = dec.MacroBlockInfo[dec.MacroBlockPos - 1]; // TODO: not sure if this - 1 is correct here
+ Vp8MacroBlock macroBlock = dec.MacroBlockInfo[dec.MacroBlockPos + dec.MbX];
+ Vp8MacroBlockData blockData = dec.MacroBlockData[dec.MacroBlockPos + dec.MbX];
+ int skip = dec.UseSkipProba ? blockData.Skip : 0;
+
+ if (skip is 0)
+ {
+ this.ParseResiduals(dec, macroBlock);
+ }
+ else
+ {
+ left.NoneZeroAcDcCoeffs = macroBlock.NoneZeroAcDcCoeffs = 0;
+ if (blockData.IsI4x4)
+ {
+ left.NoneZeroDcCoeffs = macroBlock.NoneZeroDcCoeffs = 0;
+ }
+
+ blockData.NonZeroY = 0;
+ blockData.NonZeroUv = 0;
+ blockData.Dither = 0;
+ }
+
+ // TODO: store filter info
+ }
+
private bool ParseResiduals(Vp8Decoder decoder, Vp8MacroBlock mb)
{
byte tnz, lnz;