diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs
index 770cce5ec0..49e9edd2f9 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs
+++ b/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
{
+ ///
+ /// Maximum bit length.
+ ///
+ 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
///
private const int WindowSize = (1 << WindowSizeBits) - 120;
- ///
- /// Maximum bit length.
- ///
- private const int MaxLengthBits = 12;
-
///
/// We want the max value to be attainable and stored in MaxLengthBits bits.
///
@@ -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;
}
///
@@ -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].
///
- 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 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
///
/// 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.
///
- private static void CalculateBestCacheSize(uint[] bgra, int quality, Vp8LBackwardRefs[] refs, int[] bestCacheBits)
+ private static int CalculateBestCacheSize(Span 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.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 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 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.
///
- 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 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 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();
+ }
}
///
/// Update (in-place) backward references for the specified cacheBits.
///
- private static void BackwardRefsWithLocalCache(uint[] bgra, int cacheBits, Vp8LBackwardRefs[] refs)
+ private static void BackwardRefsWithLocalCache(Span 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.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.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)
diff --git a/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs b/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs
index d1d46a7a64..96f70641c3 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/ColorCache.cs
@@ -40,19 +40,55 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
///
/// Inserts a new color into the cache.
///
- /// The color to insert.
- public void Insert(uint argb)
+ /// The color to insert.
+ 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;
}
+ ///
+ /// Gets a color for a given key.
+ ///
+ /// The key to lookup.
+ /// The color for the key.
public uint Lookup(int key)
{
return this.Colors[key];
}
- private int HashPix(uint argb, int shift)
+ ///
+ /// Returns the index of the given color.
+ ///
+ /// The color to check.
+ /// The index of the color in the cache or -1 if its not present.
+ public int Contains(uint bgra)
+ {
+ int key = HashPix(bgra, this.HashShift);
+ return (this.Colors[key] == bgra) ? key : -1;
+ }
+
+ ///
+ /// Gets the index of a color.
+ ///
+ /// The color.
+ /// The index for the color.
+ public int GetIndex(uint bgra)
+ {
+ return HashPix(bgra, this.HashShift);
+ }
+
+ ///
+ /// Adds a new color to the cache.
+ ///
+ /// The key.
+ /// The color to add.
+ public void Set(uint key, uint bgra)
+ {
+ this.Colors[key] = bgra;
+ }
+
+ public static int HashPix(uint argb, int shift)
{
return (int)((argb * HashMul) >> shift);
}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs
index 30ee2f5f47..fe85e95a94 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs
+++ b/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()
{
diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs
index b5da33ea30..7f35d08e4c 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBackwardRefs.cs
+++ b/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();
+ }
+
///
/// Common block-size.
///
public int BlockSize { get; set; }
+
+ public List Refs { get; }
+
+ public void Add(PixOrCopy pixOrCopy)
+ {
+ this.Refs.Add(pixOrCopy);
+ }
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs
index 0639b545ab..e8d383917c 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs
+++ b/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);
+ }
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs
index af0a575267..98b791bb0d 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs
+++ b/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; }
+
+ ///
+ /// Cached value of bit cost.
+ ///
+ public double BitCost { get; }
+
+ ///
+ /// Cached value of literal entropy costs.
+ ///
+ public double LiteralCost { get; }
+
+ ///
+ /// Cached value of red entropy costs.
+ ///
+ public double RedCost { get; }
+
+ ///
+ /// Cached value of blue entropy costs.
+ ///
+ 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.
diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LRefsCursor.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LRefsCursor.cs
index ce423c39a5..6578d3f512 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LRefsCursor.cs
+++ b/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; }
}
}
diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs
index 5a5de99093..7f985a29e2 100644
--- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs
+++ b/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(Image image, Span bgra, Vp8LEncoder enc)
where TPixel : unmanaged, IPixel
{
- var tmpPalette = new uint[WebPConstants.MaxPaletteSize];
+ Span tmpPalette = new uint[WebPConstants.MaxPaletteSize];
int paletteSize = enc.PaletteSize;
Span 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(Image image, Span bgra, Vp8LEncoder enc)
- where TPixel : unmanaged, IPixel
+ private void EncodeImageNoHuffman(Span 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]);