Browse Source

Refactor Vp8Encoder

pull/1552/head
Brian Popow 6 years ago
parent
commit
7a6cb423aa
  1. 1
      src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs
  2. 1
      src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs
  3. 137
      src/ImageSharp/Formats/WebP/Lossy/QuantEnc.cs
  4. 513
      src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs
  5. 580
      src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
  6. 664
      src/ImageSharp/Formats/WebP/Lossy/Vp8Encoding.cs
  7. 260
      src/ImageSharp/Formats/WebP/Lossy/YuvConversion.cs

1
src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs

@ -197,7 +197,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter
/// <param name="extraSize">The extra size in bytes needed.</param>
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)
{

1
src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs

@ -185,7 +185,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter
/// <param name="extraSize">The extra size in bytes needed.</param>
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;

137
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
{
/// <summary>
/// Quantization methods.
/// </summary>
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<short> input, Span<short> 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<short> input, Span<short> 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<short> 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<sbyte> top = it.TopDerr.AsSpan((it.X * 4) + ch, 2);
Span<sbyte> left = it.LeftDerr.AsSpan(ch, 2);
int err0, err1, err2, err3;
Span<short> 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);
}
}
}

513
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
/// </summary>
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<byte> left = this.X != 0 ? this.YLeft.AsSpan() : null;
Span<byte> 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<byte> left = this.X != 0 ? this.UvLeft.AsSpan() : null;
Span<byte> 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<byte> dst, Span<byte> left, Span<byte> 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<byte> dst, Span<byte> left, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> left, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> left, Span<byte> top, int size)
{
if (left != null)
{
if (top != null)
{
Span<byte> 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<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> top, int topOffset)
{
Span<byte> clip = this.clip1.AsSpan(255 - top[topOffset - 1]);
for (int y = 0; y < 4; ++y)
{
Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> 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<byte> src, int srcStride, Span<byte> 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;
}
}
}

580
src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs

@ -65,12 +65,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
/// <summary>
/// The filter header info's.
/// </summary>
private Vp8FilterHeader filterHeader;
private readonly Vp8FilterHeader filterHeader;
/// <summary>
/// The segment infos.
/// </summary>
private Vp8SegmentInfo[] segmentInfos;
private readonly Vp8SegmentInfo[] segmentInfos;
/// <summary>
/// Contextual macroblock infos.
@ -106,21 +106,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
/// </summary>
private int uvAlpha;
/// <summary>
/// Fixed-point precision for RGB->YUV.
/// </summary>
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
/// <summary>
/// Initializes a new instance of the <see cref="Vp8Encoder"/> class.
/// </summary>
@ -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<byte> src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc);
for (mode = 0; mode < numPredModes; ++mode)
{
Span<byte> reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I16ModeOffsets[mode]);
Span<byte> 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<byte> reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I4ModeOffsets[mode]);
Span<byte> 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<byte> src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc);
for (mode = 0; mode < numPredModes; ++mode)
{
Span<byte> reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8UvModeOffsets[mode]);
Span<byte> 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<byte> yuvOut, int mode)
{
Span<byte> reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I16ModeOffsets[mode]);
Span<byte> reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]);
Span<byte> 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<short> levels, Span<byte> src, Span<byte> yuvOut, int mode)
{
Span<byte> reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I4ModeOffsets[mode]);
Span<byte> 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<byte> yuvOut, int mode)
{
Span<byte> reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8UvModeOffsets[mode]);
Span<byte> reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]);
Span<byte> 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<byte> src, Span<byte> reference, Span<short> output, Span<short> output2)
{
this.FTransform(src, reference, output);
this.FTransform(src.Slice(4), reference.Slice(4), output2);
}
private void FTransform(Span<byte> src, Span<byte> reference, Span<short> 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<sbyte> top = it.TopDerr.AsSpan((it.X * 4) + ch, 2);
Span<sbyte> left = it.LeftDerr.AsSpan(ch, 2);
int err0, err1, err2, err3;
Span<short> 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<short> 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<short> input, Span<short> 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<short> input, Span<short> 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<short> input, Span<short> 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<byte> reference, Span<short> input, Span<byte> 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<byte> reference, Span<short> input, Span<byte> 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<int> 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);
}
}
/// <summary>
/// Converts the RGB values of the image to YUV.
/// </summary>
/// <typeparam name="TPixel">The pixel type of the image.</typeparam>
/// <param name="image">The image to convert.</param>
private void ConvertRgbToYuv<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
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<ushort> tmpRgb = this.memoryAllocator.Allocate<ushort>(4 * uvWidth);
@ -1564,18 +1312,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
Span<TPixel> 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<TPixel> 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<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
Rgba32 rgba = default;
for (int rowIndex = 0; rowIndex < image.Height; rowIndex++)
{
Span<TPixel> 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<TPixel>(Span<TPixel> rowSpan, Span<byte> y, int width)
where TPixel : unmanaged, IPixel<TPixel>
{
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<ushort> rgb, Span<byte> u, Span<byte> 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<TPixel>(Span<TPixel> rowSpan, Span<TPixel> nextRowSpan, Span<ushort> dst, int width)
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel>(Span<TPixel> rowSpan, Span<TPixel> nextRowSpan, Span<ushort> dst, int width)
where TPixel : unmanaged, IPixel<TPixel>
{
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<byte> dst, Span<byte> 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)
{

664
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
{
/// <summary>
/// Methods for encoding a VP8 frame.
/// </summary>
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<byte> reference, Span<short> input, Span<byte> dst, bool doTwo)
{
ITransformOne(reference, input, dst);
if (doTwo)
{
ITransformOne(reference.Slice(4), input.Slice(16), dst.Slice(4));
}
}
public static void ITransformOne(Span<byte> reference, Span<short> input, Span<byte> 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<int> 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<byte> src, Span<byte> reference, Span<short> output, Span<short> output2)
{
FTransform(src, reference, output);
FTransform(src.Slice(4), reference.Slice(4), output2);
}
public static void FTransform(Span<byte> src, Span<byte> reference, Span<short> 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<short> input, Span<short> 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<byte> dst, Span<byte> left, Span<byte> 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<byte> dst, Span<byte> left, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> left, Span<byte> top, int size)
{
if (left != null)
{
if (top != null)
{
Span<byte> 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<byte> 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<byte> dst, Span<byte> left, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> top, int topOffset)
{
Span<byte> clip = Clip1.AsSpan(255 - top[topOffset - 1]);
for (int y = 0; y < 4; ++y)
{
Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> dst, Span<byte> 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<byte> 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<byte> dst, Span<byte> 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;
}
}
}

260
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
{
/// <summary>
/// Fixed-point precision for RGB->YUV.
/// </summary>
private const int YuvFix = 16;
private const int YuvHalf = 1 << (YuvFix - 1);
/// <summary>
/// Checks if the image is not opaque.
/// </summary>
/// <typeparam name="TPixel">The pixel type of the image,</typeparam>
/// <param name="image">The image to check.</param>
/// <returns>Returns true if alpha has non-0xff values.</returns>
public static bool CheckNonOpaque<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
Rgba32 rgba = default;
for (int rowIndex = 0; rowIndex < image.Height; rowIndex++)
{
Span<TPixel> 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<TPixel>(Span<TPixel> rowSpan, Span<byte> y, int width)
where TPixel : unmanaged, IPixel<TPixel>
{
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<ushort> rgb, Span<byte> u, Span<byte> 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<TPixel>(Span<TPixel> rowSpan, Span<TPixel> nextRowSpan, Span<ushort> dst, int width)
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel>(Span<TPixel> rowSpan, Span<TPixel> nextRowSpan, Span<ushort> dst, int width)
where TPixel : unmanaged, IPixel<TPixel>
{
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;
}
}
}
Loading…
Cancel
Save