Browse Source

Merge remote-tracking branch 'upstream/master' into bigtiff

Conflicts:
	src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs
        tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
	tests/ImageSharp.Tests/TestImages.cs
pull/1760/head
Ildar Khayrutdinov 4 years ago
parent
commit
cf2f4e64be
  1. 8
      .gitattributes
  2. 3
      .github/ISSUE_TEMPLATE/config.yml
  3. 2
      .github/ISSUE_TEMPLATE/oss-bug-report.md
  4. 4
      Directory.Build.props
  5. 2
      README.md
  6. 2
      shared-infrastructure
  7. 4
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  8. 9
      src/ImageSharp/Advanced/AotCompilerTools.cs
  9. 24
      src/ImageSharp/Color/Color.Conversions.cs
  10. 9
      src/ImageSharp/Color/Color.cs
  11. 14
      src/ImageSharp/Common/Helpers/DebugGuard.cs
  12. 9
      src/ImageSharp/Common/Helpers/Guard.cs
  13. 56
      src/ImageSharp/Common/Helpers/Numerics.cs
  14. 41
      src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs
  15. 5
      src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs
  16. 12
      src/ImageSharp/Common/Helpers/SimdUtils.cs
  17. 82
      src/ImageSharp/Common/Tuples/Vector4Pair.cs
  18. 42
      src/ImageSharp/Configuration.cs
  19. 18
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  20. 20
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  21. 4
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  22. 4
      src/ImageSharp/Formats/Gif/LzwDecoder.cs
  23. 2
      src/ImageSharp/Formats/Gif/LzwEncoder.cs
  24. 104
      src/ImageSharp/Formats/ImageExtensions.Save.cs
  25. 1
      src/ImageSharp/Formats/ImageExtensions.Save.tt
  26. 39
      src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs
  27. 118
      src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs
  28. 123
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  29. 18
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Avx2JpegColorConverter.cs
  30. 18
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.BasicJpegColorConverter.cs
  31. 52
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs
  32. 60
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx2.cs
  33. 20
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs
  34. 51
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs
  35. 53
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs
  36. 24
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs
  37. 53
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs
  38. 34
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs
  39. 38
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs
  40. 30
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs
  41. 32
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs
  42. 26
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs
  43. 15
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs
  44. 35
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs
  45. 42
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs
  46. 50
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs
  47. 24
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs
  48. 88
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector4.cs
  49. 32
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs
  50. 13
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs
  51. 30
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs
  52. 18
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Vector8JpegColorConverter.cs
  53. 47
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs
  54. 32
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs
  55. 111
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs
  56. 22
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs
  57. 59
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs
  58. 10
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
  59. 1
      src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
  60. 82
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
  61. 15
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs
  62. 113
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
  63. 15
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs
  64. 104
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
  65. 16
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  66. 49
      src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs
  67. 10
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs
  68. 10
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs
  69. 10
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs
  70. 23
      src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs
  71. 0
      src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.tt
  72. 122
      src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
  73. 2
      src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs
  74. 2
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  75. 7
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  76. 194
      src/ImageSharp/Formats/Pbm/BinaryDecoder.cs
  77. 208
      src/ImageSharp/Formats/Pbm/BinaryEncoder.cs
  78. 65
      src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs
  79. 26
      src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs
  80. 21
      src/ImageSharp/Formats/Pbm/MetadataExtensions.cs
  81. 26
      src/ImageSharp/Formats/Pbm/PbmColorType.cs
  82. 26
      src/ImageSharp/Formats/Pbm/PbmComponentType.cs
  83. 19
      src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs
  84. 28
      src/ImageSharp/Formats/Pbm/PbmConstants.cs
  85. 79
      src/ImageSharp/Formats/Pbm/PbmDecoder.cs
  86. 195
      src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs
  87. 69
      src/ImageSharp/Formats/Pbm/PbmEncoder.cs
  88. 187
      src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
  89. 21
      src/ImageSharp/Formats/Pbm/PbmEncoding.cs
  90. 37
      src/ImageSharp/Formats/Pbm/PbmFormat.cs
  91. 36
      src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs
  92. 46
      src/ImageSharp/Formats/Pbm/PbmMetadata.cs
  93. 198
      src/ImageSharp/Formats/Pbm/PlainDecoder.cs
  94. 251
      src/ImageSharp/Formats/Pbm/PlainEncoder.cs
  95. 114
      src/ImageSharp/Formats/Png/PngDecoder.cs
  96. 149
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  97. 60
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  98. 20
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  99. 8
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  100. 7
      src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.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

3
.github/ISSUE_TEMPLATE/config.yml

@ -1,8 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Ask a Question
url: https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AQ%26A
about: Ask a question about this project.
- name: Feature Request
url: https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AIdeas
about: Share ideas for new features for this project.

2
.github/ISSUE_TEMPLATE/oss-bug-report.md

@ -1,6 +1,6 @@
---
name: "OSS : Bug Report"
about: Create a report to help us improve the project.
about: Create a report to help us improve the project. OSS Issues are not guaranteed to be triaged.
labels: needs triage
---

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
README.md

@ -20,7 +20,7 @@ ImageSharp is a new, fully featured, fully managed, cross-platform, 2D graphics
ImageSharp is designed from the ground up to be flexible and extensible. The library provides API endpoints for common image processing operations and the building blocks to allow for the development of additional operations.
Built against [.NET Standard 1.3](https://docs.microsoft.com/en-us/dotnet/standard/net-standard), ImageSharp can be used in device, cloud, and embedded/IoT scenarios.
Built against [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard), ImageSharp can be used in device, cloud, and embedded/IoT scenarios.
## License

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

9
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;
@ -74,6 +75,7 @@ namespace SixLabors.ImageSharp.Advanced
Seed<A8>();
Seed<Argb32>();
Seed<Abgr32>();
Seed<Bgr24>();
Seed<Bgr565>();
Seed<Bgra32>();
@ -148,6 +150,7 @@ namespace SixLabors.ImageSharp.Advanced
Image<TPixel> img = default;
img.CloneAs<A8>(default);
img.CloneAs<Argb32>(default);
img.CloneAs<Abgr32>(default);
img.CloneAs<Bgr24>(default);
img.CloneAs<Bgr565>(default);
img.CloneAs<Bgra32>(default);
@ -200,6 +203,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 +221,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 +239,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 +257,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 +536,7 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileMemoryManagers<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
AotCompileMemoryManager<TPixel, ArrayPoolMemoryAllocator>();
AotCompileMemoryManager<TPixel, UniformUnmanagedMemoryPoolMemoryAllocator>();
AotCompileMemoryManager<TPixel, SimpleGcMemoryAllocator>();
}

24
src/ImageSharp/Color/Color.Conversions.cs

@ -89,6 +89,17 @@ namespace SixLabors.ImageSharp
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="Abgr32"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(Abgr32 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
@ -177,6 +188,19 @@ namespace SixLabors.ImageSharp
return value;
}
[MethodImpl(InliningOptions.ShortMethod)]
internal Abgr32 ToAbgr32()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToAbgr32();
}
Abgr32 value = default;
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return value;
}
[MethodImpl(InliningOptions.ShortMethod)]
internal Rgb24 ToRgb24()
{

9
src/ImageSharp/Color/Color.cs

@ -270,8 +270,15 @@ namespace SixLabors.ImageSharp
return pixel;
}
if (this.boxedHighPrecisionPixel is null)
{
pixel = default;
pixel.FromRgba64(this.data);
return pixel;
}
pixel = default;
pixel.FromRgba64(this.data);
pixel.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return pixel;
}

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>

9
src/ImageSharp/Common/Helpers/Guard.cs

@ -2,9 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
#if NETSTANDARD1_3
using System.Reflection;
#endif
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp;
@ -22,11 +19,7 @@ namespace SixLabors
[MethodImpl(InliningOptions.ShortMethod)]
public static void MustBeValueType<TValue>(TValue value, string parameterName)
{
if (value.GetType()
#if NETSTANDARD1_3
.GetTypeInfo()
#endif
.IsValueType)
if (value.GetType().IsValueType)
{
return;
}

56
src/ImageSharp/Common/Helpers/Numerics.cs

@ -907,5 +907,61 @@ namespace SixLabors.ImageSharp
/// <param name="divisor">Divisor value.</param>
/// <returns>Ceiled division result.</returns>
public static uint DivideCeil(uint value, uint divisor) => (value + divisor - 1) / divisor;
/// <summary>
/// Rotates the specified value left by the specified number of bits.
/// </summary>
/// <param name="value">The value to rotate.</param>
/// <param name="offset">The number of bits to rotate with.</param>
/// <returns>The rotated value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint RotateLeft(uint value, int offset)
{
#if SUPPORTS_BITOPERATIONS
return BitOperations.RotateLeft(value, offset);
#else
return RotateLeftSoftwareFallback(value, offset);
#endif
}
#if !SUPPORTS_BITOPERATIONS
/// <summary>
/// Rotates the specified value left by the specified number of bits.
/// </summary>
/// <param name="value">The value to rotate.</param>
/// <param name="offset">The number of bits to rotate with.</param>
/// <returns>The rotated value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint RotateLeftSoftwareFallback(uint value, int offset)
=> (value << offset) | (value >> (32 - offset));
#endif
/// <summary>
/// Rotates the specified value right by the specified number of bits.
/// </summary>
/// <param name="value">The value to rotate.</param>
/// <param name="offset">The number of bits to rotate with.</param>
/// <returns>The rotated value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint RotateRight(uint value, int offset)
{
#if SUPPORTS_BITOPERATIONS
return BitOperations.RotateRight(value, offset);
#else
return RotateRightSoftwareFallback(value, offset);
#endif
}
#if !SUPPORTS_BITOPERATIONS
/// <summary>
/// Rotates the specified value right by the specified number of bits.
/// </summary>
/// <param name="value">The value to rotate.</param>
/// <param name="offset">The number of bits to rotate with.</param>
/// <returns>The rotated value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint RotateRightSoftwareFallback(uint value, int offset)
=> (value >> offset) | (value << (32 - offset));
#endif
}
}

41
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);
}
@ -153,7 +157,7 @@ namespace SixLabors.ImageSharp
// packed = [W Z Y X]
// ROTR(8, packedArgb) = [Y Z W X]
Unsafe.Add(ref dBase, i) = (packed >> 8) | (packed << 24);
Unsafe.Add(ref dBase, i) = Numerics.RotateRight(packed, 8);
}
}
}
@ -184,7 +188,40 @@ namespace SixLabors.ImageSharp
// tmp1 + tmp3 = [W X Y Z]
uint tmp1 = packed & 0xFF00FF00;
uint tmp2 = packed & 0x00FF00FF;
uint tmp3 = (tmp2 << 16) | (tmp2 >> 16);
uint tmp3 = Numerics.RotateLeft(tmp2, 16);
Unsafe.Add(ref dBase, i) = tmp1 + tmp3;
}
}
}
internal readonly struct XWZYShuffle4 : IShuffle4
{
public byte Control
{
[MethodImpl(InliningOptions.ShortMethod)]
get => SimdUtils.Shuffle.MmShuffle(1, 2, 3, 0);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
{
ref uint sBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(source));
ref uint dBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(dest));
int n = source.Length / 4;
for (int i = 0; i < n; i++)
{
uint packed = Unsafe.Add(ref sBase, i);
// packed = [W Z Y X]
// tmp1 = [0 Z 0 X]
// tmp2 = [W 0 Y 0]
// tmp3=ROTL(16, tmp2) = [Y 0 W 0]
// tmp1 + tmp3 = [Y Z W X]
uint tmp1 = packed & 0x00FF00FF;
uint tmp2 = packed & 0xFF00FF00;
uint tmp3 = Numerics.RotateLeft(tmp2, 16);
Unsafe.Add(ref dBase, i) = tmp1 + tmp3;
}

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,

12
src/ImageSharp/Common/Helpers/SimdUtils.cs

@ -33,18 +33,6 @@ namespace SixLabors.ImageSharp
public static bool HasVector4 { get; } =
Vector.IsHardwareAccelerated && Vector<float>.Count == 4;
public static bool HasAvx2
{
get
{
#if SUPPORTS_RUNTIME_INTRINSICS
return Avx2.IsSupported;
#else
return false;
#endif
}
}
/// <summary>
/// Transform all scalars in 'v' in a way that converting them to <see cref="int"/> would have rounding semantics.
/// </summary>

82
src/ImageSharp/Common/Tuples/Vector4Pair.cs

@ -1,82 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Tuples
{
/// <summary>
/// Its faster to process multiple Vector4-s together, so let's pair them!
/// On AVX2 this pair should be convertible to <see cref="Vector{T}"/> of <see cref="float"/>!
/// TODO: Investigate defining this as union with an Octet.OfSingle type.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct Vector4Pair
{
public Vector4 A;
public Vector4 B;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MultiplyInplace(float value)
{
this.A *= value;
this.B *= value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddInplace(Vector4 value)
{
this.A += value;
this.B += value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddInplace(ref Vector4Pair other)
{
this.A += other.A;
this.B += other.B;
}
/// <summary>.
/// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4! /// TODO: Move it somewhere else.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RoundAndDownscalePreVector8(float downscaleFactor)
{
ref Vector<float> a = ref Unsafe.As<Vector4, Vector<float>>(ref this.A);
a = a.FastRound();
ref Vector<float> b = ref Unsafe.As<Vector4, Vector<float>>(ref this.B);
b = b.FastRound();
// Downscale by 1/factor
var scale = new Vector4(1 / downscaleFactor);
this.A *= scale;
this.B *= scale;
}
/// <summary>
/// AVX2-only Downscale method, specific to Jpeg color conversion.
/// TODO: Move it somewhere else.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RoundAndDownscaleVector8(float downscaleFactor)
{
ref Vector<float> self = ref Unsafe.As<Vector4Pair, Vector<float>>(ref this);
Vector<float> v = self;
v = v.FastRound();
// Downscale by 1/factor
v *= new Vector<float>(1 / downscaleFactor);
self = v;
}
public override string ToString()
{
return $"{nameof(Vector4Pair)}({this.A}, {this.B})";
}
}
}

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",

39
src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs

@ -0,0 +1,39 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal unsafe partial struct Block8x8
{
[FieldOffset(0)]
public Vector128<short> V0;
[FieldOffset(16)]
public Vector128<short> V1;
[FieldOffset(32)]
public Vector128<short> V2;
[FieldOffset(48)]
public Vector128<short> V3;
[FieldOffset(64)]
public Vector128<short> V4;
[FieldOffset(80)]
public Vector128<short> V5;
[FieldOffset(96)]
public Vector128<short> V6;
[FieldOffset(112)]
public Vector128<short> V7;
[FieldOffset(0)]
public Vector256<short> V01;
[FieldOffset(32)]
public Vector256<short> V23;
[FieldOffset(64)]
public Vector256<short> V45;
[FieldOffset(96)]
public Vector256<short> V67;
}
}
#endif

118
src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary>
// ReSharper disable once InconsistentNaming
[StructLayout(LayoutKind.Explicit)]
internal unsafe struct Block8x8 : IEquatable<Block8x8>
internal unsafe partial struct Block8x8
{
/// <summary>
/// A number of scalar coefficients in a <see cref="Block8x8F"/>
@ -36,34 +36,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
private fixed short data[Size];
#pragma warning restore IDE0051
#if SUPPORTS_RUNTIME_INTRINSICS
[FieldOffset(0)]
public Vector128<short> V0;
[FieldOffset(16)]
public Vector128<short> V1;
[FieldOffset(32)]
public Vector128<short> V2;
[FieldOffset(48)]
public Vector128<short> V3;
[FieldOffset(64)]
public Vector128<short> V4;
[FieldOffset(80)]
public Vector128<short> V5;
[FieldOffset(96)]
public Vector128<short> V6;
[FieldOffset(112)]
public Vector128<short> V7;
[FieldOffset(0)]
public Vector256<short> V01;
[FieldOffset(32)]
public Vector256<short> V23;
[FieldOffset(64)]
public Vector256<short> V45;
[FieldOffset(96)]
public Vector256<short> V67;
#endif
/// <summary>
/// Gets or sets a <see cref="short"/> value at the given index
/// </summary>
@ -102,74 +74,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
set => this[(y * 8) + x] = value;
}
public static bool operator ==(Block8x8 left, Block8x8 right) => left.Equals(right);
public static bool operator !=(Block8x8 left, Block8x8 right) => !left.Equals(right);
/// <summary>
/// Multiply all elements by a given <see cref="int"/>
/// </summary>
public static Block8x8 operator *(Block8x8 block, int value)
{
Block8x8 result = block;
for (int i = 0; i < Size; i++)
{
int val = result[i];
val *= value;
result[i] = (short)val;
}
return result;
}
/// <summary>
/// Divide all elements by a given <see cref="int"/>
/// </summary>
public static Block8x8 operator /(Block8x8 block, int value)
{
Block8x8 result = block;
for (int i = 0; i < Size; i++)
{
int val = result[i];
val /= value;
result[i] = (short)val;
}
return result;
}
/// <summary>
/// Add an <see cref="int"/> to all elements
/// </summary>
public static Block8x8 operator +(Block8x8 block, int value)
{
Block8x8 result = block;
for (int i = 0; i < Size; i++)
{
int val = result[i];
val += value;
result[i] = (short)val;
}
return result;
}
/// <summary>
/// Subtract an <see cref="int"/> from all elements
/// </summary>
public static Block8x8 operator -(Block8x8 block, int value)
{
Block8x8 result = block;
for (int i = 0; i < Size; i++)
{
int val = result[i];
val -= value;
result[i] = (short)val;
}
return result;
}
public static Block8x8 Load(Span<short> data)
{
Unsafe.SkipInit(out Block8x8 result);
@ -260,26 +164,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
return sb.ToString();
}
/// <inheritdoc />
public bool Equals(Block8x8 other)
{
for (int i = 0; i < Size; i++)
{
if (this[i] != other[i])
{
return false;
}
}
return true;
}
/// <inheritdoc />
public override bool Equals(object obj) => obj is Block8x8 other && this.Equals(other);
/// <inheritdoc />
public override int GetHashCode() => (this[0] * 31) + this[1];
/// <summary>
/// Returns index of the last non-zero element in given matrix.
/// </summary>

123
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -98,58 +97,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
set => this[(y * 8) + x] = value;
}
public static Block8x8F operator *(Block8x8F block, float value)
{
Block8x8F result = block;
for (int i = 0; i < Size; i++)
{
float val = result[i];
val *= value;
result[i] = val;
}
return result;
}
public static Block8x8F operator /(Block8x8F block, float value)
{
Block8x8F result = block;
for (int i = 0; i < Size; i++)
{
float val = result[i];
val /= value;
result[i] = val;
}
return result;
}
public static Block8x8F operator +(Block8x8F block, float value)
{
Block8x8F result = block;
for (int i = 0; i < Size; i++)
{
float val = result[i];
val += value;
result[i] = val;
}
return result;
}
public static Block8x8F operator -(Block8x8F block, float value)
{
Block8x8F result = block;
for (int i = 0; i < Size; i++)
{
float val = result[i];
val -= value;
result[i] = val;
}
return result;
}
public static Block8x8F Load(Span<float> data)
{
Block8x8F result = default;
@ -157,13 +104,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
return result;
}
public static Block8x8F Load(Span<int> data)
{
Block8x8F result = default;
result.LoadFrom(data);
return result;
}
/// <summary>
/// Load raw 32bit floating point data from source.
/// </summary>
@ -177,15 +117,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float));
}
/// <summary>
/// Load raw 32bit floating point data from source.
/// </summary>
/// <param name="blockPtr">Block pointer</param>
/// <param name="source">Source</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static unsafe void LoadFrom(Block8x8F* blockPtr, Span<float> source)
=> blockPtr->LoadFrom(source);
/// <summary>
/// Load raw 32bit floating point data from source
/// </summary>
@ -202,44 +133,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
}
/// <summary>
/// Copy raw 32bit floating point data to dest,
/// </summary>
/// <param name="dest">Destination</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void ScaledCopyTo(Span<float> dest)
{
ref byte d = ref Unsafe.As<float, byte>(ref MemoryMarshal.GetReference(dest));
ref byte s = ref Unsafe.As<Block8x8F, byte>(ref this);
Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float));
}
/// <summary>
/// Convert scalars to byte-s and copy to dest,
/// </summary>
/// <param name="blockPtr">Pointer to block</param>
/// <param name="dest">Destination</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span<byte> dest)
{
float* fPtr = (float*)blockPtr;
for (int i = 0; i < Size; i++)
{
dest[i] = (byte)*fPtr;
fPtr++;
}
}
/// <summary>
/// Copy raw 32bit floating point data to dest.
/// </summary>
/// <param name="blockPtr">The block pointer.</param>
/// <param name="dest">The destination.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span<float> dest)
=> blockPtr->ScaledCopyTo(dest);
/// <summary>
/// Copy raw 32bit floating point data to dest
/// </summary>
@ -253,22 +146,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
}
/// <summary>
/// Copy raw 32bit floating point data to dest
/// </summary>
/// <param name="dest">Destination</param>
public unsafe void ScaledCopyTo(Span<int> dest)
{
fixed (Vector4* ptr = &this.V0L)
{
var fp = (float*)ptr;
for (int i = 0; i < Size; i++)
{
dest[i] = (int)fp[i];
}
}
}
public float[] ToArray()
{
float[] result = new float[Size];

18
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Avx2JpegColorConverter.cs

@ -1,18 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal abstract class Avx2JpegColorConverter : VectorizedJpegColorConverter
{
protected Avx2JpegColorConverter(JpegColorSpace colorSpace, int precision)
: base(colorSpace, precision, 8)
{
}
protected sealed override bool IsAvailable => SimdUtils.HasAvx2;
}
}
}

18
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.BasicJpegColorConverter.cs

@ -1,18 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal abstract class BasicJpegColorConverter : JpegColorConverter
{
protected BasicJpegColorConverter(JpegColorSpace colorSpace, int precision)
: base(colorSpace, precision)
{
}
protected override bool IsAvailable => true;
}
}
}

52
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs

@ -0,0 +1,52 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromCmykAvx : JpegColorConverterAvx
{
public FromCmykAvx(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
public override void ConvertToRgbInplace(in ComponentValues values)
{
ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> c1Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> c2Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> c3Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
// Used for the color conversion
var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue));
nint n = values.Component0.Length / Vector256<float>.Count;
for (nint i = 0; i < n; i++)
{
ref Vector256<float> c = ref Unsafe.Add(ref c0Base, i);
ref Vector256<float> m = ref Unsafe.Add(ref c1Base, i);
ref Vector256<float> y = ref Unsafe.Add(ref c2Base, i);
Vector256<float> k = Unsafe.Add(ref c3Base, i);
k = Avx.Multiply(k, scale);
c = Avx.Multiply(c, k);
m = Avx.Multiply(m, k);
y = Avx.Multiply(y, k);
}
}
}
}
}
#endif

60
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx2.cs

@ -1,60 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using static SixLabors.ImageSharp.SimdUtils;
#endif
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromCmykAvx2 : Avx2JpegColorConverter
{
public FromCmykAvx2(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{
#if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> c1Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> c2Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> c3Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
// Used for the color conversion
var scale = Vector256.Create(1 / this.MaximumValue);
nint n = values.Component0.Length / 8;
for (nint i = 0; i < n; i++)
{
ref Vector256<float> c = ref Unsafe.Add(ref c0Base, i);
ref Vector256<float> m = ref Unsafe.Add(ref c1Base, i);
ref Vector256<float> y = ref Unsafe.Add(ref c2Base, i);
Vector256<float> k = Unsafe.Add(ref c3Base, i);
k = Avx.Multiply(k, scale);
c = Avx.Multiply(Avx.Multiply(c, k), scale);
m = Avx.Multiply(Avx.Multiply(m, k), scale);
y = Avx.Multiply(Avx.Multiply(y, k), scale);
}
#endif
}
protected override void ConvertCoreInplace(in ComponentValues values) =>
FromCmykBasic.ConvertCoreInplace(values, this.MaximumValue);
}
}
}

20
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykBasic.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs

@ -1,16 +1,15 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromCmykBasic : BasicJpegColorConverter
internal sealed class FromCmykScalar : JpegColorConverterScalar
{
public FromCmykBasic(int precision)
public FromCmykScalar(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
@ -25,17 +24,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
float scale = 1 / maxValue;
float scale = 1 / (maxValue * maxValue);
for (int i = 0; i < c0.Length; i++)
{
float c = c0[i];
float m = c1[i];
float y = c2[i];
float k = c3[i] / maxValue;
float k = c3[i];
c0[i] = c * k * scale;
c1[i] = m * k * scale;
c2[i] = y * k * scale;
k *= scale;
c0[i] = c * k;
c1[i] = m * k;
c2[i] = y * k;
}
}
}

51
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs

@ -0,0 +1,51 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromCmykVector : JpegColorConverterVector
{
public FromCmykVector(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{
ref Vector<float> cBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> mBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> yBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector<float> kBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3));
var scale = new Vector<float>(1 / (this.MaximumValue * this.MaximumValue));
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
{
ref Vector<float> c = ref Unsafe.Add(ref cBase, i);
ref Vector<float> m = ref Unsafe.Add(ref mBase, i);
ref Vector<float> y = ref Unsafe.Add(ref yBase, i);
Vector<float> k = Unsafe.Add(ref kBase, i);
k *= scale;
c *= k;
m *= k;
y *= k;
}
}
protected override void ConvertCoreInplace(in ComponentValues values) =>
FromCmykScalar.ConvertCoreInplace(values, this.MaximumValue);
}
}
}

53
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs

@ -1,53 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Tuples;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromCmykVector8 : Vector8JpegColorConverter
{
public FromCmykVector8(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{
ref Vector<float> cBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> mBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> yBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector<float> kBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3));
var scale = new Vector<float>(1 / this.MaximumValue);
// Walking 8 elements at one step:
nint n = values.Component0.Length / 8;
for (nint i = 0; i < n; i++)
{
ref Vector<float> c = ref Unsafe.Add(ref cBase, i);
ref Vector<float> m = ref Unsafe.Add(ref mBase, i);
ref Vector<float> y = ref Unsafe.Add(ref yBase, i);
Vector<float> k = Unsafe.Add(ref kBase, i) * scale;
c = (c * k) * scale;
m = (m * k) * scale;
y = (y * k) * scale;
}
}
protected override void ConvertCoreInplace(in ComponentValues values) =>
FromCmykBasic.ConvertCoreInplace(values, this.MaximumValue);
}
}
}

24
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx2.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs

@ -1,47 +1,39 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using static SixLabors.ImageSharp.SimdUtils;
#endif
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromGrayscaleAvx2 : Avx2JpegColorConverter
internal sealed class FromGrayscaleAvx : JpegColorConverterAvx
{
public FromGrayscaleAvx2(int precision)
public FromGrayscaleAvx(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
public override void ConvertToRgbInplace(in ComponentValues values)
{
#if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
// Used for the color conversion
var scale = Vector256.Create(1 / this.MaximumValue);
nint n = values.Component0.Length / 8;
nint n = values.Component0.Length / Vector256<float>.Count;
for (nint i = 0; i < n; i++)
{
ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i);
c0 = Avx.Multiply(c0, scale);
}
#endif
}
protected override void ConvertCoreInplace(in ComponentValues values) =>
FromGrayscaleBasic.ScaleValues(values.Component0, this.MaximumValue);
}
}
}
#endif

53
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs

@ -1,53 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromGrayscaleBasic : BasicJpegColorConverter
{
public FromGrayscaleBasic(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
public override void ConvertToRgbInplace(in ComponentValues values) =>
ScaleValues(values.Component0, this.MaximumValue);
internal static void ScaleValues(Span<float> values, float maxValue)
{
Span<Vector4> vecValues = MemoryMarshal.Cast<float, Vector4>(values);
var scaleVector = new Vector4(1 / maxValue);
for (int i = 0; i < vecValues.Length; i++)
{
vecValues[i] *= scaleVector;
}
values = values.Slice(vecValues.Length * 4);
if (!values.IsEmpty)
{
float scaleValue = 1f / maxValue;
values[0] *= scaleValue;
if ((uint)values.Length > 1)
{
values[1] *= scaleValue;
if ((uint)values.Length > 2)
{
values[2] *= scaleValue;
}
}
}
}
}
}
}

34
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs

@ -0,0 +1,34 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromGrayscaleScalar : JpegColorConverterScalar
{
public FromGrayscaleScalar(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
public override void ConvertToRgbInplace(in ComponentValues values) =>
ConvertCoreInplace(values.Component0, this.MaximumValue);
internal static void ConvertCoreInplace(Span<float> values, float maxValue)
{
ref float valuesRef = ref MemoryMarshal.GetReference(values);
float scale = 1 / maxValue;
for (nint i = 0; i < values.Length; i++)
{
Unsafe.Add(ref valuesRef, i) *= scale;
}
}
}
}
}

38
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs

@ -0,0 +1,38 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromGrayScaleVector : JpegColorConverterVector
{
public FromGrayScaleVector(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{
ref Vector<float> cBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
var scale = new Vector<float>(1 / this.MaximumValue);
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
{
ref Vector<float> c0 = ref Unsafe.Add(ref cBase, i);
c0 *= scale;
}
}
protected override void ConvertCoreInplace(in ComponentValues values) =>
FromGrayscaleScalar.ConvertCoreInplace(values.Component0, this.MaximumValue);
}
}
}

30
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs

@ -1,40 +1,35 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using static SixLabors.ImageSharp.SimdUtils;
#endif
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromRgbAvx2 : Avx2JpegColorConverter
internal sealed class FromRgbAvx : JpegColorConverterAvx
{
public FromRgbAvx2(int precision)
public FromRgbAvx(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
public override void ConvertToRgbInplace(in ComponentValues values)
{
#if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> rBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> gBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> bBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
// Used for the color conversion
var scale = Vector256.Create(1 / this.MaximumValue);
nint n = values.Component0.Length / 8;
nint n = values.Component0.Length / Vector256<float>.Count;
for (nint i = 0; i < n; i++)
{
ref Vector256<float> r = ref Unsafe.Add(ref rBase, i);
@ -44,11 +39,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
g = Avx.Multiply(g, scale);
b = Avx.Multiply(b, scale);
}
#endif
}
protected override void ConvertCoreInplace(in ComponentValues values) =>
FromRgbBasic.ConvertCoreInplace(values, this.MaximumValue);
}
}
}
#endif

32
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs

@ -1,32 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromRgbBasic : BasicJpegColorConverter
{
public FromRgbBasic(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
public override void ConvertToRgbInplace(in ComponentValues values)
{
ConvertCoreInplace(values, this.MaximumValue);
}
internal static void ConvertCoreInplace(ComponentValues values, float maxValue)
{
FromGrayscaleBasic.ScaleValues(values.Component0, maxValue);
FromGrayscaleBasic.ScaleValues(values.Component1, maxValue);
FromGrayscaleBasic.ScaleValues(values.Component2, maxValue);
}
}
}
}

26
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs

@ -0,0 +1,26 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromRgbScalar : JpegColorConverterScalar
{
public FromRgbScalar(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
public override void ConvertToRgbInplace(in ComponentValues values) =>
ConvertCoreInplace(values, this.MaximumValue);
internal static void ConvertCoreInplace(ComponentValues values, float maxValue)
{
FromGrayscaleScalar.ConvertCoreInplace(values.Component0, maxValue);
FromGrayscaleScalar.ConvertCoreInplace(values.Component1, maxValue);
FromGrayscaleScalar.ConvertCoreInplace(values.Component2, maxValue);
}
}
}
}

15
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs

@ -1,19 +1,17 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Tuples;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromRgbVector8 : Vector8JpegColorConverter
internal sealed class FromRgbVector : JpegColorConverterVector
{
public FromRgbVector8(int precision)
public FromRgbVector(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
@ -29,8 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
var scale = new Vector<float>(1 / this.MaximumValue);
// Walking 8 elements at one step:
nint n = values.Component0.Length / 8;
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
{
ref Vector<float> r = ref Unsafe.Add(ref rBase, i);
@ -43,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
protected override void ConvertCoreInplace(in ComponentValues values) =>
FromRgbBasic.ConvertCoreInplace(values, this.MaximumValue);
FromRgbScalar.ConvertCoreInplace(values, this.MaximumValue);
}
}
}

35
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx2.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs

@ -1,31 +1,27 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using static SixLabors.ImageSharp.SimdUtils;
#endif
// ReSharper disable ImpureMethodCallOnReadonlyValueField
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromYCbCrAvx2 : Avx2JpegColorConverter
internal sealed class FromYCbCrAvx : JpegColorConverterAvx
{
public FromYCbCrAvx2(int precision)
public FromYCbCrAvx(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
public override void ConvertToRgbInplace(in ComponentValues values)
{
#if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> c1Base =
@ -36,18 +32,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
// Used for the color conversion
var chromaOffset = Vector256.Create(-this.HalfValue);
var scale = Vector256.Create(1 / this.MaximumValue);
var rCrMult = Vector256.Create(1.402F);
var gCbMult = Vector256.Create(-0.344136F);
var gCrMult = Vector256.Create(-0.714136F);
var bCbMult = Vector256.Create(1.772F);
// Used for packing.
var va = Vector256.Create(1F);
ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control);
var rCrMult = Vector256.Create(FromYCbCrScalar.RCrMult);
var gCbMult = Vector256.Create(-FromYCbCrScalar.GCbMult);
var gCrMult = Vector256.Create(-FromYCbCrScalar.GCrMult);
var bCbMult = Vector256.Create(FromYCbCrScalar.BCbMult);
// Walking 8 elements at one step:
nint n = values.Component0.Length / 8;
nint n = values.Component0.Length / Vector256<float>.Count;
for (nint i = 0; i < n; i++)
{
// y = yVals[i];
@ -64,7 +55,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
// Adding & multiplying 8 elements at one time:
Vector256<float> r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult);
Vector256<float> g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
Vector256<float> b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult);
@ -77,11 +67,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
c1 = g;
c2 = b;
}
#endif
}
protected override void ConvertCoreInplace(in ComponentValues values) =>
FromYCbCrBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
}
}
}
#endif

42
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs

@ -1,42 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromYCbCrBasic : BasicJpegColorConverter
{
public FromYCbCrBasic(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
public override void ConvertToRgbInplace(in ComponentValues values)
=> ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue)
{
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
var scale = 1 / maxValue;
for (int i = 0; i < c0.Length; i++)
{
float y = c0[i];
float cb = c1[i] - halfValue;
float cr = c2[i] - halfValue;
c0[i] = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero) * scale;
c1[i] = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero) * scale;
c2[i] = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero) * scale;
}
}
}
}
}

50
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs

@ -0,0 +1,50 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromYCbCrScalar : JpegColorConverterScalar
{
// TODO: comments, derived from ITU-T Rec. T.871
internal const float RCrMult = 1.402f;
internal const float GCbMult = (float)(0.114 * 1.772 / 0.587);
internal const float GCrMult = (float)(0.299 * 1.402 / 0.587);
internal const float BCbMult = 1.772f;
public FromYCbCrScalar(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
public override void ConvertToRgbInplace(in ComponentValues values)
=> ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue)
{
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
float scale = 1 / maxValue;
for (int i = 0; i < c0.Length; i++)
{
float y = c0[i];
float cb = c1[i] - halfValue;
float cr = c2[i] - halfValue;
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale;
c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale;
c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale;
}
}
}
}
}

24
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector8.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs

@ -1,20 +1,18 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Tuples;
// ReSharper disable ImpureMethodCallOnReadonlyValueField
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromYCbCrVector8 : Vector8JpegColorConverter
internal sealed class FromYCbCrVector : JpegColorConverterVector
{
public FromYCbCrVector8(int precision)
public FromYCbCrVector(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
@ -30,10 +28,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
var chromaOffset = new Vector<float>(-this.HalfValue);
// Walking 8 elements at one step:
nint n = values.Component0.Length / 8;
var scale = new Vector<float>(1 / this.MaximumValue);
var rCrMult = new Vector<float>(FromYCbCrScalar.RCrMult);
var gCbMult = new Vector<float>(-FromYCbCrScalar.GCbMult);
var gCrMult = new Vector<float>(-FromYCbCrScalar.GCrMult);
var bCbMult = new Vector<float>(FromYCbCrScalar.BCbMult);
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
{
// y = yVals[i];
@ -49,10 +50,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
// Adding & multiplying 8 elements at one time:
Vector<float> r = y + (cr * new Vector<float>(1.402F));
Vector<float> g = y - (cb * new Vector<float>(0.344136F)) - (cr * new Vector<float>(0.714136F));
Vector<float> b = y + (cb * new Vector<float>(1.772F));
Vector<float> r = y + (cr * rCrMult);
Vector<float> g = y + (cb * gCbMult) + (cr * gCrMult);
Vector<float> b = y + (cb * bCbMult);
r = r.FastRound();
g = g.FastRound();
@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
protected override void ConvertCoreInplace(in ComponentValues values) =>
FromYCbCrBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
FromYCbCrScalar.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
}
}
}

88
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector4.cs

@ -1,88 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Tuples;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal sealed class FromYCbCrVector4 : VectorizedJpegColorConverter
{
public FromYCbCrVector4(int precision)
: base(JpegColorSpace.YCbCr, precision, 8)
{
}
protected override bool IsAvailable => SimdUtils.HasVector4;
protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{
DebugGuard.IsTrue(values.Component0.Length % 8 == 0, nameof(values), "Length should be divisible by 8!");
ref Vector4Pair c0Base =
ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector4Pair c1Base =
ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector4Pair c2Base =
ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component2));
var chromaOffset = new Vector4(-this.HalfValue);
var maxValue = this.MaximumValue;
// Walking 8 elements at one step:
nint n = values.Component0.Length / 8;
for (nint i = 0; i < n; i++)
{
// y = yVals[i];
ref Vector4Pair c0 = ref Unsafe.Add(ref c0Base, i);
// cb = cbVals[i] - halfValue);
ref Vector4Pair c1 = ref Unsafe.Add(ref c1Base, i);
c1.AddInplace(chromaOffset);
// cr = crVals[i] - halfValue;
ref Vector4Pair c2 = ref Unsafe.Add(ref c2Base, i);
c2.AddInplace(chromaOffset);
// r = y + (1.402F * cr);
Vector4Pair r = c0;
Vector4Pair tmp = c2;
tmp.MultiplyInplace(1.402F);
r.AddInplace(ref tmp);
// g = y - (0.344136F * cb) - (0.714136F * cr);
Vector4Pair g = c0;
tmp = c1;
tmp.MultiplyInplace(-0.344136F);
g.AddInplace(ref tmp);
tmp = c2;
tmp.MultiplyInplace(-0.714136F);
g.AddInplace(ref tmp);
// b = y + (1.772F * cb);
Vector4Pair b = c0;
tmp = c1;
tmp.MultiplyInplace(1.772F);
b.AddInplace(ref tmp);
r.RoundAndDownscalePreVector8(maxValue);
g.RoundAndDownscalePreVector8(maxValue);
b.RoundAndDownscalePreVector8(maxValue);
c0 = r;
c1 = g;
c2 = b;
}
}
protected override void ConvertCoreInplace(in ComponentValues values)
=> FromYCbCrBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
}
}
}

32
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx2.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs

@ -1,30 +1,26 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using static SixLabors.ImageSharp.SimdUtils;
#endif
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromYccKAvx2 : Avx2JpegColorConverter
internal sealed class FromYccKAvx : JpegColorConverterAvx
{
public FromYccKAvx2(int precision)
public FromYccKAvx(int precision)
: base(JpegColorSpace.Ycck, precision)
{
}
protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
public override void ConvertToRgbInplace(in ComponentValues values)
{
#if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> c1Base =
@ -38,13 +34,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
var chromaOffset = Vector256.Create(-this.HalfValue);
var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue));
var max = Vector256.Create(this.MaximumValue);
var rCrMult = Vector256.Create(1.402F);
var gCbMult = Vector256.Create(-0.344136F);
var gCrMult = Vector256.Create(-0.714136F);
var bCbMult = Vector256.Create(1.772F);
var rCrMult = Vector256.Create(FromYCbCrScalar.RCrMult);
var gCbMult = Vector256.Create(-FromYCbCrScalar.GCbMult);
var gCrMult = Vector256.Create(-FromYCbCrScalar.GCrMult);
var bCbMult = Vector256.Create(FromYCbCrScalar.BCbMult);
// Walking 8 elements at one step:
nint n = values.Component0.Length / 8;
nint n = values.Component0.Length / Vector256<float>.Count;
for (nint i = 0; i < n; i++)
{
// y = yVals[i];
@ -62,7 +58,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
// Adding & multiplying 8 elements at one time:
Vector256<float> r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult);
Vector256<float> g =
HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
@ -80,11 +75,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
c1 = g;
c2 = b;
}
#endif
}
protected override void ConvertCoreInplace(in ComponentValues values) =>
FromYccKBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
}
}
}
#endif

13
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKBasic.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs

@ -1,16 +1,15 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromYccKBasic : BasicJpegColorConverter
internal sealed class FromYccKScalar : JpegColorConverterScalar
{
public FromYccKBasic(int precision)
public FromYccKScalar(int precision)
: base(JpegColorSpace.Ycck, precision)
{
}
@ -25,9 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
var v = new Vector4(0, 0, 0, 1F);
var scale = 1 / (maxValue * maxValue);
float scale = 1 / (maxValue * maxValue);
for (int i = 0; i < values.Component0.Length; i++)
{

30
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector8.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs

@ -1,19 +1,17 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Tuples;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
internal abstract partial class JpegColorConverterBase
{
internal sealed class FromYccKVector8 : Vector8JpegColorConverter
internal sealed class FromYccKVector : JpegColorConverterVector
{
public FromYccKVector8(int precision)
public FromYccKVector(int precision)
: base(JpegColorSpace.Ycck, precision)
{
}
@ -30,13 +28,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3));
var chromaOffset = new Vector<float>(-this.HalfValue);
// Walking 8 elements at one step:
nint n = values.Component0.Length / 8;
var scale = new Vector<float>(1 / (this.MaximumValue * this.MaximumValue));
var max = new Vector<float>(this.MaximumValue);
var scale = new Vector<float>(1f) / (max * max);
var rCrMult = new Vector<float>(FromYCbCrScalar.RCrMult);
var gCbMult = new Vector<float>(-FromYCbCrScalar.GCbMult);
var gCrMult = new Vector<float>(-FromYCbCrScalar.GCrMult);
var bCbMult = new Vector<float>(FromYCbCrScalar.BCbMult);
nint n = values.Component0.Length / Vector<float>.Count;
for (nint i = 0; i < n; i++)
{
// y = yVals[i];
@ -55,10 +54,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
// Adding & multiplying 8 elements at one time:
Vector<float> r = y + (cr * new Vector<float>(1.402F));
Vector<float> g = y - (cb * new Vector<float>(0.344136F)) - (cr * new Vector<float>(0.714136F));
Vector<float> b = y + (cb * new Vector<float>(1.772F));
Vector<float> r = y + (cr * rCrMult);
Vector<float> g = y + (cb * gCbMult) + (cr * gCrMult);
Vector<float> b = y + (cb * bCbMult);
r = (max - r.FastRound()) * scaledK;
g = (max - g.FastRound()) * scaledK;
@ -71,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
protected override void ConvertCoreInplace(in ComponentValues values) =>
FromYccKBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
FromYccKScalar.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
}
}
}

18
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Vector8JpegColorConverter.cs

@ -1,18 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal abstract class Vector8JpegColorConverter : VectorizedJpegColorConverter
{
protected Vector8JpegColorConverter(JpegColorSpace colorSpace, int precision)
: base(colorSpace, precision, 8)
{
}
protected sealed override bool IsAvailable => SimdUtils.HasVector8;
}
}
}

47
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs

@ -1,47 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal abstract class VectorizedJpegColorConverter : JpegColorConverter
{
private readonly int vectorSize;
protected VectorizedJpegColorConverter(JpegColorSpace colorSpace, int precision, int vectorSize)
: base(colorSpace, precision)
{
this.vectorSize = vectorSize;
}
public override void ConvertToRgbInplace(in ComponentValues values)
{
int length = values.Component0.Length;
int remainder = values.Component0.Length % this.vectorSize;
int simdCount = length - remainder;
if (simdCount > 0)
{
// This implementation is actually AVX specific.
// An AVX register is capable of storing 8 float-s.
if (!this.IsAvailable)
{
throw new InvalidOperationException(
"This converter can be used only on architecture having 256 byte floating point SIMD registers!");
}
this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount));
}
this.ConvertCoreInplace(values.Slice(simdCount, remainder));
}
protected virtual void ConvertCoreVectorizedInplace(in ComponentValues values) => throw new NotImplementedException();
protected virtual void ConvertCoreInplace(in ComponentValues values) => throw new NotImplementedException();
}
}
}

32
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs

@ -0,0 +1,32 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
/// <summary>
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
/// based on <see cref="Avx"/> instructions.
/// </summary>
/// <remarks>
/// Converters of this family would expect input buffers lengths to be
/// divisible by 8 without a remainder.
/// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks.
/// DO NOT pass test data of invalid size to these converters as they
/// potentially won't do a bound check and return a false positive result.
/// </remarks>
internal abstract class JpegColorConverterAvx : JpegColorConverterBase
{
protected JpegColorConverterAvx(JpegColorSpace colorSpace, int precision)
: base(colorSpace, precision)
{
}
public override bool IsAvailable => Avx.IsSupported;
}
}
}
#endif

111
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs

@ -4,26 +4,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Tuples;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
/// <summary>
/// Encapsulates the conversion of Jpeg channels to RGBA values packed in <see cref="Vector4"/> buffer.
/// Encapsulates the conversion of color channels from jpeg image to RGB channels.
/// </summary>
internal abstract partial class JpegColorConverter
internal abstract partial class JpegColorConverterBase
{
/// <summary>
/// The available converters
/// </summary>
private static readonly JpegColorConverter[] Converters = CreateConverters();
private static readonly JpegColorConverterBase[] Converters = CreateConverters();
/// <summary>
/// Initializes a new instance of the <see cref="JpegColorConverter"/> class.
/// Initializes a new instance of the <see cref="JpegColorConverterBase"/> class.
/// </summary>
protected JpegColorConverter(JpegColorSpace colorSpace, int precision)
protected JpegColorConverterBase(JpegColorSpace colorSpace, int precision)
{
this.ColorSpace = colorSpace;
this.Precision = precision;
@ -32,10 +30,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
/// <summary>
/// Gets a value indicating whether this <see cref="JpegColorConverter"/> is available
/// Gets a value indicating whether this <see cref="JpegColorConverterBase"/> is available
/// on the current runtime and CPU architecture.
/// </summary>
protected abstract bool IsAvailable { get; }
public abstract bool IsAvailable { get; }
/// <summary>
/// Gets the <see cref="JpegColorSpace"/> of this converter.
@ -58,11 +56,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
private float HalfValue { get; }
/// <summary>
/// Returns the <see cref="JpegColorConverter"/> corresponding to the given <see cref="JpegColorSpace"/>
/// Returns the <see cref="JpegColorConverterBase"/> corresponding to the given <see cref="JpegColorSpace"/>
/// </summary>
public static JpegColorConverter GetConverter(JpegColorSpace colorSpace, int precision)
public static JpegColorConverterBase GetConverter(JpegColorSpace colorSpace, int precision)
{
JpegColorConverter converter = Array.Find(
JpegColorConverterBase converter = Array.Find(
Converters,
c => c.ColorSpace == colorSpace
&& c.Precision == precision);
@ -82,11 +80,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
public abstract void ConvertToRgbInplace(in ComponentValues values);
/// <summary>
/// Returns the <see cref="JpegColorConverter"/>s for all supported colorspaces and precisions.
/// Returns the <see cref="JpegColorConverterBase"/>s for all supported colorspaces and precisions.
/// </summary>
private static JpegColorConverter[] CreateConverters()
private static JpegColorConverterBase[] CreateConverters()
{
var converters = new List<JpegColorConverter>();
var converters = new List<JpegColorConverterBase>();
// 8-bit converters
converters.AddRange(GetYCbCrConverters(8));
@ -106,63 +104,63 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
/// <summary>
/// Returns the <see cref="JpegColorConverter"/>s for the YCbCr colorspace.
/// Returns the <see cref="JpegColorConverterBase"/>s for the YCbCr colorspace.
/// </summary>
private static IEnumerable<JpegColorConverter> GetYCbCrConverters(int precision)
private static IEnumerable<JpegColorConverterBase> GetYCbCrConverters(int precision)
{
#if SUPPORTS_RUNTIME_INTRINSICS
yield return new FromYCbCrAvx2(precision);
yield return new FromYCbCrAvx(precision);
#endif
yield return new FromYCbCrVector8(precision);
yield return new FromYCbCrVector4(precision);
yield return new FromYCbCrBasic(precision);
yield return new FromYCbCrVector(precision);
yield return new FromYCbCrScalar(precision);
}
/// <summary>
/// Returns the <see cref="JpegColorConverter"/>s for the YccK colorspace.
/// Returns the <see cref="JpegColorConverterBase"/>s for the YccK colorspace.
/// </summary>
private static IEnumerable<JpegColorConverter> GetYccKConverters(int precision)
private static IEnumerable<JpegColorConverterBase> GetYccKConverters(int precision)
{
#if SUPPORTS_RUNTIME_INTRINSICS
yield return new FromYccKAvx2(precision);
yield return new FromYccKAvx(precision);
#endif
yield return new FromYccKVector8(precision);
yield return new FromYccKBasic(precision);
yield return new FromYccKVector(precision);
yield return new FromYccKScalar(precision);
}
/// <summary>
/// Returns the <see cref="JpegColorConverter"/>s for the CMYK colorspace.
/// Returns the <see cref="JpegColorConverterBase"/>s for the CMYK colorspace.
/// </summary>
private static IEnumerable<JpegColorConverter> GetCmykConverters(int precision)
private static IEnumerable<JpegColorConverterBase> GetCmykConverters(int precision)
{
#if SUPPORTS_RUNTIME_INTRINSICS
yield return new FromCmykAvx2(precision);
yield return new FromCmykAvx(precision);
#endif
yield return new FromCmykVector8(precision);
yield return new FromCmykBasic(precision);
yield return new FromCmykVector(precision);
yield return new FromCmykScalar(precision);
}
/// <summary>
/// Returns the <see cref="JpegColorConverter"/>s for the gray scale colorspace.
/// Returns the <see cref="JpegColorConverterBase"/>s for the gray scale colorspace.
/// </summary>
private static IEnumerable<JpegColorConverter> GetGrayScaleConverters(int precision)
private static IEnumerable<JpegColorConverterBase> GetGrayScaleConverters(int precision)
{
#if SUPPORTS_RUNTIME_INTRINSICS
yield return new FromGrayscaleAvx2(precision);
yield return new FromGrayscaleAvx(precision);
#endif
yield return new FromGrayscaleBasic(precision);
yield return new FromGrayScaleVector(precision);
yield return new FromGrayscaleScalar(precision);
}
/// <summary>
/// Returns the <see cref="JpegColorConverter"/>s for the RGB colorspace.
/// Returns the <see cref="JpegColorConverterBase"/>s for the RGB colorspace.
/// </summary>
private static IEnumerable<JpegColorConverter> GetRgbConverters(int precision)
private static IEnumerable<JpegColorConverterBase> GetRgbConverters(int precision)
{
#if SUPPORTS_RUNTIME_INTRINSICS
yield return new FromRgbAvx2(precision);
yield return new FromRgbAvx(precision);
#endif
yield return new FromRgbVector8(precision);
yield return new FromRgbBasic(precision);
yield return new FromRgbVector(precision);
yield return new FromRgbScalar(precision);
}
/// <summary>
@ -200,18 +198,39 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// <summary>
/// Initializes a new instance of the <see cref="ComponentValues"/> struct.
/// </summary>
/// <param name="componentBuffers">The 1-4 sized list of component buffers.</param>
/// <param name="row">The row to convert</param>
/// <param name="componentBuffers">List of component buffers.</param>
/// <param name="row">Row to convert</param>
public ComponentValues(IReadOnlyList<Buffer2D<float>> componentBuffers, int row)
{
DebugGuard.MustBeGreaterThan(componentBuffers.Count, 0, nameof(componentBuffers));
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].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;
}
/// <summary>
/// Initializes a new instance of the <see cref="ComponentValues"/> struct.
/// </summary>
/// <param name="processors">List of component color processors.</param>
/// <param name="row">Row to convert</param>
public ComponentValues(IReadOnlyList<JpegComponentPostProcessor> processors, int row)
{
DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors));
this.ComponentCount = processors.Count;
this.Component0 = processors[0].GetColorBufferRowSpan(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 ? processors[1].GetColorBufferRowSpan(row) : this.Component0;
this.Component2 = this.ComponentCount > 2 ? processors[2].GetColorBufferRowSpan(row) : this.Component0;
this.Component3 = this.ComponentCount > 3 ? processors[3].GetColorBufferRowSpan(row) : Span<float>.Empty;
}
internal ComponentValues(

22
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs

@ -0,0 +1,22 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
/// <summary>
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
/// based on scalar instructions.
/// </summary>
internal abstract class JpegColorConverterScalar : JpegColorConverterBase
{
protected JpegColorConverterScalar(JpegColorSpace colorSpace, int precision)
: base(colorSpace, precision)
{
}
public override bool IsAvailable => true;
}
}
}

59
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs

@ -0,0 +1,59 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverterBase
{
/// <summary>
/// <see cref="JpegColorConverterBase"/> abstract base for implementations
/// based on <see cref="Vector"/> API.
/// </summary>
/// <remarks>
/// Converters of this family can work with data of any size.
/// Even though real life data is guaranteed to be of size
/// divisible by 8 newer SIMD instructions like AVX512 won't work with
/// such data out of the box. These converters have fallback code
/// for 'remainder' data.
/// </remarks>
internal abstract class JpegColorConverterVector : JpegColorConverterBase
{
protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision)
: base(colorSpace, precision)
{
}
public sealed override bool IsAvailable => Vector.IsHardwareAccelerated && Vector<float>.Count % 4 == 0;
public override void ConvertToRgbInplace(in ComponentValues values)
{
DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware.");
int length = values.Component0.Length;
int remainder = (int)((uint)length % (uint)Vector<float>.Count);
// Jpeg images are guaranteed to have pixel strides at least 8 pixels wide
// Thus there's no need to check whether simdCount is greater than zero
int simdCount = length - remainder;
this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount));
// Jpeg images width is always divisible by 8 without a remainder
// so it's safe to say SSE/AVX implementations would never have
// 'remainder' pixels
// But some exotic simd implementations e.g. AVX-512 can have
// remainder pixels
if (remainder > 0)
{
this.ConvertCoreInplace(values.Slice(simdCount, remainder));
}
}
protected virtual void ConvertCoreVectorizedInplace(in ComponentValues values) => throw new NotImplementedException();
protected virtual void ConvertCoreInplace(in ComponentValues values) => throw new NotImplementedException();
}
}
}

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

1
src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs

@ -5,7 +5,6 @@ using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <inheritdoc />
/// <summary>
/// Represents decompressed, unprocessed jpeg data with spectral space <see cref="IJpegComponent" />-s.
/// </summary>

82
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs

@ -1,82 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Encapsulates the implementation of processing "raw" jpeg buffers into Jpeg image channels.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct JpegBlockPostProcessor
{
/// <summary>
/// Source block
/// </summary>
public Block8x8F SourceBlock;
/// <summary>
/// The quantization table as <see cref="Block8x8F"/>.
/// </summary>
public Block8x8F DequantiazationTable;
/// <summary>
/// Defines the horizontal and vertical scale we need to apply to the 8x8 sized block.
/// </summary>
private Size subSamplingDivisors;
/// <summary>
/// Initializes a new instance of the <see cref="JpegBlockPostProcessor"/> struct.
/// </summary>
/// <param name="decoder">The raw jpeg data.</param>
/// <param name="component">The raw component.</param>
public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component)
{
int qtIndex = component.QuantizationTableIndex;
this.DequantiazationTable = decoder.QuantizationTables[qtIndex];
this.subSamplingDivisors = component.SubSamplingDivisors;
this.SourceBlock = default;
}
/// <summary>
/// Processes 'sourceBlock' producing Jpeg color channel values from spectral values:
/// - Dequantize
/// - Applying IDCT
/// - Level shift by +maximumValue/2, clip to [0, maximumValue]
/// - Copy the resulting color values into 'destArea' scaling up the block by amount defined in <see cref="subSamplingDivisors"/>.
/// </summary>
/// <param name="sourceBlock">The source block.</param>
/// <param name="destAreaOrigin">Reference to the origin of the destination pixel area.</param>
/// <param name="destAreaStride">The width of the destination pixel buffer.</param>
/// <param name="maximumValue">The maximum value derived from the bitdepth.</param>
public void ProcessBlockColorsInto(
ref Block8x8 sourceBlock,
ref float destAreaOrigin,
int destAreaStride,
float maximumValue)
{
ref Block8x8F block = ref this.SourceBlock;
block.LoadFrom(ref sourceBlock);
// Dequantize:
block.MultiplyInPlace(ref this.DequantiazationTable);
FastFloatingPointDCT.TransformIDCT(ref block);
// To conform better to libjpeg we actually NEED TO loose precision here.
// This is because they store blocks as Int16 between all the operations.
// To be "more accurate", we need to emulate this by rounding!
block.NormalizeColorsAndRoundInPlace(maximumValue);
block.ScaledCopyTo(
ref destAreaOrigin,
destAreaStride,
this.subSamplingDivisors.Width,
this.subSamplingDivisors.Height);
}
}
}

15
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs

@ -10,14 +10,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
Undefined = 0,
/// <summary>
/// Color space with 1 component.
/// </summary>
Grayscale,
/// <summary>
/// Color space with 4 components.
/// </summary>
Ycck,
/// <summary>
/// Color space with 4 components.
/// </summary>
Cmyk,
/// <summary>
/// Color space with 3 components.
/// </summary>
RGB,
/// <summary>
/// Color space with 3 components.
/// </summary>
YCbCr
}
}

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

@ -11,11 +11,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
internal class JpegComponentPostProcessor : IDisposable
{
/// <summary>
/// Points to the current row in <see cref="Component"/>.
/// </summary>
private int currentComponentRowInBlocks;
/// <summary>
/// The size of the area in <see cref="ColorBuffer"/> corresponding to one 8x8 Jpeg block
/// </summary>
@ -26,6 +21,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
private readonly JpegFrame frame;
/// <summary>
/// Gets the maximal number of block rows being processed in one step.
/// </summary>
private readonly int blockRowsPerStep;
/// <summary>
/// Gets the <see cref="IJpegComponent"/> component containing decoding meta information.
/// </summary>
private readonly IJpegComponent component;
/// <summary>
/// Gets the <see cref="IRawJpegData"/> instance containing decoding meta information.
/// </summary>
private readonly IRawJpegData rawJpeg;
/// <summary>
/// Initializes a new instance of the <see cref="JpegComponentPostProcessor"/> class.
/// </summary>
@ -33,98 +43,87 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
this.frame = frame;
this.Component = component;
this.RawJpeg = rawJpeg;
this.blockAreaSize = this.Component.SubSamplingDivisors * 8;
this.component = component;
this.rawJpeg = rawJpeg;
this.blockAreaSize = this.component.SubSamplingDivisors * 8;
this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>(
postProcessorBufferSize.Width,
postProcessorBufferSize.Height,
this.blockAreaSize.Height);
this.BlockRowsPerStep = postProcessorBufferSize.Height / 8 / this.Component.SubSamplingDivisors.Height;
this.blockRowsPerStep = postProcessorBufferSize.Height / 8 / this.component.SubSamplingDivisors.Height;
}
public IRawJpegData RawJpeg { get; }
/// <summary>
/// Gets the <see cref="Component"/>
/// </summary>
public IJpegComponent Component { get; }
/// <summary>
/// Gets the temporary working buffer of color values.
/// </summary>
public Buffer2D<float> ColorBuffer { get; }
/// <summary>
/// Gets <see cref="IJpegComponent.SizeInBlocks"/>
/// </summary>
public Size SizeInBlocks => this.Component.SizeInBlocks;
/// <summary>
/// Gets the maximal number of block rows being processed in one step.
/// </summary>
public int BlockRowsPerStep { get; }
/// <inheritdoc />
public void Dispose() => this.ColorBuffer.Dispose();
/// <summary>
/// Invoke <see cref="JpegBlockPostProcessor"/> for <see cref="BlockRowsPerStep"/> block rows, copy the result into <see cref="ColorBuffer"/>.
/// Convert raw spectral DCT data to color data and copy it to the color buffer <see cref="ColorBuffer"/>.
/// </summary>
public void CopyBlocksToColorBuffer(int step)
public void CopyBlocksToColorBuffer(int spectralStep)
{
Buffer2D<Block8x8> spectralBuffer = this.Component.SpectralBlocks;
var blockPp = new JpegBlockPostProcessor(this.RawJpeg, this.Component);
Buffer2D<Block8x8> spectralBuffer = this.component.SpectralBlocks;
float maximumValue = this.frame.MaxColorChannelValue;
int destAreaStride = this.ColorBuffer.Width;
int yBlockStart = step * this.BlockRowsPerStep;
int yBlockStart = spectralStep * this.blockRowsPerStep;
for (int y = 0; y < this.BlockRowsPerStep; y++)
{
int yBlock = yBlockStart + y;
Size subSamplingDivisors = this.component.SubSamplingDivisors;
if (yBlock >= spectralBuffer.Height)
{
break;
}
Block8x8F dequantTable = this.rawJpeg.QuantizationTables[this.component.QuantizationTableIndex];
Block8x8F workspaceBlock = default;
for (int y = 0; y < this.blockRowsPerStep; y++)
{
int yBuffer = y * this.blockAreaSize.Height;
Span<float> colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer);
Span<Block8x8> blockRow = spectralBuffer.GetRowSpan(yBlock);
Span<float> colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer);
Span<Block8x8> blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y);
// see: https://github.com/SixLabors/ImageSharp/issues/824
int widthInBlocks = Math.Min(spectralBuffer.Width, this.SizeInBlocks.Width);
for (int xBlock = 0; xBlock < widthInBlocks; xBlock++)
for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++)
{
ref Block8x8 block = ref blockRow[xBlock];
int xBuffer = xBlock * this.blockAreaSize.Width;
ref float destAreaOrigin = ref colorBufferRow[xBuffer];
blockPp.ProcessBlockColorsInto(ref block, ref destAreaOrigin, destAreaStride, maximumValue);
// Integer to float
workspaceBlock.LoadFrom(ref blockRow[xBlock]);
// Dequantize
workspaceBlock.MultiplyInPlace(ref dequantTable);
// Convert from spectral to color
FastFloatingPointDCT.TransformIDCT(ref workspaceBlock);
// To conform better to libjpeg we actually NEED TO loose precision here.
// This is because they store blocks as Int16 between all the operations.
// To be "more accurate", we need to emulate this by rounding!
workspaceBlock.NormalizeColorsAndRoundInPlace(maximumValue);
// Write to color buffer acording to sampling factors
int xColorBufferStart = xBlock * this.blockAreaSize.Width;
workspaceBlock.ScaledCopyTo(
ref colorBufferRow[xColorBufferStart],
destAreaStride,
subSamplingDivisors.Width,
subSamplingDivisors.Height);
}
}
}
public void ClearSpectralBuffers()
{
Buffer2D<Block8x8> spectralBlocks = this.Component.SpectralBlocks;
Buffer2D<Block8x8> spectralBlocks = this.component.SpectralBlocks;
for (int i = 0; i < spectralBlocks.Height; i++)
{
spectralBlocks.GetRowSpan(i).Clear();
spectralBlocks.DangerousGetRowSpan(i).Clear();
}
}
public void CopyBlocksToColorBuffer()
{
this.CopyBlocksToColorBuffer(this.currentComponentRowInBlocks);
this.currentComponentRowInBlocks += this.BlockRowsPerStep;
}
public Span<float> GetColorBufferRowSpan(int row) =>
this.ColorBuffer.DangerousGetRowSpan(row);
}
}

15
src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs

@ -6,11 +6,8 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Converter used to convert jpeg spectral data.
/// Converter used to convert jpeg spectral data to color pixels.
/// </summary>
/// <remarks>
/// This is tightly coupled with <see cref="HuffmanScanDecoder"/> and <see cref="JpegDecoderCore"/>.
/// </remarks>
internal abstract class SpectralConverter
{
/// <summary>
@ -30,12 +27,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData);
/// <summary>
/// Called once per spectral stride for each component in <see cref="HuffmanScanDecoder"/>.
/// This is called only for baseline interleaved jpegs.
/// Converts single spectral jpeg stride to color stride in baseline
/// decoding mode.
/// </summary>
/// <remarks>
/// Called once per decoded spectral stride in <see cref="HuffmanScanDecoder"/>
/// only for baseline interleaved jpeg images.
/// Spectral 'stride' doesn't particularly mean 'single stride'.
/// Actual stride height depends on the subsampling factor of the given component.
/// Actual stride height depends on the subsampling factor of the given image.
/// </remarks>
public abstract void ConvertStrideBaseline();
@ -58,6 +57,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <param name="frame">The jpeg frame with the color space to convert to.</param>
/// <param name="jpegData">The raw JPEG data.</param>
/// <returns>The color converter.</returns>
protected virtual JpegColorConverter GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision);
protected virtual JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(jpegData.ColorSpace, frame.Precision);
}
}

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

@ -4,7 +4,6 @@
using System;
using System.Buffers;
using System.Linq;
using System.Numerics;
using System.Threading;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
using SixLabors.ImageSharp.Memory;
@ -12,35 +11,78 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <inheritdoc/>
/// <remarks>
/// Color decoding scheme:
/// <list type = "number" >
/// <listheader>
/// <item>Decode spectral data to Jpeg color space</item>
/// <item>Convert from Jpeg color space to RGB</item>
/// <item>Convert from RGB to target pixel space</item>
/// </listheader>
/// </list>
/// </remarks>
internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// <see cref="Configuration"/> instance associated with current
/// decoding routine.
/// </summary>
private readonly Configuration configuration;
private readonly CancellationToken cancellationToken;
/// <summary>
/// Jpeg component converters from decompressed spectral to color data.
/// </summary>
private JpegComponentPostProcessor[] componentProcessors;
private JpegColorConverter colorConverter;
/// <summary>
/// Color converter from jpeg color space to target pixel color space.
/// </summary>
private JpegColorConverterBase colorConverter;
// private IMemoryOwner<Vector4> rgbaBuffer;
/// <summary>
/// Intermediate buffer of RGB components used in color conversion.
/// </summary>
private IMemoryOwner<byte> rgbBuffer;
/// <summary>
/// Proxy buffer used in packing from RGB to target TPixel pixels.
/// </summary>
private IMemoryOwner<TPixel> paddedProxyPixelRow;
/// <summary>
/// Resulting 2D pixel buffer.
/// </summary>
private Buffer2D<TPixel> pixelBuffer;
/// <summary>
/// How many pixel rows are processed in one 'stride'.
/// </summary>
private int pixelRowsPerStep;
/// <summary>
/// How many pixel rows were processed.
/// </summary>
private int pixelRowCounter;
public SpectralConverter(Configuration configuration, CancellationToken cancellationToken)
{
/// <summary>
/// Initializes a new instance of the <see cref="SpectralConverter{TPixel}" /> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
public SpectralConverter(Configuration configuration) =>
this.configuration = configuration;
this.cancellationToken = cancellationToken;
}
public Buffer2D<TPixel> GetPixelBuffer()
/// <summary>
/// Gets converted pixel buffer.
/// </summary>
/// <remarks>
/// For non-baseline interleaved jpeg this method does a 'lazy' spectral
/// conversion from spectral to color.
/// </remarks>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Pixel buffer.</returns>
public Buffer2D<TPixel> GetPixelBuffer(CancellationToken cancellationToken)
{
if (!this.Converted)
{
@ -48,7 +90,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
for (int step = 0; step < steps; step++)
{
this.cancellationToken.ThrowIfCancellationRequested();
cancellationToken.ThrowIfCancellationRequested();
this.ConvertStride(step);
}
}
@ -102,30 +144,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
}
/// <inheritdoc/>
public void Dispose()
{
if (this.componentProcessors != null)
{
foreach (JpegComponentPostProcessor cpp in this.componentProcessors)
{
cpp.Dispose();
}
}
this.rgbBuffer?.Dispose();
this.paddedProxyPixelRow?.Dispose();
}
/// <summary>
/// Converts single spectral jpeg stride to color stride.
/// </summary>
/// <param name="spectralStep">Spectral stride index.</param>
private void ConvertStride(int spectralStep)
{
int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep);
var buffers = new Buffer2D<float>[this.componentProcessors.Length];
for (int i = 0; i < this.componentProcessors.Length; i++)
{
this.componentProcessors[i].CopyBlocksToColorBuffer(spectralStep);
buffers[i] = this.componentProcessors[i].ColorBuffer;
}
int width = this.pixelBuffer.Width;
@ -134,7 +163,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
int y = yy - this.pixelRowCounter;
var values = new JpegColorConverter.ComponentValues(buffers, y);
var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y);
this.colorConverter.ConvertToRgbInplace(values);
values = values.Slice(0, width); // slice away Jpeg padding
@ -158,11 +187,26 @@ 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));
}
}
this.pixelRowCounter += this.pixelRowsPerStep;
}
/// <inheritdoc/>
public void Dispose()
{
if (this.componentProcessors != null)
{
foreach (JpegComponentPostProcessor cpp in this.componentProcessors)
{
cpp.Dispose();
}
}
this.rgbBuffer?.Dispose();
this.paddedProxyPixelRow?.Dispose();
}
}
}

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

@ -278,11 +278,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
// ReSharper disable once InconsistentNaming
int prevDCY = 0;
var pixelConverter = LuminanceForwardConverter<TPixel>.Create();
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
var pixelConverter = new LuminanceForwardConverter<TPixel>(frame);
for (int y = 0; y < pixels.Height; y += 8)
{
cancellationToken.ThrowIfCancellationRequested();
@ -290,7 +291,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
for (int x = 0; x < pixels.Width; x += 8)
{
pixelConverter.Convert(frame, x, y, ref currentRows);
pixelConverter.Convert(x, y, ref currentRows);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
@ -649,6 +650,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
this.target.Write(this.streamWriteBuffer, 0, writeIdx);
this.emitWriteIndex = this.emitBuffer.Length;
}
/// <summary>
@ -659,11 +661,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// This must be called only if <see cref="IsStreamFlushNeeded"/> is true
/// only during the macroblocks encoding routine.
/// </remarks>
private void FlushToStream()
{
private void FlushToStream() =>
this.FlushToStream(this.emitWriteIndex * 4);
this.emitWriteIndex = this.emitBuffer.Length;
}
/// <summary>
/// Flushes final cached bits to the stream padding 1's to
@ -680,10 +679,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
// And writing only valuable count of bytes count we want to write to the output stream
int valuableBytesCount = (int)Numerics.DivideCeil((uint)this.bitCount, 8);
uint packedBytes = this.accumulatedBits | (uint.MaxValue >> this.bitCount);
this.emitBuffer[--this.emitWriteIndex] = packedBytes;
this.emitBuffer[this.emitWriteIndex - 1] = packedBytes;
// Flush cached bytes to the output stream with padding bits
this.FlushToStream((this.emitWriteIndex * 4) - 4 + valuableBytesCount);
int lastByteIndex = (this.emitWriteIndex * 4) - valuableBytesCount;
this.FlushToStream(lastByteIndex);
}
}
}

49
src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs

@ -3,6 +3,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
@ -15,39 +16,63 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
internal ref struct LuminanceForwardConverter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Number of pixels processed per single <see cref="Convert(int, int, ref RowOctet{TPixel})"/> call
/// </summary>
private const int PixelsPerSample = 8 * 8;
/// <summary>
/// The Y component
/// </summary>
public Block8x8F Y;
/// <summary>
/// Temporal 8x8 block to hold TPixel data
/// Temporal 64-pixel span to hold unconverted TPixel data.
/// </summary>
private readonly Span<TPixel> pixelSpan;
/// <summary>
/// Temporal 64-byte span to hold converted <see cref="L8"/> data.
/// </summary>
private readonly Span<L8> l8Span;
/// <summary>
/// Sampled pixel buffer size.
/// </summary>
private GenericBlock8x8<TPixel> pixelBlock;
private readonly Size samplingAreaSize;
/// <summary>
/// Temporal RGB block
/// <see cref="Configuration"/> for internal operations.
/// </summary>
private GenericBlock8x8<L8> l8Block;
private readonly Configuration config;
public static LuminanceForwardConverter<TPixel> Create()
public LuminanceForwardConverter(ImageFrame<TPixel> frame)
{
var result = default(LuminanceForwardConverter<TPixel>);
return result;
this.Y = default;
this.pixelSpan = new TPixel[PixelsPerSample].AsSpan();
this.l8Span = new L8[PixelsPerSample].AsSpan();
this.samplingAreaSize = new Size(frame.Width, frame.Height);
this.config = frame.GetConfiguration();
}
/// <summary>
/// Gets size of sampling area from given frame pixel buffer.
/// </summary>
private static Size SampleSize => new(8, 8);
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>)
/// </summary>
public void Convert(ImageFrame<TPixel> frame, int x, int y, ref RowOctet<TPixel> currentRows)
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows)
{
this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, ref currentRows);
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);
Span<L8> l8Span = this.l8Block.AsSpanUnsafe();
PixelOperations<TPixel>.Instance.ToL8(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), l8Span);
PixelOperations<TPixel>.Instance.ToL8(this.config, this.pixelSpan, this.l8Span);
ref Block8x8F yBlock = ref this.Y;
ref L8 l8Start = ref l8Span[0];
ref L8 l8Start = ref MemoryMarshal.GetReference(this.l8Span);
for (int i = 0; i < Block8x8F.Size; i++)
{

10
src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs

@ -26,11 +26,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// </summary>
private const int RgbSpanByteSize = PixelsPerSample * 3;
/// <summary>
/// <see cref="Size"/> of sampling area from given frame pixel buffer.
/// </summary>
private static readonly Size SampleSize = new Size(8, 8);
/// <summary>
/// The Red component.
/// </summary>
@ -81,6 +76,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
this.config = frame.GetConfiguration();
}
/// <summary>
/// Gets size of sampling area from given frame pixel buffer.
/// </summary>
private static Size SampleSize => new(8, 8);
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x, y) to Rgb24.
/// </summary>

10
src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs

@ -25,11 +25,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// </summary>
private const int RgbSpanByteSize = PixelsPerSample * 3;
/// <summary>
/// <see cref="Size"/> of sampling area from given frame pixel buffer
/// </summary>
private static readonly Size SampleSize = new Size(16, 8);
/// <summary>
/// The left Y component
/// </summary>
@ -102,6 +97,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
}
/// <summary>
/// Gets size of sampling area from given frame pixel buffer.
/// </summary>
private static Size SampleSize => new(16, 8);
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows, int idx)
{
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);

10
src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs

@ -25,11 +25,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// </summary>
private const int RgbSpanByteSize = PixelsPerSample * 3;
/// <summary>
/// <see cref="Size"/> of sampling area from given frame pixel buffer
/// </summary>
private static readonly Size SampleSize = new Size(8, 8);
/// <summary>
/// The Y component
/// </summary>
@ -96,6 +91,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
}
/// <summary>
/// Gets size of sampling area from given frame pixel buffer.
/// </summary>
private static Size SampleSize => new(8, 8);
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>, <see cref="Cb"/>, <see cref="Cr"/>)
/// </summary>

23
src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs

@ -1,23 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// <auto-generated />
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal unsafe partial struct GenericBlock8x8<T>
{
#pragma warning disable 169
// It's not allowed use fix-sized buffers with generics, need to place all the fields manually:
private T _y0_x0, _y0_x1, _y0_x2, _y0_x3, _y0_x4, _y0_x5, _y0_x6, _y0_x7;
private T _y1_x0, _y1_x1, _y1_x2, _y1_x3, _y1_x4, _y1_x5, _y1_x6, _y1_x7;
private T _y2_x0, _y2_x1, _y2_x2, _y2_x3, _y2_x4, _y2_x5, _y2_x6, _y2_x7;
private T _y3_x0, _y3_x1, _y3_x2, _y3_x3, _y3_x4, _y3_x5, _y3_x6, _y3_x7;
private T _y4_x0, _y4_x1, _y4_x2, _y4_x3, _y4_x4, _y4_x5, _y4_x6, _y4_x7;
private T _y5_x0, _y5_x1, _y5_x2, _y5_x3, _y5_x4, _y5_x5, _y5_x6, _y5_x7;
private T _y6_x0, _y6_x1, _y6_x2, _y6_x3, _y6_x4, _y6_x5, _y6_x6, _y6_x7;
private T _y7_x0, _y7_x1, _y7_x2, _y7_x3, _y7_x4, _y7_x5, _y7_x6, _y7_x7;
#pragma warning restore 169
}
}

0
src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.tt

122
src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs

@ -1,122 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
/// <summary>
/// A generic 8x8 block implementation, useful for manipulating custom 8x8 pixel data.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct GenericBlock8x8<T>
where T : unmanaged
{
public const int Size = 64;
/// <summary>
/// FOR TESTING ONLY!
/// Gets or sets a <see cref="Rgb24"/> value at the given index
/// </summary>
/// <param name="idx">The index</param>
/// <returns>The value</returns>
public T this[int idx]
{
get
{
ref T selfRef = ref Unsafe.As<GenericBlock8x8<T>, T>(ref this);
return Unsafe.Add(ref selfRef, idx);
}
set
{
ref T selfRef = ref Unsafe.As<GenericBlock8x8<T>, T>(ref this);
Unsafe.Add(ref selfRef, idx) = value;
}
}
/// <summary>
/// FOR TESTING ONLY!
/// Gets or sets a value in a row+column of the 8x8 block
/// </summary>
/// <param name="x">The x position index in the row</param>
/// <param name="y">The column index</param>
/// <returns>The value</returns>
public T this[int x, int y]
{
get => this[(y * 8) + x];
set => this[(y * 8) + x] = value;
}
/// <summary>
/// Load a 8x8 region of an image into the block.
/// The "outlying" area of the block will be stretched out with pixels on the right and bottom edge of the image.
/// </summary>
public void LoadAndStretchEdges(Buffer2D<T> source, int sourceX, int sourceY, ref RowOctet<T> currentRows)
{
int width = Math.Min(8, source.Width - sourceX);
int height = Math.Min(8, source.Height - sourceY);
if (width <= 0 || height <= 0)
{
return;
}
uint byteWidth = (uint)width * (uint)Unsafe.SizeOf<T>();
int remainderXCount = 8 - width;
ref byte blockStart = ref Unsafe.As<GenericBlock8x8<T>, byte>(ref this);
int blockRowSizeInBytes = 8 * Unsafe.SizeOf<T>();
for (int y = 0; y < height; y++)
{
Span<T> row = currentRows[y];
ref byte s = ref Unsafe.As<T, byte>(ref row[sourceX]);
ref byte d = ref Unsafe.Add(ref blockStart, y * blockRowSizeInBytes);
Unsafe.CopyBlock(ref d, ref s, byteWidth);
ref T last = ref Unsafe.Add(ref Unsafe.As<byte, T>(ref d), width - 1);
for (int x = 1; x <= remainderXCount; x++)
{
Unsafe.Add(ref last, x) = last;
}
}
int remainderYCount = 8 - height;
if (remainderYCount == 0)
{
return;
}
ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * blockRowSizeInBytes);
for (int y = 1; y <= remainderYCount; y++)
{
ref byte remStart = ref Unsafe.Add(ref lastRowStart, blockRowSizeInBytes * y);
Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)blockRowSizeInBytes);
}
}
/// <summary>
/// Only for on-stack instances!
/// </summary>
public Span<T> AsSpanUnsafe()
{
#if SUPPORTS_CREATESPAN
Span<GenericBlock8x8<T>> s = MemoryMarshal.CreateSpan(ref this, 1);
return MemoryMarshal.Cast<GenericBlock8x8<T>, T>(s);
#else
return new Span<T>(Unsafe.AsPointer(ref this), Size);
#endif
}
}
}

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

2
src/ImageSharp/Formats/Jpeg/JpegDecoder.cs

@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
=> await this.DecodeAsync<Rgb24>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/>

7
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -175,7 +175,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
using var spectralConverter = new SpectralConverter<TPixel>(this.Configuration, cancellationToken);
using var spectralConverter = new SpectralConverter<TPixel>(this.Configuration);
var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken);
@ -185,7 +185,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.InitIptcProfile();
this.InitDerivedMetadataProperties();
return new Image<TPixel>(this.Configuration, spectralConverter.GetPixelBuffer(), this.Metadata);
return new Image<TPixel>(
this.Configuration,
spectralConverter.GetPixelBuffer(cancellationToken),
this.Metadata);
}
/// <inheritdoc/>

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

114
src/ImageSharp/Formats/Png/PngDecoder.cs

@ -20,37 +20,137 @@ namespace SixLabors.ImageSharp.Formats.Png
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new PngDecoderCore(configuration, this);
PngDecoderCore decoder = new(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
public Image Decode(Configuration configuration, Stream stream)
{
PngDecoderCore decoder = new(configuration, true);
IImageInfo info = decoder.Identify(configuration, stream);
stream.Position = 0;
PngMetadata meta = info.Metadata.GetPngMetadata();
PngColorType color = meta.ColorType.GetValueOrDefault();
PngBitDepth bits = meta.BitDepth.GetValueOrDefault();
switch (color)
{
case PngColorType.Grayscale:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? this.Decode<L16>(configuration, stream)
: this.Decode<La32>(configuration, stream);
}
return !meta.HasTransparency
? this.Decode<L8>(configuration, stream)
: this.Decode<La16>(configuration, stream);
case PngColorType.Rgb:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? this.Decode<Rgb48>(configuration, stream)
: this.Decode<Rgba64>(configuration, stream);
}
return !meta.HasTransparency
? this.Decode<Rgb24>(configuration, stream)
: this.Decode<Rgba32>(configuration, stream);
case PngColorType.Palette:
return this.Decode<Rgba32>(configuration, stream);
case PngColorType.GrayscaleWithAlpha:
return (bits == PngBitDepth.Bit16)
? this.Decode<La32>(configuration, stream)
: this.Decode<La16>(configuration, stream);
case PngColorType.RgbWithAlpha:
return (bits == PngBitDepth.Bit16)
? this.Decode<Rgba64>(configuration, stream)
: this.Decode<Rgba32>(configuration, stream);
default:
return this.Decode<Rgba32>(configuration, stream);
}
}
/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new PngDecoderCore(configuration, this);
PngDecoderCore decoder = new(configuration, this);
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);
{
PngDecoderCore decoder = new(configuration, true);
IImageInfo info = await decoder.IdentifyAsync(configuration, stream, cancellationToken).ConfigureAwait(false);
stream.Position = 0;
PngMetadata meta = info.Metadata.GetPngMetadata();
PngColorType color = meta.ColorType.GetValueOrDefault();
PngBitDepth bits = meta.BitDepth.GetValueOrDefault();
switch (color)
{
case PngColorType.Grayscale:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? await this.DecodeAsync<L16>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<La32>(configuration, stream, cancellationToken).ConfigureAwait(false);
}
return !meta.HasTransparency
? await this.DecodeAsync<L8>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<La16>(configuration, stream, cancellationToken).ConfigureAwait(false);
case PngColorType.Rgb:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? await this.DecodeAsync<Rgb48>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<Rgba64>(configuration, stream, cancellationToken).ConfigureAwait(false);
}
return !meta.HasTransparency
? await this.DecodeAsync<Rgb24>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);
case PngColorType.Palette:
return await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);
case PngColorType.GrayscaleWithAlpha:
return (bits == PngBitDepth.Bit16)
? await this.DecodeAsync<La32>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<La16>(configuration, stream, cancellationToken).ConfigureAwait(false);
case PngColorType.RgbWithAlpha:
return (bits == PngBitDepth.Bit16)
? await this.DecodeAsync<Rgba64>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);
default:
return await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);
}
}
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
var decoder = new PngDecoderCore(configuration, this);
PngDecoderCore decoder = new(configuration, this);
return decoder.Identify(configuration, stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
var decoder = new PngDecoderCore(configuration, this);
PngDecoderCore decoder = new(configuration, this);
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
}
}

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

@ -37,6 +37,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
private readonly bool ignoreMetadata;
/// <summary>
/// Gets or sets a value indicating whether to read the IHDR and tRNS chunks only.
/// </summary>
private readonly bool colorMetadataOnly;
/// <summary>
/// Used the manage memory allocations.
/// </summary>
@ -77,11 +82,6 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
private byte[] paletteAlpha;
/// <summary>
/// A value indicating whether the end chunk has been reached.
/// </summary>
private bool isEndChunkReached;
/// <summary>
/// Previous scanline processed.
/// </summary>
@ -124,13 +124,21 @@ namespace SixLabors.ImageSharp.Formats.Png
this.ignoreMetadata = options.IgnoreMetadata;
}
internal PngDecoderCore(Configuration configuration, bool colorMetadataOnly)
{
this.Configuration = configuration ?? Configuration.Default;
this.memoryAllocator = this.Configuration.MemoryAllocator;
this.colorMetadataOnly = colorMetadataOnly;
this.ignoreMetadata = true;
}
/// <inheritdoc/>
public Configuration Configuration { get; }
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
public Size Dimensions => new Size(this.header.Width, this.header.Height);
public Size Dimensions => new(this.header.Width, this.header.Height);
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
@ -143,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Png
Image<TPixel> image = null;
try
{
while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk))
while (this.TryReadChunk(out PngChunk chunk))
{
try
{
@ -168,12 +176,12 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case PngChunkType.Palette:
var pal = new byte[chunk.Length];
byte[] pal = new byte[chunk.Length];
chunk.Data.GetSpan().CopyTo(pal);
this.palette = pal;
break;
case PngChunkType.Transparency:
var alpha = new byte[chunk.Length];
byte[] alpha = new byte[chunk.Length];
chunk.Data.GetSpan().CopyTo(alpha);
this.paletteAlpha = alpha;
this.AssignTransparentMarkers(alpha, pngMetadata);
@ -190,15 +198,14 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngChunkType.Exif:
if (!this.ignoreMetadata)
{
var exifData = new byte[chunk.Length];
byte[] exifData = new byte[chunk.Length];
chunk.Data.GetSpan().CopyTo(exifData);
metadata.ExifProfile = new ExifProfile(exifData);
}
break;
case PngChunkType.End:
this.isEndChunkReached = true;
break;
goto EOF;
case PngChunkType.ProprietaryApple:
PngThrowHelper.ThrowInvalidChunkType("Proprietary Apple PNG detected! This PNG file is not conform to the specification and cannot be decoded.");
break;
@ -210,6 +217,7 @@ namespace SixLabors.ImageSharp.Formats.Png
}
}
EOF:
if (image is null)
{
PngThrowHelper.ThrowNoData();
@ -233,7 +241,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.currentStream.Skip(8);
try
{
while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk))
while (this.TryReadChunk(out PngChunk chunk))
{
try
{
@ -243,35 +251,89 @@ namespace SixLabors.ImageSharp.Formats.Png
this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.Physical:
if (this.colorMetadataOnly)
{
this.SkipChunkDataAndCrc(chunk);
break;
}
this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
break;
case PngChunkType.Gamma:
if (this.colorMetadataOnly)
{
this.SkipChunkDataAndCrc(chunk);
break;
}
this.ReadGammaChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.Data:
// Spec says tRNS must be before IDAT so safe to exit.
if (this.colorMetadataOnly)
{
goto EOF;
}
this.SkipChunkDataAndCrc(chunk);
break;
case PngChunkType.Transparency:
byte[] alpha = new byte[chunk.Length];
chunk.Data.GetSpan().CopyTo(alpha);
this.paletteAlpha = alpha;
this.AssignTransparentMarkers(alpha, pngMetadata);
if (this.colorMetadataOnly)
{
goto EOF;
}
break;
case PngChunkType.Text:
if (this.colorMetadataOnly)
{
this.SkipChunkDataAndCrc(chunk);
break;
}
this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.CompressedText:
if (this.colorMetadataOnly)
{
this.SkipChunkDataAndCrc(chunk);
break;
}
this.ReadCompressedTextChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.InternationalText:
if (this.colorMetadataOnly)
{
this.SkipChunkDataAndCrc(chunk);
break;
}
this.ReadInternationalTextChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.Exif:
if (this.colorMetadataOnly)
{
this.SkipChunkDataAndCrc(chunk);
break;
}
if (!this.ignoreMetadata)
{
var exifData = new byte[chunk.Length];
byte[] exifData = new byte[chunk.Length];
chunk.Data.GetSpan().CopyTo(exifData);
metadata.ExifProfile = new ExifProfile(exifData);
}
break;
case PngChunkType.End:
this.isEndChunkReached = true;
break;
goto EOF;
}
}
finally
@ -279,19 +341,20 @@ namespace SixLabors.ImageSharp.Formats.Png
chunk.Data?.Dispose(); // Data is rented in ReadChunkData()
}
}
EOF:
if (this.header.Width == 0 && this.header.Height == 0)
{
PngThrowHelper.ThrowNoHeader();
}
return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata);
}
finally
{
this.scanline?.Dispose();
this.previousScanline?.Dispose();
}
if (this.header.Width == 0 && this.header.Height == 0)
{
PngThrowHelper.ThrowNoHeader();
}
return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata);
}
/// <summary>
@ -364,11 +427,10 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="pngMetadata">The metadata to read to.</param>
/// <param name="data">The data containing physical data.</param>
private void ReadGammaChunk(PngMetadata pngMetadata, ReadOnlySpan<byte> data)
{
// The value is encoded as a 4-byte unsigned integer, representing gamma times 100000.
// For example, a gamma of 1/2.2 would be stored as 45455.
pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) / 100_000F;
}
=> pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) * 1e-5F;
/// <summary>
/// Initializes the image and various buffers needed for processing
@ -477,19 +539,17 @@ namespace SixLabors.ImageSharp.Formats.Png
private void ReadScanlines<TPixel>(PngChunk chunk, ImageFrame<TPixel> image, PngMetadata pngMetadata)
where TPixel : unmanaged, IPixel<TPixel>
{
using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk))
{
deframeStream.AllocateNewBytes(chunk.Length, true);
DeflateStream dataStream = deframeStream.CompressedStream;
using var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk);
deframeStream.AllocateNewBytes(chunk.Length, true);
DeflateStream dataStream = deframeStream.CompressedStream;
if (this.header.InterlaceMethod == PngInterlaceMode.Adam7)
{
this.DecodeInterlacedPixelData(dataStream, image, pngMetadata);
}
else
{
this.DecodePixelData(dataStream, image, pngMetadata);
}
if (this.header.InterlaceMethod == PngInterlaceMode.Adam7)
{
this.DecodeInterlacedPixelData(dataStream, image, pngMetadata);
}
else
{
this.DecodePixelData(dataStream, image, pngMetadata);
}
}
@ -565,6 +625,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{
int pass = 0;
int width = this.header.Width;
Buffer2D<TPixel> imageBuffer = image.PixelBuffer;
while (true)
{
int numColumns = Adam7.ComputeColumns(width, pass);
@ -623,7 +684,7 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
}
Span<TPixel> rowSpan = image.GetPixelRowSpan(this.currentRow);
Span<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(this.currentRow);
this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetadata, Adam7.FirstColumn[pass], Adam7.ColumnIncrement[pass]);
this.SwapScanlineBuffers();
@ -656,7 +717,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);
@ -918,7 +979,7 @@ namespace SixLabors.ImageSharp.Formats.Png
int zeroIndex = data.IndexOf((byte)0);
// Keywords are restricted to 1 to 79 bytes in length.
if (zeroIndex < PngConstants.MinTextKeywordLength || zeroIndex > PngConstants.MaxTextKeywordLength)
if (zeroIndex is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength)
{
return;
}
@ -1145,8 +1206,10 @@ namespace SixLabors.ImageSharp.Formats.Png
PngChunkType type = this.ReadChunkType();
// NOTE: Reading the chunk data is the responsible of the caller
if (type == PngChunkType.Data)
// NOTE: Reading the Data chunk is the responsible of the caller
// If we're reading color metadata only we're only interested in the IHDR and tRNS chunks.
// We can skip all other chunk data in the stream for better performance.
if (type == PngChunkType.Data || (this.colorMetadataOnly && type != PngChunkType.Header && type != PngChunkType.Transparency))
{
chunk = new PngChunk(length, type);

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,

7
src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs

@ -43,15 +43,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
}
int size = (int)this.memoryStream.Position;
#if !NETSTANDARD1_3
byte[] buffer = this.memoryStream.GetBuffer();
this.Output.Write(buffer, 0, size);
#else
this.memoryStream.SetLength(size);
this.memoryStream.Position = 0;
this.memoryStream.CopyTo(this.Output);
#endif
}
/// <inheritdoc/>

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

Loading…
Cancel
Save