diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs
index a2eafb160..2649b7fb1 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs
@@ -1,3 +1,6 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
using System;
using System.Collections.Generic;
@@ -12,10 +15,23 @@ namespace SixLabors.ImageSharp.Memory
where T : struct
{
///
- /// Gets the number of elements per contiguous sub-block.
+ /// Gets the number of elements per contiguous sub-buffer preceding the last buffer.
+ /// The last buffer is allowed to be smaller.
+ ///
+ public int BufferLength { get; }
+
+ ///
+ /// Gets the aggregate number of elements in the group.
///
- public int BufferSize { get; }
+ public long TotalLength { get; }
+ ///
+ /// Gets a value indicating whether the group has been invalidated.
+ ///
+ ///
+ /// Invalidation usually occurs when an image processor capable to alter the image dimensions replaces
+ /// the image buffers internally.
+ ///
bool IsValid { get; }
}
}
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs
index df30c2ee2..51ed7e861 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs
@@ -1,3 +1,6 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
using System;
namespace SixLabors.ImageSharp.Memory
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs
new file mode 100644
index 000000000..3e0df15ea
--- /dev/null
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+namespace SixLabors.ImageSharp.Memory
+{
+ internal static class MemoryGroupExtensions
+ {
+ public static void CopyTo(this IMemoryGroup source, IMemoryGroup target)
+ where T : struct
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs
index b1077e254..ec801015a 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs
@@ -1,3 +1,6 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
using System;
using System.Buffers;
using System.Collections;
@@ -15,7 +18,8 @@ namespace SixLabors.ImageSharp.Memory
/// instance becomes invalid, throwing an exception on all operations.
///
/// The element type.
- internal class MemoryGroupView : IMemoryGroup where T : struct
+ internal class MemoryGroupView : IMemoryGroup
+ where T : struct
{
private readonly Memory.MemoryGroup owner;
private readonly MemoryOwnerWrapper[] memoryWrappers;
@@ -32,23 +36,25 @@ namespace SixLabors.ImageSharp.Memory
}
}
- public IEnumerator> GetEnumerator() => throw new NotImplementedException();
+ public int Count => this.owner.Count;
- IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
+ public int BufferLength => this.owner.BufferLength;
+
+ public long TotalLength => this.owner.TotalLength;
- public int Count { get; }
+ public bool IsValid { get; internal set; }
public Memory this[int index] => throw new NotImplementedException();
- public int BufferSize => this.owner.BufferSize;
+ public IEnumerator> GetEnumerator() => throw new NotImplementedException();
- public bool IsValid { get; internal set; }
+ IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
- class MemoryOwnerWrapper : MemoryManager
+ private class MemoryOwnerWrapper : MemoryManager
{
- private MemoryGroupView view;
+ private readonly MemoryGroupView view;
- private int index;
+ private readonly int index;
public MemoryOwnerWrapper(MemoryGroupView view, int index)
{
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs
index 20afb2d57..4b7f8acae 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs
@@ -1,3 +1,6 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
using System;
using System.Collections.Generic;
@@ -10,8 +13,8 @@ namespace SixLabors.ImageSharp.Memory
{
private readonly ReadOnlyMemory> source;
- public Consumed(ReadOnlyMemory> source, int bufferSize)
- : base(bufferSize)
+ public Consumed(ReadOnlyMemory> source, int bufferLength, long totalLength)
+ : base(bufferLength, totalLength)
{
this.source = source;
}
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs
index c90a24376..e0dfc6396 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs
@@ -1,3 +1,6 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
using System;
using System.Buffers;
using System.Collections.Generic;
@@ -12,8 +15,8 @@ namespace SixLabors.ImageSharp.Memory
{
private IMemoryOwner[] memoryOwners;
- public Owned(IMemoryOwner[] memoryOwners, int bufferSize)
- : base(bufferSize)
+ public Owned(IMemoryOwner[] memoryOwners, int bufferLength, long totalLength)
+ : base(bufferLength, totalLength)
{
this.memoryOwners = memoryOwners;
}
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
index 9d36cc8dc..ac43d6847 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
@@ -1,3 +1,6 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
using System;
using System.Buffers;
using System.Collections;
@@ -17,11 +20,17 @@ namespace SixLabors.ImageSharp.Memory
{
private static readonly int ElementSize = Unsafe.SizeOf();
- private MemoryGroup(int bufferSize) => this.BufferSize = bufferSize;
+ private MemoryGroup(int bufferLength, long totalLength)
+ {
+ this.BufferLength = bufferLength;
+ this.TotalLength = totalLength;
+ }
public abstract int Count { get; }
- public int BufferSize { get; }
+ public int BufferLength { get; }
+
+ public long TotalLength { get; }
public bool IsValid { get; private set; } = true;
@@ -51,18 +60,18 @@ namespace SixLabors.ImageSharp.Memory
}
int numberOfAlignedSegments = blockCapacityInElements / blockAlignment;
- int bufferSize = numberOfAlignedSegments * blockAlignment;
- if (totalLength > 0 && totalLength < bufferSize)
+ int bufferLength = numberOfAlignedSegments * blockAlignment;
+ if (totalLength > 0 && totalLength < bufferLength)
{
- bufferSize = (int)totalLength;
+ bufferLength = (int)totalLength;
}
- int sizeOfLastBuffer = (int)(totalLength % bufferSize);
- long bufferCount = totalLength / bufferSize;
+ int sizeOfLastBuffer = (int)(totalLength % bufferLength);
+ long bufferCount = totalLength / bufferLength;
if (sizeOfLastBuffer == 0)
{
- sizeOfLastBuffer = bufferSize;
+ sizeOfLastBuffer = bufferLength;
}
else
{
@@ -72,7 +81,7 @@ namespace SixLabors.ImageSharp.Memory
var buffers = new IMemoryOwner[bufferCount];
for (int i = 0; i < buffers.Length - 1; i++)
{
- buffers[i] = allocator.Allocate(bufferSize, allocationOptions);
+ buffers[i] = allocator.Allocate(bufferLength, allocationOptions);
}
if (bufferCount > 0)
@@ -80,28 +89,30 @@ namespace SixLabors.ImageSharp.Memory
buffers[^1] = allocator.Allocate(sizeOfLastBuffer, allocationOptions);
}
- return new Owned(buffers, bufferSize);
+ return new Owned(buffers, bufferLength, totalLength);
}
public static MemoryGroup Wrap(params Memory[] source) => Wrap(source.AsMemory());
public static MemoryGroup Wrap(ReadOnlyMemory> source)
{
- int bufferSize = source.Length > 0 ? source.Span[0].Length : 0;
+ int bufferLength = source.Length > 0 ? source.Span[0].Length : 0;
for (int i = 1; i < source.Length - 1; i++)
{
- if (source.Span[i].Length != bufferSize)
+ if (source.Span[i].Length != bufferLength)
{
throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!");
}
}
- if (source.Length > 0 && source.Span[^1].Length > bufferSize)
+ if (source.Length > 0 && source.Span[^1].Length > bufferLength)
{
throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!");
}
- return new Consumed(source, bufferSize);
+ long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source.Span[^1].Length : 0;
+
+ return new Consumed(source, bufferLength, totalLength);
}
// Analogous to current MemorySource.SwapOrCopyContent()
diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs
new file mode 100644
index 000000000..710f90216
--- /dev/null
+++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs
@@ -0,0 +1,120 @@
+using System;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
+{
+ public struct MemoryGroupIndex : IEquatable
+ {
+ public override bool Equals(object obj) => obj is MemoryGroupIndex other && this.Equals(other);
+
+ public override int GetHashCode() => HashCode.Combine(this.BufferLength, this.BufferIndex, this.ElementIndex);
+
+ public int BufferLength { get; }
+
+ public int BufferIndex { get; }
+
+ public int ElementIndex { get; }
+
+ public MemoryGroupIndex(int bufferLength, int bufferIndex, int elementIndex)
+ {
+ this.BufferLength = bufferLength;
+ this.BufferIndex = bufferIndex;
+ this.ElementIndex = elementIndex;
+ }
+
+ public static MemoryGroupIndex operator +(MemoryGroupIndex idx, int val)
+ {
+ int nextElementIndex = idx.ElementIndex + val;
+ return new MemoryGroupIndex(
+ idx.BufferLength,
+ idx.BufferIndex + (nextElementIndex / idx.BufferLength),
+ nextElementIndex % idx.BufferLength);
+ }
+
+ public bool Equals(MemoryGroupIndex other)
+ {
+ if (this.BufferLength != other.BufferLength)
+ {
+ throw new InvalidOperationException();
+ }
+
+ return this.BufferIndex == other.BufferIndex && this.ElementIndex == other.ElementIndex;
+ }
+
+ public static bool operator ==(MemoryGroupIndex a, MemoryGroupIndex b) => a.Equals(b);
+
+ public static bool operator !=(MemoryGroupIndex a, MemoryGroupIndex b) => !a.Equals(b);
+
+ public static bool operator <(MemoryGroupIndex a, MemoryGroupIndex b)
+ {
+ if (a.BufferLength != b.BufferLength)
+ {
+ throw new InvalidOperationException();
+ }
+
+ if (a.BufferIndex < b.BufferIndex)
+ {
+ return true;
+ }
+
+ if (a.BufferIndex == b.BufferIndex)
+ {
+ return a.ElementIndex < b.ElementIndex;
+ }
+
+ return false;
+ }
+
+ public static bool operator >(MemoryGroupIndex a, MemoryGroupIndex b)
+ {
+ if (a.BufferLength != b.BufferLength)
+ {
+ throw new InvalidOperationException();
+ }
+
+ if (a.BufferIndex > b.BufferIndex)
+ {
+ return true;
+ }
+
+ if (a.BufferIndex == b.BufferIndex)
+ {
+ return a.ElementIndex > b.ElementIndex;
+ }
+
+ return false;
+ }
+ }
+
+ internal static class MemoryGroupIndexExtensions
+ {
+ public static T GetElementAt(this MemoryGroup group, MemoryGroupIndex idx)
+ where T : struct
+ {
+ return group[idx.BufferIndex].Span[idx.ElementIndex];
+ }
+
+ public static void SetElementAt(this MemoryGroup group, MemoryGroupIndex idx, T value)
+ where T : struct
+ {
+ group[idx.BufferIndex].Span[idx.ElementIndex] = value;
+ }
+
+ public static MemoryGroupIndex MinIndex(this MemoryGroup group)
+ where T : struct
+ {
+ return new MemoryGroupIndex(group.BufferLength, 0, 0);
+ }
+
+ public static MemoryGroupIndex MaxIndex(this MemoryGroup group)
+ where T : struct
+ {
+ if (group.Count == 0)
+ {
+ return new MemoryGroupIndex(group.BufferLength, 0, 0);
+ }
+
+ return new MemoryGroupIndex(group.BufferLength, group.Count - 1, group[^1].Length - 1);
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs
new file mode 100644
index 000000000..6a9d322f6
--- /dev/null
+++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs
@@ -0,0 +1,64 @@
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
+{
+ public class MemoryGroupIndexTests
+ {
+ [Fact]
+ public void Equal()
+ {
+ var a = new MemoryGroupIndex(10, 1, 3);
+ var b = new MemoryGroupIndex(10, 1, 3);
+
+ Assert.True(a.Equals(b));
+ Assert.True(a == b);
+ Assert.False(a != b);
+ Assert.False(a < b);
+ Assert.False(a > b);
+ }
+
+ [Fact]
+ public void SmallerBufferIndex()
+ {
+ var a = new MemoryGroupIndex(10, 3, 3);
+ var b = new MemoryGroupIndex(10, 5, 3);
+
+ Assert.False(a == b);
+ Assert.True(a != b);
+ Assert.True(a < b);
+ Assert.False(a > b);
+ }
+
+ [Fact]
+ public void SmallerElementIndex()
+ {
+ var a = new MemoryGroupIndex(10, 3, 3);
+ var b = new MemoryGroupIndex(10, 3, 9);
+
+ Assert.False(a == b);
+ Assert.True(a != b);
+ Assert.True(a < b);
+ Assert.False(a > b);
+ }
+
+ [Fact]
+ public void Increment()
+ {
+ var a = new MemoryGroupIndex(10, 3, 3);
+ a += 1;
+ Assert.Equal(new MemoryGroupIndex(10, 3, 4), a);
+ }
+
+ [Fact]
+ public void Increment_OverflowBuffer()
+ {
+ var a = new MemoryGroupIndex(10, 5, 3);
+ var b = new MemoryGroupIndex(10, 5, 9);
+ a += 8;
+ b += 1;
+
+ Assert.Equal(new MemoryGroupIndex(10, 6, 1), a);
+ Assert.Equal(new MemoryGroupIndex(10, 6, 0), b);
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs
new file mode 100644
index 000000000..1a617d396
--- /dev/null
+++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs
@@ -0,0 +1,119 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+using System.Linq;
+using SixLabors.ImageSharp.Memory;
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
+{
+ public partial class MemoryGroupTests
+ {
+ public class Allocate : MemoryGroupTestsBase
+ {
+#pragma warning disable SA1509
+ public static TheoryData