Browse Source

Enable macroblock filtering

pull/1552/head
Brian Popow 6 years ago
parent
commit
1dc01205e9
  1. 4
      src/ImageSharp/Formats/WebP/Vp8Decoder.cs
  2. 58
      src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs
  3. 1
      tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs
  4. 42
      tests/Images/Input/WebP/lossless_vec_list.txt

4
src/ImageSharp/Formats/WebP/Vp8Decoder.cs

@ -170,6 +170,10 @@ namespace SixLabors.ImageSharp.Formats.WebP
public byte[] CacheV { get; } public byte[] CacheV { get; }
public int CacheYOffset { get; set; }
public int CacheUvOffset { get; set; }
public int CacheYStride { get; } public int CacheYStride { get; }
public int CacheUvStride { get; } public int CacheUvStride { get; }

58
src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs

@ -201,15 +201,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
private void ProcessRow(Vp8Decoder dec, Vp8Io io) private void ProcessRow(Vp8Decoder dec, Vp8Io io)
{ {
bool filterRow = (dec.Filter != LoopFilter.None) &&
(dec.MbY >= dec.TopLeftMbY) && (dec.MbY <= dec.BottomRightMbY);
this.ReconstructRow(dec); this.ReconstructRow(dec);
if (filterRow)
{
this.FilterRow(dec);
}
this.FinishRow(dec, io); this.FinishRow(dec, io);
} }
@ -455,12 +447,9 @@ namespace SixLabors.ImageSharp.Formats.WebP
} }
// Transfer reconstructed samples from yuv_buffer cache to final destination. // Transfer reconstructed samples from yuv_buffer cache to final destination.
int cacheId = 0; // TODO: what should be cacheId, always 0? Span<byte> yOut = dec.CacheY.AsSpan(dec.CacheYOffset + (mbx * 16));
int yOffset = cacheId * 16 * dec.CacheYStride; Span<byte> uOut = dec.CacheU.AsSpan(dec.CacheUvOffset + (mbx * 8));
int uvOffset = cacheId * 8 * dec.CacheUvStride; Span<byte> vOut = dec.CacheV.AsSpan(dec.CacheUvOffset + (mbx * 8));
Span<byte> yOut = dec.CacheY.AsSpan((mbx * 16) + yOffset);
Span<byte> uOut = dec.CacheU.AsSpan((mbx * 8) + uvOffset);
Span<byte> vOut = dec.CacheV.AsSpan((mbx * 8) + uvOffset);
for (int j = 0; j < 16; ++j) for (int j = 0; j < 16; ++j)
{ {
yDst.Slice(j * WebPConstants.Bps, 16).CopyTo(yOut.Slice(j * dec.CacheYStride)); yDst.Slice(j * WebPConstants.Bps, 16).CopyTo(yOut.Slice(j * dec.CacheYStride));
@ -479,7 +468,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
int mby = dec.MbY; int mby = dec.MbY;
for (int mbx = dec.TopLeftMbX; mbx < dec.BottomRightMbX; ++mbx) for (int mbx = dec.TopLeftMbX; mbx < dec.BottomRightMbX; ++mbx)
{ {
//this.DoFilter(dec, mbx, mby); this.DoFilter(dec, mbx, mby);
} }
} }
@ -497,7 +486,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
if (dec.Filter is LoopFilter.Simple) if (dec.Filter is LoopFilter.Simple)
{ {
int offset = mbx * 16; int offset = dec.CacheYOffset + (mbx * 16);
if (mbx > 0) if (mbx > 0)
{ {
LossyUtils.SimpleHFilter16(dec.CacheY, offset, yBps, limit + 4); LossyUtils.SimpleHFilter16(dec.CacheY, offset, yBps, limit + 4);
@ -521,8 +510,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
else if (dec.Filter is LoopFilter.Complex) else if (dec.Filter is LoopFilter.Complex)
{ {
int uvBps = dec.CacheUvStride; int uvBps = dec.CacheUvStride;
int yOffset = mbx * 16; int yOffset = dec.CacheYOffset + (mbx * 16);
int uvOffset = mbx * 8; int uvOffset = dec.CacheUvOffset + (mbx * 8);
int hevThresh = filterInfo.HighEdgeVarianceThreshold; int hevThresh = filterInfo.HighEdgeVarianceThreshold;
if (mbx > 0) if (mbx > 0)
{ {
@ -552,22 +541,22 @@ namespace SixLabors.ImageSharp.Formats.WebP
private void FinishRow(Vp8Decoder dec, Vp8Io io) private void FinishRow(Vp8Decoder dec, Vp8Io io)
{ {
int cacheId = 0;
int yBps = dec.CacheYStride;
int extraYRows = WebPConstants.FilterExtraRows[(int)dec.Filter]; int extraYRows = WebPConstants.FilterExtraRows[(int)dec.Filter];
int ySize = extraYRows * dec.CacheYStride; int ySize = extraYRows * dec.CacheYStride;
int uvSize = (extraYRows / 2) * dec.CacheUvStride; int uvSize = (extraYRows / 2) * dec.CacheUvStride;
int yOffset = cacheId * 16 * dec.CacheYStride;
int uvOffset = cacheId * 8 * dec.CacheUvStride;
Span<byte> yDst = dec.CacheY.AsSpan(); Span<byte> yDst = dec.CacheY.AsSpan();
Span<byte> uDst = dec.CacheU.AsSpan(); Span<byte> uDst = dec.CacheU.AsSpan();
Span<byte> vDst = dec.CacheV.AsSpan(); Span<byte> vDst = dec.CacheV.AsSpan();
int mby = dec.MbY; int mby = dec.MbY;
bool isFirstRow = mby is 0; bool isFirstRow = mby is 0;
bool isLastRow = mby >= dec.BottomRightMbY - 1; bool isLastRow = mby >= dec.BottomRightMbY - 1;
bool filterRow = (dec.Filter != LoopFilter.None) &&
(dec.MbY >= dec.TopLeftMbY) && (dec.MbY <= dec.BottomRightMbY);
// TODO: Filter row if (filterRow)
//FilterRow(dec); {
this.FilterRow(dec);
}
int yStart = mby * 16; int yStart = mby * 16;
int yEnd = (mby + 1) * 16; int yEnd = (mby + 1) * 16;
@ -580,9 +569,9 @@ namespace SixLabors.ImageSharp.Formats.WebP
} }
else else
{ {
io.Y = dec.CacheY.AsSpan(yOffset); io.Y = dec.CacheY.AsSpan(dec.CacheYOffset);
io.U = dec.CacheU.AsSpan(uvOffset); io.U = dec.CacheU.AsSpan(dec.CacheUvOffset);
io.V = dec.CacheV.AsSpan(uvOffset); io.V = dec.CacheV.AsSpan(dec.CacheUvOffset);
} }
if (!isLastRow) if (!isLastRow)
@ -605,10 +594,9 @@ namespace SixLabors.ImageSharp.Formats.WebP
// Rotate top samples if needed. // Rotate top samples if needed.
if (!isLastRow) if (!isLastRow)
{ {
// TODO: double check this. Cache needs extra rows for filtering! yDst.Slice(16 * dec.CacheYStride, ySize).CopyTo(dec.CacheY.AsSpan());
//yDst.Slice(16 * dec.CacheYStride, ySize).CopyTo(dec.CacheY); uDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheU.AsSpan());
//uDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheU); vDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheV.AsSpan());
//vDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheV);
} }
} }
@ -1125,7 +1113,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
return vp8SegmentHeader; return vp8SegmentHeader;
} }
private Vp8FilterHeader ParseFilterHeader(Vp8Decoder dec) private void ParseFilterHeader(Vp8Decoder dec)
{ {
Vp8FilterHeader vp8FilterHeader = dec.FilterHeader; Vp8FilterHeader vp8FilterHeader = dec.FilterHeader;
vp8FilterHeader.LoopFilter = this.bitReader.ReadBool() ? LoopFilter.Simple : LoopFilter.Complex; vp8FilterHeader.LoopFilter = this.bitReader.ReadBool() ? LoopFilter.Simple : LoopFilter.Complex;
@ -1160,7 +1148,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
} }
} }
return vp8FilterHeader; int extraRows = WebPConstants.FilterExtraRows[(int)dec.Filter];
int extraY = extraRows * dec.CacheYStride;
int extraUv = (extraRows / 2) * dec.CacheUvStride;
dec.CacheYOffset = extraY;
dec.CacheUvOffset = extraUv;
} }
private void ParsePartitions(Vp8Decoder dec) private void ParsePartitions(Vp8Decoder dec)

1
tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs

@ -81,6 +81,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP
[WithFile(Lossy.VeryShort, PixelTypes.Rgba32)] [WithFile(Lossy.VeryShort, PixelTypes.Rgba32)]
[WithFile(Lossy.BikeComplexFilter, PixelTypes.Rgba32)] [WithFile(Lossy.BikeComplexFilter, PixelTypes.Rgba32)]
[WithFile(Lossy.ComplexFilter01, PixelTypes.Rgba32)] [WithFile(Lossy.ComplexFilter01, PixelTypes.Rgba32)]
[WithFile(Lossy.ComplexFilter02, PixelTypes.Rgba32)]
public void WebpDecoder_CanDecode_Lossy_WithComplexFilter<TPixel>(TestImageProvider<TPixel> provider) public void WebpDecoder_CanDecode_Lossy_WithComplexFilter<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {

42
tests/Images/Input/WebP/lossless_vec_list.txt

@ -1,42 +0,0 @@
List of features used in each test vector.
All the 'lossless_vec_1_*.webp' WebP files should decode to an image comparable to equivalently 'grid.png'.
This synthetic picture is made of 16x16 grid-alternating pixels with RGBA values equal to
blue B=(0,0,255,255) and half-transparent red R=(255,0,0,128), according to
the pattern:
BRBRBRBRBRBRBRBR
RBRBRBRBRBRBRBRB
BRBRBRBRBRBRBRBR
RBRBRBRBRBRBRBRB
BRBRBRBRBRBRBRBR
RBRBRBRBRBRBRBRB
BRBRBRBRBRBRBRBR
RBRBRBRBRBRBRBRB
BRBRBRBRBRBRBRBR
RBRBRBRBRBRBRBRB
BRBRBRBRBRBRBRBR
RBRBRBRBRBRBRBRB
BRBRBRBRBRBRBRBR
RBRBRBRBRBRBRBRB
BRBRBRBRBRBRBRBR
RBRBRBRBRBRBRBRB
The 'lossless_vec_2_*.webp' WebP files should decode to an image comparable
to equivalently 'peak.png'. Their alpha channel is fully opaque.
Feature list:
lossless_vec_?_0.webp: none
lossless_vec_?_1.webp: PALETTE
lossless_vec_?_2.webp: PREDICTION
lossless_vec_?_3.webp: PREDICTION PALETTE
lossless_vec_?_4.webp: SUBTRACT-GREEN
lossless_vec_?_5.webp: SUBTRACT-GREEN PALETTE
lossless_vec_?_6.webp: PREDICTION SUBTRACT-GREEN
lossless_vec_?_7.webp: PREDICTION SUBTRACT-GREEN PALETTE
lossless_vec_?_8.webp: CROSS-COLOR-TRANSFORM
lossless_vec_?_9.webp: CROSS-COLOR-TRANSFORM PALETTE
lossless_vec_?_10.webp: PREDICTION CROSS-COLOR-TRANSFORM
lossless_vec_?_11.webp: PREDICTION CROSS-COLOR-TRANSFORM PALETTE
lossless_vec_?_12.webp: CROSS-COLOR-TRANSFORM SUBTRACT-GREEN
lossless_vec_?_13.webp: CROSS-COLOR-TRANSFORM SUBTRACT-GREEN PALETTE
lossless_vec_?_14_.webp: PREDICTION CROSS-COLOR-TRANSFORM SUBTRACT-GREEN
lossless_vec_?_15.webp: PREDICTION CROSS-COLOR-TRANSFORM SUBTRACT-GREEN PALETTE
Loading…
Cancel
Save