/// <summary> /// A Webp lossless image can go through four different types of transformation before being entropy encoded. /// This will reverse the transformations, if any are present. /// </summary> /// <param name="decoder">The decoder holding the transformation infos.</param> /// <param name="pixelData">The pixel data to apply the transformation.</param> /// <param name="memoryAllocator">The memory allocator is needed to allocate memory during the predictor transform.</param> public static void ApplyInverseTransforms(Vp8LDecoder decoder, Span <uint> pixelData, MemoryAllocator memoryAllocator) { List <Vp8LTransform> transforms = decoder.Transforms; for (int i = transforms.Count - 1; i >= 0; i--) { Vp8LTransform transform = transforms[i]; Vp8LTransformType transformType = transform.TransformType; switch (transformType) { case Vp8LTransformType.PredictorTransform: using (IMemoryOwner <uint> output = memoryAllocator.Allocate <uint>(pixelData.Length, AllocationOptions.Clean)) { LosslessUtils.PredictorInverseTransform(transform, pixelData, output.GetSpan()); } break; case Vp8LTransformType.SubtractGreen: LosslessUtils.AddGreenToBlueAndRed(pixelData); break; case Vp8LTransformType.CrossColorTransform: LosslessUtils.ColorSpaceInverseTransform(transform, pixelData); break; case Vp8LTransformType.ColorIndexingTransform: LosslessUtils.ColorIndexInverseTransform(transform, pixelData); break; } } }
public double GetDistanceCost(int distance) { int extraBits = 0; int code = LosslessUtils.PrefixEncodeBits(distance, ref extraBits); return(this.Distance[code] + extraBits); }
private static void RunAddGreenToBlueAndRedTest() { uint[] pixelData = { 4284188659, 4284254193, 4284318702, 4284187883, 4284318441, 4284383470, 4284318700, 4284124392, 4283799012, 4283864546, 4284581610, 4285163264, 4284891926, 4284497945, 4284761620, 4284893965, 4284828428, 4284959500, 4284959755, 4284828426, 4284960520, 4285289733, 4285159937, 4285292030, 4285358077, 4285228030, 4284966398, 4285097213, 4285227773, 4285096956, 4285097470, 4285228540, 4285163516, 4285425149, 4285294332, 4285228540, 4285228543, 4285163261, 4285163516, 4285032701, 4284835841, 4284835584, 4284966140, 4285228029, 4284770300, 4285097214, 4285293819, 4285228795, 4285163259, 4285228287, 4284901886 }; uint[] expectedOutput = { 4293035898, 4293101432, 4292903793, 4292838511, 4292837995, 4292771950, 4292903791, 4293299316, 4293563769, 4293629303, 4293363312, 4291913575, 4289282905, 4288692313, 4289349210, 4289809240, 4289743703, 4289874775, 4289940567, 4289743701, 4290137943, 4290860378, 4291058267, 4291386715, 4291583836, 4291715937, 4291585379, 4291650657, 4291650143, 4291584863, 4291716451, 4291847521, 4291913571, 4292044130, 4291978850, 4291847521, 4291847524, 4291847779, 4291913571, 4291848293, 4291651689, 4291585895, 4291519584, 4291715936, 4291520355, 4291650658, 4291847263, 4291913313, 4291847777, 4291781731, 4291783015 }; LosslessUtils.AddGreenToBlueAndRed(pixelData); Assert.Equal(expectedOutput, pixelData); }
// Test image: Input\Png\Bike.png private static void RunColorSpaceTransformTestWithBikeImage() { // arrange uint[] expectedData = { 4278714368, 4278192876, 4278198304, 4278198304, 4278190304, 4278190080, 4278190080, 4278198272, 4278197760, 4278198816, 4278197794, 4278197774, 4278190080, 4278190080, 4278198816, 4278197281, 4278197280, 4278197792, 4278200353, 4278191343, 4278190304, 4294713873, 4278198784, 4294844416, 4278201578, 4278200044, 4278191343, 4278190288, 4294705200, 4294717139, 4278203628, 4278201064, 4278201586, 4278197792, 4279240909 }; // Convert image pixels to bgra array. byte[] imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.Webp.Lossy.BikeSmall)); using var image = Image.Load <Rgba32>(imgBytes, new WebpDecoder()); uint[] bgra = ToBgra(image); int colorTransformBits = 4; int transformWidth = LosslessUtils.SubSampleSize(image.Width, colorTransformBits); int transformHeight = LosslessUtils.SubSampleSize(image.Height, colorTransformBits); uint[] transformData = new uint[transformWidth * transformHeight]; int[] scratch = new int[256]; // act PredictorEncoder.ColorSpaceTransform(image.Width, image.Height, colorTransformBits, 75, bgra, transformData, scratch); // assert Assert.Equal(expectedData, transformData); }
/// <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 RunTransformColorTest() { uint[] pixelData = { 5998579, 65790, 130301, 16646653, 196350, 130565, 16712702, 16583164, 16452092, 65790, 782600, 647446, 16571414, 16448771, 263931, 132601, 16711935, 131072, 511, 16711679, 132350, 329469, 16647676, 132093, 66303, 16647169, 16515584, 196607, 196096, 16646655, 514, 131326, 16712192, 327169, 16646655, 16776960, 3, 16712190, 511, 16646401, 16580612, 65535, 196092, 327425,16319743, 392450, 196861, 16712192, 16711680, 130564, 16451071 }; var m = new Vp8LMultipliers() { GreenToBlue = 240, GreenToRed = 232, RedToBlue = 0 }; uint[] expectedOutput = { 100279, 65790, 16710907, 16712190, 130813, 65028, 131840, 264449, 133377, 65790, 61697, 15917319, 14801924, 16317698, 591614, 394748, 16711935, 131072, 65792, 16711679, 328704, 656896, 132607, 328703, 197120, 66563, 16646657, 196607, 130815, 16711936, 131587, 131326, 66049, 261632, 16711936, 16776960, 3, 511, 65792, 16711938, 16580612, 65535, 65019, 327425, 16516097, 261377, 196861,66049, 16711680, 65027, 16712962 }; LosslessUtils.TransformColor(m, pixelData, pixelData.Length); Assert.Equal(expectedOutput, pixelData); }
public double GetLengthCost(int length) { int extraBits = 0; int code = LosslessUtils.PrefixEncodeBits(length, ref extraBits); return(this.Literal[ValuesInBytes + code] + extraBits); }
private static void ConvertPopulationCountTableToBitEstimates(int numSymbols, uint[] populationCounts, double[] output) { uint sum = 0; int nonzeros = 0; for (int i = 0; i < numSymbols; i++) { sum += populationCounts[i]; if (populationCounts[i] > 0) { nonzeros++; } } if (nonzeros <= 1) { output.AsSpan(0, numSymbols).Clear(); } else { double logsum = LosslessUtils.FastLog2(sum); for (int i = 0; i < numSymbols; i++) { output[i] = logsum - LosslessUtils.FastLog2(populationCounts[i]); } } }
private void UpdateDecoder(Vp8LDecoder decoder, int width, int height) { int numBits = decoder.Metadata.HuffmanSubSampleBits; decoder.Width = width; decoder.Height = height; decoder.Metadata.HuffmanXSize = LosslessUtils.SubSampleSize(width, numBits); decoder.Metadata.HuffmanMask = numBits == 0 ? ~0 : (1 << numBits) - 1; }
// Test image: Input\Webp\peak.png private static void RunColorSpaceTransformTestWithPeakImage() { // arrange uint[] expectedData = { 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4294577152, 4294707200, 4294707200, 4294707200, 4294707200, 4294837248, 4294837248, 4293926912, 4294316544, 4278191104, 4278191104, 4294837248, 4294837248, 4280287232, 4280350720, 4294447104, 4294707200, 4294838272, 4278516736, 4294837248, 4294837248, 4278516736, 4294707200, 4279298048, 4294837248, 4294837248, 4294837248, 4294837248, 4280287232, 4280287232, 4292670464, 4279633408, 4294838272, 4294837248, 4278516736, 4278516736, 4278516736, 4278516736, 4278516736, 4278778880, 4278193152, 4278191104, 4280287232, 4280287232, 4280287232, 4280287232, 4293971968, 4280612864, 4292802560, 4294837760, 4278516736, 4278516736, 4294837760, 4294707712, 4278516736, 4294837248, 4278193152, 4280287232, 4278984704, 4280287232, 4278243328, 4280287232, 4278244352, 4280287232, 4280025088, 4280025088, 4294837760, 4278192128, 4294838784, 4294837760, 4294707712, 4278778880, 4278324224, 4280287232, 4280287232, 4278202368, 4279115776, 4280287232, 4278243328, 4280287232, 4280287232, 4280025088, 4280287232, 4278192128, 4294838272, 4294838272, 4294837760, 4278190592, 4278778880, 4280875008, 4280287232, 4279896576, 4281075712, 4281075712, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4278190592, 4294709248, 4278516736, 4278516736, 4278584832, 4278909440, 4280287232, 4280287232, 4294367744, 4294621184, 4279115776, 4280287232, 4280287232, 4280351744, 4280287232, 4280287232, 4280287232, 4278513664, 4278516736, 4278716416, 4278584832, 4280291328, 4293062144, 4280287232, 4280287232, 4280287232, 4294456320, 4280291328, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4278513152, 4278716416, 4278584832, 4280291328, 4278198272, 4278198272, 4278589952, 4278198272, 4278198272, 4280287232, 4278765568, 4280287232, 4280287232, 4280287232, 4280287232, 4294712832, 4278513152, 4278716640, 4279300608, 4278584832, 4280156672, 4279373312, 4278589952, 4279373312, 4278328832, 4278328832, 4278328832, 4279634432, 4280287232, 4280287232, 4280287232, 4280287232, 4278457344, 4280483328, 4278584832, 4278385664, 4279634432, 4279373312, 4279634432, 4280287232, 4280287232, 4280156672, 4278589952, 4278328832, 4278198272, 4280156672, 4280483328, 4294363648, 4280287232, 4278376448, 4280287232, 4278647808, 4280287232, 4280287232, 4279373312, 4280287232, 4280287232, 4280156672, 4280287232, 4278198272, 4278198272, 4280156672, 4280287232, 4280287232, 4293669888, 4278765568, 4278765568, 4280287232, 4280287232, 4280287232, 4279634432, 4279634432, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4279373312, 4279764992, 4293539328, 4279896576, 4280287232, 4280287232, 4280287232, 4279634432, 4278198272, 4279634432, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4279503872, 4279503872, 4280288256, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232 }; // Convert image pixels to bgra array. byte[] imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.Webp.Peak)); using var image = Image.Load <Rgba32>(imgBytes); uint[] bgra = ToBgra(image); int colorTransformBits = 3; int transformWidth = LosslessUtils.SubSampleSize(image.Width, colorTransformBits); int transformHeight = LosslessUtils.SubSampleSize(image.Height, colorTransformBits); uint[] transformData = new uint[transformWidth * transformHeight]; int[] scratch = new int[256]; // act PredictorEncoder.ColorSpaceTransform(image.Width, image.Height, colorTransformBits, 75, bgra, transformData, scratch); // assert Assert.Equal(expectedData, transformData); }
private static void RunCombinedShannonEntropyTest() { int[] x = { 3, 5, 2, 5, 3, 1, 2, 2, 3, 3, 1, 2, 1, 2, 1, 1, 0, 0, 0, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 1, 1, 0, 0, 2, 1, 1, 0, 3, 1, 2, 3, 2, 3 }; int[] y = { 11, 12, 8, 3, 4, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 2, 1, 1, 2, 4, 6, 4 }; float expected = 884.7585f; float actual = LosslessUtils.CombinedShannonEntropy(x, y); Assert.Equal(expected, actual, 5); }
/// <summary> /// Reads the transformations, if any are present. /// </summary> /// <param name="xSize">The width of the image.</param> /// <param name="ySize">The height of the image.</param> /// <param name="decoder">Vp8LDecoder where the transformations will be stored.</param> private void ReadTransformation(int xSize, int ySize, Vp8LDecoder decoder) { var transformType = (Vp8LTransformType)this.bitReader.ReadValue(2); var transform = new Vp8LTransform(transformType, xSize, ySize); // Each transform is allowed to be used only once. foreach (Vp8LTransform decoderTransform in decoder.Transforms) { if (decoderTransform.TransformType == transform.TransformType) { WebpThrowHelper.ThrowImageFormatException("Each transform can only be present once"); } } switch (transformType) { case Vp8LTransformType.SubtractGreen: // There is no data associated with this transform. break; case Vp8LTransformType.ColorIndexingTransform: // The transform data contains color table size and the entries in the color table. // 8 bit value for color table size. uint numColors = this.bitReader.ReadValue(8) + 1; int bits = numColors > 16 ? 0 : numColors > 4 ? 1 : numColors > 2 ? 2 : 3; transform.Bits = bits; using (IMemoryOwner <uint> colorMap = this.DecodeImageStream(decoder, (int)numColors, 1, false)) { int finalNumColors = 1 << (8 >> transform.Bits); IMemoryOwner <uint> newColorMap = this.memoryAllocator.Allocate <uint>(finalNumColors, AllocationOptions.Clean); LosslessUtils.ExpandColorMap((int)numColors, colorMap.GetSpan(), newColorMap.GetSpan()); transform.Data = newColorMap; } break; case Vp8LTransformType.PredictorTransform: case Vp8LTransformType.CrossColorTransform: { // The first 3 bits of prediction data define the block width and height in number of bits. transform.Bits = (int)this.bitReader.ReadValue(3) + 2; int blockWidth = LosslessUtils.SubSampleSize(transform.XSize, transform.Bits); int blockHeight = LosslessUtils.SubSampleSize(transform.YSize, transform.Bits); IMemoryOwner <uint> transformData = this.DecodeImageStream(decoder, blockWidth, blockHeight, false); transform.Data = transformData; break; } } decoder.Transforms.Add(transform); }
public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs refs, int quality, int histoBits, int cacheBits, List <Vp8LHistogram> imageHisto, Vp8LHistogram tmpHisto, ushort[] histogramSymbols) { int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(xSize, histoBits) : 1; int histoYSize = histoBits > 0 ? LosslessUtils.SubSampleSize(ySize, histoBits) : 1; int imageHistoRawSize = histoXSize * histoYSize; int entropyCombineNumBins = BinSize; ushort[] mapTmp = new ushort[imageHistoRawSize]; ushort[] clusterMappings = new ushort[imageHistoRawSize]; var origHisto = new List <Vp8LHistogram>(imageHistoRawSize); for (int i = 0; i < imageHistoRawSize; i++) { origHisto.Add(new Vp8LHistogram(cacheBits)); } // Construct the histograms from the backward references. HistogramBuild(xSize, histoBits, refs, origHisto); // Copies the histograms and computes its bitCost. histogramSymbols is optimized. int numUsed = HistogramCopyAndAnalyze(origHisto, imageHisto, histogramSymbols); bool entropyCombine = numUsed > entropyCombineNumBins * 2 && quality < 100; if (entropyCombine) { ushort[] binMap = mapTmp; int numClusters = numUsed; double combineCostFactor = GetCombineCostFactor(imageHistoRawSize, quality); HistogramAnalyzeEntropyBin(imageHisto, binMap); // Collapse histograms with similar entropy. HistogramCombineEntropyBin(imageHisto, histogramSymbols, clusterMappings, tmpHisto, binMap, entropyCombineNumBins, combineCostFactor); OptimizeHistogramSymbols(clusterMappings, numClusters, mapTmp, histogramSymbols); } float x = quality / 100.0f; // Cubic ramp between 1 and MaxHistoGreedy: int thresholdSize = (int)(1 + (x * x * x * (MaxHistoGreedy - 1))); bool doGreedy = HistogramCombineStochastic(imageHisto, thresholdSize); if (doGreedy) { RemoveEmptyHistograms(imageHisto); HistogramCombineGreedy(imageHisto); } // Find the optimal map from original histograms to the final ones. RemoveEmptyHistograms(imageHisto); HistogramRemap(origHisto, imageHisto, histogramSymbols); }
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++; } } }
private static void RunPredictor12Test() { // arrange uint[] topData = { 4294844413, 4294779388 }; uint left = 4294844413; uint expectedResult = 4294779388; // act unsafe { fixed(uint *top = &topData[1]) { uint actual = LosslessUtils.Predictor12(left, top); // assert Assert.Equal(expectedResult, actual); } } }
/// <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++; } } }
private static void RunPredictor13Test() { // arrange uint[] topData = { 4278193922, 4278193666 }; uint left = 4278193410; uint expectedResult = 4278193154; // act unsafe { fixed(uint *top = &topData[1]) { uint actual = LosslessUtils.Predictor13(left, top); // assert Assert.Equal(expectedResult, actual); } } }
public static void CollectColorBlueTransforms(Span <uint> bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, Span <int> histo) { #if SUPPORTS_RUNTIME_INTRINSICS if (Avx2.IsSupported && tileWidth >= 16) { const int span = 16; Span <ushort> values = stackalloc ushort[span]; var multsr = Vector256.Create(LosslessUtils.Cst5b(redToBlue)); var multsg = Vector256.Create(LosslessUtils.Cst5b(greenToBlue)); for (int y = 0; y < tileHeight; y++) { Span <uint> srcSpan = bgra.Slice(y * stride); ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); for (nint x = 0; x <= tileWidth - span; x += span) { nint input0Idx = x; nint input1Idx = x + (span / 2); Vector256 <byte> input0 = Unsafe.As <uint, Vector256 <uint> >(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); Vector256 <byte> input1 = Unsafe.As <uint, Vector256 <uint> >(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); Vector256 <byte> r0 = Avx2.Shuffle(input0, CollectColorBlueTransformsShuffleLowMask256); Vector256 <byte> r1 = Avx2.Shuffle(input1, CollectColorBlueTransformsShuffleHighMask256); Vector256 <byte> r = Avx2.Or(r0, r1); Vector256 <byte> gb0 = Avx2.And(input0, CollectColorBlueTransformsGreenBlueMask256); Vector256 <byte> gb1 = Avx2.And(input1, CollectColorBlueTransformsGreenBlueMask256); Vector256 <ushort> gb = Avx2.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); Vector256 <byte> g = Avx2.And(gb.AsByte(), CollectColorBlueTransformsGreenMask256); Vector256 <short> a = Avx2.MultiplyHigh(r.AsInt16(), multsr); Vector256 <short> b = Avx2.MultiplyHigh(g.AsInt16(), multsg); Vector256 <byte> c = Avx2.Subtract(gb.AsByte(), b.AsByte()); Vector256 <byte> d = Avx2.Subtract(c, a.AsByte()); Vector256 <byte> e = Avx2.And(d, CollectColorBlueTransformsBlueMask256); ref ushort outputRef = ref MemoryMarshal.GetReference(values); Unsafe.As <ushort, Vector256 <ushort> >(ref outputRef) = e.AsUInt16(); for (int i = 0; i < span; i++) { ++histo[values[i]]; } } }
public void BitsEntropyUnrefined(Span <uint> array, int n) { this.Init(); for (int i = 0; i < n; i++) { if (array[i] != 0) { this.Sum += array[i]; this.NoneZeroCode = (uint)i; this.NoneZeros++; this.Entropy -= LosslessUtils.FastSLog2(array[i]); if (this.MaxVal < array[i]) { this.MaxVal = array[i]; } } } this.Entropy += LosslessUtils.FastSLog2(this.Sum); }
private static void RunPredictor11Test() { // arrange uint[] topData = { 4278258949, 4278258949 }; uint left = 4294839812; short[] scratch = new short[8]; uint expectedResult = 4294839812; // act unsafe { fixed(uint *top = &topData[1]) { uint actual = LosslessUtils.Predictor11(left, top, scratch); // assert Assert.Equal(expectedResult, actual); } } }
public void GetEntropyUnrefined(uint[] x, int length, Vp8LStreaks stats) { int i; int iPrev = 0; uint xPrev = x[0]; this.Init(); for (i = 1; i < length; i++) { uint xi = x[i]; if (xi != xPrev) { this.GetEntropyUnrefined(xi, i, ref xPrev, ref iPrev, stats); } } this.GetEntropyUnrefined(0, i, ref xPrev, ref iPrev, stats); this.Entropy += LosslessUtils.FastSLog2(this.Sum); }
public static void ApplyNearLossless(int xSize, int ySize, int quality, Span <uint> argbSrc, Span <uint> argbDst, int stride) { uint[] copyBuffer = new uint[xSize * 3]; int limitBits = LosslessUtils.NearLosslessBits(quality); // For small icon images, don't attempt to apply near-lossless compression. if ((xSize < MinDimForNearLossless && ySize < MinDimForNearLossless) || ySize < 3) { for (int i = 0; i < ySize; i++) { argbSrc.Slice(i * stride, xSize).CopyTo(argbDst.Slice(i * xSize, xSize)); } return; } NearLossless(xSize, ySize, argbSrc, stride, limitBits, copyBuffer, argbDst); for (int i = limitBits - 1; i != 0; i--) { NearLossless(xSize, ySize, argbDst, xSize, i, copyBuffer, argbDst); } }
#pragma warning restore SA1503 // Braces should not be omitted /// <summary> /// Quantize every component of the difference between the actual pixel value and /// its prediction to a multiple of a quantization (a power of 2, not larger than /// maxQuantization which is a power of 2, smaller than maxDiff). Take care if /// value and predict have undergone subtract green, which means that red and /// blue are represented as offsets from green. /// </summary> private static uint NearLossless(uint value, uint predict, int maxQuantization, int maxDiff, bool usedSubtractGreen) { byte newGreen = 0; byte greenDiff = 0; byte a; if (maxDiff <= 2) { return(LosslessUtils.SubPixels(value, predict)); } int quantization = maxQuantization; while (quantization >= maxDiff) { quantization >>= 1; } if (value >> 24 is 0 or 0xff) { // Preserve transparency of fully transparent or fully opaque pixels. a = NearLosslessDiff((byte)((value >> 24) & 0xff), (byte)((predict >> 24) & 0xff)); }
private void GetEntropyUnrefined(uint val, int i, ref uint valPrev, ref int iPrev, Vp8LStreaks stats) { int streak = i - iPrev; // Gather info for the bit entropy. if (valPrev != 0) { this.Sum += (uint)(valPrev * streak); this.NoneZeros += streak; this.NoneZeroCode = (uint)iPrev; this.Entropy -= LosslessUtils.FastSLog2(valPrev) * streak; if (this.MaxVal < valPrev) { this.MaxVal = valPrev; } } // Gather info for the Huffman cost. stats.Counts[valPrev != 0 ? 1 : 0] += streak > 3 ? 1 : 0; stats.Streaks[valPrev != 0 ? 1 : 0][streak > 3 ? 1 : 0] += streak; valPrev = val; iPrev = i; }
/// <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); }
public void Fill(ReadOnlySpan <uint> bgra, int quality, int xSize, int ySize, bool lowEffort) { int size = xSize * ySize; int iterMax = GetMaxItersForQuality(quality); int windowSize = GetWindowSizeForHashChain(quality, xSize); int pos; if (size <= 2) { this.OffsetLength.GetSpan()[0] = 0; return; } using IMemoryOwner <int> hashToFirstIndexBuffer = this.memoryAllocator.Allocate <int>(HashSize); using IMemoryOwner <int> chainBuffer = this.memoryAllocator.Allocate <int>(size, AllocationOptions.Clean); Span <int> hashToFirstIndex = hashToFirstIndexBuffer.GetSpan(); Span <int> chain = chainBuffer.GetSpan(); // Initialize hashToFirstIndex array to -1. hashToFirstIndex.Fill(-1); // Fill the chain linking pixels with the same hash. bool bgraComp = bgra.Length > 1 && bgra[0] == bgra[1]; Span <uint> tmp = stackalloc uint[2]; 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. tmp.Clear(); 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 > BackwardReferenceEncoder.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 - BackwardReferenceEncoder.MaxLength); len = BackwardReferenceEncoder.MaxLength; } // Process the rest of the hash chain. while (len > 0) { tmp[1] = len--; hashCode = GetPixPairHash64(tmp); chain[pos] = hashToFirstIndex[(int)hashCode]; hashToFirstIndex[(int)hashCode] = pos++; } bgraComp = false; } else { // Just move one pixel forward. hashCode = GetPixPairHash64(bgra.Slice(pos)); chain[pos] = hashToFirstIndex[(int)hashCode]; hashToFirstIndex[(int)hashCode] = pos++; bgraComp = bgraCompNext; } } // Process the penultimate pixel. chain[pos] = hashToFirstIndex[(int)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). Span <uint> offsetLength = this.OffsetLength.GetSpan(); offsetLength[0] = offsetLength[size - 1] = 0; for (int basePosition = size - 2; basePosition > 0;) { int maxLen = LosslessUtils.MaxFindCopyLength(size - 1 - basePosition); int bgraStart = basePosition; int iter = iterMax; int bestLength = 0; uint bestDistance = 0; int minPos = basePosition > windowSize ? basePosition - windowSize : 0; int lengthMax = maxLen < 256 ? maxLen : 256; pos = chain[basePosition]; int currLength; if (!lowEffort) { // Heuristic: use the comparison with the above line as an initialization. if (basePosition >= (uint)xSize) { currLength = LosslessUtils.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 = LosslessUtils.FindMatchLength(bgra.Slice(bgraStart - 1), bgra.Slice(bgraStart), bestLength, maxLen); if (currLength > bestLength) { bestLength = currLength; bestDistance = 1; } iter--; // Skip the for loop if we already have the maximum. if (bestLength == BackwardReferenceEncoder.MaxLength) { pos = minPos - 1; } } uint bestBgra = bgra.Slice(bgraStart)[bestLength]; for (; pos >= minPos && (--iter > 0); pos = chain[pos]) { if (bgra[pos + bestLength] != bestBgra) { continue; } currLength = LosslessUtils.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. uint maxBasePosition = (uint)basePosition; while (true) { offsetLength[basePosition] = (bestDistance << BackwardReferenceEncoder.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 == BackwardReferenceEncoder.MaxLength && bestDistance != 1 && basePosition + BackwardReferenceEncoder.MaxLength < maxBasePosition) { break; } if (bestLength < BackwardReferenceEncoder.MaxLength) { bestLength++; maxBasePosition = (uint)basePosition; } } } }
/// <summary> /// Stores the difference between the pixel and its prediction in "output". /// In case of a lossy encoding, updates the source image to avoid propagating /// the deviation further to pixels which depend on the current pixel for their /// predictions. /// </summary> private static void GetResidual( int width, int height, Span <uint> upperRowSpan, Span <uint> currentRowSpan, Span <byte> maxDiffs, int mode, int xStart, int xEnd, int y, int maxQuantization, WebpTransparentColorMode transparentColorMode, bool usedSubtractGreen, bool nearLossless, Span <uint> output, Span <short> scratch) { if (transparentColorMode == WebpTransparentColorMode.Preserve) { PredictBatch(mode, xStart, y, xEnd - xStart, currentRowSpan, upperRowSpan, output, scratch); } else { #pragma warning disable SA1503 // Braces should not be omitted fixed(uint *currentRow = currentRowSpan) fixed(uint *upperRow = upperRowSpan) { for (int x = xStart; x < xEnd; x++) { uint predict = 0; uint residual; if (y == 0) { predict = x == 0 ? WebpConstants.ArgbBlack : currentRow[x - 1]; // Left. } else if (x == 0) { predict = upperRow[x]; // Top. } else { switch (mode) { case 0: predict = WebpConstants.ArgbBlack; break; case 1: predict = currentRow[x - 1]; break; case 2: predict = LosslessUtils.Predictor2(currentRow[x - 1], upperRow + x); break; case 3: predict = LosslessUtils.Predictor3(currentRow[x - 1], upperRow + x); break; case 4: predict = LosslessUtils.Predictor4(currentRow[x - 1], upperRow + x); break; case 5: predict = LosslessUtils.Predictor5(currentRow[x - 1], upperRow + x); break; case 6: predict = LosslessUtils.Predictor6(currentRow[x - 1], upperRow + x); break; case 7: predict = LosslessUtils.Predictor7(currentRow[x - 1], upperRow + x); break; case 8: predict = LosslessUtils.Predictor8(currentRow[x - 1], upperRow + x); break; case 9: predict = LosslessUtils.Predictor9(currentRow[x - 1], upperRow + x); break; case 10: predict = LosslessUtils.Predictor10(currentRow[x - 1], upperRow + x); break; case 11: predict = LosslessUtils.Predictor11(currentRow[x - 1], upperRow + x, scratch); break; case 12: predict = LosslessUtils.Predictor12(currentRow[x - 1], upperRow + x); break; case 13: predict = LosslessUtils.Predictor13(currentRow[x - 1], upperRow + x); break; } } if (nearLossless) { if (maxQuantization == 1 || mode == 0 || y == 0 || y == height - 1 || x == 0 || x == width - 1) { residual = LosslessUtils.SubPixels(currentRow[x], predict); } else { residual = NearLossless(currentRow[x], predict, maxQuantization, maxDiffs[x], usedSubtractGreen); // Update the source image. currentRow[x] = LosslessUtils.AddPixels(predict, residual); // x is never 0 here so we do not need to update upperRow like below. } } else { residual = LosslessUtils.SubPixels(currentRow[x], predict); } if ((currentRow[x] & MaskAlpha) == 0) { // If alpha is 0, cleanup RGB. We can choose the RGB values of the // residual for best compression. The prediction of alpha itself can be // non-zero and must be kept though. We choose RGB of the residual to be // 0. residual &= MaskAlpha; // Update the source image. currentRow[x] = predict & ~MaskAlpha; // The prediction for the rightmost pixel in a row uses the leftmost // pixel // in that row as its top-right context pixel. Hence if we change the // leftmost pixel of current_row, the corresponding change must be // applied // to upperRow as well where top-right context is being read from. if (x == 0 && y != 0) { upperRow[width] = currentRow[0]; } } output[x - xStart] = residual; } } } }
/// <summary> /// Finds the best predictor for each tile, and converts the image to residuals /// with respect to predictions. If nearLosslessQuality < 100, applies /// near lossless processing, shaving off more bits of residuals for lower qualities. /// </summary> public static void ResidualImage( int width, int height, int bits, Span <uint> bgra, Span <uint> bgraScratch, Span <uint> image, int[][] histoArgb, int[][] bestHisto, bool nearLossless, int nearLosslessQuality, WebpTransparentColorMode transparentColorMode, bool usedSubtractGreen, bool lowEffort) { int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); int tilesPerCol = LosslessUtils.SubSampleSize(height, bits); int maxQuantization = 1 << LosslessUtils.NearLosslessBits(nearLosslessQuality); Span <short> scratch = stackalloc short[8]; // TODO: Can we optimize this? int[][] histo = new int[4][]; for (int i = 0; i < 4; i++) { histo[i] = new int[256]; } if (lowEffort) { for (int i = 0; i < tilesPerRow * tilesPerCol; i++) { image[i] = WebpConstants.ArgbBlack | (PredLowEffort << 8); } } else { for (int tileY = 0; tileY < tilesPerCol; tileY++) { for (int tileX = 0; tileX < tilesPerRow; tileX++) { int pred = GetBestPredictorForTile( width, height, tileX, tileY, bits, histo, bgraScratch, bgra, histoArgb, bestHisto, maxQuantization, transparentColorMode, usedSubtractGreen, nearLossless, image, scratch); image[(tileY * tilesPerRow) + tileX] = (uint)(WebpConstants.ArgbBlack | (pred << 8)); } } } CopyImageWithPrediction( width, height, bits, image, bgraScratch, bgra, maxQuantization, transparentColorMode, usedSubtractGreen, nearLossless, lowEffort); }
public IMemoryOwner <uint> DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0) { int transformXSize = xSize; int transformYSize = ySize; int numberOfTransformsPresent = 0; if (isLevel0) { decoder.Transforms = new List <Vp8LTransform>(WebpConstants.MaxNumberOfTransforms); // Next bit indicates, if a transformation is present. while (this.bitReader.ReadBit()) { if (numberOfTransformsPresent > WebpConstants.MaxNumberOfTransforms) { WebpThrowHelper.ThrowImageFormatException($"The maximum number of transforms of {WebpConstants.MaxNumberOfTransforms} was exceeded"); } this.ReadTransformation(transformXSize, transformYSize, decoder); if (decoder.Transforms[numberOfTransformsPresent].TransformType == Vp8LTransformType.ColorIndexingTransform) { transformXSize = LosslessUtils.SubSampleSize(transformXSize, decoder.Transforms[numberOfTransformsPresent].Bits); } numberOfTransformsPresent++; } } else { decoder.Metadata = new Vp8LMetadata(); } // Color cache. bool isColorCachePresent = this.bitReader.ReadBit(); int colorCacheBits = 0; int colorCacheSize = 0; if (isColorCachePresent) { colorCacheBits = (int)this.bitReader.ReadValue(4); // Note: According to webpinfo color cache bits of 11 are valid, even though 10 is defined in the source code as maximum. // That is why 11 bits is also considered valid here. bool colorCacheBitsIsValid = colorCacheBits is >= 1 and <= WebpConstants.MaxColorCacheBits + 1; if (!colorCacheBitsIsValid) { WebpThrowHelper.ThrowImageFormatException("Invalid color cache bits found"); } } // Read the Huffman codes (may recurse). this.ReadHuffmanCodes(decoder, transformXSize, transformYSize, colorCacheBits, isLevel0); decoder.Metadata.ColorCacheSize = colorCacheSize; // Finish setting up the color-cache. if (isColorCachePresent) { decoder.Metadata.ColorCache = new ColorCache(); colorCacheSize = 1 << colorCacheBits; decoder.Metadata.ColorCacheSize = colorCacheSize; decoder.Metadata.ColorCache.Init(colorCacheBits); } else { decoder.Metadata.ColorCacheSize = 0; } this.UpdateDecoder(decoder, transformXSize, transformYSize); if (isLevel0) { // level 0 complete. return(null); } // Use the Huffman trees to decode the LZ77 encoded data. IMemoryOwner <uint> pixelData = this.memoryAllocator.Allocate <uint>(decoder.Width * decoder.Height, AllocationOptions.Clean); this.DecodeImageData(decoder, pixelData.GetSpan()); return(pixelData); }
private void ReadHuffmanCodes(Vp8LDecoder decoder, int xSize, int ySize, int colorCacheBits, bool allowRecursion) { int maxAlphabetSize = 0; int numHTreeGroups = 1; int numHTreeGroupsMax = 1; // If the next bit is zero, there is only one meta Huffman code used everywhere in the image. No more data is stored. // If this bit is one, the image uses multiple meta Huffman codes. These meta Huffman codes are stored as an entropy image. if (allowRecursion && this.bitReader.ReadBit()) { // Use meta Huffman codes. int huffmanPrecision = (int)(this.bitReader.ReadValue(3) + 2); int huffmanXSize = LosslessUtils.SubSampleSize(xSize, huffmanPrecision); int huffmanYSize = LosslessUtils.SubSampleSize(ySize, huffmanPrecision); int huffmanPixels = huffmanXSize * huffmanYSize; IMemoryOwner <uint> huffmanImage = this.DecodeImageStream(decoder, huffmanXSize, huffmanYSize, false); Span <uint> huffmanImageSpan = huffmanImage.GetSpan(); decoder.Metadata.HuffmanSubSampleBits = huffmanPrecision; // TODO: Isn't huffmanPixels the length of the span? for (int i = 0; i < huffmanPixels; i++) { // The huffman data is stored in red and green bytes. uint group = (huffmanImageSpan[i] >> 8) & 0xffff; huffmanImageSpan[i] = group; if (group >= numHTreeGroupsMax) { numHTreeGroupsMax = (int)group + 1; } } numHTreeGroups = numHTreeGroupsMax; decoder.Metadata.HuffmanImage = huffmanImage; } // Find maximum alphabet size for the hTree group. for (int j = 0; j < WebpConstants.HuffmanCodesPerMetaCode; j++) { int alphabetSize = WebpConstants.AlphabetSize[j]; if (j == 0 && colorCacheBits > 0) { alphabetSize += 1 << colorCacheBits; } if (maxAlphabetSize < alphabetSize) { maxAlphabetSize = alphabetSize; } } int tableSize = TableSize[colorCacheBits]; var huffmanTables = new HuffmanCode[numHTreeGroups * tableSize]; var hTreeGroups = new HTreeGroup[numHTreeGroups]; Span <HuffmanCode> huffmanTable = huffmanTables.AsSpan(); int[] codeLengths = new int[maxAlphabetSize]; for (int i = 0; i < numHTreeGroupsMax; i++) { hTreeGroups[i] = new HTreeGroup(HuffmanUtils.HuffmanPackedTableSize); HTreeGroup hTreeGroup = hTreeGroups[i]; int totalSize = 0; bool isTrivialLiteral = true; int maxBits = 0; codeLengths.AsSpan().Clear(); for (int j = 0; j < WebpConstants.HuffmanCodesPerMetaCode; j++) { int alphabetSize = WebpConstants.AlphabetSize[j]; if (j == 0 && colorCacheBits > 0) { alphabetSize += 1 << colorCacheBits; } int size = this.ReadHuffmanCode(alphabetSize, codeLengths, huffmanTable); if (size == 0) { WebpThrowHelper.ThrowImageFormatException("Huffman table size is zero"); } // TODO: Avoid allocation. hTreeGroup.HTrees.Add(huffmanTable.Slice(0, size).ToArray()); HuffmanCode huffTableZero = huffmanTable[0]; if (isTrivialLiteral && LiteralMap[j] == 1) { isTrivialLiteral = huffTableZero.BitsUsed == 0; } totalSize += huffTableZero.BitsUsed; huffmanTable = huffmanTable.Slice(size); if (j <= HuffIndex.Alpha) { int localMaxBits = codeLengths[0]; int k; for (k = 1; k < alphabetSize; ++k) { int codeLengthK = codeLengths[k]; if (codeLengthK > localMaxBits) { localMaxBits = codeLengthK; } } maxBits += localMaxBits; } } hTreeGroup.IsTrivialLiteral = isTrivialLiteral; hTreeGroup.IsTrivialCode = false; if (isTrivialLiteral) { uint red = hTreeGroup.HTrees[HuffIndex.Red][0].Value; uint blue = hTreeGroup.HTrees[HuffIndex.Blue][0].Value; uint green = hTreeGroup.HTrees[HuffIndex.Green][0].Value; uint alpha = hTreeGroup.HTrees[HuffIndex.Alpha][0].Value; hTreeGroup.LiteralArb = (alpha << 24) | (red << 16) | blue; if (totalSize == 0 && green < WebpConstants.NumLiteralCodes) { hTreeGroup.IsTrivialCode = true; hTreeGroup.LiteralArb |= green << 8; } } hTreeGroup.UsePackedTable = !hTreeGroup.IsTrivialCode && maxBits < HuffmanUtils.HuffmanPackedBits; if (hTreeGroup.UsePackedTable) { this.BuildPackedTable(hTreeGroup); } } decoder.Metadata.NumHTreeGroups = numHTreeGroups; decoder.Metadata.HTreeGroups = hTreeGroups; decoder.Metadata.HuffmanTables = huffmanTables; }