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][]; ///