Browse Source

SetSegmentProbas

pull/1552/head
Brian Popow 6 years ago
parent
commit
4b30b9294b
  1. 6
      src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs
  2. 40
      src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs
  3. 8
      src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs
  4. 331
      src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs

6
src/ImageSharp/Formats/WebP/Lossy/LossyUtils.cs

@ -784,6 +784,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
return (byte)((v & ~0xff) == 0 ? v : (v < 0) ? 0 : 255);
}
// Cost of coding one event with probability 'proba'.
public static int Vp8BitCost(int bit, byte proba)
{
return bit == 0 ? WebPLookupTables.Vp8EntropyCost[proba] : WebPLookupTables.Vp8EntropyCost[255 - proba];
}
// Complex In-loop filtering (Paragraph 15.3)
private static void FilterLoop24(
Span<byte> p,

40
src/ImageSharp/Formats/WebP/Lossy/Vp8EncProba.cs

@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
public byte SkipProba { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to use the skip probability. Note: we always use SkipProba for now.
/// Gets or sets a value indicating whether to use the skip probability.
/// </summary>
public bool UseSkipProba { get; set; }
@ -131,13 +131,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
{
Vp8ProbaArray p = this.Coeffs[ctype][band].Probabilities[ctx];
Span<ushort> table = this.LevelCost[ctype][band].Costs.AsSpan(ctx * MaxVariableLevel);
int cost0 = (ctx > 0) ? this.BitCost(1, p.Probabilities[0]) : 0;
int costBase = this.BitCost(1, p.Probabilities[1]) + cost0;
int cost0 = (ctx > 0) ? LossyUtils.Vp8BitCost(1, p.Probabilities[0]) : 0;
int costBase = LossyUtils.Vp8BitCost(1, p.Probabilities[1]) + cost0;
int v;
table[0] = (ushort)(this.BitCost(0, p.Probabilities[1]) + cost0);
table[0] = (ushort)(LossyUtils.Vp8BitCost(0, p.Probabilities[1]) + cost0);
for (v = 1; v <= MaxVariableLevel; ++v)
{
table[v] = (ushort)(costBase + this.VariableLevelCost(v, p.Probabilities));
table[v] = (ushort)(costBase + VariableLevelCost(v, p.Probabilities));
}
// Starting at level 67 and up, the variable part of the cost is actually constant.
@ -175,11 +175,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
int total = (int)((stats >> 16) & 0xffff);
int updateProba = WebPLookupTables.CoeffsUpdateProba[t, b, c, p];
int oldP = WebPLookupTables.DefaultCoeffsProba[t, b, c, p];
int newP = this.CalcTokenProba(nb, total);
int oldCost = this.BranchCost(nb, total, oldP) + this.BitCost(0, (byte)updateProba);
int newCost = this.BranchCost(nb, total, newP) + this.BitCost(1, (byte)updateProba) + (8 * 256);
int newP = CalcTokenProba(nb, total);
int oldCost = BranchCost(nb, total, oldP) + LossyUtils.Vp8BitCost(0, (byte)updateProba);
int newCost = BranchCost(nb, total, newP) + LossyUtils.Vp8BitCost(1, (byte)updateProba) + (8 * 256);
bool useNewP = oldCost > newCost;
size += this.BitCost(useNewP ? 1 : 0, (byte)updateProba);
size += LossyUtils.Vp8BitCost(useNewP ? 1 : 0, (byte)updateProba);
if (useNewP)
{
// Only use proba that seem meaningful enough.
@ -204,13 +204,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
{
int nbMbs = mbw * mbh;
int nbEvents = this.NbSkip;
this.SkipProba = (byte)this.CalcSkipProba(nbEvents, nbMbs);
this.SkipProba = (byte)CalcSkipProba(nbEvents, nbMbs);
this.UseSkipProba = this.SkipProba < SkipProbaThreshold;
int size = 256;
if (this.UseSkipProba)
{
size += (nbEvents * this.BitCost(1, this.SkipProba)) + ((nbMbs - nbEvents) * this.BitCost(0, this.SkipProba));
size += (nbEvents * LossyUtils.Vp8BitCost(1, this.SkipProba)) + ((nbMbs - nbEvents) * LossyUtils.Vp8BitCost(0, this.SkipProba));
size += 8 * 256; // cost of signaling the skipProba itself.
}
@ -234,12 +234,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
}
private int CalcSkipProba(long nb, long total)
private static int CalcSkipProba(long nb, long total)
{
return (int)(total != 0 ? (total - nb) * 255 / total : 255);
}
private int VariableLevelCost(int level, Span<byte> probas)
private static int VariableLevelCost(int level, Span<byte> probas)
{
int pattern = WebPLookupTables.Vp8LevelCodes[level - 1][0];
int bits = WebPLookupTables.Vp8LevelCodes[level - 1][1];
@ -248,7 +248,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
{
if ((pattern & 1) != 0)
{
cost += this.BitCost(bits & 1, probas[i]);
cost += LossyUtils.Vp8BitCost(bits & 1, probas[i]);
}
bits >>= 1;
@ -260,21 +260,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
// Collect statistics and deduce probabilities for next coding pass.
// Return the total bit-cost for coding the probability updates.
private int CalcTokenProba(int nb, int total)
private static int CalcTokenProba(int nb, int total)
{
return nb != 0 ? (255 - (nb * 255 / total)) : 255;
}
// Cost of coding 'nb' 1's and 'total-nb' 0's using 'proba' probability.
private int BranchCost(int nb, int total, int proba)
private static int BranchCost(int nb, int total, int proba)
{
return (nb * this.BitCost(1, (byte)proba)) + ((total - nb) * this.BitCost(0, (byte)proba));
}
// Cost of coding one event with probability 'proba'.
private int BitCost(int bit, byte proba)
{
return bit == 0 ? WebPLookupTables.Vp8EntropyCost[proba] : WebPLookupTables.Vp8EntropyCost[255 - proba];
return (nb * LossyUtils.Vp8BitCost(1, (byte)proba)) + ((total - nb) * LossyUtils.Vp8BitCost(0, (byte)proba));
}
}
}

8
src/ImageSharp/Formats/WebP/Lossy/Vp8EncSegmentHeader.cs

@ -22,13 +22,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
public int NumSegments { get; }
/// <summary>
/// Gets a value indicating whether to update the segment map or not. Must be false if there's only 1 segment.
/// Gets or sets a value indicating whether to update the segment map or not. Must be false if there's only 1 segment.
/// </summary>
public bool UpdateMap { get; }
public bool UpdateMap { get; set; }
/// <summary>
/// Gets the bit-cost for transmitting the segment map.
/// Gets or sets the bit-cost for transmitting the segment map.
/// </summary>
public int Size { get; }
public int Size { get; set; }
}
}

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

@ -258,9 +258,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
// Analysis is done, proceed to actual encoding.
this.segmentHeader = new Vp8EncSegmentHeader(4);
this.AssignSegments(segmentInfos, alphas);
this.SetSegmentParams(segmentInfos, this.quality);
this.SetSegmentProbas(segmentInfos);
this.ResetStats();
this.SetLoopParams(segmentInfos, this.quality);
// TODO: EncodeAlpha();
// Stats-collection loop.
@ -415,7 +413,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
else
{
stats.Value = this.GetPsnr(distortion, pixelCount);
stats.Value = GetPsnr(distortion, pixelCount);
}
return sizeP0;
@ -427,7 +425,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.SetSegmentParams(dqm, q);
// Compute segment probabilities.
this.SetSegmentProbas(dqm);
this.SetSegmentProbas();
this.ResetStats();
}
@ -577,8 +575,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
{
int alpha = 255 * (centers[n] - mid) / (max - min);
int beta = 255 * (centers[n] - min) / (max - min);
dqm[n].Alpha = this.Clip(alpha, -127, 127);
dqm[n].Beta = this.Clip(beta, 0, 255);
dqm[n].Alpha = Clip(alpha, -127, 127);
dqm[n].Beta = Clip(beta, 0, 255);
}
}
@ -587,7 +585,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
int nb = this.segmentHeader.NumSegments;
int snsStrength = 50; // TODO: Spatial Noise Shaping, hardcoded for now.
double amp = WebPConstants.SnsToDq * snsStrength / 100.0d / 128.0d;
double cBase = this.QualityToCompression(quality / 100.0d);
double cBase = QualityToCompression(quality / 100.0d);
for (int i = 0; i < nb; ++i)
{
// We modulate the base coefficient to accommodate for the quantization
@ -595,7 +593,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
double expn = 1.0d - (amp * dqm[i].Alpha);
double c = Math.Pow(cBase, expn);
int q = (int)(127.0d * (1.0d - c));
dqm[i].Quant = this.Clip(q, 0, 127);
dqm[i].Quant = Clip(q, 0, 127);
}
// uvAlpha is normally spread around ~60. The useful range is
@ -607,23 +605,60 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.dqUvAc = this.dqUvAc * snsStrength / 100;
// and make it safe.
this.dqUvAc = this.Clip(this.dqUvAc, WebPConstants.QuantEncMinDqUv, WebPConstants.QuantEncMaxDqUv);
this.dqUvAc = Clip(this.dqUvAc, WebPConstants.QuantEncMinDqUv, WebPConstants.QuantEncMaxDqUv);
// We also boost the dc-uv-quant a little, based on sns-strength, since
// U/V channels are quite more reactive to high quants (flat DC-blocks
// tend to appear, and are unpleasant).
this.dqUvDc = -4 * snsStrength / 100;
this.dqUvDc = this.Clip(this.dqUvDc, -15, 15); // 4bit-signed max allowed
this.dqUvDc = Clip(this.dqUvDc, -15, 15); // 4bit-signed max allowed
this.SetupMatrices(dqm);
}
private void SetSegmentProbas(Vp8SegmentInfo[] dqm)
private void SetSegmentProbas()
{
// var p = new int[4];
// int n;
var p = new int[NumMbSegments];
int n;
for (n = 0; n < this.mbw * this.mbh; ++n)
{
Vp8MacroBlockInfo mb = this.mbInfo[n];
++p[mb.Segment];
}
if (this.segmentHeader.NumSegments > 1)
{
byte[] probas = this.proba.Segments;
probas[0] = (byte)GetProba(p[0] + p[1], p[2] + p[3]);
probas[1] = (byte)GetProba(p[0], p[1]);
probas[2] = (byte)GetProba(p[2], p[3]);
this.segmentHeader.UpdateMap = (probas[0] != 255) || (probas[1] != 255) || (probas[2] != 255);
if (!this.segmentHeader.UpdateMap)
{
this.ResetSegments();
}
this.segmentHeader.Size = (p[0] * (LossyUtils.Vp8BitCost(0, probas[0]) + LossyUtils.Vp8BitCost(0, probas[1]))) +
(p[1] * (LossyUtils.Vp8BitCost(0, probas[0]) + LossyUtils.Vp8BitCost(1, probas[1]))) +
(p[2] * (LossyUtils.Vp8BitCost(1, probas[0]) + LossyUtils.Vp8BitCost(0, probas[2]))) +
(p[3] * (LossyUtils.Vp8BitCost(1, probas[0]) + LossyUtils.Vp8BitCost(1, probas[2])));
}
else
{
this.segmentHeader.UpdateMap = false;
this.segmentHeader.Size = 0;
}
}
// TODO: SetSegmentProbas
private void ResetSegments()
{
int n;
for (n = 0; n < this.mbw * this.mbh; ++n)
{
this.mbInfo[n].Segment = 0;
}
}
private void ResetStats()
@ -644,14 +679,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
m.Y2 = new Vp8Matrix();
m.Uv = new Vp8Matrix();
m.Y1.Q[0] = WebPLookupTables.DcTable[this.Clip(q, 0, 127)];
m.Y1.Q[1] = WebPLookupTables.AcTable[this.Clip(q, 0, 127)];
m.Y1.Q[0] = WebPLookupTables.DcTable[Clip(q, 0, 127)];
m.Y1.Q[1] = WebPLookupTables.AcTable[Clip(q, 0, 127)];
m.Y2.Q[0] = (ushort)(WebPLookupTables.DcTable[this.Clip(q, 0, 127)] * 2);
m.Y2.Q[1] = WebPLookupTables.AcTable2[this.Clip(q, 0, 127)];
m.Y2.Q[0] = (ushort)(WebPLookupTables.DcTable[Clip(q, 0, 127)] * 2);
m.Y2.Q[1] = WebPLookupTables.AcTable2[Clip(q, 0, 127)];
m.Uv.Q[0] = WebPLookupTables.DcTable[this.Clip(q + this.dqUvDc, 0, 117)];
m.Uv.Q[1] = WebPLookupTables.AcTable[this.Clip(q + this.dqUvAc, 0, 127)];
m.Uv.Q[0] = WebPLookupTables.DcTable[Clip(q + this.dqUvDc, 0, 117)];
m.Uv.Q[1] = WebPLookupTables.AcTable[Clip(q + this.dqUvAc, 0, 127)];
var qi4 = m.Y1.Expand(0);
var qi16 = m.Y2.Expand(1);
@ -702,7 +737,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
// Final susceptibility mix.
bestAlpha = ((3 * bestAlpha) + bestUvAlpha + 2) >> 2;
bestAlpha = this.FinalAlphaValue(bestAlpha);
bestAlpha = FinalAlphaValue(bestAlpha);
alphas[bestAlpha]++;
it.CurrentMacroBlockInfo.Alpha = bestAlpha; // For later remapping.
@ -757,7 +792,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
for (mode = 0; mode < numPredModes; ++mode)
{
Span<byte> reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I16ModeOffsets[mode]);
long score = (this.Vp8Sse16X16(src, reference) * WebPConstants.RdDistoMult) + (WebPConstants.Vp8FixedCostsI16[mode] * lambdaDi16);
long score = (Vp8Sse16X16(src, reference) * WebPConstants.RdDistoMult) + (WebPConstants.Vp8FixedCostsI16[mode] * lambdaDi16);
if (mode > 0 && WebPConstants.Vp8FixedCostsI16[mode] > bitLimit)
{
@ -774,7 +809,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
if (it.X == 0 || it.Y == 0)
{
// Avoid starting a checkerboard resonance from the border. See bug #432 of libwebp.
if (this.IsFlatSource16(src))
if (IsFlatSource16(src))
{
bestMode = (it.X == 0) ? 0 : 2;
tryBothModes = false; // Stick to i16.
@ -804,7 +839,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
for (mode = 0; mode < numBModes; ++mode)
{
Span<byte> reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8I4ModeOffsets[mode]);
long score = (this.Vp8Sse4X4(src, reference) * WebPConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4);
long score = (Vp8Sse4X4(src, reference) * WebPConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4);
if (score < bestI4Score)
{
bestI4Mode = mode;
@ -852,7 +887,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
for (mode = 0; mode < numPredModes; ++mode)
{
Span<byte> reference = it.YuvP.AsSpan(Vp8EncIterator.Vp8UvModeOffsets[mode]);
long score = (this.Vp8Sse16X8(src, reference) * WebPConstants.RdDistoMult) + (WebPConstants.Vp8FixedCostsUv[mode] * lambdaDuv);
long score = (Vp8Sse16X8(src, reference) * WebPConstants.RdDistoMult) + (WebPConstants.Vp8FixedCostsUv[mode] * lambdaDuv);
if (score < bestUvScore)
{
bestMode = mode;
@ -1041,7 +1076,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
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> src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc);
Span<byte> src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc);
int nz = 0;
int n;
var tmp = new short[8 * 16];
@ -1063,7 +1098,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
for (n = 0; n < 8; n += 2)
{
nz |= this.Quantize2Blocks(tmp.AsSpan(n * 16, 32), rd.UvLevels.AsSpan(n, 32), dqm.Uv) << n;
nz |= this.Quantize2Blocks(tmp.AsSpan(n * 16, 32), rd.UvLevels.AsSpan(n * 16, 32), dqm.Uv) << n;
}
for (n = 0; n < 8; n += 2)
@ -1176,7 +1211,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
uint q = mtx.Q[j];
uint iQ = mtx.IQ[j];
uint b = mtx.Bias[j];
int level = this.QuantDiv(coeff, iQ, b);
int level = QuantDiv(coeff, iQ, b);
if (level > MaxLevel)
{
level = MaxLevel;
@ -1225,8 +1260,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
// vertical pass.
int a = input[0] + input[8];
int b = input[0] - input[8];
int c = this.Mul(input[4], KC2) - this.Mul(input[12], KC1);
int d = this.Mul(input[4], KC1) + this.Mul(input[12], KC2);
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;
@ -1242,12 +1277,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
int dc = tmp[0] + 4;
int a = dc + tmp[8];
int b = dc - tmp[8];
int c = this.Mul(tmp[4], KC2) - this.Mul(tmp[12], KC1);
int d = this.Mul(tmp[4], KC1) + this.Mul(tmp[12], KC2);
this.Store(dst, reference, 0, i, (byte)(a + d));
this.Store(dst, reference, 1, i, (byte)(b + c));
this.Store(dst, reference, 2, i, (byte)(b - c));
this.Store(dst, reference, 3, i, (byte)(a - d));
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, (byte)(a + d));
Store(dst, reference, 1, i, (byte)(b + c));
Store(dst, reference, 2, i, (byte)(b - c));
Store(dst, reference, 3, i, (byte)(a - d));
tmp = tmp.Slice(1);
}
}
@ -1259,47 +1294,45 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
bool hasAlpha = this.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))
using IMemoryOwner<ushort> tmpRgb = this.memoryAllocator.Allocate<ushort>(4 * uvWidth);
Span<ushort> tmpRgbSpan = tmpRgb.GetSpan();
int uvRowIndex = 0;
int rowIndex;
for (rowIndex = 0; rowIndex < image.Height - 1; rowIndex += 2)
{
Span<ushort> tmpRgbSpan = tmpRgb.GetSpan();
int uvRowIndex = 0;
int rowIndex;
for (rowIndex = 0; rowIndex < image.Height - 1; rowIndex += 2)
// Downsample U/V planes, two rows at a time.
Span<TPixel> rowSpan = image.GetPixelRowSpan(rowIndex);
Span<TPixel> nextRowSpan = image.GetPixelRowSpan(rowIndex + 1);
if (!hasAlpha)
{
// Downsample U/V planes, two rows at a time.
Span<TPixel> rowSpan = image.GetPixelRowSpan(rowIndex);
Span<TPixel> nextRowSpan = image.GetPixelRowSpan(rowIndex + 1);
if (!hasAlpha)
{
this.AccumulateRgb(rowSpan, nextRowSpan, tmpRgbSpan, image.Width);
}
else
{
this.AccumulateRgba(rowSpan, nextRowSpan, tmpRgbSpan, image.Width);
}
this.AccumulateRgb(rowSpan, nextRowSpan, tmpRgbSpan, image.Width);
}
else
{
this.AccumulateRgba(rowSpan, nextRowSpan, tmpRgbSpan, image.Width);
}
this.ConvertRgbaToUv(tmpRgbSpan, this.U.Slice(uvRowIndex * uvWidth), this.V.Slice(uvRowIndex * uvWidth), uvWidth);
uvRowIndex++;
this.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);
}
this.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width);
this.ConvertRgbaToY(nextRowSpan, this.Y.Slice((rowIndex + 1) * image.Width), image.Width);
}
// Extra last row.
if ((image.Height & 1) != 0)
// Extra last row.
if ((image.Height & 1) != 0)
{
Span<TPixel> rowSpan = image.GetPixelRowSpan(rowIndex);
if (!hasAlpha)
{
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);
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);
}
}
@ -1333,7 +1366,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
{
TPixel color = rowSpan[x];
color.ToRgba32(ref rgba);
y[x] = (byte)this.RgbToY(rgba.R, rgba.G, rgba.B, YuvHalf);
y[x] = (byte)RgbToY(rgba.R, rgba.G, rgba.B, YuvHalf);
}
}
@ -1343,8 +1376,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
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)this.RgbToU(r, g, b, YuvHalf << 2);
v[i] = (byte)this.RgbToV(r, g, b, YuvHalf << 2);
u[i] = (byte)RgbToU(r, g, b, YuvHalf << 2);
v[i] = (byte)RgbToV(r, g, b, YuvHalf << 2);
}
}
@ -1368,21 +1401,21 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
color = nextRowSpan[j + 1];
color.ToRgba32(ref rgba3);
dst[dstIdx] = (ushort)this.LinearToGamma(
this.GammaToLinear(rgba0.R) +
this.GammaToLinear(rgba1.R) +
this.GammaToLinear(rgba2.R) +
this.GammaToLinear(rgba3.R), 0);
dst[dstIdx + 1] = (ushort)this.LinearToGamma(
this.GammaToLinear(rgba0.G) +
this.GammaToLinear(rgba1.G) +
this.GammaToLinear(rgba2.G) +
this.GammaToLinear(rgba3.G), 0);
dst[dstIdx + 2] = (ushort)this.LinearToGamma(
this.GammaToLinear(rgba0.B) +
this.GammaToLinear(rgba1.B) +
this.GammaToLinear(rgba2.B) +
this.GammaToLinear(rgba3.B), 0);
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)
@ -1392,9 +1425,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
color = nextRowSpan[j];
color.ToRgba32(ref rgba1);
dst[dstIdx] = (ushort)this.LinearToGamma(this.GammaToLinear(rgba0.R) + this.GammaToLinear(rgba1.R), 1);
dst[dstIdx + 1] = (ushort)this.LinearToGamma(this.GammaToLinear(rgba0.G) + this.GammaToLinear(rgba1.G), 1);
dst[dstIdx + 2] = (ushort)this.LinearToGamma(this.GammaToLinear(rgba0.B) + this.GammaToLinear(rgba1.B), 1);
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);
}
}
@ -1421,27 +1454,27 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
int r, g, b;
if (a == 4 * 0xff || a == 0)
{
r = (ushort)this.LinearToGamma(
this.GammaToLinear(rgba0.R) +
this.GammaToLinear(rgba1.R) +
this.GammaToLinear(rgba2.R) +
this.GammaToLinear(rgba3.R), 0);
g = (ushort)this.LinearToGamma(
this.GammaToLinear(rgba0.G) +
this.GammaToLinear(rgba1.G) +
this.GammaToLinear(rgba2.G) +
this.GammaToLinear(rgba3.G), 0);
b = (ushort)this.LinearToGamma(
this.GammaToLinear(rgba0.B) +
this.GammaToLinear(rgba1.B) +
this.GammaToLinear(rgba2.B) +
this.GammaToLinear(rgba3.B), 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 = this.LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a);
g = this.LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a);
b = this.LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a);
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;
@ -1460,15 +1493,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
int r, g, b;
if (a == 4 * 0xff || a == 0)
{
r = (ushort)this.LinearToGamma(this.GammaToLinear(rgba0.R) + this.GammaToLinear(rgba1.R), 1);
g = (ushort)this.LinearToGamma(this.GammaToLinear(rgba0.G) + this.GammaToLinear(rgba1.G), 1);
b = (ushort)this.LinearToGamma(this.GammaToLinear(rgba0.B) + this.GammaToLinear(rgba1.B), 1);
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 = this.LinearToGammaWeighted(rgba0.R, rgba1.R, rgba2.R, rgba3.R, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a);
g = this.LinearToGammaWeighted(rgba0.G, rgba1.G, rgba2.G, rgba3.G, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a);
b = this.LinearToGammaWeighted(rgba0.B, rgba1.B, rgba2.B, rgba3.B, rgba0.A, rgba1.A, rgba2.A, rgba3.A, a);
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;
@ -1478,29 +1511,29 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
}
private int LinearToGammaWeighted(byte rgb0, byte rgb1, byte rgb2, byte rgb3, byte a0, byte a1, byte a2, byte a3, uint totalA)
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 * this.GammaToLinear(rgb0)) + (a1 * this.GammaToLinear(rgb1)) + (a2 * this.GammaToLinear(rgb2)) + (a3 * this.GammaToLinear(rgb3));
return this.LinearToGamma((sum * WebPLookupTables.InvAlpha[totalA]) >> (WebPConstants.AlphaFix - 2), 0);
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 int LinearToGamma(uint baseValue, int shift)
private static int LinearToGamma(uint baseValue, int shift)
{
int y = this.Interpolate((int)(baseValue << shift)); // Final uplifted value.
int y = Interpolate((int)(baseValue << shift)); // Final uplifted value.
return (y + WebPConstants.GammaTabRounder) >> WebPConstants.GammaTabFix; // Descale.
}
[MethodImpl(InliningOptions.ShortMethod)]
private uint GammaToLinear(byte v)
private static uint GammaToLinear(byte v)
{
return WebPLookupTables.GammaToLinearTab[v];
}
[MethodImpl(InliningOptions.ShortMethod)]
private int Interpolate(int v)
private static int Interpolate(int v)
{
int tabPos = v >> (WebPConstants.GammaTabFix + 2); // integer part.
int x = v & ((WebPConstants.GammaTabScale << 2) - 1); // fractional part.
@ -1512,65 +1545,65 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
[MethodImpl(InliningOptions.ShortMethod)]
private int RgbToY(byte r, byte g, byte b, int rounding)
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 int RgbToU(int r, int g, int b, int rounding)
private static int RgbToU(int r, int g, int b, int rounding)
{
int u = (-9719 * r) - (19081 * g) + (28800 * b);
return this.ClipUv(u, rounding);
return ClipUv(u, rounding);
}
[MethodImpl(InliningOptions.ShortMethod)]
private int RgbToV(int r, int g, int b, int rounding)
private static int RgbToV(int r, int g, int b, int rounding)
{
int v = (+28800 * r) - (24116 * g) - (4684 * b);
return this.ClipUv(v, rounding);
return ClipUv(v, rounding);
}
[MethodImpl(InliningOptions.ShortMethod)]
private int ClipUv(int uv, int rounding)
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 int FinalAlphaValue(int alpha)
private static int FinalAlphaValue(int alpha)
{
alpha = WebPConstants.MaxAlpha - alpha;
return this.Clip(alpha, 0, WebPConstants.MaxAlpha);
return Clip(alpha, 0, WebPConstants.MaxAlpha);
}
[MethodImpl(InliningOptions.ShortMethod)]
private int Clip(int v, int min, int max)
private static int Clip(int v, int min, int max)
{
return (v < min) ? min : (v > max) ? max : v;
}
[MethodImpl(InliningOptions.ShortMethod)]
private int Vp8Sse16X16(Span<byte> a, Span<byte> b)
private static int Vp8Sse16X16(Span<byte> a, Span<byte> b)
{
return this.GetSse(a, b, 16, 16);
return GetSse(a, b, 16, 16);
}
private int Vp8Sse16X8(Span<byte> a, Span<byte> b)
private static int Vp8Sse16X8(Span<byte> a, Span<byte> b)
{
return this.GetSse(a, b, 16, 8);
return GetSse(a, b, 16, 8);
}
[MethodImpl(InliningOptions.ShortMethod)]
private int Vp8Sse4X4(Span<byte> a, Span<byte> b)
private static int Vp8Sse4X4(Span<byte> a, Span<byte> b)
{
return this.GetSse(a, b, 4, 4);
return GetSse(a, b, 4, 4);
}
[MethodImpl(InliningOptions.ShortMethod)]
private int GetSse(Span<byte> a, Span<byte> b, int w, int h)
private static int GetSse(Span<byte> a, Span<byte> b, int w, int h)
{
int count = 0;
int aOffset = 0;
@ -1591,7 +1624,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
[MethodImpl(InliningOptions.ShortMethod)]
private bool IsFlatSource16(Span<byte> src)
private static bool IsFlatSource16(Span<byte> src)
{
uint v = src[0] * 0x01010101u;
Span<byte> vSpan = BitConverter.GetBytes(v).AsSpan();
@ -1614,7 +1647,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
/// is around q=75. Internally, our "good" middle is around c=50. So we
/// map accordingly using linear piece-wise function
/// </summary>
private double QualityToCompression(double c)
private static double QualityToCompression(double c)
{
double linearC = (c < 0.75) ? c * (2.0d / 3.0d) : (2.0d * c) - 1.0d;
@ -1630,27 +1663,35 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
[MethodImpl(InliningOptions.ShortMethod)]
private double GetPsnr(long mse, long size)
private static double GetPsnr(long mse, long size)
{
return (mse > 0 && size > 0) ? 10.0f * Math.Log10(255.0f * 255.0f * size / mse) : 99;
}
[MethodImpl(InliningOptions.ShortMethod)]
private int QuantDiv(uint n, uint iQ, uint b)
private static int QuantDiv(uint n, uint iQ, uint b)
{
return (int)(((n * iQ) + b) >> WebPConstants.QFix);
}
[MethodImpl(InliningOptions.ShortMethod)]
private void Store(Span<byte> dst, Span<byte> reference, int x, int y, byte v)
private static void Store(Span<byte> dst, Span<byte> reference, int x, int y, byte v)
{
dst[x + (y * WebPConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebPConstants.Bps)] + (v >> 3));
}
[MethodImpl(InliningOptions.ShortMethod)]
private int Mul(int a, int b)
private static int Mul(int a, int b)
{
return (a * b) >> 16;
}
[MethodImpl(InliningOptions.ShortMethod)]
private static int GetProba(int a, int b)
{
int total = a + b;
return (total == 0) ? 255 // that's the default probability.
: ((255 * a) + (total / 2)) / total; // rounded proba
}
}
}

Loading…
Cancel
Save