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;
+ }
+ }
+}