Browse Source

Merge branch 'is_master/master' into je/nonstandard-png-exif

pull/1877/head
WINDEV2110EVAL\User 4 years ago
parent
commit
0409d9699a
  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. 4
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  7. 9
      src/ImageSharp/Advanced/AotCompilerTools.cs
  8. 24
      src/ImageSharp/Color/Color.Conversions.cs
  9. 14
      src/ImageSharp/Common/Helpers/DebugGuard.cs
  10. 9
      src/ImageSharp/Common/Helpers/Guard.cs
  11. 66
      src/ImageSharp/Common/Helpers/Numerics.cs
  12. 41
      src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs
  13. 5
      src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs
  14. 12
      src/ImageSharp/Common/Helpers/SimdUtils.cs
  15. 82
      src/ImageSharp/Common/Tuples/Vector4Pair.cs
  16. 42
      src/ImageSharp/Configuration.cs
  17. 18
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  18. 20
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  19. 4
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  20. 4
      src/ImageSharp/Formats/Gif/LzwDecoder.cs
  21. 2
      src/ImageSharp/Formats/Gif/LzwEncoder.cs
  22. 104
      src/ImageSharp/Formats/ImageExtensions.Save.cs
  23. 1
      src/ImageSharp/Formats/ImageExtensions.Save.tt
  24. 18
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Avx2JpegColorConverter.cs
  25. 18
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.BasicJpegColorConverter.cs
  26. 52
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs
  27. 60
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx2.cs
  28. 20
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs
  29. 51
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs
  30. 53
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs
  31. 24
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs
  32. 53
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs
  33. 34
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs
  34. 38
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs
  35. 30
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs
  36. 32
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs
  37. 26
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs
  38. 15
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs
  39. 35
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs
  40. 42
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs
  41. 50
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs
  42. 24
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs
  43. 88
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector4.cs
  44. 32
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs
  45. 13
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs
  46. 30
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs
  47. 18
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Vector8JpegColorConverter.cs
  48. 47
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs
  49. 32
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs
  50. 114
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs
  51. 22
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs
  52. 59
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs
  53. 10
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
  54. 15
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs
  55. 11
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs
  56. 8
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
  57. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs
  58. 16
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
  59. 11
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  60. 2
      src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs
  61. 29
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  62. 20
      src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
  63. 194
      src/ImageSharp/Formats/Pbm/BinaryDecoder.cs
  64. 208
      src/ImageSharp/Formats/Pbm/BinaryEncoder.cs
  65. 65
      src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs
  66. 26
      src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs
  67. 21
      src/ImageSharp/Formats/Pbm/MetadataExtensions.cs
  68. 26
      src/ImageSharp/Formats/Pbm/PbmColorType.cs
  69. 26
      src/ImageSharp/Formats/Pbm/PbmComponentType.cs
  70. 19
      src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs
  71. 28
      src/ImageSharp/Formats/Pbm/PbmConstants.cs
  72. 79
      src/ImageSharp/Formats/Pbm/PbmDecoder.cs
  73. 195
      src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs
  74. 69
      src/ImageSharp/Formats/Pbm/PbmEncoder.cs
  75. 187
      src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
  76. 21
      src/ImageSharp/Formats/Pbm/PbmEncoding.cs
  77. 37
      src/ImageSharp/Formats/Pbm/PbmFormat.cs
  78. 36
      src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs
  79. 46
      src/ImageSharp/Formats/Pbm/PbmMetadata.cs
  80. 198
      src/ImageSharp/Formats/Pbm/PlainDecoder.cs
  81. 251
      src/ImageSharp/Formats/Pbm/PlainEncoder.cs
  82. 114
      src/ImageSharp/Formats/Png/PngDecoder.cs
  83. 156
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  84. 60
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  85. 20
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  86. 8
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  87. 7
      src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs
  88. 2
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
  89. 2
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs
  90. 12
      src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs
  91. 2
      src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
  92. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs
  93. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs
  94. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs
  95. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs
  96. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs
  97. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs
  98. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs
  99. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs
  100. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs

8
.gitattributes

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

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

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()
{

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

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

@ -907,5 +907,71 @@ 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
/// <summary>
/// Tells whether input value is outside of the given range.
/// </summary>
/// <param name="value">Value.</param>
/// <param name="min">Mininum value, inclusive.</param>
/// <param name="max">Maximum value, inclusive.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsOutOfRange(int value, int min, int max)
=> (uint)(value - min) > (uint)(max - min);
}
}

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

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

114
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,35 +198,39 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// <summary>
/// Initializes a new instance of the <see cref="ComponentValues"/> struct.
/// </summary>
/// <param name="componentProcessors">The 1-4 sized list of component post processors.</param>
/// <param name="row">The row to convert</param>
public ComponentValues(IReadOnlyList<JpegComponentPostProcessor> componentProcessors, int row)
/// <param name="componentBuffers">List of component buffers.</param>
/// <param name="row">Row to convert</param>
public ComponentValues(IReadOnlyList<Buffer2D<float>> componentBuffers, int row)
{
this.ComponentCount = componentProcessors.Count;
DebugGuard.MustBeGreaterThan(componentBuffers.Count, 0, nameof(componentBuffers));
this.ComponentCount = componentBuffers.Count;
this.Component0 = componentProcessors[0].GetColorBufferRowSpan(row);
this.Component0 = componentBuffers[0].DangerousGetRowSpan(row);
// In case of grayscale, Component1 and Component2 point to Component0 memory area
this.Component1 = this.ComponentCount > 1 ? componentProcessors[1].GetColorBufferRowSpan(row) : this.Component0;
this.Component2 = this.ComponentCount > 2 ? componentProcessors[2].GetColorBufferRowSpan(row) : this.Component0;
this.Component3 = this.ComponentCount > 3 ? componentProcessors[3].GetColorBufferRowSpan(row) : Span<float>.Empty;
this.Component1 = this.ComponentCount > 1 ? componentBuffers[1].DangerousGetRowSpan(row) : this.Component0;
this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].DangerousGetRowSpan(row) : this.Component0;
this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].DangerousGetRowSpan(row) : Span<float>.Empty;
}
/// <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>
public ComponentValues(IReadOnlyList<Buffer2D<float>> componentBuffers, int row)
/// <param name="processors">List of component color processors.</param>
/// <param name="row">Row to convert</param>
public ComponentValues(IReadOnlyList<JpegComponentPostProcessor> processors, int row)
{
this.ComponentCount = componentBuffers.Count;
DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors));
this.ComponentCount = processors.Count;
this.Component0 = componentBuffers[0].GetRowSpan(row);
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++)

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

11
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs

@ -19,21 +19,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.Frame = frame;
this.Id = id;
// Validate sampling factors.
if (horizontalFactor == 0 || verticalFactor == 0)
{
JpegThrowHelper.ThrowBadSampling();
}
this.HorizontalSamplingFactor = horizontalFactor;
this.VerticalSamplingFactor = verticalFactor;
this.SamplingFactors = new Size(this.HorizontalSamplingFactor, this.VerticalSamplingFactor);
if (quantizationTableIndex > 3)
{
JpegThrowHelper.ThrowBadQuantizationTableIndex(quantizationTableIndex);
}
this.QuantizationTableIndex = quantizationTableIndex;
this.Index = index;
}

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

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

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

@ -57,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);
}
}

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

@ -14,11 +14,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <inheritdoc/>
/// <remarks>
/// Color decoding scheme:
/// <list type = "bullet|number|table" >
/// <list type = "number" >
/// <listheader>
/// <item>1. Decode spectral data to Jpeg color space</item>
/// <item>2. Convert from Jpeg color space to RGB</item>
/// <item>3. Convert from RGB to target pixel space</item>
/// <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>
@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary>
/// Color converter from jpeg color space to target pixel color space.
/// </summary>
private JpegColorConverter colorConverter;
private JpegColorConverterBase colorConverter;
/// <summary>
/// Intermediate buffer of RGB components used in color conversion.
@ -163,7 +163,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
int y = yy - this.pixelRowCounter;
var values = new JpegColorConverter.ComponentValues(this.componentProcessors, y);
var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y);
this.colorConverter.ConvertToRgbInplace(values);
values = values.Slice(0, width); // slice away Jpeg padding
@ -179,7 +179,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// PackFromRgbPlanes expects the destination to be padded, so try to get padded span containing extra elements from the next row.
// If we can't get such a padded row because we are on a MemoryGroup boundary or at the last row,
// pack pixels to a temporary, padded proxy buffer, then copy the relevant values to the destination row.
if (this.pixelBuffer.TryGetPaddedRowSpan(yy, 3, out Span<TPixel> destRow))
if (this.pixelBuffer.DangerousTryGetPaddedRowSpan(yy, 3, out Span<TPixel> destRow))
{
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.configuration, r, g, b, destRow);
}
@ -187,7 +187,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
Span<TPixel> proxyRow = this.paddedProxyPixelRow.GetSpan();
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.configuration, r, g, b, proxyRow);
proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.GetRowSpan(yy));
proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.DangerousGetRowSpan(yy));
}
}

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

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

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

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

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

@ -1022,10 +1022,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int index = 0;
for (int i = 0; i < componentCount; i++)
{
// 1 byte: component identifier
byte componentId = this.temp[index];
// 1 byte: component sampling factors
byte hv = this.temp[index + 1];
int h = (hv >> 4) & 15;
int v = hv & 15;
// Validate: 1-4 range
if (Numerics.IsOutOfRange(h, 1, 4))
{
JpegThrowHelper.ThrowBadSampling(h);
}
// Validate: 1-4 range
if (Numerics.IsOutOfRange(v, 1, 4))
{
JpegThrowHelper.ThrowBadSampling(v);
}
if (maxH < h)
{
maxH = h;
@ -1036,10 +1052,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
maxV = v;
}
var component = new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, this.temp[index], h, v, this.temp[index + 2], i);
// 1 byte: quantization table destination selector
byte quantTableIndex = this.temp[index + 2];
// Validate: 0-3 range
if (quantTableIndex > 3)
{
JpegThrowHelper.ThrowBadQuantizationTableIndex(quantTableIndex);
}
var component = new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i);
this.Frame.Components[i] = component;
this.Frame.ComponentIds[i] = component.Id;
this.Frame.ComponentIds[i] = componentId;
index += componentBytes;
}

20
src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs

@ -22,23 +22,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage);
/// <summary>
/// Cold path optimization for throwing <see cref="InvalidImageContentException"/>'s.
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference
/// if no inner exception is specified.</param>
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) => throw new InvalidImageContentException(errorMessage, innerException);
/// <summary>
/// Cold path optimization for throwing <see cref="NotImplementedException"/>'s
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotImplementedException(string errorMessage)
=> throw new NotImplementedException(errorMessage);
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}.");
@ -51,6 +34,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadSampling() => throw new InvalidImageContentException("Bad sampling factor.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadSampling(int factor) => throw new InvalidImageContentException($"Bad sampling factor: {factor}");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadProgressiveScan(int ss, int se, int ah, int al) => throw new InvalidImageContentException($"Invalid progressive parameters Ss={ss} Se={se} Ah={ah} Al={al}.");

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

156
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);
this.MergeOrSetExifProfile(metadata, new ExifProfile(exifData), replaceExistingKeys: true);
}
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(metadata, 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);
this.MergeOrSetExifProfile(metadata, new ExifProfile(exifData), replaceExistingKeys: true);
}
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;
}
@ -1390,14 +1451,16 @@ namespace SixLabors.ImageSharp.Formats.Png
PngChunkType type = this.ReadChunkType();
// NOTE: Reading the chunk data is the responsible of the caller
if (type == PngChunkType.Data)
// 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 (this.colorMetadataOnly && type != PngChunkType.Header && type != PngChunkType.Transparency)
{
chunk = new PngChunk(length, type);
return true;
}
long pos = this.currentStream.Position;
chunk = new PngChunk(
length: length,
type: type,
@ -1405,6 +1468,13 @@ namespace SixLabors.ImageSharp.Formats.Png
this.ValidateChunk(chunk);
// Restore the stream position for IDAT chunks, because it will be decoded later and
// was only read to verifying the CRC is correct.
if (type == PngChunkType.Data)
{
this.currentStream.Position = pos;
}
return true;
}

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/>

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

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

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

@ -27,6 +27,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
/// <inheritdoc/>
protected override JpegColorConverter GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(JpegColorSpace.RGB, frame.Precision);
protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, frame.Precision);
}
}

12
src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs

@ -18,9 +18,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
private uint nextIfdOffset;
private const int DirectoryMax = 65534;
// used for sequential read big values (actual for multiframe big files)
// todo: different tags can link to the same data (stream offset) - investigate
private readonly SortedList<uint, Action> lazyLoaders = new SortedList<uint, Action>(new DuplicateKeyComparer<uint>());
private readonly SortedList<uint, Action> lazyLoaders = new(new DuplicateKeyComparer<uint>());
public DirectoryReader(Stream stream) => this.stream = stream;
@ -48,7 +50,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
return ByteOrder.LittleEndian;
}
else if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian)
if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian)
{
return ByteOrder.BigEndian;
}
@ -67,6 +70,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.nextIfdOffset = reader.NextIfdOffset;
readers.Add(reader);
if (readers.Count >= DirectoryMax)
{
TiffThrowHelper.ThrowImageFormatException("TIFF image contains too many directories");
}
}
// Sequential reading big values.

2
src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs

@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.lazyLoaders = lazyLoaders;
}
public List<IExifValue> Values { get; } = new List<IExifValue>();
public List<IExifValue> Values { get; } = new();
public uint NextIfdOffset { get; private set; }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save