Browse Source

Implement HashChainFill

pull/1552/head
Brian Popow 6 years ago
parent
commit
5547202d39
  1. 661
      src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs
  2. 31
      src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs
  3. 26
      src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs
  4. 21
      src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs
  5. 29
      src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs
  6. 16
      src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs
  7. 13
      src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs
  8. 44
      src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
  9. 31
      src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs
  10. 14
      src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs
  11. 14
      src/ImageSharp/Formats/WebP/Lossless/Vp8LLz77Type.cs
  12. 18
      src/ImageSharp/Formats/WebP/Lossless/Vp8LRefsCursor.cs
  13. 7
      src/ImageSharp/Formats/WebP/WebPConstants.cs
  14. 157
      src/ImageSharp/Formats/WebP/WebPEncoderCore.cs
  15. 11
      src/ImageSharp/Formats/WebP/WebPLookupTables.cs

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

@ -0,0 +1,661 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
using System;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
internal class BackwardReferenceEncoder
{
private const int HashBits = 18;
private const int HashSize = 1 << HashBits;
private const uint HashMultiplierHi = 0xc6a4a793u;
private const uint HashMultiplierLo = 0x5bd1e996u;
private const float MaxEntropy = 1e30f;
private const int WindowOffsetsSizeMax = 32;
/// <summary>
/// Minimum block size for backward references.
/// </summary>
private const int MinBlockSize = 256;
/// <summary>
/// The number of bits for the window size.
/// </summary>
private const int WindowSizeBits = 20;
/// <summary>
/// 1M window (4M bytes) minus 120 special codes for short distances.
/// </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>
private const int MaxLength = (1 << MaxLengthBits) - 1;
/// <summary>
/// Minimum number of pixels for which it is cheaper to encode a
/// distance + length instead of each pixel as a literal.
/// </summary>
private const int MinLength = 4;
public static void HashChainFill(Vp8LHashChain p, Span<uint> bgra, int quality, int xSize, int ySize)
{
int size = xSize * ySize;
int iterMax = GetMaxItersForQuality(quality);
int windowSize = GetWindowSizeForHashChain(quality, xSize);
int pos;
var hashToFirstIndex = new int[HashSize]; // TODO: use memory allocator
// Initialize hashToFirstIndex array to -1.
hashToFirstIndex.AsSpan().Fill(-1);
var chain = new int[size]; // TODO: use memory allocator.
// Fill the chain linking pixels with the same hash.
var bgraComp = bgra[0] == bgra[1];
for (pos = 0; pos < size - 2;)
{
uint hashCode;
bool bgraCompNext = bgra[pos + 1] == bgra[pos + 2];
if (bgraComp && bgraCompNext)
{
// Consecutive pixels with the same color will share the same hash.
// We therefore use a different hash: the color and its repetition length.
var tmp = new uint[2];
uint len = 1;
tmp[0] = bgra[pos];
// Figure out how far the pixels are the same. The last pixel has a different 64 bit hash,
// as its next pixel does not have the same color, so we just need to get to
// the last pixel equal to its follower.
while (pos + (int)len + 2 < size && bgra[(int)(pos + len + 2)] == bgra[pos])
{
++len;
}
if (len > MaxLength)
{
// Skip the pixels that match for distance=1 and length>MaxLength
// because they are linked to their predecessor and we automatically
// check that in the main for loop below. Skipping means setting no
// predecessor in the chain, hence -1.
pos += (int)(len - MaxLength);
len = MaxLength;
}
// Process the rest of the hash chain.
while (len > 0)
{
tmp[1] = len--;
hashCode = GetPixPairHash64(tmp);
chain[pos] = hashToFirstIndex[hashCode];
hashToFirstIndex[hashCode] = pos++;
}
bgraComp = false;
}
else
{
// Just move one pixel forward.
hashCode = GetPixPairHash64(bgra.Slice(pos));
chain[pos] = hashToFirstIndex[hashCode];
hashToFirstIndex[hashCode] = pos++;
bgraComp = bgraCompNext;
}
}
// Process the penultimate pixel.
chain[pos] = hashToFirstIndex[GetPixPairHash64(bgra.Slice(pos))];
// 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).
p.OffsetLength[0] = p.OffsetLength[size - 1] = 0;
for (int basePosition = size - 2; basePosition > 0;)
{
int maxLen = MaxFindCopyLength(size - 1 - basePosition);
int bgraStart = basePosition;
int iter = iterMax;
int bestLength = 0;
uint bestDistance = 0;
uint bestBgra;
int minPos = (basePosition > windowSize) ? basePosition - windowSize : 0;
int lengthMax = (maxLen < 256) ? maxLen : 256;
uint maxBasePosition;
pos = (int)chain[basePosition];
int currLength;
// Heuristic: use the comparison with the above line as an initialization.
if (basePosition >= (uint)xSize)
{
currLength = FindMatchLength(bgra.Slice(bgraStart - xSize), bgra.Slice(bgraStart), bestLength, maxLen);
if (currLength > bestLength)
{
bestLength = currLength;
bestDistance = (uint)xSize;
}
iter--;
}
// Heuristic: compare to the previous pixel.
currLength = FindMatchLength(bgra.Slice(bgraStart - 1), bgra.Slice(bgraStart), bestLength, maxLen);
if (currLength > bestLength)
{
bestLength = currLength;
bestDistance = 1;
}
iter--;
if (bestLength == MaxLength)
{
pos = minPos - 1;
}
bestBgra = bgra.Slice(bgraStart)[bestLength];
for (; pos >= minPos && (--iter > 0); pos = chain[pos])
{
if (bgra[pos + bestLength] != bestBgra)
{
continue;
}
currLength = VectorMismatch(bgra.Slice(pos), bgra.Slice(bgraStart), maxLen);
if (bestLength < currLength)
{
bestLength = currLength;
bestDistance = (uint)(basePosition - pos);
bestBgra = bgra.Slice(bgraStart)[bestLength];
// Stop if we have reached a good enough length.
if (bestLength >= lengthMax)
{
break;
}
}
}
// We have the best match but in case the two intervals continue matching
// to the left, we have the best matches for the left-extended pixels.
maxBasePosition = (uint)basePosition;
while (true)
{
p.OffsetLength[basePosition] = (bestDistance << MaxLengthBits) | (uint)bestLength;
--basePosition;
// Stop if we don't have a match or if we are out of bounds.
if (bestDistance == 0 || basePosition == 0)
{
break;
}
// Stop if we cannot extend the matching intervals to the left.
if (basePosition < bestDistance || bgra[(int)(basePosition - bestDistance)] != bgra[basePosition])
{
break;
}
// Stop if we are matching at its limit because there could be a closer
// matching interval with the same maximum length. Then again, if the
// matching interval is as close as possible (best_distance == 1), we will
// never find anything better so let's continue.
if (bestLength == MaxLength && bestDistance != 1 && basePosition + MaxLength < maxBasePosition)
{
break;
}
if (bestLength < MaxLength)
{
bestLength++;
maxBasePosition = (uint)basePosition;
}
}
}
int foo = 0;
}
/// <summary>
/// Evaluates best possible backward references for specified quality.
/// The input cache_bits to 'VP8LGetBackwardReferences' sets the maximum cache
/// bits to use (passing 0 implies disabling the local color cache).
/// The optimal cache bits is evaluated and set for the *cache_bits parameter.
/// 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)
{
var histo = new Vp8LHistogram[WebPConstants.MaxColorCacheBits];
int lz77Type = 0;
int lz77TypeBest = 0;
double bitCostBest = -1;
int[] cacheBitsInitial = cacheBits;
// TODO: var hashChainBox = new Vp8LHashChain();
for (lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1)
{
int res = 0;
double bitCost;
int[] cacheBitsTmp = cacheBitsInitial;
if ((lz77TypesToTry & lz77Type) == 0)
{
continue;
}
switch ((Vp8LLz77Type)lz77Type)
{
case Vp8LLz77Type.Lz77Rle:
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.
BackwardReferencesLz77(width, height, bgra, 0, hashChain, worst);
break;
case Vp8LLz77Type.Lz77Box:
// TODO: HashChainInit(hashChainBox, 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)
{
BackwardRefsWithLocalCache(bgra, cacheBitsTmp[0], worst);
}
// Keep the best backward references.
// TODO: VP8LHistogramCreate(histo, worst, cacheBitsTmp);
bitCost = histo[0].EstimateBits();
if (lz77TypeBest == 0 || bitCost < bitCostBest)
{
Vp8LBackwardRefs[] tmp = worst;
worst = best;
best = tmp;
bitCostBest = bitCost;
//*cacheBits = cacheBitsTmp;
lz77TypeBest = lz77Type;
}
}
// 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))
{
double bitCostTrace;
//HistogramCreate(histo, worst, cacheBits);
bitCostTrace = histo[0].EstimateBits();
if (bitCostTrace < bitCostBest)
{
best = worst;
}
}*/
}
BackwardReferences2DLocality(width, best);
return best;
}
/// <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.
/// </summary>
private static void CalculateBestCacheSize(uint[] bgra, int quality, Vp8LBackwardRefs[] refs, int[] bestCacheBits)
{
int cacheBitsMax = (quality <= 25) ? 0 : bestCacheBits[0];
double entropyMin = MaxEntropy;
var ccInit = new int[WebPConstants.MaxColorCacheBits + 1];
var hashers = new ColorCache[WebPConstants.MaxColorCacheBits + 1];
var c = new Vp8LRefsCursor(refs);
var histos = new Vp8LHistogram[WebPConstants.MaxColorCacheBits + 1];
if (cacheBitsMax == 0)
{
// Local color cache is disabled.
bestCacheBits[0] = 0;
return;
}
// 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)
{
//PixOrCopy[] v = c.cur_pos;
if (v.IsLiteral())
{
uint pix = *bgra++;
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);
// 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];
// Deal with cache_bits > 0.
for (int i = cacheBitsMax; i >= 1; --i, key >>= 1)
{
if (VP8LColorCacheLookup(hashers[i], key) == pix)
{
++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];
}
}
}
else
{
// We should compute the contribution of the (distance,length)
// 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.
}
}*/
}
private static void BackwardReferencesTraceBackwards()
{
}
private static void BackwardReferencesLz77(int xSize, int ySize, 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();
if (useColorCache)
{
hashers.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);
if (len >= MinLength)
{
int lenIni = len;
int maxReach = 0;
int jMax = (i + lenIni >= pixCount) ? pixCount - 1 : i + lenIni;
// Only start from what we have not checked already.
iLastCheck = (i > iLastCheck) ? i : iLastCheck;
// We know the best match for the current pixel but we try to find the
// best matches for the current pixel AND the next one combined.
// The naive method would use the intervals:
// [i,i+len) + [i+len, length of best match at i+len)
// while we check if we can use:
// [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 reach = j + (lenJ >= MinLength ? lenJ : 1); // 1 for single literal.
if (reach > maxReach)
{
len = j - i;
maxReach = reach;
if (maxReach >= pixCount)
{
break;
}
}
}
}
else
{
len = 1;
}
// Go with literal or backward reference.
/*if (len == 1)
{
AddSingleLiteral(bgra[i], useColorCache, hashers, refs);
}
else
{
VP8LBackwardRefsCursorAdd(refs, PixOrCopyCreateCopy(offset, len));
if (useColorCache)
{
for (j = i; j < i + len; ++j)
{
VP8LColorCacheInsert(hashers, bgra[j]);
}
}
}
*/
i += len;
}
}
/// <summary>
/// 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)
{
int i;
int pixCount = xSize * ySize;
short[] counts;
var windowOffsets = new int[WindowOffsetsSizeMax];
var windowOffsetsNew = new int[WindowOffsetsSizeMax];
int windowOffsetsSize = 0;
int windowOffsetsNewSize = 0;
short[] countsIni = 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--)
{
if (bgra[i] == bgra[i + 1])
{
// Max out the counts to MAX_LENGTH.
counts[0] = counts[1] + (counts[1] != MaxLength);
}
else
{
counts[0] = 1;
}
}*/
}
private static void BackwardReferencesRle(int xSize, int ySize, uint[] bgra, int cacheBits, Vp8LBackwardRefs[] refs)
{
int pixCount = xSize * ySize;
int i, k;
bool useColorCache = cacheBits > 0;
}
/// <summary>
/// Update (in-place) backward references for the specified cacheBits.
/// </summary>
private static void BackwardRefsWithLocalCache(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)
{
PixOrCopy[] v = c.curPos;
if (v.IsLiteral())
{
uint bgraLiteral = v.BgraOrDistance;
int ix = VP8LColorCacheContains(hashers, bgraLiteral);
if (ix >= 0)
{
// hashers contains bgraLiteral
v = PixOrCopyCreateCacheIdx(ix);
}
else
{
VP8LColorCacheInsert(hashers, bgraLiteral);
}
pixelIndex++;
}
else
{
// refs was created without local cache, so it can not have cache indexes.
for (int k = 0; k < v.len; k++)
{
VP8LColorCacheInsert(hashers, bgra[pixelIndex++]);
}
}
VP8LRefsCursorNext(c);
}
VP8LColorCacheClear(hashers);*/
}
private static void BackwardReferences2DLocality(int xSize, Vp8LBackwardRefs[] refs)
{
var c = new Vp8LRefsCursor(refs);
/*while (VP8LRefsCursorOk(&c))
{
if (c.cur_pos.IsCopy())
{
int dist = c.curPos.ArgbOrDistance;
int transformedDist = DistanceToPlaneCode(xSize, dist);
c.curPos.ArgbOrDistance = transformedDist;
}
VP8LRefsCursorNext(&c);
}*/
}
private static int DistanceToPlaneCode(int xSize, int dist)
{
int yOffset = dist / xSize;
int xOffset = dist - (yOffset * xSize);
if (xOffset <= 8 && yOffset < 8)
{
return (int)WebPLookupTables.PlaneToCodeLut[(yOffset * 16) + 8 - xOffset] + 1;
}
else if (xOffset > xSize - 8 && yOffset < 7)
{
return (int)WebPLookupTables.PlaneToCodeLut[((yOffset + 1) * 16) + 8 + (xSize - xOffset)] + 1;
}
return dist + 120;
}
/// <summary>
/// Returns the exact index where array1 and array2 are different. For an index
/// inferior or equal to best_len_match, the return value just has to be strictly
/// inferior to best_len_match. The current behavior is to return 0 if this index
/// is best_len_match, and the index itself otherwise.
/// If no two elements are the same, it returns max_limit.
/// </summary>
private static int FindMatchLength(Span<uint> array1, Span<uint> array2, int bestLenMatch, int maxLimit)
{
// Before 'expensive' linear match, check if the two arrays match at the
// current best length index.
if (array1[bestLenMatch] != array2[bestLenMatch])
{
return 0;
}
return VectorMismatch(array1, array2, maxLimit);
}
private static int VectorMismatch(Span<uint> array1, Span<uint> array2, int length)
{
int matchLen = 0;
while (matchLen < length && array1[matchLen] == array2[matchLen])
{
matchLen++;
}
return matchLen;
}
/// <summary>
/// Calculates the hash for a pixel pair.
/// </summary>
/// <param name="bgra">An Span with two pixels.</param>
/// <returns>The hash.</returns>
private static uint GetPixPairHash64(Span<uint> bgra)
{
uint key = bgra[1] * HashMultiplierHi;
key += bgra[0] * HashMultiplierLo;
key = key >> (32 - HashBits);
return key;
}
/// <summary>
/// Returns the maximum number of hash chain lookups to do for a
/// given compression quality. Return value in range [8, 86].
/// </summary>
/// <param name="quality">The quality.</param>
/// <returns>Number of hash chain lookups.</returns>
private static int GetMaxItersForQuality(int quality)
{
return 8 + (quality * quality / 128);
}
private static int MaxFindCopyLength(int len)
{
return (len < MaxLength) ? len : MaxLength;
}
private static int GetWindowSizeForHashChain(int quality, int xSize)
{
int maxWindowSize = (quality > 75) ? WindowSize
: (quality > 50) ? (xSize << 8)
: (quality > 25) ? (xSize << 6)
: (xSize << 4);
return (maxWindowSize > WindowSize) ? WindowSize : maxWindowSize;
}
}
}

31
src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
/// <summary>
/// Represents the Huffman tree.
/// </summary>
internal class HuffmanTree
{
/// <summary>
/// Gets the symbol frequency.
/// </summary>
public int TotalCount { get; }
/// <summary>
/// Gets the symbol value.
/// </summary>
public int Value { get; }
/// <summary>
/// Gets the index for the left sub-tree.
/// </summary>
public int PoolIndexLeft { get; }
/// <summary>
/// Gets the index for the right sub-tree.
/// </summary>
public int PoolIndexRight { get; }
}
}

26
src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs

@ -0,0 +1,26 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
/// <summary>
/// Represents the tree codes (depth and bits array).
/// </summary>
internal class HuffmanTreeCode
{
/// <summary>
/// Gets the number of symbols.
/// </summary>
public int NumSymbols { get; }
/// <summary>
/// Gets the code lengths of the symbols.
/// </summary>
public byte[] CodeLengths { get; }
/// <summary>
/// Gets the symbol Codes.
/// </summary>
public short[] Codes { get; }
}
}

21
src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
/// <summary>
/// Holds the tree header in coded form.
/// </summary>
internal class HuffmanTreeToken
{
/// <summary>
/// Gets the code. Value (0..15) or escape code (16, 17, 18).
/// </summary>
public byte Code { get; }
/// <summary>
/// Gets extra bits for escape codes.
/// </summary>
public byte ExtraBits { get; }
}
}

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

@ -0,0 +1,29 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
internal class PixOrCopy
{
public PixOrCopyMode Mode { get; }
public short Len { get; }
public uint ArgbOrDistance { get; }
public bool IsLiteral()
{
return this.Mode == PixOrCopyMode.Literal;
}
public bool IsCacheIdx()
{
return this.Mode == PixOrCopyMode.CacheIdx;
}
public bool IsCopy()
{
return this.Mode == PixOrCopyMode.Copy;
}
}
}

16
src/ImageSharp/Formats/WebP/Lossless/PixOrCopyMode.cs

@ -0,0 +1,16 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
internal enum PixOrCopyMode
{
Literal,
CacheIdx,
Copy,
None
}
}

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

@ -0,0 +1,13 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
internal class Vp8LBackwardRefs
{
/// <summary>
/// Common block-size.
/// </summary>
public int BlockSize { get; set; }
}
}

44
src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs

@ -12,9 +12,31 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
/// </summary>
internal class Vp8LEncoder : IDisposable
{
public Vp8LEncoder(MemoryAllocator memoryAllocator)
/// <summary>
/// Maximum number of reference blocks the image will be segmented into.
/// </summary>
private const int MaxRefsBlockPerImage = 16;
/// <summary>
/// Minimum block size for backward references.
/// </summary>
private const int MinBlockSize = 256;
public Vp8LEncoder(MemoryAllocator memoryAllocator, int width, int height)
{
var pixelCount = width * height;
this.Palette = memoryAllocator.Allocate<uint>(WebPConstants.MaxPaletteSize);
this.Refs = new Vp8LBackwardRefs[3];
this.HashChain = new Vp8LHashChain(pixelCount);
// We round the block size up, so we're guaranteed to have at most MAX_REFS_BLOCK_PER_IMAGE blocks used:
int refsBlockSize = ((pixelCount - 1) / MaxRefsBlockPerImage) + 1;
for (int i = 0; i < this.Refs.Length; ++i)
{
this.Refs[i] = new Vp8LBackwardRefs();
this.Refs[i].BlockSize = (refsBlockSize < MinBlockSize) ? MinBlockSize : refsBlockSize;
}
}
/// <summary>
@ -28,24 +50,24 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
public int TransformBits { get; set; }
/// <summary>
/// Gets or sets the cache bits.
/// Gets or sets a value indicating whether to use a color cache.
/// </summary>
public bool CacheBits { get; }
public bool UseColorCache { get; set; }
/// <summary>
/// Gets a value indicating whether to use the cross color transform.
/// Gets or sets a value indicating whether to use the cross color transform.
/// </summary>
public bool UseCrossColorTransform { get; }
public bool UseCrossColorTransform { get; set; }
/// <summary>
/// Gets a value indicating whether to use the substract green transform.
/// Gets or sets a value indicating whether to use the substract green transform.
/// </summary>
public bool UseSubtractGreenTransform { get; }
public bool UseSubtractGreenTransform { get; set; }
/// <summary>
/// Gets a value indicating whether to use the predictor transform.
/// Gets or sets a value indicating whether to use the predictor transform.
/// </summary>
public bool UsePredictorTransform { get; }
public bool UsePredictorTransform { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to use color indexing transform.
@ -62,6 +84,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
/// </summary>
public IMemoryOwner<uint> Palette { get; }
public Vp8LBackwardRefs[] Refs { get; }
public Vp8LHashChain HashChain { get; }
/// <inheritdoc/>
public void Dispose()
{

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

@ -0,0 +1,31 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
using System;
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
internal class Vp8LHashChain
{
/// <summary>
/// The 20 most significant bits contain the offset at which the best match is found.
/// These 20 bits are the limit defined by GetWindowSizeForHashChain (through WindowSize = 1 << 20).
/// The lower 12 bits contain the length of the match. The 12 bit limit is
/// defined in MaxFindCopyLength with MAX_LENGTH=4096.
/// </summary>
public uint[] OffsetLength { get; }
/// <summary>
/// This is the maximum size of the hash_chain that can be constructed.
/// Typically this is the pixel count (width x height) for a given image.
/// </summary>
public int Size { get; }
public Vp8LHashChain(int size)
{
this.OffsetLength = new uint[size];
this.OffsetLength.AsSpan().Fill(0xcdcdcdcd);
this.Size = size;
}
}
}

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

@ -0,0 +1,14 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
internal class Vp8LHistogram
{
public double EstimateBits()
{
// TODO: implement this.
return 0.0;
}
}
}

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

@ -0,0 +1,14 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
internal enum Vp8LLz77Type
{
Lz77Standard = 1,
Lz77Rle = 2,
Lz77Box = 4
}
}

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

@ -0,0 +1,18 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
internal class Vp8LRefsCursor
{
public Vp8LRefsCursor(Vp8LBackwardRefs[] refs)
{
//this.Refs = refs;
this.CurrentPos = 0;
}
public PixOrCopy[] Refs { get; }
public int CurrentPos { get; }
}
}

7
src/ImageSharp/Formats/WebP/WebPConstants.cs

@ -102,6 +102,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary>
public const int MaxNumberOfTransforms = 4;
/// <summary>
/// The bit to be written when next data to be read is a transform.
/// </summary>
public const int TransformPresent = 1;
/// <summary>
/// The maximum allowed width or height of a webp image.
/// </summary>
@ -123,6 +128,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
public const int NumDistanceCodes = 40;
public const int CodeLengthCodes = 19;
public const int LengthTableBits = 7;
public const uint CodeLengthLiterals = 16;

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

@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
private void EncodeStream<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new Vp8LEncoder(this.memoryAllocator);
var encoder = new Vp8LEncoder(this.memoryAllocator, image.Width, image.Height);
// Analyze image (entropy, num_palettes etc).
this.EncoderAnalyze(image, encoder);
@ -118,17 +118,134 @@ namespace SixLabors.ImageSharp.Formats.WebP
private void EncoderAnalyze<TPixel>(Image<TPixel> image, Vp8LEncoder enc)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
// Check if we only deal with a small number of colors and should use a palette.
var usePalette = this.AnalyzeAndCreatePalette(image, enc);
// Empirical bit sizes.
int method = 4; // TODO: method hardcoded to 4 for now.
enc.HistoBits = GetHistoBits(method, usePalette, image.Width, image.Height);
enc.HistoBits = GetHistoBits(method, usePalette, width, height);
enc.TransformBits = GetTransformBits(method, enc.HistoBits);
// Convert image pixels to bgra array.
using System.Buffers.IMemoryOwner<uint> bgraBuffer = this.memoryAllocator.Allocate<uint>(width * height);
Span<uint> bgra = bgraBuffer.Memory.Span;
int idx = 0;
for (int y = 0; y < height; y++)
{
Span<TPixel> rowSpan = image.GetPixelRowSpan(y);
for (int x = 0; x < rowSpan.Length; x++)
{
bgra[idx++] = ToBgra32(rowSpan[x]).PackedValue;
}
}
// Try out multiple LZ77 on images with few colors.
var nlz77s = (enc.PaletteSize > 0 && enc.PaletteSize <= 16) ? 2 : 1;
this.AnalyzeEntropy(image, usePalette, enc.PaletteSize, enc.TransformBits, out bool redAndBlueAlwaysZero);
EntropyIx entropyIdx = this.AnalyzeEntropy(image, usePalette, enc.PaletteSize, enc.TransformBits, out bool redAndBlueAlwaysZero);
enc.UsePalette = entropyIdx == EntropyIx.Palette;
enc.UseSubtractGreenTransform = (entropyIdx == EntropyIx.SubGreen) || (entropyIdx == EntropyIx.SpatialSubGreen);
enc.UsePredictorTransform = (entropyIdx == EntropyIx.Spatial) || (entropyIdx == EntropyIx.SpatialSubGreen);
enc.UseCrossColorTransform = redAndBlueAlwaysZero ? false : enc.UsePredictorTransform;
enc.UseColorCache = false;
// Encode palette.
if (enc.UsePalette)
{
this.EncodePalette(image, bgra, enc);
}
}
/// <summary>
/// Save the palette to the bitstream.
/// </summary>
/// <param name="image">The image.</param>
/// <param name="enc">The Vp8L Encoder.</param>
private void EncodePalette<TPixel>(Image<TPixel> image, Span<uint> bgra, Vp8LEncoder enc)
where TPixel : unmanaged, IPixel<TPixel>
{
var tmpPalette = new uint[WebPConstants.MaxPaletteSize];
int paletteSize = enc.PaletteSize;
Span<uint> palette = enc.Palette.Memory.Span;
this.bitWriter.PutBits(WebPConstants.TransformPresent, 1);
this.bitWriter.PutBits((uint)Vp8LTransformType.ColorIndexingTransform, 2);
this.bitWriter.PutBits((uint)paletteSize - 1, 8);
for (int i = paletteSize - 1; i >= 1; i--)
{
tmpPalette[i] = LosslessUtils.SubPixels(palette[i], palette[i - 1]);
}
tmpPalette[0] = palette[0];
this.EncodeImageNoHuffman(image, tmpPalette, enc);
}
private void EncodeImageNoHuffman<TPixel>(Image<TPixel> image, Span<uint> bgra, Vp8LEncoder enc)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
int paletteSize = enc.PaletteSize;
Vp8LHashChain hashChain = enc.HashChain;
var huffmanCodes = new HuffmanTreeCode[5];
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);
// Build histogram image and symbols from backward references.
//VP8LHistogramStoreRefs(refs, histogram_image->histograms[0]);
// Create Huffman bit lengths and codes for each histogram image.
//GetHuffBitLengthsAndCodes(histogram_image, huffman_codes)
// No color cache, no Huffman image.
this.bitWriter.PutBits(0, 1);
// Find maximum number of symbols for the huffman tree-set.
/*for (i = 0; i < 5; ++i)
{
HuffmanTreeCode * const codes = &huffman_codes[i];
if (max_tokens < codes->num_symbols)
{
max_tokens = codes->num_symbols;
}
}*/
// Store Huffman codes.
/*
for (i = 0; i < 5; ++i)
{
HuffmanTreeCode * const codes = &huffman_codes[i];
StoreHuffmanCode(bw, huff_tree, tokens, codes);
ClearHuffmanTreeIfOnlyOneSymbol(codes);
}
// Store actual literals.
StoreImageToBitMask(bw, width, 0, refs, histogram_symbols, huffman_codes);
*/
}
private void StoreImageToBitMask(int width, int histoBits, short[] histogramSymbols, HuffmanTreeCode[] huffmanCodes)
{
int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(width, histoBits) : 1;
int tileMask = (histoBits == 0) ? 0 : -(1 << histoBits);
// x and y trace the position in the image.
int x = 0;
int y = 0;
int tileX = x & tileMask;
int tileY = y & tileMask;
int histogramIx = histogramSymbols[0];
Span<HuffmanTreeCode> codes = huffmanCodes.AsSpan(5 * histogramIx);
}
/// <summary>
@ -161,10 +278,10 @@ namespace SixLabors.ImageSharp.Formats.WebP
Span<TPixel> prevRow = null;
for (int y = 0; y < height; y++)
{
Span<TPixel> currentRow = image.GetPixelRowSpan(y);
for (int x = 0; x < width; x++)
{
Span<TPixel> currentRow = image.GetPixelRowSpan(y);
Bgra32 pix = ToBgra32(currentRow[0]);
Bgra32 pix = ToBgra32(currentRow[x]);
uint pixDiff = LosslessUtils.SubPixels(pix.PackedValue, pixPrev.PackedValue);
pixPrev = pix;
if ((pixDiff == 0) || (prevRow != null && pix == ToBgra32(prevRow[x])))
@ -180,25 +297,26 @@ namespace SixLabors.ImageSharp.Formats.WebP
histo.Slice((int)HistoIx.HistoBlue * 256));
AddSingle(
pixDiff,
histo.Slice((int)HistoIx.HistoAlpha * 256),
histo.Slice((int)HistoIx.HistoRed * 256),
histo.Slice((int)HistoIx.HistoGreen * 256),
histo.Slice((int)HistoIx.HistoBlue * 256));
histo.Slice((int)HistoIx.HistoAlphaPred * 256),
histo.Slice((int)HistoIx.HistoRedPred * 256),
histo.Slice((int)HistoIx.HistoGreenPred * 256),
histo.Slice((int)HistoIx.HistoBluePred * 256));
AddSingleSubGreen(
pix.PackedValue,
histo.Slice((int)HistoIx.HistoRedSubGreen * 256),
histo.Slice((int)HistoIx.HistoBlueSubGreen * 256));
AddSingleSubGreen(
pixDiff,
histo.Slice((int)HistoIx.HistoRedSubGreen * 256),
histo.Slice((int)HistoIx.HistoBlueSubGreen * 256));
histo.Slice((int)HistoIx.HistoRedPredSubGreen * 256),
histo.Slice((int)HistoIx.HistoBluePredSubGreen * 256));
// Approximate the palette by the entropy of the multiplicative hash.
uint hash = HashPix(pix.PackedValue);
histo[((int)HistoIx.HistoPalette * 256) + (int)hash]++;
prevRow = currentRow;
}
var histo0 = histo[0];
prevRow = currentRow;
}
var entropyComp = new double[(int)HistoIx.HistoTotal];
@ -206,7 +324,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
int lastModeToAnalyze = usePalette ? (int)EntropyIx.Palette : (int)EntropyIx.SpatialSubGreen;
// Let's add one zero to the predicted histograms. The zeros are removed
// too efficiently by the pix_diff == 0 comparison, at least one of the
// too efficiently by the pixDiff == 0 comparison, at least one of the
// zeros is likely to exist.
histo[(int)HistoIx.HistoRedPredSubGreen * 256]++;
histo[(int)HistoIx.HistoBluePredSubGreen * 256]++;
@ -218,8 +336,9 @@ namespace SixLabors.ImageSharp.Formats.WebP
for (int j = 0; j < (int)HistoIx.HistoTotal; ++j)
{
var bitEntropy = new Vp8LBitEntropy();
bitEntropy.BitsEntropyUnrefined(histo, 256);
entropyComp[j] = bitEntropy.BitsEntropyRefine(histo.Slice(j * 256), 256);
Span<uint> curHisto = histo.Slice(j * 256, 256);
bitEntropy.BitsEntropyUnrefined(curHisto, 256);
entropyComp[j] = bitEntropy.BitsEntropyRefine(curHisto, 256);
}
entropy[(int)EntropyIx.Direct] = entropyComp[(int)HistoIx.HistoAlpha] +
@ -247,8 +366,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
LosslessUtils.SubSampleSize(height, transformBits) *
LosslessUtils.FastLog2(14);
// For color transforms: 24 as only 3 channels are considered in a
// ColorTransformElement.
// For color transforms: 24 as only 3 channels are considered in a ColorTransformElement.
entropy[(int)EntropyIx.SpatialSubGreen] += LosslessUtils.SubSampleSize(width, transformBits) *
LosslessUtils.SubSampleSize(height, transformBits) *
LosslessUtils.FastLog2(24);
@ -260,7 +378,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
entropy[(int)EntropyIx.Palette] += paletteSize * 8;
EntropyIx minEntropyIx = EntropyIx.Direct;
for (int k = (int)EntropyIx.Direct + 1; k <= lastModeToAnalyze; ++k)
for (int k = (int)EntropyIx.Direct + 1; k <= lastModeToAnalyze; k++)
{
if (entropy[(int)minEntropyIx] > entropy[k])
{
@ -307,6 +425,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
enc.PaletteSize = this.GetColorPalette(image, palette);
if (enc.PaletteSize > WebPConstants.MaxPaletteSize)
{
enc.PaletteSize = 0;
return false;
}

11
src/ImageSharp/Formats/WebP/WebPLookupTables.cs

@ -236,6 +236,17 @@ namespace SixLabors.ImageSharp.Formats.WebP
0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70
};
public static readonly uint[] PlaneToCodeLut = {
96, 73, 55, 39, 23, 13, 5, 1, 255, 255, 255, 255, 255, 255, 255, 255,
101, 78, 58, 42, 26, 16, 8, 2, 0, 3, 9, 17, 27, 43, 59, 79,
102, 86, 62, 46, 32, 20, 10, 6, 4, 7, 11, 21, 33, 47, 63, 87,
105, 90, 70, 52, 37, 28, 18, 14, 12, 15, 19, 29, 38, 53, 71, 91,
110, 99, 82, 66, 48, 35, 30, 24, 22, 25, 31, 36, 49, 67, 83, 100,
115, 108, 94, 76, 64, 50, 44, 40, 34, 41, 45, 51, 65, 77, 95, 109,
118, 113, 103, 92, 80, 68, 60, 56, 54, 57, 61, 69, 81, 93, 104, 114,
119, 116, 111, 106, 97, 88, 84, 74, 72, 75, 85, 89, 98, 107, 112, 117
};
// 31 ^ clz(i)
public static readonly byte[] LogTable8bit =
{

Loading…
Cancel
Save