/// <summary> /// Accumulate a token 'v' into a histogram. /// </summary> /// <param name="v">The token to add.</param> /// <param name="useDistanceModifier">Indicates whether to use the distance modifier.</param> /// <param name="xSize">xSize is only used when useDistanceModifier is true.</param> public void AddSinglePixOrCopy(PixOrCopy v, bool useDistanceModifier, int xSize = 0) { if (v.IsLiteral()) { this.Alpha[v.Literal(3)]++; this.Red[v.Literal(2)]++; this.Literal[v.Literal(1)]++; this.Blue[v.Literal(0)]++; } else if (v.IsCacheIdx()) { int literalIx = (int)(WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + v.CacheIdx()); this.Literal[literalIx]++; } else { int extraBits = 0; int code = LosslessUtils.PrefixEncodeBits(v.Length(), ref extraBits); this.Literal[WebpConstants.NumLiteralCodes + code]++; if (!useDistanceModifier) { code = LosslessUtils.PrefixEncodeBits((int)v.Distance(), ref extraBits); } else { code = LosslessUtils.PrefixEncodeBits(BackwardReferenceEncoder.DistanceToPlaneCode(xSize, (int)v.Distance()), ref extraBits); } this.Distance[code]++; } }
private static void BackwardReferencesHashChainFollowChosenPath(ReadOnlySpan <uint> bgra, int cacheBits, Span <ushort> chosenPath, int chosenPathSize, Vp8LHashChain hashChain, Vp8LBackwardRefs backwardRefs) { bool useColorCache = cacheBits > 0; var colorCache = new ColorCache(); int i = 0; if (useColorCache) { colorCache.Init(cacheBits); } backwardRefs.Refs.Clear(); for (int ix = 0; ix < chosenPathSize; ix++) { int len = chosenPath[ix]; if (len != 1) { int offset = hashChain.FindOffset(i); backwardRefs.Add(PixOrCopy.CreateCopy((uint)offset, (ushort)len)); if (useColorCache) { for (int k = 0; k < len; k++) { colorCache.Insert(bgra[i + k]); } } i += len; } else { PixOrCopy v; int idx = useColorCache ? colorCache.Contains(bgra[i]) : -1; if (idx >= 0) { // useColorCache is true and color cache contains bgra[i] // Push pixel as a color cache index. v = PixOrCopy.CreateCacheIdx(idx); } else { if (useColorCache) { colorCache.Insert(bgra[i]); } v = PixOrCopy.CreateLiteral(bgra[i]); } backwardRefs.Add(v); i++; } } }
private static void BackwardReferencesRle(int xSize, int ySize, ReadOnlySpan <uint> bgra, int cacheBits, Vp8LBackwardRefs refs) { int pixelCount = xSize * ySize; bool useColorCache = cacheBits > 0; var colorCache = new ColorCache(); if (useColorCache) { colorCache.Init(cacheBits); } refs.Refs.Clear(); // Add first pixel as literal. AddSingleLiteral(bgra[0], useColorCache, colorCache, refs); int i = 1; while (i < pixelCount) { int maxLen = LosslessUtils.MaxFindCopyLength(pixelCount - i); int rleLen = LosslessUtils.FindMatchLength(bgra.Slice(i), bgra.Slice(i - 1), 0, maxLen); int prevRowLen = i < xSize ? 0 : LosslessUtils.FindMatchLength(bgra.Slice(i), bgra.Slice(i - xSize), 0, maxLen); if (rleLen >= prevRowLen && rleLen >= MinLength) { refs.Add(PixOrCopy.CreateCopy(1, (ushort)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, (ushort)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++; } } }
/// <summary> /// Construct the histograms from the backward references. /// </summary> private static void HistogramBuild(int xSize, int histoBits, Vp8LBackwardRefs backwardRefs, List <Vp8LHistogram> histograms) { int x = 0, y = 0; int histoXSize = LosslessUtils.SubSampleSize(xSize, histoBits); using List <PixOrCopy> .Enumerator backwardRefsEnumerator = backwardRefs.Refs.GetEnumerator(); while (backwardRefsEnumerator.MoveNext()) { PixOrCopy v = backwardRefsEnumerator.Current; int ix = ((y >> histoBits) * histoXSize) + (x >> histoBits); histograms[ix].AddSinglePixOrCopy(v, false); x += v.Len; while (x >= xSize) { x -= xSize; y++; } } }
/// <summary> /// Update (in-place) backward references for the specified cacheBits. /// </summary> private static void BackwardRefsWithLocalCache(ReadOnlySpan <uint> bgra, int cacheBits, Vp8LBackwardRefs refs) { int pixelIndex = 0; var colorCache = new ColorCache(); colorCache.Init(cacheBits); for (int idx = 0; idx < refs.Refs.Count; idx++) { PixOrCopy v = refs.Refs[idx]; if (v.IsLiteral()) { uint bgraLiteral = v.BgraOrDistance; int ix = colorCache.Contains(bgraLiteral); if (ix >= 0) { // Color cache contains bgraLiteral v.Mode = PixOrCopyMode.CacheIdx; v.BgraOrDistance = (uint)ix; v.Len = 1; } else { colorCache.Insert(bgraLiteral); } pixelIndex++; } else { // refs was created without local cache, so it can not have cache indexes. for (int k = 0; k < v.Len; ++k) { colorCache.Insert(bgra[pixelIndex++]); } } } }
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); } refs.Add(v); }
private static void BackwardReferencesLz77(int xSize, int ySize, ReadOnlySpan <uint> bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refs) { int iLastCheck = -1; bool useColorCache = cacheBits > 0; int pixCount = xSize * ySize; var colorCache = new ColorCache(); if (useColorCache) { colorCache.Init(cacheBits); } refs.Refs.Clear(); for (int i = 0; i < pixCount;) { // Alternative #1: Code the pixels starting at 'i' using backward reference. int j; int offset = hashChain.FindOffset(i); int len = hashChain.FindLength(i); 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 = hashChain.FindLength(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, colorCache, refs); } else { refs.Add(PixOrCopy.CreateCopy((uint)offset, (ushort)len)); if (useColorCache) { for (j = i; j < i + len; j++) { colorCache.Insert(bgra[j]); } } } i += len; } }
/// <summary> /// Evaluate optimal cache bits for the local color cache. /// The input bestCacheBits 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 (smaller then 25) quality. /// </summary> /// <returns>Best cache size.</returns> private static int CalculateBestCacheSize(ReadOnlySpan <uint> bgra, int quality, Vp8LBackwardRefs refs, int bestCacheBits) { int cacheBitsMax = quality <= 25 ? 0 : bestCacheBits; if (cacheBitsMax == 0) { // Local color cache is disabled. return(0); } double entropyMin = MaxEntropy; int pos = 0; var colorCache = new ColorCache[WebpConstants.MaxColorCacheBits + 1]; var histos = new Vp8LHistogram[WebpConstants.MaxColorCacheBits + 1]; for (int i = 0; i <= WebpConstants.MaxColorCacheBits; i++) { histos[i] = new Vp8LHistogram(paletteCodeBits: i); colorCache[i] = new ColorCache(); colorCache[i].Init(i); } // Find the cacheBits giving the lowest entropy. for (int idx = 0; idx < refs.Refs.Count; idx++) { PixOrCopy v = refs.Refs[idx]; if (v.IsLiteral()) { 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 = ColorCache.HashPix(pix, 32 - cacheBitsMax); // Do not use the color cache for cacheBits = 0. ++histos[0].Blue[b]; ++histos[0].Literal[g]; ++histos[0].Red[r]; ++histos[0].Alpha[a]; // Deal with cacheBits > 0. for (int i = cacheBitsMax; i >= 1; --i, key >>= 1) { if (colorCache[i].Lookup(key) == pix) { ++histos[i].Literal[WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + key]; } else { colorCache[i].Set((uint)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 ignore them, except for the length // prefix that is part of the literal_ histogram. int len = v.Len; uint bgraPrev = bgra[pos] ^ 0xffffffffu; int extraBits = 0, extraBitsValue = 0; int code = LosslessUtils.PrefixEncode(len, ref extraBits, ref extraBitsValue); for (int i = 0; i <= cacheBitsMax; i++) { ++histos[i].Literal[WebpConstants.NumLiteralCodes + code]; } // 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); } } var stats = new Vp8LStreaks(); var bitsEntropy = new Vp8LBitEntropy(); for (int i = 0; i <= cacheBitsMax; i++) { double entropy = histos[i].EstimateBits(stats, bitsEntropy); if (i == 0 || entropy < entropyMin) { entropyMin = entropy; bestCacheBits = i; } } return(bestCacheBits); }