Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 11 additions & 16 deletions src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,8 @@ private static int CalculateBestCacheSize(
}

// Find the cacheBits giving the lowest entropy.
for (int idx = 0; idx < refs.Refs.Count; idx++)
foreach (PixOrCopy v in refs)
{
PixOrCopy v = refs.Refs[idx];
if (v.IsLiteral())
{
uint pix = bgra[pos++];
Expand Down Expand Up @@ -387,7 +386,7 @@ private static void BackwardReferencesHashChainFollowChosenPath(ReadOnlySpan<uin
colorCache = new ColorCache(cacheBits);
}

backwardRefs.Refs.Clear();
backwardRefs.Clear();
for (int ix = 0; ix < chosenPathSize; ix++)
{
int len = chosenPath[ix];
Expand Down Expand Up @@ -479,7 +478,7 @@ private static void BackwardReferencesLz77(int xSize, int ySize, ReadOnlySpan<ui
colorCache = new ColorCache(cacheBits);
}

refs.Refs.Clear();
refs.Clear();
for (int i = 0; i < pixCount;)
{
// Alternative #1: Code the pixels starting at 'i' using backward reference.
Expand Down Expand Up @@ -734,7 +733,7 @@ private static void BackwardReferencesRle(int xSize, int ySize, ReadOnlySpan<uin
colorCache = new ColorCache(cacheBits);
}

refs.Refs.Clear();
refs.Clear();

// Add first pixel as literal.
AddSingleLiteral(bgra[0], useColorCache, colorCache, refs);
Expand Down Expand Up @@ -779,20 +778,17 @@ private static void BackwardReferencesRle(int xSize, int ySize, ReadOnlySpan<uin
private static void BackwardRefsWithLocalCache(ReadOnlySpan<uint> bgra, int cacheBits, Vp8LBackwardRefs refs)
{
int pixelIndex = 0;
ColorCache colorCache = new ColorCache(cacheBits);
for (int idx = 0; idx < refs.Refs.Count; idx++)
ColorCache colorCache = new(cacheBits);
foreach (ref PixOrCopy v in refs)
{
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;
v = PixOrCopy.CreateCacheIdx(ix);
}
else
{
Expand All @@ -814,14 +810,13 @@ private static void BackwardRefsWithLocalCache(ReadOnlySpan<uint> bgra, int cach

private static void BackwardReferences2DLocality(int xSize, Vp8LBackwardRefs refs)
{
using List<PixOrCopy>.Enumerator c = refs.Refs.GetEnumerator();
while (c.MoveNext())
foreach (ref PixOrCopy v in refs)
{
if (c.Current.IsCopy())
if (v.IsCopy())
{
int dist = (int)c.Current.BgraOrDistance;
int dist = (int)v.BgraOrDistance;
int transformedDist = DistanceToPlaneCode(xSize, dist);
c.Current.BgraOrDistance = (uint)transformedDist;
v = PixOrCopy.CreateCopy((uint)transformedDist, v.Len);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/ImageSharp/Formats/Webp/Lossless/CostModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ public void Build(int xSize, int cacheBits, Vp8LBackwardRefs backwardRefs)
using OwnedVp8LHistogram histogram = OwnedVp8LHistogram.Create(this.memoryAllocator, cacheBits);

// The following code is similar to HistogramCreate but converts the distance to plane code.
for (int i = 0; i < backwardRefs.Refs.Count; i++)
foreach (PixOrCopy v in backwardRefs)
{
histogram.AddSinglePixOrCopy(backwardRefs.Refs[i], true, xSize);
histogram.AddSinglePixOrCopy(v, true, xSize);
}

ConvertPopulationCountTableToBitEstimates(histogram.NumCodes(), histogram.Literal, this.Literal);
Expand Down
11 changes: 5 additions & 6 deletions src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,9 @@ private static void HistogramBuild(
{
int x = 0, y = 0;
int histoXSize = LosslessUtils.SubSampleSize(xSize, histoBits);
using List<PixOrCopy>.Enumerator backwardRefsEnumerator = backwardRefs.Refs.GetEnumerator();
while (backwardRefsEnumerator.MoveNext())

foreach (PixOrCopy v in backwardRefs)
{
PixOrCopy v = backwardRefsEnumerator.Current;
int ix = ((y >> histoBits) * histoXSize) + (x >> histoBits);
histograms[ix].AddSinglePixOrCopy(v, false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method could be updated to take the struct via in to avoid the copy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made the change. But if there is any performance difference, it's hard to notice. The structure is not that big.

x += v.Len;
Expand Down Expand Up @@ -217,7 +216,7 @@ private static void HistogramCombineEntropyBin(
clusterMappings[idx] = (ushort)idx;
}

List<int> indicesToRemove = new();
List<int> indicesToRemove = [];
Vp8LStreaks stats = new();
Vp8LBitEntropy bitsEntropy = new();
for (int idx = 0; idx < histograms.Count; idx++)
Expand Down Expand Up @@ -345,7 +344,7 @@ private static bool HistogramCombineStochastic(Vp8LHistogramSet histograms, int

// Priority list of histogram pairs. Its size impacts the quality of the compression and the speed:
// the smaller the faster but the worse for the compression.
List<HistogramPair> histoPriorityList = new();
List<HistogramPair> histoPriorityList = [];
const int maxSize = 9;

// Fill the initial mapping.
Expand Down Expand Up @@ -480,7 +479,7 @@ private static void HistogramCombineGreedy(Vp8LHistogramSet histograms)
int histoSize = histograms.Count(h => h != null);

// Priority list of histogram pairs.
List<HistogramPair> histoPriorityList = new();
List<HistogramPair> histoPriorityList = [];
int maxSize = histoSize * histoSize;
Vp8LStreaks stats = new();
Vp8LBitEntropy bitsEntropy = new();
Expand Down
45 changes: 16 additions & 29 deletions src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,24 @@
namespace SixLabors.ImageSharp.Formats.Webp.Lossless;

[DebuggerDisplay("Mode: {Mode}, Len: {Len}, BgraOrDistance: {BgraOrDistance}")]
internal sealed class PixOrCopy
internal readonly struct PixOrCopy
{
public PixOrCopyMode Mode { get; set; }

public ushort Len { get; set; }

public uint BgraOrDistance { get; set; }

public static PixOrCopy CreateCacheIdx(int idx) =>
new PixOrCopy
{
Mode = PixOrCopyMode.CacheIdx,
BgraOrDistance = (uint)idx,
Len = 1
};

public static PixOrCopy CreateLiteral(uint bgra) =>
new PixOrCopy
{
Mode = PixOrCopyMode.Literal,
BgraOrDistance = bgra,
Len = 1
};

public static PixOrCopy CreateCopy(uint distance, ushort len) =>
new PixOrCopy
public readonly PixOrCopyMode Mode;
public readonly ushort Len;
public readonly uint BgraOrDistance;

private PixOrCopy(PixOrCopyMode mode, ushort len, uint bgraOrDistance)
{
Mode = PixOrCopyMode.Copy,
BgraOrDistance = distance,
Len = len
};
this.Mode = mode;
this.Len = len;
this.BgraOrDistance = bgraOrDistance;
}

public static PixOrCopy CreateCacheIdx(int idx) => new(PixOrCopyMode.CacheIdx, 1, (uint)idx);

public static PixOrCopy CreateLiteral(uint bgra) => new(PixOrCopyMode.Literal, 1, bgra);

public static PixOrCopy CreateCopy(uint distance, ushort len) => new(PixOrCopyMode.Copy, len, distance);

public int Literal(int component) => (int)(this.BgraOrDistance >> (component * 8)) & 0xFF;

Expand Down
29 changes: 18 additions & 11 deletions src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

using System.Buffers;
using SixLabors.ImageSharp.Memory;

namespace SixLabors.ImageSharp.Formats.Webp.Lossless;

internal class Vp8LBackwardRefs
internal class Vp8LBackwardRefs : IDisposable
{
public Vp8LBackwardRefs(int pixels) => this.Refs = new List<PixOrCopy>(pixels);
private readonly IMemoryOwner<PixOrCopy> refs;
private int count;

public Vp8LBackwardRefs(MemoryAllocator memoryAllocator, int pixels)
{
this.refs = memoryAllocator.Allocate<PixOrCopy>(pixels);
this.count = 0;
}

public void Add(PixOrCopy pixOrCopy) => this.refs.Memory.Span[this.count++] = pixOrCopy;

/// <summary>
/// Gets or sets the common block-size.
/// </summary>
public int BlockSize { get; set; }
public void Clear() => this.count = 0;

/// <summary>
/// Gets the backward references.
/// </summary>
public List<PixOrCopy> Refs { get; }
public Span<PixOrCopy>.Enumerator GetEnumerator() => this.refs.Slice(0, this.count).GetEnumerator();

public void Add(PixOrCopy pixOrCopy) => this.Refs.Add(pixOrCopy);
/// <inheritdoc/>
public void Dispose() => this.refs.Dispose();
}
53 changes: 24 additions & 29 deletions src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ internal class Vp8LEncoder : IDisposable
/// </summary>
private ScratchBuffer scratch; // mutable struct, don't make readonly

private readonly int[][] histoArgb = { new int[256], new int[256], new int[256], new int[256] };
private readonly int[][] histoArgb = [new int[256], new int[256], new int[256], new int[256]];

private readonly int[][] bestHisto = { new int[256], new int[256], new int[256], new int[256] };
private readonly int[][] bestHisto = [new int[256], new int[256], new int[256], new int[256]];

/// <summary>
/// The <see cref="MemoryAllocator"/> to use for buffer allocations.
Expand All @@ -45,11 +45,6 @@ internal class Vp8LEncoder : IDisposable
/// </summary>
private const int MaxRefsBlockPerImage = 16;

/// <summary>
/// Minimum block size for backward references.
/// </summary>
private const int MinBlockSize = 256;

/// <summary>
/// A bit writer for writing lossless webp streams.
/// </summary>
Expand Down Expand Up @@ -136,25 +131,20 @@ public Vp8LEncoder(
this.Refs = new Vp8LBackwardRefs[3];
this.HashChain = new Vp8LHashChain(memoryAllocator, pixelCount);

// We round the block size up, so we're guaranteed to have at most MaxRefsBlockPerImage blocks used:
int refsBlockSize = ((pixelCount - 1) / MaxRefsBlockPerImage) + 1;
for (int i = 0; i < this.Refs.Length; i++)
{
this.Refs[i] = new Vp8LBackwardRefs(pixelCount)
{
BlockSize = refsBlockSize < MinBlockSize ? MinBlockSize : refsBlockSize
};
this.Refs[i] = new Vp8LBackwardRefs(memoryAllocator, pixelCount);
}
}

// 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 };
private static ReadOnlySpan<byte> StorageOrder => [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> Order => new byte[] { 1, 2, 0, 3 };
private static ReadOnlySpan<byte> Order => [1, 2, 0, 3];

/// <summary>
/// Gets the memory for the image data as packed bgra values.
Expand Down Expand Up @@ -547,7 +537,7 @@ private CrunchConfig[] EncoderAnalyze(ReadOnlySpan<uint> bgra, int width, int he
EntropyIx entropyIdx = this.AnalyzeEntropy(bgra, width, height, usePalette, this.PaletteSize, this.TransformBits, out redAndBlueAlwaysZero);

bool doNotCache = false;
List<CrunchConfig> crunchConfigs = new();
List<CrunchConfig> crunchConfigs = [];

if (this.method == WebpEncodingMethod.BestQuality && this.quality == 100)
{
Expand Down Expand Up @@ -593,7 +583,7 @@ private CrunchConfig[] EncoderAnalyze(ReadOnlySpan<uint> bgra, int width, int he
}
}

return crunchConfigs.ToArray();
return [.. crunchConfigs];
}

private void EncodeImage(int width, int height, bool useCache, CrunchConfig config, int cacheBits, bool lowEffort)
Expand Down Expand Up @@ -1068,9 +1058,8 @@ private void StoreImageToBitMask(
int histogramIx = histogramSymbols[0];
Span<HuffmanTreeCode> codes = huffmanCodes.AsSpan(5 * histogramIx);

for (int i = 0; i < backwardRefs.Refs.Count; i++)
foreach (PixOrCopy v in backwardRefs)
{
PixOrCopy v = backwardRefs.Refs[i];
if (tileX != (x & tileMask) || tileY != (y & tileMask))
{
tileX = x & tileMask;
Expand Down Expand Up @@ -1265,13 +1254,13 @@ private EntropyIx AnalyzeEntropy(ReadOnlySpan<uint> bgra, int width, int height,
// non-zero red and blue values. If all are zero, we can later skip
// the cross color optimization.
byte[][] histoPairs =
{
new[] { (byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue },
new[] { (byte)HistoIx.HistoRedPred, (byte)HistoIx.HistoBluePred },
new[] { (byte)HistoIx.HistoRedSubGreen, (byte)HistoIx.HistoBlueSubGreen },
new[] { (byte)HistoIx.HistoRedPredSubGreen, (byte)HistoIx.HistoBluePredSubGreen },
new[] { (byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue }
};
[
[(byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue],
[(byte)HistoIx.HistoRedPred, (byte)HistoIx.HistoBluePred],
[(byte)HistoIx.HistoRedSubGreen, (byte)HistoIx.HistoBlueSubGreen],
[(byte)HistoIx.HistoRedPredSubGreen, (byte)HistoIx.HistoBluePredSubGreen],
[(byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue]
];
Span<uint> redHisto = histo[(256 * histoPairs[(int)minEntropyIx][0])..];
Span<uint> blueHisto = histo[(256 * histoPairs[(int)minEntropyIx][1])..];
for (int i = 1; i < 256; i++)
Expand Down Expand Up @@ -1325,7 +1314,7 @@ private bool AnalyzeAndCreatePalette(ReadOnlySpan<uint> bgra, int width, int hei
/// <returns>The number of palette entries.</returns>
private static int GetColorPalette(ReadOnlySpan<uint> bgra, int width, int height, Span<uint> palette)
{
HashSet<uint> colors = new();
HashSet<uint> colors = [];
for (int y = 0; y < height; y++)
{
ReadOnlySpan<uint> bgraRow = bgra.Slice(y * width, width);
Expand Down Expand Up @@ -1904,9 +1893,9 @@ public void AllocateTransformBuffer(int width, int height)
/// </summary>
public void ClearRefs()
{
foreach (Vp8LBackwardRefs t in this.Refs)
foreach (Vp8LBackwardRefs refs in this.Refs)
{
t.Refs.Clear();
refs.Clear();
}
}

Expand All @@ -1918,6 +1907,12 @@ public void Dispose()
this.BgraScratch?.Dispose();
this.Palette.Dispose();
this.TransformData?.Dispose();

foreach (Vp8LBackwardRefs refs in this.Refs)
{
refs.Dispose();
}

this.HashChain.Dispose();
}

Expand Down
4 changes: 2 additions & 2 deletions src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,9 @@ public void Clear()
/// <param name="refs">The backward references.</param>
public void StoreRefs(Vp8LBackwardRefs refs)
{
for (int i = 0; i < refs.Refs.Count; i++)
foreach (PixOrCopy v in refs)
{
this.AddSinglePixOrCopy(refs.Refs[i], false);
this.AddSinglePixOrCopy(v, false);
}
}

Expand Down
12 changes: 4 additions & 8 deletions tests/ImageSharp.Tests/Formats/WebP/Vp8LHistogramTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,14 @@ private static void RunAddVectorTest()
// All remaining values are expected to be zero.
literals.AsSpan().CopyTo(expectedLiterals);

Vp8LBackwardRefs backwardRefs = new(pixelData.Length);
MemoryAllocator memoryAllocator = Configuration.Default.MemoryAllocator;

using Vp8LBackwardRefs backwardRefs = new(memoryAllocator, pixelData.Length);
for (int i = 0; i < pixelData.Length; i++)
{
backwardRefs.Add(new PixOrCopy()
{
BgraOrDistance = pixelData[i],
Len = 1,
Mode = PixOrCopyMode.Literal
});
backwardRefs.Add(PixOrCopy.CreateLiteral(pixelData[i]));
}

MemoryAllocator memoryAllocator = Configuration.Default.MemoryAllocator;
using OwnedVp8LHistogram histogram0 = OwnedVp8LHistogram.Create(memoryAllocator, backwardRefs, 3);
using OwnedVp8LHistogram histogram1 = OwnedVp8LHistogram.Create(memoryAllocator, backwardRefs, 3);
for (int i = 0; i < 5; i++)
Expand Down
Loading