Browse Source

Merge branch 'master' into dp/jpeg-decoder-color-converters

pull/1864/head
Dmitry Pentin 4 years ago
parent
commit
b039502cbb
  1. 8
      .gitattributes
  2. 4
      Directory.Build.props
  3. 4
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  4. 2
      src/ImageSharp/Advanced/AotCompilerTools.cs
  5. 14
      src/ImageSharp/Common/Helpers/DebugGuard.cs
  6. 4
      src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs
  7. 5
      src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs
  8. 39
      src/ImageSharp/Configuration.cs
  9. 18
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  10. 20
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  11. 4
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  12. 4
      src/ImageSharp/Formats/Gif/LzwDecoder.cs
  13. 2
      src/ImageSharp/Formats/Gif/LzwEncoder.cs
  14. 10
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs
  15. 10
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
  16. 8
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
  17. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
  18. 11
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  19. 2
      src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs
  20. 5
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  21. 60
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  22. 20
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  23. 8
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  24. 2
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
  25. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs
  26. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs
  27. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs
  28. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs
  29. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs
  30. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs
  31. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs
  32. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs
  33. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs
  34. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs
  35. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs
  36. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs
  37. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs
  38. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs
  39. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs
  40. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs
  41. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs
  42. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs
  43. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs
  44. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs
  45. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs
  46. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs
  47. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs
  48. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs
  49. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs
  50. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs
  51. 26
      src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs
  52. 2
      src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs
  53. 4
      src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs
  54. 7
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  55. 2
      src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs
  56. 788
      src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs
  57. 4
      src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs
  58. 11
      src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
  59. 152
      src/ImageSharp/ImageFrame{TPixel}.cs
  60. 137
      src/ImageSharp/Image{TPixel}.cs
  61. 7
      src/ImageSharp/IndexedImageFrame{TPixel}.cs
  62. 7
      src/ImageSharp/Memory/Allocators/AllocationOptions.cs
  63. 10
      src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs
  64. 105
      src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs
  65. 76
      src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs
  66. 185
      src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs
  67. 18
      src/ImageSharp/Memory/Allocators/IManagedByteBuffer.cs
  68. 3
      src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs
  69. 20
      src/ImageSharp/Memory/Allocators/Internals/BasicByteBuffer.cs
  70. 115
      src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs
  71. 21
      src/ImageSharp/Memory/Allocators/Internals/IRefCounted.cs
  72. 3
      src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs
  73. 56
      src/ImageSharp/Memory/Allocators/Internals/RefCountedLifetimeGuard.cs
  74. 80
      src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs
  75. 65
      src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs
  76. 356
      src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs
  77. 27
      src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs
  78. 80
      src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs
  79. 140
      src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs
  80. 50
      src/ImageSharp/Memory/Allocators/MemoryAllocator.cs
  81. 31
      src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs
  82. 8
      src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs
  83. 164
      src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs
  84. 33
      src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs
  85. 14
      src/ImageSharp/Memory/Buffer2DExtensions.cs
  86. 4
      src/ImageSharp/Memory/Buffer2DRegion{T}.cs
  87. 101
      src/ImageSharp/Memory/Buffer2D{T}.cs
  88. 5
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs
  89. 136
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs
  90. 112
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
  91. 69
      src/ImageSharp/Memory/MemoryAllocatorExtensions.cs
  92. 2
      src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs
  93. 72
      src/ImageSharp/PixelAccessor{TPixel}.cs
  94. 9
      src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs
  95. 12
      src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs
  96. 9
      src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs
  97. 16
      src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs
  98. 10
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs
  99. 20
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
  100. 8
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs

8
.gitattributes

@ -87,7 +87,6 @@
*.eot binary
*.exe binary
*.otf binary
*.pbm binary
*.pdf binary
*.ppt binary
*.pptx binary
@ -95,7 +94,6 @@
*.snk binary
*.ttc binary
*.ttf binary
*.wbmp binary
*.woff binary
*.woff2 binary
*.xls binary
@ -126,3 +124,9 @@
*.dds filter=lfs diff=lfs merge=lfs -text
*.ktx filter=lfs diff=lfs merge=lfs -text
*.ktx2 filter=lfs diff=lfs merge=lfs -text
*.pam filter=lfs diff=lfs merge=lfs -text
*.pbm filter=lfs diff=lfs merge=lfs -text
*.pgm filter=lfs diff=lfs merge=lfs -text
*.ppm filter=lfs diff=lfs merge=lfs -text
*.pnm filter=lfs diff=lfs merge=lfs -text
*.wbmp filter=lfs diff=lfs merge=lfs -text

4
Directory.Build.props

@ -13,6 +13,9 @@
<PropertyGroup>
<!-- This MUST be defined before importing props. -->
<SixLaborsSolutionDirectory>$(MSBuildThisFileDirectory)</SixLaborsSolutionDirectory>
<!-- For some reason Debug-InnerLoop doesn't define DEBUG by default. -->
<DefineConstants Condition="'$(Configuration)' == 'Debug-InnerLoop'">$(DefineConstants);DEBUG</DefineConstants>
</PropertyGroup>
<!-- Import the shared global .props file -->
@ -30,5 +33,4 @@
<PropertyGroup Condition="$(Configuration.StartsWith('Release')) == true">
<Optimize>true</Optimize>
</PropertyGroup>
</Project>

4
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -143,7 +143,7 @@ namespace SixLabors.ImageSharp.Advanced
/// <param name="source">The source.</param>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
public static Memory<TPixel> GetPixelRowMemory<TPixel>(this ImageFrame<TPixel> source, int rowIndex)
public static Memory<TPixel> DangerousGetPixelRowMemory<TPixel>(this ImageFrame<TPixel> source, int rowIndex)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(source, nameof(source));
@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp.Advanced
/// <param name="source">The source.</param>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
public static Memory<TPixel> GetPixelRowMemory<TPixel>(this Image<TPixel> source, int rowIndex)
public static Memory<TPixel> DangerousGetPixelRowMemory<TPixel>(this Image<TPixel> source, int rowIndex)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(source, nameof(source));

2
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -529,7 +529,7 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileMemoryManagers<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
AotCompileMemoryManager<TPixel, ArrayPoolMemoryAllocator>();
AotCompileMemoryManager<TPixel, UniformUnmanagedMemoryPoolMemoryAllocator>();
AotCompileMemoryManager<TPixel, SimpleGcMemoryAllocator>();
}

14
src/ImageSharp/Common/Helpers/DebugGuard.cs

@ -26,6 +26,20 @@ namespace SixLabors
}
}
/// <summary>
/// Verifies whether a condition (indicating disposed state) is met, throwing an ObjectDisposedException if it's true.
/// </summary>
/// <param name="isDisposed">Whether the object is disposed.</param>
/// <param name="objectName">The name of the object.</param>
[Conditional("DEBUG")]
public static void NotDisposed(bool isDisposed, string objectName)
{
if (isDisposed)
{
throw new ObjectDisposedException(objectName);
}
}
/// <summary>
/// Verifies, that the target span is of same size than the 'other' span.
/// </summary>

4
src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs

@ -28,6 +28,10 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
/// <remarks>
/// Implementation can assume that source.Length is less or equal than dest.Length.
/// Loops should iterate using source.Length.
/// </remarks>
void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest);
}

5
src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs

@ -77,6 +77,7 @@ namespace SixLabors.ImageSharp
TShuffle shuffle)
where TShuffle : struct, IShuffle3
{
// Source length should be smaller than dest length, and divisible by 3.
VerifyShuffle3SpanInput(source, dest);
#if SUPPORTS_RUNTIME_INTRINSICS
@ -182,9 +183,9 @@ namespace SixLabors.ImageSharp
where T : struct
{
DebugGuard.IsTrue(
source.Length == dest.Length,
source.Length <= dest.Length,
nameof(source),
"Input spans must be of same length!");
"Source should fit into dest!");
DebugGuard.IsTrue(
source.Length % 3 == 0,

39
src/ImageSharp/Configuration.cs

@ -26,10 +26,11 @@ namespace SixLabors.ImageSharp
/// <summary>
/// A lazily initialized configuration default instance.
/// </summary>
private static readonly Lazy<Configuration> Lazy = new Lazy<Configuration>(CreateDefaultInstance);
private static readonly Lazy<Configuration> Lazy = new(CreateDefaultInstance);
private const int DefaultStreamProcessingBufferSize = 8096;
private int streamProcessingBufferSize = DefaultStreamProcessingBufferSize;
private int maxDegreeOfParallelism = Environment.ProcessorCount;
private MemoryAllocator memoryAllocator = MemoryAllocator.Default;
/// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class.
@ -95,6 +96,14 @@ namespace SixLabors.ImageSharp
}
}
/// <summary>
/// Gets or sets a value indicating whether to force image buffers to be contiguous whenever possible.
/// </summary>
/// <remarks>
/// Contiguous allocations are not possible, if the image needs a buffer larger than <see cref="int.MaxValue"/>.
/// </remarks>
public bool PreferContiguousImageBuffers { get; set; }
/// <summary>
/// Gets a set of properties for the Configuration.
/// </summary>
@ -117,9 +126,31 @@ namespace SixLabors.ImageSharp
public ImageFormatManager ImageFormatsManager { get; set; } = new ImageFormatManager();
/// <summary>
/// Gets or sets the <see cref="MemoryAllocator"/> that is currently in use.
/// Gets or sets the <see cref="ImageSharp.Memory.MemoryAllocator"/> that is currently in use.
/// Defaults to <see cref="ImageSharp.Memory.MemoryAllocator.Default"/>.
/// <para />
/// Allocators are expensive, so it is strongly recommended to use only one busy instance per process.
/// In case you need to customize it, you can ensure this by changing
/// </summary>
public MemoryAllocator MemoryAllocator { get; set; } = ArrayPoolMemoryAllocator.CreateDefault();
/// <remarks>
/// It's possible to reduce allocator footprint by assigning a custom instance created with
/// <see cref="Memory.MemoryAllocator.Create(MemoryAllocatorOptions)"/>, but note that since the default pooling
/// allocators are expensive, it is strictly recommended to use a single process-wide allocator.
/// You can ensure this by altering the allocator of <see cref="Default"/>, or by implementing custom application logic that
/// manages allocator lifetime.
/// <para />
/// If an allocator has to be dropped for some reason, <see cref="Memory.MemoryAllocator.ReleaseRetainedResources"/>
/// shall be invoked after disposing all associated <see cref="Image"/> instances.
/// </remarks>
public MemoryAllocator MemoryAllocator
{
get => this.memoryAllocator;
set
{
Guard.NotNull(value, nameof(this.MemoryAllocator));
this.memoryAllocator = value;
}
}
/// <summary>
/// Gets the maximum header size of all the formats.
@ -165,7 +196,7 @@ namespace SixLabors.ImageSharp
MaxDegreeOfParallelism = this.MaxDegreeOfParallelism,
StreamProcessingBufferSize = this.StreamProcessingBufferSize,
ImageFormatsManager = this.ImageFormatsManager,
MemoryAllocator = this.MemoryAllocator,
memoryAllocator = this.memoryAllocator,
ImageOperationsProvider = this.ImageOperationsProvider,
ReadOrigin = this.ReadOrigin,
FileSystem = this.FileSystem,

18
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -306,7 +306,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int newY = Invert(y, height, inverted);
int rowStartIdx = y * width;
Span<byte> bufferRow = bufferSpan.Slice(rowStartIdx, width);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y];
if (rowHasUndefinedPixels)
@ -377,7 +377,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y];
if (rowHasUndefinedPixels)
{
@ -826,7 +826,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int newY = Invert(y, height, inverted);
this.stream.Read(rowSpan);
int offset = 0;
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
for (int x = 0; x < arrayWidth; x++)
{
@ -878,7 +878,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
this.stream.Read(bufferSpan);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
int offset = 0;
for (int x = 0; x < width; x++)
@ -933,7 +933,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
this.stream.Read(rowSpan);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgr24Bytes(
this.Configuration,
rowSpan,
@ -961,7 +961,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
this.stream.Read(rowSpan);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.Configuration,
rowSpan,
@ -1031,7 +1031,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.stream.Read(rowSpan);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.Configuration,
@ -1054,7 +1054,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
width);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
for (int x = 0; x < width; x++)
{
@ -1109,7 +1109,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
this.stream.Read(bufferSpan);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
int offset = 0;
for (int x = 0; x < width; x++)

20
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -274,7 +274,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(
this.configuration,
pixelSpan,
@ -300,7 +300,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgr24Bytes(
this.configuration,
pixelSpan,
@ -326,7 +326,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra5551Bytes(
this.configuration,
@ -379,7 +379,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int y = image.Height - 1; y >= 0; y--)
{
ReadOnlySpan<byte> pixelSpan = quantized.GetPixelRowSpan(y);
ReadOnlySpan<byte> pixelSpan = quantized.DangerousGetRowSpan(y);
stream.Write(pixelSpan);
for (int i = 0; i < this.padding; i++)
@ -413,10 +413,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
stream.Write(colorPalette);
Buffer2D<TPixel> imageBuffer = image.PixelBuffer;
for (int y = image.Height - 1; y >= 0; y--)
{
ReadOnlySpan<TPixel> inputPixelRow = image.GetPixelRowSpan(y);
ReadOnlySpan<TPixel> inputPixelRow = imageBuffer.DangerousGetRowSpan(y);
ReadOnlySpan<byte> outputPixelRow = MemoryMarshal.AsBytes(inputPixelRow);
stream.Write(outputPixelRow);
@ -447,11 +447,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
ReadOnlySpan<byte> pixelRowSpan = quantized.GetPixelRowSpan(0);
ReadOnlySpan<byte> pixelRowSpan = quantized.DangerousGetRowSpan(0);
int rowPadding = pixelRowSpan.Length % 2 != 0 ? this.padding - 1 : this.padding;
for (int y = image.Height - 1; y >= 0; y--)
{
pixelRowSpan = quantized.GetPixelRowSpan(y);
pixelRowSpan = quantized.DangerousGetRowSpan(y);
int endIdx = pixelRowSpan.Length % 2 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 1;
for (int i = 0; i < endIdx; i += 2)
@ -491,11 +491,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
ReadOnlySpan<byte> quantizedPixelRow = quantized.GetPixelRowSpan(0);
ReadOnlySpan<byte> quantizedPixelRow = quantized.DangerousGetRowSpan(0);
int rowPadding = quantizedPixelRow.Length % 8 != 0 ? this.padding - 1 : this.padding;
for (int y = image.Height - 1; y >= 0; y--)
{
quantizedPixelRow = quantized.GetPixelRowSpan(y);
quantizedPixelRow = quantized.DangerousGetRowSpan(y);
int endIdx = quantizedPixelRow.Length % 8 == 0 ? quantizedPixelRow.Length : quantizedPixelRow.Length - 8;
for (int i = 0; i < endIdx; i += 8)

4
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -445,7 +445,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++)
{
ref byte indicesRowRef = ref MemoryMarshal.GetReference(indices.GetRowSpan(y - descriptorTop));
ref byte indicesRowRef = ref MemoryMarshal.GetReference(indices.DangerousGetRowSpan(y - descriptorTop));
// Check if this image is interlaced.
int writeY; // the target y offset to write to
@ -481,7 +481,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
writeY = y;
}
ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.GetPixelRowSpan(writeY));
ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.PixelBuffer.DangerousGetRowSpan(writeY));
if (!transFlag)
{

4
src/ImageSharp/Formats/Gif/LzwDecoder.cs

@ -115,14 +115,14 @@ namespace SixLabors.ImageSharp.Formats.Gif
int y = 0;
int x = 0;
int rowMax = width;
ref byte pixelsRowRef = ref MemoryMarshal.GetReference(pixels.GetRowSpan(y));
ref byte pixelsRowRef = ref MemoryMarshal.GetReference(pixels.DangerousGetRowSpan(y));
while (xyz < length)
{
// Reset row reference.
if (xyz == rowMax)
{
x = 0;
pixelsRowRef = ref MemoryMarshal.GetReference(pixels.GetRowSpan(++y));
pixelsRowRef = ref MemoryMarshal.GetReference(pixels.DangerousGetRowSpan(++y));
rowMax = (y * width) + width;
}

2
src/ImageSharp/Formats/Gif/LzwEncoder.cs

@ -275,7 +275,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
for (int y = 0; y < indexedPixels.Height; y++)
{
ref byte rowSpanRef = ref MemoryMarshal.GetReference(indexedPixels.GetRowSpan(y));
ref byte rowSpanRef = ref MemoryMarshal.GetReference(indexedPixels.DangerousGetRowSpan(y));
int offsetX = y == 0 ? 1 : 0;
for (int x = offsetX; x < indexedPixels.Width; x++)

10
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs

@ -223,14 +223,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors));
this.ComponentCount = processors.Count;
this.Component0 = processors[0].ColorBuffer.GetRowSpan(row);
this.Component0 = componentBuffers[0].DangerousGetRowSpan(row);
// In case of grayscale, Component1 and Component2 point to Component0 memory area
this.Component1 = this.ComponentCount > 1 ? processors[1].ColorBuffer.GetRowSpan(row) : this.Component0;
this.Component2 = this.ComponentCount > 2 ? processors[2].ColorBuffer.GetRowSpan(row) : this.Component0;
this.Component3 = this.ComponentCount > 3 ? processors[3].ColorBuffer.GetRowSpan(row) : Span<float>.Empty;
this.Component1 = this.ComponentCount > 1 ? componentBuffers[1].DangerousGetRowSpan(row) : this.Component0;
this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].DangerousGetRowSpan(row) : this.Component0;
this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].DangerousGetRowSpan(row) : Span<float>.Empty;
}
internal ComponentValues(

10
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs

@ -203,7 +203,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// by the basic H and V specified for the component
for (int y = 0; y < v; y++)
{
Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(y);
Span<Block8x8> blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int x = 0; x < h; x++)
@ -254,7 +254,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
for (int j = 0; j < h; j++)
{
this.cancellationToken.ThrowIfCancellationRequested();
Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(j);
Span<Block8x8> blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int i = 0; i < w; i++)
@ -377,7 +377,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
for (int y = 0; y < v; y++)
{
int blockRow = (mcuRow * v) + y;
Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(blockRow);
Span<Block8x8> blockSpan = component.SpectralBlocks.DangerousGetRowSpan(blockRow);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int x = 0; x < h; x++)
@ -422,7 +422,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
this.cancellationToken.ThrowIfCancellationRequested();
Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(j);
Span<Block8x8> blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int i = 0; i < w; i++)
@ -450,7 +450,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
this.cancellationToken.ThrowIfCancellationRequested();
Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(j);
Span<Block8x8> blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int i = 0; i < w; i++)

8
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs

@ -84,8 +84,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
int yBuffer = y * this.blockAreaSize.Height;
Span<float> colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer);
Span<Block8x8> blockRow = spectralBuffer.GetRowSpan(yBlockStart + y);
Span<float> colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer);
Span<Block8x8> blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y);
for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++)
{
@ -119,11 +119,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
Buffer2D<Block8x8> spectralBlocks = this.component.SpectralBlocks;
for (int i = 0; i < spectralBlocks.Height; i++)
{
spectralBlocks.GetRowSpan(i).Clear();
spectralBlocks.DangerousGetRowSpan(i).Clear();
}
}
public Span<float> GetColorBufferRowSpan(int row) =>
this.ColorBuffer.GetRowSpan(row);
this.ColorBuffer.DangerousGetRowSpan(row);
}
}

2
src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs

@ -187,7 +187,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
Span<TPixel> proxyRow = this.paddedProxyPixelRow.GetSpan();
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.configuration, r, g, b, proxyRow);
proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.GetRowSpan(yy));
proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.DangerousGetRowSpan(yy));
}
}

11
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs

@ -650,6 +650,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
this.target.Write(this.streamWriteBuffer, 0, writeIdx);
this.emitWriteIndex = this.emitBuffer.Length;
}
/// <summary>
@ -660,11 +661,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// This must be called only if <see cref="IsStreamFlushNeeded"/> is true
/// only during the macroblocks encoding routine.
/// </remarks>
private void FlushToStream()
{
private void FlushToStream() =>
this.FlushToStream(this.emitWriteIndex * 4);
this.emitWriteIndex = this.emitBuffer.Length;
}
/// <summary>
/// Flushes final cached bits to the stream padding 1's to
@ -681,10 +679,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
// And writing only valuable count of bytes count we want to write to the output stream
int valuableBytesCount = (int)Numerics.DivideCeil((uint)this.bitCount, 8);
uint packedBytes = this.accumulatedBits | (uint.MaxValue >> this.bitCount);
this.emitBuffer[--this.emitWriteIndex] = packedBytes;
this.emitBuffer[this.emitWriteIndex - 1] = packedBytes;
// Flush cached bytes to the output stream with padding bits
this.FlushToStream((this.emitWriteIndex * 4) - 4 + valuableBytesCount);
int lastByteIndex = (this.emitWriteIndex * 4) - valuableBytesCount;
this.FlushToStream(lastByteIndex);
}
}
}

2
src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs

@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
int i = 0;
while (y < yEnd)
{
this[i++] = buffer.GetRowSpan(y++);
this[i++] = buffer.DangerousGetRowSpan(y++);
}
}

5
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -565,6 +565,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{
int pass = 0;
int width = this.header.Width;
Buffer2D<TPixel> imageBuffer = image.PixelBuffer;
while (true)
{
int numColumns = Adam7.ComputeColumns(width, pass);
@ -623,7 +624,7 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
}
Span<TPixel> rowSpan = image.GetPixelRowSpan(this.currentRow);
Span<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(this.currentRow);
this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetadata, Adam7.FirstColumn[pass], Adam7.ColumnIncrement[pass]);
this.SwapScanlineBuffers();
@ -656,7 +657,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private void ProcessDefilteredScanline<TPixel>(ReadOnlySpan<byte> defilteredScanline, ImageFrame<TPixel> pixels, PngMetadata pngMetadata)
where TPixel : unmanaged, IPixel<TPixel>
{
Span<TPixel> rowSpan = pixels.GetPixelRowSpan(this.currentRow);
Span<TPixel> rowSpan = pixels.PixelBuffer.DangerousGetRowSpan(this.currentRow);
// Trim the first marker byte from the buffer
ReadOnlySpan<byte> trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1);

60
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -163,23 +163,25 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="image">The cloned image where the transparent pixels will be changed.</param>
private static void ClearTransparentPixels<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
Rgba32 rgba32 = default;
for (int y = 0; y < image.Height; y++)
where TPixel : unmanaged, IPixel<TPixel> =>
image.ProcessPixelRows(accessor =>
{
Span<TPixel> span = image.GetPixelRowSpan(y);
for (int x = 0; x < image.Width; x++)
Rgba32 rgba32 = default;
Rgba32 transparent = Color.Transparent;
for (int y = 0; y < accessor.Height; y++)
{
span[x].ToRgba32(ref rgba32);
if (rgba32.A == 0)
Span<TPixel> span = accessor.GetRowSpan(y);
for (int x = 0; x < accessor.Width; x++)
{
span[x].FromRgba32(Color.Transparent);
span[x].ToRgba32(ref rgba32);
if (rgba32.A == 0)
{
span[x].FromRgba32(transparent);
}
}
}
}
}
});
/// <summary>
/// Creates the quantized image and sets calculates and sets the bit depth.
@ -391,11 +393,11 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.bitDepth < 8)
{
PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.GetPixelRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth);
PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.DangerousGetRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth);
}
else
{
quantized.GetPixelRowSpan(row).CopyTo(this.currentScanline.GetSpan());
quantized.DangerousGetRowSpan(row).CopyTo(this.currentScanline.GetSpan());
}
break;
@ -914,27 +916,31 @@ namespace SixLabors.ImageSharp.Formats.Png
using IMemoryOwner<byte> filterBuffer = this.memoryAllocator.Allocate<byte>(filterLength, AllocationOptions.Clean);
using IMemoryOwner<byte> attemptBuffer = this.memoryAllocator.Allocate<byte>(filterLength, AllocationOptions.Clean);
Span<byte> filter = filterBuffer.GetSpan();
Span<byte> attempt = attemptBuffer.GetSpan();
for (int y = 0; y < this.height; y++)
pixels.ProcessPixelRows(accessor =>
{
this.CollectAndFilterPixelRow(pixels.GetPixelRowSpan(y), ref filter, ref attempt, quantized, y);
deflateStream.Write(filter);
this.SwapScanlineBuffers();
}
Span<byte> filter = filterBuffer.GetSpan();
Span<byte> attempt = attemptBuffer.GetSpan();
for (int y = 0; y < this.height; y++)
{
this.CollectAndFilterPixelRow(accessor.GetRowSpan(y), ref filter, ref attempt, quantized, y);
deflateStream.Write(filter);
this.SwapScanlineBuffers();
}
});
}
/// <summary>
/// Interlaced encoding the pixels.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="pixels">The pixels.</param>
/// <param name="image">The image.</param>
/// <param name="deflateStream">The deflate stream.</param>
private void EncodeAdam7Pixels<TPixel>(Image<TPixel> pixels, ZlibDeflateStream deflateStream)
private void EncodeAdam7Pixels<TPixel>(Image<TPixel> image, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = pixels.Width;
int height = pixels.Height;
int width = image.Width;
int height = image.Height;
Buffer2D<TPixel> pixelBuffer = image.Frames.RootFrame.PixelBuffer;
for (int pass = 0; pass < 7; pass++)
{
int startRow = Adam7.FirstRow[pass];
@ -959,7 +965,7 @@ namespace SixLabors.ImageSharp.Formats.Png
for (int row = startRow; row < height; row += Adam7.RowIncrement[pass])
{
// Collect pixel data
Span<TPixel> srcRow = pixels.GetPixelRowSpan(row);
Span<TPixel> srcRow = pixelBuffer.DangerousGetRowSpan(row);
for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass])
{
block[i++] = srcRow[col];
@ -1014,7 +1020,7 @@ namespace SixLabors.ImageSharp.Formats.Png
row += Adam7.RowIncrement[pass])
{
// Collect data
ReadOnlySpan<byte> srcRow = quantized.GetPixelRowSpan(row);
ReadOnlySpan<byte> srcRow = quantized.DangerousGetRowSpan(row);
for (int col = startCol, i = 0;
col < width;
col += Adam7.ColumnIncrement[pass])

20
src/ImageSharp/Formats/Tga/TgaDecoderCore.cs

@ -234,7 +234,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
for (int y = 0; y < height; y++)
{
int newY = InvertY(y, height, origin);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
switch (colorMapPixelSizeInBytes)
{
@ -318,7 +318,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
for (int y = 0; y < height; y++)
{
int newY = InvertY(y, height, origin);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
int rowStartIdx = y * width * bytesPerPixel;
for (int x = 0; x < width; x++)
{
@ -364,7 +364,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
for (int y = 0; y < height; y++)
{
int newY = InvertY(y, height, origin);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
for (int x = width - 1; x >= 0; x--)
{
this.ReadL8Pixel(color, x, pixelSpan);
@ -412,7 +412,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
for (int y = 0; y < height; y++)
{
int newY = InvertY(y, height, origin);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
if (invertX)
{
@ -479,7 +479,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
for (int y = 0; y < height; y++)
{
int newY = InvertY(y, height, origin);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
for (int x = width - 1; x >= 0; x--)
{
this.ReadBgr24Pixel(color, x, pixelSpan);
@ -548,7 +548,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
for (int y = 0; y < height; y++)
{
int newY = InvertY(y, height, origin);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
if (invertX)
{
for (int x = width - 1; x >= 0; x--)
@ -587,7 +587,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
for (int y = 0; y < height; y++)
{
int newY = InvertY(y, height, origin);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
int rowStartIdx = y * width * bytesPerPixel;
for (int x = 0; x < width; x++)
{
@ -654,7 +654,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
where TPixel : unmanaged, IPixel<TPixel>
{
this.currentStream.Read(row);
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromL8Bytes(this.Configuration, row, pixelSpan, width);
}
@ -681,7 +681,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
where TPixel : unmanaged, IPixel<TPixel>
{
this.currentStream.Read(row);
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromBgr24Bytes(this.Configuration, row, pixelSpan, width);
}
@ -700,7 +700,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
where TPixel : unmanaged, IPixel<TPixel>
{
this.currentStream.Read(row);
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(this.Configuration, row, pixelSpan, width);
}

8
src/ImageSharp/Formats/Tga/TgaEncoderCore.cs

@ -276,7 +276,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8Bytes(
this.configuration,
pixelSpan,
@ -300,7 +300,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra5551Bytes(
this.configuration,
pixelSpan,
@ -324,7 +324,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgr24Bytes(
this.configuration,
pixelSpan,
@ -348,7 +348,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(
this.configuration,
pixelSpan,

2
src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs

@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
int offset = 0;
for (int y = 0; y < pixelBuffer.Height; y++)
{
Span<Rgb24> pixelRowSpan = pixelBuffer.GetRowSpan(y);
Span<Rgb24> pixelRowSpan = pixelBuffer.DangerousGetRowSpan(y);
Span<byte> rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan);
rgbBytes.CopyTo(buffer.Slice(offset));
offset += rgbBytes.Length;

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs

@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs

@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs

@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs

@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs

@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
int byteCount = pixelRow.Length;
PixelOperations<TPixel>.Instance.FromL8Bytes(
this.configuration,

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs

@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
int value = bitReader.ReadBits(this.bitsPerSample0);

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs

@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
int index = bitReader.ReadBits(this.bitsPerSample0);

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs

@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs

@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs

@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
Span<byte> bufferSpan = buffer.AsSpan(bufferStartIdx);
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs

@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs

@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs

@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs

@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
var bgra = default(Bgra4444);
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y);
for (int x = left; x < left + width; x += 2)
{

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs

@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
int byteCount = pixelRow.Length * 3;
PixelOperations<TPixel>.Instance.FromRgb24Bytes(
this.configuration,

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs

@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs

@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor;

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs

@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor;

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs

@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs

@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs

@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs

@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs

@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
var l8 = default(L8);
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
byte intensity = (byte)(byte.MaxValue - data[offset++]);

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs

@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
int value = bitReader.ReadBits(this.bitsPerSample0);

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs

@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
Rgba32 rgba = this.converter.ConvertToRgba32(yData[offset], cbData[offset], crData[offset]);

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs

@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
Rgba32 rgba = this.converter.ConvertToRgba32(ycbcrData[offset], ycbcrData[offset + 1], ycbcrData[offset + 2]);

26
src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs

@ -42,18 +42,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
// Special case for T4BitCompressor.
int stripPixels = width * height;
this.pixelsAsGray ??= this.MemoryAllocator.Allocate<byte>(stripPixels);
Span<byte> pixelAsGraySpan = this.pixelsAsGray.GetSpan();
int lastRow = y + height;
int grayRowIdx = 0;
for (int row = y; row < lastRow; row++)
this.imageBlackWhite.ProcessPixelRows(accessor =>
{
Span<TPixel> pixelsBlackWhiteRow = this.imageBlackWhite.GetPixelRowSpan(row);
Span<byte> pixelAsGrayRow = pixelAsGraySpan.Slice(grayRowIdx * width, width);
PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGrayRow, width);
grayRowIdx++;
}
Span<byte> pixelAsGraySpan = this.pixelsAsGray.GetSpan();
int lastRow = y + height;
int grayRowIdx = 0;
for (int row = y; row < lastRow; row++)
{
Span<TPixel> pixelsBlackWhiteRow = accessor.GetRowSpan(row);
Span<byte> pixelAsGrayRow = pixelAsGraySpan.Slice(grayRowIdx * width, width);
PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGrayRow, width);
grayRowIdx++;
}
compressor.CompressStrip(pixelAsGraySpan.Slice(0, stripPixels), height);
compressor.CompressStrip(pixelAsGraySpan.Slice(0, stripPixels), height);
});
}
else
{
@ -65,6 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
Span<byte> rows = this.bitStrip.Slice(0, bytesPerStrip);
rows.Clear();
Buffer2D<TPixel> blackWhiteBuffer = this.imageBlackWhite.Frames.RootFrame.PixelBuffer;
int outputRowIdx = 0;
int lastRow = y + height;
@ -73,7 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
int bitIndex = 0;
int byteIndex = 0;
Span<byte> outputRow = rows.Slice(outputRowIdx * this.BytesPerRow);
Span<TPixel> pixelsBlackWhiteRow = this.imageBlackWhite.GetPixelRowSpan(row);
Span<TPixel> pixelsBlackWhiteRow = blackWhiteBuffer.DangerousGetRowSpan(row);
PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGraySpan, width);
for (int x = 0; x < this.Image.Width; x++)
{

2
src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs

@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
int stripPixelsRowIdx = 0;
for (int row = y; row < lastRow; row++)
{
Span<TPixel> stripPixelsRow = this.Image.PixelBuffer.GetRowSpan(row);
Span<TPixel> stripPixelsRow = this.Image.PixelBuffer.DangerousGetRowSpan(row);
stripPixelsRow.CopyTo(stripPixels.Slice(stripPixelsRowIdx * width, width));
stripPixelsRowIdx++;
}

4
src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs

@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
int lastRow = y + height;
for (int row = y; row < lastRow; row++)
{
ReadOnlySpan<byte> indexedPixelRow = this.quantizedImage.GetPixelRowSpan(row);
ReadOnlySpan<byte> indexedPixelRow = this.quantizedImage.DangerousGetRowSpan(row);
int idxPixels = 0;
for (int x = 0; x < halfWidth; x++)
{
@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
int indexedPixelsRowIdx = 0;
for (int row = y; row < lastRow; row++)
{
ReadOnlySpan<byte> indexedPixelRow = this.quantizedImage.GetPixelRowSpan(row);
ReadOnlySpan<byte> indexedPixelRow = this.quantizedImage.DangerousGetRowSpan(row);
indexedPixelRow.CopyTo(indexedPixels.Slice(indexedPixelsRowIdx * width, width));
indexedPixelsRowIdx++;
}

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

@ -192,7 +192,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
public bool UseCrossColorTransform { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to use the substract green transform.
/// Gets or sets a value indicating whether to use the subtract green transform.
/// </summary>
public bool UseSubtractGreenTransform { get; set; }
@ -407,13 +407,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
private bool ConvertPixelsToBgra<TPixel>(Image<TPixel> image, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> imageBuffer = image.Frames.RootFrame.PixelBuffer;
bool nonOpaque = false;
Span<uint> bgra = this.Bgra.GetSpan();
Span<byte> bgraBytes = MemoryMarshal.Cast<uint, byte>(bgra);
int widthBytes = width * 4;
for (int y = 0; y < height; y++)
{
Span<TPixel> rowSpan = image.GetPixelRowSpan(y);
Span<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(y);
Span<byte> rowBytes = bgraBytes.Slice(y * widthBytes, widthBytes);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(this.configuration, rowSpan, rowBytes, width);
if (!nonOpaque)
@ -1049,7 +1050,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
return EntropyIx.Palette;
}
using IMemoryOwner<uint> histoBuffer = this.memoryAllocator.Allocate<uint>((int)HistoIx.HistoTotal * 256);
using IMemoryOwner<uint> histoBuffer = this.memoryAllocator.Allocate<uint>((int)HistoIx.HistoTotal * 256, AllocationOptions.Clean);
Span<uint> histo = histoBuffer.Memory.Span;
uint pixPrev = bgra[0]; // Skip the first pixel.
ReadOnlySpan<uint> prevRow = null;

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

@ -196,7 +196,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
for (int y = 0; y < height; y++)
{
Span<byte> rowAsBytes = pixelDataAsBytes.Slice(y * bytesPerRow, bytesPerRow);
Span<TPixel> pixelRow = pixels.GetRowSpan(y);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.configuration,
rowAsBytes.Slice(0, bytesPerRow),

788
src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs

@ -18,12 +18,24 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
#if SUPPORTS_RUNTIME_INTRINSICS
private static readonly Vector128<byte> Mean16x4Mask = Vector128.Create((short)0x00ff).AsByte();
private static readonly Vector128<byte> SignBit = Vector128.Create((byte)0x80);
private static readonly Vector128<sbyte> Three = Vector128.Create((byte)3).AsSByte();
private static readonly Vector128<short> FourShort = Vector128.Create((short)4);
private static readonly Vector128<sbyte> FourSByte = Vector128.Create((byte)4).AsSByte();
private static readonly Vector128<sbyte> Nine = Vector128.Create((short)0x0900).AsSByte();
private static readonly Vector128<sbyte> SixtyThree = Vector128.Create((short)63).AsSByte();
private static readonly Vector128<sbyte> SixtyFour = Vector128.Create((byte)64).AsSByte();
private static readonly Vector128<short> K1 = Vector128.Create((short)20091);
private static readonly Vector128<short> K2 = Vector128.Create((short)-30068);
private static readonly Vector128<short> Four = Vector128.Create((short)4);
#endif
// Note: method name in libwebp reference implementation is called VP8SSE16x16.
@ -908,7 +920,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Horizontal pass and subsequent transpose.
// First pass, c and d calculations are longer because of the "trick" multiplications.
Vector128<short> dc = Sse2.Add(t0.AsInt16(), Four);
Vector128<short> dc = Sse2.Add(t0.AsInt16(), FourShort);
a = Sse2.Add(dc, t2.AsInt16());
b = Sse2.Subtract(dc, t2.AsInt16());
@ -1029,7 +1041,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Horizontal pass and subsequent transpose.
// First pass, c and d calculations are longer because of the "trick" multiplications.
Vector128<short> dc = Sse2.Add(t0.AsInt16(), Four);
Vector128<short> dc = Sse2.Add(t0.AsInt16(), FourShort);
a = Sse2.Add(dc, t2.AsInt16());
b = Sse2.Subtract(dc, t2.AsInt16());
@ -1207,71 +1219,292 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Simple In-loop filtering (Paragraph 15.2)
public static void SimpleVFilter16(Span<byte> p, int offset, int stride, int thresh)
{
int thresh2 = (2 * thresh) + 1;
int end = 16 + offset;
for (int i = offset; i < end; i++)
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse2.IsSupported)
{
if (NeedsFilter(p, i, stride, thresh2))
// Load.
ref byte pRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(p), offset);
Vector128<byte> p1 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Subtract(ref pRef, 2 * stride));
Vector128<byte> p0 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Subtract(ref pRef, stride));
Vector128<byte> q0 = Unsafe.As<byte, Vector128<byte>>(ref pRef);
Vector128<byte> q1 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref pRef, stride));
DoFilter2Sse2(ref p1, ref p0, ref q0, ref q1, thresh);
// Store.
ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(p), offset);
Unsafe.As<byte, Vector128<sbyte>>(ref Unsafe.Subtract(ref outputRef, stride)) = p0.AsSByte();
Unsafe.As<byte, Vector128<sbyte>>(ref outputRef) = q0.AsSByte();
}
else
#endif
{
int thresh2 = (2 * thresh) + 1;
int end = 16 + offset;
for (int i = offset; i < end; i++)
{
DoFilter2(p, i, stride);
if (NeedsFilter(p, i, stride, thresh2))
{
DoFilter2(p, i, stride);
}
}
}
}
public static void SimpleHFilter16(Span<byte> p, int offset, int stride, int thresh)
{
int thresh2 = (2 * thresh) + 1;
int end = offset + (16 * stride);
for (int i = offset; i < end; i += stride)
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse2.IsSupported)
{
if (NeedsFilter(p, i, 1, thresh2))
// Beginning of p1
ref byte pRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(p), offset - 2);
Load16x4(ref pRef, ref Unsafe.Add(ref pRef, 8 * stride), stride, out Vector128<byte> p1, out Vector128<byte> p0, out Vector128<byte> q0, out Vector128<byte> q1);
DoFilter2Sse2(ref p1, ref p0, ref q0, ref q1, thresh);
Store16x4(p1, p0, q0, q1, ref pRef, ref Unsafe.Add(ref pRef, 8 * stride), stride);
}
else
#endif
{
int thresh2 = (2 * thresh) + 1;
int end = offset + (16 * stride);
for (int i = offset; i < end; i += stride)
{
DoFilter2(p, i, 1);
if (NeedsFilter(p, i, 1, thresh2))
{
DoFilter2(p, i, 1);
}
}
}
}
public static void SimpleVFilter16i(Span<byte> p, int offset, int stride, int thresh)
{
for (int k = 3; k > 0; --k)
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse2.IsSupported)
{
offset += 4 * stride;
SimpleVFilter16(p, offset, stride, thresh);
for (int k = 3; k > 0; k--)
{
offset += 4 * stride;
SimpleVFilter16(p, offset, stride, thresh);
}
}
else
#endif
{
for (int k = 3; k > 0; k--)
{
offset += 4 * stride;
SimpleVFilter16(p, offset, stride, thresh);
}
}
}
public static void SimpleHFilter16i(Span<byte> p, int offset, int stride, int thresh)
{
for (int k = 3; k > 0; --k)
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse2.IsSupported)
{
offset += 4;
SimpleHFilter16(p, offset, stride, thresh);
for (int k = 3; k > 0; k--)
{
offset += 4;
SimpleHFilter16(p, offset, stride, thresh);
}
}
else
#endif
{
for (int k = 3; k > 0; k--)
{
offset += 4;
SimpleHFilter16(p, offset, stride, thresh);
}
}
}
// On macroblock edges.
[MethodImpl(InliningOptions.ShortMethod)]
public static void VFilter16(Span<byte> p, int offset, int stride, int thresh, int ithresh, int hevThresh)
=> FilterLoop26(p, offset, stride, 1, 16, thresh, ithresh, hevThresh);
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse2.IsSupported)
{
ref byte pRef = ref MemoryMarshal.GetReference(p);
Vector128<byte> t1 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref pRef, offset - (4 * stride)));
Vector128<byte> p2 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref pRef, offset - (3 * stride)));
Vector128<byte> p1 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref pRef, offset - (2 * stride)));
Vector128<byte> p0 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref pRef, offset - stride));
Vector128<byte> mask = Abs(p1, p0);
mask = Sse2.Max(mask, Abs(t1, p2));
mask = Sse2.Max(mask, Abs(p2, p1));
Vector128<byte> q0 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref pRef, offset));
Vector128<byte> q1 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref pRef, offset + stride));
Vector128<byte> q2 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref pRef, offset + (2 * stride)));
t1 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref pRef, offset + (3 * stride)));
mask = Sse2.Max(mask, Abs(q1, q0));
mask = Sse2.Max(mask, Abs(t1, q2));
mask = Sse2.Max(mask, Abs(q2, q1));
ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask);
DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh);
// Store.
ref byte outputRef = ref MemoryMarshal.GetReference(p);
Unsafe.As<byte, Vector128<int>>(ref Unsafe.Add(ref outputRef, offset - (3 * stride))) = p2.AsInt32();
Unsafe.As<byte, Vector128<int>>(ref Unsafe.Add(ref outputRef, offset - (2 * stride))) = p1.AsInt32();
Unsafe.As<byte, Vector128<int>>(ref Unsafe.Add(ref outputRef, offset - stride)) = p0.AsInt32();
Unsafe.As<byte, Vector128<int>>(ref Unsafe.Add(ref outputRef, offset)) = q0.AsInt32();
Unsafe.As<byte, Vector128<int>>(ref Unsafe.Add(ref outputRef, offset + stride)) = q1.AsInt32();
Unsafe.As<byte, Vector128<int>>(ref Unsafe.Add(ref outputRef, offset + (2 * stride))) = q2.AsInt32();
}
else
#endif
{
FilterLoop26(p, offset, stride, 1, 16, thresh, ithresh, hevThresh);
}
}
[MethodImpl(InliningOptions.ShortMethod)]
public static void HFilter16(Span<byte> p, int offset, int stride, int thresh, int ithresh, int hevThresh)
=> FilterLoop26(p, offset, 1, stride, 16, thresh, ithresh, hevThresh);
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse2.IsSupported)
{
ref byte pRef = ref MemoryMarshal.GetReference(p);
ref byte bRef = ref Unsafe.Add(ref pRef, offset - 4);
Load16x4(ref bRef, ref Unsafe.Add(ref bRef, 8 * stride), stride, out Vector128<byte> p3, out Vector128<byte> p2, out Vector128<byte> p1, out Vector128<byte> p0);
Vector128<byte> mask = Abs(p1, p0);
mask = Sse2.Max(mask, Abs(p3, p2));
mask = Sse2.Max(mask, Abs(p2, p1));
Load16x4(ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride, out Vector128<byte> q0, out Vector128<byte> q1, out Vector128<byte> q2, out Vector128<byte> q3);
mask = Sse2.Max(mask, Abs(q1, q0));
mask = Sse2.Max(mask, Abs(q3, q2));
mask = Sse2.Max(mask, Abs(q2, q1));
ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask);
DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh);
Store16x4(p3, p2, p1, p0, ref bRef, ref Unsafe.Add(ref bRef, 8 * stride), stride);
Store16x4(q0, q1, q2, q3, ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride);
}
else
#endif
{
FilterLoop26(p, offset, 1, stride, 16, thresh, ithresh, hevThresh);
}
}
public static void VFilter16i(Span<byte> p, int offset, int stride, int thresh, int ithresh, int hevThresh)
{
for (int k = 3; k > 0; --k)
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse2.IsSupported)
{
offset += 4 * stride;
FilterLoop24(p, offset, stride, 1, 16, thresh, ithresh, hevThresh);
ref byte pRef = ref MemoryMarshal.GetReference(p);
Vector128<byte> p3 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref pRef, offset));
Vector128<byte> p2 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref pRef, offset + stride));
Vector128<byte> p1 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref pRef, offset + (2 * stride)));
Vector128<byte> p0 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref pRef, offset + (3 * stride)));
for (int k = 3; k > 0; k--)
{
// Beginning of p1.
Span<byte> b = p.Slice(offset + (2 * stride));
offset += 4 * stride;
Vector128<byte> mask = Abs(p0, p1);
mask = Sse2.Max(mask, Abs(p3, p2));
mask = Sse2.Max(mask, Abs(p2, p1));
p3 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref pRef, offset));
p2 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref pRef, offset + stride));
Vector128<byte> tmp1 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref pRef, offset + (2 * stride)));
Vector128<byte> tmp2 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref pRef, offset + (3 * stride)));
mask = Sse2.Max(mask, Abs(tmp1, tmp2));
mask = Sse2.Max(mask, Abs(p3, p2));
mask = Sse2.Max(mask, Abs(p2, tmp1));
// p3 and p2 are not just temporary variables here: they will be
// re-used for next span. And q2/q3 will become p1/p0 accordingly.
ComplexMask(p1, p0, p3, p2, thresh, ithresh, ref mask);
DoFilter4Sse2(ref p1, ref p0, ref p3, ref p2, mask, hevThresh);
// Store.
ref byte outputRef = ref MemoryMarshal.GetReference(b);
Unsafe.As<byte, Vector128<int>>(ref outputRef) = p1.AsInt32();
Unsafe.As<byte, Vector128<int>>(ref Unsafe.Add(ref outputRef, stride)) = p0.AsInt32();
Unsafe.As<byte, Vector128<int>>(ref Unsafe.Add(ref outputRef, stride * 2)) = p3.AsInt32();
Unsafe.As<byte, Vector128<int>>(ref Unsafe.Add(ref outputRef, stride * 3)) = p2.AsInt32();
// Rotate samples.
p1 = tmp1;
p0 = tmp2;
}
}
else
#endif
{
for (int k = 3; k > 0; k--)
{
offset += 4 * stride;
FilterLoop24(p, offset, stride, 1, 16, thresh, ithresh, hevThresh);
}
}
}
public static void HFilter16i(Span<byte> p, int offset, int stride, int thresh, int ithresh, int hevThresh)
{
for (int k = 3; k > 0; --k)
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse2.IsSupported)
{
offset += 4;
FilterLoop24(p, offset, 1, stride, 16, thresh, ithresh, hevThresh);
ref byte pRef = ref MemoryMarshal.GetReference(p);
Load16x4(ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride, out Vector128<byte> p3, out Vector128<byte> p2, out Vector128<byte> p1, out Vector128<byte> p0);
Vector128<byte> mask;
for (int k = 3; k > 0; k--)
{
// Beginning of p1.
ref byte bRef = ref Unsafe.Add(ref pRef, offset + 2);
// Beginning of q0 (and next span).
offset += 4;
// Compute partial mask.
mask = Abs(p1, p0);
mask = Sse2.Max(mask, Abs(p3, p2));
mask = Sse2.Max(mask, Abs(p2, p1));
Load16x4(ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride, out p3, out p2, out Vector128<byte> tmp1, out Vector128<byte> tmp2);
mask = Sse2.Max(mask, Abs(tmp1, tmp2));
mask = Sse2.Max(mask, Abs(p3, p2));
mask = Sse2.Max(mask, Abs(p2, tmp1));
ComplexMask(p1, p0, p3, p2, thresh, ithresh, ref mask);
DoFilter4Sse2(ref p1, ref p0, ref p3, ref p2, mask, hevThresh);
Store16x4(p1, p0, p3, p2, ref bRef, ref Unsafe.Add(ref bRef, 8 * stride), stride);
// Rotate samples.
p1 = tmp1;
p0 = tmp2;
}
}
else
#endif
{
for (int k = 3; k > 0; k--)
{
offset += 4;
FilterLoop24(p, offset, 1, stride, 16, thresh, ithresh, hevThresh);
}
}
}
@ -1279,31 +1512,167 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
[MethodImpl(InliningOptions.ShortMethod)]
public static void VFilter8(Span<byte> u, Span<byte> v, int offset, int stride, int thresh, int ithresh, int hevThresh)
{
FilterLoop26(u, offset, stride, 1, 8, thresh, ithresh, hevThresh);
FilterLoop26(v, offset, stride, 1, 8, thresh, ithresh, hevThresh);
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse2.IsSupported)
{
// Load uv h-edges.
ref byte uRef = ref MemoryMarshal.GetReference(u);
ref byte vRef = ref MemoryMarshal.GetReference(v);
Vector128<byte> t1 = LoadUvEdge(ref uRef, ref vRef, offset - (4 * stride));
Vector128<byte> p2 = LoadUvEdge(ref uRef, ref vRef, offset - (3 * stride));
Vector128<byte> p1 = LoadUvEdge(ref uRef, ref vRef, offset - (2 * stride));
Vector128<byte> p0 = LoadUvEdge(ref uRef, ref vRef, offset - stride);
Vector128<byte> mask = Abs(p1, p0);
mask = Sse2.Max(mask, Abs(t1, p2));
mask = Sse2.Max(mask, Abs(p2, p1));
Vector128<byte> q0 = LoadUvEdge(ref uRef, ref vRef, offset);
Vector128<byte> q1 = LoadUvEdge(ref uRef, ref vRef, offset + stride);
Vector128<byte> q2 = LoadUvEdge(ref uRef, ref vRef, offset + (2 * stride));
t1 = LoadUvEdge(ref uRef, ref vRef, offset + (3 * stride));
mask = Sse2.Max(mask, Abs(q1, q0));
mask = Sse2.Max(mask, Abs(t1, q2));
mask = Sse2.Max(mask, Abs(q2, q1));
ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask);
DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh);
// Store.
StoreUv(p2, ref uRef, ref vRef, offset - (3 * stride));
StoreUv(p1, ref uRef, ref vRef, offset - (2 * stride));
StoreUv(p0, ref uRef, ref vRef, offset - stride);
StoreUv(q0, ref uRef, ref vRef, offset);
StoreUv(q1, ref uRef, ref vRef, offset + (1 * stride));
StoreUv(q2, ref uRef, ref vRef, offset + (2 * stride));
}
else
#endif
{
FilterLoop26(u, offset, stride, 1, 8, thresh, ithresh, hevThresh);
FilterLoop26(v, offset, stride, 1, 8, thresh, ithresh, hevThresh);
}
}
[MethodImpl(InliningOptions.ShortMethod)]
public static void HFilter8(Span<byte> u, Span<byte> v, int offset, int stride, int thresh, int ithresh, int hevThresh)
{
FilterLoop26(u, offset, 1, stride, 8, thresh, ithresh, hevThresh);
FilterLoop26(v, offset, 1, stride, 8, thresh, ithresh, hevThresh);
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse2.IsSupported)
{
ref byte uRef = ref MemoryMarshal.GetReference(u);
ref byte vRef = ref MemoryMarshal.GetReference(v);
Load16x4(ref Unsafe.Add(ref uRef, offset - 4), ref Unsafe.Add(ref vRef, offset - 4), stride, out Vector128<byte> p3, out Vector128<byte> p2, out Vector128<byte> p1, out Vector128<byte> p0);
Vector128<byte> mask = Abs(p1, p0);
mask = Sse2.Max(mask, Abs(p3, p2));
mask = Sse2.Max(mask, Abs(p2, p1));
Load16x4(ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride, out Vector128<byte> q0, out Vector128<byte> q1, out Vector128<byte> q2, out Vector128<byte> q3);
mask = Sse2.Max(mask, Abs(q1, q0));
mask = Sse2.Max(mask, Abs(q3, q2));
mask = Sse2.Max(mask, Abs(q2, q1));
ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask);
DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh);
Store16x4(p3, p2, p1, p0, ref Unsafe.Add(ref uRef, offset - 4), ref Unsafe.Add(ref vRef, offset - 4), stride);
Store16x4(q0, q1, q2, q3, ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride);
}
else
#endif
{
FilterLoop26(u, offset, 1, stride, 8, thresh, ithresh, hevThresh);
FilterLoop26(v, offset, 1, stride, 8, thresh, ithresh, hevThresh);
}
}
[MethodImpl(InliningOptions.ShortMethod)]
public static void VFilter8i(Span<byte> u, Span<byte> v, int offset, int stride, int thresh, int ithresh, int hevThresh)
{
int offset4mulstride = offset + (4 * stride);
FilterLoop24(u, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh);
FilterLoop24(v, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh);
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse2.IsSupported)
{
// Load uv h-edges.
ref byte uRef = ref MemoryMarshal.GetReference(u);
ref byte vRef = ref MemoryMarshal.GetReference(v);
Vector128<byte> t2 = LoadUvEdge(ref uRef, ref vRef, offset);
Vector128<byte> t1 = LoadUvEdge(ref uRef, ref vRef, offset + stride);
Vector128<byte> p1 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 2));
Vector128<byte> p0 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 3));
Vector128<byte> mask = Abs(p1, p0);
mask = Sse2.Max(mask, Abs(t2, t1));
mask = Sse2.Max(mask, Abs(t1, p1));
offset += 4 * stride;
Vector128<byte> q0 = LoadUvEdge(ref uRef, ref vRef, offset);
Vector128<byte> q1 = LoadUvEdge(ref uRef, ref vRef, offset + stride);
t1 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 2));
t2 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 3));
mask = Sse2.Max(mask, Abs(q1, q0));
mask = Sse2.Max(mask, Abs(t2, t1));
mask = Sse2.Max(mask, Abs(t1, q1));
ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask);
DoFilter4Sse2(ref p1, ref p0, ref q0, ref q1, mask, hevThresh);
// Store.
StoreUv(p1, ref uRef, ref vRef, offset + (-2 * stride));
StoreUv(p0, ref uRef, ref vRef, offset + (-1 * stride));
StoreUv(q0, ref uRef, ref vRef, offset);
StoreUv(q1, ref uRef, ref vRef, offset + stride);
}
else
#endif
{
int offset4mulstride = offset + (4 * stride);
FilterLoop24(u, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh);
FilterLoop24(v, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh);
}
}
[MethodImpl(InliningOptions.ShortMethod)]
public static void HFilter8i(Span<byte> u, Span<byte> v, int offset, int stride, int thresh, int ithresh, int hevThresh)
{
int offsetPlus4 = offset + 4;
FilterLoop24(u, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh);
FilterLoop24(v, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh);
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse2.IsSupported)
{
ref byte uRef = ref MemoryMarshal.GetReference(u);
ref byte vRef = ref MemoryMarshal.GetReference(v);
Load16x4(ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride, out Vector128<byte> t2, out Vector128<byte> t1, out Vector128<byte> p1, out Vector128<byte> p0);
Vector128<byte> mask = Abs(p1, p0);
mask = Sse2.Max(mask, Abs(t2, t1));
mask = Sse2.Max(mask, Abs(t1, p1));
// Beginning of q0.
offset += 4;
Load16x4(ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride, out Vector128<byte> q0, out Vector128<byte> q1, out t1, out t2);
mask = Sse2.Max(mask, Abs(q1, q0));
mask = Sse2.Max(mask, Abs(t2, t1));
mask = Sse2.Max(mask, Abs(t1, q1));
ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask);
DoFilter4Sse2(ref p1, ref p0, ref q0, ref q1, mask, hevThresh);
// Beginning of p1.
offset -= 2;
Store16x4(p1, p0, q0, q1, ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride);
}
else
#endif
{
int offsetPlus4 = offset + 4;
FilterLoop24(u, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh);
FilterLoop24(v, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh);
}
}
public static void Mean16x4(Span<byte> input, Span<uint> dc)
@ -1460,6 +1829,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
}
}
// Applies filter on 2 pixels (p0 and q0)
private static void DoFilter2(Span<byte> p, int offset, int step)
{
// 4 pixels in, 2 pixels out.
@ -1474,6 +1844,143 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
p[offset] = WebpLookupTables.Clip1(q0 - a1);
}
#if SUPPORTS_RUNTIME_INTRINSICS
// Applies filter on 2 pixels (p0 and q0)
private static void DoFilter2Sse2(ref Vector128<byte> p1, ref Vector128<byte> p0, ref Vector128<byte> q0, ref Vector128<byte> q1, int thresh)
{
// Convert p1/q1 to byte (for GetBaseDelta).
Vector128<byte> p1s = Sse2.Xor(p1, SignBit);
Vector128<byte> q1s = Sse2.Xor(q1, SignBit);
Vector128<byte> mask = NeedsFilter(p1, p0, q0, q1, thresh);
// Flip sign.
p0 = Sse2.Xor(p0, SignBit);
q0 = Sse2.Xor(q0, SignBit);
Vector128<byte> a = GetBaseDelta(p1s.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1s.AsSByte()).AsByte();
// Mask filter values we don't care about.
a = Sse2.And(a, mask);
DoSimpleFilterSse2(ref p0, ref q0, a);
// Flip sign.
p0 = Sse2.Xor(p0, SignBit);
q0 = Sse2.Xor(q0, SignBit);
}
// Applies filter on 4 pixels (p1, p0, q0 and q1)
private static void DoFilter4Sse2(ref Vector128<byte> p1, ref Vector128<byte> p0, ref Vector128<byte> q0, ref Vector128<byte> q1, Vector128<byte> mask, int tresh)
{
// Compute hev mask.
Vector128<byte> notHev = GetNotHev(ref p1, ref p0, ref q0, ref q1, tresh);
// Convert to signed values.
p1 = Sse2.Xor(p1, SignBit);
p0 = Sse2.Xor(p0, SignBit);
q0 = Sse2.Xor(q0, SignBit);
q1 = Sse2.Xor(q1, SignBit);
Vector128<sbyte> t1 = Sse2.SubtractSaturate(p1.AsSByte(), q1.AsSByte()); // p1 - q1
t1 = Sse2.AndNot(notHev, t1.AsByte()).AsSByte(); // hev(p1 - q1)
Vector128<sbyte> t2 = Sse2.SubtractSaturate(q0.AsSByte(), p0.AsSByte()); // q0 - p0
t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 1 * (q0 - p0)
t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 2 * (q0 - p0)
t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 3 * (q0 - p0)
t1 = Sse2.And(t1.AsByte(), mask).AsSByte(); // mask filter values we don't care about.
t2 = Sse2.AddSaturate(t1, Three); // 3 * (q0 - p0) + hev(p1 - q1) + 3
Vector128<sbyte> t3 = Sse2.AddSaturate(t1, FourSByte); // 3 * (q0 - p0) + hev(p1 - q1) + 4
t2 = SignedShift8b(t2.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 3) >> 3
t3 = SignedShift8b(t3.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 4) >> 3
p0 = Sse2.AddSaturate(p0.AsSByte(), t2).AsByte(); // p0 += t2
q0 = Sse2.SubtractSaturate(q0.AsSByte(), t3).AsByte(); // q0 -= t3
p0 = Sse2.Xor(p0, SignBit);
q0 = Sse2.Xor(q0, SignBit);
// This is equivalent to signed (a + 1) >> 1 calculation.
t2 = Sse2.Add(t3, SignBit.AsSByte());
t3 = Sse2.Average(t2.AsByte(), Vector128<byte>.Zero).AsSByte();
t3 = Sse2.Subtract(t3, SixtyFour);
t3 = Sse2.And(notHev, t3.AsByte()).AsSByte(); // if !hev
q1 = Sse2.SubtractSaturate(q1.AsSByte(), t3).AsByte(); // q1 -= t3
p1 = Sse2.AddSaturate(p1.AsSByte(), t3).AsByte(); // p1 += t3
p1 = Sse2.Xor(p1.AsByte(), SignBit);
q1 = Sse2.Xor(q1.AsByte(), SignBit);
}
// Applies filter on 6 pixels (p2, p1, p0, q0, q1 and q2)
private static void DoFilter6Sse2(ref Vector128<byte> p2, ref Vector128<byte> p1, ref Vector128<byte> p0, ref Vector128<byte> q0, ref Vector128<byte> q1, ref Vector128<byte> q2, Vector128<byte> mask, int tresh)
{
// Compute hev mask.
Vector128<byte> notHev = GetNotHev(ref p1, ref p0, ref q0, ref q1, tresh);
// Convert to signed values.
p1 = Sse2.Xor(p1, SignBit);
p0 = Sse2.Xor(p0, SignBit);
q0 = Sse2.Xor(q0, SignBit);
q1 = Sse2.Xor(q1, SignBit);
p2 = Sse2.Xor(p2, SignBit);
q2 = Sse2.Xor(q2, SignBit);
Vector128<sbyte> a = GetBaseDelta(p1.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1.AsSByte());
// Do simple filter on pixels with hev.
Vector128<byte> m = Sse2.AndNot(notHev, mask);
Vector128<byte> f = Sse2.And(a.AsByte(), m);
DoSimpleFilterSse2(ref p0, ref q0, f);
// Do strong filter on pixels with not hev.
m = Sse2.And(notHev, mask);
f = Sse2.And(a.AsByte(), m);
Vector128<byte> flow = Sse2.UnpackLow(Vector128<byte>.Zero, f);
Vector128<byte> fhigh = Sse2.UnpackHigh(Vector128<byte>.Zero, f);
Vector128<short> f9Low = Sse2.MultiplyHigh(flow.AsInt16(), Nine.AsInt16()); // Filter (lo) * 9
Vector128<short> f9High = Sse2.MultiplyHigh(fhigh.AsInt16(), Nine.AsInt16()); // Filter (hi) * 9
Vector128<short> a2Low = Sse2.Add(f9Low, SixtyThree.AsInt16()); // Filter * 9 + 63
Vector128<short> a2High = Sse2.Add(f9High, SixtyThree.AsInt16()); // Filter * 9 + 63
Vector128<short> a1Low = Sse2.Add(a2Low, f9Low); // Filter * 18 + 63
Vector128<short> a1High = Sse2.Add(a2High, f9High); // // Filter * 18 + 63
Vector128<short> a0Low = Sse2.Add(a1Low, f9Low); // Filter * 27 + 63
Vector128<short> a0High = Sse2.Add(a1High, f9High); // Filter * 27 + 63
Update2Pixels(ref p2, ref q2, a2Low, a2High);
Update2Pixels(ref p1, ref q1, a1Low, a1High);
Update2Pixels(ref p0, ref q0, a0Low, a0High);
}
private static void DoSimpleFilterSse2(ref Vector128<byte> p0, ref Vector128<byte> q0, Vector128<byte> fl)
{
Vector128<sbyte> v3 = Sse2.AddSaturate(fl.AsSByte(), Three);
Vector128<sbyte> v4 = Sse2.AddSaturate(fl.AsSByte(), FourSByte);
v4 = SignedShift8b(v4.AsByte()).AsSByte(); // v4 >> 3
v3 = SignedShift8b(v3.AsByte()).AsSByte(); // v3 >> 3
q0 = Sse2.SubtractSaturate(q0.AsSByte(), v4).AsByte(); // q0 -= v4
p0 = Sse2.AddSaturate(p0.AsSByte(), v3).AsByte(); // p0 += v3
}
private static Vector128<byte> GetNotHev(ref Vector128<byte> p1, ref Vector128<byte> p0, ref Vector128<byte> q0, ref Vector128<byte> q1, int hevThresh)
{
Vector128<byte> t1 = Abs(p1, p0);
Vector128<byte> t2 = Abs(q1, q0);
var h = Vector128.Create((byte)hevThresh);
Vector128<byte> tMax = Sse2.Max(t1, t2);
Vector128<byte> tMaxH = Sse2.SubtractSaturate(tMax, h);
// not_hev <= t1 && not_hev <= t2
return Sse2.CompareEqual(tMaxH, Vector128<byte>.Zero);
}
#endif
// Applies filter on 4 pixels (p1, p0, q0 and q1)
private static void DoFilter4(Span<byte> p, int offset, int step)
{
// 4 pixels in, 4 pixels out.
@ -1492,6 +1999,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
p[offset + step] = WebpLookupTables.Clip1(q1 - a3);
}
// Applies filter on 6 pixels (p2, p1, p0, q0, q1 and q2)
private static void DoFilter6(Span<byte> p, int offset, int step)
{
// 6 pixels in, 6 pixels out.
@ -1550,6 +2058,201 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
WebpLookupTables.Abs0(q2 - q1) <= it && WebpLookupTables.Abs0(q1 - q0) <= it;
}
#if SUPPORTS_RUNTIME_INTRINSICS
private static Vector128<byte> NeedsFilter(Vector128<byte> p1, Vector128<byte> p0, Vector128<byte> q0, Vector128<byte> q1, int thresh)
{
var mthresh = Vector128.Create((byte)thresh);
Vector128<byte> t1 = Abs(p1, q1); // abs(p1 - q1)
var fe = Vector128.Create((byte)0xFE);
Vector128<byte> t2 = Sse2.And(t1, fe); // set lsb of each byte to zero.
Vector128<short> t3 = Sse2.ShiftRightLogical(t2.AsInt16(), 1); // abs(p1 - q1) / 2
Vector128<byte> t4 = Abs(p0, q0); // abs(p0 - q0)
Vector128<byte> t5 = Sse2.AddSaturate(t4, t4); // abs(p0 - q0) * 2
Vector128<byte> t6 = Sse2.AddSaturate(t5.AsByte(), t3.AsByte()); // abs(p0-q0)*2 + abs(p1-q1)/2
Vector128<byte> t7 = Sse2.SubtractSaturate(t6, mthresh.AsByte()); // mask <= m_thresh
return Sse2.CompareEqual(t7, Vector128<byte>.Zero);
}
private static void Load16x4(ref byte r0, ref byte r8, int stride, out Vector128<byte> p1, out Vector128<byte> p0, out Vector128<byte> q0, out Vector128<byte> q1)
{
// Assume the pixels around the edge (|) are numbered as follows
// 00 01 | 02 03
// 10 11 | 12 13
// ... | ...
// e0 e1 | e2 e3
// f0 f1 | f2 f3
//
// r0 is pointing to the 0th row (00)
// r8 is pointing to the 8th row (80)
// Load
// p1 = 71 61 51 41 31 21 11 01 70 60 50 40 30 20 10 00
// q0 = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02
// p0 = f1 e1 d1 c1 b1 a1 91 81 f0 e0 d0 c0 b0 a0 90 80
// q1 = f3 e3 d3 c3 b3 a3 93 83 f2 e2 d2 c2 b2 a2 92 82
Load8x4(ref r0, stride, out Vector128<byte> t1, out Vector128<byte> t2);
Load8x4(ref r8, stride, out p0, out q1);
// p1 = f0 e0 d0 c0 b0 a0 90 80 70 60 50 40 30 20 10 00
// p0 = f1 e1 d1 c1 b1 a1 91 81 71 61 51 41 31 21 11 01
// q0 = f2 e2 d2 c2 b2 a2 92 82 72 62 52 42 32 22 12 02
// q1 = f3 e3 d3 c3 b3 a3 93 83 73 63 53 43 33 23 13 03
p1 = Sse2.UnpackLow(t1.AsInt64(), p0.AsInt64()).AsByte();
p0 = Sse2.UnpackHigh(t1.AsInt64(), p0.AsInt64()).AsByte();
q0 = Sse2.UnpackLow(t2.AsInt64(), q1.AsInt64()).AsByte();
q1 = Sse2.UnpackHigh(t2.AsInt64(), q1.AsInt64()).AsByte();
}
// Reads 8 rows across a vertical edge.
private static void Load8x4(ref byte bRef, int stride, out Vector128<byte> p, out Vector128<byte> q)
{
// A0 = 63 62 61 60 23 22 21 20 43 42 41 40 03 02 01 00
// A1 = 73 72 71 70 33 32 31 30 53 52 51 50 13 12 11 10
uint a00 = Unsafe.As<byte, uint>(ref Unsafe.Add(ref bRef, 6 * stride));
uint a01 = Unsafe.As<byte, uint>(ref Unsafe.Add(ref bRef, 2 * stride));
uint a02 = Unsafe.As<byte, uint>(ref Unsafe.Add(ref bRef, 4 * stride));
uint a03 = Unsafe.As<byte, uint>(ref Unsafe.Add(ref bRef, 0 * stride));
Vector128<byte> a0 = Vector128.Create(a03, a02, a01, a00).AsByte();
uint a10 = Unsafe.As<byte, uint>(ref Unsafe.Add(ref bRef, 7 * stride));
uint a11 = Unsafe.As<byte, uint>(ref Unsafe.Add(ref bRef, 3 * stride));
uint a12 = Unsafe.As<byte, uint>(ref Unsafe.Add(ref bRef, 5 * stride));
uint a13 = Unsafe.As<byte, uint>(ref Unsafe.Add(ref bRef, 1 * stride));
Vector128<byte> a1 = Vector128.Create(a13, a12, a11, a10).AsByte();
// B0 = 53 43 52 42 51 41 50 40 13 03 12 02 11 01 10 00
// B1 = 73 63 72 62 71 61 70 60 33 23 32 22 31 21 30 20
Vector128<sbyte> b0 = Sse2.UnpackLow(a0.AsSByte(), a1.AsSByte());
Vector128<sbyte> b1 = Sse2.UnpackHigh(a0.AsSByte(), a1.AsSByte());
// C0 = 33 23 13 03 32 22 12 02 31 21 11 01 30 20 10 00
// C1 = 73 63 53 43 72 62 52 42 71 61 51 41 70 60 50 40
Vector128<short> c0 = Sse2.UnpackLow(b0.AsInt16(), b1.AsInt16());
Vector128<short> c1 = Sse2.UnpackHigh(b0.AsInt16(), b1.AsInt16());
// *p = 71 61 51 41 31 21 11 01 70 60 50 40 30 20 10 00
// *q = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02
p = Sse2.UnpackLow(c0.AsInt32(), c1.AsInt32()).AsByte();
q = Sse2.UnpackHigh(c0.AsInt32(), c1.AsInt32()).AsByte();
}
// Transpose back and store
private static void Store16x4(Vector128<byte> p1, Vector128<byte> p0, Vector128<byte> q0, Vector128<byte> q1, ref byte r0Ref, ref byte r8Ref, int stride)
{
// p0 = 71 70 61 60 51 50 41 40 31 30 21 20 11 10 01 00
// p1 = f1 f0 e1 e0 d1 d0 c1 c0 b1 b0 a1 a0 91 90 81 80
Vector128<byte> p0s = Sse2.UnpackLow(p1, p0);
Vector128<byte> p1s = Sse2.UnpackHigh(p1, p0);
// q0 = 73 72 63 62 53 52 43 42 33 32 23 22 13 12 03 02
// q1 = f3 f2 e3 e2 d3 d2 c3 c2 b3 b2 a3 a2 93 92 83 82
Vector128<byte> q0s = Sse2.UnpackLow(q0, q1);
Vector128<byte> q1s = Sse2.UnpackHigh(q0, q1);
// p0 = 33 32 31 30 23 22 21 20 13 12 11 10 03 02 01 00
// q0 = 73 72 71 70 63 62 61 60 53 52 51 50 43 42 41 40
Vector128<byte> t1 = p0s;
p0s = Sse2.UnpackLow(t1.AsInt16(), q0s.AsInt16()).AsByte();
q0s = Sse2.UnpackHigh(t1.AsInt16(), q0s.AsInt16()).AsByte();
// p1 = b3 b2 b1 b0 a3 a2 a1 a0 93 92 91 90 83 82 81 80
// q1 = f3 f2 f1 f0 e3 e2 e1 e0 d3 d2 d1 d0 c3 c2 c1 c0
t1 = p1s;
p1s = Sse2.UnpackLow(t1.AsInt16(), q1s.AsInt16()).AsByte();
q1s = Sse2.UnpackHigh(t1.AsInt16(), q1s.AsInt16()).AsByte();
Store4x4(p0s, ref r0Ref, stride);
Store4x4(q0s, ref Unsafe.Add(ref r0Ref, 4 * stride), stride);
Store4x4(p1s, ref r8Ref, stride);
Store4x4(q1s, ref Unsafe.Add(ref r8Ref, 4 * stride), stride);
}
private static void Store4x4(Vector128<byte> x, ref byte dstRef, int stride)
{
int offset = 0;
for (int i = 0; i < 4; i++)
{
Unsafe.As<byte, int>(ref Unsafe.Add(ref dstRef, offset)) = Sse2.ConvertToInt32(x.AsInt32());
x = Sse2.ShiftRightLogical128BitLane(x, 4);
offset += stride;
}
}
[MethodImpl(InliningOptions.ShortMethod)]
private static Vector128<sbyte> GetBaseDelta(Vector128<sbyte> p1, Vector128<sbyte> p0, Vector128<sbyte> q0, Vector128<sbyte> q1)
{
// Beware of addition order, for saturation!
Vector128<sbyte> p1q1 = Sse2.SubtractSaturate(p1, q1); // p1 - q1
Vector128<sbyte> q0p0 = Sse2.SubtractSaturate(q0, p0); // q0 - p0
Vector128<sbyte> s1 = Sse2.AddSaturate(p1q1, q0p0); // p1 - q1 + 1 * (q0 - p0)
Vector128<sbyte> s2 = Sse2.AddSaturate(q0p0, s1); // p1 - q1 + 2 * (q0 - p0)
Vector128<sbyte> s3 = Sse2.AddSaturate(q0p0, s2); // p1 - q1 + 3 * (q0 - p0)
return s3;
}
// Shift each byte of "x" by 3 bits while preserving by the sign bit.
[MethodImpl(InliningOptions.ShortMethod)]
private static Vector128<sbyte> SignedShift8b(Vector128<byte> x)
{
Vector128<byte> low0 = Sse2.UnpackLow(Vector128<byte>.Zero, x);
Vector128<byte> high0 = Sse2.UnpackHigh(Vector128<byte>.Zero, x);
Vector128<short> low1 = Sse2.ShiftRightArithmetic(low0.AsInt16(), 3 + 8);
Vector128<short> high1 = Sse2.ShiftRightArithmetic(high0.AsInt16(), 3 + 8);
return Sse2.PackSignedSaturate(low1, high1);
}
[MethodImpl(InliningOptions.ShortMethod)]
private static void ComplexMask(Vector128<byte> p1, Vector128<byte> p0, Vector128<byte> q0, Vector128<byte> q1, int thresh, int ithresh, ref Vector128<byte> mask)
{
var it = Vector128.Create((byte)ithresh);
Vector128<byte> diff = Sse2.SubtractSaturate(mask, it);
Vector128<byte> threshMask = Sse2.CompareEqual(diff, Vector128<byte>.Zero);
Vector128<byte> filterMask = NeedsFilter(p1, p0, q0, q1, thresh);
mask = Sse2.And(threshMask, filterMask);
}
// Updates values of 2 pixels at MB edge during complex filtering.
// Update operations:
// q = q - delta and p = p + delta; where delta = [(a_hi >> 7), (a_lo >> 7)]
// Pixels 'pi' and 'qi' are int8_t on input, uint8_t on output (sign flip).
private static void Update2Pixels(ref Vector128<byte> pi, ref Vector128<byte> qi, Vector128<short> a0Low, Vector128<short> a0High)
{
Vector128<short> a1Low = Sse2.ShiftRightArithmetic(a0Low, 7);
Vector128<short> a1High = Sse2.ShiftRightArithmetic(a0High, 7);
Vector128<sbyte> delta = Sse2.PackSignedSaturate(a1Low, a1High);
pi = Sse2.AddSaturate(pi.AsSByte(), delta).AsByte();
qi = Sse2.SubtractSaturate(qi.AsSByte(), delta).AsByte();
pi = Sse2.Xor(pi, SignBit.AsByte());
qi = Sse2.Xor(qi, SignBit.AsByte());
}
[MethodImpl(InliningOptions.ShortMethod)]
private static Vector128<byte> LoadUvEdge(ref byte uRef, ref byte vRef, int offset)
{
var uVec = Vector128.Create(Unsafe.As<byte, long>(ref Unsafe.Add(ref uRef, offset)), 0);
var vVec = Vector128.Create(Unsafe.As<byte, long>(ref Unsafe.Add(ref vRef, offset)), 0);
return Sse2.UnpackLow(uVec, vVec).AsByte();
}
[MethodImpl(InliningOptions.ShortMethod)]
private static void StoreUv(Vector128<byte> x, ref byte uRef, ref byte vRef, int offset)
{
Unsafe.As<byte, Vector64<byte>>(ref Unsafe.Add(ref uRef, offset)) = x.GetLower();
Unsafe.As<byte, Vector64<byte>>(ref Unsafe.Add(ref vRef, offset)) = x.GetUpper();
}
// Compute abs(p - q) = subs(p - q) OR subs(q - p)
[MethodImpl(InliningOptions.ShortMethod)]
private static Vector128<byte> Abs(Vector128<byte> p, Vector128<byte> q)
=> Sse2.Or(Sse2.SubtractSaturate(q, p), Sse2.SubtractSaturate(p, q));
#endif
[MethodImpl(InliningOptions.ShortMethod)]
private static bool Hev(Span<byte> p, int offset, int step, int thresh)
{
@ -1594,14 +2297,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
}
[MethodImpl(InliningOptions.ShortMethod)]
private static void Memset(Span<byte> dst, byte value, int startIdx, int count)
{
int end = startIdx + count;
for (int i = startIdx; i < end; i++)
{
dst[i] = value;
}
}
private static void Memset(Span<byte> dst, byte value, int startIdx, int count) => dst.Slice(startIdx, count).Fill(value);
[MethodImpl(InliningOptions.ShortMethod)]
private static int Clamp255(int x) => x < 0 ? 0 : x > 255 ? 255 : x;

4
src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs

@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
for (int y = 0; y < height; y++)
{
Span<byte> row = pixelData.Slice(y * widthMul3, widthMul3);
Span<TPixel> decodedPixelRow = decodedPixels.GetRowSpan(y);
Span<TPixel> decodedPixelRow = decodedPixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromBgr24Bytes(
this.configuration,
row,
@ -146,7 +146,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
for (int y = 0; y < height; y++)
{
int yMulWidth = y * width;
Span<TPixel> decodedPixelRow = decodedPixels.GetRowSpan(y);
Span<TPixel> decodedPixelRow = decodedPixels.DangerousGetRowSpan(y);
for (int x = 0; x < width; x++)
{
int offset = yMulWidth + x;

11
src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs

@ -321,8 +321,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
public static void ConvertRgbToYuv<TPixel>(Image<TPixel> image, Configuration configuration, MemoryAllocator memoryAllocator, Span<byte> y, Span<byte> u, Span<byte> v)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
Buffer2D<TPixel> imageBuffer = image.Frames.RootFrame.PixelBuffer;
int width = imageBuffer.Width;
int height = imageBuffer.Height;
int uvWidth = (width + 1) >> 1;
// Temporary storage for accumulated R/G/B values during conversion to U/V.
@ -336,8 +337,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
int rowIndex;
for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2)
{
Span<TPixel> rowSpan = image.GetPixelRowSpan(rowIndex);
Span<TPixel> nextRowSpan = image.GetPixelRowSpan(rowIndex + 1);
Span<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex);
Span<TPixel> nextRowSpan = imageBuffer.DangerousGetRowSpan(rowIndex + 1);
PixelOperations<TPixel>.Instance.ToBgra32(configuration, rowSpan, bgraRow0);
PixelOperations<TPixel>.Instance.ToBgra32(configuration, nextRowSpan, bgraRow1);
@ -363,7 +364,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Extra last row.
if ((height & 1) != 0)
{
Span<TPixel> rowSpan = image.GetPixelRowSpan(rowIndex);
Span<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex);
PixelOperations<TPixel>.Instance.ToBgra32(configuration, rowSpan, bgraRow0);
ConvertRgbaToY(bgraRow0, y.Slice(rowIndex * width), width);

152
src/ImageSharp/ImageFrame{TPixel}.cs

@ -58,7 +58,11 @@ namespace SixLabors.ImageSharp
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D<TPixel>(width, height, AllocationOptions.Clean);
this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D<TPixel>(
width,
height,
configuration.PreferContiguousImageBuffers,
AllocationOptions.Clean);
}
/// <summary>
@ -87,7 +91,10 @@ namespace SixLabors.ImageSharp
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D<TPixel>(width, height);
this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D<TPixel>(
width,
height,
configuration.PreferContiguousImageBuffers);
this.Clear(backgroundColor);
}
@ -131,7 +138,10 @@ namespace SixLabors.ImageSharp
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(source, nameof(source));
this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D<TPixel>(source.PixelBuffer.Width, source.PixelBuffer.Height);
this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D<TPixel>(
source.PixelBuffer.Width,
source.PixelBuffer.Height,
configuration.PreferContiguousImageBuffers);
source.PixelBuffer.FastMemoryGroup.CopyTo(this.PixelBuffer.FastMemoryGroup);
}
@ -168,36 +178,128 @@ namespace SixLabors.ImageSharp
}
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the first pixel on that row.
/// Execute <paramref name="processPixels"/> to process image pixels in a safe and efficient manner.
/// </summary>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when row index is out of range.</exception>
public Span<TPixel> GetPixelRowSpan(int rowIndex)
/// <param name="processPixels">The <see cref="PixelAccessorAction{TPixel}"/> defining the pixel operations.</param>
public void ProcessPixelRows(PixelAccessorAction<TPixel> processPixels)
{
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex));
Guard.NotNull(processPixels, nameof(processPixels));
return this.PixelBuffer.GetRowSpan(rowIndex);
this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts();
try
{
var accessor = new PixelAccessor<TPixel>(this.PixelBuffer);
processPixels(accessor);
}
finally
{
this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts();
}
}
/// <summary>
/// Execute <paramref name="processPixels"/> to process pixels of multiple image frames in a safe and efficient manner.
/// </summary>
/// <param name="frame2">The second image frame.</param>
/// <param name="processPixels">The <see cref="PixelAccessorAction{TPixel, TPixel2}"/> defining the pixel operations.</param>
/// <typeparam name="TPixel2">The pixel type of the second image frame.</typeparam>
public void ProcessPixelRows<TPixel2>(
ImageFrame<TPixel2> frame2,
PixelAccessorAction<TPixel, TPixel2> processPixels)
where TPixel2 : unmanaged, IPixel<TPixel2>
{
Guard.NotNull(frame2, nameof(frame2));
Guard.NotNull(processPixels, nameof(processPixels));
this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts();
frame2.PixelBuffer.FastMemoryGroup.IncreaseRefCounts();
try
{
var accessor1 = new PixelAccessor<TPixel>(this.PixelBuffer);
var accessor2 = new PixelAccessor<TPixel2>(frame2.PixelBuffer);
processPixels(accessor1, accessor2);
}
finally
{
frame2.PixelBuffer.FastMemoryGroup.DecreaseRefCounts();
this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts();
}
}
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> in the source image's pixel format
/// Execute <paramref name="processPixels"/> to process pixels of multiple image frames in a safe and efficient manner.
/// </summary>
/// <param name="frame2">The second image frame.</param>
/// <param name="frame3">The third image frame.</param>
/// <param name="processPixels">The <see cref="PixelAccessorAction{TPixel, TPixel2, TPixel3}"/> defining the pixel operations.</param>
/// <typeparam name="TPixel2">The pixel type of the second image frame.</typeparam>
/// <typeparam name="TPixel3">The pixel type of the third image frame.</typeparam>
public void ProcessPixelRows<TPixel2, TPixel3>(
ImageFrame<TPixel2> frame2,
ImageFrame<TPixel3> frame3,
PixelAccessorAction<TPixel, TPixel2, TPixel3> processPixels)
where TPixel2 : unmanaged, IPixel<TPixel2>
where TPixel3 : unmanaged, IPixel<TPixel3>
{
Guard.NotNull(frame2, nameof(frame2));
Guard.NotNull(frame3, nameof(frame3));
Guard.NotNull(processPixels, nameof(processPixels));
this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts();
frame2.PixelBuffer.FastMemoryGroup.IncreaseRefCounts();
frame3.PixelBuffer.FastMemoryGroup.IncreaseRefCounts();
try
{
var accessor1 = new PixelAccessor<TPixel>(this.PixelBuffer);
var accessor2 = new PixelAccessor<TPixel2>(frame2.PixelBuffer);
var accessor3 = new PixelAccessor<TPixel3>(frame3.PixelBuffer);
processPixels(accessor1, accessor2, accessor3);
}
finally
{
frame3.PixelBuffer.FastMemoryGroup.DecreaseRefCounts();
frame2.PixelBuffer.FastMemoryGroup.DecreaseRefCounts();
this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts();
}
}
/// <summary>
/// Copy image pixels to <paramref name="destination"/>.
/// </summary>
/// <param name="destination">The <see cref="Span{TPixel}"/> to copy image pixels to.</param>
public void CopyPixelDataTo(Span<TPixel> destination) => this.GetPixelMemoryGroup().CopyTo(destination);
/// <summary>
/// Copy image pixels to <paramref name="destination"/>.
/// </summary>
/// <param name="destination">The <see cref="Span{T}"/> of <see cref="byte"/> to copy image pixels to.</param>
public void CopyPixelDataTo(Span<byte> destination) => this.GetPixelMemoryGroup().CopyTo(MemoryMarshal.Cast<byte, TPixel>(destination));
/// <summary>
/// Gets the representation of the pixels as a <see cref="Memory{T}"/> in the source image's pixel format
/// stored in row major order, if the backing buffer is contiguous.
/// <para />
/// To ensure the memory is contiguous, <see cref="Configuration.PreferContiguousImageBuffers"/> should be set
/// to true, preferably on a non-global configuration instance (not <see cref="Configuration.Default"/>).
/// <para />
/// WARNING: Disposing or leaking the underlying image while still working with the <paramref name="memory"/>'s <see cref="Span{T}"/>
/// might lead to memory corruption.
/// </summary>
/// <param name="span">The <see cref="Span{T}"/>.</param>
/// <returns>The <see cref="bool"/>.</returns>
public bool TryGetSinglePixelSpan(out Span<TPixel> span)
/// <param name="memory">The <see cref="Memory{T}"/> referencing the image buffer.</param>
/// <returns>The <see cref="bool"/> indicating the success.</returns>
public bool DangerousTryGetSinglePixelMemory(out Memory<TPixel> memory)
{
IMemoryGroup<TPixel> mg = this.GetPixelMemoryGroup();
if (mg.Count > 1)
{
span = default;
memory = default;
return false;
}
span = mg.Single().Span;
memory = mg.Single();
return true;
}
@ -310,7 +412,7 @@ namespace SixLabors.ImageSharp
}
var target = new ImageFrame<TPixel2>(configuration, this.Width, this.Height, this.Metadata.DeepClone());
var operation = new RowIntervalOperation<TPixel2>(this, target, configuration);
var operation = new RowIntervalOperation<TPixel2>(this.PixelBuffer, target.PixelBuffer, configuration);
ParallelRowIterator.IterateRowIntervals(
configuration,
@ -364,14 +466,14 @@ namespace SixLabors.ImageSharp
private readonly struct RowIntervalOperation<TPixel2> : IRowIntervalOperation
where TPixel2 : unmanaged, IPixel<TPixel2>
{
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel2> target;
private readonly Buffer2D<TPixel> source;
private readonly Buffer2D<TPixel2> target;
private readonly Configuration configuration;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
ImageFrame<TPixel> source,
ImageFrame<TPixel2> target,
Buffer2D<TPixel> source,
Buffer2D<TPixel2> target,
Configuration configuration)
{
this.source = source;
@ -385,8 +487,8 @@ namespace SixLabors.ImageSharp
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
Span<TPixel2> targetRow = this.target.GetPixelRowSpan(y);
Span<TPixel> sourceRow = this.source.DangerousGetRowSpan(y);
Span<TPixel2> targetRow = this.target.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.To(this.configuration, sourceRow, targetRow);
}
}

137
src/ImageSharp/Image{TPixel}.cs

@ -2,9 +2,11 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
@ -204,39 +206,136 @@ namespace SixLabors.ImageSharp
}
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the first pixel on that row.
/// Execute <paramref name="processPixels"/> to process image pixels in a safe and efficient manner.
/// </summary>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when row index is out of range.</exception>
public Span<TPixel> GetPixelRowSpan(int rowIndex)
/// <param name="processPixels">The <see cref="PixelAccessorAction{TPixel}"/> defining the pixel operations.</param>
public void ProcessPixelRows(PixelAccessorAction<TPixel> processPixels)
{
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex));
Guard.NotNull(processPixels, nameof(processPixels));
Buffer2D<TPixel> buffer = this.Frames.RootFrame.PixelBuffer;
buffer.FastMemoryGroup.IncreaseRefCounts();
this.EnsureNotDisposed();
try
{
var accessor = new PixelAccessor<TPixel>(buffer);
processPixels(accessor);
}
finally
{
buffer.FastMemoryGroup.DecreaseRefCounts();
}
}
/// <summary>
/// Execute <paramref name="processPixels"/> to process pixels of multiple images in a safe and efficient manner.
/// </summary>
/// <param name="image2">The second image.</param>
/// <param name="processPixels">The <see cref="PixelAccessorAction{TPixel, TPixel2}"/> defining the pixel operations.</param>
/// <typeparam name="TPixel2">The pixel type of the second image.</typeparam>
public void ProcessPixelRows<TPixel2>(
Image<TPixel2> image2,
PixelAccessorAction<TPixel, TPixel2> processPixels)
where TPixel2 : unmanaged, IPixel<TPixel2>
{
Guard.NotNull(image2, nameof(image2));
Guard.NotNull(processPixels, nameof(processPixels));
Buffer2D<TPixel> buffer1 = this.Frames.RootFrame.PixelBuffer;
Buffer2D<TPixel2> buffer2 = image2.Frames.RootFrame.PixelBuffer;
buffer1.FastMemoryGroup.IncreaseRefCounts();
buffer2.FastMemoryGroup.IncreaseRefCounts();
try
{
var accessor1 = new PixelAccessor<TPixel>(buffer1);
var accessor2 = new PixelAccessor<TPixel2>(buffer2);
processPixels(accessor1, accessor2);
}
finally
{
buffer2.FastMemoryGroup.DecreaseRefCounts();
buffer1.FastMemoryGroup.DecreaseRefCounts();
}
}
/// <summary>
/// Execute <paramref name="processPixels"/> to process pixels of multiple images in a safe and efficient manner.
/// </summary>
/// <param name="image2">The second image.</param>
/// <param name="image3">The third image.</param>
/// <param name="processPixels">The <see cref="PixelAccessorAction{TPixel, TPixel2, TPixel3}"/> defining the pixel operations.</param>
/// <typeparam name="TPixel2">The pixel type of the second image.</typeparam>
/// <typeparam name="TPixel3">The pixel type of the third image.</typeparam>
public void ProcessPixelRows<TPixel2, TPixel3>(
Image<TPixel2> image2,
Image<TPixel3> image3,
PixelAccessorAction<TPixel, TPixel2, TPixel3> processPixels)
where TPixel2 : unmanaged, IPixel<TPixel2>
where TPixel3 : unmanaged, IPixel<TPixel3>
{
Guard.NotNull(image2, nameof(image2));
Guard.NotNull(image3, nameof(image3));
Guard.NotNull(processPixels, nameof(processPixels));
return this.PixelSourceUnsafe.PixelBuffer.GetRowSpan(rowIndex);
Buffer2D<TPixel> buffer1 = this.Frames.RootFrame.PixelBuffer;
Buffer2D<TPixel2> buffer2 = image2.Frames.RootFrame.PixelBuffer;
Buffer2D<TPixel3> buffer3 = image3.Frames.RootFrame.PixelBuffer;
buffer1.FastMemoryGroup.IncreaseRefCounts();
buffer2.FastMemoryGroup.IncreaseRefCounts();
buffer3.FastMemoryGroup.IncreaseRefCounts();
try
{
var accessor1 = new PixelAccessor<TPixel>(buffer1);
var accessor2 = new PixelAccessor<TPixel2>(buffer2);
var accessor3 = new PixelAccessor<TPixel3>(buffer3);
processPixels(accessor1, accessor2, accessor3);
}
finally
{
buffer3.FastMemoryGroup.DecreaseRefCounts();
buffer2.FastMemoryGroup.DecreaseRefCounts();
buffer1.FastMemoryGroup.DecreaseRefCounts();
}
}
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> in the source image's pixel format
/// Copy image pixels to <paramref name="destination"/>.
/// </summary>
/// <param name="destination">The <see cref="Span{TPixel}"/> to copy image pixels to.</param>
public void CopyPixelDataTo(Span<TPixel> destination) => this.GetPixelMemoryGroup().CopyTo(destination);
/// <summary>
/// Copy image pixels to <paramref name="destination"/>.
/// </summary>
/// <param name="destination">The <see cref="Span{T}"/> of <see cref="byte"/> to copy image pixels to.</param>
public void CopyPixelDataTo(Span<byte> destination) => this.GetPixelMemoryGroup().CopyTo(MemoryMarshal.Cast<byte, TPixel>(destination));
/// <summary>
/// Gets the representation of the pixels as a <see cref="Memory{T}"/> in the source image's pixel format
/// stored in row major order, if the backing buffer is contiguous.
/// <para />
/// To ensure the memory is contiguous, <see cref="Configuration.PreferContiguousImageBuffers"/> should be set
/// to true, preferably on a non-global configuration instance (not <see cref="Configuration.Default"/>).
/// <para />
/// WARNING: Disposing or leaking the underlying image while still working with the <paramref name="memory"/>'s <see cref="Span{T}"/>
/// might lead to memory corruption.
/// </summary>
/// <param name="span">The <see cref="Span{T}"/>.</param>
/// <returns>The <see cref="bool"/>.</returns>
public bool TryGetSinglePixelSpan(out Span<TPixel> span)
/// <param name="memory">The <see cref="Memory{T}"/> referencing the image buffer.</param>
/// <returns>The <see cref="bool"/> indicating the success.</returns>
public bool DangerousTryGetSinglePixelMemory(out Memory<TPixel> memory)
{
IMemoryGroup<TPixel> mg = this.GetPixelMemoryGroup();
if (mg.Count == 1)
if (mg.Count > 1)
{
span = mg[0].Span;
return true;
memory = default;
return false;
}
span = default;
return false;
memory = mg.Single();
return true;
}
/// <summary>

7
src/ImageSharp/IndexedImageFrame{TPixel}.cs

@ -75,11 +75,14 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Gets the representation of the pixels as a <see cref="ReadOnlySpan{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the first pixel on that row.
/// <para />
/// WARNING: Disposing or leaking the underlying <see cref="IndexedImageFrame{TPixel}"/> while still working with it's <see cref="Span{T}"/>
/// might lead to memory corruption.
/// </summary>
/// <param name="rowIndex">The row index in the pixel buffer.</param>
/// <returns>The pixel row as a <see cref="ReadOnlySpan{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public ReadOnlySpan<byte> GetPixelRowSpan(int rowIndex)
public ReadOnlySpan<byte> DangerousGetRowSpan(int rowIndex)
=> this.GetWritablePixelRowSpanUnsafe(rowIndex);
/// <summary>
@ -96,7 +99,7 @@ namespace SixLabors.ImageSharp
/// <returns>The pixel row as a <see cref="Span{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public Span<byte> GetWritablePixelRowSpanUnsafe(int rowIndex)
=> this.pixelBuffer.GetRowSpan(rowIndex);
=> this.pixelBuffer.DangerousGetRowSpan(rowIndex);
/// <inheritdoc/>
public void Dispose()

7
src/ImageSharp/Memory/Allocators/AllocationOptions.cs

@ -1,21 +1,24 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Options for allocating buffers.
/// </summary>
[Flags]
public enum AllocationOptions
{
/// <summary>
/// Indicates that the buffer should just be allocated.
/// </summary>
None,
None = 0,
/// <summary>
/// Indicates that the allocated buffer should be cleaned following allocation.
/// </summary>
Clean
Clean = 1
}
}

10
src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs

@ -0,0 +1,10 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Memory
{
internal static class AllocationOptionsExtensions
{
public static bool Has(this AllocationOptions options, AllocationOptions flag) => (options & flag) == flag;
}
}

105
src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs

@ -1,105 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory.Internals;
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Contains <see cref="Buffer{T}"/> and <see cref="ManagedByteBuffer"/>.
/// </summary>
public partial class ArrayPoolMemoryAllocator
{
/// <summary>
/// The buffer implementation of <see cref="ArrayPoolMemoryAllocator"/>.
/// </summary>
private class Buffer<T> : ManagedBufferBase<T>
where T : struct
{
/// <summary>
/// The length of the buffer.
/// </summary>
private readonly int length;
/// <summary>
/// A weak reference to the source pool.
/// </summary>
/// <remarks>
/// By using a weak reference here, we are making sure that array pools and their retained arrays are always GC-ed
/// after a call to <see cref="ArrayPoolMemoryAllocator.ReleaseRetainedResources"/>, regardless of having buffer instances still being in use.
/// </remarks>
private WeakReference<ArrayPool<byte>> sourcePoolReference;
public Buffer(byte[] data, int length, ArrayPool<byte> sourcePool)
{
this.Data = data;
this.length = length;
this.sourcePoolReference = new WeakReference<ArrayPool<byte>>(sourcePool);
}
/// <summary>
/// Gets the buffer as a byte array.
/// </summary>
protected byte[] Data { get; private set; }
/// <inheritdoc />
public override Span<T> GetSpan()
{
if (this.Data is null)
{
ThrowObjectDisposedException();
}
#if SUPPORTS_CREATESPAN
ref byte r0 = ref MemoryMarshal.GetReference<byte>(this.Data);
return MemoryMarshal.CreateSpan(ref Unsafe.As<byte, T>(ref r0), this.length);
#else
return MemoryMarshal.Cast<byte, T>(this.Data.AsSpan()).Slice(0, this.length);
#endif
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (!disposing || this.Data is null || this.sourcePoolReference is null)
{
return;
}
if (this.sourcePoolReference.TryGetTarget(out ArrayPool<byte> pool))
{
pool.Return(this.Data);
}
this.sourcePoolReference = null;
this.Data = null;
}
protected override object GetPinnableObject() => this.Data;
[MethodImpl(InliningOptions.ColdPath)]
private static void ThrowObjectDisposedException()
{
throw new ObjectDisposedException("ArrayPoolMemoryAllocator.Buffer<T>");
}
}
/// <summary>
/// The <see cref="IManagedByteBuffer"/> implementation of <see cref="ArrayPoolMemoryAllocator"/>.
/// </summary>
private sealed class ManagedByteBuffer : Buffer<byte>, IManagedByteBuffer
{
public ManagedByteBuffer(byte[] data, int length, ArrayPool<byte> sourcePool)
: base(data, length, sourcePool)
{
}
/// <inheritdoc />
public byte[] Array => this.Data;
}
}
}

76
src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs

@ -1,76 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Contains common factory methods and configuration constants.
/// </summary>
public partial class ArrayPoolMemoryAllocator
{
/// <summary>
/// The default value for: maximum size of pooled arrays in bytes.
/// Currently set to 24MB, which is equivalent to 8 megapixels of raw RGBA32 data.
/// </summary>
internal const int DefaultMaxPooledBufferSizeInBytes = 24 * 1024 * 1024;
/// <summary>
/// The value for: The threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.
/// </summary>
private const int DefaultBufferSelectorThresholdInBytes = 8 * 1024 * 1024;
/// <summary>
/// The default bucket count for <see cref="largeArrayPool"/>.
/// </summary>
private const int DefaultLargePoolBucketCount = 6;
/// <summary>
/// The default bucket count for <see cref="normalArrayPool"/>.
/// </summary>
private const int DefaultNormalPoolBucketCount = 16;
// TODO: This value should be determined by benchmarking
private const int DefaultBufferCapacityInBytes = int.MaxValue / 4;
/// <summary>
/// This is the default. Should be good for most use cases.
/// </summary>
/// <returns>The memory manager.</returns>
public static ArrayPoolMemoryAllocator CreateDefault()
{
return new ArrayPoolMemoryAllocator(
DefaultMaxPooledBufferSizeInBytes,
DefaultBufferSelectorThresholdInBytes,
DefaultLargePoolBucketCount,
DefaultNormalPoolBucketCount,
DefaultBufferCapacityInBytes);
}
/// <summary>
/// For environments with very limited memory capabilities, only small buffers like image rows are pooled.
/// </summary>
/// <returns>The memory manager.</returns>
public static ArrayPoolMemoryAllocator CreateWithMinimalPooling()
{
return new ArrayPoolMemoryAllocator(64 * 1024, 32 * 1024, 8, 24);
}
/// <summary>
/// For environments with limited memory capabilities, only small array requests are pooled, which can result in reduced throughput.
/// </summary>
/// <returns>The memory manager.</returns>
public static ArrayPoolMemoryAllocator CreateWithModeratePooling()
{
return new ArrayPoolMemoryAllocator(1024 * 1024, 32 * 1024, 16, 24);
}
/// <summary>
/// For environments where memory capabilities are not an issue, the maximum amount of array requests are pooled which results in optimal throughput.
/// </summary>
/// <returns>The memory manager.</returns>
public static ArrayPoolMemoryAllocator CreateWithAggressivePooling()
{
return new ArrayPoolMemoryAllocator(128 * 1024 * 1024, 32 * 1024 * 1024, 16, 32);
}
}
}

185
src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs

@ -1,185 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Implements <see cref="MemoryAllocator"/> by allocating memory from <see cref="ArrayPool{T}"/>.
/// </summary>
public sealed partial class ArrayPoolMemoryAllocator : MemoryAllocator
{
private readonly int maxArraysPerBucketNormalPool;
private readonly int maxArraysPerBucketLargePool;
/// <summary>
/// The <see cref="ArrayPool{T}"/> for small-to-medium buffers which is not kept clean.
/// </summary>
private ArrayPool<byte> normalArrayPool;
/// <summary>
/// The <see cref="ArrayPool{T}"/> for huge buffers, which is not kept clean.
/// </summary>
private ArrayPool<byte> largeArrayPool;
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
/// </summary>
public ArrayPoolMemoryAllocator()
: this(DefaultMaxPooledBufferSizeInBytes, DefaultBufferSelectorThresholdInBytes)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
/// </summary>
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes)
: this(maxPoolSizeInBytes, GetLargeBufferThresholdInBytes(maxPoolSizeInBytes))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
/// </summary>
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
/// <param name="poolSelectorThresholdInBytes">Arrays over this threshold will be pooled in <see cref="largeArrayPool"/> which has less buckets for memory safety.</param>
public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes)
: this(maxPoolSizeInBytes, poolSelectorThresholdInBytes, DefaultLargePoolBucketCount, DefaultNormalPoolBucketCount)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
/// </summary>
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
/// <param name="poolSelectorThresholdInBytes">The threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.</param>
/// <param name="maxArraysPerBucketLargePool">Max arrays per bucket for the large array pool.</param>
/// <param name="maxArraysPerBucketNormalPool">Max arrays per bucket for the normal array pool.</param>
public ArrayPoolMemoryAllocator(
int maxPoolSizeInBytes,
int poolSelectorThresholdInBytes,
int maxArraysPerBucketLargePool,
int maxArraysPerBucketNormalPool)
: this(
maxPoolSizeInBytes,
poolSelectorThresholdInBytes,
maxArraysPerBucketLargePool,
maxArraysPerBucketNormalPool,
DefaultBufferCapacityInBytes)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
/// </summary>
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
/// <param name="poolSelectorThresholdInBytes">The threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.</param>
/// <param name="maxArraysPerBucketLargePool">Max arrays per bucket for the large array pool.</param>
/// <param name="maxArraysPerBucketNormalPool">Max arrays per bucket for the normal array pool.</param>
/// <param name="bufferCapacityInBytes">The length of the largest contiguous buffer that can be handled by this allocator instance.</param>
public ArrayPoolMemoryAllocator(
int maxPoolSizeInBytes,
int poolSelectorThresholdInBytes,
int maxArraysPerBucketLargePool,
int maxArraysPerBucketNormalPool,
int bufferCapacityInBytes)
{
Guard.MustBeGreaterThan(maxPoolSizeInBytes, 0, nameof(maxPoolSizeInBytes));
Guard.MustBeLessThanOrEqualTo(poolSelectorThresholdInBytes, maxPoolSizeInBytes, nameof(poolSelectorThresholdInBytes));
this.MaxPoolSizeInBytes = maxPoolSizeInBytes;
this.PoolSelectorThresholdInBytes = poolSelectorThresholdInBytes;
this.BufferCapacityInBytes = bufferCapacityInBytes;
this.maxArraysPerBucketLargePool = maxArraysPerBucketLargePool;
this.maxArraysPerBucketNormalPool = maxArraysPerBucketNormalPool;
this.InitArrayPools();
}
/// <summary>
/// Gets the maximum size of pooled arrays in bytes.
/// </summary>
public int MaxPoolSizeInBytes { get; }
/// <summary>
/// Gets the threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.
/// </summary>
public int PoolSelectorThresholdInBytes { get; }
/// <summary>
/// Gets the length of the largest contiguous buffer that can be handled by this allocator instance.
/// </summary>
public int BufferCapacityInBytes { get; internal set; } // Setter is internal for easy configuration in tests
/// <inheritdoc />
public override void ReleaseRetainedResources()
{
this.InitArrayPools();
}
/// <inheritdoc />
protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes;
/// <inheritdoc />
public override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
{
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
int itemSizeBytes = Unsafe.SizeOf<T>();
int bufferSizeInBytes = length * itemSizeBytes;
ArrayPool<byte> pool = this.GetArrayPool(bufferSizeInBytes);
byte[] byteArray = pool.Rent(bufferSizeInBytes);
var buffer = new Buffer<T>(byteArray, length, pool);
if (options == AllocationOptions.Clean)
{
buffer.GetSpan().Clear();
}
return buffer;
}
/// <inheritdoc />
public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None)
{
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
ArrayPool<byte> pool = this.GetArrayPool(length);
byte[] byteArray = pool.Rent(length);
var buffer = new ManagedByteBuffer(byteArray, length, pool);
if (options == AllocationOptions.Clean)
{
buffer.GetSpan().Clear();
}
return buffer;
}
private static int GetLargeBufferThresholdInBytes(int maxPoolSizeInBytes)
{
return maxPoolSizeInBytes / 4;
}
[MethodImpl(InliningOptions.ColdPath)]
private static void ThrowInvalidAllocationException<T>(int length, int max) =>
throw new InvalidMemoryOperationException(
$"Requested allocation: '{length}' elements of '{typeof(T).Name}' is over the capacity in bytes '{max}' of the MemoryAllocator.");
private ArrayPool<byte> GetArrayPool(int bufferSizeInBytes)
{
return bufferSizeInBytes <= this.PoolSelectorThresholdInBytes ? this.normalArrayPool : this.largeArrayPool;
}
private void InitArrayPools()
{
this.largeArrayPool = ArrayPool<byte>.Create(this.MaxPoolSizeInBytes, this.maxArraysPerBucketLargePool);
this.normalArrayPool = ArrayPool<byte>.Create(this.PoolSelectorThresholdInBytes, this.maxArraysPerBucketNormalPool);
}
}
}

18
src/ImageSharp/Memory/Allocators/IManagedByteBuffer.cs

@ -1,18 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Buffers;
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Represents a byte buffer backed by a managed array. Useful for interop with classic .NET API-s.
/// </summary>
public interface IManagedByteBuffer : IMemoryOwner<byte>
{
/// <summary>
/// Gets the managed array backing this buffer instance.
/// </summary>
byte[] Array { get; }
}
}

3
src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs

@ -2,11 +2,12 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
namespace SixLabors.ImageSharp.Memory.Internals
{
/// <summary>
/// Wraps an array as an <see cref="IManagedByteBuffer"/> instance.
/// Wraps an array as an <see cref="MemoryManager{T}"/> instance.
/// </summary>
/// <inheritdoc />
internal class BasicArrayBuffer<T> : ManagedBufferBase<T>

20
src/ImageSharp/Memory/Allocators/Internals/BasicByteBuffer.cs

@ -1,20 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Memory.Internals
{
/// <summary>
/// Provides an <see cref="IManagedByteBuffer"/> based on <see cref="BasicArrayBuffer{T}"/>.
/// </summary>
internal sealed class BasicByteBuffer : BasicArrayBuffer<byte>, IManagedByteBuffer
{
/// <summary>
/// Initializes a new instance of the <see cref="BasicByteBuffer"/> class.
/// </summary>
/// <param name="array">The byte array.</param>
internal BasicByteBuffer(byte[] array)
: base(array)
{
}
}
}

115
src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs

@ -0,0 +1,115 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Port of BCL internal utility:
// https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/libraries/System.Private.CoreLib/src/System/Gen2GcCallback.cs
#if NETCOREAPP3_1_OR_GREATER
using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Memory.Internals
{
/// <summary>
/// Schedules a callback roughly every gen 2 GC (you may see a Gen 0 an Gen 1 but only once)
/// (We can fix this by capturing the Gen 2 count at startup and testing, but I mostly don't care)
/// </summary>
internal sealed class Gen2GcCallback : CriticalFinalizerObject
{
private readonly Func<bool> callback0;
private readonly Func<object, bool> callback1;
private GCHandle weakTargetObj;
private Gen2GcCallback(Func<bool> callback)
{
this.callback0 = callback;
}
private Gen2GcCallback(Func<object, bool> callback, object targetObj)
{
this.callback1 = callback;
this.weakTargetObj = GCHandle.Alloc(targetObj, GCHandleType.Weak);
}
~Gen2GcCallback()
{
if (this.weakTargetObj.IsAllocated)
{
// Check to see if the target object is still alive.
object targetObj = this.weakTargetObj.Target;
if (targetObj == null)
{
// The target object is dead, so this callback object is no longer needed.
this.weakTargetObj.Free();
return;
}
// Execute the callback method.
try
{
if (!this.callback1(targetObj))
{
// If the callback returns false, this callback object is no longer needed.
this.weakTargetObj.Free();
return;
}
}
catch
{
// Ensure that we still get a chance to resurrect this object, even if the callback throws an exception.
#if DEBUG
// Except in DEBUG, as we really shouldn't be hitting any exceptions here.
throw;
#endif
}
}
else
{
// Execute the callback method.
try
{
if (!this.callback0())
{
// If the callback returns false, this callback object is no longer needed.
return;
}
}
catch
{
// Ensure that we still get a chance to resurrect this object, even if the callback throws an exception.
#if DEBUG
// Except in DEBUG, as we really shouldn't be hitting any exceptions here.
throw;
#endif
}
}
// Resurrect ourselves by re-registering for finalization.
GC.ReRegisterForFinalize(this);
}
/// <summary>
/// Schedule 'callback' to be called in the next GC. If the callback returns true it is
/// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop.
/// </summary>
public static void Register(Func<bool> callback)
{
// Create a unreachable object that remembers the callback function and target object.
_ = new Gen2GcCallback(callback);
}
/// <summary>
/// Schedule 'callback' to be called in the next GC. If the callback returns true it is
/// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop.
///
/// NOTE: This callback will be kept alive until either the callback function returns false,
/// or the target object dies.
/// </summary>
public static void Register(Func<object, bool> callback, object targetObj)
{
// Create a unreachable object that remembers the callback function and target object.
_ = new Gen2GcCallback(callback, targetObj);
}
}
}
#endif

21
src/ImageSharp/Memory/Allocators/Internals/IRefCounted.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Memory.Internals
{
/// <summary>
/// Defines an common interface for ref-counted objects.
/// </summary>
internal interface IRefCounted
{
/// <summary>
/// Increments the reference counter.
/// </summary>
void AddRef();
/// <summary>
/// Decrements the reference counter.
/// </summary>
void ReleaseRef();
}
}

3
src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Memory.Internals
@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Memory.Internals
this.pinHandle = GCHandle.Alloc(this.GetPinnableObject(), GCHandleType.Pinned);
}
void* ptr = (void*)this.pinHandle.AddrOfPinnedObject();
void* ptr = Unsafe.Add<T>((void*)this.pinHandle.AddrOfPinnedObject(), elementIndex);
// We should only pass pinnable:this, when GCHandle lifetime is managed by the MemoryManager<T> instance.
return new MemoryHandle(ptr, pinnable: this);

56
src/ImageSharp/Memory/Allocators/Internals/RefCountedLifetimeGuard.cs

@ -0,0 +1,56 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace SixLabors.ImageSharp.Memory.Internals
{
/// <summary>
/// Implements reference counting lifetime guard mechanism similar to the one provided by <see cref="SafeHandle"/>,
/// but without the restriction of the guarded object being a handle.
/// </summary>
internal abstract class RefCountedLifetimeGuard : IDisposable
{
private int refCount = 1;
private int disposed;
private int released;
~RefCountedLifetimeGuard()
{
Interlocked.Exchange(ref this.disposed, 1);
this.ReleaseRef();
}
public bool IsDisposed => this.disposed == 1;
public void AddRef() => Interlocked.Increment(ref this.refCount);
public void ReleaseRef()
{
Interlocked.Decrement(ref this.refCount);
if (this.refCount == 0)
{
int wasReleased = Interlocked.Exchange(ref this.released, 1);
if (wasReleased == 0)
{
this.Release();
}
}
}
public void Dispose()
{
int wasDisposed = Interlocked.Exchange(ref this.disposed, 1);
if (wasDisposed == 0)
{
this.ReleaseRef();
GC.SuppressFinalize(this);
}
}
protected abstract void Release();
}
}

80
src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs

@ -0,0 +1,80 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Memory.Internals
{
internal class SharedArrayPoolBuffer<T> : ManagedBufferBase<T>, IRefCounted
where T : struct
{
private readonly int lengthInBytes;
private byte[] array;
private LifetimeGuard lifetimeGuard;
public SharedArrayPoolBuffer(int lengthInElements)
{
this.lengthInBytes = lengthInElements * Unsafe.SizeOf<T>();
this.array = ArrayPool<byte>.Shared.Rent(this.lengthInBytes);
this.lifetimeGuard = new LifetimeGuard(this.array);
}
protected override void Dispose(bool disposing)
{
if (this.array == null)
{
return;
}
this.lifetimeGuard.Dispose();
this.array = null;
}
public override Span<T> GetSpan()
{
this.CheckDisposed();
return MemoryMarshal.Cast<byte, T>(this.array.AsSpan(0, this.lengthInBytes));
}
protected override object GetPinnableObject() => this.array;
public void AddRef()
{
this.CheckDisposed();
this.lifetimeGuard.AddRef();
}
public void ReleaseRef() => this.lifetimeGuard.ReleaseRef();
[Conditional("DEBUG")]
private void CheckDisposed()
{
if (this.array == null)
{
throw new ObjectDisposedException("SharedArrayPoolBuffer");
}
}
private sealed class LifetimeGuard : RefCountedLifetimeGuard
{
private byte[] array;
public LifetimeGuard(byte[] array) => this.array = array;
protected override void Release()
{
// If this is called by a finalizer, we will end storing the first array of this bucket
// on the thread local storage of the finalizer thread.
// This is not ideal, but subsequent leaks will end up returning arrays to per-cpu buckets,
// meaning likely a different bucket than it was rented from,
// but this is PROBABLY better than not returning the arrays at all.
ArrayPool<byte>.Shared.Return(this.array);
this.array = null;
}
}
}
}

65
src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs

@ -0,0 +1,65 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Memory.Internals
{
internal partial class UniformUnmanagedMemoryPool
{
public UnmanagedBuffer<T> CreateGuardedBuffer<T>(
UnmanagedMemoryHandle handle,
int lengthInElements,
bool clear)
where T : struct
{
var buffer = new UnmanagedBuffer<T>(lengthInElements, new ReturnToPoolBufferLifetimeGuard(this, handle));
if (clear)
{
buffer.Clear();
}
return buffer;
}
public RefCountedLifetimeGuard CreateGroupLifetimeGuard(UnmanagedMemoryHandle[] handles) => new GroupLifetimeGuard(this, handles);
private sealed class GroupLifetimeGuard : RefCountedLifetimeGuard
{
private readonly UniformUnmanagedMemoryPool pool;
private readonly UnmanagedMemoryHandle[] handles;
public GroupLifetimeGuard(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle[] handles)
{
this.pool = pool;
this.handles = handles;
}
protected override void Release()
{
if (!this.pool.Return(this.handles))
{
foreach (UnmanagedMemoryHandle handle in this.handles)
{
handle.Free();
}
}
}
}
private sealed class ReturnToPoolBufferLifetimeGuard : UnmanagedBufferLifetimeGuard
{
private readonly UniformUnmanagedMemoryPool pool;
public ReturnToPoolBufferLifetimeGuard(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle handle)
: base(handle) =>
this.pool = pool;
protected override void Release()
{
if (!this.pool.Return(this.Handle))
{
this.Handle.Free();
}
}
}
}
}

356
src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs

@ -0,0 +1,356 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
namespace SixLabors.ImageSharp.Memory.Internals
{
internal partial class UniformUnmanagedMemoryPool
#if !NETSTANDARD1_3
// In case UniformUnmanagedMemoryPool is finalized, we prefer to run its finalizer after the guard finalizers,
// but we should not rely on this.
: System.Runtime.ConstrainedExecution.CriticalFinalizerObject
#endif
{
private static int minTrimPeriodMilliseconds = int.MaxValue;
private static readonly List<WeakReference<UniformUnmanagedMemoryPool>> AllPools = new();
private static Timer trimTimer;
private static readonly Stopwatch Stopwatch = Stopwatch.StartNew();
private readonly TrimSettings trimSettings;
private readonly UnmanagedMemoryHandle[] buffers;
private int index;
private long lastTrimTimestamp;
private int finalized;
public UniformUnmanagedMemoryPool(int bufferLength, int capacity)
: this(bufferLength, capacity, TrimSettings.Default)
{
}
public UniformUnmanagedMemoryPool(int bufferLength, int capacity, TrimSettings trimSettings)
{
this.trimSettings = trimSettings;
this.Capacity = capacity;
this.BufferLength = bufferLength;
this.buffers = new UnmanagedMemoryHandle[capacity];
if (trimSettings.Enabled)
{
UpdateTimer(trimSettings, this);
#if NETCOREAPP3_1_OR_GREATER
Gen2GcCallback.Register(s => ((UniformUnmanagedMemoryPool)s).Trim(), this);
#endif
this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds;
}
}
// We don't want UniformUnmanagedMemoryPool and MemoryAllocator to be IDisposable,
// since the types don't really match Disposable semantics.
// If a user wants to drop a MemoryAllocator after they finished using it, they should call allocator.ReleaseRetainedResources(),
// which normally should free the already returned (!) buffers.
// However in case if this doesn't happen, we need the retained memory to be freed by the finalizer.
~UniformUnmanagedMemoryPool()
{
Interlocked.Exchange(ref this.finalized, 1);
this.TrimAll(this.buffers);
}
public int BufferLength { get; }
public int Capacity { get; }
private bool Finalized => this.finalized == 1;
/// <summary>
/// Rent a single buffer. If the pool is full, return <see cref="UnmanagedMemoryHandle.NullHandle"/>.
/// </summary>
public UnmanagedMemoryHandle Rent()
{
UnmanagedMemoryHandle[] buffersLocal = this.buffers;
// Avoid taking the lock if the pool is is over it's limit:
if (this.index == buffersLocal.Length || this.Finalized)
{
return UnmanagedMemoryHandle.NullHandle;
}
UnmanagedMemoryHandle buffer;
lock (buffersLocal)
{
// Check again after taking the lock:
if (this.index == buffersLocal.Length || this.Finalized)
{
return UnmanagedMemoryHandle.NullHandle;
}
buffer = buffersLocal[this.index];
buffersLocal[this.index++] = default;
}
if (buffer.IsInvalid)
{
buffer = UnmanagedMemoryHandle.Allocate(this.BufferLength);
}
return buffer;
}
/// <summary>
/// Rent <paramref name="bufferCount"/> buffers or return 'null' if the pool is full.
/// </summary>
public UnmanagedMemoryHandle[] Rent(int bufferCount)
{
UnmanagedMemoryHandle[] buffersLocal = this.buffers;
// Avoid taking the lock if the pool is is over it's limit:
if (this.index + bufferCount >= buffersLocal.Length + 1 || this.Finalized)
{
return null;
}
UnmanagedMemoryHandle[] result;
lock (buffersLocal)
{
// Check again after taking the lock:
if (this.index + bufferCount >= buffersLocal.Length + 1 || this.Finalized)
{
return null;
}
result = new UnmanagedMemoryHandle[bufferCount];
for (int i = 0; i < bufferCount; i++)
{
result[i] = buffersLocal[this.index];
buffersLocal[this.index++] = UnmanagedMemoryHandle.NullHandle;
}
}
for (int i = 0; i < result.Length; i++)
{
if (result[i].IsInvalid)
{
result[i] = UnmanagedMemoryHandle.Allocate(this.BufferLength);
}
}
return result;
}
// The Return methods return false if and only if:
// (1) More buffers are returned than rented OR
// (2) The pool has been finalized.
// This is defensive programming, since neither of the cases should happen normally
// (case 1 would be a programming mistake in the library, case 2 should be prevented by the CriticalFinalizerObject contract),
// so we throw in Debug instead of returning false.
// In Release, the caller should Free() the handles if false is returned to avoid memory leaks.
public bool Return(UnmanagedMemoryHandle bufferHandle)
{
Guard.IsTrue(bufferHandle.IsValid, nameof(bufferHandle), "Returning NullHandle to the pool is not allowed.");
lock (this.buffers)
{
if (this.Finalized || this.index == 0)
{
this.DebugThrowInvalidReturn();
return false;
}
this.buffers[--this.index] = bufferHandle;
}
return true;
}
public bool Return(Span<UnmanagedMemoryHandle> bufferHandles)
{
lock (this.buffers)
{
if (this.Finalized || this.index - bufferHandles.Length + 1 <= 0)
{
this.DebugThrowInvalidReturn();
return false;
}
for (int i = bufferHandles.Length - 1; i >= 0; i--)
{
ref UnmanagedMemoryHandle h = ref bufferHandles[i];
Guard.IsTrue(h.IsValid, nameof(bufferHandles), "Returning NullHandle to the pool is not allowed.");
this.buffers[--this.index] = h;
}
}
return true;
}
public void Release()
{
lock (this.buffers)
{
for (int i = this.index; i < this.buffers.Length; i++)
{
ref UnmanagedMemoryHandle buffer = ref this.buffers[i];
if (buffer.IsInvalid)
{
break;
}
buffer.Free();
}
}
}
[Conditional("DEBUG")]
private void DebugThrowInvalidReturn()
{
if (this.Finalized)
{
throw new ObjectDisposedException(
nameof(UniformUnmanagedMemoryPool),
"Invalid handle return to the pool! The pool has been finalized.");
}
throw new InvalidOperationException(
"Invalid handle return to the pool! Returning more buffers than rented.");
}
private static void UpdateTimer(TrimSettings settings, UniformUnmanagedMemoryPool pool)
{
lock (AllPools)
{
AllPools.Add(new WeakReference<UniformUnmanagedMemoryPool>(pool));
// Invoke the timer callback more frequently, than trimSettings.TrimPeriodMilliseconds.
// We are checking in the callback if enough time passed since the last trimming. If not, we do nothing.
int period = settings.TrimPeriodMilliseconds / 4;
if (trimTimer == null)
{
trimTimer = new Timer(_ => TimerCallback(), null, period, period);
}
else if (settings.TrimPeriodMilliseconds < minTrimPeriodMilliseconds)
{
trimTimer.Change(period, period);
}
minTrimPeriodMilliseconds = Math.Min(minTrimPeriodMilliseconds, settings.TrimPeriodMilliseconds);
}
}
private static void TimerCallback()
{
lock (AllPools)
{
// Remove lost references from the list:
for (int i = AllPools.Count - 1; i >= 0; i--)
{
if (!AllPools[i].TryGetTarget(out _))
{
AllPools.RemoveAt(i);
}
}
foreach (WeakReference<UniformUnmanagedMemoryPool> weakPoolRef in AllPools)
{
if (weakPoolRef.TryGetTarget(out UniformUnmanagedMemoryPool pool))
{
pool.Trim();
}
}
}
}
private bool Trim()
{
if (this.Finalized)
{
return false;
}
UnmanagedMemoryHandle[] buffersLocal = this.buffers;
bool isHighPressure = this.IsHighMemoryPressure();
if (isHighPressure)
{
this.TrimAll(buffersLocal);
return true;
}
long millisecondsSinceLastTrim = Stopwatch.ElapsedMilliseconds - this.lastTrimTimestamp;
if (millisecondsSinceLastTrim > this.trimSettings.TrimPeriodMilliseconds)
{
return this.TrimLowPressure(buffersLocal);
}
return true;
}
private void TrimAll(UnmanagedMemoryHandle[] buffersLocal)
{
lock (buffersLocal)
{
// Trim all:
for (int i = this.index; i < buffersLocal.Length && buffersLocal[i].IsValid; i++)
{
buffersLocal[i].Free();
}
}
}
private bool TrimLowPressure(UnmanagedMemoryHandle[] buffersLocal)
{
lock (buffersLocal)
{
// Count the buffers in the pool:
int retainedCount = 0;
for (int i = this.index; i < buffersLocal.Length && buffersLocal[i].IsValid; i++)
{
retainedCount++;
}
// Trim 'trimRate' of 'retainedCount':
int trimCount = (int)Math.Ceiling(retainedCount * this.trimSettings.Rate);
int trimStart = this.index + retainedCount - 1;
int trimStop = this.index + retainedCount - trimCount;
for (int i = trimStart; i >= trimStop; i--)
{
buffersLocal[i].Free();
}
this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds;
}
return true;
}
private bool IsHighMemoryPressure()
{
#if NETCOREAPP3_1_OR_GREATER
GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo();
return memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * this.trimSettings.HighPressureThresholdRate;
#else
// We don't have high pressure detection triggering full trimming on other platforms,
// to counterpart this, the maximum pool size is small.
return false;
#endif
}
public class TrimSettings
{
// Trim half of the retained pool buffers every minute.
public int TrimPeriodMilliseconds { get; set; } = 60_000;
public float Rate { get; set; } = 0.5f;
// Be more strict about high pressure on 32 bit.
public unsafe float HighPressureThresholdRate { get; set; } = sizeof(IntPtr) == 8 ? 0.9f : 0.6f;
public bool Enabled => this.Rate > 0;
public static TrimSettings Default => new TrimSettings();
}
}
}

27
src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs

@ -0,0 +1,27 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Memory.Internals
{
/// <summary>
/// Defines a strategy for managing unmanaged memory ownership.
/// </summary>
internal abstract class UnmanagedBufferLifetimeGuard : RefCountedLifetimeGuard
{
private UnmanagedMemoryHandle handle;
protected UnmanagedBufferLifetimeGuard(UnmanagedMemoryHandle handle) => this.handle = handle;
public ref UnmanagedMemoryHandle Handle => ref this.handle;
public sealed class FreeHandle : UnmanagedBufferLifetimeGuard
{
public FreeHandle(UnmanagedMemoryHandle handle)
: base(handle)
{
}
protected override void Release() => this.Handle.Free();
}
}
}

80
src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs

@ -0,0 +1,80 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
namespace SixLabors.ImageSharp.Memory.Internals
{
/// <summary>
/// Allocates and provides an <see cref="IMemoryOwner{T}"/> implementation giving
/// access to unmanaged buffers allocated by <see cref="Marshal.AllocHGlobal(int)"/>.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
internal sealed unsafe class UnmanagedBuffer<T> : MemoryManager<T>, IRefCounted
where T : struct
{
private readonly int lengthInElements;
private readonly UnmanagedBufferLifetimeGuard lifetimeGuard;
private int disposed;
public UnmanagedBuffer(int lengthInElements, UnmanagedBufferLifetimeGuard lifetimeGuard)
{
DebugGuard.NotNull(lifetimeGuard, nameof(lifetimeGuard));
this.lengthInElements = lengthInElements;
this.lifetimeGuard = lifetimeGuard;
}
private void* Pointer => this.lifetimeGuard.Handle.Pointer;
public override Span<T> GetSpan()
{
DebugGuard.NotDisposed(this.disposed == 1, this.GetType().Name);
DebugGuard.NotDisposed(this.lifetimeGuard.IsDisposed, this.lifetimeGuard.GetType().Name);
return new(this.Pointer, this.lengthInElements);
}
/// <inheritdoc />
public override MemoryHandle Pin(int elementIndex = 0)
{
DebugGuard.NotDisposed(this.disposed == 1, this.GetType().Name);
DebugGuard.NotDisposed(this.lifetimeGuard.IsDisposed, this.lifetimeGuard.GetType().Name);
// Will be released in Unpin
this.lifetimeGuard.AddRef();
void* pbData = Unsafe.Add<T>(this.Pointer, elementIndex);
return new MemoryHandle(pbData, pinnable: this);
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
DebugGuard.IsTrue(disposing, nameof(disposing), "Unmanaged buffers should not have finalizer!");
if (Interlocked.Exchange(ref this.disposed, 1) == 1)
{
// Already disposed
return;
}
this.lifetimeGuard.Dispose();
}
/// <inheritdoc />
public override void Unpin() => this.lifetimeGuard.ReleaseRef();
public void AddRef() => this.lifetimeGuard.AddRef();
public void ReleaseRef() => this.lifetimeGuard.ReleaseRef();
public static UnmanagedBuffer<T> Allocate(int lengthInElements) =>
new(lengthInElements, new UnmanagedBufferLifetimeGuard.FreeHandle(UnmanagedMemoryHandle.Allocate(lengthInElements * Unsafe.SizeOf<T>())));
}
}

140
src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs

@ -0,0 +1,140 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
namespace SixLabors.ImageSharp.Memory.Internals
{
/// <summary>
/// Encapsulates the functionality around allocating and releasing unmanaged memory. NOT a <see cref="SafeHandle"/>.
/// </summary>
internal struct UnmanagedMemoryHandle : IEquatable<UnmanagedMemoryHandle>
{
// Number of allocation re-attempts when detecting OutOfMemoryException.
private const int MaxAllocationAttempts = 1000;
// Track allocations for testing purposes:
private static int totalOutstandingHandles;
private static long totalOomRetries;
// A Monitor to wait/signal when we are low on memory.
private static object lowMemoryMonitor;
public static readonly UnmanagedMemoryHandle NullHandle = default;
private IntPtr handle;
private int lengthInBytes;
private UnmanagedMemoryHandle(IntPtr handle, int lengthInBytes)
{
this.handle = handle;
this.lengthInBytes = lengthInBytes;
if (lengthInBytes > 0)
{
GC.AddMemoryPressure(lengthInBytes);
}
Interlocked.Increment(ref totalOutstandingHandles);
}
public IntPtr Handle => this.handle;
public bool IsInvalid => this.Handle == IntPtr.Zero;
public bool IsValid => this.Handle != IntPtr.Zero;
public unsafe void* Pointer => (void*)this.Handle;
/// <summary>
/// Gets the total outstanding handle allocations for testing purposes.
/// </summary>
internal static int TotalOutstandingHandles => totalOutstandingHandles;
/// <summary>
/// Gets the total number <see cref="OutOfMemoryException"/>-s retried.
/// </summary>
internal static long TotalOomRetries => totalOomRetries;
public static bool operator ==(UnmanagedMemoryHandle a, UnmanagedMemoryHandle b) => a.Equals(b);
public static bool operator !=(UnmanagedMemoryHandle a, UnmanagedMemoryHandle b) => !a.Equals(b);
public static UnmanagedMemoryHandle Allocate(int lengthInBytes)
{
IntPtr handle = AllocateHandle(lengthInBytes);
return new UnmanagedMemoryHandle(handle, lengthInBytes);
}
private static IntPtr AllocateHandle(int lengthInBytes)
{
int counter = 0;
IntPtr handle = IntPtr.Zero;
while (handle == IntPtr.Zero)
{
try
{
handle = Marshal.AllocHGlobal(lengthInBytes);
}
catch (OutOfMemoryException)
{
// We are low on memory, but expect some memory to be freed soon.
// Block the thread & retry to avoid OOM.
if (counter < MaxAllocationAttempts)
{
counter++;
Interlocked.Increment(ref totalOomRetries);
Interlocked.CompareExchange(ref lowMemoryMonitor, new object(), null);
Monitor.Enter(lowMemoryMonitor);
Monitor.Wait(lowMemoryMonitor, millisecondsTimeout: 1);
Monitor.Exit(lowMemoryMonitor);
}
else
{
throw;
}
}
}
return handle;
}
public void Free()
{
IntPtr h = Interlocked.Exchange(ref this.handle, IntPtr.Zero);
if (h == IntPtr.Zero)
{
return;
}
Marshal.FreeHGlobal(h);
Interlocked.Decrement(ref totalOutstandingHandles);
if (this.lengthInBytes > 0)
{
GC.RemoveMemoryPressure(this.lengthInBytes);
}
if (Volatile.Read(ref lowMemoryMonitor) != null)
{
// We are low on memory. Signal all threads waiting in AllocateHandle().
Monitor.Enter(lowMemoryMonitor);
Monitor.PulseAll(lowMemoryMonitor);
Monitor.Exit(lowMemoryMonitor);
}
this.lengthInBytes = 0;
}
public bool Equals(UnmanagedMemoryHandle other) => this.handle.Equals(other.handle);
public override bool Equals(object obj) => obj is UnmanagedMemoryHandle other && this.Equals(other);
public override int GetHashCode() => this.handle.GetHashCode();
}
}

50
src/ImageSharp/Memory/Allocators/MemoryAllocator.cs

@ -11,12 +11,37 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
public abstract class MemoryAllocator
{
/// <summary>
/// Gets the default platform-specific global <see cref="MemoryAllocator"/> instance that
/// serves as the default value for <see cref="Configuration.MemoryAllocator"/>.
/// <para />
/// This is a get-only property,
/// you should set <see cref="Configuration.Default"/>'s <see cref="Configuration.MemoryAllocator"/>
/// to change the default allocator used by <see cref="Image"/> and it's operations.
/// </summary>
public static MemoryAllocator Default { get; } = Create();
/// <summary>
/// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes.
/// </summary>
/// <returns>The length of the largest contiguous buffer that can be handled by this allocator instance.</returns>
protected internal abstract int GetBufferCapacityInBytes();
/// <summary>
/// Creates a default instance of a <see cref="MemoryAllocator"/> optimized for the executing platform.
/// </summary>
/// <returns>The <see cref="MemoryAllocator"/>.</returns>
public static MemoryAllocator Create() =>
new UniformUnmanagedMemoryPoolMemoryAllocator(null);
/// <summary>
/// Creates the default <see cref="MemoryAllocator"/> using the provided options.
/// </summary>
/// <param name="options">The <see cref="MemoryAllocatorOptions"/>.</param>
/// <returns>The <see cref="MemoryAllocator"/>.</returns>
public static MemoryAllocator Create(MemoryAllocatorOptions options) =>
new UniformUnmanagedMemoryPoolMemoryAllocator(options.MaximumPoolSizeMegabytes);
/// <summary>
/// Allocates an <see cref="IMemoryOwner{T}" />, holding a <see cref="Memory{T}"/> of length <paramref name="length"/>.
/// </summary>
@ -29,16 +54,6 @@ namespace SixLabors.ImageSharp.Memory
public abstract IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
where T : struct;
/// <summary>
/// Allocates an <see cref="IManagedByteBuffer"/>.
/// </summary>
/// <param name="length">The requested buffer length.</param>
/// <param name="options">The allocation options.</param>
/// <returns>The <see cref="IManagedByteBuffer"/>.</returns>
/// <exception cref="ArgumentOutOfRangeException">When length is zero or negative.</exception>
/// <exception cref="InvalidMemoryOperationException">When length is over the capacity of the allocator.</exception>
public abstract IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None);
/// <summary>
/// Releases all retained resources not being in use.
/// Eg: by resetting array pools and letting GC to free the arrays.
@ -46,5 +61,20 @@ namespace SixLabors.ImageSharp.Memory
public virtual void ReleaseRetainedResources()
{
}
/// <summary>
/// Allocates a <see cref="MemoryGroup{T}"/>.
/// </summary>
/// <param name="totalLength">The total length of the buffer.</param>
/// <param name="bufferAlignment">The expected alignment (eg. to make sure image rows fit into single buffers).</param>
/// <param name="options">The <see cref="AllocationOptions"/>.</param>
/// <returns>A new <see cref="MemoryGroup{T}"/>.</returns>
/// <exception cref="InvalidMemoryOperationException">Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator.</exception>
internal virtual MemoryGroup<T> AllocateGroup<T>(
long totalLength,
int bufferAlignment,
AllocationOptions options = AllocationOptions.None)
where T : struct
=> MemoryGroup<T>.Allocate(this, totalLength, bufferAlignment, options);
}
}

31
src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Defines options for creating the default <see cref="MemoryAllocator"/>.
/// </summary>
public struct MemoryAllocatorOptions
{
private int? maximumPoolSizeMegabytes;
/// <summary>
/// Gets or sets a value defining the maximum size of the <see cref="MemoryAllocator"/>'s internal memory pool
/// in Megabytes. <see langword="null"/> means platform default.
/// </summary>
public int? MaximumPoolSizeMegabytes
{
get => this.maximumPoolSizeMegabytes;
set
{
if (value.HasValue)
{
Guard.MustBeGreaterThanOrEqualTo(value.Value, 0, nameof(this.MaximumPoolSizeMegabytes));
}
this.maximumPoolSizeMegabytes = value;
}
}
}
}

8
src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs

@ -21,13 +21,5 @@ namespace SixLabors.ImageSharp.Memory
return new BasicArrayBuffer<T>(new T[length]);
}
/// <inheritdoc />
public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None)
{
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
return new BasicByteBuffer(new byte[length]);
}
}
}

164
src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs

@ -0,0 +1,164 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using SixLabors.ImageSharp.Memory.Internals;
namespace SixLabors.ImageSharp.Memory
{
internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocator
{
private const int OneMegabyte = 1 << 20;
// 4 MB seemed to perform slightly better in benchmarks than 2MB or higher values:
private const int DefaultContiguousPoolBlockSizeBytes = 4 * OneMegabyte;
private const int DefaultNonPoolBlockSizeBytes = 32 * OneMegabyte;
private readonly int sharedArrayPoolThresholdInBytes;
private readonly int poolBufferSizeInBytes;
private readonly int poolCapacity;
private readonly UniformUnmanagedMemoryPool.TrimSettings trimSettings;
private UniformUnmanagedMemoryPool pool;
private readonly UnmanagedMemoryAllocator nonPoolAllocator;
public UniformUnmanagedMemoryPoolMemoryAllocator(int? maxPoolSizeMegabytes)
: this(
DefaultContiguousPoolBlockSizeBytes,
maxPoolSizeMegabytes.HasValue ? (long)maxPoolSizeMegabytes.Value * OneMegabyte : GetDefaultMaxPoolSizeBytes(),
DefaultNonPoolBlockSizeBytes)
{
}
public UniformUnmanagedMemoryPoolMemoryAllocator(
int poolBufferSizeInBytes,
long maxPoolSizeInBytes,
int unmanagedBufferSizeInBytes)
: this(
OneMegabyte,
poolBufferSizeInBytes,
maxPoolSizeInBytes,
unmanagedBufferSizeInBytes)
{
}
internal UniformUnmanagedMemoryPoolMemoryAllocator(
int sharedArrayPoolThresholdInBytes,
int poolBufferSizeInBytes,
long maxPoolSizeInBytes,
int unmanagedBufferSizeInBytes)
: this(
sharedArrayPoolThresholdInBytes,
poolBufferSizeInBytes,
maxPoolSizeInBytes,
unmanagedBufferSizeInBytes,
UniformUnmanagedMemoryPool.TrimSettings.Default)
{
}
internal UniformUnmanagedMemoryPoolMemoryAllocator(
int sharedArrayPoolThresholdInBytes,
int poolBufferSizeInBytes,
long maxPoolSizeInBytes,
int unmanagedBufferSizeInBytes,
UniformUnmanagedMemoryPool.TrimSettings trimSettings)
{
this.sharedArrayPoolThresholdInBytes = sharedArrayPoolThresholdInBytes;
this.poolBufferSizeInBytes = poolBufferSizeInBytes;
this.poolCapacity = (int)(maxPoolSizeInBytes / poolBufferSizeInBytes);
this.trimSettings = trimSettings;
this.pool = new UniformUnmanagedMemoryPool(this.poolBufferSizeInBytes, this.poolCapacity, this.trimSettings);
this.nonPoolAllocator = new UnmanagedMemoryAllocator(unmanagedBufferSizeInBytes);
}
/// <inheritdoc />
protected internal override int GetBufferCapacityInBytes() => this.poolBufferSizeInBytes;
/// <inheritdoc />
public override IMemoryOwner<T> Allocate<T>(
int length,
AllocationOptions options = AllocationOptions.None)
{
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
int lengthInBytes = length * Unsafe.SizeOf<T>();
if (lengthInBytes <= this.sharedArrayPoolThresholdInBytes)
{
var buffer = new SharedArrayPoolBuffer<T>(length);
if (options.Has(AllocationOptions.Clean))
{
buffer.GetSpan().Clear();
}
return buffer;
}
if (lengthInBytes <= this.poolBufferSizeInBytes)
{
UnmanagedMemoryHandle mem = this.pool.Rent();
if (mem.IsValid)
{
UnmanagedBuffer<T> buffer = this.pool.CreateGuardedBuffer<T>(mem, length, options.Has(AllocationOptions.Clean));
return buffer;
}
}
return this.nonPoolAllocator.Allocate<T>(length, options);
}
/// <inheritdoc />
internal override MemoryGroup<T> AllocateGroup<T>(
long totalLength,
int bufferAlignment,
AllocationOptions options = AllocationOptions.None)
{
long totalLengthInBytes = totalLength * Unsafe.SizeOf<T>();
if (totalLengthInBytes <= this.sharedArrayPoolThresholdInBytes)
{
var buffer = new SharedArrayPoolBuffer<T>((int)totalLength);
return MemoryGroup<T>.CreateContiguous(buffer, options.Has(AllocationOptions.Clean));
}
if (totalLengthInBytes <= this.poolBufferSizeInBytes)
{
// Optimized path renting single array from the pool
UnmanagedMemoryHandle mem = this.pool.Rent();
if (mem.IsValid)
{
UnmanagedBuffer<T> buffer = this.pool.CreateGuardedBuffer<T>(mem, (int)totalLength, options.Has(AllocationOptions.Clean));
return MemoryGroup<T>.CreateContiguous(buffer, options.Has(AllocationOptions.Clean));
}
}
// Attempt to rent the whole group from the pool, allocate a group of unmanaged buffers if the attempt fails:
if (MemoryGroup<T>.TryAllocate(this.pool, totalLength, bufferAlignment, options, out MemoryGroup<T> poolGroup))
{
return poolGroup;
}
return MemoryGroup<T>.Allocate(this.nonPoolAllocator, totalLength, bufferAlignment, options);
}
public override void ReleaseRetainedResources() => this.pool.Release();
private static long GetDefaultMaxPoolSizeBytes()
{
#if NETCOREAPP3_1_OR_GREATER
// On 64 bit .NET Core 3.1+, set the pool size to a portion of the total available memory.
// There is a bug in GC.GetGCMemoryInfo() on .NET 5 + 32 bit, making TotalAvailableMemoryBytes unreliable:
// https://github.com/dotnet/runtime/issues/55126#issuecomment-876779327
if (Environment.Is64BitProcess || !RuntimeInformation.FrameworkDescription.StartsWith(".NET 5.0"))
{
GCMemoryInfo info = GC.GetGCMemoryInfo();
return info.TotalAvailableMemoryBytes / 8;
}
#endif
// Stick to a conservative value of 128 Megabytes on other platforms and 32 bit .NET 5.0:
return 128 * OneMegabyte;
}
}
}

33
src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs

@ -0,0 +1,33 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Memory.Internals;
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// A <see cref="MemoryAllocator"/> implementation that allocates memory on the unmanaged heap
/// without any pooling.
/// </summary>
internal class UnmanagedMemoryAllocator : MemoryAllocator
{
private readonly int bufferCapacityInBytes;
public UnmanagedMemoryAllocator(int bufferCapacityInBytes) => this.bufferCapacityInBytes = bufferCapacityInBytes;
protected internal override int GetBufferCapacityInBytes() => this.bufferCapacityInBytes;
public override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
{
var buffer = UnmanagedBuffer<T>.Allocate(length);
if (options.Has(AllocationOptions.Clean))
{
buffer.GetSpan().Clear();
}
return buffer;
}
}
}

14
src/ImageSharp/Memory/Buffer2DExtensions.cs

@ -111,10 +111,16 @@ namespace SixLabors.ImageSharp.Memory
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <returns>The <see cref="Size{T}"/> of the buffer</returns>
internal static Size Size<T>(this Buffer2D<T> buffer)
where T : struct
{
return new Size(buffer.Width, buffer.Height);
}
where T : struct =>
new(buffer.Width, buffer.Height);
/// <summary>
/// Gets the bounds of the buffer.
/// </summary>
/// <returns>The <see cref="Rectangle"/></returns>
internal static Rectangle Bounds<T>(this Buffer2D<T> buffer)
where T : struct =>
new(0, 0, buffer.Width, buffer.Height);
[Conditional("DEBUG")]
private static void CheckColumnRegionsDoNotOverlap<T>(

4
src/ImageSharp/Memory/Buffer2DRegion{T}.cs

@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Memory
int xx = this.Rectangle.X;
int width = this.Rectangle.Width;
return this.Buffer.GetRowSpan(yy).Slice(xx, width);
return this.Buffer.DangerousGetRowSpan(yy).Slice(xx, width);
}
/// <summary>
@ -138,7 +138,7 @@ namespace SixLabors.ImageSharp.Memory
{
int y = this.Rectangle.Y;
int x = this.Rectangle.X;
return ref this.Buffer.GetRowSpan(y)[x];
return ref this.Buffer.DangerousGetRowSpan(y)[x];
}
internal void Clear()

101
src/ImageSharp/Memory/Buffer2D{T}.cs

@ -19,8 +19,6 @@ namespace SixLabors.ImageSharp.Memory
public sealed class Buffer2D<T> : IDisposable
where T : struct
{
private Memory<T> cachedMemory = default;
/// <summary>
/// Initializes a new instance of the <see cref="Buffer2D{T}"/> class.
/// </summary>
@ -32,11 +30,6 @@ namespace SixLabors.ImageSharp.Memory
this.FastMemoryGroup = memoryGroup;
this.Width = width;
this.Height = height;
if (memoryGroup.Count == 1)
{
this.cachedMemory = memoryGroup[0];
}
}
/// <summary>
@ -82,18 +75,14 @@ namespace SixLabors.ImageSharp.Memory
DebugGuard.MustBeLessThan(x, this.Width, nameof(x));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
return ref this.GetRowSpan(y)[x];
return ref this.DangerousGetRowSpan(y)[x];
}
}
/// <summary>
/// Disposes the <see cref="Buffer2D{T}"/> instance
/// </summary>
public void Dispose()
{
this.FastMemoryGroup.Dispose();
this.cachedMemory = default;
}
public void Dispose() => this.FastMemoryGroup.Dispose();
/// <summary>
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
@ -106,14 +95,12 @@ namespace SixLabors.ImageSharp.Memory
/// <returns>The <see cref="Span{T}"/> of the pixels in the row.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when row index is out of range.</exception>
[MethodImpl(InliningOptions.ShortMethod)]
public Span<T> GetRowSpan(int y)
public Span<T> DangerousGetRowSpan(int y)
{
DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
return this.cachedMemory.Length > 0
? this.cachedMemory.Span.Slice(y * this.Width, this.Width)
: this.GetRowMemorySlow(y).Span;
return this.GetRowMemoryCore(y).Span;
}
internal bool TryGetPaddedRowSpan(int y, int padding, out Span<T> paddedSpan)
@ -122,17 +109,6 @@ namespace SixLabors.ImageSharp.Memory
DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
int stride = this.Width + padding;
if (this.cachedMemory.Length > 0)
{
paddedSpan = this.cachedMemory.Span.Slice(y * this.Width);
if (paddedSpan.Length < stride)
{
return false;
}
paddedSpan = paddedSpan.Slice(0, stride);
return true;
}
Memory<T> memory = this.FastMemoryGroup.GetRemainingSliceOfBuffer(y * (long)this.Width);
@ -149,31 +125,8 @@ namespace SixLabors.ImageSharp.Memory
[MethodImpl(InliningOptions.ShortMethod)]
internal ref T GetElementUnsafe(int x, int y)
{
if (this.cachedMemory.Length > 0)
{
Span<T> span = this.cachedMemory.Span;
ref T start = ref MemoryMarshal.GetReference(span);
return ref Unsafe.Add(ref start, (y * this.Width) + x);
}
return ref this.GetElementSlow(x, y);
}
/// <summary>
/// Gets a <see cref="Memory{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// This method is intended for internal use only, since it does not use the indirection provided by
/// <see cref="MemoryGroupView{T}"/>.
/// </summary>
/// <param name="y">The y (row) coordinate.</param>
/// <returns>The <see cref="Span{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
internal Memory<T> GetFastRowMemory(int y)
{
DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
return this.cachedMemory.Length > 0
? this.cachedMemory.Slice(y * this.Width, this.Width)
: this.GetRowMemorySlow(y);
Span<T> span = this.GetRowMemoryCore(y).Span;
return ref span[x];
}
/// <summary>
@ -198,11 +151,7 @@ namespace SixLabors.ImageSharp.Memory
/// Thrown when the backing group is discontiguous.
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
internal Span<T> DangerousGetSingleSpan()
{
// TODO: If we need a public version of this method, we need to cache the non-fast Memory<T> of this.MemoryGroup
return this.cachedMemory.Length != 0 ? this.cachedMemory.Span : this.DangerousGetSingleSpanSlow();
}
internal Span<T> DangerousGetSingleSpan() => this.FastMemoryGroup.Single().Span;
/// <summary>
/// Gets a <see cref="Memory{T}"/> to the backing data of if the backing group consists of a single contiguous memory buffer.
@ -213,11 +162,7 @@ namespace SixLabors.ImageSharp.Memory
/// Thrown when the backing group is discontiguous.
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
internal Memory<T> DangerousGetSingleMemory()
{
// TODO: If we need a public version of this method, we need to cache the non-fast Memory<T> of this.MemoryGroup
return this.cachedMemory.Length != 0 ? this.cachedMemory : this.DangerousGetSingleMemorySlow();
}
internal Memory<T> DangerousGetSingleMemory() => this.FastMemoryGroup.Single();
/// <summary>
/// Swaps the contents of 'destination' with 'source' if the buffers are owned (1),
@ -225,27 +170,14 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
internal static void SwapOrCopyContent(Buffer2D<T> destination, Buffer2D<T> source)
{
bool swap = MemoryGroup<T>.SwapOrCopyContent(destination.FastMemoryGroup, source.FastMemoryGroup);
SwapOwnData(destination, source, swap);
MemoryGroup<T>.SwapOrCopyContent(destination.FastMemoryGroup, source.FastMemoryGroup);
SwapOwnData(destination, source);
}
[MethodImpl(InliningOptions.ColdPath)]
private Memory<T> GetRowMemorySlow(int y) => this.FastMemoryGroup.GetBoundedSlice(y * (long)this.Width, this.Width);
[MethodImpl(InliningOptions.ColdPath)]
private Memory<T> DangerousGetSingleMemorySlow() => this.FastMemoryGroup.Single();
[MethodImpl(InliningOptions.ColdPath)]
private Span<T> DangerousGetSingleSpanSlow() => this.FastMemoryGroup.Single().Span;
[MethodImpl(InliningOptions.ColdPath)]
private ref T GetElementSlow(int x, int y)
{
Span<T> span = this.GetRowMemorySlow(y).Span;
return ref span[x];
}
[MethodImpl(InliningOptions.ShortMethod)]
private Memory<T> GetRowMemoryCore(int y) => this.FastMemoryGroup.GetBoundedSlice(y * (long)this.Width, this.Width);
private static void SwapOwnData(Buffer2D<T> a, Buffer2D<T> b, bool swapCachedMemory)
private static void SwapOwnData(Buffer2D<T> a, Buffer2D<T> b)
{
Size aSize = a.Size();
Size bSize = b.Size();
@ -255,13 +187,6 @@ namespace SixLabors.ImageSharp.Memory
a.Width = bSize.Width;
a.Height = bSize.Height;
if (swapCachedMemory)
{
Memory<T> aCached = a.cachedMemory;
a.cachedMemory = b.cachedMemory;
b.cachedMemory = aCached;
}
}
}
}

5
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs

@ -50,10 +50,7 @@ namespace SixLabors.ImageSharp.Memory
return ((IList<Memory<T>>)this.source).GetEnumerator();
}
public override void Dispose()
{
this.View.Invalidate();
}
public override void Dispose() => this.View.Invalidate();
}
}
}

136
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs

@ -6,6 +6,7 @@ using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory.Internals;
namespace SixLabors.ImageSharp.Memory
{
@ -17,6 +18,7 @@ namespace SixLabors.ImageSharp.Memory
public sealed class Owned : MemoryGroup<T>, IEnumerable<Memory<T>>
{
private IMemoryOwner<T>[] memoryOwners;
private RefCountedLifetimeGuard groupLifetimeGuard;
public Owned(IMemoryOwner<T>[] memoryOwners, int bufferLength, long totalLength, bool swappable)
: base(bufferLength, totalLength)
@ -26,6 +28,16 @@ namespace SixLabors.ImageSharp.Memory
this.View = new MemoryGroupView<T>(this);
}
public Owned(
UniformUnmanagedMemoryPool pool,
UnmanagedMemoryHandle[] pooledHandles,
int bufferLength,
long totalLength,
int sizeOfLastBuffer,
AllocationOptions options)
: this(CreateBuffers(pooledHandles, bufferLength, sizeOfLastBuffer, options), bufferLength, totalLength, true) =>
this.groupLifetimeGuard = pool.CreateGroupLifetimeGuard(pooledHandles);
public bool Swappable { get; }
private bool IsDisposed => this.memoryOwners == null;
@ -49,11 +61,65 @@ namespace SixLabors.ImageSharp.Memory
}
}
private static IMemoryOwner<T>[] CreateBuffers(
UnmanagedMemoryHandle[] pooledBuffers,
int bufferLength,
int sizeOfLastBuffer,
AllocationOptions options)
{
var result = new IMemoryOwner<T>[pooledBuffers.Length];
for (int i = 0; i < pooledBuffers.Length - 1; i++)
{
var currentBuffer = ObservedBuffer.Create(pooledBuffers[i], bufferLength, options);
result[i] = currentBuffer;
}
var lastBuffer = ObservedBuffer.Create(pooledBuffers[pooledBuffers.Length - 1], sizeOfLastBuffer, options);
result[result.Length - 1] = lastBuffer;
return result;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public override MemoryGroupEnumerator<T> GetEnumerator()
public override MemoryGroupEnumerator<T> GetEnumerator() => new(this);
public override void IncreaseRefCounts()
{
return new MemoryGroupEnumerator<T>(this);
this.EnsureNotDisposed();
if (this.groupLifetimeGuard != null)
{
this.groupLifetimeGuard.AddRef();
}
else
{
foreach (IMemoryOwner<T> memoryOwner in this.memoryOwners)
{
if (memoryOwner is IRefCounted unmanagedBuffer)
{
unmanagedBuffer.AddRef();
}
}
}
}
public override void DecreaseRefCounts()
{
this.EnsureNotDisposed();
if (this.groupLifetimeGuard != null)
{
this.groupLifetimeGuard.ReleaseRef();
}
else
{
foreach (IMemoryOwner<T> memoryOwner in this.memoryOwners)
{
if (memoryOwner is IRefCounted unmanagedBuffer)
{
unmanagedBuffer.ReleaseRef();
}
}
}
}
/// <inheritdoc/>
@ -72,13 +138,21 @@ namespace SixLabors.ImageSharp.Memory
this.View.Invalidate();
foreach (IMemoryOwner<T> memoryOwner in this.memoryOwners)
if (this.groupLifetimeGuard != null)
{
this.groupLifetimeGuard.Dispose();
}
else
{
memoryOwner.Dispose();
foreach (IMemoryOwner<T> memoryOwner in this.memoryOwners)
{
memoryOwner.Dispose();
}
}
this.memoryOwners = null;
this.IsValid = false;
this.groupLifetimeGuard = null;
}
[MethodImpl(InliningOptions.ShortMethod)]
@ -91,10 +165,7 @@ namespace SixLabors.ImageSharp.Memory
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowObjectDisposedException()
{
throw new ObjectDisposedException(nameof(MemoryGroup<T>));
}
private static void ThrowObjectDisposedException() => throw new ObjectDisposedException(nameof(MemoryGroup<T>));
internal static void SwapContents(Owned a, Owned b)
{
@ -104,20 +175,69 @@ namespace SixLabors.ImageSharp.Memory
IMemoryOwner<T>[] tempOwners = a.memoryOwners;
long tempTotalLength = a.TotalLength;
int tempBufferLength = a.BufferLength;
RefCountedLifetimeGuard tempGroupOwner = a.groupLifetimeGuard;
a.memoryOwners = b.memoryOwners;
a.TotalLength = b.TotalLength;
a.BufferLength = b.BufferLength;
a.groupLifetimeGuard = b.groupLifetimeGuard;
b.memoryOwners = tempOwners;
b.TotalLength = tempTotalLength;
b.BufferLength = tempBufferLength;
b.groupLifetimeGuard = tempGroupOwner;
a.View.Invalidate();
b.View.Invalidate();
a.View = new MemoryGroupView<T>(a);
b.View = new MemoryGroupView<T>(b);
}
// When the MemoryGroup points to multiple buffers via `groupLifetimeGuard`,
// the lifetime of the individual buffers is managed by the guard.
// Group buffer IMemoryOwner<T>-s d not manage ownership.
private sealed class ObservedBuffer : MemoryManager<T>
{
private readonly UnmanagedMemoryHandle handle;
private readonly int lengthInElements;
private ObservedBuffer(UnmanagedMemoryHandle handle, int lengthInElements)
{
this.handle = handle;
this.lengthInElements = lengthInElements;
}
public static ObservedBuffer Create(
UnmanagedMemoryHandle handle,
int lengthInElements,
AllocationOptions options)
{
var buffer = new ObservedBuffer(handle, lengthInElements);
if (options.Has(AllocationOptions.Clean))
{
buffer.GetSpan().Clear();
}
return buffer;
}
protected override void Dispose(bool disposing)
{
// No-op.
}
public override unsafe Span<T> GetSpan() => new(this.handle.Pointer, this.lengthInElements);
public override unsafe MemoryHandle Pin(int elementIndex = 0)
{
void* pbData = Unsafe.Add<T>(this.handle.Pointer, elementIndex);
return new MemoryHandle(pbData);
}
public override void Unpin()
{
}
}
}
}
}

112
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs

@ -6,6 +6,7 @@ using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory.Internals;
namespace SixLabors.ImageSharp.Memory
{
@ -67,44 +68,45 @@ namespace SixLabors.ImageSharp.Memory
/// Creates a new memory group, allocating it's buffers with the provided allocator.
/// </summary>
/// <param name="allocator">The <see cref="MemoryAllocator"/> to use.</param>
/// <param name="totalLength">The total length of the buffer.</param>
/// <param name="bufferAlignment">The expected alignment (eg. to make sure image rows fit into single buffers).</param>
/// <param name="totalLengthInElements">The total length of the buffer.</param>
/// <param name="bufferAlignmentInElements">The expected alignment (eg. to make sure image rows fit into single buffers).</param>
/// <param name="options">The <see cref="AllocationOptions"/>.</param>
/// <returns>A new <see cref="MemoryGroup{T}"/>.</returns>
/// <exception cref="InvalidMemoryOperationException">Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator.</exception>
public static MemoryGroup<T> Allocate(
MemoryAllocator allocator,
long totalLength,
int bufferAlignment,
long totalLengthInElements,
int bufferAlignmentInElements,
AllocationOptions options = AllocationOptions.None)
{
int bufferCapacityInBytes = allocator.GetBufferCapacityInBytes();
Guard.NotNull(allocator, nameof(allocator));
Guard.MustBeGreaterThanOrEqualTo(totalLength, 0, nameof(totalLength));
Guard.MustBeGreaterThanOrEqualTo(bufferAlignment, 0, nameof(bufferAlignment));
Guard.MustBeGreaterThanOrEqualTo(totalLengthInElements, 0, nameof(totalLengthInElements));
Guard.MustBeGreaterThanOrEqualTo(bufferAlignmentInElements, 0, nameof(bufferAlignmentInElements));
int blockCapacityInElements = allocator.GetBufferCapacityInBytes() / ElementSize;
int blockCapacityInElements = bufferCapacityInBytes / ElementSize;
if (bufferAlignment > blockCapacityInElements)
if (bufferAlignmentInElements > blockCapacityInElements)
{
throw new InvalidMemoryOperationException(
$"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {bufferAlignment}.");
$"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {bufferAlignmentInElements}.");
}
if (totalLength == 0)
if (totalLengthInElements == 0)
{
var buffers0 = new IMemoryOwner<T>[1] { allocator.Allocate<T>(0, options) };
return new Owned(buffers0, 0, 0, true);
}
int numberOfAlignedSegments = blockCapacityInElements / bufferAlignment;
int bufferLength = numberOfAlignedSegments * bufferAlignment;
if (totalLength > 0 && totalLength < bufferLength)
int numberOfAlignedSegments = blockCapacityInElements / bufferAlignmentInElements;
int bufferLength = numberOfAlignedSegments * bufferAlignmentInElements;
if (totalLengthInElements > 0 && totalLengthInElements < bufferLength)
{
bufferLength = (int)totalLength;
bufferLength = (int)totalLengthInElements;
}
int sizeOfLastBuffer = (int)(totalLength % bufferLength);
long bufferCount = totalLength / bufferLength;
int sizeOfLastBuffer = (int)(totalLengthInElements % bufferLength);
long bufferCount = totalLengthInElements / bufferLength;
if (sizeOfLastBuffer == 0)
{
@ -126,7 +128,75 @@ namespace SixLabors.ImageSharp.Memory
buffers[buffers.Length - 1] = allocator.Allocate<T>(sizeOfLastBuffer, options);
}
return new Owned(buffers, bufferLength, totalLength, true);
return new Owned(buffers, bufferLength, totalLengthInElements, true);
}
public static MemoryGroup<T> CreateContiguous(IMemoryOwner<T> buffer, bool clear)
{
if (clear)
{
buffer.GetSpan().Clear();
}
int length = buffer.Memory.Length;
var buffers = new IMemoryOwner<T>[1] { buffer };
return new Owned(buffers, length, length, true);
}
public static bool TryAllocate(
UniformUnmanagedMemoryPool pool,
long totalLengthInElements,
int bufferAlignmentInElements,
AllocationOptions options,
out MemoryGroup<T> memoryGroup)
{
Guard.NotNull(pool, nameof(pool));
Guard.MustBeGreaterThanOrEqualTo(totalLengthInElements, 0, nameof(totalLengthInElements));
Guard.MustBeGreaterThanOrEqualTo(bufferAlignmentInElements, 0, nameof(bufferAlignmentInElements));
int blockCapacityInElements = pool.BufferLength / ElementSize;
if (bufferAlignmentInElements > blockCapacityInElements)
{
memoryGroup = null;
return false;
}
if (totalLengthInElements == 0)
{
throw new InvalidMemoryOperationException("Allocating 0 length buffer from UniformByteArrayPool is disallowed");
}
int numberOfAlignedSegments = blockCapacityInElements / bufferAlignmentInElements;
int bufferLength = numberOfAlignedSegments * bufferAlignmentInElements;
if (totalLengthInElements > 0 && totalLengthInElements < bufferLength)
{
bufferLength = (int)totalLengthInElements;
}
int sizeOfLastBuffer = (int)(totalLengthInElements % bufferLength);
int bufferCount = (int)(totalLengthInElements / bufferLength);
if (sizeOfLastBuffer == 0)
{
sizeOfLastBuffer = bufferLength;
}
else
{
bufferCount++;
}
UnmanagedMemoryHandle[] arrays = pool.Rent(bufferCount);
if (arrays == null)
{
// Pool is full
memoryGroup = null;
return false;
}
memoryGroup = new Owned(pool, arrays, bufferLength, totalLengthInElements, sizeOfLastBuffer, options);
return true;
}
public static MemoryGroup<T> Wrap(params Memory<T>[] source)
@ -196,5 +266,13 @@ namespace SixLabors.ImageSharp.Memory
return false;
}
}
public virtual void IncreaseRefCounts()
{
}
public virtual void DecreaseRefCounts()
{
}
}
}

69
src/ImageSharp/Memory/MemoryAllocatorExtensions.cs

@ -20,20 +20,50 @@ namespace SixLabors.ImageSharp.Memory
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="width">The buffer width.</param>
/// <param name="height">The buffer height.</param>
/// <param name="preferContiguosImageBuffers">A value indicating whether the allocated buffer should be contiguous, unless bigger than <see cref="int.MaxValue"/>.</param>
/// <param name="options">The allocation options.</param>
/// <returns>The <see cref="Buffer2D{T}"/>.</returns>
public static Buffer2D<T> Allocate2D<T>(
this MemoryAllocator memoryAllocator,
int width,
int height,
bool preferContiguosImageBuffers,
AllocationOptions options = AllocationOptions.None)
where T : struct
{
long groupLength = (long)width * height;
MemoryGroup<T> memoryGroup = memoryAllocator.AllocateGroup<T>(groupLength, width, options);
MemoryGroup<T> memoryGroup;
if (preferContiguosImageBuffers && groupLength < int.MaxValue)
{
IMemoryOwner<T> buffer = memoryAllocator.Allocate<T>((int)groupLength, options);
memoryGroup = MemoryGroup<T>.CreateContiguous(buffer, false);
}
else
{
memoryGroup = memoryAllocator.AllocateGroup<T>(groupLength, width, options);
}
return new Buffer2D<T>(memoryGroup, width, height);
}
/// <summary>
/// Allocates a buffer of value type objects interpreted as a 2D region
/// of <paramref name="width"/> x <paramref name="height"/> elements.
/// </summary>
/// <typeparam name="T">The type of buffer items to allocate.</typeparam>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="width">The buffer width.</param>
/// <param name="height">The buffer height.</param>
/// <param name="options">The allocation options.</param>
/// <returns>The <see cref="Buffer2D{T}"/>.</returns>
public static Buffer2D<T> Allocate2D<T>(
this MemoryAllocator memoryAllocator,
int width,
int height,
AllocationOptions options = AllocationOptions.None)
where T : struct =>
Allocate2D<T>(memoryAllocator, width, height, false, options);
/// <summary>
/// Allocates a buffer of value type objects interpreted as a 2D region
/// of <paramref name="size"/> width x <paramref name="size"/> height elements.
@ -41,14 +71,32 @@ namespace SixLabors.ImageSharp.Memory
/// <typeparam name="T">The type of buffer items to allocate.</typeparam>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="size">The buffer size.</param>
/// <param name="preferContiguosImageBuffers">A value indicating whether the allocated buffer should be contiguous, unless bigger than <see cref="int.MaxValue"/>.</param>
/// <param name="options">The allocation options.</param>
/// <returns>The <see cref="Buffer2D{T}"/>.</returns>
public static Buffer2D<T> Allocate2D<T>(
this MemoryAllocator memoryAllocator,
Size size,
bool preferContiguosImageBuffers,
AllocationOptions options = AllocationOptions.None)
where T : struct =>
Allocate2D<T>(memoryAllocator, size.Width, size.Height, options);
Allocate2D<T>(memoryAllocator, size.Width, size.Height, preferContiguosImageBuffers, options);
/// <summary>
/// Allocates a buffer of value type objects interpreted as a 2D region
/// of <paramref name="size"/> width x <paramref name="size"/> height elements.
/// </summary>
/// <typeparam name="T">The type of buffer items to allocate.</typeparam>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="size">The buffer size.</param>
/// <param name="options">The allocation options.</param>
/// <returns>The <see cref="Buffer2D{T}"/>.</returns>
public static Buffer2D<T> Allocate2D<T>(
this MemoryAllocator memoryAllocator,
Size size,
AllocationOptions options = AllocationOptions.None)
where T : struct =>
Allocate2D<T>(memoryAllocator, size.Width, size.Height, false, options);
internal static Buffer2D<T> Allocate2DOveraligned<T>(
this MemoryAllocator memoryAllocator,
@ -83,22 +131,5 @@ namespace SixLabors.ImageSharp.Memory
int length = (width * pixelSizeInBytes) + paddingInBytes;
return memoryAllocator.Allocate<byte>(length);
}
/// <summary>
/// Allocates a <see cref="MemoryGroup{T}"/>.
/// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use.</param>
/// <param name="totalLength">The total length of the buffer.</param>
/// <param name="bufferAlignment">The expected alignment (eg. to make sure image rows fit into single buffers).</param>
/// <param name="options">The <see cref="AllocationOptions"/>.</param>
/// <returns>A new <see cref="MemoryGroup{T}"/>.</returns>
/// <exception cref="InvalidMemoryOperationException">Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator.</exception>
internal static MemoryGroup<T> AllocateGroup<T>(
this MemoryAllocator memoryAllocator,
long totalLength,
int bufferAlignment,
AllocationOptions options = AllocationOptions.None)
where T : struct
=> MemoryGroup<T>.Allocate(memoryAllocator, totalLength, bufferAlignment, options);
}
}

2
src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs

@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Memory
/// <inheritdoc/>
public override MemoryHandle Pin(int elementIndex = 0)
{
return new MemoryHandle(((T*)this.pointer) + elementIndex);
return new MemoryHandle(((T*)this.pointer) + elementIndex, pinnable: this);
}
/// <inheritdoc/>

72
src/ImageSharp/PixelAccessor{TPixel}.cs

@ -0,0 +1,72 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
{
/// <summary>
/// A delegate to be executed on a <see cref="PixelAccessor{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
public delegate void PixelAccessorAction<TPixel>(PixelAccessor<TPixel> pixelAccessor)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
/// A delegate to be executed on two instances of <see cref="PixelAccessor{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel1">The first pixel type.</typeparam>
/// <typeparam name="TPixel2">The second pixel type.</typeparam>
public delegate void PixelAccessorAction<TPixel1, TPixel2>(
PixelAccessor<TPixel1> pixelAccessor1,
PixelAccessor<TPixel2> pixelAccessor2)
where TPixel1 : unmanaged, IPixel<TPixel1>
where TPixel2 : unmanaged, IPixel<TPixel2>;
/// <summary>
/// A delegate to be executed on three instances of <see cref="PixelAccessor{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel1">The first pixel type.</typeparam>
/// <typeparam name="TPixel2">The second pixel type.</typeparam>
/// <typeparam name="TPixel3">The third pixel type.</typeparam>
public delegate void PixelAccessorAction<TPixel1, TPixel2, TPixel3>(
PixelAccessor<TPixel1> pixelAccessor1,
PixelAccessor<TPixel2> pixelAccessor2,
PixelAccessor<TPixel3> pixelAccessor3)
where TPixel1 : unmanaged, IPixel<TPixel1>
where TPixel2 : unmanaged, IPixel<TPixel2>
where TPixel3 : unmanaged, IPixel<TPixel3>;
/// <summary>
/// Provides efficient access the pixel buffers of an <see cref="Image{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
public ref struct PixelAccessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private Buffer2D<TPixel> buffer;
internal PixelAccessor(Buffer2D<TPixel> buffer) => this.buffer = buffer;
/// <summary>
/// Gets the width of the backing <see cref="Image{TPixel}"/>.
/// </summary>
public int Width => this.buffer.Width;
/// <summary>
/// Gets the height of the backing <see cref="Image{TPixel}"/>.
/// </summary>
public int Height => this.buffer.Height;
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the first pixel on that row.
/// </summary>
/// <param name="rowIndex">The row index.</param>
/// <returns>The <see cref="Span{TPixel}"/>.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when row index is out of range.</exception>
public Span<TPixel> GetRowSpan(int rowIndex) => this.buffer.DangerousGetRowSpan(rowIndex);
}
}

9
src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs

@ -30,12 +30,13 @@ namespace SixLabors.ImageSharp.Processing
Buffer2D<ulong> intImage = configuration.MemoryAllocator.Allocate2D<ulong>(source.Width, source.Height);
ulong sumX0 = 0;
Buffer2D<TPixel> sourceBuffer = source.Frames.RootFrame.PixelBuffer;
using (IMemoryOwner<L8> tempRow = configuration.MemoryAllocator.Allocate<L8>(source.Width))
{
Span<L8> tempSpan = tempRow.GetSpan();
Span<TPixel> sourceRow = source.GetPixelRowSpan(0);
Span<ulong> destRow = intImage.GetRowSpan(0);
Span<TPixel> sourceRow = sourceBuffer.DangerousGetRowSpan(0);
Span<ulong> destRow = intImage.DangerousGetRowSpan(0);
PixelOperations<TPixel>.Instance.ToL8(configuration, sourceRow, tempSpan);
@ -51,8 +52,8 @@ namespace SixLabors.ImageSharp.Processing
// All other rows
for (int y = 1; y < endY; y++)
{
sourceRow = source.GetPixelRowSpan(y);
destRow = intImage.GetRowSpan(y);
sourceRow = sourceBuffer.DangerousGetRowSpan(y);
destRow = intImage.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8(configuration, sourceRow, tempSpan);

12
src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs

@ -52,6 +52,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
// ClusterSize defines the size of cluster to used to check for average. Tweaked to support up to 4k wide pixels and not more. 4096 / 16 is 256 thus the '-1'
byte clusterSize = (byte)Math.Truncate((width / 16f) - 1);
Buffer2D<TPixel> sourceBuffer = source.PixelBuffer;
// Using pooled 2d buffer for integer image table and temp memory to hold Rgb24 converted pixel data.
using (Buffer2D<ulong> intImage = this.Configuration.MemoryAllocator.Allocate2D<ulong>(width, height))
{
@ -61,7 +63,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
ulong sum = 0;
for (int y = startY; y < endY; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
Span<TPixel> row = sourceBuffer.DangerousGetRowSpan(y);
ref TPixel rowRef = ref MemoryMarshal.GetReference(row);
ref TPixel color = ref Unsafe.Add(ref rowRef, x);
color.ToRgba32(ref rgb);
@ -79,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
}
}
var operation = new RowOperation(intersect, source, intImage, upper, lower, thresholdLimit, clusterSize, startX, endX, startY);
var operation = new RowOperation(intersect, source.PixelBuffer, intImage, upper, lower, thresholdLimit, clusterSize, startX, endX, startY);
ParallelRowIterator.IterateRows(
configuration,
intersect,
@ -90,7 +92,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
private readonly struct RowOperation : IRowOperation
{
private readonly Rectangle bounds;
private readonly ImageFrame<TPixel> source;
private readonly Buffer2D<TPixel> source;
private readonly Buffer2D<ulong> intImage;
private readonly TPixel upper;
private readonly TPixel lower;
@ -103,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
[MethodImpl(InliningOptions.ShortMethod)]
public RowOperation(
Rectangle bounds,
ImageFrame<TPixel> source,
Buffer2D<TPixel> source,
Buffer2D<ulong> intImage,
TPixel upper,
TPixel lower,
@ -130,7 +132,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
public void Invoke(int y)
{
Rgba32 rgb = default;
Span<TPixel> pixelRow = this.source.GetPixelRowSpan(y);
Span<TPixel> pixelRow = this.source.DangerousGetRowSpan(y);
for (int x = this.startX; x < this.endX; x++)
{

9
src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs

@ -4,6 +4,7 @@
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Binarization
@ -41,7 +42,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
var operation = new RowOperation(
interest.X,
source,
source.PixelBuffer,
upper,
lower,
threshold,
@ -59,7 +60,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
/// </summary>
private readonly struct RowOperation : IRowOperation<Rgb24>
{
private readonly ImageFrame<TPixel> source;
private readonly Buffer2D<TPixel> source;
private readonly TPixel upper;
private readonly TPixel lower;
private readonly byte threshold;
@ -70,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
[MethodImpl(InliningOptions.ShortMethod)]
public RowOperation(
int startX,
ImageFrame<TPixel> source,
Buffer2D<TPixel> source,
TPixel upper,
TPixel lower,
byte threshold,
@ -93,7 +94,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
TPixel upper = this.upper;
TPixel lower = this.lower;
Span<TPixel> rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length);
Span<TPixel> rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length);
PixelOperations<TPixel>.Instance.ToRgb24(this.configuration, rowSpan, span);
switch (this.mode)

16
src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs

@ -231,11 +231,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
int kernelSize = this.kernel.Length;
// Clear the target buffer for each row run
Span<ComplexVector4> targetBuffer = this.targetValues.GetRowSpan(y);
Span<ComplexVector4> targetBuffer = this.targetValues.DangerousGetRowSpan(y);
targetBuffer.Clear();
// Execute the bulk pixel format conversion for the current row
Span<TPixel> sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth);
Span<TPixel> sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRow, span);
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(span);
@ -295,7 +295,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)
{
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
Span<TPixel> targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span, PixelConversionModifiers.Premultiply);
ref Vector4 baseRef = ref MemoryMarshal.GetReference(span);
@ -335,7 +335,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)
{
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
Span<TPixel> targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span, PixelConversionModifiers.Premultiply);
@ -378,8 +378,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Vector4 low = Vector4.Zero;
var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
Span<TPixel> targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
Span<Vector4> sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X);
Span<TPixel> targetPixelSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X);
Span<Vector4> sourceRowSpan = this.sourceValues.DangerousGetRowSpan(y).Slice(this.bounds.X);
ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan);
for (int x = 0; x < this.bounds.Width; x++)
@ -422,13 +422,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
[MethodImpl(InliningOptions.ShortMethod)]
public unsafe void Invoke(int y)
{
Span<Vector4> sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
Span<Vector4> sourceRowSpan = this.sourceValues.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan);
Numerics.Clamp(MemoryMarshal.Cast<Vector4, float>(sourceRowSpan), 0, float.PositiveInfinity);
Numerics.CubeRootOnXYZ(sourceRowSpan);
Span<TPixel> targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
Span<TPixel> targetPixelSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, sourceRowSpan.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply);
}

10
src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs

@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
// Get the precalculated source sample row for this kernel row and copy to our buffer.
int sampleY = Unsafe.Add(ref sampleRowBase, kY);
sourceRow = this.sourcePixels.GetRowSpan(sampleY).Slice(boundsX, boundsWidth);
sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleY).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer);
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer);
@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// Now we need to combine the values and copy the original alpha values
// from the source row.
sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth);
sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer);
for (int x = 0; x < sourceRow.Length; x++)
@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
target.W = Unsafe.Add(ref MemoryMarshal.GetReference(sourceBuffer), x).W;
}
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth);
Span<TPixel> targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRowSpan);
}
@ -152,7 +152,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
// Get the precalculated source sample row for this kernel row and copy to our buffer.
int sampleY = Unsafe.Add(ref sampleRowBase, kY);
Span<TPixel> sourceRow = this.sourcePixels.GetRowSpan(sampleY).Slice(boundsX, boundsWidth);
Span<TPixel> sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleY).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer);
Numerics.Premultiply(sourceBuffer);
@ -186,7 +186,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Numerics.UnPremultiply(targetYBuffer);
Span<TPixel> targetRow = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth);
Span<TPixel> targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRow);
}
}

20
src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs

@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
targetBuffer.Clear();
// Get the precalculated source sample row for this kernel row and copy to our buffer.
Span<TPixel> sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth);
Span<TPixel> sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer);
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer);
@ -187,7 +187,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}
// Now we need to copy the original alpha values from the source row.
sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth);
sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer);
targetStart = ref MemoryMarshal.GetReference(targetBuffer);
@ -200,7 +200,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
}
Span<TPixel> targetRow = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth);
Span<TPixel> targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow);
}
@ -219,7 +219,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
targetBuffer.Clear();
// Get the precalculated source sample row for this kernel row and copy to our buffer.
Span<TPixel> sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth);
Span<TPixel> sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer);
Numerics.Premultiply(sourceBuffer);
@ -252,7 +252,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Numerics.UnPremultiply(targetBuffer);
Span<TPixel> targetRow = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth);
Span<TPixel> targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow);
}
}
@ -327,7 +327,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd))
{
// Get the precalculated source sample row for this kernel row and copy to our buffer.
sourceRow = this.sourcePixels.GetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth);
sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer);
@ -349,7 +349,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}
// Now we need to copy the original alpha values from the source row.
sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth);
sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer);
{
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer);
@ -364,7 +364,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}
}
Span<TPixel> targetRow = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth);
Span<TPixel> targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow);
}
@ -392,7 +392,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd))
{
// Get the precalculated source sample row for this kernel row and copy to our buffer.
sourceRow = this.sourcePixels.GetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth);
sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer);
@ -417,7 +417,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Numerics.UnPremultiply(targetBuffer);
Span<TPixel> targetRow = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth);
Span<TPixel> targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow);
}
}

8
src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs

@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Span<Vector4> targetBuffer = span.Slice(this.bounds.Width);
ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span);
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth);
Span<TPixel> targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth);
var state = new ConvolutionState(in this.kernel, this.map);
int row = y - this.bounds.Y;
@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
// Get the precalculated source sample row for this kernel row and copy to our buffer.
int offsetY = Unsafe.Add(ref sampleRowBase, kY);
sourceRow = this.sourcePixels.GetRowSpan(offsetY).Slice(boundsX, boundsWidth);
sourceRow = this.sourcePixels.DangerousGetRowSpan(offsetY).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer);
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer);
@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}
// Now we need to copy the original alpha values from the source row.
sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth);
sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer);
for (int x = 0; x < sourceRow.Length; x++)
@ -174,7 +174,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
// Get the precalculated source sample row for this kernel row and copy to our buffer.
int offsetY = Unsafe.Add(ref sampleRowBase, kY);
Span<TPixel> sourceRow = this.sourcePixels.GetRowSpan(offsetY).Slice(boundsX, boundsWidth);
Span<TPixel> sourceRow = this.sourcePixels.DangerousGetRowSpan(offsetY).Slice(boundsX, boundsWidth);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer);
Numerics.Premultiply(sourceBuffer);

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save