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;