mirror of https://github.com/SixLabors/ImageSharp
15 changed files with 1065 additions and 28 deletions
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
Loading…
Reference in new issue