From 7a6cb423aae5a194e32cefb557ad2eb30394e5c1 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 14 Nov 2020 17:00:48 +0100 Subject: [PATCH] Refactor Vp8Encoder --- .../Formats/WebP/BitWriter/Vp8BitWriter.cs | 1 - .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 1 - src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs | 137 ++++ .../Formats/WebP/Lossy/Vp8EncIterator.cs | 513 +------------- .../Formats/WebP/Lossy/Vp8Encoder.cs | 580 +-------------- .../Formats/WebP/Lossy/Vp8Encoding.cs | 664 ++++++++++++++++++ .../Formats/WebP/Lossy/YuvConversion.cs | 260 +++++++ 7 files changed, 1103 insertions(+), 1053 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs index 813fbb7bca..7eb5bb0d84 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs @@ -197,7 +197,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter /// The extra size in bytes needed. public override void BitWriterResize(int extraSize) { - // TODO: review again if this works as intended. Probably needs a unit test ... var neededSize = this.pos + extraSize; if (neededSize <= this.maxPos) { diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index eb25027204..ade6cb61af 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -185,7 +185,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter /// The extra size in bytes needed. public override void BitWriterResize(int extraSize) { - // TODO: review again if this works as intended. Probably needs a unit test ... int maxBytes = this.end + this.Buffer.Length; int sizeRequired = this.cur + extraSize; diff --git a/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs new file mode 100644 index 0000000000..e8f7826025 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs @@ -0,0 +1,137 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + /// + /// Quantization methods. + /// + internal static class QuantEnc + { + private static readonly byte[] Zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; + + private const int MaxLevel = 2047; + + // Diffusion weights. We under-correct a bit (15/16th of the error is actually + // diffused) to avoid 'rainbow' chessboard pattern of blocks at q~=0. + private const int C1 = 7; // fraction of error sent to the 4x4 block below + private const int C2 = 8; // fraction of error sent to the 4x4 block on the right + private const int DSHIFT = 4; + private const int DSCALE = 1; // storage descaling, needed to make the error fit byte + + public static int Quantize2Blocks(Span input, Span output, Vp8Matrix mtx) + { + int nz; + nz = QuantEnc.QuantizeBlock(input, output, mtx) << 0; + nz |= QuantEnc.QuantizeBlock(input.Slice(1 * 16), output.Slice(1 * 16), mtx) << 1; + return nz; + } + + public static 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 = mtx.Q[j]; + uint iQ = mtx.IQ[j]; + uint b = mtx.Bias[j]; + int level = 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; + } + + // Quantize as usual, but also compute and return the quantization error. + // Error is already divided by DSHIFT. + public static int QuantizeSingle(Span v, Vp8Matrix mtx) + { + int v0 = v[0]; + bool sign = v0 < 0; + if (sign) + { + v0 = -v0; + } + + if (v0 > (int)mtx.ZThresh[0]) + { + int qV = QuantDiv((uint)v0, mtx.IQ[0], mtx.Bias[0]) * mtx.Q[0]; + int err = v0 - qV; + v[0] = (short)(sign ? -qV : qV); + return (sign ? -err : err) >> DSCALE; + } + + v[0] = 0; + return (sign ? -v0 : v0) >> DSCALE; + } + + public static void CorrectDcValues(Vp8EncIterator it, Vp8Matrix mtx, short[] tmp, Vp8ModeScore rd) + { +#pragma warning disable SA1005 // Single line comments should begin with single space + // | top[0] | top[1] + // --------+--------+--------- + // left[0] | tmp[0] tmp[1] <-> err0 err1 + // left[1] | tmp[2] tmp[3] err2 err3 + // + // Final errors {err1,err2,err3} are preserved and later restored + // as top[]/left[] on the next block. +#pragma warning restore SA1005 // Single line comments should begin with single space + for (int ch = 0; ch <= 1; ++ch) + { + Span top = it.TopDerr.AsSpan((it.X * 4) + ch, 2); + Span left = it.LeftDerr.AsSpan(ch, 2); + int err0, err1, err2, err3; + Span c = tmp.AsSpan(ch * 4 * 16, 4 * 16); + c[0] += (short)(((C1 * top[0]) + (C2 * left[0])) >> (DSHIFT - DSCALE)); + err0 = QuantEnc.QuantizeSingle(c, mtx); + c[1 * 16] += (short)(((C1 * top[1]) + (C2 * err0)) >> (DSHIFT - DSCALE)); + err1 = QuantEnc.QuantizeSingle(c.Slice(1 * 16), mtx); + c[2 * 16] += (short)(((C1 * err0) + (C2 * left[1])) >> (DSHIFT - DSCALE)); + err2 = QuantEnc.QuantizeSingle(c.Slice(2 * 16), mtx); + c[3 * 16] += (short)(((C1 * err1) + (C2 * err2)) >> (DSHIFT - DSCALE)); + err3 = QuantEnc.QuantizeSingle(c.Slice(3 * 16), mtx); + + // TODO: set errors in rd + // rd->derr[ch][0] = (int8_t)err1; + // rd->derr[ch][1] = (int8_t)err2; + // rd->derr[ch][2] = (int8_t)err3; + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int QuantDiv(uint n, uint iQ, uint b) + { + return (int)(((n * iQ) + b) >> WebPConstants.QFix); + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs index 7ddc2edd08..a347e82637 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers.Binary; using SixLabors.ImageSharp.Formats.WebP.Lossless; namespace SixLabors.ImageSharp.Formats.WebP.Lossy @@ -23,8 +22,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private const int MaxIntra16Mode = 2; - private const int MaxIntra4Mode = 2; - private readonly int mbw; private readonly int mbh; @@ -34,50 +31,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// private readonly int predsWidth; - private const int I16DC16 = 0 * 16 * WebPConstants.Bps; - - private const int I16TM16 = I16DC16 + 16; - - private const int I16VE16 = 1 * 16 * WebPConstants.Bps; - - private const int I16HE16 = I16VE16 + 16; - - private const int C8DC8 = 2 * 16 * WebPConstants.Bps; - - private const int C8TM8 = C8DC8 + (1 * 16); - - private const int C8VE8 = (2 * 16 * WebPConstants.Bps) + (8 * WebPConstants.Bps); - - private const int C8HE8 = C8VE8 + (1 * 16); - - public static readonly int[] Vp8I16ModeOffsets = { I16DC16, I16TM16, I16VE16, I16HE16 }; - - public static readonly int[] Vp8UvModeOffsets = { C8DC8, C8TM8, C8VE8, C8HE8 }; - - private const int I4DC4 = (3 * 16 * WebPConstants.Bps) + 0; - - private const int I4TM4 = I4DC4 + 4; - - private const int I4VE4 = I4DC4 + 8; - - private const int I4HE4 = I4DC4 + 12; - - private const int I4RD4 = I4DC4 + 16; - - private const int I4VR4 = I4DC4 + 20; - - private const int I4LD4 = I4DC4 + 24; - - private const int I4VL4 = I4DC4 + 28; - - private const int I4HD4 = (3 * 16 * WebPConstants.Bps) + (4 * WebPConstants.Bps); - - private const int I4HU4 = I4HD4 + 4; - - public static readonly int[] Vp8I4ModeOffsets = { I4DC4, I4TM4, I4VE4, I4HE4, I4RD4, I4VR4, I4LD4, I4VL4, I4HD4, I4HU4 }; - - private readonly byte[] clip1 = new byte[255 + 510 + 1]; // clips [-255,510] to [0,255] - // Array to record the position of the top sample to pass to the prediction functions. private readonly byte[] vp8TopLeftI4 = { @@ -134,11 +87,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy this.YLeft.AsSpan().Fill(defaultInitVal); this.UvLeft.AsSpan().Fill(defaultInitVal); - for (int i = -255; i <= 255 + 255; ++i) - { - this.clip1[255 + i] = this.Clip8b(i); - } - this.Reset(); } @@ -440,7 +388,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (mode = 0; mode < maxMode; ++mode) { var histo = new Vp8LHistogram(); - histo.CollectHistogram(this.YuvIn.AsSpan(YOffEnc), this.YuvP.AsSpan(Vp8I16ModeOffsets[mode]), 0, 16); + histo.CollectHistogram(this.YuvIn.AsSpan(YOffEnc), this.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]), 0, 16); int alpha = histo.GetAlpha(); if (alpha > bestAlpha) { @@ -465,7 +413,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (mode = 0; mode < maxMode; ++mode) { var histo = new Vp8LHistogram(); - histo.CollectHistogram(this.YuvIn.AsSpan(UOffEnc), this.YuvP.AsSpan(Vp8UvModeOffsets[mode]), 16, 16 + 4 + 4); + histo.CollectHistogram(this.YuvIn.AsSpan(UOffEnc), this.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]), 16, 16 + 4 + 4); int alpha = histo.GetAlpha(); if (alpha > bestAlpha) { @@ -663,19 +611,19 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { Span left = this.X != 0 ? this.YLeft.AsSpan() : null; Span top = this.Y != 0 ? this.YTop.AsSpan(this.yTopIdx) : null; - this.EncPredLuma16(this.YuvP, left, top); + Vp8Encoding.EncPredLuma16(this.YuvP, left, top); } public void MakeChroma8Preds() { Span left = this.X != 0 ? this.UvLeft.AsSpan() : null; Span top = this.Y != 0 ? this.UvTop.AsSpan(this.uvTopIdx) : null; - this.EncPredChroma8(this.YuvP, left, top); + Vp8Encoding.EncPredChroma8(this.YuvP, left, top); } public void MakeIntra4Preds() { - this.EncPredLuma4(this.YuvP, this.I4Boundary, this.I4BoundaryIdx); + Vp8Encoding.EncPredLuma4(this.YuvP, this.I4Boundary, this.I4BoundaryIdx); } public void SwapOut() @@ -767,452 +715,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy } } - // luma 16x16 prediction (paragraph 12.3). - private void EncPredLuma16(Span dst, Span left, Span top) - { - this.DcMode(dst.Slice(I16DC16), left, top, 16, 16, 5); - this.VerticalPred(dst.Slice(I16VE16), top, 16); - this.HorizontalPred(dst.Slice(I16HE16), left, 16); - this.TrueMotion(dst.Slice(I16TM16), left, top, 16); - } - - // Chroma 8x8 prediction (paragraph 12.2). - private void EncPredChroma8(Span dst, Span left, Span top) - { - // U block. - this.DcMode(dst.Slice(C8DC8), left, top, 8, 8, 4); - this.VerticalPred(dst.Slice(C8VE8), top, 8); - this.HorizontalPred(dst.Slice(C8HE8), left, 8); - this.TrueMotion(dst.Slice(C8TM8), left, top, 8); - - // V block. - dst = dst.Slice(8); - if (top != null) - { - top = top.Slice(8); - } - - if (left != null) - { - left = left.Slice(16); - } - - this.DcMode(dst.Slice(C8DC8), left, top, 8, 8, 4); - this.VerticalPred(dst.Slice(C8VE8), top, 8); - this.HorizontalPred(dst.Slice(C8HE8), left, 8); - this.TrueMotion(dst.Slice(C8TM8), left, top, 8); - } - - // Left samples are top[-5 .. -2], top_left is top[-1], top are - // located at top[0..3], and top right is top[4..7] - private void EncPredLuma4(Span dst, Span top, int topOffset) - { - this.Dc4(dst.Slice(I4DC4), top, topOffset); - this.Tm4(dst.Slice(I4TM4), top, topOffset); - this.Ve4(dst.Slice(I4VE4), top, topOffset); - this.He4(dst.Slice(I4HE4), top, topOffset); - this.Rd4(dst.Slice(I4RD4), top, topOffset); - this.Vr4(dst.Slice(I4VR4), top, topOffset); - this.Ld4(dst.Slice(I4LD4), top, topOffset); - this.Vl4(dst.Slice(I4VL4), top, topOffset); - this.Hd4(dst.Slice(I4HD4), top, topOffset); - this.Hu4(dst.Slice(I4HU4), top, topOffset); - } - - private void DcMode(Span dst, Span left, Span top, int size, int round, int shift) - { - int dc = 0; - int j; - if (top != null) - { - for (j = 0; j < size; ++j) - { - dc += top[j]; - } - - if (left != null) - { - // top and left present. - left = left.Slice(1); // in the reference implementation, left starts at -1. - for (j = 0; j < size; ++j) - { - dc += left[j]; - } - } - else - { - // top, but no left. - dc += dc; - } - - dc = (dc + round) >> shift; - } - else if (left != null) - { - // left but no top. - left = left.Slice(1); // in the reference implementation, left starts at -1. - for (j = 0; j < size; ++j) - { - dc += left[j]; - } - - dc += dc; - dc = (dc + round) >> shift; - } - else - { - // no top, no left, nothing. - dc = 0x80; - } - - this.Fill(dst, dc, size); - } - - private void VerticalPred(Span dst, Span top, int size) - { - if (top != null) - { - for (int j = 0; j < size; ++j) - { - top.Slice(0, size).CopyTo(dst.Slice(j * WebPConstants.Bps)); - } - } - else - { - this.Fill(dst, 127, size); - } - } - - private void HorizontalPred(Span dst, Span left, int size) - { - if (left != null) - { - left = left.Slice(1); // in the reference implementation, left starts at - 1. - for (int j = 0; j < size; ++j) - { - dst.Slice(j * WebPConstants.Bps, size).Fill(left[j]); - } - } - else - { - this.Fill(dst, 129, size); - } - } - - private void TrueMotion(Span dst, Span left, Span top, int size) - { - if (left != null) - { - if (top != null) - { - Span clip = this.clip1.AsSpan(255 - left[0]); // left [0] instead of left[-1], original left starts at -1 - for (int y = 0; y < size; ++y) - { - Span clipTable = clip.Slice(left[y + 1]); // left[y] - for (int x = 0; x < size; ++x) - { - dst[x] = clipTable[top[x]]; - } - - dst = dst.Slice(WebPConstants.Bps); - } - } - else - { - this.HorizontalPred(dst, left, size); - } - } - else - { - // true motion without left samples (hence: with default 129 value) - // is equivalent to VE prediction where you just copy the top samples. - // Note that if top samples are not available, the default value is - // then 129, and not 127 as in the VerticalPred case. - if (top != null) - { - this.VerticalPred(dst, top, size); - } - else - { - this.Fill(dst, 129, size); - } - } - } - - private void Dc4(Span dst, Span top, int topOffset) - { - uint dc = 4; - int i; - for (i = 0; i < 4; ++i) - { - dc += (uint)(top[topOffset + i] + top[topOffset - 5 + i]); - } - - this.Fill(dst, (int)(dc >> 3), 4); - } - - private void Tm4(Span dst, Span top, int topOffset) - { - Span clip = this.clip1.AsSpan(255 - top[topOffset - 1]); - for (int y = 0; y < 4; ++y) - { - Span clipTable = clip.Slice(top[topOffset - 2 - y]); - for (int x = 0; x < 4; ++x) - { - dst[x] = clipTable[top[topOffset + x]]; - } - - dst = dst.Slice(WebPConstants.Bps); - } - } - - private void Ve4(Span dst, Span top, int topOffset) - { - // vertical - byte[] vals = - { - LossyUtils.Avg3(top[topOffset - 1], top[topOffset], top[topOffset + 1]), - LossyUtils.Avg3(top[topOffset], top[topOffset + 1], top[topOffset + 2]), - LossyUtils.Avg3(top[topOffset + 1], top[topOffset + 2], top[topOffset + 3]), - LossyUtils.Avg3(top[topOffset + 2], top[topOffset + 3], top[topOffset + 4]) - }; - - for (int i = 0; i < 4; ++i) - { - vals.AsSpan().CopyTo(dst.Slice(i * WebPConstants.Bps)); - } - } - - private void He4(Span dst, Span top, int topOffset) - { - // horizontal - byte x = top[topOffset - 1]; - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte l = top[topOffset - 5]; - - uint val = 0x01010101U * LossyUtils.Avg3(x, i, j); - BinaryPrimitives.WriteUInt32BigEndian(dst, val); - val = 0x01010101U * LossyUtils.Avg3(i, j, k); - BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(1 * WebPConstants.Bps), val); - val = 0x01010101U * LossyUtils.Avg3(j, k, l); - BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(2 * WebPConstants.Bps), val); - val = 0x01010101U * LossyUtils.Avg3(k, l, l); - BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(3 * WebPConstants.Bps), val); - } - - private void Rd4(Span dst, Span top, int topOffset) - { - byte x = top[topOffset - 1]; - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte l = top[topOffset - 5]; - byte a = top[topOffset]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - byte d = top[topOffset + 3]; - - LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(j, k, l)); - var ijk = LossyUtils.Avg3(i, j, k); - LossyUtils.Dst(dst, 0, 2, ijk); - LossyUtils.Dst(dst, 1, 3, ijk); - var xij = LossyUtils.Avg3(x, i, j); - LossyUtils.Dst(dst, 0, 1, xij); - LossyUtils.Dst(dst, 1, 2, xij); - LossyUtils.Dst(dst, 2, 3, xij); - var axi = LossyUtils.Avg3(a, x, i); - LossyUtils.Dst(dst, 0, 0, axi); - LossyUtils.Dst(dst, 1, 1, axi); - LossyUtils.Dst(dst, 2, 2, axi); - LossyUtils.Dst(dst, 3, 3, axi); - var bax = LossyUtils.Avg3(b, a, x); - LossyUtils.Dst(dst, 1, 0, bax); - LossyUtils.Dst(dst, 2, 1, bax); - LossyUtils.Dst(dst, 3, 2, bax); - var cba = LossyUtils.Avg3(c, b, a); - LossyUtils.Dst(dst, 2, 0, cba); - LossyUtils.Dst(dst, 3, 1, cba); - LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(d, c, b)); - } - - private void Vr4(Span dst, Span top, int topOffset) - { - byte x = top[topOffset - 1]; - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte a = top[topOffset]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - byte d = top[topOffset + 3]; - - var xa = LossyUtils.Avg2(x, a); - LossyUtils.Dst(dst, 0, 0, xa); - LossyUtils.Dst(dst, 1, 2, xa); - var ab = LossyUtils.Avg2(a, b); - LossyUtils.Dst(dst, 1, 0, ab); - LossyUtils.Dst(dst, 2, 2, ab); - var bc = LossyUtils.Avg2(b, c); - LossyUtils.Dst(dst, 2, 0, bc); - LossyUtils.Dst(dst, 3, 2, bc); - LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg2(c, d)); - LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(k, j, i)); - LossyUtils.Dst(dst, 0, 2, LossyUtils.Avg3(j, i, x)); - var ixa = LossyUtils.Avg3(i, x, a); - LossyUtils.Dst(dst, 0, 1, ixa); - LossyUtils.Dst(dst, 1, 3, ixa); - var xab = LossyUtils.Avg3(x, a, b); - LossyUtils.Dst(dst, 1, 1, xab); - LossyUtils.Dst(dst, 2, 3, xab); - var abc = LossyUtils.Avg3(a, b, c); - LossyUtils.Dst(dst, 2, 1, abc); - LossyUtils.Dst(dst, 3, 3, abc); - LossyUtils.Dst(dst, 3, 1, LossyUtils.Avg3(b, c, d)); - } - - private void Ld4(Span dst, Span top, int topOffset) - { - byte a = top[topOffset + 0]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - byte d = top[topOffset + 3]; - byte e = top[topOffset + 4]; - byte f = top[topOffset + 5]; - byte g = top[topOffset + 6]; - byte h = top[topOffset + 7]; - - LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg3(a, b, c)); - var bcd = LossyUtils.Avg3(b, c, d); - LossyUtils.Dst(dst, 1, 0, bcd); - LossyUtils.Dst(dst, 0, 1, bcd); - var cde = LossyUtils.Avg3(c, d, e); - LossyUtils.Dst(dst, 2, 0, cde); - LossyUtils.Dst(dst, 1, 1, cde); - LossyUtils.Dst(dst, 0, 2, cde); - var def = LossyUtils.Avg3(d, e, f); - LossyUtils.Dst(dst, 3, 0, def); - LossyUtils.Dst(dst, 2, 1, def); - LossyUtils.Dst(dst, 1, 2, def); - LossyUtils.Dst(dst, 0, 3, def); - var efg = LossyUtils.Avg3(e, f, g); - LossyUtils.Dst(dst, 3, 1, efg); - LossyUtils.Dst(dst, 2, 2, efg); - LossyUtils.Dst(dst, 1, 3, efg); - var fgh = LossyUtils.Avg3(f, g, h); - LossyUtils.Dst(dst, 3, 2, fgh); - LossyUtils.Dst(dst, 2, 3, fgh); - LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(g, h, h)); - } - - private void Vl4(Span dst, Span top, int topOffset) - { - byte a = top[topOffset + 0]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - byte d = top[topOffset + 3]; - byte e = top[topOffset + 4]; - byte f = top[topOffset + 5]; - byte g = top[topOffset + 6]; - byte h = top[topOffset + 7]; - - LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(a, b)); - var bc = LossyUtils.Avg2(b, c); - LossyUtils.Dst(dst, 1, 0, bc); - LossyUtils.Dst(dst, 0, 2, bc); - var cd = LossyUtils.Avg2(c, d); - LossyUtils.Dst(dst, 2, 0, cd); - LossyUtils.Dst(dst, 1, 2, cd); - var de = LossyUtils.Avg2(d, e); - LossyUtils.Dst(dst, 3, 0, de); - LossyUtils.Dst(dst, 2, 2, de); - LossyUtils.Dst(dst, 0, 1, LossyUtils.Avg3(a, b, c)); - var bcd = LossyUtils.Avg3(b, c, d); - LossyUtils.Dst(dst, 1, 1, bcd); - LossyUtils.Dst(dst, 0, 3, bcd); - var cde = LossyUtils.Avg3(c, d, e); - LossyUtils.Dst(dst, 2, 1, cde); - LossyUtils.Dst(dst, 1, 3, cde); - var def = LossyUtils.Avg3(d, e, f); - LossyUtils.Dst(dst, 3, 1, def); - LossyUtils.Dst(dst, 2, 3, def); - LossyUtils.Dst(dst, 3, 2, LossyUtils.Avg3(e, f, g)); - LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(f, g, h)); - } - - private void Hd4(Span dst, Span top, int topOffset) - { - byte x = top[topOffset - 1]; - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte l = top[topOffset - 5]; - byte a = top[topOffset]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - - var ix = LossyUtils.Avg2(i, x); - LossyUtils.Dst(dst, 0, 0, ix); - LossyUtils.Dst(dst, 2, 1, ix); - var ji = LossyUtils.Avg2(j, i); - LossyUtils.Dst(dst, 0, 1, ji); - LossyUtils.Dst(dst, 2, 2, ji); - var kj = LossyUtils.Avg2(k, j); - LossyUtils.Dst(dst, 0, 2, kj); - LossyUtils.Dst(dst, 2, 3, kj); - LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg2(l, k)); - LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(a, b, c)); - LossyUtils.Dst(dst, 2, 0, LossyUtils.Avg3(x, a, b)); - var ixa = LossyUtils.Avg3(i, x, a); - LossyUtils.Dst(dst, 1, 0, ixa); - LossyUtils.Dst(dst, 3, 1, ixa); - var jix = LossyUtils.Avg3(j, i, x); - LossyUtils.Dst(dst, 1, 1, jix); - LossyUtils.Dst(dst, 3, 2, jix); - var kji = LossyUtils.Avg3(k, j, i); - LossyUtils.Dst(dst, 1, 2, kji); - LossyUtils.Dst(dst, 3, 3, kji); - LossyUtils.Dst(dst, 1, 3, LossyUtils.Avg3(l, k, j)); - } - - private void Hu4(Span dst, Span top, int topOffset) - { - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte l = top[topOffset - 5]; - - LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(i, j)); - var jk = LossyUtils.Avg2(j, k); - LossyUtils.Dst(dst, 2, 0, jk); - LossyUtils.Dst(dst, 0, 1, jk); - var kl = LossyUtils.Avg2(k, l); - LossyUtils.Dst(dst, 2, 1, kl); - LossyUtils.Dst(dst, 0, 2, kl); - LossyUtils.Dst(dst, 1, 0, LossyUtils.Avg3(i, j, k)); - var jkl = LossyUtils.Avg3(j, k, l); - LossyUtils.Dst(dst, 3, 0, jkl); - LossyUtils.Dst(dst, 1, 1, jkl); - var kll = LossyUtils.Avg3(k, l, l); - LossyUtils.Dst(dst, 3, 1, kll); - LossyUtils.Dst(dst, 1, 2, kll); - LossyUtils.Dst(dst, 3, 2, l); - LossyUtils.Dst(dst, 2, 2, l); - LossyUtils.Dst(dst, 0, 3, l); - LossyUtils.Dst(dst, 1, 3, l); - LossyUtils.Dst(dst, 2, 3, l); - LossyUtils.Dst(dst, 3, 3, l); - } - - private void Fill(Span dst, int value, int size) - { - for (int j = 0; j < size; ++j) - { - dst.Slice(j * WebPConstants.Bps, size).Fill((byte)value); - } - } - private void ImportBlock(Span src, int srcStride, Span dst, int w, int h, int size) { int dstIdx = 0; @@ -1328,10 +830,5 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { this.CountDown = countDown; } - - private byte Clip8b(int v) - { - return ((v & ~0xff) == 0) ? (byte)v : (v < 0) ? (byte)0 : (byte)255; - } } } diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index e60f5fdc1a..dbe9314e9a 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -65,12 +65,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// /// The filter header info's. /// - private Vp8FilterHeader filterHeader; + private readonly Vp8FilterHeader filterHeader; /// /// The segment infos. /// - private Vp8SegmentInfo[] segmentInfos; + private readonly Vp8SegmentInfo[] segmentInfos; /// /// Contextual macroblock infos. @@ -106,21 +106,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy /// private int uvAlpha; - /// - /// Fixed-point precision for RGB->YUV. - /// - private const int YuvFix = 16; - - 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 readonly byte[] zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; - private readonly byte[] averageBytesPerMb = { 50, 24, 16, 9, 7, 5, 3, 2 }; private const int NumMbSegments = 4; @@ -141,13 +126,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // TODO: filterStrength is hardcoded, should be configurable. private const int FilterStrength = 60; - // Diffusion weights. We under-correct a bit (15/16th of the error is actually - // diffused) to avoid 'rainbow' chessboard pattern of blocks at q~=0. - private const int C1 = 7; // fraction of error sent to the 4x4 block below - private const int C2 = 8; // fraction of error sent to the 4x4 block on the right - private const int DSHIFT = 4; - private const int DSCALE = 1; // storage descaling, needed to make the error fit byte - /// /// Initializes a new instance of the class. /// @@ -444,11 +422,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy { int targetSize = 0; // TODO: target size is hardcoded. float targetPsnr = 0.0f; // TODO: targetPsnr is hardcoded. - int method = this.method; bool doSearch = false; // TODO: doSearch hardcoded for now. - bool fastProbe = (method == 0 || method == 3) && !doSearch; + bool fastProbe = (this.method == 0 || this.method == 3) && !doSearch; int numPassLeft = this.entropyPasses; - Vp8RdLevel rdOpt = (method >= 3 || doSearch) ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; + Vp8RdLevel rdOpt = (this.method >= 3 || doSearch) ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; int nbMbs = this.mbw * this.mbh; var stats = new PassStats(targetSize, targetPsnr, QMin, QMax, this.quality); @@ -457,7 +434,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy // Fast mode: quick analysis pass over few mbs. Better than nothing. if (fastProbe) { - if (method == 3) + if (this.method == 3) { // We need more stats for method 3 to be reliable. nbMbs = (nbMbs > 200) ? nbMbs >> 1 : 100; @@ -996,7 +973,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); for (mode = 0; mode < numPredModes; ++mode) { - Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I16ModeOffsets[mode]); + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]); long score = (Vp8Sse16X16(src, reference) * WebPConstants.RdDistoMult) + (WebPConstants.Vp8FixedCostsI16[mode] * lambdaDi16); if (mode > 0 && WebPConstants.Vp8FixedCostsI16[mode] > bitLimit) @@ -1043,7 +1020,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy it.MakeIntra4Preds(); for (mode = 0; mode < numBModes; ++mode) { - Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I4ModeOffsets[mode]); + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); long score = (Vp8Sse4X4(src, reference) * WebPConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4); if (score < bestI4Score) { @@ -1092,7 +1069,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); for (mode = 0; mode < numPredModes; ++mode) { - Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8UvModeOffsets[mode]); + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]); long score = (Vp8Sse16X8(src, reference) * WebPConstants.RdDistoMult) + (WebPConstants.Vp8FixedCostsUv[mode] * lambdaDuv); if (score < bestUvScore) { @@ -1235,7 +1212,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private int ReconstructIntra16(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) { - Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I16ModeOffsets[mode]); + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]); Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); int nz = 0; int n; @@ -1245,25 +1222,25 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (n = 0; n < 16; n += 2) { - this.FTransform2(src.Slice(WebPLookupTables.Vp8Scan[n]), reference.Slice(WebPLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 16), tmpSpan.Slice((n + 1) * 16, 16)); + Vp8Encoding.FTransform2(src.Slice(WebPLookupTables.Vp8Scan[n]), reference.Slice(WebPLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 16), tmpSpan.Slice((n + 1) * 16, 16)); } - this.FTransformWht(tmp, dcTmp); - nz |= this.QuantizeBlock(dcTmp, rd.YDcLevels, dqm.Y2) << 24; + Vp8Encoding.FTransformWht(tmp, dcTmp); + nz |= QuantEnc.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 * 16] = tmp[(n + 1) * 16] = 0; - nz |= this.Quantize2Blocks(tmpSpan.Slice(n * 16, 32), rd.YAcLevels.AsSpan(n * 16, 32), dqm.Y1) << n; + nz |= QuantEnc.Quantize2Blocks(tmpSpan.Slice(n * 16, 32), rd.YAcLevels.AsSpan(n * 16, 32), dqm.Y1) << n; } // Transform back. LossyUtils.TransformWht(dcTmp, tmpSpan); for (n = 0; n < 16; n += 2) { - this.ITransform(reference.Slice(WebPLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 32), yuvOut.Slice(WebPLookupTables.Vp8Scan[n]), true); + Vp8Encoding.ITransform(reference.Slice(WebPLookupTables.Vp8Scan[n]), tmpSpan.Slice(n * 16, 32), yuvOut.Slice(WebPLookupTables.Vp8Scan[n]), true); } return nz; @@ -1271,18 +1248,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy private int ReconstructIntra4(Vp8EncIterator it, Vp8SegmentInfo dqm, Span levels, Span src, Span yuvOut, int mode) { - Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I4ModeOffsets[mode]); + Span reference = it.YuvP.AsSpan(Vp8Encoding.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); + Vp8Encoding.FTransform(src, reference, tmp); + var nz = QuantEnc.QuantizeBlock(tmp, levels, dqm.Y1); + Vp8Encoding.ITransform(reference, tmp, yuvOut, false); return nz; } private int ReconstructUv(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) { - Span reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8UvModeOffsets[mode]); + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]); Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); int nz = 0; int n; @@ -1290,267 +1267,38 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy for (n = 0; n < 8; n += 2) { - this.FTransform2( + Vp8Encoding.FTransform2( src.Slice(WebPLookupTables.Vp8ScanUv[n]), reference.Slice(WebPLookupTables.Vp8ScanUv[n]), tmp.AsSpan(n * 16, 16), tmp.AsSpan((n + 1) * 16, 16)); } - this.CorrectDCValues(it, dqm.Uv, tmp, rd); + QuantEnc.CorrectDcValues(it, dqm.Uv, tmp, rd); for (n = 0; n < 8; n += 2) { - nz |= this.Quantize2Blocks(tmp.AsSpan(n * 16, 32), rd.UvLevels.AsSpan(n * 16, 32), dqm.Uv) << n; + nz |= QuantEnc.Quantize2Blocks(tmp.AsSpan(n * 16, 32), rd.UvLevels.AsSpan(n * 16, 32), dqm.Uv) << n; } for (n = 0; n < 8; n += 2) { - this.ITransform(reference.Slice(WebPLookupTables.Vp8ScanUv[n]), tmp.AsSpan(n * 16, 32), yuvOut.Slice(WebPLookupTables.Vp8ScanUv[n]), true); + Vp8Encoding.ITransform(reference.Slice(WebPLookupTables.Vp8ScanUv[n]), tmp.AsSpan(n * 16, 32), yuvOut.Slice(WebPLookupTables.Vp8ScanUv[n]), true); } return nz << 16; } - private void FTransform2(Span src, Span reference, Span output, Span output2) - { - this.FTransform(src, reference, output); - this.FTransform(src.Slice(4), reference.Slice(4), output2); - } - - private void FTransform(Span src, Span reference, Span output) - { - int i; - var tmp = new int[16]; - int srcIdx = 0; - int refIdx = 0; - for (i = 0; i < 4; ++i) - { - int d0 = src[srcIdx] - reference[refIdx]; // 9bit dynamic range ([-255,255]) - int d1 = src[srcIdx + 1] - reference[refIdx + 1]; - int d2 = src[srcIdx + 2] - reference[refIdx + 2]; - int d3 = src[srcIdx + 3] - reference[refIdx + 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; - - srcIdx += WebPConstants.Bps; - refIdx += 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 CorrectDCValues(Vp8EncIterator it, Vp8Matrix mtx, short[] tmp, Vp8ModeScore rd) - { -#pragma warning disable SA1005 // Single line comments should begin with single space - // | top[0] | top[1] - // --------+--------+--------- - // left[0] | tmp[0] tmp[1] <-> err0 err1 - // left[1] | tmp[2] tmp[3] err2 err3 - // - // Final errors {err1,err2,err3} are preserved and later restored - // as top[]/left[] on the next block. -#pragma warning restore SA1005 // Single line comments should begin with single space - for (int ch = 0; ch <= 1; ++ch) - { - Span top = it.TopDerr.AsSpan((it.X * 4) + ch, 2); - Span left = it.LeftDerr.AsSpan(ch, 2); - int err0, err1, err2, err3; - Span c = tmp.AsSpan(ch * 4 * 16, 4 * 16); - c[0] += (short)(((C1 * top[0]) + (C2 * left[0])) >> (DSHIFT - DSCALE)); - err0 = QuantizeSingle(c, mtx); - c[1 * 16] += (short)(((C1 * top[1]) + (C2 * err0)) >> (DSHIFT - DSCALE)); - err1 = QuantizeSingle(c.Slice(1 * 16), mtx); - c[2 * 16] += (short)(((C1 * err0) + (C2 * left[1])) >> (DSHIFT - DSCALE)); - err2 = QuantizeSingle(c.Slice(2 * 16), mtx); - c[3 * 16] += (short)(((C1 * err1) + (C2 * err2)) >> (DSHIFT - DSCALE)); - err3 = QuantizeSingle(c.Slice(3 * 16), mtx); - - // TODO: set errors in rd - // rd->derr[ch][0] = (int8_t)err1; - // rd->derr[ch][1] = (int8_t)err2; - // rd->derr[ch][2] = (int8_t)err3; - } - } - - // Quantize as usual, but also compute and return the quantization error. - // Error is already divided by DSHIFT. - private static int QuantizeSingle(Span v, Vp8Matrix mtx) - { - int v0 = v[0]; - bool sign = v0 < 0; - if (sign) - { - v0 = -v0; - } - - if (v0 > (int)mtx.ZThresh[0]) - { - int qV = QuantDiv((uint)v0, mtx.IQ[0], mtx.Bias[0]) * mtx.Q[0]; - int err = v0 - qV; - v[0] = (short)(sign ? -qV : qV); - return (sign ? -err : err) >> DSCALE; - } - - v[0] = 0; - return (sign ? -v0 : v0) >> DSCALE; - } - - private void FTransformWht(Span input, Span output) - { - var tmp = new int[16]; - int i; - int inputIdx = 0; - for (i = 0; i < 4; ++i) - { - int a0 = input[inputIdx + (0 * 16)] + input[inputIdx + (2 * 16)]; // 13b - int a1 = input[inputIdx + (1 * 16)] + input[inputIdx + (3 * 16)]; - int a2 = input[inputIdx + (1 * 16)] - input[inputIdx + (3 * 16)]; - int a3 = input[inputIdx + (0 * 16)] - input[inputIdx + (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; - - inputIdx += 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 = this.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 = mtx.Q[j]; - uint iQ = mtx.IQ[j]; - uint b = mtx.Bias[j]; - int level = 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, Span input, Span dst, bool doTwo) - { - this.ITransformOne(reference, input, dst); - if (doTwo) - { - this.ITransformOne(reference.Slice(4), input.Slice(16), dst.Slice(4)); - } - } - - private void ITransformOne(Span reference, Span input, Span dst) - { - int i; -#pragma warning disable SA1312 // Variable names should begin with lower-case letter - var C = new int[4 * 4]; -#pragma warning restore SA1312 // Variable names should begin with lower-case letter - 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 = Mul(input[4], KC2) - Mul(input[12], KC1); - int d = Mul(input[4], KC1) + 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 = Mul(tmp[4], KC2) - Mul(tmp[12], KC1); - int d = Mul(tmp[4], KC1) + Mul(tmp[12], KC2); - Store(dst, reference, 0, i, a + d); - Store(dst, reference, 1, i, b + c); - Store(dst, reference, 2, i, b - c); - Store(dst, reference, 3, i, a - d); - tmp = tmp.Slice(1); - } - } - + /// + /// Converts the RGB values of the image to YUV. + /// + /// The pixel type of the image. + /// The image to convert. private void ConvertRgbToYuv(Image image) where TPixel : unmanaged, IPixel { int uvWidth = (image.Width + 1) >> 1; - bool hasAlpha = this.CheckNonOpaque(image); + bool hasAlpha = YuvConversion.CheckNonOpaque(image); // Temporary storage for accumulated R/G/B values during conversion to U/V. using IMemoryOwner tmpRgb = this.memoryAllocator.Allocate(4 * uvWidth); @@ -1564,18 +1312,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy Span nextRowSpan = image.GetPixelRowSpan(rowIndex + 1); if (!hasAlpha) { - this.AccumulateRgb(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); + YuvConversion.AccumulateRgb(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); } else { - this.AccumulateRgba(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); + YuvConversion.AccumulateRgba(rowSpan, nextRowSpan, tmpRgbSpan, image.Width); } - this.ConvertRgbaToUv(tmpRgbSpan, this.U.Slice(uvRowIndex * uvWidth), this.V.Slice(uvRowIndex * uvWidth), uvWidth); + YuvConversion.ConvertRgbaToUv(tmpRgbSpan, this.U.Slice(uvRowIndex * uvWidth), this.V.Slice(uvRowIndex * uvWidth), uvWidth); uvRowIndex++; - this.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width); - this.ConvertRgbaToY(nextRowSpan, this.Y.Slice((rowIndex + 1) * image.Width), image.Width); + YuvConversion.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width); + YuvConversion.ConvertRgbaToY(nextRowSpan, this.Y.Slice((rowIndex + 1) * image.Width), image.Width); } // Extra last row. @@ -1584,253 +1332,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy Span rowSpan = image.GetPixelRowSpan(rowIndex); if (!hasAlpha) { - this.AccumulateRgb(rowSpan, rowSpan, tmpRgbSpan, image.Width); - } - else - { - this.AccumulateRgba(rowSpan, rowSpan, tmpRgbSpan, image.Width); - } - - this.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width); - } - } - - // Returns true if alpha has non-0xff values. - private bool CheckNonOpaque(Image image) - where TPixel : unmanaged, IPixel - { - Rgba32 rgba = default; - for (int rowIndex = 0; rowIndex < image.Height; rowIndex++) - { - Span rowSpan = image.GetPixelRowSpan(rowIndex); - for (int x = 0; x < image.Width; x++) - { - TPixel color = rowSpan[x]; - color.ToRgba32(ref rgba); - if (rgba.A != 255) - { - return true; - } - } - } - - return false; - } - - private void ConvertRgbaToY(Span rowSpan, Span y, int width) - where TPixel : unmanaged, IPixel - { - Rgba32 rgba = default; - for (int x = 0; x < width; x++) - { - TPixel color = rowSpan[x]; - color.ToRgba32(ref rgba); - y[x] = (byte)RgbToY(rgba.R, rgba.G, rgba.B, YuvHalf); - } - } - - private void ConvertRgbaToUv(Span rgb, Span u, Span v, int width) - { - int rgbIdx = 0; - for (int i = 0; i < width; i += 1, rgbIdx += 4) - { - int r = rgb[rgbIdx], g = rgb[rgbIdx + 1], b = rgb[rgbIdx + 2]; - u[i] = (byte)RgbToU(r, g, b, YuvHalf << 2); - v[i] = (byte)RgbToV(r, g, b, YuvHalf << 2); - } - } - - private void AccumulateRgb(Span rowSpan, Span nextRowSpan, Span dst, int width) - where TPixel : unmanaged, IPixel - { - Rgba32 rgba0 = default; - Rgba32 rgba1 = default; - Rgba32 rgba2 = default; - Rgba32 rgba3 = default; - int i, j; - int dstIdx = 0; - for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) - { - TPixel color = rowSpan[j]; - color.ToRgba32(ref rgba0); - color = rowSpan[j + 1]; - color.ToRgba32(ref rgba1); - color = nextRowSpan[j]; - color.ToRgba32(ref rgba2); - color = nextRowSpan[j + 1]; - color.ToRgba32(ref rgba3); - - dst[dstIdx] = (ushort)LinearToGamma( - GammaToLinear(rgba0.R) + - GammaToLinear(rgba1.R) + - GammaToLinear(rgba2.R) + - GammaToLinear(rgba3.R), 0); - dst[dstIdx + 1] = (ushort)LinearToGamma( - GammaToLinear(rgba0.G) + - GammaToLinear(rgba1.G) + - GammaToLinear(rgba2.G) + - GammaToLinear(rgba3.G), 0); - dst[dstIdx + 2] = (ushort)LinearToGamma( - GammaToLinear(rgba0.B) + - GammaToLinear(rgba1.B) + - GammaToLinear(rgba2.B) + - GammaToLinear(rgba3.B), 0); - } - - if ((width & 1) != 0) - { - TPixel color = rowSpan[j]; - color.ToRgba32(ref rgba0); - color = nextRowSpan[j]; - color.ToRgba32(ref rgba1); - - dst[dstIdx] = (ushort)LinearToGamma(GammaToLinear(rgba0.R) + GammaToLinear(rgba1.R), 1); - dst[dstIdx + 1] = (ushort)LinearToGamma(GammaToLinear(rgba0.G) + GammaToLinear(rgba1.G), 1); - dst[dstIdx + 2] = (ushort)LinearToGamma(GammaToLinear(rgba0.B) + GammaToLinear(rgba1.B), 1); - } - } - - private void AccumulateRgba(Span rowSpan, Span nextRowSpan, Span dst, int width) - where TPixel : unmanaged, IPixel - { - Rgba32 rgba0 = default; - Rgba32 rgba1 = default; - Rgba32 rgba2 = default; - Rgba32 rgba3 = default; - int i, j; - int dstIdx = 0; - for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) - { - TPixel color = rowSpan[j]; - color.ToRgba32(ref rgba0); - color = rowSpan[j + 1]; - color.ToRgba32(ref rgba1); - color = nextRowSpan[j]; - color.ToRgba32(ref rgba2); - color = nextRowSpan[j + 1]; - color.ToRgba32(ref rgba3); - uint a = (uint)(rgba0.A + rgba1.A + rgba2.A + rgba3.A); - int r, g, b; - if (a == 4 * 0xff || a == 0) - { - r = (ushort)LinearToGamma( - GammaToLinear(rgba0.R) + - GammaToLinear(rgba1.R) + - GammaToLinear(rgba2.R) + - GammaToLinear(rgba3.R), 0); - g = (ushort)LinearToGamma( - GammaToLinear(rgba0.G) + - GammaToLinear(rgba1.G) + - GammaToLinear(rgba2.G) + - GammaToLinear(rgba3.G), 0); - b = (ushort)LinearToGamma( - GammaToLinear(rgba0.B) + - GammaToLinear(rgba1.B) + - GammaToLinear(rgba2.B) + - GammaToLinear(rgba3.B), 0); - } - else - { - r = LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); - g = LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); - b = LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); - } - - dst[dstIdx] = (ushort)r; - dst[dstIdx + 1] = (ushort)g; - dst[dstIdx + 2] = (ushort)b; - dst[dstIdx + 3] = (ushort)a; - } - - if ((width & 1) != 0) - { - TPixel color = rowSpan[j]; - color.ToRgba32(ref rgba0); - color = nextRowSpan[j]; - color.ToRgba32(ref rgba1); - uint a = (uint)(2u * (rgba0.A + rgba1.A)); - int r, g, b; - if (a == 4 * 0xff || a == 0) - { - r = (ushort)LinearToGamma(GammaToLinear(rgba0.R) + GammaToLinear(rgba1.R), 1); - g = (ushort)LinearToGamma(GammaToLinear(rgba0.G) + GammaToLinear(rgba1.G), 1); - b = (ushort)LinearToGamma(GammaToLinear(rgba0.B) + GammaToLinear(rgba1.B), 1); + YuvConversion.AccumulateRgb(rowSpan, rowSpan, tmpRgbSpan, image.Width); } else { - r = LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); - g = LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); - b = LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + YuvConversion.AccumulateRgba(rowSpan, rowSpan, tmpRgbSpan, image.Width); } - dst[dstIdx] = (ushort)r; - dst[dstIdx + 1] = (ushort)g; - dst[dstIdx + 2] = (ushort)b; - dst[dstIdx + 3] = (ushort)a; + YuvConversion.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width); } } - private static int LinearToGammaWeighted(byte rgb0, byte rgb1, byte rgb2, byte rgb3, byte a0, byte a1, byte a2, byte a3, uint totalA) - { - uint sum = (a0 * GammaToLinear(rgb0)) + (a1 * GammaToLinear(rgb1)) + (a2 * GammaToLinear(rgb2)) + (a3 * GammaToLinear(rgb3)); - return LinearToGamma((sum * WebPLookupTables.InvAlpha[totalA]) >> (WebPConstants.AlphaFix - 2), 0); - } - - // Convert a linear value 'v' to YUV_FIX+2 fixed-point precision - // U/V value, suitable for RGBToU/V calls. - [MethodImpl(InliningOptions.ShortMethod)] - private static int LinearToGamma(uint baseValue, int shift) - { - int y = Interpolate((int)(baseValue << shift)); // Final uplifted value. - return (y + WebPConstants.GammaTabRounder) >> WebPConstants.GammaTabFix; // Descale. - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint GammaToLinear(byte v) - { - return WebPLookupTables.GammaToLinearTab[v]; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Interpolate(int v) - { - int tabPos = v >> (WebPConstants.GammaTabFix + 2); // integer part. - int x = v & ((WebPConstants.GammaTabScale << 2) - 1); // fractional part. - int v0 = WebPLookupTables.LinearToGammaTab[tabPos]; - int v1 = WebPLookupTables.LinearToGammaTab[tabPos + 1]; - int y = (v1 * x) + (v0 * ((WebPConstants.GammaTabScale << 2) - x)); // interpolate - - return y; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int RgbToY(byte r, byte g, byte b, int rounding) - { - int luma = (16839 * r) + (33059 * g) + (6420 * b); - return (luma + rounding + (16 << YuvFix)) >> YuvFix; // No need to clip. - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int RgbToU(int r, int g, int b, int rounding) - { - int u = (-9719 * r) - (19081 * g) + (28800 * b); - return ClipUv(u, rounding); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int RgbToV(int r, int g, int b, int rounding) - { - int v = (+28800 * r) - (24116 * g) - (4684 * b); - return ClipUv(v, rounding); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int ClipUv(int uv, int rounding) - { - uv = (uv + rounding + (128 << (YuvFix + 2))) >> (YuvFix + 2); - return ((uv & ~0xff) == 0) ? uv : (uv < 0) ? 0 : 255; - } - [MethodImpl(InliningOptions.ShortMethod)] private static int FinalAlphaValue(int alpha) { @@ -1933,24 +1445,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy return (mse > 0 && size > 0) ? 10.0f * Math.Log10(255.0f * 255.0f * size / mse) : 99; } - [MethodImpl(InliningOptions.ShortMethod)] - private static int QuantDiv(uint n, uint iQ, uint b) - { - return (int)(((n * iQ) + b) >> WebPConstants.QFix); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Store(Span dst, Span reference, int x, int y, int v) - { - dst[x + (y * WebPConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebPConstants.Bps)] + (v >> 3)); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Mul(int a, int b) - { - return (a * b) >> 16; - } - [MethodImpl(InliningOptions.ShortMethod)] private static int GetProba(int a, int b) { diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs new file mode 100644 index 0000000000..3f07abe311 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs @@ -0,0 +1,664 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + /// + /// Methods for encoding a VP8 frame. + /// + internal static class Vp8Encoding + { + private const int KC1 = 20091 + (1 << 16); + + private const int KC2 = 35468; + + private static readonly byte[] Clip1 = new byte[255 + 510 + 1]; // clips [-255,510] to [0,255] + + private const int I16DC16 = 0 * 16 * WebPConstants.Bps; + + private const int I16TM16 = I16DC16 + 16; + + private const int I16VE16 = 1 * 16 * WebPConstants.Bps; + + private const int I16HE16 = I16VE16 + 16; + + private const int C8DC8 = 2 * 16 * WebPConstants.Bps; + + private const int C8TM8 = C8DC8 + (1 * 16); + + private const int C8VE8 = (2 * 16 * WebPConstants.Bps) + (8 * WebPConstants.Bps); + + private const int C8HE8 = C8VE8 + (1 * 16); + + public static readonly int[] Vp8I16ModeOffsets = { I16DC16, I16TM16, I16VE16, I16HE16 }; + + public static readonly int[] Vp8UvModeOffsets = { C8DC8, C8TM8, C8VE8, C8HE8 }; + + private const int I4DC4 = (3 * 16 * WebPConstants.Bps) + 0; + + private const int I4TM4 = I4DC4 + 4; + + private const int I4VE4 = I4DC4 + 8; + + private const int I4HE4 = I4DC4 + 12; + + private const int I4RD4 = I4DC4 + 16; + + private const int I4VR4 = I4DC4 + 20; + + private const int I4LD4 = I4DC4 + 24; + + private const int I4VL4 = I4DC4 + 28; + + private const int I4HD4 = (3 * 16 * WebPConstants.Bps) + (4 * WebPConstants.Bps); + + private const int I4HU4 = I4HD4 + 4; + + public static readonly int[] Vp8I4ModeOffsets = { I4DC4, I4TM4, I4VE4, I4HE4, I4RD4, I4VR4, I4LD4, I4VL4, I4HD4, I4HU4 }; + + static Vp8Encoding() + { + for (int i = -255; i <= 255 + 255; ++i) + { + Clip1[255 + i] = Clip8b(i); + } + } + + public static void ITransform(Span reference, Span input, Span dst, bool doTwo) + { + ITransformOne(reference, input, dst); + if (doTwo) + { + ITransformOne(reference.Slice(4), input.Slice(16), dst.Slice(4)); + } + } + + public static void ITransformOne(Span reference, Span input, Span dst) + { + int i; +#pragma warning disable SA1312 // Variable names should begin with lower-case letter + var C = new int[4 * 4]; +#pragma warning restore SA1312 // Variable names should begin with lower-case letter + 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 = Mul(input[4], KC2) - Mul(input[12], KC1); + int d = Mul(input[4], KC1) + 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 = Mul(tmp[4], KC2) - Mul(tmp[12], KC1); + int d = Mul(tmp[4], KC1) + Mul(tmp[12], KC2); + Store(dst, reference, 0, i, a + d); + Store(dst, reference, 1, i, b + c); + Store(dst, reference, 2, i, b - c); + Store(dst, reference, 3, i, a - d); + tmp = tmp.Slice(1); + } + } + + public static void FTransform2(Span src, Span reference, Span output, Span output2) + { + FTransform(src, reference, output); + FTransform(src.Slice(4), reference.Slice(4), output2); + } + + public static void FTransform(Span src, Span reference, Span output) + { + int i; + var tmp = new int[16]; + int srcIdx = 0; + int refIdx = 0; + for (i = 0; i < 4; ++i) + { + int d0 = src[srcIdx] - reference[refIdx]; // 9bit dynamic range ([-255,255]) + int d1 = src[srcIdx + 1] - reference[refIdx + 1]; + int d2 = src[srcIdx + 2] - reference[refIdx + 2]; + int d3 = src[srcIdx + 3] - reference[refIdx + 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; + + srcIdx += WebPConstants.Bps; + refIdx += 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); + } + } + + public static void FTransformWht(Span input, Span output) + { + var tmp = new int[16]; + int i; + int inputIdx = 0; + for (i = 0; i < 4; ++i) + { + int a0 = input[inputIdx + (0 * 16)] + input[inputIdx + (2 * 16)]; // 13b + int a1 = input[inputIdx + (1 * 16)] + input[inputIdx + (3 * 16)]; + int a2 = input[inputIdx + (1 * 16)] - input[inputIdx + (3 * 16)]; + int a3 = input[inputIdx + (0 * 16)] - input[inputIdx + (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; + + inputIdx += 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); + } + } + + // luma 16x16 prediction (paragraph 12.3). + public static void EncPredLuma16(Span dst, Span left, Span top) + { + DcMode(dst.Slice(I16DC16), left, top, 16, 16, 5); + VerticalPred(dst.Slice(I16VE16), top, 16); + HorizontalPred(dst.Slice(I16HE16), left, 16); + TrueMotion(dst.Slice(I16TM16), left, top, 16); + } + + // Chroma 8x8 prediction (paragraph 12.2). + public static void EncPredChroma8(Span dst, Span left, Span top) + { + // U block. + DcMode(dst.Slice(C8DC8), left, top, 8, 8, 4); + VerticalPred(dst.Slice(C8VE8), top, 8); + HorizontalPred(dst.Slice(C8HE8), left, 8); + TrueMotion(dst.Slice(C8TM8), left, top, 8); + + // V block. + dst = dst.Slice(8); + if (top != null) + { + top = top.Slice(8); + } + + if (left != null) + { + left = left.Slice(16); + } + + DcMode(dst.Slice(C8DC8), left, top, 8, 8, 4); + VerticalPred(dst.Slice(C8VE8), top, 8); + HorizontalPred(dst.Slice(C8HE8), left, 8); + TrueMotion(dst.Slice(C8TM8), left, top, 8); + } + + // Left samples are top[-5 .. -2], top_left is top[-1], top are + // located at top[0..3], and top right is top[4..7] + public static void EncPredLuma4(Span dst, Span top, int topOffset) + { + Dc4(dst.Slice(I4DC4), top, topOffset); + Tm4(dst.Slice(I4TM4), top, topOffset); + Ve4(dst.Slice(I4VE4), top, topOffset); + He4(dst.Slice(I4HE4), top, topOffset); + Rd4(dst.Slice(I4RD4), top, topOffset); + Vr4(dst.Slice(I4VR4), top, topOffset); + Ld4(dst.Slice(I4LD4), top, topOffset); + Vl4(dst.Slice(I4VL4), top, topOffset); + Hd4(dst.Slice(I4HD4), top, topOffset); + Hu4(dst.Slice(I4HU4), top, topOffset); + } + + private static void VerticalPred(Span dst, Span top, int size) + { + if (top != null) + { + for (int j = 0; j < size; ++j) + { + top.Slice(0, size).CopyTo(dst.Slice(j * WebPConstants.Bps)); + } + } + else + { + Fill(dst, 127, size); + } + } + + public static void HorizontalPred(Span dst, Span left, int size) + { + if (left != null) + { + left = left.Slice(1); // in the reference implementation, left starts at - 1. + for (int j = 0; j < size; ++j) + { + dst.Slice(j * WebPConstants.Bps, size).Fill(left[j]); + } + } + else + { + Fill(dst, 129, size); + } + } + + public static void TrueMotion(Span dst, Span left, Span top, int size) + { + if (left != null) + { + if (top != null) + { + Span clip = Clip1.AsSpan(255 - left[0]); // left [0] instead of left[-1], original left starts at -1 + for (int y = 0; y < size; ++y) + { + Span clipTable = clip.Slice(left[y + 1]); // left[y] + for (int x = 0; x < size; ++x) + { + dst[x] = clipTable[top[x]]; + } + + dst = dst.Slice(WebPConstants.Bps); + } + } + else + { + Vp8Encoding.HorizontalPred(dst, left, size); + } + } + else + { + // true motion without left samples (hence: with default 129 value) + // is equivalent to VE prediction where you just copy the top samples. + // Note that if top samples are not available, the default value is + // then 129, and not 127 as in the VerticalPred case. + if (top != null) + { + Vp8Encoding.VerticalPred(dst, top, size); + } + else + { + Fill(dst, 129, size); + } + } + } + + private static void DcMode(Span dst, Span left, Span top, int size, int round, int shift) + { + int dc = 0; + int j; + if (top != null) + { + for (j = 0; j < size; ++j) + { + dc += top[j]; + } + + if (left != null) + { + // top and left present. + left = left.Slice(1); // in the reference implementation, left starts at -1. + for (j = 0; j < size; ++j) + { + dc += left[j]; + } + } + else + { + // top, but no left. + dc += dc; + } + + dc = (dc + round) >> shift; + } + else if (left != null) + { + // left but no top. + left = left.Slice(1); // in the reference implementation, left starts at -1. + for (j = 0; j < size; ++j) + { + dc += left[j]; + } + + dc += dc; + dc = (dc + round) >> shift; + } + else + { + // no top, no left, nothing. + dc = 0x80; + } + + Fill(dst, dc, size); + } + + private static void Dc4(Span dst, Span top, int topOffset) + { + uint dc = 4; + int i; + for (i = 0; i < 4; ++i) + { + dc += (uint)(top[topOffset + i] + top[topOffset - 5 + i]); + } + + Fill(dst, (int)(dc >> 3), 4); + } + + private static void Tm4(Span dst, Span top, int topOffset) + { + Span clip = Clip1.AsSpan(255 - top[topOffset - 1]); + for (int y = 0; y < 4; ++y) + { + Span clipTable = clip.Slice(top[topOffset - 2 - y]); + for (int x = 0; x < 4; ++x) + { + dst[x] = clipTable[top[topOffset + x]]; + } + + dst = dst.Slice(WebPConstants.Bps); + } + } + + private static void Ve4(Span dst, Span top, int topOffset) + { + // vertical + byte[] vals = + { + LossyUtils.Avg3(top[topOffset - 1], top[topOffset], top[topOffset + 1]), + LossyUtils.Avg3(top[topOffset], top[topOffset + 1], top[topOffset + 2]), + LossyUtils.Avg3(top[topOffset + 1], top[topOffset + 2], top[topOffset + 3]), + LossyUtils.Avg3(top[topOffset + 2], top[topOffset + 3], top[topOffset + 4]) + }; + + for (int i = 0; i < 4; ++i) + { + vals.AsSpan().CopyTo(dst.Slice(i * WebPConstants.Bps)); + } + } + + private static void He4(Span dst, Span top, int topOffset) + { + // horizontal + byte x = top[topOffset - 1]; + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte l = top[topOffset - 5]; + + uint val = 0x01010101U * LossyUtils.Avg3(x, i, j); + BinaryPrimitives.WriteUInt32BigEndian(dst, val); + val = 0x01010101U * LossyUtils.Avg3(i, j, k); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(1 * WebPConstants.Bps), val); + val = 0x01010101U * LossyUtils.Avg3(j, k, l); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(2 * WebPConstants.Bps), val); + val = 0x01010101U * LossyUtils.Avg3(k, l, l); + BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(3 * WebPConstants.Bps), val); + } + + private static void Rd4(Span dst, Span top, int topOffset) + { + byte x = top[topOffset - 1]; + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte l = top[topOffset - 5]; + byte a = top[topOffset]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + byte d = top[topOffset + 3]; + + LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(j, k, l)); + var ijk = LossyUtils.Avg3(i, j, k); + LossyUtils.Dst(dst, 0, 2, ijk); + LossyUtils.Dst(dst, 1, 3, ijk); + var xij = LossyUtils.Avg3(x, i, j); + LossyUtils.Dst(dst, 0, 1, xij); + LossyUtils.Dst(dst, 1, 2, xij); + LossyUtils.Dst(dst, 2, 3, xij); + var axi = LossyUtils.Avg3(a, x, i); + LossyUtils.Dst(dst, 0, 0, axi); + LossyUtils.Dst(dst, 1, 1, axi); + LossyUtils.Dst(dst, 2, 2, axi); + LossyUtils.Dst(dst, 3, 3, axi); + var bax = LossyUtils.Avg3(b, a, x); + LossyUtils.Dst(dst, 1, 0, bax); + LossyUtils.Dst(dst, 2, 1, bax); + LossyUtils.Dst(dst, 3, 2, bax); + var cba = LossyUtils.Avg3(c, b, a); + LossyUtils.Dst(dst, 2, 0, cba); + LossyUtils.Dst(dst, 3, 1, cba); + LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(d, c, b)); + } + + private static void Vr4(Span dst, Span top, int topOffset) + { + byte x = top[topOffset - 1]; + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte a = top[topOffset]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + byte d = top[topOffset + 3]; + + var xa = LossyUtils.Avg2(x, a); + LossyUtils.Dst(dst, 0, 0, xa); + LossyUtils.Dst(dst, 1, 2, xa); + var ab = LossyUtils.Avg2(a, b); + LossyUtils.Dst(dst, 1, 0, ab); + LossyUtils.Dst(dst, 2, 2, ab); + var bc = LossyUtils.Avg2(b, c); + LossyUtils.Dst(dst, 2, 0, bc); + LossyUtils.Dst(dst, 3, 2, bc); + LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg2(c, d)); + LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(k, j, i)); + LossyUtils.Dst(dst, 0, 2, LossyUtils.Avg3(j, i, x)); + var ixa = LossyUtils.Avg3(i, x, a); + LossyUtils.Dst(dst, 0, 1, ixa); + LossyUtils.Dst(dst, 1, 3, ixa); + var xab = LossyUtils.Avg3(x, a, b); + LossyUtils.Dst(dst, 1, 1, xab); + LossyUtils.Dst(dst, 2, 3, xab); + var abc = LossyUtils.Avg3(a, b, c); + LossyUtils.Dst(dst, 2, 1, abc); + LossyUtils.Dst(dst, 3, 3, abc); + LossyUtils.Dst(dst, 3, 1, LossyUtils.Avg3(b, c, d)); + } + + private static void Ld4(Span dst, Span top, int topOffset) + { + byte a = top[topOffset + 0]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + byte d = top[topOffset + 3]; + byte e = top[topOffset + 4]; + byte f = top[topOffset + 5]; + byte g = top[topOffset + 6]; + byte h = top[topOffset + 7]; + + LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg3(a, b, c)); + var bcd = LossyUtils.Avg3(b, c, d); + LossyUtils.Dst(dst, 1, 0, bcd); + LossyUtils.Dst(dst, 0, 1, bcd); + var cde = LossyUtils.Avg3(c, d, e); + LossyUtils.Dst(dst, 2, 0, cde); + LossyUtils.Dst(dst, 1, 1, cde); + LossyUtils.Dst(dst, 0, 2, cde); + var def = LossyUtils.Avg3(d, e, f); + LossyUtils.Dst(dst, 3, 0, def); + LossyUtils.Dst(dst, 2, 1, def); + LossyUtils.Dst(dst, 1, 2, def); + LossyUtils.Dst(dst, 0, 3, def); + var efg = LossyUtils.Avg3(e, f, g); + LossyUtils.Dst(dst, 3, 1, efg); + LossyUtils.Dst(dst, 2, 2, efg); + LossyUtils.Dst(dst, 1, 3, efg); + var fgh = LossyUtils.Avg3(f, g, h); + LossyUtils.Dst(dst, 3, 2, fgh); + LossyUtils.Dst(dst, 2, 3, fgh); + LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(g, h, h)); + } + + private static void Vl4(Span dst, Span top, int topOffset) + { + byte a = top[topOffset + 0]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + byte d = top[topOffset + 3]; + byte e = top[topOffset + 4]; + byte f = top[topOffset + 5]; + byte g = top[topOffset + 6]; + byte h = top[topOffset + 7]; + + LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(a, b)); + var bc = LossyUtils.Avg2(b, c); + LossyUtils.Dst(dst, 1, 0, bc); + LossyUtils.Dst(dst, 0, 2, bc); + var cd = LossyUtils.Avg2(c, d); + LossyUtils.Dst(dst, 2, 0, cd); + LossyUtils.Dst(dst, 1, 2, cd); + var de = LossyUtils.Avg2(d, e); + LossyUtils.Dst(dst, 3, 0, de); + LossyUtils.Dst(dst, 2, 2, de); + LossyUtils.Dst(dst, 0, 1, LossyUtils.Avg3(a, b, c)); + var bcd = LossyUtils.Avg3(b, c, d); + LossyUtils.Dst(dst, 1, 1, bcd); + LossyUtils.Dst(dst, 0, 3, bcd); + var cde = LossyUtils.Avg3(c, d, e); + LossyUtils.Dst(dst, 2, 1, cde); + LossyUtils.Dst(dst, 1, 3, cde); + var def = LossyUtils.Avg3(d, e, f); + LossyUtils.Dst(dst, 3, 1, def); + LossyUtils.Dst(dst, 2, 3, def); + LossyUtils.Dst(dst, 3, 2, LossyUtils.Avg3(e, f, g)); + LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(f, g, h)); + } + + private static void Hd4(Span dst, Span top, int topOffset) + { + byte x = top[topOffset - 1]; + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte l = top[topOffset - 5]; + byte a = top[topOffset]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + + var ix = LossyUtils.Avg2(i, x); + LossyUtils.Dst(dst, 0, 0, ix); + LossyUtils.Dst(dst, 2, 1, ix); + var ji = LossyUtils.Avg2(j, i); + LossyUtils.Dst(dst, 0, 1, ji); + LossyUtils.Dst(dst, 2, 2, ji); + var kj = LossyUtils.Avg2(k, j); + LossyUtils.Dst(dst, 0, 2, kj); + LossyUtils.Dst(dst, 2, 3, kj); + LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg2(l, k)); + LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(a, b, c)); + LossyUtils.Dst(dst, 2, 0, LossyUtils.Avg3(x, a, b)); + var ixa = LossyUtils.Avg3(i, x, a); + LossyUtils.Dst(dst, 1, 0, ixa); + LossyUtils.Dst(dst, 3, 1, ixa); + var jix = LossyUtils.Avg3(j, i, x); + LossyUtils.Dst(dst, 1, 1, jix); + LossyUtils.Dst(dst, 3, 2, jix); + var kji = LossyUtils.Avg3(k, j, i); + LossyUtils.Dst(dst, 1, 2, kji); + LossyUtils.Dst(dst, 3, 3, kji); + LossyUtils.Dst(dst, 1, 3, LossyUtils.Avg3(l, k, j)); + } + + private static void Hu4(Span dst, Span top, int topOffset) + { + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte l = top[topOffset - 5]; + + LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(i, j)); + var jk = LossyUtils.Avg2(j, k); + LossyUtils.Dst(dst, 2, 0, jk); + LossyUtils.Dst(dst, 0, 1, jk); + var kl = LossyUtils.Avg2(k, l); + LossyUtils.Dst(dst, 2, 1, kl); + LossyUtils.Dst(dst, 0, 2, kl); + LossyUtils.Dst(dst, 1, 0, LossyUtils.Avg3(i, j, k)); + var jkl = LossyUtils.Avg3(j, k, l); + LossyUtils.Dst(dst, 3, 0, jkl); + LossyUtils.Dst(dst, 1, 1, jkl); + var kll = LossyUtils.Avg3(k, l, l); + LossyUtils.Dst(dst, 3, 1, kll); + LossyUtils.Dst(dst, 1, 2, kll); + LossyUtils.Dst(dst, 3, 2, l); + LossyUtils.Dst(dst, 2, 2, l); + LossyUtils.Dst(dst, 0, 3, l); + LossyUtils.Dst(dst, 1, 3, l); + LossyUtils.Dst(dst, 2, 3, l); + LossyUtils.Dst(dst, 3, 3, l); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void Fill(Span dst, int value, int size) + { + for (int j = 0; j < size; ++j) + { + dst.Slice(j * WebPConstants.Bps, size).Fill((byte)value); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static byte Clip8b(int v) + { + return ((v & ~0xff) == 0) ? (byte)v : (v < 0) ? (byte)0 : (byte)255; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void Store(Span dst, Span reference, int x, int y, int v) + { + dst[x + (y * WebPConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebPConstants.Bps)] + (v >> 3)); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int Mul(int a, int b) + { + return (a * b) >> 16; + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs new file mode 100644 index 0000000000..bd919e93d6 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs @@ -0,0 +1,260 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossy +{ + internal static class YuvConversion + { + /// + /// Fixed-point precision for RGB->YUV. + /// + private const int YuvFix = 16; + + private const int YuvHalf = 1 << (YuvFix - 1); + + /// + /// Checks if the image is not opaque. + /// + /// The pixel type of the image, + /// The image to check. + /// Returns true if alpha has non-0xff values. + public static bool CheckNonOpaque(Image image) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba = default; + for (int rowIndex = 0; rowIndex < image.Height; rowIndex++) + { + Span rowSpan = image.GetPixelRowSpan(rowIndex); + for (int x = 0; x < image.Width; x++) + { + TPixel color = rowSpan[x]; + color.ToRgba32(ref rgba); + if (rgba.A != 255) + { + return true; + } + } + } + + return false; + } + + public static void ConvertRgbaToY(Span rowSpan, Span y, int width) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba = default; + for (int x = 0; x < width; x++) + { + TPixel color = rowSpan[x]; + color.ToRgba32(ref rgba); + y[x] = (byte)RgbToY(rgba.R, rgba.G, rgba.B, YuvHalf); + } + } + + public static void ConvertRgbaToUv(Span rgb, Span u, Span v, int width) + { + int rgbIdx = 0; + for (int i = 0; i < width; i += 1, rgbIdx += 4) + { + int r = rgb[rgbIdx], g = rgb[rgbIdx + 1], b = rgb[rgbIdx + 2]; + u[i] = (byte)RgbToU(r, g, b, YuvHalf << 2); + v[i] = (byte)RgbToV(r, g, b, YuvHalf << 2); + } + } + + public static void AccumulateRgb(Span rowSpan, Span nextRowSpan, Span dst, int width) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba0 = default; + Rgba32 rgba1 = default; + Rgba32 rgba2 = default; + Rgba32 rgba3 = default; + int i, j; + int dstIdx = 0; + for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) + { + TPixel color = rowSpan[j]; + color.ToRgba32(ref rgba0); + color = rowSpan[j + 1]; + color.ToRgba32(ref rgba1); + color = nextRowSpan[j]; + color.ToRgba32(ref rgba2); + color = nextRowSpan[j + 1]; + color.ToRgba32(ref rgba3); + + dst[dstIdx] = (ushort)LinearToGamma( + GammaToLinear(rgba0.R) + + GammaToLinear(rgba1.R) + + GammaToLinear(rgba2.R) + + GammaToLinear(rgba3.R), 0); + dst[dstIdx + 1] = (ushort)LinearToGamma( + GammaToLinear(rgba0.G) + + GammaToLinear(rgba1.G) + + GammaToLinear(rgba2.G) + + GammaToLinear(rgba3.G), 0); + dst[dstIdx + 2] = (ushort)LinearToGamma( + GammaToLinear(rgba0.B) + + GammaToLinear(rgba1.B) + + GammaToLinear(rgba2.B) + + GammaToLinear(rgba3.B), 0); + } + + if ((width & 1) != 0) + { + TPixel color = rowSpan[j]; + color.ToRgba32(ref rgba0); + color = nextRowSpan[j]; + color.ToRgba32(ref rgba1); + + dst[dstIdx] = (ushort)LinearToGamma(GammaToLinear(rgba0.R) + GammaToLinear(rgba1.R), 1); + dst[dstIdx + 1] = (ushort)LinearToGamma(GammaToLinear(rgba0.G) + GammaToLinear(rgba1.G), 1); + dst[dstIdx + 2] = (ushort)LinearToGamma(GammaToLinear(rgba0.B) + GammaToLinear(rgba1.B), 1); + } + } + + public static void AccumulateRgba(Span rowSpan, Span nextRowSpan, Span dst, int width) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba0 = default; + Rgba32 rgba1 = default; + Rgba32 rgba2 = default; + Rgba32 rgba3 = default; + int i, j; + int dstIdx = 0; + for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) + { + TPixel color = rowSpan[j]; + color.ToRgba32(ref rgba0); + color = rowSpan[j + 1]; + color.ToRgba32(ref rgba1); + color = nextRowSpan[j]; + color.ToRgba32(ref rgba2); + color = nextRowSpan[j + 1]; + color.ToRgba32(ref rgba3); + uint a = (uint)(rgba0.A + rgba1.A + rgba2.A + rgba3.A); + int r, g, b; + if (a == 4 * 0xff || a == 0) + { + r = (ushort)LinearToGamma( + GammaToLinear(rgba0.R) + + GammaToLinear(rgba1.R) + + GammaToLinear(rgba2.R) + + GammaToLinear(rgba3.R), 0); + g = (ushort)LinearToGamma( + GammaToLinear(rgba0.G) + + GammaToLinear(rgba1.G) + + GammaToLinear(rgba2.G) + + GammaToLinear(rgba3.G), 0); + b = (ushort)LinearToGamma( + GammaToLinear(rgba0.B) + + GammaToLinear(rgba1.B) + + GammaToLinear(rgba2.B) + + GammaToLinear(rgba3.B), 0); + } + else + { + r = LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + g = LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + b = LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + } + + dst[dstIdx] = (ushort)r; + dst[dstIdx + 1] = (ushort)g; + dst[dstIdx + 2] = (ushort)b; + dst[dstIdx + 3] = (ushort)a; + } + + if ((width & 1) != 0) + { + TPixel color = rowSpan[j]; + color.ToRgba32(ref rgba0); + color = nextRowSpan[j]; + color.ToRgba32(ref rgba1); + uint a = (uint)(2u * (rgba0.A + rgba1.A)); + int r, g, b; + if (a == 4 * 0xff || a == 0) + { + r = (ushort)LinearToGamma(GammaToLinear(rgba0.R) + GammaToLinear(rgba1.R), 1); + g = (ushort)LinearToGamma(GammaToLinear(rgba0.G) + GammaToLinear(rgba1.G), 1); + b = (ushort)LinearToGamma(GammaToLinear(rgba0.B) + GammaToLinear(rgba1.B), 1); + } + else + { + r = LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + g = LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + b = LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a); + } + + dst[dstIdx] = (ushort)r; + dst[dstIdx + 1] = (ushort)g; + dst[dstIdx + 2] = (ushort)b; + dst[dstIdx + 3] = (ushort)a; + } + } + + private static int LinearToGammaWeighted(byte rgb0, byte rgb1, byte rgb2, byte rgb3, byte a0, byte a1, byte a2, byte a3, uint totalA) + { + uint sum = (a0 * GammaToLinear(rgb0)) + (a1 * GammaToLinear(rgb1)) + (a2 * GammaToLinear(rgb2)) + (a3 * GammaToLinear(rgb3)); + return LinearToGamma((sum * WebPLookupTables.InvAlpha[totalA]) >> (WebPConstants.AlphaFix - 2), 0); + } + + // Convert a linear value 'v' to YUV_FIX+2 fixed-point precision + // U/V value, suitable for RGBToU/V calls. + [MethodImpl(InliningOptions.ShortMethod)] + private static int LinearToGamma(uint baseValue, int shift) + { + int y = Interpolate((int)(baseValue << shift)); // Final uplifted value. + return (y + WebPConstants.GammaTabRounder) >> WebPConstants.GammaTabFix; // Descale. + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint GammaToLinear(byte v) + { + return WebPLookupTables.GammaToLinearTab[v]; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int Interpolate(int v) + { + int tabPos = v >> (WebPConstants.GammaTabFix + 2); // integer part. + int x = v & ((WebPConstants.GammaTabScale << 2) - 1); // fractional part. + int v0 = WebPLookupTables.LinearToGammaTab[tabPos]; + int v1 = WebPLookupTables.LinearToGammaTab[tabPos + 1]; + int y = (v1 * x) + (v0 * ((WebPConstants.GammaTabScale << 2) - x)); // interpolate + + return y; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int RgbToY(byte r, byte g, byte b, int rounding) + { + int luma = (16839 * r) + (33059 * g) + (6420 * b); + return (luma + rounding + (16 << YuvFix)) >> YuvFix; // No need to clip. + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int RgbToU(int r, int g, int b, int rounding) + { + int u = (-9719 * r) - (19081 * g) + (28800 * b); + return ClipUv(u, rounding); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int RgbToV(int r, int g, int b, int rounding) + { + int v = (+28800 * r) - (24116 * g) - (4684 * b); + return ClipUv(v, rounding); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int ClipUv(int uv, int rounding) + { + uv = (uv + rounding + (128 << (YuvFix + 2))) >> (YuvFix + 2); + return ((uv & ~0xff) == 0) ? uv : (uv < 0) ? 0 : 255; + } + } +}