diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs b/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs
index 47f85b7388..457e24cbe6 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs
@@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
///
/// The GetLengthCost(costModel, k) are cached in a CostCacheInterval.
///
- [DebuggerDisplay("Start: {Start}, End: {End}, Cost: {Cost}")]
+ [DebuggerDisplay("Start: {Start}, End: {End}, Cost: {Cost}, Position: {Position}")]
internal class CostCacheInterval
{
public double Cost { get; set; }
diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs b/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs
index 16f2edc159..75afac6f2b 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs
@@ -5,6 +5,22 @@ using System.Diagnostics;
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
+ ///
+ /// To perform backward reference every pixel at index index_ is considered and
+ /// the cost for the MAX_LENGTH following pixels computed. Those following pixels
+ /// at index index_ + k (k from 0 to MAX_LENGTH) have a cost of:
+ /// cost = distance cost at index + GetLengthCost(costModel, k)
+ /// and the minimum value is kept. GetLengthCost(costModel, k) is cached in an
+ /// array of size MAX_LENGTH.
+ /// Instead of performing MAX_LENGTH comparisons per pixel, we keep track of the
+ /// minimal values using intervals of constant cost.
+ /// An interval is defined by the index_ of the pixel that generated it and
+ /// is only useful in a range of indices from start to end (exclusive), i.e.
+ /// it contains the minimum value for pixels between start and end.
+ /// Intervals are stored in a linked list and ordered by start. When a new
+ /// interval has a better value, old intervals are split or removed. There are
+ /// therefore no overlapping intervals.
+ ///
[DebuggerDisplay("Start: {Start}, End: {End}, Cost: {Cost}")]
internal class CostInterval
{
@@ -15,5 +31,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
public int End { get; set; }
public int Index { get; set; }
+
+ public CostInterval Previous { get; set; }
+
+ public CostInterval Next { get; set; }
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs
index f648ae75c3..279aa6f51d 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs
@@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
///
/// The CostManager is in charge of managing intervals and costs.
/// It caches the different CostCacheInterval, caches the different
- /// GetLengthCost(cost_model, k) in cost_cache_ and the CostInterval's.
+ /// GetLengthCost(costModel, k) in cost_cache_ and the CostInterval's.
///
internal class CostManager
{
@@ -70,10 +70,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
}
}
+ private CostInterval head;
+
///
- /// Gets the number of stored intervals.
+ /// Gets or sets the number of stored intervals.
///
- public int Count { get; }
+ public int Count { get; set; }
///
/// Gets the costs cache. Contains the GetLengthCost(costModel, k).
@@ -97,34 +99,32 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
/// If 'doCleanIntervals' is true, intervals that end before 'i' will be popped.
public void UpdateCostAtIndex(int i, bool doCleanIntervals)
{
- var indicesToRemove = new List();
+ CostInterval current = this.head;
using List.Enumerator intervalEnumerator = this.Intervals.GetEnumerator();
- while (intervalEnumerator.MoveNext() && intervalEnumerator.Current.Start <= i)
+ while (current != null && current.Start <= i)
{
- if (intervalEnumerator.Current.End <= i)
+ CostInterval next = current.Next;
+ if (current.End <= i)
{
if (doCleanIntervals)
{
// We have an outdated interval, remove it.
- indicesToRemove.Add(i);
+ this.PopInterval(current);
}
}
else
{
- this.UpdateCost(i, intervalEnumerator.Current.Index, intervalEnumerator.Current.Cost);
+ this.UpdateCost(i, current.Index, current.Cost);
}
- }
- foreach (int index in indicesToRemove.OrderByDescending(i => i))
- {
- this.Intervals.RemoveAt(index);
+ current = next;
}
}
///
/// Given a new cost interval defined by its start at position, its length value
/// and distanceCost, add its contributions to the previous intervals and costs.
- /// If handling the interval or one of its subintervals becomes to heavy, its
+ /// If handling the interval or one of its sub-intervals becomes to heavy, its
/// contribution is added to the costs right away.
///
public void PushInterval(double distanceCost, int position, int len)
@@ -150,6 +150,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
return;
}
+ CostInterval interval = this.head;
for (int i = 0; i < this.CacheIntervalsSize && this.CacheIntervals[i].Start < len; i++)
{
// Define the intersection of the ith interval with the new one.
@@ -158,10 +159,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
float cost = (float)(distanceCost + this.CacheIntervals[i].Cost);
var idx = i;
- CostCacheInterval interval = this.CacheIntervals[idx];
- var indicesToRemove = new List();
- for (; interval.Start < end; idx++)
+ CostInterval intervalNext;
+ for (; interval != null && interval.Start < end; interval = intervalNext)
{
+ intervalNext = interval.Next;
+
// Make sure we have some overlap.
if (start >= interval.End)
{
@@ -170,8 +172,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
if (cost >= interval.Cost)
{
+ // If we are worse than what we already have, add whatever we have so far up to interval.
int startNew = interval.End;
- this.InsertInterval(cost, position, start, interval.Start);
+ this.InsertInterval(interval, cost, position, start, interval.Start);
start = startNew;
if (start >= end)
{
@@ -185,7 +188,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
if (interval.End <= end)
{
- indicesToRemove.Add(idx);
+ // We can safely remove the old interval as it is fully included.
+ this.PopInterval(interval);
}
else
{
@@ -197,9 +201,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
if (end < interval.End)
{
+ // We have to split the old interval as it fully contains the new one.
int endOriginal = interval.End;
interval.End = start;
- this.InsertInterval(interval.Cost, idx, end, endOriginal);
+ this.InsertInterval(interval, interval.Cost, idx, end, endOriginal);
break;
}
else
@@ -209,27 +214,98 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
}
}
- foreach (int indice in indicesToRemove.OrderByDescending(i => i))
- {
- this.Intervals.RemoveAt(indice);
- }
-
// Insert the remaining interval from start to end.
- this.InsertInterval(cost, position, start, end);
+ this.InsertInterval(interval, cost, position, start, end);
+ }
+ }
+
+ ///
+ /// Pop an interval from the manager.
+ ///
+ /// The interval to remove.
+ private void PopInterval(CostInterval interval)
+ {
+ if (interval == null)
+ {
+ return;
}
+
+ ConnectIntervals(interval.Previous, interval.Next);
+ this.Count--;
}
- private void InsertInterval(double cost, int position, int start, int end)
+ private void InsertInterval(CostInterval intervalIn, float cost, int position, int start, int end)
{
- // TODO: use COST_CACHE_INTERVAL_SIZE_MAX
- var interval = new CostCacheInterval()
+ if (start >= end)
+ {
+ return;
+ }
+
+ // TODO: should we use COST_CACHE_INTERVAL_SIZE_MAX?
+ var intervalNew = new CostInterval()
{
Cost = cost,
Start = start,
- End = end
+ End = end,
+ Index = position
};
- this.CacheIntervals.Insert(position, interval);
+ this.PositionOrphanInterval(intervalNew, intervalIn);
+ this.Count++;
+ }
+
+ ///
+ /// Given a current orphan interval and its previous interval, before
+ /// it was orphaned (which can be NULL), set it at the right place in the list
+ /// of intervals using the start_ ordering and the previous interval as a hint.
+ ///
+ private void PositionOrphanInterval(CostInterval current, CostInterval previous)
+ {
+ if (previous == null)
+ {
+ previous = this.head;
+ }
+
+ while (previous != null && current.Start < previous.Start)
+ {
+ previous = previous.Previous;
+ }
+
+ while (previous != null && previous.Next != null && previous.Next.Start < current.Start)
+ {
+ previous = previous.Next;
+ }
+
+ if (previous != null)
+ {
+ this.ConnectIntervals(current, previous.Next);
+ }
+ else
+ {
+ this.ConnectIntervals(current, this.head);
+ }
+
+ this.ConnectIntervals(previous, current);
+ }
+
+ ///
+ /// Given two intervals, make 'prev' be the previous one of 'next' in 'manager'.
+ ///
+ private void ConnectIntervals(CostInterval prev, CostInterval next)
+ {
+ if (prev != null)
+ {
+ prev.Next = next;
+ }
+ else
+ {
+ this.head = next;
+ }
+
+ if (next != null)
+ {
+ next.Previous = prev;
+ }
}
///
diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs
index fcbac5267c..bc2c7b5946 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs
@@ -629,7 +629,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
for (int x = 1; x < width - 1; x++)
{
- uint up = argb[-stride + x]; // TODO: -stride!
+ uint up = argb[-stride + x];
uint down = argb[stride + x];
uint left = current;
current = right;