diff --git a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs
index 83e8e95e1a..8c73c094c1 100644
--- a/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs
+++ b/src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs
@@ -455,6 +455,44 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
Dst(dst, 3, 3, l);
}
+ ///
+ /// Paragraph 14.3: Implementation of the Walsh-Hadamard transform inversion.
+ ///
+ public static void TransformWht(short[] input, short[] output)
+ {
+ var tmp = new int[16];
+ for (int i = 0; i < 4; ++i)
+ {
+ int iPlus4 = 4 + i;
+ int iPlus8 = 8 + i;
+ int iPlus12 = 12 + i;
+ int a0 = input[i] + input[iPlus12];
+ int a1 = input[iPlus4] + input[iPlus8];
+ int a2 = input[iPlus4] - input[iPlus8];
+ int a3 = input[i] - input[iPlus12];
+ tmp[i] = a0 + a1;
+ tmp[iPlus8] = a0 - a1;
+ tmp[iPlus4] = a3 + a2;
+ tmp[iPlus12] = a3 - a2;
+ }
+
+ int outputOffset = 0;
+ for (int i = 0; i < 4; ++i)
+ {
+ int imul4 = i * 4;
+ int dc = tmp[0 + imul4] + 3;
+ int a0 = dc + tmp[3 + imul4];
+ int a1 = tmp[1 + imul4] + tmp[2 + imul4];
+ int a2 = tmp[1 + imul4] - tmp[2 + imul4];
+ int a3 = dc - tmp[3 + imul4];
+ output[outputOffset + 0] = (short)((a0 + a1) >> 3);
+ output[outputOffset + 16] = (short)((a3 + a2) >> 3);
+ output[outputOffset + 32] = (short)((a0 - a1) >> 3);
+ output[outputOffset + 48] = (short)((a3 - a2) >> 3);
+ outputOffset += 64;
+ }
+ }
+
public static void TransformTwo(Span src, Span dst)
{
TransformOne(src, dst);
@@ -740,6 +778,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
dst[x + (y * WebPConstants.Bps)] = v;
}
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static byte Clip8B(int v)
+ {
+ return (byte)((v & ~0xff) == 0 ? v : (v < 0) ? 0 : 255);
+ }
+
// Complex In-loop filtering (Paragraph 15.3)
private static void FilterLoop24(
Span p,
@@ -933,12 +977,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
return (a * 35468) >> 16;
}
- [MethodImpl(InliningOptions.ShortMethod)]
- private static byte Clip8B(int v)
- {
- return (byte)((v & ~0xff) == 0 ? v : (v < 0) ? 0 : 255);
- }
-
[MethodImpl(InliningOptions.ShortMethod)]
private static byte Clip8(int v)
{
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
index 3374dff01c..2ca51a9cb6 100644
--- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
+++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
@@ -34,6 +34,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
private const int YuvHalf = 1 << (YuvFix - 1);
+ private const int KC1 = 20091 + (1 << 16);
+
+ private const int KC2 = 35468;
+
+ private const int MaxLevel = 2047;
+
+ private const int QFix = 17;
+
+ private readonly byte[] zigzag = new byte[] { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 };
+
///
/// Initializes a new instance of the class.
///
@@ -117,6 +127,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
mb[i] = new Vp8MacroBlockInfo();
}
+ var segmentInfos = new Vp8SegmentInfo[4];
+ for (int i = 0; i < 4; i++)
+ {
+ segmentInfos[i] = new Vp8SegmentInfo();
+ }
+
var it = new Vp8EncIterator(this.YTop, this.UvTop, this.Preds, this.Nz, mb, mbw, mbh);
int method = 4; // TODO: hardcoded for now
int quality = 100; // TODO: hardcoded for now
@@ -126,13 +142,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
// Analysis is done, proceed to actual coding.
// TODO: EncodeAlpha();
+ // Compute segment probabilities.
+ this.SetSegmentProbas(segmentInfos);
+ this.SetupMatrices(segmentInfos);
it.Init();
it.InitFilter();
do
{
var info = new Vp8ModeScore();
it.Import(y, u, v, yStride, uvStride, width, height);
- if (!this.Decimate(it, info, method))
+ if (!this.Decimate(it, segmentInfos, info, method))
{
this.CodeResiduals(it);
}
@@ -159,6 +178,19 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.Preds.Dispose();
}
+ private void SetSegmentProbas(Vp8SegmentInfo[] dqm)
+ {
+ var p = new int[4];
+ int n;
+
+ // TODO: SetSegmentProbas
+ }
+
+ private void SetupMatrices(Vp8SegmentInfo[] dqm)
+ {
+ // TODO: SetupMatrices
+ }
+
private int MacroBlockAnalysis(int width, int height, Vp8EncIterator it, Span y, Span u, Span v, int yStride, int uvStride, int method, int quality, int[] alphas, out int uvAlpha)
{
int alpha = 0;
@@ -207,7 +239,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
return bestAlpha; // Mixed susceptibility (not just luma).
}
- private bool Decimate(Vp8EncIterator it, Vp8ModeScore rd, int method)
+ private bool Decimate(Vp8EncIterator it, Vp8SegmentInfo[] segmentInfos, Vp8ModeScore rd, int method)
{
rd.InitScore();
@@ -219,7 +251,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
// For method >= 2, pick the best intra4/intra16 based on SSE (~tad slower).
// For method <= 1, we don't re-examine the decision but just go ahead with
// quantization/reconstruction.
- this.RefineUsingDistortion(it, rd, method >= 2, method >= 1);
+ this.RefineUsingDistortion(it, segmentInfos, rd, method >= 2, method >= 1);
bool isSkipped = rd.Nz == 0;
it.SetSkip(isSkipped);
@@ -228,14 +260,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
// Refine intra16/intra4 sub-modes based on distortion only (not rate).
- private void RefineUsingDistortion(Vp8EncIterator it, Vp8ModeScore rd, bool tryBothModes, bool refineUvMode)
+ private void RefineUsingDistortion(Vp8EncIterator it, Vp8SegmentInfo[] segmentInfos, Vp8ModeScore rd, bool tryBothModes, bool refineUvMode)
{
long bestScore = Vp8ModeScore.MaxCost;
int nz = 0;
int mode;
bool isI16 = tryBothModes || (it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16);
-
- // TODO: VP8SegmentInfo* const dqm = &it->enc_->dqm_[it->mb_->segment_];
+ Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment];
// Some empiric constants, of approximate order of magnitude.
int lambdaDi16 = 106;
@@ -324,7 +355,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
{
// Reconstruct partial block inside yuv_out2 buffer
Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc + WebPLookupTables.Vp8Scan[it.I4]);
- // TODO: nz |= ReconstructIntra4(it, rd.YAcLevels[it.I4], src, tmpDst, bestI4Mode) << it.I4;
+ nz |= this.ReconstructIntra4(it, dqm, rd.YAcLevels[it.I4], src, tmpDst, bestI4Mode) << it.I4;
}
}
while (it.RotateI4(it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc)));
@@ -339,7 +370,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
else
{
- // TODO: nz = ReconstructIntra16(it, rd, it.YuvOut.AsSpan(Vp8EncIterator.YOffEnc), it.Preds.GetSpan()[0]);
+ nz = this.ReconstructIntra16(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.YOffEnc), it.Preds.GetSpan()[0]);
}
// ... and UV!
@@ -362,7 +393,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
it.SetIntraUvMode(bestMode);
}
- // TODO: nz |= ReconstructUv(it, rd, it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc), it.CurrentMacroBlockInfo.UvMode);
+ nz |= this.ReconstructUv(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc), it.CurrentMacroBlockInfo.UvMode);
rd.Nz = (uint)nz;
rd.Score = bestScore;
@@ -373,19 +404,263 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
- private void ReconstructIntra16()
+ private int ReconstructIntra16(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode)
+ {
+ Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I16ModeOffsets[mode]);
+ Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc);
+ int nz = 0;
+ int n;
+ var dcTmp = new short[16];
+ var tmp = new short[16][];
+ for (int i = 0; i < 16; i++)
+ {
+ tmp[i] = new short[16];
+ }
+
+ for (n = 0; n < 16; n += 2)
+ {
+ this.FTransform2(src.Slice(WebPLookupTables.Vp8Scan[n]), reference.Slice(WebPLookupTables.Vp8Scan[n]), tmp[n]);
+ }
+
+ this.FTransformWht(tmp[0], dcTmp);
+ nz |= this.QuantizeBlock(dcTmp, rd.YDcLevels, dqm.Y2) << 24;
+
+ for (n = 0; n < 16; n += 2)
+ {
+ // Zero-out the first coeff, so that: a) nz is correct below, and
+ // b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified.
+ tmp[n][0] = tmp[n + 1][0] = 0;
+ nz |= this.Quantize2Blocks(tmp[n], rd.YAcLevels[n], dqm.Y1) << n;
+ }
+
+ // Transform back.
+ LossyUtils.TransformWht(dcTmp, tmp[0]);
+ for (n = 0; n < 16; n += 2)
+ {
+ this.ITransform(reference.Slice(WebPLookupTables.Vp8Scan[n]), tmp[n], yuvOut.Slice(WebPLookupTables.Vp8Scan[n]), true);
+ }
+
+ return nz;
+ }
+
+ private int ReconstructIntra4(Vp8EncIterator it, Vp8SegmentInfo dqm, short[] levels, Span src, Span yuvOut, int mode)
{
+ Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I4ModeOffsets[mode]);
+ var tmp = new short[16];
+ this.FTransform(src, reference, tmp);
+ var nz = this.QuantizeBlock(tmp, levels, dqm.Y1);
+ this.ITransform(reference, tmp, yuvOut, false);
+ return nz;
}
- private void ReconstructIntra4()
+ private int ReconstructUv(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode)
{
+ Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8UvModeOffsets[mode]);
+ Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc);
+ int nz = 0;
+ int n;
+ var tmp = new short[8][];
+ for (int i = 0; i < 8; i++)
+ {
+ tmp[i] = new short[16];
+ }
+
+ for (n = 0; n < 8; n += 2)
+ {
+ this.FTransform2(src.Slice(WebPLookupTables.Vp8ScanUv[n]), reference.Slice(WebPLookupTables.Vp8ScanUv[n]), tmp[n]);
+ }
+
+ /* TODO:
+ if (it->top_derr_ != NULL)
+ {
+ CorrectDCValues(it, &dqm->uv_, tmp, rd);
+ }*/
+
+ for (n = 0; n < 8; n += 2)
+ {
+ nz |= this.Quantize2Blocks(tmp[n], rd.UvLevels[n], dqm.Uv) << n;
+ }
+
+ for (n = 0; n < 8; n += 2)
+ {
+ this.ITransform(reference.Slice(WebPLookupTables.Vp8ScanUv[n]), tmp[n], yuvOut.Slice(WebPLookupTables.Vp8ScanUv[n]), true);
+ }
+
+ return nz << 16;
+ }
+ private void FTransform2(Span src, Span reference, short[] output)
+ {
+ this.FTransform(src, reference, output);
+ this.FTransform(src.Slice(4), reference.Slice(4), output.AsSpan(16));
}
- private void ReconstructUv()
+ private void FTransform(Span src, Span reference, Span output)
{
+ int i;
+ var tmp = new int[16];
+ for (i = 0; i < 4; ++i)
+ {
+ int d0 = src[0] - reference[0]; // 9bit dynamic range ([-255,255])
+ int d1 = src[1] - reference[1];
+ int d2 = src[2] - reference[2];
+ int d3 = src[3] - reference[3];
+ int a0 = d0 + d3; // 10b [-510,510]
+ int a1 = d1 + d2;
+ int a2 = d1 - d2;
+ int a3 = d0 - d3;
+ tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160]
+ tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542]
+ tmp[2 + (i * 4)] = (a0 - a1) * 8;
+ tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9;
+ src = src.Slice(WebPConstants.Bps);
+ reference = reference.Slice(WebPConstants.Bps);
+ }
+
+ for (i = 0; i < 4; ++i)
+ {
+ int a0 = tmp[0 + i] + tmp[12 + i]; // 15b
+ int a1 = tmp[4 + i] + tmp[8 + i];
+ int a2 = tmp[4 + i] - tmp[8 + i];
+ int a3 = tmp[0 + i] - tmp[12 + i];
+ output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b
+ output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + (a3 != 0 ? 1 : 0));
+ output[8 + i] = (short)((a0 - a1 + 7) >> 4);
+ output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16);
+ }
+ }
+
+ private void FTransformWht(Span input, Span output)
+ {
+ var tmp = new int[16];
+ int i;
+ for (i = 0; i < 4; ++i)
+ {
+ int a0 = input[0 * 16] + input[2 * 16]; // 13b
+ int a1 = input[1 * 16] + input[3 * 16];
+ int a2 = input[1 * 16] - input[3 * 16];
+ int a3 = input[0 * 16] - input[2 * 16];
+ tmp[0 + (i * 4)] = a0 + a1; // 14b
+ tmp[1 + (i * 4)] = a3 + a2;
+ tmp[2 + (i * 4)] = a3 - a2;
+ tmp[3 + (i * 4)] = a0 - a1;
+
+ input = input.Slice(64);
+ }
+
+ for (i = 0; i < 4; ++i)
+ {
+ int a0 = tmp[0 + i] + tmp[8 + i]; // 15b
+ int a1 = tmp[4 + i] + tmp[12 + i];
+ int a2 = tmp[4 + i] - tmp[12 + i];
+ int a3 = tmp[0 + i] - tmp[8 + i];
+ int b0 = a0 + a1; // 16b
+ int b1 = a3 + a2;
+ int b2 = a3 - a2;
+ int b3 = a0 - a1;
+ output[ 0 + i] = (short)(b0 >> 1); // 15b
+ output[ 4 + i] = (short)(b1 >> 1);
+ output[ 8 + i] = (short)(b2 >> 1);
+ output[12 + i] = (short)(b3 >> 1);
+ }
+ }
+
+ private int Quantize2Blocks(Span input, Span output, Vp8Matrix mtx)
+ {
+ int nz;
+ nz = this.QuantizeBlock(input, output, mtx) << 0;
+ nz |= this.QuantizeBlock(input.Slice(1 * 16), output.Slice(1 * 16), mtx) << 1;
+ return nz;
+ }
+
+ private int QuantizeBlock(Span input, Span output, Vp8Matrix mtx)
+ {
+ int last = -1;
+ int n;
+ for (n = 0; n < 16; ++n)
+ {
+ int j = zigzag[n];
+ bool sign = input[j] < 0;
+ uint coeff = (uint)((sign ? -input[j] : input[j]) + mtx.Sharpen[j]);
+ if (coeff > mtx.ZThresh[j])
+ {
+ uint Q = (uint)mtx.Q[j];
+ uint iQ = (uint)mtx.IQ[j];
+ uint B = mtx.Bias[j];
+ int level = this.QuantDiv(coeff, iQ, B);
+ if (level > MaxLevel)
+ {
+ level = MaxLevel;
+ }
+
+ if (sign)
+ {
+ level = -level;
+ }
+
+ input[j] = (short)(level * (int)Q);
+ output[n] = (short)level;
+ if (level != 0)
+ {
+ last = n;
+ }
+ }
+ else
+ {
+ output[n] = 0;
+ input[j] = 0;
+ }
+ }
+
+ return (last >= 0) ? 1 : 0;
+ }
+
+ private void ITransform(Span reference, short[] input, Span dst, bool doTwo)
+ {
+ this.ITransformOne(reference, input, dst);
+ if (doTwo)
+ {
+ this.ITransformOne(reference.Slice(4), input.AsSpan(16), dst.Slice(4));
+ }
+ }
+
+ private void ITransformOne(Span reference, Span input, Span dst)
+ {
+ int i;
+ var C = new int[4 * 4];
+ Span tmp = C.AsSpan();
+ for (i = 0; i < 4; ++i)
+ {
+ // vertical pass.
+ int a = input[0] + input[8];
+ int b = input[0] - input[8];
+ int c = this.Mul(input[4], KC2) - this.Mul(input[12], KC1);
+ int d = this.Mul(input[4], KC1) + this.Mul(input[12], KC2);
+ tmp[0] = a + d;
+ tmp[1] = b + c;
+ tmp[2] = b - c;
+ tmp[3] = a - d;
+ tmp = tmp.Slice(4);
+ input = input.Slice(1);
+ }
+
+ tmp = C.AsSpan();
+ for (i = 0; i < 4; ++i)
+ {
+ // horizontal pass.
+ int dc = tmp[0] + 4;
+ int a = dc + tmp[8];
+ int b = dc - tmp[8];
+ int c = this.Mul(tmp[4], KC2) - this.Mul(tmp[12], KC1);
+ int d = this.Mul(tmp[4], KC1) + this.Mul(tmp[12], KC2);
+ this.Store(dst, reference, 0, i, (byte)(a + d));
+ this.Store(dst, reference, 1, i, (byte)(b + c));
+ this.Store(dst, reference, 2, i, (byte)(b - c));
+ this.Store(dst, reference, 3, i, (byte)(a - d));
+ tmp = tmp.Slice(1);
+ }
}
private void ConvertRgbToYuv(Image image)
@@ -742,5 +1017,23 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
return true;
}
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private int QuantDiv(uint n, uint iQ, uint b)
+ {
+ return (int)(((n * iQ) + b) >> QFix);
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private void Store(Span dst, Span reference, int x, int y, byte v)
+ {
+ dst[x + (y * WebPConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebPConstants.Bps)] + (v >> 3));
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private int Mul(int a, int b)
+ {
+ return (a * b) >> 16;
+ }
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs
new file mode 100644
index 0000000000..5fe529e5bc
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP.Lossy
+{
+ internal class Vp8Matrix
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Vp8Matrix()
+ {
+ this.Q = new short[16];
+ this.IQ = new short[16];
+ this.Bias = new uint[16];
+ this.ZThresh = new uint[16];
+ this.Sharpen = new short[16];
+ }
+
+ ///
+ /// quantizer steps.
+ ///
+ public short[] Q { get; }
+
+ ///
+ /// reciprocals, fixed point.
+ ///
+ public short[] IQ { get; }
+
+ ///
+ /// rounding bias.
+ ///
+ public uint[] Bias { get; }
+
+ ///
+ /// value below which a coefficient is zeroed.
+ ///
+ public uint[] ZThresh { get; }
+
+ ///
+ /// frequency boosters for slight sharpening.
+ ///
+ public short[] Sharpen { get; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs
new file mode 100644
index 0000000000..85069bf5e2
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8SegmentInfo.cs
@@ -0,0 +1,53 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP.Lossy
+{
+ internal class Vp8SegmentInfo
+ {
+ ///
+ /// quantization matrix y1.
+ ///
+ public Vp8Matrix Y1 { get; set; }
+
+ ///
+ /// quantization matrix y2.
+ ///
+ public Vp8Matrix Y2 { get; set; }
+
+ ///
+ /// quantization matrix uv.
+ ///
+ public Vp8Matrix Uv { get; set; }
+
+ ///
+ /// quant-susceptibility, range [-127,127]. Zero is neutral. Lower values indicate a lower risk of blurriness.
+ ///
+ public int Alpha { get; set; }
+
+ ///
+ /// filter-susceptibility, range [0,255].
+ ///
+ public int Beta { get; set; }
+
+ ///
+ /// final segment quantizer.
+ ///
+ public int Quant { get; set; }
+
+ ///
+ /// final in-loop filtering strength.
+ ///
+ public int FStrength { get; set; }
+
+ ///
+ /// max edge delta (for filtering strength).
+ ///
+ public int MaxEdge { get; set; }
+
+ ///
+ /// penalty for using Intra4.
+ ///
+ public long I4Penalty { get; set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs
index 1b1884cdc7..351f1a45e8 100644
--- a/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs
+++ b/src/ImageSharp/Formats/WebP/Lossy/WebPLossyDecoder.cs
@@ -890,7 +890,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
if (nz > 1)
{
// More than just the DC -> perform the full transform.
- this.TransformWht(dc, dst);
+ LossyUtils.TransformWht(dc, dst);
}
else
{
@@ -1078,44 +1078,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
return v;
}
- ///
- /// Paragraph 14.3: Implementation of the Walsh-Hadamard transform inversion.
- ///
- private void TransformWht(short[] input, short[] output)
- {
- var tmp = new int[16];
- for (int i = 0; i < 4; ++i)
- {
- int iPlus4 = 4 + i;
- int iPlus8 = 8 + i;
- int iPlus12 = 12 + i;
- int a0 = input[i] + input[iPlus12];
- int a1 = input[iPlus4] + input[iPlus8];
- int a2 = input[iPlus4] - input[iPlus8];
- int a3 = input[i] - input[iPlus12];
- tmp[i] = a0 + a1;
- tmp[iPlus8] = a0 - a1;
- tmp[iPlus4] = a3 + a2;
- tmp[iPlus12] = a3 - a2;
- }
-
- int outputOffset = 0;
- for (int i = 0; i < 4; ++i)
- {
- int imul4 = i * 4;
- int dc = tmp[0 + imul4] + 3;
- int a0 = dc + tmp[3 + imul4];
- int a1 = tmp[1 + imul4] + tmp[2 + imul4];
- int a2 = tmp[1 + imul4] - tmp[2 + imul4];
- int a3 = dc - tmp[3 + imul4];
- output[outputOffset + 0] = (short)((a0 + a1) >> 3);
- output[outputOffset + 16] = (short)((a3 + a2) >> 3);
- output[outputOffset + 32] = (short)((a0 - a1) >> 3);
- output[outputOffset + 48] = (short)((a3 - a2) >> 3);
- outputOffset += 64;
- }
- }
-
private Vp8SegmentHeader ParseSegmentHeader(Vp8Proba proba)
{
var vp8SegmentHeader = new Vp8SegmentHeader
diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs
index 9cfd9ec4cd..fe4eef63e1 100644
--- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs
+++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs
@@ -45,6 +45,12 @@ namespace SixLabors.ImageSharp.Formats.WebP
0 + (12 * WebPConstants.Bps), 4 + (12 * WebPConstants.Bps), 8 + (12 * WebPConstants.Bps), 12 + (12 * WebPConstants.Bps),
};
+ public static readonly short[] Vp8ScanUv =
+ {
+ 0 + (0 * WebPConstants.Bps), 4 + (0 * WebPConstants.Bps), 0 + (4 * WebPConstants.Bps), 4 + (4 * WebPConstants.Bps), // U
+ 8 + (0 * WebPConstants.Bps), 12 + (0 * WebPConstants.Bps), 8 + (4 * WebPConstants.Bps), 12 + (4 * WebPConstants.Bps) // V
+ };
+
public static readonly short[,][] Vp8FixedCostsI4 = new short[10, 10][];
///