Browse Source

Merge branch 'master' into bp/shanonsse

pull/1848/head
Brian Popow 5 years ago
committed by GitHub
parent
commit
93f06bb533
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/ImageSharp/Formats/Webp/EntropyIx.cs
  2. 2
      src/ImageSharp/Formats/Webp/HistoIx.cs
  3. 49
      src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs
  4. 74
      src/ImageSharp/Formats/Webp/Lossless/CostManager.cs
  5. 10
      src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs
  6. 2
      src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs
  7. 4
      src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs
  8. 51
      src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs
  9. 2
      src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs
  10. 2
      src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs
  11. 2
      src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs
  12. 26
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  13. 40
      src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs
  14. 27
      src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs
  15. 3
      src/ImageSharp/Formats/Webp/WebpLookupTables.cs
  16. 2
      tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs
  17. 2
      tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs
  18. 2
      tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs
  19. 2
      tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs

2
src/ImageSharp/Formats/Webp/EntropyIx.cs

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// <summary> /// <summary>
/// These five modes are evaluated and their respective entropy is computed. /// These five modes are evaluated and their respective entropy is computed.
/// </summary> /// </summary>
internal enum EntropyIx internal enum EntropyIx : byte
{ {
Direct = 0, Direct = 0,

2
src/ImageSharp/Formats/Webp/HistoIx.cs

@ -3,7 +3,7 @@
namespace SixLabors.ImageSharp.Formats.Webp namespace SixLabors.ImageSharp.Formats.Webp
{ {
internal enum HistoIx internal enum HistoIx : byte
{ {
HistoAlpha = 0, HistoAlpha = 0,

49
src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs

@ -2,11 +2,13 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Webp.Lossless namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{ {
internal class BackwardReferenceEncoder internal static class BackwardReferenceEncoder
{ {
/// <summary> /// <summary>
/// Maximum bit length. /// Maximum bit length.
@ -41,6 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
int quality, int quality,
int lz77TypesToTry, int lz77TypesToTry,
ref int cacheBits, ref int cacheBits,
MemoryAllocator memoryAllocator,
Vp8LHashChain hashChain, Vp8LHashChain hashChain,
Vp8LBackwardRefs best, Vp8LBackwardRefs best,
Vp8LBackwardRefs worst) Vp8LBackwardRefs worst)
@ -69,7 +72,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
BackwardReferencesLz77(width, height, bgra, 0, hashChain, worst); BackwardReferencesLz77(width, height, bgra, 0, hashChain, worst);
break; break;
case Vp8LLz77Type.Lz77Box: case Vp8LLz77Type.Lz77Box:
hashChainBox = new Vp8LHashChain(width * height); hashChainBox = new Vp8LHashChain(memoryAllocator, width * height);
BackwardReferencesLz77Box(width, height, bgra, 0, hashChain, hashChainBox, worst); BackwardReferencesLz77Box(width, height, bgra, 0, hashChain, hashChainBox, worst);
break; break;
} }
@ -100,7 +103,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
if ((lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard || lz77TypeBest == (int)Vp8LLz77Type.Lz77Box) && quality >= 25) if ((lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard || lz77TypeBest == (int)Vp8LLz77Type.Lz77Box) && quality >= 25)
{ {
Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox; Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox;
BackwardReferencesTraceBackwards(width, height, bgra, cacheBits, hashChainTmp, best, worst); BackwardReferencesTraceBackwards(width, height, memoryAllocator, bgra, cacheBits, hashChainTmp, best, worst);
var histo = new Vp8LHistogram(worst, cacheBits); var histo = new Vp8LHistogram(worst, cacheBits);
double bitCostTrace = histo.EstimateBits(stats, bitsEntropy); double bitCostTrace = histo.EstimateBits(stats, bitsEntropy);
if (bitCostTrace < bitCostBest) if (bitCostTrace < bitCostBest)
@ -111,6 +114,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
BackwardReferences2DLocality(width, best); BackwardReferences2DLocality(width, best);
hashChainBox?.Dispose();
return best; return best;
} }
@ -234,6 +239,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
private static void BackwardReferencesTraceBackwards( private static void BackwardReferencesTraceBackwards(
int xSize, int xSize,
int ySize, int ySize,
MemoryAllocator memoryAllocator,
ReadOnlySpan<uint> bgra, ReadOnlySpan<uint> bgra,
int cacheBits, int cacheBits,
Vp8LHashChain hashChain, Vp8LHashChain hashChain,
@ -241,22 +247,24 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
Vp8LBackwardRefs refsDst) Vp8LBackwardRefs refsDst)
{ {
int distArraySize = xSize * ySize; int distArraySize = xSize * ySize;
ushort[] distArray = new ushort[distArraySize]; using IMemoryOwner<ushort> distArrayBuffer = memoryAllocator.Allocate<ushort>(distArraySize);
Span<ushort> distArray = distArrayBuffer.GetSpan();
BackwardReferencesHashChainDistanceOnly(xSize, ySize, bgra, cacheBits, hashChain, refsSrc, distArray); BackwardReferencesHashChainDistanceOnly(xSize, ySize, memoryAllocator, bgra, cacheBits, hashChain, refsSrc, distArrayBuffer);
int chosenPathSize = TraceBackwards(distArray, distArraySize); int chosenPathSize = TraceBackwards(distArray, distArraySize);
Span<ushort> chosenPath = distArray.AsSpan(distArraySize - chosenPathSize); Span<ushort> chosenPath = distArray.Slice(distArraySize - chosenPathSize);
BackwardReferencesHashChainFollowChosenPath(bgra, cacheBits, chosenPath, chosenPathSize, hashChain, refsDst); BackwardReferencesHashChainFollowChosenPath(bgra, cacheBits, chosenPath, chosenPathSize, hashChain, refsDst);
} }
private static void BackwardReferencesHashChainDistanceOnly( private static void BackwardReferencesHashChainDistanceOnly(
int xSize, int xSize,
int ySize, int ySize,
MemoryAllocator memoryAllocator,
ReadOnlySpan<uint> bgra, ReadOnlySpan<uint> bgra,
int cacheBits, int cacheBits,
Vp8LHashChain hashChain, Vp8LHashChain hashChain,
Vp8LBackwardRefs refs, Vp8LBackwardRefs refs,
ushort[] distArray) IMemoryOwner<ushort> distArrayBuffer)
{ {
int pixCount = xSize * ySize; int pixCount = xSize * ySize;
bool useColorCache = cacheBits > 0; bool useColorCache = cacheBits > 0;
@ -275,22 +283,24 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
costModel.Build(xSize, cacheBits, refs); costModel.Build(xSize, cacheBits, refs);
var costManager = new CostManager(distArray, pixCount, costModel); using var costManager = new CostManager(memoryAllocator, distArrayBuffer, pixCount, costModel);
Span<float> costManagerCosts = costManager.Costs.GetSpan();
Span<ushort> distArray = distArrayBuffer.GetSpan();
// We loop one pixel at a time, but store all currently best points to non-processed locations from this point. // We loop one pixel at a time, but store all currently best points to non-processed locations from this point.
distArray[0] = 0; distArray[0] = 0;
// Add first pixel as literal. // Add first pixel as literal.
AddSingleLiteralWithCostModel(bgra, colorCache, costModel, 0, useColorCache, 0.0f, costManager.Costs, distArray); AddSingleLiteralWithCostModel(bgra, colorCache, costModel, 0, useColorCache, 0.0f, costManagerCosts, distArray);
for (int i = 1; i < pixCount; i++) for (int i = 1; i < pixCount; i++)
{ {
float prevCost = costManager.Costs[i - 1]; float prevCost = costManagerCosts[i - 1];
int offset = hashChain.FindOffset(i); int offset = hashChain.FindOffset(i);
int len = hashChain.FindLength(i); int len = hashChain.FindLength(i);
// Try adding the pixel as a literal. // Try adding the pixel as a literal.
AddSingleLiteralWithCostModel(bgra, colorCache, costModel, i, useColorCache, prevCost, costManager.Costs, distArray); AddSingleLiteralWithCostModel(bgra, colorCache, costModel, i, useColorCache, prevCost, costManagerCosts, distArray);
// If we are dealing with a non-literal. // If we are dealing with a non-literal.
if (len >= 2) if (len >= 2)
@ -334,7 +344,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
costManager.UpdateCostAtIndex(j - 1, false); costManager.UpdateCostAtIndex(j - 1, false);
costManager.UpdateCostAtIndex(j, false); costManager.UpdateCostAtIndex(j, false);
costManager.PushInterval(costManager.Costs[j - 1] + offsetCost, j, lenJ); costManager.PushInterval(costManagerCosts[j - 1] + offsetCost, j, lenJ);
reach = j + lenJ - 1; reach = j + lenJ - 1;
} }
} }
@ -346,7 +356,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
} }
private static int TraceBackwards(ushort[] distArray, int distArraySize) private static int TraceBackwards(Span<ushort> distArray, int distArraySize)
{ {
int chosenPathSize = 0; int chosenPathSize = 0;
int pathPos = distArraySize; int pathPos = distArraySize;
@ -426,8 +436,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
int idx, int idx,
bool useColorCache, bool useColorCache,
float prevCost, float prevCost,
float[] cost, Span<float> cost,
ushort[] distArray) Span<ushort> distArray)
{ {
double costVal = prevCost; double costVal = prevCost;
uint color = bgra[idx]; uint color = bgra[idx];
@ -617,7 +627,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
} }
hashChain.OffsetLength[0] = 0; Span<uint> hashChainOffsetLength = hashChain.OffsetLength.GetSpan();
hashChainOffsetLength[0] = 0;
for (i = 1; i < pixelCount; i++) for (i = 1; i < pixelCount; i++)
{ {
int ind; int ind;
@ -695,19 +706,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
if (bestLength <= MinLength) if (bestLength <= MinLength)
{ {
hashChain.OffsetLength[i] = 0; hashChainOffsetLength[i] = 0;
bestOffsetPrev = 0; bestOffsetPrev = 0;
bestLengthPrev = 0; bestLengthPrev = 0;
} }
else else
{ {
hashChain.OffsetLength[i] = (uint)((bestOffset << MaxLengthBits) | bestLength); hashChainOffsetLength[i] = (uint)((bestOffset << MaxLengthBits) | bestLength);
bestOffsetPrev = bestOffset; bestOffsetPrev = bestOffset;
bestLengthPrev = bestLength; bestLengthPrev = bestLength;
} }
} }
hashChain.OffsetLength[0] = 0; hashChainOffsetLength[0] = 0;
BackwardReferencesLz77(xSize, ySize, bgra, cacheBits, hashChain, refs); BackwardReferencesLz77(xSize, ySize, bgra, cacheBits, hashChain, refs);
} }

74
src/ImageSharp/Formats/Webp/Lossless/CostManager.cs

@ -1,7 +1,10 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Webp.Lossless namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{ {
@ -10,20 +13,29 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// It caches the different CostCacheInterval, caches the different /// It caches the different CostCacheInterval, caches the different
/// GetLengthCost(costModel, k) in costCache and the CostInterval's. /// GetLengthCost(costModel, k) in costCache and the CostInterval's.
/// </summary> /// </summary>
internal class CostManager internal sealed class CostManager : IDisposable
{ {
private CostInterval head; private CostInterval head;
public CostManager(ushort[] distArray, int pixCount, CostModel costModel) private const int FreeIntervalsStartCount = 25;
private readonly Stack<CostInterval> freeIntervals = new(FreeIntervalsStartCount);
public CostManager(MemoryAllocator memoryAllocator, IMemoryOwner<ushort> distArray, int pixCount, CostModel costModel)
{ {
int costCacheSize = pixCount > BackwardReferenceEncoder.MaxLength ? BackwardReferenceEncoder.MaxLength : pixCount; int costCacheSize = pixCount > BackwardReferenceEncoder.MaxLength ? BackwardReferenceEncoder.MaxLength : pixCount;
this.CacheIntervals = new List<CostCacheInterval>(); this.CacheIntervals = new List<CostCacheInterval>();
this.CostCache = new List<double>(); this.CostCache = new List<double>();
this.Costs = new float[pixCount]; this.Costs = memoryAllocator.Allocate<float>(pixCount);
this.DistArray = distArray; this.DistArray = distArray;
this.Count = 0; this.Count = 0;
for (int i = 0; i < FreeIntervalsStartCount; i++)
{
this.freeIntervals.Push(new CostInterval());
}
// Fill in the cost cache. // Fill in the cost cache.
this.CacheIntervalsSize++; this.CacheIntervalsSize++;
this.CostCache.Add(costModel.GetLengthCost(0)); this.CostCache.Add(costModel.GetLengthCost(0));
@ -64,10 +76,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
// Set the initial costs high for every pixel as we will keep the minimum. // Set the initial costs high for every pixel as we will keep the minimum.
for (int i = 0; i < pixCount; i++) this.Costs.GetSpan().Fill(1e38f);
{
this.Costs[i] = 1e38f;
}
} }
/// <summary> /// <summary>
@ -82,9 +91,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
public int CacheIntervalsSize { get; } public int CacheIntervalsSize { get; }
public float[] Costs { get; } public IMemoryOwner<float> Costs { get; }
public ushort[] DistArray { get; } public IMemoryOwner<ushort> DistArray { get; }
public List<CostCacheInterval> CacheIntervals { get; } public List<CostCacheInterval> CacheIntervals { get; }
@ -128,6 +137,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
// interval logic, just serialize it right away. This constant is empirical. // interval logic, just serialize it right away. This constant is empirical.
int skipDistance = 10; int skipDistance = 10;
Span<float> costs = this.Costs.GetSpan();
Span<ushort> distArray = this.DistArray.GetSpan();
if (len < skipDistance) if (len < skipDistance)
{ {
for (int j = position; j < position + len; j++) for (int j = position; j < position + len; j++)
@ -135,10 +146,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
int k = j - position; int k = j - position;
float costTmp = (float)(distanceCost + this.CostCache[k]); float costTmp = (float)(distanceCost + this.CostCache[k]);
if (this.Costs[j] > costTmp) if (costs[j] > costTmp)
{ {
this.Costs[j] = costTmp; costs[j] = costTmp;
this.DistArray[j] = (ushort)(k + 1); distArray[j] = (ushort)(k + 1);
} }
} }
@ -201,10 +212,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.InsertInterval(interval, interval.Cost, interval.Index, end, endOriginal); this.InsertInterval(interval, interval.Cost, interval.Index, end, endOriginal);
break; break;
} }
else
{ interval.End = start;
interval.End = start;
}
} }
} }
@ -226,6 +235,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.ConnectIntervals(interval.Previous, interval.Next); this.ConnectIntervals(interval.Previous, interval.Next);
this.Count--; this.Count--;
interval.Next = null;
interval.Previous = null;
this.freeIntervals.Push(interval);
} }
private void InsertInterval(CostInterval intervalIn, float cost, int position, int start, int end) private void InsertInterval(CostInterval intervalIn, float cost, int position, int start, int end)
@ -236,13 +249,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
// TODO: should we use COST_CACHE_INTERVAL_SIZE_MAX? // TODO: should we use COST_CACHE_INTERVAL_SIZE_MAX?
var intervalNew = new CostInterval() CostInterval intervalNew;
if (this.freeIntervals.Count > 0)
{ {
Cost = cost, intervalNew = this.freeIntervals.Pop();
Start = start, intervalNew.Cost = cost;
End = end, intervalNew.Start = start;
Index = position intervalNew.End = end;
}; intervalNew.Index = position;
}
else
{
intervalNew = new CostInterval() { Cost = cost, Start = start, End = end, Index = position };
}
this.PositionOrphanInterval(intervalNew, intervalIn); this.PositionOrphanInterval(intervalNew, intervalIn);
this.Count++; this.Count++;
@ -297,12 +316,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// </summary> /// </summary>
private void UpdateCost(int i, int position, float cost) private void UpdateCost(int i, int position, float cost)
{ {
Span<float> costs = this.Costs.GetSpan();
Span<ushort> distArray = this.DistArray.GetSpan();
int k = i - position; int k = i - position;
if (this.Costs[i] > cost) if (costs[i] > cost)
{ {
this.Costs[i] = cost; costs[i] = cost;
this.DistArray[i] = (ushort)(k + 1); distArray[i] = (ushort)(k + 1);
} }
} }
/// <inheritdoc />
public void Dispose() => this.Costs.Dispose();
} }
} }

10
src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs

@ -13,16 +13,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// - UsePackedTable: few enough literal symbols, so all the bit codes can fit into a small look-up table PackedTable[] /// - UsePackedTable: few enough literal symbols, so all the bit codes can fit into a small look-up table PackedTable[]
/// The common literal base, if applicable, is stored in 'LiteralArb'. /// The common literal base, if applicable, is stored in 'LiteralArb'.
/// </summary> /// </summary>
internal class HTreeGroup internal struct HTreeGroup
{ {
public HTreeGroup(uint packedTableSize) public HTreeGroup(uint packedTableSize)
{ {
this.HTrees = new List<HuffmanCode[]>(WebpConstants.HuffmanCodesPerMetaCode); this.HTrees = new List<HuffmanCode[]>(WebpConstants.HuffmanCodesPerMetaCode);
this.PackedTable = new HuffmanCode[packedTableSize]; this.PackedTable = new HuffmanCode[packedTableSize];
for (int i = 0; i < packedTableSize; i++) this.IsTrivialCode = false;
{ this.IsTrivialLiteral = false;
this.PackedTable[i] = new HuffmanCode(); this.LiteralArb = 0;
} this.UsePackedTable = false;
} }
/// <summary> /// <summary>

2
src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// A classic way to do entropy coding where a smaller number of bits are used for more frequent codes. /// A classic way to do entropy coding where a smaller number of bits are used for more frequent codes.
/// </summary> /// </summary>
[DebuggerDisplay("BitsUsed: {BitsUsed}, Value: {Value}")] [DebuggerDisplay("BitsUsed: {BitsUsed}, Value: {Value}")]
internal class HuffmanCode internal struct HuffmanCode
{ {
/// <summary> /// <summary>
/// Gets or sets the number of bits used for this symbol. /// Gets or sets the number of bits used for this symbol.

4
src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// Represents the Huffman tree. /// Represents the Huffman tree.
/// </summary> /// </summary>
[DebuggerDisplay("TotalCount = {TotalCount}, Value = {Value}, Left = {PoolIndexLeft}, Right = {PoolIndexRight}")] [DebuggerDisplay("TotalCount = {TotalCount}, Value = {Value}, Left = {PoolIndexLeft}, Right = {PoolIndexRight}")]
internal struct HuffmanTree : IDeepCloneable internal struct HuffmanTree
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HuffmanTree"/> struct. /// Initializes a new instance of the <see cref="HuffmanTree"/> struct.
@ -57,7 +57,5 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
return t1.Value < t2.Value ? -1 : 1; return t1.Value < t2.Value ? -1 : 1;
} }
public IDeepCloneable DeepClone() => new HuffmanTree(this);
} }
} }

51
src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Webp.Lossless namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{ {
@ -218,8 +219,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
while (treeSize > 1) while (treeSize > 1)
{ {
// Finish when we have only one root. // Finish when we have only one root.
treePool[treePoolSize++] = (HuffmanTree)tree[treeSize - 1].DeepClone(); treePool[treePoolSize++] = tree[treeSize - 1];
treePool[treePoolSize++] = (HuffmanTree)tree[treeSize - 2].DeepClone(); treePool[treePoolSize++] = tree[treeSize - 2];
int count = treePool[treePoolSize - 1].TotalCount + treePool[treePoolSize - 2].TotalCount; int count = treePool[treePoolSize - 1].TotalCount + treePool[treePoolSize - 2].TotalCount;
treeSize -= 2; treeSize -= 2;
@ -238,7 +239,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
int startIdx = endIdx + num - 1; int startIdx = endIdx + num - 1;
for (int i = startIdx; i >= endIdx; i--) for (int i = startIdx; i >= endIdx; i--)
{ {
tree[i] = (HuffmanTree)tree[i - 1].DeepClone(); tree[i] = tree[i - 1];
} }
tree[k].TotalCount = count; tree[k].TotalCount = count;
@ -307,9 +308,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
public static int BuildHuffmanTable(Span<HuffmanCode> table, int rootBits, int[] codeLengths, int codeLengthsSize) public static int BuildHuffmanTable(Span<HuffmanCode> table, int rootBits, int[] codeLengths, int codeLengthsSize)
{ {
Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); DebugGuard.MustBeGreaterThan(rootBits, 0, nameof(rootBits));
Guard.NotNull(codeLengths, nameof(codeLengths)); DebugGuard.NotNull(codeLengths, nameof(codeLengths));
Guard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize)); DebugGuard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize));
// sorted[codeLengthsSize] is a pre-allocated array for sorting symbols by code length. // sorted[codeLengthsSize] is a pre-allocated array for sorting symbols by code length.
int[] sorted = new int[codeLengthsSize]; int[] sorted = new int[codeLengthsSize];
@ -467,27 +468,27 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
break; break;
} }
else if (repetitions < 11)
if (repetitions < 11)
{ {
tokens[pos].Code = 17; tokens[pos].Code = 17;
tokens[pos].ExtraBits = (byte)(repetitions - 3); tokens[pos].ExtraBits = (byte)(repetitions - 3);
pos++; pos++;
break; break;
} }
else if (repetitions < 139)
if (repetitions < 139)
{ {
tokens[pos].Code = 18; tokens[pos].Code = 18;
tokens[pos].ExtraBits = (byte)(repetitions - 11); tokens[pos].ExtraBits = (byte)(repetitions - 11);
pos++; pos++;
break; break;
} }
else
{ tokens[pos].Code = 18;
tokens[pos].Code = 18; tokens[pos].ExtraBits = 0x7f; // 138 repeated 0s
tokens[pos].ExtraBits = 0x7f; // 138 repeated 0s pos++;
pos++; repetitions -= 138;
repetitions -= 138;
}
} }
return pos; return pos;
@ -519,20 +520,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
break; break;
} }
else if (repetitions < 7)
if (repetitions < 7)
{ {
tokens[pos].Code = 16; tokens[pos].Code = 16;
tokens[pos].ExtraBits = (byte)(repetitions - 3); tokens[pos].ExtraBits = (byte)(repetitions - 3);
pos++; pos++;
break; break;
} }
else
{ tokens[pos].Code = 16;
tokens[pos].Code = 16; tokens[pos].ExtraBits = 3;
tokens[pos].ExtraBits = 3; pos++;
pos++; repetitions -= 6;
repetitions -= 6;
}
} }
return pos; return pos;
@ -541,7 +541,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// <summary> /// <summary>
/// Get the actual bit values for a tree of bit depths. /// Get the actual bit values for a tree of bit depths.
/// </summary> /// </summary>
/// <param name="tree">The hiffman tree.</param> /// <param name="tree">The huffman tree.</param>
private static void ConvertBitDepthsToSymbols(HuffmanTreeCode tree) private static void ConvertBitDepthsToSymbols(HuffmanTreeCode tree)
{ {
// 0 bit-depth means that the symbol does not exist. // 0 bit-depth means that the symbol does not exist.
@ -628,7 +628,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// </summary> /// </summary>
private static void ReplicateValue(Span<HuffmanCode> table, int step, int end, HuffmanCode code) private static void ReplicateValue(Span<HuffmanCode> table, int step, int end, HuffmanCode code)
{ {
Guard.IsTrue(end % step == 0, nameof(end), "end must be a multiple of step"); DebugGuard.IsTrue(end % step == 0, nameof(end), "end must be a multiple of step");
do do
{ {
@ -656,6 +656,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// <summary> /// <summary>
/// Heuristics for selecting the stride ranges to collapse. /// Heuristics for selecting the stride ranges to collapse.
/// </summary> /// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
private static bool ValuesShouldBeCollapsedToStrideAverage(int a, int b) => Math.Abs(a - b) < 4; private static bool ValuesShouldBeCollapsedToStrideAverage(int a, int b) => Math.Abs(a - b) < 4;
} }
} }

2
src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs

@ -6,7 +6,7 @@ using System.Diagnostics;
namespace SixLabors.ImageSharp.Formats.Webp.Lossless namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{ {
[DebuggerDisplay("Mode: {Mode}, Len: {Len}, BgraOrDistance: {BgraOrDistance}")] [DebuggerDisplay("Mode: {Mode}, Len: {Len}, BgraOrDistance: {BgraOrDistance}")]
internal class PixOrCopy internal sealed class PixOrCopy
{ {
public PixOrCopyMode Mode { get; set; } public PixOrCopyMode Mode { get; set; }

2
src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs

@ -3,7 +3,7 @@
namespace SixLabors.ImageSharp.Formats.Webp.Lossless namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{ {
internal enum PixOrCopyMode internal enum PixOrCopyMode : byte
{ {
Literal, Literal,

2
src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs

@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{ {
internal class Vp8LBackwardRefs internal class Vp8LBackwardRefs
{ {
public Vp8LBackwardRefs() => this.Refs = new List<PixOrCopy>(); public Vp8LBackwardRefs(int pixels) => this.Refs = new List<PixOrCopy>(pixels);
/// <summary> /// <summary>
/// Gets or sets the common block-size. /// Gets or sets the common block-size.

26
src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs

@ -124,19 +124,25 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.EncodedData = memoryAllocator.Allocate<uint>(pixelCount); this.EncodedData = memoryAllocator.Allocate<uint>(pixelCount);
this.Palette = memoryAllocator.Allocate<uint>(WebpConstants.MaxPaletteSize); this.Palette = memoryAllocator.Allocate<uint>(WebpConstants.MaxPaletteSize);
this.Refs = new Vp8LBackwardRefs[3]; this.Refs = new Vp8LBackwardRefs[3];
this.HashChain = new Vp8LHashChain(pixelCount); this.HashChain = new Vp8LHashChain(memoryAllocator, pixelCount);
// We round the block size up, so we're guaranteed to have at most MaxRefsBlockPerImage blocks used: // We round the block size up, so we're guaranteed to have at most MaxRefsBlockPerImage blocks used:
int refsBlockSize = ((pixelCount - 1) / MaxRefsBlockPerImage) + 1; int refsBlockSize = ((pixelCount - 1) / MaxRefsBlockPerImage) + 1;
for (int i = 0; i < this.Refs.Length; i++) for (int i = 0; i < this.Refs.Length; i++)
{ {
this.Refs[i] = new Vp8LBackwardRefs this.Refs[i] = new Vp8LBackwardRefs(pixelCount)
{ {
BlockSize = refsBlockSize < MinBlockSize ? MinBlockSize : refsBlockSize BlockSize = refsBlockSize < MinBlockSize ? MinBlockSize : refsBlockSize
}; };
} }
} }
// RFC 1951 will calm you down if you are worried about this funny sequence.
// This sequence is tuned from that, but more weighted for lower symbol count,
// and more spiking histograms.
// This uses C#'s compiler optimization to refer to assembly's static data directly.
private static ReadOnlySpan<byte> StorageOrder => new byte[] { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
// This uses C#'s compiler optimization to refer to assembly's static data directly. // This uses C#'s compiler optimization to refer to assembly's static data directly.
private static ReadOnlySpan<byte> Order => new byte[] { 1, 2, 0, 3 }; private static ReadOnlySpan<byte> Order => new byte[] { 1, 2, 0, 3 };
@ -515,7 +521,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
// Calculate backward references from BGRA image. // Calculate backward references from BGRA image.
this.HashChain.Fill(this.memoryAllocator, bgra, this.quality, width, height, lowEffort); this.HashChain.Fill(bgra, this.quality, width, height, lowEffort);
Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter; Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter;
Vp8LBitWriter bwInit = this.bitWriter; Vp8LBitWriter bwInit = this.bitWriter;
@ -529,6 +535,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.quality, this.quality,
subConfig.Lz77, subConfig.Lz77,
ref cacheBits, ref cacheBits,
this.memoryAllocator,
this.HashChain, this.HashChain,
this.Refs[0], this.Refs[0],
this.Refs[1]); this.Refs[1]);
@ -735,7 +742,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
// Calculate backward references from the image pixels. // Calculate backward references from the image pixels.
hashChain.Fill(this.memoryAllocator, bgra, quality, width, height, lowEffort); hashChain.Fill(bgra, quality, width, height, lowEffort);
Vp8LBackwardRefs refs = BackwardReferenceEncoder.GetBackwardReferences( Vp8LBackwardRefs refs = BackwardReferenceEncoder.GetBackwardReferences(
width, width,
@ -744,6 +751,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
quality, quality,
(int)Vp8LLz77Type.Lz77Standard | (int)Vp8LLz77Type.Lz77Rle, (int)Vp8LLz77Type.Lz77Standard | (int)Vp8LLz77Type.Lz77Rle,
ref cacheBits, ref cacheBits,
this.memoryAllocator,
hashChain, hashChain,
refsTmp1, refsTmp1,
refsTmp2); refsTmp2);
@ -940,16 +948,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
private void StoreHuffmanTreeOfHuffmanTreeToBitMask(byte[] codeLengthBitDepth) private void StoreHuffmanTreeOfHuffmanTreeToBitMask(byte[] codeLengthBitDepth)
{ {
// RFC 1951 will calm you down if you are worried about this funny sequence.
// This sequence is tuned from that, but more weighted for lower symbol count,
// and more spiking histograms.
byte[] storageOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
// Throw away trailing zeros: // Throw away trailing zeros:
int codesToStore = WebpConstants.CodeLengthCodes; int codesToStore = WebpConstants.CodeLengthCodes;
for (; codesToStore > 4; codesToStore--) for (; codesToStore > 4; codesToStore--)
{ {
if (codeLengthBitDepth[storageOrder[codesToStore - 1]] != 0) if (codeLengthBitDepth[StorageOrder[codesToStore - 1]] != 0)
{ {
break; break;
} }
@ -958,7 +961,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.bitWriter.PutBits((uint)codesToStore - 4, 4); this.bitWriter.PutBits((uint)codesToStore - 4, 4);
for (int i = 0; i < codesToStore; i++) for (int i = 0; i < codesToStore; i++)
{ {
this.bitWriter.PutBits(codeLengthBitDepth[storageOrder[i]], 3); this.bitWriter.PutBits(codeLengthBitDepth[StorageOrder[i]], 3);
} }
} }
@ -1802,6 +1805,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.BgraScratch.Dispose(); this.BgraScratch.Dispose();
this.Palette.Dispose(); this.Palette.Dispose();
this.TransformData.Dispose(); this.TransformData.Dispose();
this.HashChain.Dispose();
} }
} }
} }

40
src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs

@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Webp.Lossless namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{ {
internal class Vp8LHashChain internal sealed class Vp8LHashChain : IDisposable
{ {
private const uint HashMultiplierHi = 0xc6a4a793u; private const uint HashMultiplierHi = 0xc6a4a793u;
@ -28,14 +28,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// </summary> /// </summary>
private const int WindowSize = (1 << WindowSizeBits) - 120; private const int WindowSize = (1 << WindowSizeBits) - 120;
private readonly MemoryAllocator memoryAllocator;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Vp8LHashChain"/> class. /// Initializes a new instance of the <see cref="Vp8LHashChain"/> class.
/// </summary> /// </summary>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="size">The size off the chain.</param> /// <param name="size">The size off the chain.</param>
public Vp8LHashChain(int size) public Vp8LHashChain(MemoryAllocator memoryAllocator, int size)
{ {
this.OffsetLength = new uint[size]; this.memoryAllocator = memoryAllocator;
this.OffsetLength.AsSpan().Fill(0xcdcdcdcd); this.OffsetLength = this.memoryAllocator.Allocate<uint>(size, AllocationOptions.Clean);
this.Size = size; this.Size = size;
} }
@ -45,16 +48,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// These 20 bits are the limit defined by GetWindowSizeForHashChain (through WindowSize = 1 &lt;&lt; 20). /// These 20 bits are the limit defined by GetWindowSizeForHashChain (through WindowSize = 1 &lt;&lt; 20).
/// The lower 12 bits contain the length of the match. /// The lower 12 bits contain the length of the match.
/// </summary> /// </summary>
public uint[] OffsetLength { get; } public IMemoryOwner<uint> OffsetLength { get; }
/// <summary> /// <summary>
/// Gets the size of the hash chain. /// Gets the size of the hash chain.
/// This is the maximum size of the hash_chain that can be constructed. /// This is the maximum size of the hashchain that can be constructed.
/// Typically this is the pixel count (width x height) for a given image. /// Typically this is the pixel count (width x height) for a given image.
/// </summary> /// </summary>
public int Size { get; } public int Size { get; }
public void Fill(MemoryAllocator memoryAllocator, ReadOnlySpan<uint> bgra, int quality, int xSize, int ySize, bool lowEffort) public void Fill(ReadOnlySpan<uint> bgra, int quality, int xSize, int ySize, bool lowEffort)
{ {
int size = xSize * ySize; int size = xSize * ySize;
int iterMax = GetMaxItersForQuality(quality); int iterMax = GetMaxItersForQuality(quality);
@ -63,20 +66,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
if (size <= 2) if (size <= 2)
{ {
this.OffsetLength[0] = 0; this.OffsetLength.GetSpan()[0] = 0;
return; return;
} }
using IMemoryOwner<int> hashToFirstIndexBuffer = memoryAllocator.Allocate<int>(HashSize); 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> hashToFirstIndex = hashToFirstIndexBuffer.GetSpan();
Span<int> chain = chainBuffer.GetSpan();
// Initialize hashToFirstIndex array to -1. // Initialize hashToFirstIndex array to -1.
hashToFirstIndex.Fill(-1); hashToFirstIndex.Fill(-1);
int[] chain = new int[size];
// Fill the chain linking pixels with the same hash. // Fill the chain linking pixels with the same hash.
bool bgraComp = bgra.Length > 1 && bgra[0] == bgra[1]; bool bgraComp = bgra.Length > 1 && bgra[0] == bgra[1];
Span<uint> tmp = stackalloc uint[2];
for (pos = 0; pos < size - 2;) for (pos = 0; pos < size - 2;)
{ {
uint hashCode; uint hashCode;
@ -85,7 +89,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{ {
// Consecutive pixels with the same color will share the same hash. // Consecutive pixels with the same color will share the same hash.
// We therefore use a different hash: the color and its repetition length. // We therefore use a different hash: the color and its repetition length.
uint[] tmp = new uint[2]; tmp.Clear();
uint len = 1; uint len = 1;
tmp[0] = bgra[pos]; tmp[0] = bgra[pos];
@ -134,7 +138,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
// Find the best match interval at each pixel, defined by an offset to the // 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 // 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).
this.OffsetLength[0] = this.OffsetLength[size - 1] = 0; Span<uint> offsetLength = this.OffsetLength.GetSpan();
offsetLength[0] = offsetLength[size - 1] = 0;
for (int basePosition = size - 2; basePosition > 0;) for (int basePosition = size - 2; basePosition > 0;)
{ {
int maxLen = LosslessUtils.MaxFindCopyLength(size - 1 - basePosition); int maxLen = LosslessUtils.MaxFindCopyLength(size - 1 - basePosition);
@ -208,7 +213,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
uint maxBasePosition = (uint)basePosition; uint maxBasePosition = (uint)basePosition;
while (true) while (true)
{ {
this.OffsetLength[basePosition] = (bestDistance << BackwardReferenceEncoder.MaxLengthBits) | (uint)bestLength; offsetLength[basePosition] = (bestDistance << BackwardReferenceEncoder.MaxLengthBits) | (uint)bestLength;
--basePosition; --basePosition;
// Stop if we don't have a match or if we are out of bounds. // Stop if we don't have a match or if we are out of bounds.
@ -242,10 +247,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public int FindLength(int basePosition) => (int)(this.OffsetLength[basePosition] & ((1U << BackwardReferenceEncoder.MaxLengthBits) - 1)); public int FindLength(int basePosition) => (int)(this.OffsetLength.GetSpan()[basePosition] & ((1U << BackwardReferenceEncoder.MaxLengthBits) - 1));
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public int FindOffset(int basePosition) => (int)(this.OffsetLength[basePosition] >> BackwardReferenceEncoder.MaxLengthBits); public int FindOffset(int basePosition) => (int)(this.OffsetLength.GetSpan()[basePosition] >> BackwardReferenceEncoder.MaxLengthBits);
/// <summary> /// <summary>
/// Calculates the hash for a pixel pair. /// Calculates the hash for a pixel pair.
@ -280,5 +285,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
return maxWindowSize > WindowSize ? WindowSize : maxWindowSize; return maxWindowSize > WindowSize ? WindowSize : maxWindowSize;
} }
/// <inheritdoc />
public void Dispose() => this.OffsetLength.Dispose();
} }
} }

27
src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs

@ -65,15 +65,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
FixedTableSize + 2704 FixedTableSize + 2704
}; };
private static readonly byte[] CodeLengthCodeOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
private static readonly int NumCodeLengthCodes = CodeLengthCodeOrder.Length; private static readonly int NumCodeLengthCodes = CodeLengthCodeOrder.Length;
private static readonly byte[] LiteralMap =
{
0, 1, 1, 1, 0
};
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WebpLosslessDecoder"/> class. /// Initializes a new instance of the <see cref="WebpLosslessDecoder"/> class.
/// </summary> /// </summary>
@ -87,6 +80,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.configuration = configuration; this.configuration = configuration;
} }
// This uses C#'s compiler optimization to refer to assembly's static data directly.
private static ReadOnlySpan<byte> CodeLengthCodeOrder => new byte[] { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
// This uses C#'s compiler optimization to refer to assembly's static data directly.
private static ReadOnlySpan<byte> LiteralMap => new byte[] { 0, 1, 1, 1, 0 };
/// <summary> /// <summary>
/// Decodes the image from the stream using the bitreader. /// Decodes the image from the stream using the bitreader.
/// </summary> /// </summary>
@ -834,10 +833,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
private void BuildPackedTable(HTreeGroup hTreeGroup) private void BuildPackedTable(HTreeGroup hTreeGroup)
{ {
for (uint code = 0; code < HuffmanUtils.HuffmanPackedTableSize; ++code) for (uint code = 0; code < HuffmanUtils.HuffmanPackedTableSize; code++)
{ {
uint bits = code; uint bits = code;
HuffmanCode huff = hTreeGroup.PackedTable[bits]; ref HuffmanCode huff = ref hTreeGroup.PackedTable[bits];
HuffmanCode hCode = hTreeGroup.HTrees[HuffIndex.Green][bits]; HuffmanCode hCode = hTreeGroup.HTrees[HuffIndex.Green][bits];
if (hCode.Value >= WebpConstants.NumLiteralCodes) if (hCode.Value >= WebpConstants.NumLiteralCodes)
{ {
@ -848,10 +847,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{ {
huff.BitsUsed = 0; huff.BitsUsed = 0;
huff.Value = 0; huff.Value = 0;
bits >>= AccumulateHCode(hCode, 8, huff); bits >>= AccumulateHCode(hCode, 8, ref huff);
bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Red][bits], 16, huff); bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Red][bits], 16, ref huff);
bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Blue][bits], 0, huff); bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Blue][bits], 0, ref huff);
bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Alpha][bits], 24, huff); bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Alpha][bits], 24, ref huff);
} }
} }
} }
@ -992,7 +991,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static int AccumulateHCode(HuffmanCode hCode, int shift, HuffmanCode huff) private static int AccumulateHCode(HuffmanCode hCode, int shift, ref HuffmanCode huff)
{ {
huff.BitsUsed += hCode.BitsUsed; huff.BitsUsed += hCode.BitsUsed;
huff.Value |= hCode.Value << shift; huff.Value |= hCode.Value << shift;

3
src/ImageSharp/Formats/Webp/WebpLookupTables.cs

@ -239,7 +239,8 @@ namespace SixLabors.ImageSharp.Formats.Webp
} }
}; };
public static readonly byte[] Norm = // This uses C#'s compiler optimization to refer to assembly's static data directly.
public static ReadOnlySpan<byte> Norm => new byte[]
{ {
// renorm_sizes[i] = 8 - log2(i) // renorm_sizes[i] = 8 - log2(i)
7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4,

2
tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs

@ -5,7 +5,7 @@ using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit; using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.WebP namespace SixLabors.ImageSharp.Tests.Formats.Webp
{ {
[Trait("Format", "Webp")] [Trait("Format", "Webp")]
public class ColorSpaceTransformUtilsTests public class ColorSpaceTransformUtilsTests

2
tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs

@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit; using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.WebP namespace SixLabors.ImageSharp.Tests.Formats.Webp
{ {
[Trait("Format", "Webp")] [Trait("Format", "Webp")]
public class LossyUtilsTests public class LossyUtilsTests

2
tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs

@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit; using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.WebP namespace SixLabors.ImageSharp.Tests.Formats.Webp
{ {
[Trait("Format", "Webp")] [Trait("Format", "Webp")]
public class QuantEncTests public class QuantEncTests

2
tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs

@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit; using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.WebP namespace SixLabors.ImageSharp.Tests.Formats.Webp
{ {
[Trait("Format", "Webp")] [Trait("Format", "Webp")]
public class Vp8EncodingTests public class Vp8EncodingTests

Loading…
Cancel
Save