Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
25 changes: 10 additions & 15 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 @@ -780,19 +779,16 @@ private static void BackwardRefsWithLocalCache(ReadOnlySpan<uint> bgra, int cach
{
int pixelIndex = 0;
ColorCache colorCache = new ColorCache(cacheBits);
for (int idx = 0; idx < refs.Refs.Count; idx++)
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(in v, true, xSize);
}

ConvertPopulationCountTableToBitEstimates(histogram.NumCodes(), histogram.Literal, this.Literal);
Expand Down
19 changes: 8 additions & 11 deletions src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,11 @@ 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);
histograms[ix].AddSinglePixOrCopy(in v, false);
x += v.Len;
while (x >= xSize)
{
Expand Down Expand Up @@ -465,7 +464,7 @@ private static bool HistogramCombineStochastic(Vp8LHistogramSet histograms, int
}
}

HistoListUpdateHead(histoPriorityList, p);
HistoListUpdateHead(histoPriorityList, p, j);
j++;
}

Expand Down Expand Up @@ -525,7 +524,7 @@ private static void HistogramCombineGreedy(Vp8LHistogramSet histograms)
}
else
{
HistoListUpdateHead(histoPriorityList, p);
HistoListUpdateHead(histoPriorityList, p, i);
i++;
}
}
Expand Down Expand Up @@ -647,7 +646,7 @@ private static double HistoPriorityListPush(

histoList.Add(pair);

HistoListUpdateHead(histoList, pair);
HistoListUpdateHead(histoList, pair, histoList.Count - 1);

return pair.CostDiff;
}
Expand All @@ -674,13 +673,11 @@ private static void HistoListUpdatePair(
/// <summary>
/// Check whether a pair in the list should be updated as head or not.
/// </summary>
private static void HistoListUpdateHead(List<HistogramPair> histoList, HistogramPair pair)
private static void HistoListUpdateHead(List<HistogramPair> histoList, HistogramPair pair, int idx)
{
if (pair.CostDiff < histoList[0].CostDiff)
{
// Replace the best pair.
int oldIdx = histoList.IndexOf(pair);
histoList[oldIdx] = histoList[0];
histoList[idx] = histoList[0];
histoList[0] = pair;
}
}
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;
Copy link

Copilot AI Jun 13, 2025

Choose a reason for hiding this comment

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

Consider adding a boundary check to ensure 'this.count' does not exceed the allocated memory length to prevent potential overflows.

Suggested change
public void Add(PixOrCopy pixOrCopy) => this.refs.Memory.Span[this.count++] = pixOrCopy;
public void Add(PixOrCopy pixOrCopy)
{
if (this.count >= this.refs.Memory.Length)
{
throw new InvalidOperationException("Cannot add more elements. Memory limit exceeded.");
}
this.refs.Memory.Span[this.count++] = pixOrCopy;
}

Copilot uses AI. Check for mistakes.


/// <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();
}
20 changes: 10 additions & 10 deletions src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,9 @@ 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);
}
}

Expand Down Expand Up @@ -1071,9 +1066,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 @@ -1907,9 +1901,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 @@ -1921,6 +1915,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
6 changes: 3 additions & 3 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(in v, false);
}
}

Expand All @@ -150,7 +150,7 @@ public void StoreRefs(Vp8LBackwardRefs refs)
/// <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)
public void AddSinglePixOrCopy(in PixOrCopy v, bool useDistanceModifier, int xSize = 0)
{
if (v.IsLiteral())
{
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