Browse Source

Merge branch 'master' into js/png-decode-to-type

pull/1861/head
James Jackson-South 5 years ago
committed by GitHub
parent
commit
3a76e34ab1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      .gitattributes
  2. 4
      Directory.Build.props
  3. 2
      shared-infrastructure
  4. 4
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  5. 7
      src/ImageSharp/Advanced/AotCompilerTools.cs
  6. 14
      src/ImageSharp/Common/Helpers/DebugGuard.cs
  7. 4
      src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs
  8. 5
      src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs
  9. 42
      src/ImageSharp/Configuration.cs
  10. 18
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  11. 20
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  12. 4
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  13. 4
      src/ImageSharp/Formats/Gif/LzwDecoder.cs
  14. 2
      src/ImageSharp/Formats/Gif/LzwEncoder.cs
  15. 104
      src/ImageSharp/Formats/ImageExtensions.Save.cs
  16. 1
      src/ImageSharp/Formats/ImageExtensions.Save.tt
  17. 8
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
  18. 10
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
  19. 8
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
  20. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
  21. 2
      src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs
  22. 194
      src/ImageSharp/Formats/Pbm/BinaryDecoder.cs
  23. 208
      src/ImageSharp/Formats/Pbm/BinaryEncoder.cs
  24. 65
      src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs
  25. 26
      src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs
  26. 21
      src/ImageSharp/Formats/Pbm/MetadataExtensions.cs
  27. 26
      src/ImageSharp/Formats/Pbm/PbmColorType.cs
  28. 26
      src/ImageSharp/Formats/Pbm/PbmComponentType.cs
  29. 19
      src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs
  30. 28
      src/ImageSharp/Formats/Pbm/PbmConstants.cs
  31. 79
      src/ImageSharp/Formats/Pbm/PbmDecoder.cs
  32. 195
      src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs
  33. 69
      src/ImageSharp/Formats/Pbm/PbmEncoder.cs
  34. 187
      src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
  35. 21
      src/ImageSharp/Formats/Pbm/PbmEncoding.cs
  36. 37
      src/ImageSharp/Formats/Pbm/PbmFormat.cs
  37. 36
      src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs
  38. 46
      src/ImageSharp/Formats/Pbm/PbmMetadata.cs
  39. 198
      src/ImageSharp/Formats/Pbm/PlainDecoder.cs
  40. 251
      src/ImageSharp/Formats/Pbm/PlainEncoder.cs
  41. 5
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  42. 60
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  43. 20
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  44. 8
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  45. 2
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
  46. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs
  47. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs
  48. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs
  49. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs
  50. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs
  51. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs
  52. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs
  53. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs
  54. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs
  55. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs
  56. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs
  57. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs
  58. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs
  59. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs
  60. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs
  61. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs
  62. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs
  63. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs
  64. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs
  65. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs
  66. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs
  67. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs
  68. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs
  69. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs
  70. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs
  71. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs
  72. 26
      src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs
  73. 2
      src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs
  74. 4
      src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs
  75. 3
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  76. 2
      src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs
  77. 788
      src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs
  78. 4
      src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs
  79. 11
      src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
  80. 152
      src/ImageSharp/ImageFrame{TPixel}.cs
  81. 2
      src/ImageSharp/ImageSharp.csproj
  82. 137
      src/ImageSharp/Image{TPixel}.cs
  83. 7
      src/ImageSharp/IndexedImageFrame{TPixel}.cs
  84. 7
      src/ImageSharp/Memory/Allocators/AllocationOptions.cs
  85. 10
      src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs
  86. 105
      src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs
  87. 76
      src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs
  88. 185
      src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs
  89. 18
      src/ImageSharp/Memory/Allocators/IManagedByteBuffer.cs
  90. 3
      src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs
  91. 20
      src/ImageSharp/Memory/Allocators/Internals/BasicByteBuffer.cs
  92. 115
      src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs
  93. 21
      src/ImageSharp/Memory/Allocators/Internals/IRefCounted.cs
  94. 3
      src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs
  95. 56
      src/ImageSharp/Memory/Allocators/Internals/RefCountedLifetimeGuard.cs
  96. 80
      src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs
  97. 65
      src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs
  98. 356
      src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs
  99. 27
      src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs
  100. 80
      src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.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>

2
shared-infrastructure

@ -1 +1 @@
Subproject commit a042aba176cdb840d800c6ed4cfe41a54fb7b1e3
Subproject commit 59ce17f5a4e1f956811133f41add7638e74c2836

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));

7
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -10,6 +10,7 @@ using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff;
@ -200,6 +201,7 @@ namespace SixLabors.ImageSharp.Advanced
default(BmpEncoderCore).Encode<TPixel>(default, default, default);
default(GifEncoderCore).Encode<TPixel>(default, default, default);
default(JpegEncoderCore).Encode<TPixel>(default, default, default);
default(PbmEncoderCore).Encode<TPixel>(default, default, default);
default(PngEncoderCore).Encode<TPixel>(default, default, default);
default(TgaEncoderCore).Encode<TPixel>(default, default, default);
default(TiffEncoderCore).Encode<TPixel>(default, default, default);
@ -217,6 +219,7 @@ namespace SixLabors.ImageSharp.Advanced
default(BmpDecoderCore).Decode<TPixel>(default, default, default);
default(GifDecoderCore).Decode<TPixel>(default, default, default);
default(JpegDecoderCore).Decode<TPixel>(default, default, default);
default(PbmDecoderCore).Decode<TPixel>(default, default, default);
default(PngDecoderCore).Decode<TPixel>(default, default, default);
default(TgaDecoderCore).Decode<TPixel>(default, default, default);
default(TiffDecoderCore).Decode<TPixel>(default, default, default);
@ -234,6 +237,7 @@ namespace SixLabors.ImageSharp.Advanced
AotCompileImageEncoder<TPixel, BmpEncoder>();
AotCompileImageEncoder<TPixel, GifEncoder>();
AotCompileImageEncoder<TPixel, JpegEncoder>();
AotCompileImageEncoder<TPixel, PbmEncoder>();
AotCompileImageEncoder<TPixel, PngEncoder>();
AotCompileImageEncoder<TPixel, TgaEncoder>();
AotCompileImageEncoder<TPixel, TiffEncoder>();
@ -251,6 +255,7 @@ namespace SixLabors.ImageSharp.Advanced
AotCompileImageDecoder<TPixel, BmpDecoder>();
AotCompileImageDecoder<TPixel, GifDecoder>();
AotCompileImageDecoder<TPixel, JpegDecoder>();
AotCompileImageDecoder<TPixel, PbmDecoder>();
AotCompileImageDecoder<TPixel, PngDecoder>();
AotCompileImageDecoder<TPixel, TgaDecoder>();
AotCompileImageDecoder<TPixel, TiffDecoder>();
@ -529,7 +534,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,

42
src/ImageSharp/Configuration.cs

@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff;
@ -26,10 +27,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 +97,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 +127,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 +197,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,
@ -178,6 +210,7 @@ namespace SixLabors.ImageSharp
/// <see cref="JpegConfigurationModule"/>
/// <see cref="GifConfigurationModule"/>
/// <see cref="BmpConfigurationModule"/>.
/// <see cref="PbmConfigurationModule"/>.
/// <see cref="TgaConfigurationModule"/>.
/// <see cref="TiffConfigurationModule"/>.
/// <see cref="WebpConfigurationModule"/>.
@ -188,6 +221,7 @@ namespace SixLabors.ImageSharp
new JpegConfigurationModule(),
new GifConfigurationModule(),
new BmpConfigurationModule(),
new PbmConfigurationModule(),
new TgaConfigurationModule(),
new TiffConfigurationModule(),
new WebpConfigurationModule());

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++)

104
src/ImageSharp/Formats/ImageExtensions.Save.cs

@ -10,6 +10,7 @@ using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Webp;
@ -331,6 +332,109 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Pbm format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsPbm(this Image source, string path) => SaveAsPbm(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Pbm format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsPbmAsync(this Image source, string path) => SaveAsPbmAsync(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Pbm format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsPbmAsync(this Image source, string path, CancellationToken cancellationToken)
=> SaveAsPbmAsync(source, path, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Pbm format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsPbm(this Image source, string path, PbmEncoder encoder) =>
source.Save(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Pbm format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsPbmAsync(this Image source, string path, PbmEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Pbm format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsPbm(this Image source, Stream stream)
=> SaveAsPbm(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the Pbm format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsPbmAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
=> SaveAsPbmAsync(source, stream, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Pbm format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static void SaveAsPbm(this Image source, Stream stream, PbmEncoder encoder)
=> source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Pbm format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsPbmAsync(this Image source, Stream stream, PbmEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Png format.
/// </summary>

1
src/ImageSharp/Formats/ImageExtensions.Save.tt

@ -15,6 +15,7 @@ using SixLabors.ImageSharp.Advanced;
"Bmp",
"Gif",
"Jpeg",
"Pbm",
"Png",
"Tga",
"Webp",

8
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs

@ -223,12 +223,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
this.ComponentCount = componentBuffers.Count;
this.Component0 = componentBuffers[0].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 ? componentBuffers[1].GetRowSpan(row) : this.Component0;
this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].GetRowSpan(row) : this.Component0;
this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].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));
}
}

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++);
}
}

194
src/ImageSharp/Formats/Pbm/BinaryDecoder.cs

@ -0,0 +1,194 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <summary>
/// Pixel decoding methods for the PBM binary encoding.
/// </summary>
internal class BinaryDecoder
{
private static L8 white = new(255);
private static L8 black = new(0);
/// <summary>
/// Decode the specified pixels.
/// </summary>
/// <typeparam name="TPixel">The type of pixel to encode to.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="pixels">The pixel array to encode into.</param>
/// <param name="stream">The stream to read the data from.</param>
/// <param name="colorType">The ColorType to decode.</param>
/// <param name="componentType">Data type of the pixles components.</param>
/// <exception cref="InvalidImageContentException">
/// Thrown if an invalid combination of setting is requested.
/// </exception>
public static void Process<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType)
where TPixel : unmanaged, IPixel<TPixel>
{
if (colorType == PbmColorType.Grayscale)
{
if (componentType == PbmComponentType.Byte)
{
ProcessGrayscale(configuration, pixels, stream);
}
else
{
ProcessWideGrayscale(configuration, pixels, stream);
}
}
else if (colorType == PbmColorType.Rgb)
{
if (componentType == PbmComponentType.Byte)
{
ProcessRgb(configuration, pixels, stream);
}
else
{
ProcessWideRgb(configuration, pixels, stream);
}
}
else
{
ProcessBlackAndWhite(configuration, pixels, stream);
}
}
private static void ProcessGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
const int bytesPerPixel = 1;
int width = pixels.Width;
int height = pixels.Height;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel);
Span<byte> rowSpan = row.GetSpan();
for (int y = 0; y < height; y++)
{
stream.Read(rowSpan);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromL8Bytes(
configuration,
rowSpan,
pixelSpan,
width);
}
}
private static void ProcessWideGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
const int bytesPerPixel = 2;
int width = pixels.Width;
int height = pixels.Height;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel);
Span<byte> rowSpan = row.GetSpan();
for (int y = 0; y < height; y++)
{
stream.Read(rowSpan);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromL16Bytes(
configuration,
rowSpan,
pixelSpan,
width);
}
}
private static void ProcessRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
const int bytesPerPixel = 3;
int width = pixels.Width;
int height = pixels.Height;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel);
Span<byte> rowSpan = row.GetSpan();
for (int y = 0; y < height; y++)
{
stream.Read(rowSpan);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromRgb24Bytes(
configuration,
rowSpan,
pixelSpan,
width);
}
}
private static void ProcessWideRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
const int bytesPerPixel = 6;
int width = pixels.Width;
int height = pixels.Height;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel);
Span<byte> rowSpan = row.GetSpan();
for (int y = 0; y < height; y++)
{
stream.Read(rowSpan);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromRgb48Bytes(
configuration,
rowSpan,
pixelSpan,
width);
}
}
private static void ProcessBlackAndWhite<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = pixels.Width;
int height = pixels.Height;
int startBit = 0;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width);
Span<L8> rowSpan = row.GetSpan();
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width;)
{
int raw = stream.ReadByte();
int bit = startBit;
startBit = 0;
for (; bit < 8; bit++)
{
bool bitValue = (raw & (0x80 >> bit)) != 0;
rowSpan[x] = bitValue ? black : white;
x++;
if (x == width)
{
startBit = (bit + 1) & 7; // Round off to below 8.
if (startBit != 0)
{
stream.Seek(-1, System.IO.SeekOrigin.Current);
}
break;
}
}
}
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromL8(
configuration,
rowSpan,
pixelSpan);
}
}
}
}

208
src/ImageSharp/Formats/Pbm/BinaryEncoder.cs

@ -0,0 +1,208 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <summary>
/// Pixel encoding methods for the PBM binary encoding.
/// </summary>
internal class BinaryEncoder
{
/// <summary>
/// Decode pixels into the PBM binary encoding.
/// </summary>
/// <typeparam name="TPixel">The type of input pixel.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="stream">The bytestream to write to.</param>
/// <param name="image">The input image.</param>
/// <param name="colorType">The ColorType to use.</param>
/// <param name="componentType">Data type of the pixles components.</param>
/// <exception cref="InvalidImageContentException">
/// Thrown if an invalid combination of setting is requested.
/// </exception>
public static void WritePixels<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image, PbmColorType colorType, PbmComponentType componentType)
where TPixel : unmanaged, IPixel<TPixel>
{
if (colorType == PbmColorType.Grayscale)
{
if (componentType == PbmComponentType.Byte)
{
WriteGrayscale(configuration, stream, image);
}
else
{
WriteWideGrayscale(configuration, stream, image);
}
}
else if (colorType == PbmColorType.Rgb)
{
if (componentType == PbmComponentType.Byte)
{
WriteRgb(configuration, stream, image);
}
else
{
WriteWideRgb(configuration, stream, image);
}
}
else
{
WriteBlackAndWhite(configuration, stream, image);
}
}
private static void WriteGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width);
Span<byte> rowSpan = row.GetSpan();
for (int y = 0; y < height; y++)
{
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8Bytes(
configuration,
pixelSpan,
rowSpan,
width);
stream.Write(rowSpan);
}
}
private static void WriteWideGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
const int bytesPerPixel = 2;
int width = image.Width;
int height = image.Height;
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel);
Span<byte> rowSpan = row.GetSpan();
for (int y = 0; y < height; y++)
{
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL16Bytes(
configuration,
pixelSpan,
rowSpan,
width);
stream.Write(rowSpan);
}
}
private static void WriteRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
const int bytesPerPixel = 3;
int width = image.Width;
int height = image.Height;
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel);
Span<byte> rowSpan = row.GetSpan();
for (int y = 0; y < height; y++)
{
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgb24Bytes(
configuration,
pixelSpan,
rowSpan,
width);
stream.Write(rowSpan);
}
}
private static void WriteWideRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
const int bytesPerPixel = 6;
int width = image.Width;
int height = image.Height;
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<byte> row = allocator.Allocate<byte>(width * bytesPerPixel);
Span<byte> rowSpan = row.GetSpan();
for (int y = 0; y < height; y++)
{
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgb48Bytes(
configuration,
pixelSpan,
rowSpan,
width);
stream.Write(rowSpan);
}
}
private static void WriteBlackAndWhite<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width);
Span<L8> rowSpan = row.GetSpan();
int previousValue = 0;
int startBit = 0;
for (int y = 0; y < height; y++)
{
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8(
configuration,
pixelSpan,
rowSpan);
for (int x = 0; x < width;)
{
int value = previousValue;
for (int i = startBit; i < 8; i++)
{
if (rowSpan[x].PackedValue < 128)
{
value |= 0x80 >> i;
}
x++;
if (x == width)
{
previousValue = value;
startBit = (i + 1) & 7; // Round off to below 8.
break;
}
}
if (startBit == 0)
{
stream.WriteByte((byte)value);
previousValue = 0;
}
}
}
}
}
}

65
src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs

@ -0,0 +1,65 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <summary>
/// Extensions methods for <see cref="BufferedReadStream"/>.
/// </summary>
internal static class BufferedReadStreamExtensions
{
/// <summary>
/// Skip over any whitespace or any comments.
/// </summary>
public static void SkipWhitespaceAndComments(this BufferedReadStream stream)
{
bool isWhitespace;
do
{
int val = stream.ReadByte();
// Comments start with '#' and end at the next new-line.
if (val == 0x23)
{
int innerValue;
do
{
innerValue = stream.ReadByte();
}
while (innerValue != 0x0a);
// Continue searching for whitespace.
val = innerValue;
}
isWhitespace = val is 0x09 or 0x0a or 0x0d or 0x20;
}
while (isWhitespace);
stream.Seek(-1, SeekOrigin.Current);
}
/// <summary>
/// Read a decimal text value.
/// </summary>
/// <returns>The integer value of the decimal.</returns>
public static int ReadDecimal(this BufferedReadStream stream)
{
int value = 0;
while (true)
{
int current = stream.ReadByte() - 0x30;
if ((uint)current > 9)
{
break;
}
value = (value * 10) + current;
}
return value;
}
}
}

26
src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs

@ -0,0 +1,26 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <summary>
/// Configuration options for use during PBM encoding.
/// </summary>
internal interface IPbmEncoderOptions
{
/// <summary>
/// Gets the encoding of the pixels.
/// </summary>
PbmEncoding? Encoding { get; }
/// <summary>
/// Gets the Color type of the resulting image.
/// </summary>
PbmColorType? ColorType { get; }
/// <summary>
/// Gets the Data Type of the pixel components.
/// </summary>
PbmComponentType? ComponentType { get; }
}
}

21
src/ImageSharp/Formats/Pbm/MetadataExtensions.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Extension methods for the <see cref="ImageMetadata"/> type.
/// </summary>
public static partial class MetadataExtensions
{
/// <summary>
/// Gets the pbm format specific metadata for the image.
/// </summary>
/// <param name="metadata">The metadata this method extends.</param>
/// <returns>The <see cref="PbmMetadata"/>.</returns>
public static PbmMetadata GetPbmMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(PbmFormat.Instance);
}
}

26
src/ImageSharp/Formats/Pbm/PbmColorType.cs

@ -0,0 +1,26 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <summary>
/// Provides enumeration of available PBM color types.
/// </summary>
public enum PbmColorType : byte
{
/// <summary>
/// PBM
/// </summary>
BlackAndWhite = 0,
/// <summary>
/// PGM - Greyscale. Single component.
/// </summary>
Grayscale = 1,
/// <summary>
/// PPM - RGB Color. 3 components.
/// </summary>
Rgb = 2,
}
}

26
src/ImageSharp/Formats/Pbm/PbmComponentType.cs

@ -0,0 +1,26 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <summary>
/// The data type of the components of the pixels.
/// </summary>
public enum PbmComponentType : byte
{
/// <summary>
/// Single bit per pixel, exclusively for <see cref="PbmColorType.BlackAndWhite"/>.
/// </summary>
Bit = 0,
/// <summary>
/// 8 bits unsigned integer per component.
/// </summary>
Byte = 1,
/// <summary>
/// 16 bits unsigned integer per component.
/// </summary>
Short = 2
}
}

19
src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <summary>
/// Registers the image encoders, decoders and mime type detectors for the Pbm format.
/// </summary>
public sealed class PbmConfigurationModule : IConfigurationModule
{
/// <inheritdoc/>
public void Configure(Configuration configuration)
{
configuration.ImageFormatsManager.SetEncoder(PbmFormat.Instance, new PbmEncoder());
configuration.ImageFormatsManager.SetDecoder(PbmFormat.Instance, new PbmDecoder());
configuration.ImageFormatsManager.AddImageFormatDetector(new PbmImageFormatDetector());
}
}
}

28
src/ImageSharp/Formats/Pbm/PbmConstants.cs

@ -0,0 +1,28 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <summary>
/// Contains PBM constant values defined in the specification.
/// </summary>
internal static class PbmConstants
{
/// <summary>
/// The maximum allowable pixel value of a ppm image.
/// </summary>
public const ushort MaxLength = 65535;
/// <summary>
/// The list of mimetypes that equate to a ppm.
/// </summary>
public static readonly IEnumerable<string> MimeTypes = new[] { "image/x-portable-pixmap", "image/x-portable-anymap" };
/// <summary>
/// The list of file extensions that equate to a ppm.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "ppm", "pbm", "pgm" };
}
}

79
src/ImageSharp/Formats/Pbm/PbmDecoder.cs

@ -0,0 +1,79 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <summary>
/// Image decoder for reading PGM, PBM or PPM bitmaps from a stream. These images are from
/// the family of PNM images.
/// <list type="bullet">
/// <item>
/// <term>PBM</term>
/// <description>Black and white images.</description>
/// </item>
/// <item>
/// <term>PGM</term>
/// <description>Grayscale images.</description>
/// </item>
/// <item>
/// <term>PPM</term>
/// <description>Color images, with RGB pixels.</description>
/// </item>
/// </list>
/// The specification of these images is found at <seealso href="http://netpbm.sourceforge.net/doc/pnm.html"/>.
/// </summary>
public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector
{
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new PbmDecoderCore(configuration);
return decoder.Decode<TPixel>(configuration, stream);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgb24>(configuration, stream);
/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new PbmDecoderCore(configuration);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new PbmDecoderCore(configuration);
return decoder.Identify(configuration, stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new PbmDecoderCore(configuration);
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
}
}
}

195
src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs

@ -0,0 +1,195 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Threading;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <summary>
/// Performs the PBM decoding operation.
/// </summary>
internal sealed class PbmDecoderCore : IImageDecoderInternals
{
private int maxPixelValue;
/// <summary>
/// Initializes a new instance of the <see cref="PbmDecoderCore" /> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
public PbmDecoderCore(Configuration configuration) => this.Configuration = configuration ?? Configuration.Default;
/// <inheritdoc />
public Configuration Configuration { get; }
/// <summary>
/// Gets the colortype to use
/// </summary>
public PbmColorType ColorType { get; private set; }
/// <summary>
/// Gets the size of the pixel array
/// </summary>
public Size PixelSize { get; private set; }
/// <summary>
/// Gets the component data type
/// </summary>
public PbmComponentType ComponentType { get; private set; }
/// <summary>
/// Gets the Encoding of pixels
/// </summary>
public PbmEncoding Encoding { get; private set; }
/// <summary>
/// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance.
/// </summary>
public ImageMetadata Metadata { get; private set; }
/// <inheritdoc/>
Size IImageDecoderInternals.Dimensions => this.PixelSize;
private bool NeedsUpscaling => this.ColorType != PbmColorType.BlackAndWhite && this.maxPixelValue is not 255 and not 65535;
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
this.ProcessHeader(stream);
var image = new Image<TPixel>(this.Configuration, this.PixelSize.Width, this.PixelSize.Height, this.Metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
this.ProcessPixels(stream, pixels);
if (this.NeedsUpscaling)
{
this.ProcessUpscaling(image);
}
return image;
}
/// <inheritdoc/>
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ProcessHeader(stream);
// BlackAndWhite pixels are encoded into a byte.
int bitsPerPixel = this.ComponentType == PbmComponentType.Short ? 16 : 8;
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.PixelSize.Width, this.PixelSize.Height, this.Metadata);
}
/// <summary>
/// Processes the ppm header.
/// </summary>
/// <param name="stream">The input stream.</param>
private void ProcessHeader(BufferedReadStream stream)
{
Span<byte> buffer = stackalloc byte[2];
int bytesRead = stream.Read(buffer);
if (bytesRead != 2 || buffer[0] != 'P')
{
throw new InvalidImageContentException("Empty or not an PPM image.");
}
switch ((char)buffer[1])
{
case '1':
// Plain PBM format: 1 component per pixel, boolean value ('0' or '1').
this.ColorType = PbmColorType.BlackAndWhite;
this.Encoding = PbmEncoding.Plain;
break;
case '2':
// Plain PGM format: 1 component per pixel, in decimal text.
this.ColorType = PbmColorType.Grayscale;
this.Encoding = PbmEncoding.Plain;
break;
case '3':
// Plain PPM format: 3 components per pixel, in decimal text.
this.ColorType = PbmColorType.Rgb;
this.Encoding = PbmEncoding.Plain;
break;
case '4':
// Binary PBM format: 1 component per pixel, 8 pixels per byte.
this.ColorType = PbmColorType.BlackAndWhite;
this.Encoding = PbmEncoding.Binary;
break;
case '5':
// Binary PGM format: 1 components per pixel, in binary integers.
this.ColorType = PbmColorType.Grayscale;
this.Encoding = PbmEncoding.Binary;
break;
case '6':
// Binary PPM format: 3 components per pixel, in binary integers.
this.ColorType = PbmColorType.Rgb;
this.Encoding = PbmEncoding.Binary;
break;
case '7':
// PAM image: sequence of images.
// Not implemented yet
default:
throw new InvalidImageContentException("Unknown of not implemented image type encountered.");
}
stream.SkipWhitespaceAndComments();
int width = stream.ReadDecimal();
stream.SkipWhitespaceAndComments();
int height = stream.ReadDecimal();
stream.SkipWhitespaceAndComments();
if (this.ColorType != PbmColorType.BlackAndWhite)
{
this.maxPixelValue = stream.ReadDecimal();
if (this.maxPixelValue > 255)
{
this.ComponentType = PbmComponentType.Short;
}
else
{
this.ComponentType = PbmComponentType.Byte;
}
stream.SkipWhitespaceAndComments();
}
else
{
this.ComponentType = PbmComponentType.Bit;
}
this.PixelSize = new Size(width, height);
this.Metadata = new ImageMetadata();
PbmMetadata meta = this.Metadata.GetPbmMetadata();
meta.Encoding = this.Encoding;
meta.ColorType = this.ColorType;
meta.ComponentType = this.ComponentType;
}
private void ProcessPixels<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
if (this.Encoding == PbmEncoding.Binary)
{
BinaryDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.ComponentType);
}
else
{
PlainDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.ComponentType);
}
}
private void ProcessUpscaling<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
int maxAllocationValue = this.ComponentType == PbmComponentType.Short ? 65535 : 255;
float factor = maxAllocationValue / this.maxPixelValue;
image.Mutate(x => x.Brightness(factor));
}
}
}

69
src/ImageSharp/Formats/Pbm/PbmEncoder.cs

@ -0,0 +1,69 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <summary>
/// Image encoder for writing an image to a stream as PGM, PBM or PPM bitmap. These images are from
/// the family of PNM images.
/// <para>
/// The PNM formats are a fairly simple image format. They share a plain text header, consisting of:
/// signature, width, height and max_pixel_value only. The pixels follow thereafter and can be in
/// plain text decimals separated by spaces, or binary encoded.
/// <list type="bullet">
/// <item>
/// <term>PBM</term>
/// <description>Black and white images, with 1 representing black and 0 representing white.</description>
/// </item>
/// <item>
/// <term>PGM</term>
/// <description>Grayscale images, scaling from 0 to max_pixel_value, 0 representing black and max_pixel_value representing white.</description>
/// </item>
/// <item>
/// <term>PPM</term>
/// <description>Color images, with RGB pixels (in that order), with 0 representing black and 2 representing full color.</description>
/// </item>
/// </list>
/// </para>
/// The specification of these images is found at <seealso href="http://netpbm.sourceforge.net/doc/pnm.html"/>.
/// </summary>
public sealed class PbmEncoder : IImageEncoder, IPbmEncoderOptions
{
/// <summary>
/// Gets or sets the Encoding of the pixels.
/// </summary>
public PbmEncoding? Encoding { get; set; }
/// <summary>
/// Gets or sets the Color type of the resulting image.
/// </summary>
public PbmColorType? ColorType { get; set; }
/// <summary>
/// Gets or sets the data type of the pixels components.
/// </summary>
public PbmComponentType? ComponentType { get; set; }
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new PbmEncoderCore(image.GetConfiguration(), this);
encoder.Encode(image, stream);
}
/// <inheritdoc/>
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new PbmEncoderCore(image.GetConfiguration(), this);
return encoder.EncodeAsync(image, stream, cancellationToken);
}
}
}

187
src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs

@ -0,0 +1,187 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Text;
using System.IO;
using System.Threading;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <summary>
/// Image encoder for writing an image to a stream as a PGM, PBM, PPM or PAM bitmap.
/// </summary>
internal sealed class PbmEncoderCore : IImageEncoderInternals
{
private const byte NewLine = (byte)'\n';
private const byte Space = (byte)' ';
private const byte P = (byte)'P';
/// <summary>
/// The global configuration.
/// </summary>
private Configuration configuration;
/// <summary>
/// The encoder options.
/// </summary>
private readonly IPbmEncoderOptions options;
/// <summary>
/// The encoding for the pixels.
/// </summary>
private PbmEncoding encoding;
/// <summary>
/// Gets the Color type of the resulting image.
/// </summary>
private PbmColorType colorType;
/// <summary>
/// Gets the maximum pixel value, per component.
/// </summary>
private PbmComponentType componentType;
/// <summary>
/// Initializes a new instance of the <see cref="PbmEncoderCore"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The encoder options.</param>
public PbmEncoderCore(Configuration configuration, IPbmEncoderOptions options)
{
this.configuration = configuration;
this.options = options;
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="cancellationToken">The token to request cancellation.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
this.DeduceOptions(image);
byte signature = this.DeduceSignature();
this.WriteHeader(stream, signature, image.Size());
this.WritePixels(stream, image.Frames.RootFrame);
stream.Flush();
}
private void DeduceOptions<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
this.configuration = image.GetConfiguration();
PbmMetadata metadata = image.Metadata.GetPbmMetadata();
this.encoding = this.options.Encoding ?? metadata.Encoding;
this.colorType = this.options.ColorType ?? metadata.ColorType;
if (this.colorType != PbmColorType.BlackAndWhite)
{
this.componentType = this.options.ComponentType ?? metadata.ComponentType;
}
else
{
this.componentType = PbmComponentType.Bit;
}
}
private byte DeduceSignature()
{
byte signature;
if (this.colorType == PbmColorType.BlackAndWhite)
{
if (this.encoding == PbmEncoding.Plain)
{
signature = (byte)'1';
}
else
{
signature = (byte)'4';
}
}
else if (this.colorType == PbmColorType.Grayscale)
{
if (this.encoding == PbmEncoding.Plain)
{
signature = (byte)'2';
}
else
{
signature = (byte)'5';
}
}
else
{
// RGB ColorType
if (this.encoding == PbmEncoding.Plain)
{
signature = (byte)'3';
}
else
{
signature = (byte)'6';
}
}
return signature;
}
private void WriteHeader(Stream stream, byte signature, Size pixelSize)
{
Span<byte> buffer = stackalloc byte[128];
int written = 3;
buffer[0] = P;
buffer[1] = signature;
buffer[2] = NewLine;
Utf8Formatter.TryFormat(pixelSize.Width, buffer.Slice(written), out int bytesWritten);
written += bytesWritten;
buffer[written++] = Space;
Utf8Formatter.TryFormat(pixelSize.Height, buffer.Slice(written), out bytesWritten);
written += bytesWritten;
buffer[written++] = NewLine;
if (this.colorType != PbmColorType.BlackAndWhite)
{
int maxPixelValue = this.componentType == PbmComponentType.Short ? 65535 : 255;
Utf8Formatter.TryFormat(maxPixelValue, buffer.Slice(written), out bytesWritten);
written += bytesWritten;
buffer[written++] = NewLine;
}
stream.Write(buffer, 0, written);
}
/// <summary>
/// Writes the pixel data to the binary stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image">
/// The <see cref="ImageFrame{TPixel}"/> containing pixel data.
/// </param>
private void WritePixels<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
if (this.encoding == PbmEncoding.Plain)
{
PlainEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.componentType);
}
else
{
BinaryEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.componentType);
}
}
}
}

21
src/ImageSharp/Formats/Pbm/PbmEncoding.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <summary>
/// Provides enumeration of available PBM encodings.
/// </summary>
public enum PbmEncoding : byte
{
/// <summary>
/// Plain text decimal encoding.
/// </summary>
Plain = 0,
/// <summary>
/// Binary integer encoding.
/// </summary>
Binary = 1,
}
}

37
src/ImageSharp/Formats/Pbm/PbmFormat.cs

@ -0,0 +1,37 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <summary>
/// Registers the image encoders, decoders and mime type detectors for the PBM format.
/// </summary>
public sealed class PbmFormat : IImageFormat<PbmMetadata>
{
private PbmFormat()
{
}
/// <summary>
/// Gets the current instance.
/// </summary>
public static PbmFormat Instance { get; } = new();
/// <inheritdoc/>
public string Name => "PBM";
/// <inheritdoc/>
public string DefaultMimeType => "image/x-portable-pixmap";
/// <inheritdoc/>
public IEnumerable<string> MimeTypes => PbmConstants.MimeTypes;
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => PbmConstants.FileExtensions;
/// <inheritdoc/>
public PbmMetadata CreateDefaultFormatMetadata() => new();
}
}

36
src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs

@ -0,0 +1,36 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <summary>
/// Detects Pbm file headers.
/// </summary>
public sealed class PbmImageFormatDetector : IImageFormatDetector
{
private const byte P = (byte)'P';
private const byte Zero = (byte)'0';
private const byte Seven = (byte)'7';
/// <inheritdoc/>
public int HeaderSize => 2;
/// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) => this.IsSupportedFileFormat(header) ? PbmFormat.Instance : null;
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{
#pragma warning disable SA1131 // Use readable conditions
if (1 < (uint)header.Length)
#pragma warning restore SA1131 // Use readable conditions
{
// Signature should be between P1 and P6.
return header[0] == P && (uint)(header[1] - Zero - 1) < (Seven - Zero - 1);
}
return false;
}
}
}

46
src/ImageSharp/Formats/Pbm/PbmMetadata.cs

@ -0,0 +1,46 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <summary>
/// Provides PBM specific metadata information for the image.
/// </summary>
public class PbmMetadata : IDeepCloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="PbmMetadata"/> class.
/// </summary>
public PbmMetadata() =>
this.ComponentType = this.ColorType == PbmColorType.BlackAndWhite ? PbmComponentType.Bit : PbmComponentType.Byte;
/// <summary>
/// Initializes a new instance of the <see cref="PbmMetadata"/> class.
/// </summary>
/// <param name="other">The metadata to create an instance from.</param>
private PbmMetadata(PbmMetadata other)
{
this.Encoding = other.Encoding;
this.ColorType = other.ColorType;
this.ComponentType = other.ComponentType;
}
/// <summary>
/// Gets or sets the encoding of the pixels.
/// </summary>
public PbmEncoding Encoding { get; set; } = PbmEncoding.Plain;
/// <summary>
/// Gets or sets the color type.
/// </summary>
public PbmColorType ColorType { get; set; } = PbmColorType.Grayscale;
/// <summary>
/// Gets or sets the data type of the pixel components.
/// </summary>
public PbmComponentType ComponentType { get; set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new PbmMetadata(this);
}
}

198
src/ImageSharp/Formats/Pbm/PlainDecoder.cs

@ -0,0 +1,198 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <summary>
/// Pixel decoding methods for the PBM plain encoding.
/// </summary>
internal class PlainDecoder
{
private static readonly L8 White = new(255);
private static readonly L8 Black = new(0);
/// <summary>
/// Decode the specified pixels.
/// </summary>
/// <typeparam name="TPixel">The type of pixel to encode to.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="pixels">The pixel array to encode into.</param>
/// <param name="stream">The stream to read the data from.</param>
/// <param name="colorType">The ColorType to decode.</param>
/// <param name="componentType">Data type of the pixles components.</param>
public static void Process<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType)
where TPixel : unmanaged, IPixel<TPixel>
{
if (colorType == PbmColorType.Grayscale)
{
if (componentType == PbmComponentType.Byte)
{
ProcessGrayscale(configuration, pixels, stream);
}
else
{
ProcessWideGrayscale(configuration, pixels, stream);
}
}
else if (colorType == PbmColorType.Rgb)
{
if (componentType == PbmComponentType.Byte)
{
ProcessRgb(configuration, pixels, stream);
}
else
{
ProcessWideRgb(configuration, pixels, stream);
}
}
else
{
ProcessBlackAndWhite(configuration, pixels, stream);
}
}
private static void ProcessGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = pixels.Width;
int height = pixels.Height;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width);
Span<L8> rowSpan = row.GetSpan();
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
byte value = (byte)stream.ReadDecimal();
stream.SkipWhitespaceAndComments();
rowSpan[x] = new L8(value);
}
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromL8(
configuration,
rowSpan,
pixelSpan);
}
}
private static void ProcessWideGrayscale<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = pixels.Width;
int height = pixels.Height;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<L16> row = allocator.Allocate<L16>(width);
Span<L16> rowSpan = row.GetSpan();
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
ushort value = (ushort)stream.ReadDecimal();
stream.SkipWhitespaceAndComments();
rowSpan[x] = new L16(value);
}
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromL16(
configuration,
rowSpan,
pixelSpan);
}
}
private static void ProcessRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = pixels.Width;
int height = pixels.Height;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<Rgb24> row = allocator.Allocate<Rgb24>(width);
Span<Rgb24> rowSpan = row.GetSpan();
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
byte red = (byte)stream.ReadDecimal();
stream.SkipWhitespaceAndComments();
byte green = (byte)stream.ReadDecimal();
stream.SkipWhitespaceAndComments();
byte blue = (byte)stream.ReadDecimal();
stream.SkipWhitespaceAndComments();
rowSpan[x] = new Rgb24(red, green, blue);
}
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromRgb24(
configuration,
rowSpan,
pixelSpan);
}
}
private static void ProcessWideRgb<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = pixels.Width;
int height = pixels.Height;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<Rgb48> row = allocator.Allocate<Rgb48>(width);
Span<Rgb48> rowSpan = row.GetSpan();
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
ushort red = (ushort)stream.ReadDecimal();
stream.SkipWhitespaceAndComments();
ushort green = (ushort)stream.ReadDecimal();
stream.SkipWhitespaceAndComments();
ushort blue = (ushort)stream.ReadDecimal();
stream.SkipWhitespaceAndComments();
rowSpan[x] = new Rgb48(red, green, blue);
}
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromRgb48(
configuration,
rowSpan,
pixelSpan);
}
}
private static void ProcessBlackAndWhite<TPixel>(Configuration configuration, Buffer2D<TPixel> pixels, BufferedReadStream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = pixels.Width;
int height = pixels.Height;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width);
Span<L8> rowSpan = row.GetSpan();
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int value = stream.ReadDecimal();
stream.SkipWhitespaceAndComments();
rowSpan[x] = value == 0 ? White : Black;
}
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromL8(
configuration,
rowSpan,
pixelSpan);
}
}
}
}

251
src/ImageSharp/Formats/Pbm/PlainEncoder.cs

@ -0,0 +1,251 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Buffers.Text;
using System.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Pbm
{
/// <summary>
/// Pixel encoding methods for the PBM plain encoding.
/// </summary>
internal class PlainEncoder
{
private const byte NewLine = 0x0a;
private const byte Space = 0x20;
private const byte Zero = 0x30;
private const byte One = 0x31;
private const int MaxCharsPerPixelBlackAndWhite = 2;
private const int MaxCharsPerPixelGrayscale = 4;
private const int MaxCharsPerPixelGrayscaleWide = 6;
private const int MaxCharsPerPixelRgb = 4 * 3;
private const int MaxCharsPerPixelRgbWide = 6 * 3;
private static readonly StandardFormat DecimalFormat = StandardFormat.Parse("D");
/// <summary>
/// Decode pixels into the PBM plain encoding.
/// </summary>
/// <typeparam name="TPixel">The type of input pixel.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="stream">The bytestream to write to.</param>
/// <param name="image">The input image.</param>
/// <param name="colorType">The ColorType to use.</param>
/// <param name="componentType">Data type of the pixles components.</param>
public static void WritePixels<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image, PbmColorType colorType, PbmComponentType componentType)
where TPixel : unmanaged, IPixel<TPixel>
{
if (colorType == PbmColorType.Grayscale)
{
if (componentType == PbmComponentType.Byte)
{
WriteGrayscale(configuration, stream, image);
}
else
{
WriteWideGrayscale(configuration, stream, image);
}
}
else if (colorType == PbmColorType.Rgb)
{
if (componentType == PbmComponentType.Byte)
{
WriteRgb(configuration, stream, image);
}
else
{
WriteWideRgb(configuration, stream, image);
}
}
else
{
WriteBlackAndWhite(configuration, stream, image);
}
// Write EOF indicator, as some encoders expect it.
stream.WriteByte(Space);
}
private static void WriteGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width);
Span<L8> rowSpan = row.GetSpan();
using IMemoryOwner<byte> plainMemory = allocator.Allocate<byte>(width * MaxCharsPerPixelGrayscale);
Span<byte> plainSpan = plainMemory.GetSpan();
for (int y = 0; y < height; y++)
{
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8(
configuration,
pixelSpan,
rowSpan);
int written = 0;
for (int x = 0; x < width; x++)
{
Utf8Formatter.TryFormat(rowSpan[x].PackedValue, plainSpan.Slice(written), out int bytesWritten, DecimalFormat);
written += bytesWritten;
plainSpan[written++] = Space;
}
plainSpan[written - 1] = NewLine;
stream.Write(plainSpan, 0, written);
}
}
private static void WriteWideGrayscale<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<L16> row = allocator.Allocate<L16>(width);
Span<L16> rowSpan = row.GetSpan();
using IMemoryOwner<byte> plainMemory = allocator.Allocate<byte>(width * MaxCharsPerPixelGrayscaleWide);
Span<byte> plainSpan = plainMemory.GetSpan();
for (int y = 0; y < height; y++)
{
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL16(
configuration,
pixelSpan,
rowSpan);
int written = 0;
for (int x = 0; x < width; x++)
{
Utf8Formatter.TryFormat(rowSpan[x].PackedValue, plainSpan.Slice(written), out int bytesWritten, DecimalFormat);
written += bytesWritten;
plainSpan[written++] = Space;
}
plainSpan[written - 1] = NewLine;
stream.Write(plainSpan, 0, written);
}
}
private static void WriteRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<Rgb24> row = allocator.Allocate<Rgb24>(width);
Span<Rgb24> rowSpan = row.GetSpan();
using IMemoryOwner<byte> plainMemory = allocator.Allocate<byte>(width * MaxCharsPerPixelRgb);
Span<byte> plainSpan = plainMemory.GetSpan();
for (int y = 0; y < height; y++)
{
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgb24(
configuration,
pixelSpan,
rowSpan);
int written = 0;
for (int x = 0; x < width; x++)
{
Utf8Formatter.TryFormat(rowSpan[x].R, plainSpan.Slice(written), out int bytesWritten, DecimalFormat);
written += bytesWritten;
plainSpan[written++] = Space;
Utf8Formatter.TryFormat(rowSpan[x].G, plainSpan.Slice(written), out bytesWritten, DecimalFormat);
written += bytesWritten;
plainSpan[written++] = Space;
Utf8Formatter.TryFormat(rowSpan[x].B, plainSpan.Slice(written), out bytesWritten, DecimalFormat);
written += bytesWritten;
plainSpan[written++] = Space;
}
plainSpan[written - 1] = NewLine;
stream.Write(plainSpan, 0, written);
}
}
private static void WriteWideRgb<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<Rgb48> row = allocator.Allocate<Rgb48>(width);
Span<Rgb48> rowSpan = row.GetSpan();
using IMemoryOwner<byte> plainMemory = allocator.Allocate<byte>(width * MaxCharsPerPixelRgbWide);
Span<byte> plainSpan = plainMemory.GetSpan();
for (int y = 0; y < height; y++)
{
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgb48(
configuration,
pixelSpan,
rowSpan);
int written = 0;
for (int x = 0; x < width; x++)
{
Utf8Formatter.TryFormat(rowSpan[x].R, plainSpan.Slice(written), out int bytesWritten, DecimalFormat);
written += bytesWritten;
plainSpan[written++] = Space;
Utf8Formatter.TryFormat(rowSpan[x].G, plainSpan.Slice(written), out bytesWritten, DecimalFormat);
written += bytesWritten;
plainSpan[written++] = Space;
Utf8Formatter.TryFormat(rowSpan[x].B, plainSpan.Slice(written), out bytesWritten, DecimalFormat);
written += bytesWritten;
plainSpan[written++] = Space;
}
plainSpan[written - 1] = NewLine;
stream.Write(plainSpan, 0, written);
}
}
private static void WriteBlackAndWhite<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
Buffer2D<TPixel> pixelBuffer = image.PixelBuffer;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner<L8> row = allocator.Allocate<L8>(width);
Span<L8> rowSpan = row.GetSpan();
using IMemoryOwner<byte> plainMemory = allocator.Allocate<byte>(width * MaxCharsPerPixelBlackAndWhite);
Span<byte> plainSpan = plainMemory.GetSpan();
for (int y = 0; y < height; y++)
{
Span<TPixel> pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8(
configuration,
pixelSpan,
rowSpan);
int written = 0;
for (int x = 0; x < width; x++)
{
byte value = (rowSpan[x].PackedValue < 128) ? One : Zero;
plainSpan[written++] = value;
plainSpan[written++] = Space;
}
plainSpan[written - 1] = NewLine;
stream.Write(plainSpan, 0, written);
}
}
}
}

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

@ -630,6 +630,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);
@ -688,7 +689,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();
@ -721,7 +722,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++;
}

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

@ -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)

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);
}
}

2
src/ImageSharp/ImageSharp.csproj

@ -10,7 +10,7 @@
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<RepositoryUrl Condition="'$(RepositoryUrl)' == ''">https://github.com/SixLabors/ImageSharp/</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
<PackageTags>Image Resize Crop Gif Jpg Jpeg Bitmap Png Tga NetCore</PackageTags>
<PackageTags>Image Resize Crop Gif Jpg Jpeg Bitmap Pbm Png Tga NetCore</PackageTags>
<Description>A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET</Description>
<Configurations>Debug;Release;Debug-InnerLoop;Release-InnerLoop</Configurations>
</PropertyGroup>

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>())));
}
}

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

Loading…
Cancel
Save