Browse Source

Implement backward reference encoder

pull/1552/head
Brian Popow 6 years ago
parent
commit
5860f8c84f
  1. 471
      src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs
  2. 46
      src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs
  3. 42
      src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs
  4. 14
      src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs
  5. 10
      src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs
  6. 52
      src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs
  7. 8
      src/ImageSharp/Formats/WebP/Lossless/Vp8LRefsCursor.cs
  8. 30
      src/ImageSharp/Formats/WebP/WebPEncoderCore.cs

471
src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs

@ -2,12 +2,17 @@
// Licensed under the GNU Affero General Public License, Version 3.
using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
internal class BackwardReferenceEncoder
{
/// <summary>
/// Maximum bit length.
/// </summary>
public const int MaxLengthBits = 12;
private const int HashBits = 18;
private const int HashSize = 1 << HashBits;
@ -35,11 +40,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
/// </summary>
private const int WindowSize = (1 << WindowSizeBits) - 120;
/// <summary>
/// Maximum bit length.
/// </summary>
private const int MaxLengthBits = 12;
/// <summary>
/// We want the max value to be attainable and stored in MaxLengthBits bits.
/// </summary>
@ -122,8 +122,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
// Find the best match interval at each pixel, defined by an offset to the
// pixel and a length. The right-most pixel cannot match anything to the right
// (hence a best length of 0) and the left-most pixel nothing to the left
// (hence an offset of 0).
// (hence a best length of 0) and the left-most pixel nothing to the left (hence an offset of 0).
p.OffsetLength[0] = p.OffsetLength[size - 1] = 0;
for (int basePosition = size - 2; basePosition > 0;)
{
@ -227,8 +226,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
}
}
}
int foo = 0;
}
/// <summary>
@ -239,20 +236,20 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
/// The return value is the pointer to the best of the two backward refs viz,
/// refs[0] or refs[1].
/// </summary>
private static Vp8LBackwardRefs[] GetBackwardReferences(int width, int height, uint[] bgra, int quality,
int lz77TypesToTry, int[] cacheBits, Vp8LHashChain[] hashChain, Vp8LBackwardRefs[] best, Vp8LBackwardRefs[] worst)
public static Vp8LBackwardRefs GetBackwardReferences(int width, int height, Span<uint> bgra, int quality,
int lz77TypesToTry, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs best, Vp8LBackwardRefs worst)
{
var histo = new Vp8LHistogram[WebPConstants.MaxColorCacheBits];
int lz77Type = 0;
int lz77TypeBest = 0;
double bitCostBest = -1;
int[] cacheBitsInitial = cacheBits;
// TODO: var hashChainBox = new Vp8LHashChain();
int cacheBitsInitial = cacheBits;
Vp8LHashChain hashChainBox = null;
for (lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1)
{
int res = 0;
double bitCost;
int[] cacheBitsTmp = cacheBitsInitial;
int cacheBitsTmp = cacheBitsInitial;
if ((lz77TypesToTry & lz77Type) == 0)
{
continue;
@ -264,34 +261,33 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
BackwardReferencesRle(width, height, bgra, 0, worst);
break;
case Vp8LLz77Type.Lz77Standard:
// Compute LZ77 with no cache (0 bits), as the ideal LZ77 with a color
// cache is not that different in practice.
// Compute LZ77 with no cache (0 bits), as the ideal LZ77 with a color cache is not that different in practice.
BackwardReferencesLz77(width, height, bgra, 0, hashChain, worst);
break;
case Vp8LLz77Type.Lz77Box:
// TODO: HashChainInit(hashChainBox, width * height);
//BackwardReferencesLz77Box(width, height, bgra, 0, hashChain, hashChainBox, worst);
hashChainBox = new Vp8LHashChain(width * height);
BackwardReferencesLz77Box(width, height, bgra, 0, hashChain, hashChainBox, worst);
break;
}
// Next, try with a color cache and update the references.
CalculateBestCacheSize(bgra, quality, worst, cacheBitsTmp);
if (cacheBitsTmp[0] > 0)
cacheBitsTmp = CalculateBestCacheSize(bgra, quality, worst, cacheBitsTmp);
if (cacheBitsTmp > 0)
{
BackwardRefsWithLocalCache(bgra, cacheBitsTmp[0], worst);
BackwardRefsWithLocalCache(bgra, cacheBitsTmp, worst);
}
// Keep the best backward references.
// TODO: VP8LHistogramCreate(histo, worst, cacheBitsTmp);
histo[0] = new Vp8LHistogram(worst, cacheBitsTmp);
bitCost = histo[0].EstimateBits();
if (lz77TypeBest == 0 || bitCost < bitCostBest)
{
Vp8LBackwardRefs[] tmp = worst;
Vp8LBackwardRefs tmp = worst;
worst = best;
best = tmp;
bitCostBest = bitCost;
//*cacheBits = cacheBitsTmp;
cacheBits = cacheBitsTmp;
lz77TypeBest = lz77Type;
}
}
@ -299,17 +295,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
// Improve on simple LZ77 but only for high quality (TraceBackwards is costly).
if ((lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard || lz77TypeBest == (int)Vp8LLz77Type.Lz77Box) && quality >= 25)
{
/*HashChain[] hashChainTmp = (lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard) ? hashChain : hashChainBox;
if (BackwardReferencesTraceBackwards(width, height, bgra, cacheBits, hashChainTmp, best, worst))
Vp8LHashChain hashChainTmp = (lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard) ? hashChain : hashChainBox;
BackwardReferencesTraceBackwards(width, height, bgra, cacheBits, hashChainTmp, best, worst);
histo[0] = new Vp8LHistogram(worst, cacheBits);
double bitCostTrace = histo[0].EstimateBits();
if (bitCostTrace < bitCostBest)
{
double bitCostTrace;
//HistogramCreate(histo, worst, cacheBits);
bitCostTrace = histo[0].EstimateBits();
if (bitCostTrace < bitCostBest)
{
best = worst;
}
}*/
best = worst;
}
}
BackwardReferences2DLocality(width, best);
@ -319,62 +312,60 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
/// <summary>
/// Evaluate optimal cache bits for the local color cache.
/// The input *best_cache_bits sets the maximum cache bits to use (passing 0
/// implies disabling the local color cache). The local color cache is also
/// disabled for the lower (<= 25) quality.
/// The input *best_cache_bits sets the maximum cache bits to use (passing 0 implies disabling the local color cache).
/// The local color cache is also disabled for the lower (<= 25) quality.
/// </summary>
private static void CalculateBestCacheSize(uint[] bgra, int quality, Vp8LBackwardRefs[] refs, int[] bestCacheBits)
private static int CalculateBestCacheSize(Span<uint> bgra, int quality, Vp8LBackwardRefs refs, int bestCacheBits)
{
int cacheBitsMax = (quality <= 25) ? 0 : bestCacheBits[0];
int cacheBitsMax = (quality <= 25) ? 0 : bestCacheBits;
double entropyMin = MaxEntropy;
int pos = 0;
var ccInit = new int[WebPConstants.MaxColorCacheBits + 1];
var hashers = new ColorCache[WebPConstants.MaxColorCacheBits + 1];
var c = new Vp8LRefsCursor(refs);
var colorCache = new ColorCache[WebPConstants.MaxColorCacheBits + 1];
var histos = new Vp8LHistogram[WebPConstants.MaxColorCacheBits + 1];
if (cacheBitsMax == 0)
{
// Local color cache is disabled.
bestCacheBits[0] = 0;
return;
return 0;
}
// Find the cache_bits giving the lowest entropy. The search is done in a
// brute-force way as the function (entropy w.r.t cache_bits) can be anything in practice.
//while (VP8LRefsCursorOk(&c))
/*while (true)
using List<PixOrCopy>.Enumerator c = refs.Refs.GetEnumerator();
while (c.MoveNext())
{
//PixOrCopy[] v = c.cur_pos;
PixOrCopy v = c.Current;
if (v.IsLiteral())
{
uint pix = *bgra++;
uint pix = bgra[pos++];
uint a = (pix >> 24) & 0xff;
uint r = (pix >> 16) & 0xff;
uint g = (pix >> 8) & 0xff;
uint b = (pix >> 0) & 0xff;
// The keys of the caches can be derived from the longest one.
int key = HashPix(pix, 32 - cacheBitsMax);
int key = ColorCache.HashPix(pix, 32 - cacheBitsMax);
// Do not use the color cache for cache_bits = 0.
++histos[0].blue[b];
++histos[0].literal[g];
++histos[0].red[r];
++histos[0].alpha[a];
++histos[0].Blue[b];
++histos[0].Literal[g];
++histos[0].Red[r];
++histos[0].Alpha[a];
// Deal with cache_bits > 0.
for (int i = cacheBitsMax; i >= 1; --i, key >>= 1)
for (int i = cacheBitsMax; i >= 1; i--, key >>= 1)
{
if (VP8LColorCacheLookup(hashers[i], key) == pix)
if (colorCache[i].Lookup(key) == pix)
{
++histos[i]->literal[WebPConstants.NumLiteralCodes + WebPConstants.CodeLengthCodes + key];
++histos[i].Literal[WebPConstants.NumLiteralCodes + WebPConstants.CodeLengthCodes + key];
}
else
{
VP8LColorCacheSet(hashers[i], key, pix);
++histos[i].blue[b];
++histos[i].literal[g];
++histos[i].red[r];
++histos[i].alpha[a];
colorCache[i].Set((uint)key, pix);
++histos[i].Blue[b];
++histos[i].Literal[g];
++histos[i].Red[r];
++histos[i].Alpha[a];
}
}
}
@ -384,36 +375,75 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
// histograms but those are the same independently from the cache size.
// As those constant contributions are in the end added to the other
// histogram contributions, we can safely ignore them.
int len = v.Len;
uint bgraPrev = bgra[pos] ^ 0xffffffffu;
// Update the color caches.
do
{
if (bgra[pos] != bgraPrev)
{
// Efficiency: insert only if the color changes.
int key = ColorCache.HashPix(bgra[pos], 32 - cacheBitsMax);
for (int i = cacheBitsMax; i >= 1; --i, key >>= 1)
{
colorCache[i].Colors[key] = bgra[pos];
}
bgraPrev = bgra[pos];
}
pos++;
}
while (--len != 0);
}
}*/
}
for (int i = 0; i <= cacheBitsMax; i++)
{
double entropy = histos[i].EstimateBits();
if (i == 0 || entropy < entropyMin)
{
entropyMin = entropy;
bestCacheBits = i;
}
}
return bestCacheBits;
}
private static void BackwardReferencesTraceBackwards()
private static void BackwardReferencesTraceBackwards(int xSize, int ySize, Span<uint> bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refsSrc, Vp8LBackwardRefs refsDst)
{
int distArraySize = xSize * ySize;
var distArray = new short[distArraySize];
short[] chosenPath;
int chosenPathSize = 0;
// TODO:
// BackwardReferencesHashChainDistanceOnly(xSize, ySize, bgra, cacheBits, hashChain, refsSrc, distArray);
// TraceBackwards(distArray, distArraySize, chosenPath, chosenPathSize);
// BackwardReferencesHashChainFollowChosenPath(bgra, cacheBits, chosenPath, chosenPathSize, hashChain, refsDst);
}
private static void BackwardReferencesLz77(int xSize, int ySize, uint[] bgra, int cacheBits, Vp8LHashChain[] hashChain, Vp8LBackwardRefs[] refs)
private static void BackwardReferencesLz77(int xSize, int ySize, Span<uint> bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refs)
{
int iLastCheck = -1;
int ccInit = 0;
bool useColorCache = cacheBits > 0;
int pixCount = xSize * ySize;
var hashers = new ColorCache();
var colorCache = new ColorCache();
if (useColorCache)
{
hashers.Init(cacheBits);
colorCache.Init(cacheBits);
}
// TODO: VP8LClearBackwardRefs(refs);
for (int i = 0; i < pixCount;)
{
// Alternative #1: Code the pixels starting at 'i' using backward reference.
int offset = 0;
int len = 0;
int j;
// TODO: VP8LHashChainFindCopy(hashChain, i, offset, ref len);
int offset = hashChain.FindOffset(i);
int len = hashChain.FindLength(i);
if (len >= MinLength)
{
int lenIni = len;
@ -431,7 +461,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
// [i,j) (where j<=i+len) + [j, length of best match at j)
for (j = iLastCheck + 1; j <= jMax; j++)
{
int lenJ = 0; // TODO: HashChainFindLength(hashChain, j);
int lenJ = hashChain.FindLength(j);
int reach = j + (lenJ >= MinLength ? lenJ : 1); // 1 for single literal.
if (reach > maxReach)
{
@ -450,22 +480,22 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
}
// Go with literal or backward reference.
/*if (len == 1)
if (len == 1)
{
AddSingleLiteral(bgra[i], useColorCache, hashers, refs);
AddSingleLiteral(bgra[i], useColorCache, colorCache, refs);
}
else
{
VP8LBackwardRefsCursorAdd(refs, PixOrCopyCreateCopy(offset, len));
refs.Add(PixOrCopy.CreateCopy((uint)offset, (short)len));
if (useColorCache)
{
for (j = i; j < i + len; ++j)
{
VP8LColorCacheInsert(hashers, bgra[j]);
colorCache.Insert(bgra[j]);
}
}
}
*/
i += len;
}
}
@ -474,69 +504,260 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
/// Compute an LZ77 by forcing matches to happen within a given distance cost.
/// We therefore limit the algorithm to the lowest 32 values in the PlaneCode definition.
/// </summary>
private static void BackwardReferencesLz77Box(int xSize, int ySize, uint[] bgra, int cacheBits, Vp8LHashChain[] hashChainBest, Vp8LHashChain[] hashChain, Vp8LBackwardRefs[] refs)
private static void BackwardReferencesLz77Box(int xSize, int ySize, Span<uint> bgra, int cacheBits, Vp8LHashChain hashChainBest, Vp8LHashChain hashChain, Vp8LBackwardRefs refs)
{
int i;
int pixCount = xSize * ySize;
short[] counts;
int pixelCount = xSize * ySize;
var windowOffsets = new int[WindowOffsetsSizeMax];
var windowOffsetsNew = new int[WindowOffsetsSizeMax];
int windowOffsetsSize = 0;
int windowOffsetsNewSize = 0;
short[] countsIni = new short[xSize * ySize];
var counts = new short[xSize * ySize];
int bestOffsetPrev = -1;
int bestLengthPrev = -1;
// counts[i] counts how many times a pixel is repeated starting at position i.
i = pixCount - 2;
/*counts = countsIni + i;
counts[1] = 1;
for (; i >= 0; i--, counts--)
int i = pixelCount - 2;
int countsPos = i;
counts[countsPos + 1] = 1;
for (; i >= 0; i--, countsPos--)
{
if (bgra[i] == bgra[i + 1])
{
// Max out the counts to MAX_LENGTH.
counts[0] = counts[1] + (counts[1] != MaxLength);
counts[countsPos] = counts[countsPos + 1]; // TODO: + (counts[1] != MaxLength);
}
else
{
counts[0] = 1;
counts[countsPos] = 1;
}
}
// Figure out the window offsets around a pixel. They are stored in a
// spiraling order around the pixel as defined by VP8LDistanceToPlaneCode.
for (int y = 0; y <= 6; y++)
{
for (int x = -6; x <= 6; x++)
{
int offset = (y * xSize) + x;
// Ignore offsets that bring us after the pixel.
if (offset <= 0)
{
continue;
}
int planeCode = DistanceToPlaneCode(xSize, offset) - 1;
if (planeCode >= WindowOffsetsSizeMax)
{
continue;
}
windowOffsets[planeCode] = offset;
}
}
// For narrow images, not all plane codes are reached, so remove those.
for (i = 0; i < WindowOffsetsSizeMax; i++)
{
if (windowOffsets[i] == 0)
{
continue;
}
windowOffsets[windowOffsetsSize++] = windowOffsets[i];
}
// Given a pixel P, find the offsets that reach pixels unreachable from P-1
// with any of the offsets in windowOffsets[].
for (i = 0; i < windowOffsetsSize; i++)
{
bool isReachable = false;
for (int j = 0; j < windowOffsetsSize && !isReachable; ++j)
{
isReachable |= windowOffsets[i] == windowOffsets[j] + 1;
}
if (!isReachable)
{
windowOffsetsNew[windowOffsetsNewSize] = windowOffsets[i];
windowOffsetsNewSize++;
}
}
hashChain.OffsetLength[0] = 0;
for (i = 1; i < pixelCount; ++i)
{
int ind;
int bestLength = hashChainBest.FindLength(i);
int bestOffset = 0;
bool doCompute = true;
if (bestLength >= MaxLength)
{
// Do not recompute the best match if we already have a maximal one in the window.
bestOffset = hashChainBest.FindOffset(i);
for (ind = 0; ind < windowOffsetsSize; ++ind)
{
if (bestOffset == windowOffsets[ind])
{
doCompute = false;
break;
}
}
}
}*/
if (doCompute)
{
// Figure out if we should use the offset/length from the previous pixel
// as an initial guess and therefore only inspect the offsets in windowOffsetsNew[].
bool usePrev = (bestLengthPrev > 1) && (bestLengthPrev < MaxLength);
int numInd = usePrev ? windowOffsetsNewSize : windowOffsetsSize;
bestLength = usePrev ? bestLengthPrev - 1 : 0;
bestOffset = usePrev ? bestOffsetPrev : 0;
// Find the longest match in a window around the pixel.
for (ind = 0; ind < numInd; ++ind)
{
int currLength = 0;
int j = i;
int jOffset = usePrev ? i - windowOffsetsNew[ind] : i - windowOffsets[ind];
if (jOffset < 0 || bgra[jOffset] != bgra[i])
{
continue;
}
// The longest match is the sum of how many times each pixel is repeated.
do
{
int countsJOffset = counts[jOffset];
int countsJ = counts[j];
if (countsJOffset != countsJ)
{
currLength += (countsJOffset < countsJ) ? countsJOffset : countsJ;
break;
}
// The same color is repeated counts_pos times at j_offset and j.
currLength += countsJOffset;
jOffset += countsJOffset;
j += countsJOffset;
}
while (currLength <= MaxLength && j < pixelCount && bgra[jOffset] == bgra[j]);
if (bestLength < currLength)
{
bestOffset = usePrev ? windowOffsetsNew[ind] : windowOffsets[ind];
if (currLength >= MaxLength)
{
bestLength = MaxLength;
break;
}
else
{
bestLength = currLength;
}
}
}
}
if (bestLength <= MinLength)
{
hashChain.OffsetLength[i] = 0;
bestOffsetPrev = 0;
bestLengthPrev = 0;
}
else
{
hashChain.OffsetLength[i] = (uint)((bestOffset << MaxLengthBits) | bestLength);
bestOffsetPrev = bestOffset;
bestLengthPrev = bestLength;
}
}
hashChain.OffsetLength[0] = 0;
BackwardReferencesLz77(xSize, ySize, bgra, cacheBits, hashChain, refs);
}
private static void BackwardReferencesRle(int xSize, int ySize, uint[] bgra, int cacheBits, Vp8LBackwardRefs[] refs)
private static void BackwardReferencesRle(int xSize, int ySize, Span<uint> bgra, int cacheBits, Vp8LBackwardRefs refs)
{
int pixCount = xSize * ySize;
int i, k;
int pixelCount = xSize * ySize;
bool useColorCache = cacheBits > 0;
var colorCache = new ColorCache();
if (useColorCache)
{
colorCache.Init(cacheBits);
}
// VP8LClearBackwardRefs(refs);
// Add first pixel as literal.
AddSingleLiteral(bgra[0], useColorCache, colorCache, refs);
int i = 1;
while (i < pixelCount)
{
int maxLen = MaxFindCopyLength(pixelCount - i);
int rleLen = FindMatchLength(bgra.Slice(i), bgra.Slice(i - 1), 0, maxLen);
int prevRowLen = (i < xSize) ? 0 : FindMatchLength(bgra.Slice(i), bgra.Slice(i - xSize), 0, maxLen);
if (rleLen >= prevRowLen && rleLen >= MinLength)
{
refs.Add(PixOrCopy.CreateCopy(1, (short)rleLen));
// We don't need to update the color cache here since it is always the
// same pixel being copied, and that does not change the color cache
// state.
i += rleLen;
}
else if (prevRowLen >= MinLength)
{
refs.Add(PixOrCopy.CreateCopy((uint)xSize, (short)prevRowLen));
if (useColorCache)
{
for (int k = 0; k < prevRowLen; k++)
{
colorCache.Insert(bgra[i + k]);
}
}
i += prevRowLen;
}
else
{
AddSingleLiteral(bgra[i], useColorCache, colorCache, refs);
i++;
}
}
if (useColorCache)
{
// VP8LColorCacheClear();
}
}
/// <summary>
/// Update (in-place) backward references for the specified cacheBits.
/// </summary>
private static void BackwardRefsWithLocalCache(uint[] bgra, int cacheBits, Vp8LBackwardRefs[] refs)
private static void BackwardRefsWithLocalCache(Span<uint> bgra, int cacheBits, Vp8LBackwardRefs refs)
{
int pixelIndex = 0;
var c = new Vp8LRefsCursor(refs);
var hashers = new ColorCache();
hashers.Init(cacheBits);
//while (VP8LRefsCursorOk(&c))
/*while (true)
using List<PixOrCopy>.Enumerator c = refs.Refs.GetEnumerator();
var colorCache = new ColorCache();
colorCache.Init(cacheBits);
while (c.MoveNext())
{
PixOrCopy[] v = c.curPos;
PixOrCopy v = c.Current;
if (v.IsLiteral())
{
uint bgraLiteral = v.BgraOrDistance;
int ix = VP8LColorCacheContains(hashers, bgraLiteral);
int ix = colorCache.Contains(bgraLiteral);
if (ix >= 0)
{
// hashers contains bgraLiteral
v = PixOrCopyCreateCacheIdx(ix);
// color cache contains bgraLiteral
v = PixOrCopy.CreateCacheIdx(ix);
}
else
{
VP8LColorCacheInsert(hashers, bgraLiteral);
colorCache.Insert(bgraLiteral);
}
pixelIndex++;
@ -544,32 +765,52 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
else
{
// refs was created without local cache, so it can not have cache indexes.
for (int k = 0; k < v.len; k++)
for (int k = 0; k < v.Len; k++)
{
VP8LColorCacheInsert(hashers, bgra[pixelIndex++]);
colorCache.Insert(bgra[pixelIndex++]);
}
}
VP8LRefsCursorNext(c);
}
VP8LColorCacheClear(hashers);*/
// VP8LColorCacheClear(colorCache);
}
private static void BackwardReferences2DLocality(int xSize, Vp8LBackwardRefs[] refs)
private static void BackwardReferences2DLocality(int xSize, Vp8LBackwardRefs refs)
{
var c = new Vp8LRefsCursor(refs);
/*while (VP8LRefsCursorOk(&c))
using List<PixOrCopy>.Enumerator c = refs.Refs.GetEnumerator();
while (c.MoveNext())
{
if (c.cur_pos.IsCopy())
if (c.Current.IsCopy())
{
int dist = c.curPos.ArgbOrDistance;
int dist = (int)c.Current.BgraOrDistance;
int transformedDist = DistanceToPlaneCode(xSize, dist);
c.curPos.ArgbOrDistance = transformedDist;
c.Current.BgraOrDistance = (uint)transformedDist;
}
}
}
private static void AddSingleLiteral(uint pixel, bool useColorCache, ColorCache colorCache, Vp8LBackwardRefs refs)
{
PixOrCopy v;
if (useColorCache)
{
int key = colorCache.GetIndex(pixel);
if (colorCache.Lookup(key) == pixel)
{
v = PixOrCopy.CreateCacheIdx(key);
}
else
{
v = PixOrCopy.CreateLiteral(pixel);
colorCache.Set((uint)key, pixel);
}
}
else
{
v = PixOrCopy.CreateLiteral(pixel);
}
VP8LRefsCursorNext(&c);
}*/
refs.Add(v);
}
private static int DistanceToPlaneCode(int xSize, int dist)

46
src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs

@ -40,19 +40,55 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
/// <summary>
/// Inserts a new color into the cache.
/// </summary>
/// <param name="argb">The color to insert.</param>
public void Insert(uint argb)
/// <param name="bgra">The color to insert.</param>
public void Insert(uint bgra)
{
int key = this.HashPix(argb, this.HashShift);
this.Colors[key] = argb;
int key = HashPix(bgra, this.HashShift);
this.Colors[key] = bgra;
}
/// <summary>
/// Gets a color for a given key.
/// </summary>
/// <param name="key">The key to lookup.</param>
/// <returns>The color for the key.</returns>
public uint Lookup(int key)
{
return this.Colors[key];
}
private int HashPix(uint argb, int shift)
/// <summary>
/// Returns the index of the given color.
/// </summary>
/// <param name="bgra">The color to check.</param>
/// <returns>The index of the color in the cache or -1 if its not present.</returns>
public int Contains(uint bgra)
{
int key = HashPix(bgra, this.HashShift);
return (this.Colors[key] == bgra) ? key : -1;
}
/// <summary>
/// Gets the index of a color.
/// </summary>
/// <param name="bgra">The color.</param>
/// <returns>The index for the color.</returns>
public int GetIndex(uint bgra)
{
return HashPix(bgra, this.HashShift);
}
/// <summary>
/// Adds a new color to the cache.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="bgra">The color to add.</param>
public void Set(uint key, uint bgra)
{
this.Colors[key] = bgra;
}
public static int HashPix(uint argb, int shift)
{
return (int)((argb * HashMul) >> shift);
}

42
src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs

@ -5,11 +5,47 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
internal class PixOrCopy
{
public PixOrCopyMode Mode { get; }
public PixOrCopyMode Mode { get; set; }
public short Len { get; }
public short Len { get; set; }
public uint ArgbOrDistance { get; }
public uint BgraOrDistance { get; set; }
public static PixOrCopy CreateCacheIdx(int idx)
{
var retval = new PixOrCopy()
{
Mode = PixOrCopyMode.CacheIdx,
BgraOrDistance = (uint)idx,
Len = 1
};
return retval;
}
public static PixOrCopy CreateLiteral(uint bgra)
{
var retval = new PixOrCopy()
{
Mode = PixOrCopyMode.Literal,
BgraOrDistance = bgra,
Len = 1
};
return retval;
}
public static PixOrCopy CreateCopy(uint distance, short len)
{
var retval = new PixOrCopy()
{
Mode = PixOrCopyMode.Copy,
BgraOrDistance = distance,
Len = len
};
return retval;
}
public bool IsLiteral()
{

14
src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs

@ -1,13 +1,27 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
internal class Vp8LBackwardRefs
{
public Vp8LBackwardRefs()
{
this.Refs = new List<PixOrCopy>();
}
/// <summary>
/// Common block-size.
/// </summary>
public int BlockSize { get; set; }
public List<PixOrCopy> Refs { get; }
public void Add(PixOrCopy pixOrCopy)
{
this.Refs.Add(pixOrCopy);
}
}
}

10
src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs

@ -27,5 +27,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
this.OffsetLength.AsSpan().Fill(0xcdcdcdcd);
this.Size = size;
}
public int FindLength(int basePosition)
{
return (int)(this.OffsetLength[basePosition] & ((1U << BackwardReferenceEncoder.MaxLengthBits) - 1));
}
public int FindOffset(int basePosition)
{
return (int)(this.OffsetLength[basePosition] >> BackwardReferenceEncoder.MaxLengthBits);
}
}
}

52
src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs

@ -5,6 +5,58 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
internal class Vp8LHistogram
{
public Vp8LHistogram(Vp8LBackwardRefs refs, int paletteCodeBits)
{
if (paletteCodeBits >= 0)
{
this.PaletteCodeBits = paletteCodeBits;
}
//HistogramClear();
// TODO: VP8LHistogramStoreRefs(refs);
}
public Vp8LHistogram()
{
this.Red = new uint[WebPConstants.NumLiteralCodes];
this.Blue = new uint[WebPConstants.NumLiteralCodes];
this.Alpha = new uint[WebPConstants.NumLiteralCodes];
this.Distance = new uint[WebPConstants.NumLiteralCodes];
this.Literal = new uint[WebPConstants.NumLiteralCodes]; // TODO: is this enough?
}
public int PaletteCodeBits { get; }
/// <summary>
/// Cached value of bit cost.
/// </summary>
public double BitCost { get; }
/// <summary>
/// Cached value of literal entropy costs.
/// </summary>
public double LiteralCost { get; }
/// <summary>
/// Cached value of red entropy costs.
/// </summary>
public double RedCost { get; }
/// <summary>
/// Cached value of blue entropy costs.
/// </summary>
public double BlueCost { get; }
public uint[] Red { get; }
public uint[] Blue { get; }
public uint[] Alpha { get; }
public uint[] Literal { get; }
public uint[] Distance { get; }
public double EstimateBits()
{
// TODO: implement this.

8
src/ImageSharp/Formats/WebP/Lossless/Vp8LRefsCursor.cs

@ -5,14 +5,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
internal class Vp8LRefsCursor
{
public Vp8LRefsCursor(Vp8LBackwardRefs[] refs)
public Vp8LRefsCursor(Vp8LBackwardRefs refs)
{
//this.Refs = refs;
this.CurrentPos = 0;
//this.CurrentPos = 0;
}
public PixOrCopy[] Refs { get; }
//public PixOrCopy Refs { get; }
public int CurrentPos { get; }
public PixOrCopy CurrentPos { get; }
}
}

30
src/ImageSharp/Formats/WebP/WebPEncoderCore.cs

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.WebP.BitWriter;
using SixLabors.ImageSharp.Formats.WebP.Lossless;
using SixLabors.ImageSharp.Memory;
@ -167,7 +168,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
private void EncodePalette<TPixel>(Image<TPixel> image, Span<uint> bgra, Vp8LEncoder enc)
where TPixel : unmanaged, IPixel<TPixel>
{
var tmpPalette = new uint[WebPConstants.MaxPaletteSize];
Span<uint> tmpPalette = new uint[WebPConstants.MaxPaletteSize];
int paletteSize = enc.PaletteSize;
Span<uint> palette = enc.Palette.Memory.Span;
this.bitWriter.PutBits(WebPConstants.TransformPresent, 1);
@ -179,26 +180,29 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
tmpPalette[0] = palette[0];
this.EncodeImageNoHuffman(image, tmpPalette, enc);
this.EncodeImageNoHuffman(tmpPalette, enc.HashChain, enc.Refs[0], enc.Refs[1], width: paletteSize, height: 1, quality: 20);
}
private void EncodeImageNoHuffman<TPixel>(Image<TPixel> image, Span<uint> bgra, Vp8LEncoder enc)
where TPixel : unmanaged, IPixel<TPixel>
private void EncodeImageNoHuffman(Span<uint> bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs refsTmp1, Vp8LBackwardRefs refsTmp2, int width, int height, int quality)
{
int width = image.Width;
int height = image.Height;
int paletteSize = enc.PaletteSize;
Vp8LHashChain hashChain = enc.HashChain;
var huffmanCodes = new HuffmanTreeCode[5];
int cacheBits = 0;
HuffmanTreeToken[] tokens;
var huffTree = new HuffmanTree[3UL * WebPConstants.CodeLengthCodes];
int quality = 20; // TODO: hardcoded for now.
// Calculate backward references from ARGB image.
BackwardReferenceEncoder.HashChainFill(hashChain, bgra, quality, paletteSize, 1);
//var refs = GetBackwardReferences(width, height, argb, quality, 0, kLZ77Standard | kLZ77RLE, cacheBits, hashChain, refsTmp1, refsTmp2);
BackwardReferenceEncoder.HashChainFill(hashChain, bgra, quality, width, height);
Vp8LBackwardRefs refs = BackwardReferenceEncoder.GetBackwardReferences(
width,
height,
bgra,
quality,
(int)Vp8LLz77Type.Lz77Standard | (int)Vp8LLz77Type.Lz77Rle,
cacheBits,
hashChain,
refsTmp1,
refsTmp2);
// Build histogram image and symbols from backward references.
//VP8LHistogramStoreRefs(refs, histogram_image->histograms[0]);

Loading…
Cancel
Save